From 722970eb12ef100d44b0d24fa1f93a2add34daf3 Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Sat, 3 Aug 2024 01:26:10 -0500 Subject: [PATCH] core: fix various bugs and simplify things --- src/botinterface.cpp | 258 +++++++++++++++++++++++----------------- src/botinterface.hpp | 16 ++- src/common.cpp | 5 + src/common.hpp | 3 +- src/gamestate.cpp | 43 ++----- src/gamestate.hpp | 7 +- src/main.cpp | 1 + src/playerinterface.cpp | 5 +- 8 files changed, 184 insertions(+), 154 deletions(-) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index 9d3317f..ed22273 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -1,5 +1,6 @@ #include "botinterface.hpp" #include "common.hpp" +#include "gamestate.hpp" #include #include #include @@ -15,9 +16,14 @@ AISnake::AISnake() { PlayerDirection AISnake::GetInput(const sf::Vector2f* source) { + // TODO: Figure out why bot is suddenly going rogue sf::Vector2f directionDelta; - if (*source == path.top()) { path.pop(); } - if (path.empty()) { return kUp; } // Snake is trapped + 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) @@ -46,30 +52,158 @@ void AISnake::AdjustProbability(double amount) probabilityBFS += amount; if (probabilityBFS > 1.0) { probabilityBFS = 1.0; } if (probabilityBFS < 0.0) { probabilityBFS = 0.0; } - std::cout << "[Info - AISnake] New BFS probability: " << probabilityBFS << std::endl; + 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 std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize) +void AISnake::GetNewPath(const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize) { // Search for food - /* - BFS(gameBoard, source, boundaries); - if (gameBoard[botPathUnsanitized.top().y][botPathUnsanitized.top().x] != 'X') { - while (!botPathUnsanitized.empty()) { botPathUnsanitized.pop(); } - DFS(gameBoard, source, boundaries); - while (botPathUnsanitized.size() > 15) { botPathUnsanitized.pop(); } - } - */ // Probability-based approach for fun double roll = ((double) GenerateRandomNumber(RAND_MAX)) / ((double) RAND_MAX); - if (roll <= probabilityBFS) { BFS(gameBoard, source, boundaries); } - else { DFS(gameBoard, source, boundaries); } + 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); @@ -79,99 +213,3 @@ void AISnake::GetNewPath(const std::vector< std::vector >& gameBoard, botPathUnsanitized.pop(); } } - -void AISnake::BFS(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) { - std::queue search; - std::vector> visited(boundaries.y, std::vector (boundaries.x, false)); - bool foodFound = false; - search.push(source); - while (!search.empty()) { - sf::Vector2f currentLocation = search.front(); - search.pop(); - if (foodFound) { break; } - if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; } - if (gameBoard.at(currentLocation.y).at(currentLocation.x).m_bFood) { - foodFound = true; - } - 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 (auto i : localLocations) { - try { - if (gameBoard.at(i.y).at(i.x).m_bFood) { - botPathUnsanitized.push(i); - foodFound = true; - } - } catch (const std::out_of_range& error) { - continue; // Out of bounds - } - } - for (sf::Vector2f newLocation : localLocations) { - try { - if ((!visited.at(newLocation.y).at(newLocation.x)) - && (gameBoard.at(newLocation.y).at(newLocation.x).m_bFood) - && (gameBoard.at(newLocation.y).at(newLocation.x).m_bSnake)) { - search.push(newLocation); - } - } catch (const std::out_of_range& error) { - continue; // Out of bounds - } - } - visited.at(currentLocation.y).at(currentLocation.x) = true; - } -} - -void AISnake::DFS(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) { - std::stack search; - std::vector> visited(boundaries.y, std::vector (boundaries.x, false)); - bool foodFound = false; - search.push(source); - while (!search.empty()) { - sf::Vector2f currentLocation = search.top(); - search.pop(); - if (foodFound) { break; } - if (visited[currentLocation.y][currentLocation.x]) { continue; } - if (gameBoard[currentLocation.y][currentLocation.x].m_bFood) { - foodFound = true; - } - 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 (auto i : localLocations) { - try { - if (gameBoard.at(i.y).at(i.x).m_bFood) { - botPathUnsanitized.push(i); - foodFound = true; - } - } catch (const std::out_of_range& error) { - continue; // Out of bounds - } - } - for (sf::Vector2f newLocation : localLocations) { - try { - if (newLocation.x < 1 || newLocation.y < 1 - || newLocation.x > boundaries.x - 2 - || newLocation.y > boundaries.y - 2) { - continue; - - } - if ((!visited.at(newLocation.y).at(newLocation.x)) - && !(gameBoard.at(newLocation.y).at(newLocation.x).m_bFood) - && !(gameBoard.at(newLocation.y).at(newLocation.x).m_bSnake)) { - search.push(newLocation); - } - } catch (const std::out_of_range& error) { - continue; // Out of bounds - } - } - visited.at(currentLocation.y).at(currentLocation.x) = true; - } -} diff --git a/src/botinterface.hpp b/src/botinterface.hpp index 1f60659..b1615cf 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -3,22 +3,30 @@ #include "common.hpp" #include -#include #include class AISnake { public: std::stack path; AISnake(); - void GetNewPath(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize); + void GetNewPath(const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize); PlayerDirection GetInput(const sf::Vector2f* source); void UpdateProbability(int snakeSize); void AdjustProbability(double amount); + void AddIteration(const int size); + void ResetPath(void); + int amountPlayed = 0; private: + int totalLength = 0; + double average = 0; double probabilityBFS = 0.500; std::stack botPathUnsanitized; - void BFS(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries); - void DFS(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries); + void BFS(const sf::Vector2f& source, const sf::Vector2f& boundaries); + void DFS(const sf::Vector2f& source, const sf::Vector2f& boundaries); + sf::Vector2f GetAnyOpenPath(const sf::Vector2f& source); + void UnvisitBoard(void); + void UpdateAverage(const int size); + void TrimPath(void); }; #endif diff --git a/src/common.cpp b/src/common.cpp index 3ff09ed..975b28a 100755 --- a/src/common.cpp +++ b/src/common.cpp @@ -18,6 +18,11 @@ int GenerateRandomNumber(int generationLimit) } GameSpace::GameSpace(void) { + Reset(); +} + +void GameSpace::Reset(void) { m_bFood = 0; m_bSnake = 0; + m_bVisited = 0; } diff --git a/src/common.hpp b/src/common.hpp index 61be1d0..dc133a9 100755 --- a/src/common.hpp +++ b/src/common.hpp @@ -17,12 +17,13 @@ struct GameSpace { GameSpace(); unsigned char m_bFood : 1 = 0; unsigned char m_bSnake : 1 = 0; - unsigned char _2 : 1 = 0; + unsigned char m_bVisited : 1 = 0; // Used for BFS/DFS unsigned char _3 : 1 = 0; unsigned char _4 : 1 = 0; unsigned char _5 : 1 = 0; unsigned char _6 : 1 = 0; unsigned char _7 : 1 = 0; + void Reset(void); }; #endif diff --git a/src/gamestate.cpp b/src/gamestate.cpp index 311390e..35659a8 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -1,5 +1,4 @@ // GameState.cpp -#include #include #include #include "botinterface.hpp" @@ -24,7 +23,8 @@ void GameEngine::Start() void GameEngine::Reset() { - AddIteration(); + graphics.CheckContinue(state.m_bIsBotControlled); + bot.AddIteration(player.body.size()); player.Reset(); if (state.m_bIsBotControlled) { while (!bot.path.empty()) { bot.path.pop(); } } PrepareGameBoard(); @@ -32,25 +32,10 @@ void GameEngine::Reset() if (state.m_bNoDisplay) graphics.SetShowGame(false); else - graphics.SetShowGame((amountPlayed + 1) % 50 == 0); + graphics.SetShowGame((bot.amountPlayed + 1) % 50 == 0); return; } -void GameEngine::AddIteration(void) -{ - graphics.CheckContinue(state.m_bIsBotControlled); - if (player.body.size() > 40) - { - UpdateAverage(); - double adjustmentAmount = 0.002; - if (average > player.body.size()) { bot.AdjustProbability(adjustmentAmount); } - else { bot.AdjustProbability(-adjustmentAmount); } - } - std::cout << "[Info - GameEngine] Current average: " << average << std::endl; - std::cout << "[Info - GameEngine] Previous iteration size: " << player.body.size() << std::endl; - -} - void GameEngine::Loop(void) { int currentScore = 0; @@ -61,7 +46,6 @@ void GameEngine::Loop(void) PlaceNewSnakePart(MovePlayer()); RegenerateFood(); currentScore = player.body.size() * 100; - //bot.UpdateProbability(player.body.size()); if (!state.m_bNoDisplay) graphics.DisplayGameState(gameBoard, currentScore); } @@ -91,8 +75,11 @@ void GameEngine::PlaceNewSnakePart(sf::Vector2f location) { player.headLocation = location; if (playerFood.location != location) player.Pop(); - else + else { locationState->m_bFood = false; + if (state.m_bIsBotControlled) + bot.ResetPath(); + } } catch (const std::out_of_range& error) { state.m_bIsGameOver = true; // Snake ran into edge } @@ -104,12 +91,12 @@ void GameEngine::PlaceNewSnakePart(sf::Vector2f location) { void GameEngine::RegenerateFood() { // Generate a new food location if the current one is occupied - while (gameBoard[playerFood.location.y][playerFood.location.x].m_bSnake) { + while (gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bSnake) { playerFood.GenerateNewFood(GetGameBoundaries()); } // Update the game board with the new food location - gameBoard[playerFood.location.y][playerFood.location.x].m_bFood = 1; + gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bFood = 1; } @@ -122,13 +109,13 @@ void GameEngine::PrepareGameBoard(void) player.headLocation.x = GenerateRandomNumber(boardDimensions.x); player.headLocation.y = GenerateRandomNumber(boardDimensions.y); { - GameSpace* locationState = &gameBoard[player.headLocation.y][player.headLocation.x]; + GameSpace* locationState = &gameBoard.at(player.headLocation.y).at(player.headLocation.x); player.body.push(locationState); locationState->m_bSnake = true; } // Food setup playerFood.GenerateNewFood(boardDimensions); - gameBoard[playerFood.location.y][playerFood.location.x].m_bFood = true; + gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bFood = true; return; } @@ -137,7 +124,7 @@ void GameEngine::UpdatePlayerSpeed(void) PlayerDirection controller; if (state.m_bIsBotControlled) { if (bot.path.empty()) { - bot.GetNewPath(gameBoard, player.headLocation, GetGameBoundaries(), player.body.size()); + bot.GetNewPath(player.headLocation, GetGameBoundaries(), player.body.size()); } controller = bot.GetInput(&player.headLocation); } @@ -168,9 +155,3 @@ void GameEngine::UpdatePlayerSpeed(void) } return; } - -void GameEngine::UpdateAverage() { - totalLength += player.body.size(); - amountPlayed += 1; - average = (double)totalLength / amountPlayed; -} diff --git a/src/gamestate.hpp b/src/gamestate.hpp index 297b57c..e284b5d 100755 --- a/src/gamestate.hpp +++ b/src/gamestate.hpp @@ -16,7 +16,6 @@ public: GameEngine(); void Start(void); void Reset(void); - void AddIteration(void); sf::Vector2f GetGameBoundaries(void); struct GameState { unsigned char m_bIsGameOver : 1 = 0; @@ -28,8 +27,8 @@ public: unsigned char _6 : 1 = 0; unsigned char _7 : 1 = 0; } state; -private: std::vector< std::vector > gameBoard; +private: PlayerOutput graphics; Snake player; Food playerFood; @@ -40,10 +39,6 @@ private: void RegenerateFood(void); void PrepareGameBoard(void); void UpdatePlayerSpeed(); - void UpdateAverage(); - int totalLength = 0; - int amountPlayed = 0; - double average = 0; }; inline std::unique_ptr g_pEngine; diff --git a/src/main.cpp b/src/main.cpp index 8c591b7..4b8b439 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ int main(int argc, char* argv[]) for (auto it = args.begin(); it != args.end(); it++) { if (it->compare("--no-gui") == 0) { g_pEngine->state.m_bNoDisplay = true; + std::cout << "[LOG - Main] Disabling display" << std::endl; } else { std::cerr << "[ERROR] Argument option not found, exiting..." << std::endl; return 1; diff --git a/src/playerinterface.cpp b/src/playerinterface.cpp index 2a7ff00..d7d1723 100755 --- a/src/playerinterface.cpp +++ b/src/playerinterface.cpp @@ -1,6 +1,7 @@ #include "playerinterface.hpp" #include #include +#include PlayerDirection GetPlayerInput(void) { @@ -92,9 +93,9 @@ void PlayerOutput::DisplayGameState(std::vector< std::vector >& gameB { for (float x = 0; x < gameBoundaries.x; x++) { - if (gameBoard[y][x].m_bSnake) + if (gameBoard.at(y).at(x).m_bSnake) DrawSnake(sf::Vector2f(x, y)); - else if (gameBoard[y][x].m_bFood) + else if (gameBoard.at(y).at(x).m_bFood) DrawFood(sf::Vector2f(x,y)); else DrawEmpty(sf::Vector2f(x,y));