#include "botinterface.hpp" #include "common.hpp" #include "gamestate.hpp" #include #include #include #include #include #include PlayerDirection lastKnownDirection = kNone; AISnake::AISnake() { ; } PlayerDirection AISnake::GetInput(const sf::Vector2f* source) { sf::Vector2f directionDelta; if (!source) return kUp; while (*source == path.top() && !path.empty()) { path.pop(); } if (path.empty()) { path.push(GetAnyOpenPath(*source)); } directionDelta = *source - path.top(); path.pop(); if ((directionDelta.y == 1) && (lastKnownDirection != kDown)) { lastKnownDirection = kUp; } else if ((directionDelta.y == -1) && (lastKnownDirection != kUp)) { lastKnownDirection = kDown; } else if ((directionDelta.x == 1) && (lastKnownDirection != kRight)) { lastKnownDirection = kLeft; } else if ((directionDelta.x == -1) && (lastKnownDirection != kLeft)) { lastKnownDirection = kRight; } return lastKnownDirection; } void AISnake::UpdateProbability(int snakeSize) { probabilityBFS = 1 - ((double) snakeSize) / 1000; return; } void AISnake::AdjustProbability(double amount) { probabilityBFS += amount; if (probabilityBFS > 1.0) { probabilityBFS = 1.0; } if (probabilityBFS < 0.0) { probabilityBFS = 0.0; } return; } void AISnake::AddIteration(const int size) { if (size > 40) { UpdateAverage(size); double adjustmentAmount = 0.002; if (average > size) { AdjustProbability(adjustmentAmount); } else { AdjustProbability(-adjustmentAmount); } } std::cout << "[LOG - AI] Current average: " << average << std::endl; std::cout << "[LOG - AI] Previous iteration size: " << size << std::endl; } void AISnake::ResetPath(void) { while (!path.empty()) { path.pop(); } } // Gets a new path for the bot to follow // Uses DFS algorithm void AISnake::GetNewPath(const sf::Vector2f& source) { // Search for food // Probability-based approach for fun double roll = ((double) GenerateRandomNumber(RAND_MAX)) / ((double) RAND_MAX); if (roll <= probabilityBFS) { BFS(source); } else { DFS(source); } UnvisitBoard(); if (pathFailed) { pathFailed = false; EmptyPath(); path.push(GetAnyOpenPath(source)); } else { TrimPath(); if (path.empty()) path.push(GetAnyOpenPath(source)); } } void AISnake::BFS(const sf::Vector2f& source) { std::queue search; search.push(source); while (!search.empty()) { sf::Vector2f currentLocation = search.front(); search.pop(); if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited) continue; botPathUnsanitized.push(currentLocation); std::array localLocations; localLocations.fill(currentLocation); localLocations[0].y += 1; localLocations[1].x += 1; localLocations[2].y -= 1; localLocations[3].x -= 1; for (sf::Vector2f nearby : localLocations) { try { GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); if (space->m_bFood) { botPathUnsanitized.push(nearby); return; } if (nearby.x < 1 || nearby.y < 1) continue; if (space->m_bVisited) continue; if (space->m_bSnake) continue; search.push(nearby); } catch (const std::out_of_range& error) { continue; // Out of bounds } } g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true; } pathFailed = true; } void AISnake::DFS(const sf::Vector2f& source) { std::stack search; search.push(source); while (!search.empty()) { sf::Vector2f currentLocation = search.top(); search.pop(); if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited) continue; botPathUnsanitized.push(currentLocation); std::array localLocations; localLocations.fill(currentLocation); localLocations.at(0).y += 1; localLocations.at(1).x += 1; localLocations.at(2).y -= 1; localLocations.at(3).x -= 1; for (sf::Vector2f nearby : localLocations) { try { GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); if (space->m_bFood) { botPathUnsanitized.push(nearby); return; } if (nearby.x < 1 || nearby.x > g_pEngine->GetGameBoundaries().x - 2) continue; if (space->m_bVisited) continue; if (space->m_bSnake) continue; search.push(nearby); } catch (const std::out_of_range& error) { continue; // Out of bounds } } g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true; } pathFailed = true; } sf::Vector2f AISnake::GetAnyOpenPath(const sf::Vector2f& source) { sf::Vector2f bail; std::array paths; paths.fill(source); paths[0].x -= 1; paths[1].x += 1; paths[2].y -= 1; paths[3].y += 1; for (auto path : paths) { try { bail = path; if (g_pEngine->gameBoard.at(path.y).at(path.x).m_bSnake) continue; return path; } catch (const std::out_of_range& error) { continue; // Out of bounds } } return bail; // Snake is trapped, give up and die } void AISnake::UnvisitBoard(void) { for (std::vector& i : g_pEngine->gameBoard) for (GameSpace& j : i) j.m_bVisited = false; } void AISnake::UpdateAverage(const int size) { totalLength += size; amountPlayed += 1; average = (double)totalLength / amountPlayed; } void AISnake::TrimPath(void) { bool reachedSnake = false; path.push(botPathUnsanitized.top()); // Push food location while (!botPathUnsanitized.empty()) { if (!reachedSnake) { sf::Vector2f location = botPathUnsanitized.top(); if (g_pEngine->gameBoard[location.y][location.x].m_bSnake) reachedSnake = true; sf::Vector2f deltaVector = location - path.top(); int delta = abs(deltaVector.x) + abs(deltaVector.y); if (delta == 1) path.push(location); } botPathUnsanitized.pop(); } } void AISnake::EmptyPath(void) { while (!botPathUnsanitized.empty()) botPathUnsanitized.pop(); }