#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) { // TODO: Figure out why bot is suddenly going rogue sf::Vector2f directionDelta; if (!source) { std::cout << "[ERROR - AI] Source was borked, bailing" << std::endl; 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; } std::cout << "[Info - AI] New BFS probability: " << probabilityBFS << std::endl; 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 << "[Info - AI] Current average: " << average << std::endl; std::cout << "[Info - 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, const sf::Vector2f& boundaries, const int snakeSize) { // Search for food // Probability-based approach for fun double roll = ((double) GenerateRandomNumber(RAND_MAX)) / ((double) RAND_MAX); if (roll <= probabilityBFS) { BFS(source, boundaries); } else { DFS(source, boundaries); } UnvisitBoard(); // Create path for food path.push(botPathUnsanitized.top()); botPathUnsanitized.pop(); TrimPath(); } void AISnake::BFS(const sf::Vector2f& source, const sf::Vector2f& boundaries) { std::queue search; bool foodFound = false; search.push(source); while (!search.empty()) { sf::Vector2f currentLocation = search.front(); search.pop(); if (foodFound) { break; } 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 { if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bFood) { botPathUnsanitized.push(nearby); foodFound = true; break; } if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bVisited) continue; if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).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; } } void AISnake::DFS(const sf::Vector2f& source, const sf::Vector2f& boundaries) { std::stack search; bool foodFound = false; search.push(source); while (!search.empty()) { sf::Vector2f currentLocation = search.top(); search.pop(); if (foodFound) { break; } if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited) { continue; } if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bFood) { foodFound = true; } 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 { if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bFood) { botPathUnsanitized.push(nearby); foodFound = true; std::cout << "[TRACE - AI] Found food, breaking..." << std::endl; break; } if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bVisited) continue; if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).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; } } sf::Vector2f AISnake::GetAnyOpenPath(const sf::Vector2f& source) { sf::Vector2f bail; sf::Vector2f paths[4]; paths[0] = source; paths[0].x -= 1; paths[1] = source; paths[1].x += 1; paths[2] = source; paths[2].y -= 1; paths[3] = source; paths[3].y += 1; for (auto path : paths) { try { if (g_pEngine->gameBoard.at(path.y).at(path.x).m_bSnake) { bail = path; 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 (auto i : g_pEngine->gameBoard) for (auto j : i) j.m_bVisited = false; } void AISnake::UpdateAverage(const int size) { totalLength += size; amountPlayed += 1; average = (double)totalLength / amountPlayed; } void AISnake::TrimPath(void) { while (!botPathUnsanitized.empty()) { sf::Vector2f deltaVector = botPathUnsanitized.top() - path.top(); int delta = abs(deltaVector.x) + abs(deltaVector.y); if (delta == 1) { path.push(botPathUnsanitized.top()); } botPathUnsanitized.pop(); } }