From e5698ec96b48287abdc7b25397fb3097f8394bfb Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:38:26 -0500 Subject: [PATCH 01/12] cmake: update standard to C++23 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f260804..8b4ab0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project( snakeplusplus LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 11 CACHE STRING "The C++ standard to use") +set(CMAKE_CXX_STANDARD 23 CACHE STRING "The C++ standard to use") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) -- 2.45.2 From d5c797d4605aace398d67fcf8ad99c34789efc42 Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:39:35 -0500 Subject: [PATCH 02/12] git: update gitignore for clangd stuff --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 99352dd..9a179cb 100755 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ build # Extras .vs* +.cache -- 2.45.2 From 0ff3ef3f62b41c206c7a76b4714731ca778e9926 Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:45:07 -0500 Subject: [PATCH 03/12] refactor: remove all code from being in namespace --- src/botinterface.cpp | 321 ++++++++++++++++++++-------------------- src/botinterface.hpp | 31 ++-- src/common.cpp | 27 ++-- src/common.hpp | 24 ++- src/gamestate.cpp | 318 ++++++++++++++++++++------------------- src/gamestate.hpp | 62 ++++---- src/main.cpp | 2 +- src/playerinterface.cpp | 317 ++++++++++++++++++++------------------- src/playerinterface.hpp | 53 ++++--- src/snake.cpp | 43 +++--- src/snake.hpp | 33 ++--- 11 files changed, 601 insertions(+), 630 deletions(-) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index 15bfbba..d72b498 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -7,172 +7,169 @@ #include #include -namespace snakeplusplus +PlayerDirection lastKnownDirection = kNone; + +AISnake::AISnake() { + ; +} + +PlayerDirection AISnake::GetInput(const sf::Vector2f* source) { - PlayerDirection lastKnownDirection = kNone; + sf::Vector2f directionDelta; + if (*source == path.top()) { path.pop(); } + if (path.empty()) { return kUp; } // Snake is trapped + 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; +} - AISnake::AISnake() { - ; +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 - AISnake] New BFS probability: " << probabilityBFS << std::endl; + return; +} + +// 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) +{ + // 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(); } } - - PlayerDirection AISnake::GetInput(const sf::Vector2f* source) - { - sf::Vector2f directionDelta; - if (*source == path.top()) { path.pop(); } - if (path.empty()) { return kUp; } // Snake is trapped - 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 - AISnake] New BFS probability: " << probabilityBFS << std::endl; - return; - } - - // 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) - { - // 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); } + // Create path for food + path.push(botPathUnsanitized.top()); + botPathUnsanitized.pop(); + 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()); } - */ - // 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); } - // Create path for food - path.push(botPathUnsanitized.top()); botPathUnsanitized.pop(); - 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(); - } - } - - 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) == 'X') { - 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) == 'X') { - 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) == ' ')) { - 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.at(currentLocation.y).at(currentLocation.x)) { continue; } - if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') { - 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) == 'X') { - 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) == ' ')) { - search.push(newLocation); - } - } catch (const std::out_of_range& error) { - continue; // Out of bounds - } - } - visited.at(currentLocation.y).at(currentLocation.x) = true; - } + } +} + +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) == 'X') { + 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) == 'X') { + 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) == ' ')) { + 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.at(currentLocation.y).at(currentLocation.x)) { continue; } + if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') { + 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) == 'X') { + 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) == ' ')) { + 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 7bf29b7..bfe68fe 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -6,22 +6,19 @@ #include #include -namespace snakeplusplus -{ - 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); - PlayerDirection GetInput(const sf::Vector2f* source); - void UpdateProbability(int snakeSize); - void AdjustProbability(double amount); - private: - 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); - }; -} +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); + PlayerDirection GetInput(const sf::Vector2f* source); + void UpdateProbability(int snakeSize); + void AdjustProbability(double amount); +private: + 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); +}; #endif diff --git a/src/common.cpp b/src/common.cpp index 62088ee..7a83edd 100755 --- a/src/common.cpp +++ b/src/common.cpp @@ -2,20 +2,17 @@ #include #include "common.hpp" -namespace snakeplusplus +std::default_random_engine generator; +void InitializeGenerator(void) { - std::default_random_engine generator; - void InitializeGenerator(void) - { - generator.seed(std::random_device{}()); - } - - // Returns a newly generated number - int GenerateRandomNumber(int generationLimit) - { - int generatedNumber; - std::uniform_int_distribution<> distribution(0, generationLimit - 1); - generatedNumber = distribution(snakeplusplus::generator); - return generatedNumber; - } + generator.seed(std::random_device{}()); +} + +// Returns a newly generated number +int GenerateRandomNumber(int generationLimit) +{ + int generatedNumber; + std::uniform_int_distribution<> distribution(0, generationLimit - 1); + generatedNumber = distribution(generator); + return generatedNumber; } diff --git a/src/common.hpp b/src/common.hpp index 82f60e2..d11a937 100755 --- a/src/common.hpp +++ b/src/common.hpp @@ -1,20 +1,16 @@ #ifndef COMMON_HPP #define COMMON_HPP -namespace snakeplusplus +void InitializeGenerator(void); +int GenerateRandomNumber(int generationLimit); + +enum PlayerDirection { - void InitializeGenerator(void); - int GenerateRandomNumber(int generationLimit); - - enum PlayerDirection - { - kNone = 0, - kLeft = 1, - kUp = 2, - kDown = 3, - kRight = 4 - }; - -} + kNone = 0, + kLeft = 1, + kUp = 2, + kDown = 3, + kRight = 4 +}; #endif diff --git a/src/gamestate.cpp b/src/gamestate.cpp index 98840d0..cc95091 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -7,165 +7,163 @@ #include "playerinterface.hpp" #include "gamestate.hpp" -namespace snakeplusplus +GameEngine::GameEngine() { - GameEngine::GameEngine() - { - InitializeGenerator(); - return; - } - - void GameEngine::Start() - { - PrepareGameBoard(); - graphics.StartGameWindow(); - Loop(); - return; - } - - void GameEngine::Reset() - { - AddIteration(); - player.Reset(); - if (isBotControlled) { while (!bot.path.empty()) { bot.path.pop(); } } - PrepareGameBoard(); - isGameOver = false; - graphics.SetShowGame((amountPlayed + 1) % 50 == 0); - return; - } - - void GameEngine::AddIteration(void) - { - graphics.CheckContinue(isBotControlled); - 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; - while (graphics.IsOpen()) - { - if (isGameOver) { Reset(); } - UpdatePlayerSpeed(); - PlaceNewSnakePart(MovePlayer()); - RegenerateFood(); - currentScore = player.body.size() * 100; - //bot.UpdateProbability(player.body.size()); - graphics.DisplayGameState(gameBoard, currentScore); - } - return; - } - - sf::Vector2f GameEngine::MovePlayer(void) - { - return sf::Vector2f(player.headLocation.x + player.speed.x, player.headLocation.y + player.speed.y); - } - - - sf::Vector2f GameEngine::GetGameBoundaries(void) - { - return graphics.gameBoundaries; - } - - void GameEngine::PlaceNewSnakePart(sf::Vector2f location) { - if (!player.speed.x && !player.speed.y) { return; } - try { - char* locationState = &gameBoard.at(location.y).at(location.x); - if (*locationState == 'O' && (player.body.size() > 1)) { - isGameOver = true; // Game should end (Snake touching snake) - } - *locationState = 'O'; - player.body.push(locationState); - player.headLocation = location; - if (playerFood.location != location) - player.Pop(); - } catch (const std::out_of_range& error) { - isGameOver = true; // Snake ran into edge - } - return; - } - - - // Generates new food until not colliding with player - void GameEngine::RegenerateFood() - { - // Generate a new food location if the current one is occupied - while (gameBoard.at(playerFood.location.y).at(playerFood.location.x) == 'O') { - playerFood.GenerateNewFood(GetGameBoundaries()); - } - - // Update the game board with the new food location - gameBoard.at(playerFood.location.y).at(playerFood.location.x) = 'X'; - } - - - void GameEngine::PrepareGameBoard(void) - { - gameBoard.clear(); - sf::Vector2f boardDimensions = GetGameBoundaries(); - gameBoard.resize(boardDimensions.y, std::vector (boardDimensions.x, ' ')); - // Snake setup - player.headLocation.x = GenerateRandomNumber(boardDimensions.x); - player.headLocation.y = GenerateRandomNumber(boardDimensions.y); - { - char* locationState = &gameBoard.at(player.headLocation.y).at(player.headLocation.x); - player.body.push(locationState); - *locationState = 'O'; - } - // Food setup - playerFood.GenerateNewFood(boardDimensions); - gameBoard.at(playerFood.location.y).at(playerFood.location.x) = 'X'; - return; - } - - void GameEngine::UpdatePlayerSpeed(void) - { - PlayerDirection controller; - if (isBotControlled) { - if (bot.path.empty()) { - bot.GetNewPath(gameBoard, player.headLocation, GetGameBoundaries(), player.body.size()); - } - controller = bot.GetInput(&player.headLocation); - } - else { controller = GetPlayerInput(); } - switch (controller) { - case kUp: - if (player.speed.y == kUnitSpeed) { break; } - player.speed.x = 0; - player.speed.y = -kUnitSpeed; - break; - case kLeft: - if (player.speed.x == kUnitSpeed) { break; } - player.speed.x = -kUnitSpeed; - player.speed.y = 0; - break; - case kRight: - if (player.speed.x == -kUnitSpeed) { break; } - player.speed.x = kUnitSpeed; - player.speed.y = 0; - break; - case kDown: - if (player.speed.y == -kUnitSpeed) { break; } - player.speed.x = 0; - player.speed.y = kUnitSpeed; - break; - default: - break; - } - return; - } - void GameEngine::UpdateAverage() { - totalLength += player.body.size(); - amountPlayed += 1; - average = (double)totalLength / amountPlayed; - } + InitializeGenerator(); + return; +} + +void GameEngine::Start() +{ + PrepareGameBoard(); + graphics.StartGameWindow(); + Loop(); + return; +} + +void GameEngine::Reset() +{ + AddIteration(); + player.Reset(); + if (isBotControlled) { while (!bot.path.empty()) { bot.path.pop(); } } + PrepareGameBoard(); + isGameOver = false; + graphics.SetShowGame((amountPlayed + 1) % 50 == 0); + return; +} + +void GameEngine::AddIteration(void) +{ + graphics.CheckContinue(isBotControlled); + 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; + while (graphics.IsOpen()) + { + if (isGameOver) { Reset(); } + UpdatePlayerSpeed(); + PlaceNewSnakePart(MovePlayer()); + RegenerateFood(); + currentScore = player.body.size() * 100; + //bot.UpdateProbability(player.body.size()); + graphics.DisplayGameState(gameBoard, currentScore); + } + return; +} + +sf::Vector2f GameEngine::MovePlayer(void) +{ + return sf::Vector2f(player.headLocation.x + player.speed.x, player.headLocation.y + player.speed.y); +} + + +sf::Vector2f GameEngine::GetGameBoundaries(void) +{ + return graphics.gameBoundaries; +} + +void GameEngine::PlaceNewSnakePart(sf::Vector2f location) { + if (!player.speed.x && !player.speed.y) { return; } + try { + char* locationState = &gameBoard.at(location.y).at(location.x); + if (*locationState == 'O' && (player.body.size() > 1)) { + isGameOver = true; // Game should end (Snake touching snake) + } + *locationState = 'O'; + player.body.push(locationState); + player.headLocation = location; + if (playerFood.location != location) + player.Pop(); + } catch (const std::out_of_range& error) { + isGameOver = true; // Snake ran into edge + } + return; +} + + +// Generates new food until not colliding with player +void GameEngine::RegenerateFood() +{ + // Generate a new food location if the current one is occupied + while (gameBoard.at(playerFood.location.y).at(playerFood.location.x) == 'O') { + playerFood.GenerateNewFood(GetGameBoundaries()); + } + + // Update the game board with the new food location + gameBoard.at(playerFood.location.y).at(playerFood.location.x) = 'X'; +} + + +void GameEngine::PrepareGameBoard(void) +{ + gameBoard.clear(); + sf::Vector2f boardDimensions = GetGameBoundaries(); + gameBoard.resize(boardDimensions.y, std::vector (boardDimensions.x, ' ')); + // Snake setup + player.headLocation.x = GenerateRandomNumber(boardDimensions.x); + player.headLocation.y = GenerateRandomNumber(boardDimensions.y); + { + char* locationState = &gameBoard.at(player.headLocation.y).at(player.headLocation.x); + player.body.push(locationState); + *locationState = 'O'; + } + // Food setup + playerFood.GenerateNewFood(boardDimensions); + gameBoard.at(playerFood.location.y).at(playerFood.location.x) = 'X'; + return; +} + +void GameEngine::UpdatePlayerSpeed(void) +{ + PlayerDirection controller; + if (isBotControlled) { + if (bot.path.empty()) { + bot.GetNewPath(gameBoard, player.headLocation, GetGameBoundaries(), player.body.size()); + } + controller = bot.GetInput(&player.headLocation); + } + else { controller = GetPlayerInput(); } + switch (controller) { + case kUp: + if (player.speed.y == kUnitSpeed) { break; } + player.speed.x = 0; + player.speed.y = -kUnitSpeed; + break; + case kLeft: + if (player.speed.x == kUnitSpeed) { break; } + player.speed.x = -kUnitSpeed; + player.speed.y = 0; + break; + case kRight: + if (player.speed.x == -kUnitSpeed) { break; } + player.speed.x = kUnitSpeed; + player.speed.y = 0; + break; + case kDown: + if (player.speed.y == -kUnitSpeed) { break; } + player.speed.x = 0; + player.speed.y = kUnitSpeed; + break; + default: + break; + } + 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 e71949d..cbee036 100755 --- a/src/gamestate.hpp +++ b/src/gamestate.hpp @@ -3,42 +3,40 @@ #define GAMESTATE_HPP #include +#include #include "botinterface.hpp" #include "snake.hpp" #include "playerinterface.hpp" -namespace snakeplusplus -{ - const int kUnitSpeed = 1; +const int kUnitSpeed = 1; - class GameEngine - { - public: - GameEngine(); - void Start(void); - void Reset(void); - void AddIteration(void); - sf::Vector2f GetGameBoundaries(void); - private: - std::vector< std::vector > gameBoard; - PlayerOutput graphics; - Snake player; - Food playerFood; - AISnake bot; - bool isGameOver = 0; - bool isBotControlled = 1; - void DisplayEndScreen(void); - void Loop(void); - sf::Vector2f MovePlayer(void); - void PlaceNewSnakePart(sf::Vector2f location); - void RegenerateFood(void); - void PrepareGameBoard(void); - void UpdatePlayerSpeed(); - void UpdateAverage(); - int totalLength = 0; - int amountPlayed = 0; - double average = 0; - }; -} +class GameEngine +{ +public: + GameEngine(); + void Start(void); + void Reset(void); + void AddIteration(void); + sf::Vector2f GetGameBoundaries(void); +private: + std::vector< std::vector > gameBoard; + PlayerOutput graphics; + Snake player; + Food playerFood; + AISnake bot; + bool isGameOver = 0; + bool isBotControlled = 1; + void DisplayEndScreen(void); + void Loop(void); + sf::Vector2f MovePlayer(void); + void PlaceNewSnakePart(sf::Vector2f location); + void RegenerateFood(void); + void PrepareGameBoard(void); + void UpdatePlayerSpeed(); + void UpdateAverage(); + int totalLength = 0; + int amountPlayed = 0; + double average = 0; +}; #endif diff --git a/src/main.cpp b/src/main.cpp index 2d529fb..a26a74d 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,7 @@ int main(void) { - snakeplusplus::GameEngine game; + GameEngine game; game.Start(); return 0; } diff --git a/src/playerinterface.cpp b/src/playerinterface.cpp index fe4a5f4..7bbbadf 100755 --- a/src/playerinterface.cpp +++ b/src/playerinterface.cpp @@ -2,173 +2,170 @@ #include #include -namespace snakeplusplus +PlayerDirection GetPlayerInput(void) { - PlayerDirection GetPlayerInput(void) - { - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) - || sf::Keyboard::isKeyPressed(sf::Keyboard::A)) - return kLeft; - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) - || sf::Keyboard::isKeyPressed(sf::Keyboard::W)) - return kUp; - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) - || sf::Keyboard::isKeyPressed(sf::Keyboard::S)) - return kDown; - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) - || sf::Keyboard::isKeyPressed(sf::Keyboard::D)) - return kRight; - return kNone; - } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) + || sf::Keyboard::isKeyPressed(sf::Keyboard::A)) + return kLeft; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) + || sf::Keyboard::isKeyPressed(sf::Keyboard::W)) + return kUp; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) + || sf::Keyboard::isKeyPressed(sf::Keyboard::S)) + return kDown; + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) + || sf::Keyboard::isKeyPressed(sf::Keyboard::D)) + return kRight; + return kNone; +} - bool PlayerOutput::IsOpen(void) - { - return gameWindow.isOpen(); - } +bool PlayerOutput::IsOpen(void) +{ + return gameWindow.isOpen(); +} - PlayerOutput::PlayerOutput(void) - { - float kWidth = 1025; - float kHeight = 725; - float kBoardWidth = kWidth / kGridSize; - float kBoardHeight = kHeight / kGridSize; - gameBoundaries = sf::Vector2f(kBoardWidth, kBoardHeight); - gameVideoSettings = sf::VideoMode(kWidth, kHeight); - drawObject.setSize(sf::Vector2f(kGridSize, kGridSize)); - return; - } +PlayerOutput::PlayerOutput(void) +{ + float kWidth = 1025; + float kHeight = 725; + float kBoardWidth = kWidth / kGridSize; + float kBoardHeight = kHeight / kGridSize; + gameBoundaries = sf::Vector2f(kBoardWidth, kBoardHeight); + gameVideoSettings = sf::VideoMode(kWidth, kHeight); + drawObject.setSize(sf::Vector2f(kGridSize, kGridSize)); + return; +} - void PlayerOutput::CheckContinue(bool isBotControlled) +void PlayerOutput::CheckContinue(bool isBotControlled) +{ + if (isBotControlled) { return; } + DisplayEndScreen(); + while (true) { - if (isBotControlled) { return; } - DisplayEndScreen(); - while (true) + gameWindow.pollEvent(event); + if ((event.type == sf::Event::Closed) + || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) { - gameWindow.pollEvent(event); - if ((event.type == sf::Event::Closed) - || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) - { - gameWindow.close(); - return; - } - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Enter)) { return; } - sf::sleep(delay); + gameWindow.close(); + return; } - } - - void PlayerOutput::DisplayEndScreen(void) - { - gameWindow.clear(); - sf::Vector2f textPosition(gameBoundaries); - textPosition.x = textPosition.x / 2; - textPosition.y = textPosition.y / 2; - sf::Font font; - font.loadFromFile("Arial.ttf"); - sf::Text gameOverText("Game Over\nPress 'Enter' to play again", font); - gameOverText.setPosition(textPosition); - gameWindow.draw(gameOverText); - gameWindow.display(); - return; - } - - void PlayerOutput::DisplayScore(int score) { - sf::Vector2f textPosition(gameBoundaries); - textPosition.x = textPosition.x / 2; - textPosition.y = textPosition.y / 2; - sf::Font font; - font.loadFromFile("Arial.ttf"); - std::string text = "Score: " + std::to_string(score); - sf::Text ScoreText(text, font); - ScoreText.setPosition(textPosition); - gameWindow.draw(ScoreText); - - } - - void PlayerOutput::DisplayGameState(std::vector< std::vector >& gameBoard, int score) - { - CheckWindowEvents(); - if (delay == sf::milliseconds(0)) { return; } - char* letterOnBoard; - for (float y = 0; y < gameBoundaries.y; y++) - { - for (float x = 0; x < gameBoundaries.x; x++) - { - letterOnBoard = &gameBoard.at(y).at(x); - switch (*letterOnBoard) - { - case 'O': - DrawSnake(sf::Vector2f(x, y)); - break; - case 'X': - DrawFood(sf::Vector2f(x,y)); - break; - default: - DrawEmpty(sf::Vector2f(x,y)); - break; - } - } - } - DisplayScore(score); - gameWindow.display(); + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Enter)) { return; } sf::sleep(delay); - return; - } - - void PlayerOutput::StartGameWindow(void) - { - gameWindow.create(gameVideoSettings, "SnakePlusPlus"); - isWindowAlive = true; - return; - } - - void PlayerOutput::SetShowGame(bool isShowing) { - if (isShowing) { delay = sf::milliseconds(2); } - else { delay = sf::milliseconds(0); } - return; - } - - void PlayerOutput::CheckWindowEvents(void) - { - while (gameWindow.pollEvent(event)) - { - if ((event.type == sf::Event::Closed) - || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) - gameWindow.close(); - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Equal)) { - if (delay > sf::milliseconds(16)) { continue; } - delay += sf::milliseconds(1); - } - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Hyphen)) { - if (delay == sf::milliseconds(0)) { continue; } - delay -= sf::milliseconds(1); - } - } - } - - void PlayerOutput::DrawEmpty(sf::Vector2f location) - { - location *= static_cast(kGridSize); - drawObject.setPosition(location); - drawObject.setFillColor(sf::Color::Black); - gameWindow.draw(drawObject); - return; - } - - void PlayerOutput::DrawFood(sf::Vector2f location) - { - location *= static_cast(kGridSize); - drawObject.setPosition(location); - drawObject.setFillColor(sf::Color::Red); - gameWindow.draw(drawObject); - return; - } - - void PlayerOutput::DrawSnake(sf::Vector2f location) - { - location *= static_cast(kGridSize); - drawObject.setPosition(location); - drawObject.setFillColor(sf::Color::Green); - gameWindow.draw(drawObject); - return; } } + +void PlayerOutput::DisplayEndScreen(void) +{ + gameWindow.clear(); + sf::Vector2f textPosition(gameBoundaries); + textPosition.x = textPosition.x / 2; + textPosition.y = textPosition.y / 2; + sf::Font font; + font.loadFromFile("Arial.ttf"); + sf::Text gameOverText("Game Over\nPress 'Enter' to play again", font); + gameOverText.setPosition(textPosition); + gameWindow.draw(gameOverText); + gameWindow.display(); + return; +} + +void PlayerOutput::DisplayScore(int score) { + sf::Vector2f textPosition(gameBoundaries); + textPosition.x = textPosition.x / 2; + textPosition.y = textPosition.y / 2; + sf::Font font; + font.loadFromFile("Arial.ttf"); + std::string text = "Score: " + std::to_string(score); + sf::Text ScoreText(text, font); + ScoreText.setPosition(textPosition); + gameWindow.draw(ScoreText); + +} + +void PlayerOutput::DisplayGameState(std::vector< std::vector >& gameBoard, int score) +{ + CheckWindowEvents(); + if (delay == sf::milliseconds(0)) { return; } + char* letterOnBoard; + for (float y = 0; y < gameBoundaries.y; y++) + { + for (float x = 0; x < gameBoundaries.x; x++) + { + letterOnBoard = &gameBoard.at(y).at(x); + switch (*letterOnBoard) + { + case 'O': + DrawSnake(sf::Vector2f(x, y)); + break; + case 'X': + DrawFood(sf::Vector2f(x,y)); + break; + default: + DrawEmpty(sf::Vector2f(x,y)); + break; + } + } + } + DisplayScore(score); + gameWindow.display(); + sf::sleep(delay); + return; +} + +void PlayerOutput::StartGameWindow(void) +{ + gameWindow.create(gameVideoSettings, "SnakePlusPlus"); + isWindowAlive = true; + return; +} + +void PlayerOutput::SetShowGame(bool isShowing) { + if (isShowing) { delay = sf::milliseconds(2); } + else { delay = sf::milliseconds(0); } + return; +} + +void PlayerOutput::CheckWindowEvents(void) +{ + while (gameWindow.pollEvent(event)) + { + if ((event.type == sf::Event::Closed) + || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) + gameWindow.close(); + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Equal)) { + if (delay > sf::milliseconds(16)) { continue; } + delay += sf::milliseconds(1); + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Hyphen)) { + if (delay == sf::milliseconds(0)) { continue; } + delay -= sf::milliseconds(1); + } + } +} + +void PlayerOutput::DrawEmpty(sf::Vector2f location) +{ + location *= static_cast(kGridSize); + drawObject.setPosition(location); + drawObject.setFillColor(sf::Color::Black); + gameWindow.draw(drawObject); + return; +} + +void PlayerOutput::DrawFood(sf::Vector2f location) +{ + location *= static_cast(kGridSize); + drawObject.setPosition(location); + drawObject.setFillColor(sf::Color::Red); + gameWindow.draw(drawObject); + return; +} + +void PlayerOutput::DrawSnake(sf::Vector2f location) +{ + location *= static_cast(kGridSize); + drawObject.setPosition(location); + drawObject.setFillColor(sf::Color::Green); + gameWindow.draw(drawObject); + return; +} diff --git a/src/playerinterface.hpp b/src/playerinterface.hpp index 5f5fb3a..3fad189 100755 --- a/src/playerinterface.hpp +++ b/src/playerinterface.hpp @@ -6,34 +6,31 @@ const int kGridSize = 25; -namespace snakeplusplus -{ - PlayerDirection GetPlayerInput(void); +PlayerDirection GetPlayerInput(void); - class PlayerOutput - { - public: - sf::Vector2f gameBoundaries; - PlayerOutput(void); - bool IsOpen(void); - void CheckContinue(bool isBotControlled); - void DisplayGameState(std::vector< std::vector >& gameBoard, int score); - void DisplayScore(int score); - void StartGameWindow(void); - void SetShowGame(bool isShowing); - private: - void CheckWindowEvents(void); - void DisplayEndScreen(void); - void DrawEmpty(sf::Vector2f location); - void DrawFood(sf::Vector2f location); - void DrawSnake(sf::Vector2f location); - sf::RenderWindow gameWindow; - sf::VideoMode gameVideoSettings; - sf::RectangleShape drawObject; - sf::Event event; - bool isWindowAlive; - sf::Time delay = sf::milliseconds(1); - }; -} +class PlayerOutput +{ +public: + sf::Vector2f gameBoundaries; + PlayerOutput(void); + bool IsOpen(void); + void CheckContinue(bool isBotControlled); + void DisplayGameState(std::vector< std::vector >& gameBoard, int score); + void DisplayScore(int score); + void StartGameWindow(void); + void SetShowGame(bool isShowing); +private: + void CheckWindowEvents(void); + void DisplayEndScreen(void); + void DrawEmpty(sf::Vector2f location); + void DrawFood(sf::Vector2f location); + void DrawSnake(sf::Vector2f location); + sf::RenderWindow gameWindow; + sf::VideoMode gameVideoSettings; + sf::RectangleShape drawObject; + sf::Event event; + bool isWindowAlive; + sf::Time delay = sf::milliseconds(1); +}; #endif diff --git a/src/snake.cpp b/src/snake.cpp index c9602a5..65357a7 100755 --- a/src/snake.cpp +++ b/src/snake.cpp @@ -4,28 +4,25 @@ #include "common.hpp" #include "snake.hpp" -namespace snakeplusplus +void Snake::Pop(void) { - void Snake::Pop(void) - { - *(body.front()) = ' '; - body.pop(); - return; - } - - void Snake::Reset(void) - { - while (!body.empty()) Pop(); - speed.x = 0; - speed.y = 0; - return; - } - - // Returns a new food object for the snakeFood - void Food::GenerateNewFood(sf::Vector2f boundaries) - { - location.x = GenerateRandomNumber(boundaries.x); - location.y = GenerateRandomNumber(boundaries.y); - return; - } + *(body.front()) = ' '; + body.pop(); + return; +} + +void Snake::Reset(void) +{ + while (!body.empty()) Pop(); + speed.x = 0; + speed.y = 0; + return; +} + +// Returns a new food object for the snakeFood +void Food::GenerateNewFood(sf::Vector2f boundaries) +{ + location.x = GenerateRandomNumber(boundaries.x); + location.y = GenerateRandomNumber(boundaries.y); + return; } diff --git a/src/snake.hpp b/src/snake.hpp index 80cec53..a2388e4 100755 --- a/src/snake.hpp +++ b/src/snake.hpp @@ -5,25 +5,22 @@ #include #include -namespace snakeplusplus +struct Snake { - struct Snake - { - public: - sf::Vector2f headLocation; - sf::Vector2f speed; - std::queue body; - void Pop(void); - void Reset(void); - }; +public: + sf::Vector2f headLocation; + sf::Vector2f speed; + std::queue body; + void Pop(void); + void Reset(void); +}; - struct Food - { - public: - sf::Vector2f location; - char* food; - void GenerateNewFood(sf::Vector2f boundaries); - }; -} +struct Food +{ +public: + sf::Vector2f location; + char* food; + void GenerateNewFood(sf::Vector2f boundaries); +}; #endif -- 2.45.2 From b9b771bc1ccccbdd4a7c27d4e56c4fd4dd1413ed Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:50:48 -0500 Subject: [PATCH 04/12] refactor: convert engine to a singleton --- src/gamestate.hpp | 2 ++ src/main.cpp | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/gamestate.hpp b/src/gamestate.hpp index cbee036..ec9e118 100755 --- a/src/gamestate.hpp +++ b/src/gamestate.hpp @@ -39,4 +39,6 @@ private: double average = 0; }; +inline std::unique_ptr g_pEngine; + #endif diff --git a/src/main.cpp b/src/main.cpp index a26a74d..cdf6822 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,9 @@ -#include "gamestate.hpp" - -int main(void) -{ - GameEngine game; - game.Start(); - return 0; -} +#include "gamestate.hpp" +#include + +int main(void) +{ + g_pEngine = std::make_unique(); + g_pEngine->Start(); + return 0; +} -- 2.45.2 From ccf5843e618189135e488bd0902c6974491626ac Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:23:49 -0500 Subject: [PATCH 05/12] core: move various booleans to single bits in struct --- src/botinterface.cpp | 22 ++++++++++--------- src/botinterface.hpp | 6 +++--- src/common.cpp | 5 +++++ src/common.hpp | 44 ++++++++++++++++++++++++-------------- src/gamestate.cpp | 47 +++++++++++++++++++++++------------------ src/gamestate.hpp | 15 +++++++++---- src/main.cpp | 14 +++++++++++- src/playerinterface.cpp | 18 ++++++---------- src/playerinterface.hpp | 4 ++-- src/snake.cpp | 2 +- src/snake.hpp | 3 ++- 11 files changed, 110 insertions(+), 70 deletions(-) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index d72b498..9d3317f 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -52,7 +52,7 @@ void AISnake::AdjustProbability(double amount) // 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 std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize) { // Search for food /* @@ -80,7 +80,7 @@ void AISnake::GetNewPath(const std::vector< std::vector >& gameBoard, cons } } -void AISnake::BFS(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) { +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; @@ -90,7 +90,7 @@ void AISnake::BFS(const std::vector< std::vector >& gameBoard, const sf::V search.pop(); if (foodFound) { break; } if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; } - if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') { + if (gameBoard.at(currentLocation.y).at(currentLocation.x).m_bFood) { foodFound = true; } botPathUnsanitized.push(currentLocation); @@ -102,7 +102,7 @@ void AISnake::BFS(const std::vector< std::vector >& gameBoard, const sf::V localLocations[3].x -= 1; for (auto i : localLocations) { try { - if (gameBoard.at(i.y).at(i.x) == 'X') { + if (gameBoard.at(i.y).at(i.x).m_bFood) { botPathUnsanitized.push(i); foodFound = true; } @@ -113,7 +113,8 @@ void AISnake::BFS(const std::vector< std::vector >& gameBoard, const sf::V for (sf::Vector2f newLocation : localLocations) { try { if ((!visited.at(newLocation.y).at(newLocation.x)) - && (gameBoard.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) { @@ -124,7 +125,7 @@ void AISnake::BFS(const std::vector< std::vector >& gameBoard, const sf::V } } -void AISnake::DFS(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) { +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; @@ -133,8 +134,8 @@ void AISnake::DFS(const std::vector< std::vector >& gameBoard, const sf::V sf::Vector2f currentLocation = search.top(); search.pop(); if (foodFound) { break; } - if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; } - if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') { + if (visited[currentLocation.y][currentLocation.x]) { continue; } + if (gameBoard[currentLocation.y][currentLocation.x].m_bFood) { foodFound = true; } botPathUnsanitized.push(currentLocation); @@ -146,7 +147,7 @@ void AISnake::DFS(const std::vector< std::vector >& gameBoard, const sf::V localLocations[3].x -= 1; for (auto i : localLocations) { try { - if (gameBoard.at(i.y).at(i.x) == 'X') { + if (gameBoard.at(i.y).at(i.x).m_bFood) { botPathUnsanitized.push(i); foodFound = true; } @@ -163,7 +164,8 @@ void AISnake::DFS(const std::vector< std::vector >& gameBoard, const sf::V } if ((!visited.at(newLocation.y).at(newLocation.x)) - && (gameBoard.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) { diff --git a/src/botinterface.hpp b/src/botinterface.hpp index bfe68fe..1f60659 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -10,15 +10,15 @@ 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 std::vector< std::vector >& gameBoard, 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); private: 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 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); }; #endif diff --git a/src/common.cpp b/src/common.cpp index 7a83edd..3ff09ed 100755 --- a/src/common.cpp +++ b/src/common.cpp @@ -16,3 +16,8 @@ int GenerateRandomNumber(int generationLimit) generatedNumber = distribution(generator); return generatedNumber; } + +GameSpace::GameSpace(void) { + m_bFood = 0; + m_bSnake = 0; +} diff --git a/src/common.hpp b/src/common.hpp index d11a937..61be1d0 100755 --- a/src/common.hpp +++ b/src/common.hpp @@ -1,16 +1,28 @@ -#ifndef COMMON_HPP -#define COMMON_HPP - -void InitializeGenerator(void); -int GenerateRandomNumber(int generationLimit); - -enum PlayerDirection -{ - kNone = 0, - kLeft = 1, - kUp = 2, - kDown = 3, - kRight = 4 -}; - -#endif +#ifndef COMMON_HPP +#define COMMON_HPP + +void InitializeGenerator(void); +int GenerateRandomNumber(int generationLimit); + +enum PlayerDirection +{ + kNone = 0, + kLeft = 1, + kUp = 2, + kDown = 3, + kRight = 4 +}; + +struct GameSpace { + GameSpace(); + unsigned char m_bFood : 1 = 0; + unsigned char m_bSnake : 1 = 0; + unsigned char _2 : 1 = 0; + 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; +}; + +#endif diff --git a/src/gamestate.cpp b/src/gamestate.cpp index cc95091..311390e 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -16,7 +16,8 @@ GameEngine::GameEngine() void GameEngine::Start() { PrepareGameBoard(); - graphics.StartGameWindow(); + if (!state.m_bNoDisplay) + graphics.StartGameWindow(); Loop(); return; } @@ -25,16 +26,19 @@ void GameEngine::Reset() { AddIteration(); player.Reset(); - if (isBotControlled) { while (!bot.path.empty()) { bot.path.pop(); } } + if (state.m_bIsBotControlled) { while (!bot.path.empty()) { bot.path.pop(); } } PrepareGameBoard(); - isGameOver = false; - graphics.SetShowGame((amountPlayed + 1) % 50 == 0); + state.m_bIsGameOver = false; + if (state.m_bNoDisplay) + graphics.SetShowGame(false); + else + graphics.SetShowGame((amountPlayed + 1) % 50 == 0); return; } void GameEngine::AddIteration(void) { - graphics.CheckContinue(isBotControlled); + graphics.CheckContinue(state.m_bIsBotControlled); if (player.body.size() > 40) { UpdateAverage(); @@ -50,15 +54,16 @@ void GameEngine::AddIteration(void) void GameEngine::Loop(void) { int currentScore = 0; - while (graphics.IsOpen()) + while (graphics.IsOpen() || state.m_bNoDisplay) { - if (isGameOver) { Reset(); } + if (state.m_bIsGameOver) { Reset(); } UpdatePlayerSpeed(); PlaceNewSnakePart(MovePlayer()); RegenerateFood(); currentScore = player.body.size() * 100; //bot.UpdateProbability(player.body.size()); - graphics.DisplayGameState(gameBoard, currentScore); + if (!state.m_bNoDisplay) + graphics.DisplayGameState(gameBoard, currentScore); } return; } @@ -77,17 +82,19 @@ sf::Vector2f GameEngine::GetGameBoundaries(void) void GameEngine::PlaceNewSnakePart(sf::Vector2f location) { if (!player.speed.x && !player.speed.y) { return; } try { - char* locationState = &gameBoard.at(location.y).at(location.x); - if (*locationState == 'O' && (player.body.size() > 1)) { - isGameOver = true; // Game should end (Snake touching snake) + GameSpace* locationState = &gameBoard.at(location.y).at(location.x); + if (locationState->m_bSnake && (player.body.size() > 1)) { + state.m_bIsGameOver = true; // Game should end (Snake touching snake) } - *locationState = 'O'; + locationState->m_bSnake = true; player.body.push(locationState); player.headLocation = location; if (playerFood.location != location) player.Pop(); + else + locationState->m_bFood = false; } catch (const std::out_of_range& error) { - isGameOver = true; // Snake ran into edge + state.m_bIsGameOver = true; // Snake ran into edge } return; } @@ -97,12 +104,12 @@ void GameEngine::PlaceNewSnakePart(sf::Vector2f location) { void GameEngine::RegenerateFood() { // Generate a new food location if the current one is occupied - while (gameBoard.at(playerFood.location.y).at(playerFood.location.x) == 'O') { + while (gameBoard[playerFood.location.y][playerFood.location.x].m_bSnake) { playerFood.GenerateNewFood(GetGameBoundaries()); } // Update the game board with the new food location - gameBoard.at(playerFood.location.y).at(playerFood.location.x) = 'X'; + gameBoard[playerFood.location.y][playerFood.location.x].m_bFood = 1; } @@ -110,25 +117,25 @@ void GameEngine::PrepareGameBoard(void) { gameBoard.clear(); sf::Vector2f boardDimensions = GetGameBoundaries(); - gameBoard.resize(boardDimensions.y, std::vector (boardDimensions.x, ' ')); + gameBoard.resize(boardDimensions.y, std::vector(boardDimensions.x)); // Snake setup player.headLocation.x = GenerateRandomNumber(boardDimensions.x); player.headLocation.y = GenerateRandomNumber(boardDimensions.y); { - char* locationState = &gameBoard.at(player.headLocation.y).at(player.headLocation.x); + GameSpace* locationState = &gameBoard[player.headLocation.y][player.headLocation.x]; player.body.push(locationState); - *locationState = 'O'; + locationState->m_bSnake = true; } // Food setup playerFood.GenerateNewFood(boardDimensions); - gameBoard.at(playerFood.location.y).at(playerFood.location.x) = 'X'; + gameBoard[playerFood.location.y][playerFood.location.x].m_bFood = true; return; } void GameEngine::UpdatePlayerSpeed(void) { PlayerDirection controller; - if (isBotControlled) { + if (state.m_bIsBotControlled) { if (bot.path.empty()) { bot.GetNewPath(gameBoard, player.headLocation, GetGameBoundaries(), player.body.size()); } diff --git a/src/gamestate.hpp b/src/gamestate.hpp index ec9e118..297b57c 100755 --- a/src/gamestate.hpp +++ b/src/gamestate.hpp @@ -18,15 +18,22 @@ public: void Reset(void); void AddIteration(void); sf::Vector2f GetGameBoundaries(void); + struct GameState { + unsigned char m_bIsGameOver : 1 = 0; + unsigned char m_bIsBotControlled : 1 = 1; + unsigned char m_bNoDisplay : 1 = 0; + 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; + } state; private: - std::vector< std::vector > gameBoard; + std::vector< std::vector > gameBoard; PlayerOutput graphics; Snake player; Food playerFood; AISnake bot; - bool isGameOver = 0; - bool isBotControlled = 1; - void DisplayEndScreen(void); void Loop(void); sf::Vector2f MovePlayer(void); void PlaceNewSnakePart(sf::Vector2f location); diff --git a/src/main.cpp b/src/main.cpp index cdf6822..8c591b7 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,20 @@ #include "gamestate.hpp" #include +#include +#include +#include -int main(void) +int main(int argc, char* argv[]) { + std::vector args{argv + 1, argv + argc}; + for (auto it = args.begin(); it != args.end(); it++) { + if (it->compare("--no-gui") == 0) { + g_pEngine->state.m_bNoDisplay = true; + } else { + std::cerr << "[ERROR] Argument option not found, exiting..." << std::endl; + return 1; + } + } g_pEngine = std::make_unique(); g_pEngine->Start(); return 0; diff --git a/src/playerinterface.cpp b/src/playerinterface.cpp index 7bbbadf..2a7ff00 100755 --- a/src/playerinterface.cpp +++ b/src/playerinterface.cpp @@ -21,7 +21,7 @@ PlayerDirection GetPlayerInput(void) bool PlayerOutput::IsOpen(void) { - return gameWindow.isOpen(); + return isWindowAlive; } PlayerOutput::PlayerOutput(void) @@ -47,6 +47,7 @@ void PlayerOutput::CheckContinue(bool isBotControlled) || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) { gameWindow.close(); + isWindowAlive = false; return; } if (sf::Keyboard::isKeyPressed(sf::Keyboard::Enter)) { return; } @@ -82,7 +83,7 @@ void PlayerOutput::DisplayScore(int score) { } -void PlayerOutput::DisplayGameState(std::vector< std::vector >& gameBoard, int score) +void PlayerOutput::DisplayGameState(std::vector< std::vector >& gameBoard, int score) { CheckWindowEvents(); if (delay == sf::milliseconds(0)) { return; } @@ -91,19 +92,12 @@ void PlayerOutput::DisplayGameState(std::vector< std::vector >& gameBoard, { for (float x = 0; x < gameBoundaries.x; x++) { - letterOnBoard = &gameBoard.at(y).at(x); - switch (*letterOnBoard) - { - case 'O': + if (gameBoard[y][x].m_bSnake) DrawSnake(sf::Vector2f(x, y)); - break; - case 'X': + else if (gameBoard[y][x].m_bFood) DrawFood(sf::Vector2f(x,y)); - break; - default: + else DrawEmpty(sf::Vector2f(x,y)); - break; - } } } DisplayScore(score); diff --git a/src/playerinterface.hpp b/src/playerinterface.hpp index 3fad189..fea4a70 100755 --- a/src/playerinterface.hpp +++ b/src/playerinterface.hpp @@ -15,7 +15,7 @@ public: PlayerOutput(void); bool IsOpen(void); void CheckContinue(bool isBotControlled); - void DisplayGameState(std::vector< std::vector >& gameBoard, int score); + void DisplayGameState(std::vector< std::vector >& gameBoard, int score); void DisplayScore(int score); void StartGameWindow(void); void SetShowGame(bool isShowing); @@ -29,7 +29,7 @@ private: sf::VideoMode gameVideoSettings; sf::RectangleShape drawObject; sf::Event event; - bool isWindowAlive; + bool isWindowAlive = false; sf::Time delay = sf::milliseconds(1); }; diff --git a/src/snake.cpp b/src/snake.cpp index 65357a7..3ec32ab 100755 --- a/src/snake.cpp +++ b/src/snake.cpp @@ -6,7 +6,7 @@ void Snake::Pop(void) { - *(body.front()) = ' '; + body.front()->m_bSnake = false; body.pop(); return; } diff --git a/src/snake.hpp b/src/snake.hpp index a2388e4..c6f145e 100755 --- a/src/snake.hpp +++ b/src/snake.hpp @@ -4,13 +4,14 @@ #include #include +#include "common.hpp" struct Snake { public: sf::Vector2f headLocation; sf::Vector2f speed; - std::queue body; + std::queue body; void Pop(void); void Reset(void); }; -- 2.45.2 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 06/12] 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)); -- 2.45.2 From 6e7ae19e8b0349c08e2792ab6d92ecd8a974015c Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:34:14 -0500 Subject: [PATCH 07/12] core: fix not closing when window exits and add bot gameplay to be an argument --- src/gamestate.cpp | 16 +++++++++------- src/gamestate.hpp | 2 +- src/main.cpp | 4 ++++ src/playerinterface.cpp | 9 +++++---- src/playerinterface.hpp | 4 ++-- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/gamestate.cpp b/src/gamestate.cpp index 35659a8..b246f6e 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -23,17 +23,19 @@ void GameEngine::Start() void GameEngine::Reset() { - graphics.CheckContinue(state.m_bIsBotControlled); - bot.AddIteration(player.body.size()); + if (!state.m_bIsBotControlled) + graphics.CheckContinue(); player.Reset(); - if (state.m_bIsBotControlled) { while (!bot.path.empty()) { bot.path.pop(); } } PrepareGameBoard(); state.m_bIsGameOver = false; - if (state.m_bNoDisplay) - graphics.SetShowGame(false); - else + if (state.m_bIsBotControlled) { + while (!bot.path.empty()) + bot.path.pop(); + if (state.m_bNoDisplay) + graphics.SetShowGame(false); graphics.SetShowGame((bot.amountPlayed + 1) % 50 == 0); - return; + bot.AddIteration(player.body.size()); + } } void GameEngine::Loop(void) diff --git a/src/gamestate.hpp b/src/gamestate.hpp index e284b5d..8998dad 100755 --- a/src/gamestate.hpp +++ b/src/gamestate.hpp @@ -19,7 +19,7 @@ public: sf::Vector2f GetGameBoundaries(void); struct GameState { unsigned char m_bIsGameOver : 1 = 0; - unsigned char m_bIsBotControlled : 1 = 1; + unsigned char m_bIsBotControlled : 1 = 0; unsigned char m_bNoDisplay : 1 = 0; unsigned char _3 : 1 = 0; unsigned char _4 : 1 = 0; diff --git a/src/main.cpp b/src/main.cpp index 4b8b439..3e85364 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,10 @@ int main(int argc, char* argv[]) if (it->compare("--no-gui") == 0) { g_pEngine->state.m_bNoDisplay = true; std::cout << "[LOG - Main] Disabling display" << std::endl; + } else if (it->compare("--bot") == 0) { + g_pEngine->state.m_bIsBotControlled = true; + std::cout << "[LOG - Main] Bot control enabled" << 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 d7d1723..6d6311a 100755 --- a/src/playerinterface.cpp +++ b/src/playerinterface.cpp @@ -37,9 +37,8 @@ PlayerOutput::PlayerOutput(void) return; } -void PlayerOutput::CheckContinue(bool isBotControlled) +void PlayerOutput::CheckContinue() { - if (isBotControlled) { return; } DisplayEndScreen(); while (true) { @@ -115,7 +114,7 @@ void PlayerOutput::StartGameWindow(void) } void PlayerOutput::SetShowGame(bool isShowing) { - if (isShowing) { delay = sf::milliseconds(2); } + if (isShowing) { delay = sf::milliseconds(5); } else { delay = sf::milliseconds(0); } return; } @@ -125,8 +124,10 @@ void PlayerOutput::CheckWindowEvents(void) while (gameWindow.pollEvent(event)) { if ((event.type == sf::Event::Closed) - || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) + || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) { gameWindow.close(); + isWindowAlive = false; + } if (sf::Keyboard::isKeyPressed(sf::Keyboard::Equal)) { if (delay > sf::milliseconds(16)) { continue; } delay += sf::milliseconds(1); diff --git a/src/playerinterface.hpp b/src/playerinterface.hpp index fea4a70..55084e3 100755 --- a/src/playerinterface.hpp +++ b/src/playerinterface.hpp @@ -14,7 +14,7 @@ public: sf::Vector2f gameBoundaries; PlayerOutput(void); bool IsOpen(void); - void CheckContinue(bool isBotControlled); + void CheckContinue(); void DisplayGameState(std::vector< std::vector >& gameBoard, int score); void DisplayScore(int score); void StartGameWindow(void); @@ -30,7 +30,7 @@ private: sf::RectangleShape drawObject; sf::Event event; bool isWindowAlive = false; - sf::Time delay = sf::milliseconds(1); + sf::Time delay = sf::milliseconds(12); }; #endif -- 2.45.2 From 64c5c5d20ec6915faeba824d8f3dbc086c1c393f Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:40:38 -0500 Subject: [PATCH 08/12] core: fix arguments check crash --- src/main.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3e85364..e65f9e9 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,23 +4,21 @@ #include #include -int main(int argc, char* argv[]) -{ - std::vector args{argv + 1, argv + argc}; - for (auto it = args.begin(); it != args.end(); it++) { - if (it->compare("--no-gui") == 0) { +int main(int argc, char* argv[]) { + std::vector args(argv, argv + argc); + g_pEngine = std::make_unique(); + for (int i = 1; i < args.size(); ++i) { + if (args[i].compare("--no-gui") == 0) { g_pEngine->state.m_bNoDisplay = true; std::cout << "[LOG - Main] Disabling display" << std::endl; - } else if (it->compare("--bot") == 0) { + } else if (args[i].compare("--bot") == 0) { g_pEngine->state.m_bIsBotControlled = true; std::cout << "[LOG - Main] Bot control enabled" << std::endl; - } else { - std::cerr << "[ERROR] Argument option not found, exiting..." << std::endl; + std::cerr << "[ERROR] Argument " << args[i].c_str() << "not found, exiting..." << std::endl; return 1; } } - g_pEngine = std::make_unique(); g_pEngine->Start(); return 0; } -- 2.45.2 From 35bbc152df3a559212225714a49513376928e0c4 Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:41:36 -0500 Subject: [PATCH 09/12] core: implicitly set bot usage when no gui --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index e65f9e9..14f389d 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ int main(int argc, char* argv[]) { for (int i = 1; i < args.size(); ++i) { if (args[i].compare("--no-gui") == 0) { g_pEngine->state.m_bNoDisplay = true; + g_pEngine->state.m_bIsBotControlled = true; std::cout << "[LOG - Main] Disabling display" << std::endl; } else if (args[i].compare("--bot") == 0) { g_pEngine->state.m_bIsBotControlled = true; -- 2.45.2 From ff70b0dbd4b42a30998ab5c6cd1e32fb9a720672 Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:42:34 -0500 Subject: [PATCH 10/12] bot: fix pathfinding breaking --- src/botinterface.cpp | 109 +++++++++++++++++++++++++++---------------- src/botinterface.hpp | 10 ++-- src/gamestate.cpp | 2 +- 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index ed22273..f72d7f3 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -16,7 +16,6 @@ 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; @@ -67,7 +66,6 @@ void AISnake::AddIteration(const int size) } std::cout << "[Info - AI] Current average: " << average << std::endl; std::cout << "[Info - AI] Previous iteration size: " << size << std::endl; - } void AISnake::ResetPath(void) { @@ -76,29 +74,34 @@ void AISnake::ResetPath(void) { // 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) +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, boundaries); } - else { DFS(source, boundaries); } + if (roll <= probabilityBFS) { BFS(source); } + else { DFS(source); } UnvisitBoard(); - // Create path for food - path.push(botPathUnsanitized.top()); - botPathUnsanitized.pop(); - TrimPath(); + 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, const sf::Vector2f& boundaries) { +void AISnake::BFS(const sf::Vector2f& source) { + std::cout << "[Info - AI] Rolled BFS" << std::endl; 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; } + if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited) + continue; botPathUnsanitized.push(currentLocation); std::array localLocations; localLocations.fill(currentLocation); @@ -108,14 +111,21 @@ void AISnake::BFS(const sf::Vector2f& source, const sf::Vector2f& boundaries) { localLocations[3].x -= 1; for (sf::Vector2f nearby : localLocations) { try { - if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bFood) { + GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); + if (space->m_bFood) { botPathUnsanitized.push(nearby); - foodFound = true; - break; + std::cout << "[TRACE - BFS] Path successfully found food" << std::endl; + return; } - if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bVisited) + if (nearby.x < 1 || nearby.y < 1 + || nearby.x > g_pEngine->GetGameBoundaries().x - 1 + || nearby.y > g_pEngine->GetGameBoundaries().y - 1) { + continue; + + } + if (space->m_bVisited) continue; - if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bSnake) + if (space->m_bSnake) continue; search.push(nearby); } catch (const std::out_of_range& error) { @@ -124,18 +134,19 @@ void AISnake::BFS(const sf::Vector2f& source, const sf::Vector2f& boundaries) { } g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true; } + std::cout << "[ERROR - BFS] Failed to get a path to food" << std::endl; + pathFailed = true; } -void AISnake::DFS(const sf::Vector2f& source, const sf::Vector2f& boundaries) { +void AISnake::DFS(const sf::Vector2f& source) { + std::cout << "[Info - AI] Rolled DFS" << std::endl; 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; } + if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited) + continue; botPathUnsanitized.push(currentLocation); std::array localLocations; localLocations.fill(currentLocation); @@ -145,15 +156,21 @@ void AISnake::DFS(const sf::Vector2f& source, const sf::Vector2f& boundaries) { localLocations.at(3).x -= 1; for (sf::Vector2f nearby : localLocations) { try { - if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bFood) { + GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); + if (space->m_bFood) { botPathUnsanitized.push(nearby); - foodFound = true; - std::cout << "[TRACE - AI] Found food, breaking..." << std::endl; - break; + std::cout << "[TRACE - DFS] Path successfully found food" << std::endl; + return; } - if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bVisited) + if (nearby.x < 1 || nearby.y < 1 + || nearby.x > g_pEngine->GetGameBoundaries().x - 1 + || nearby.y > g_pEngine->GetGameBoundaries().y - 1) { + continue; + + } + if (space->m_bVisited) continue; - if (g_pEngine->gameBoard.at(nearby.y).at(nearby.x).m_bSnake) + if (space->m_bSnake) continue; search.push(nearby); } catch (const std::out_of_range& error) { @@ -162,18 +179,17 @@ void AISnake::DFS(const sf::Vector2f& source, const sf::Vector2f& boundaries) { } g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true; } + std::cout << "[ERROR - DFS] Failed to get a path to food" << std::endl; + pathFailed = true; } sf::Vector2f AISnake::GetAnyOpenPath(const sf::Vector2f& source) { sf::Vector2f bail; - sf::Vector2f paths[4]; - paths[0] = source; + std::array paths; + paths.fill(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) { @@ -192,9 +208,10 @@ sf::Vector2f AISnake::GetAnyOpenPath(const sf::Vector2f& source) { } void AISnake::UnvisitBoard(void) { - for (auto i : g_pEngine->gameBoard) - for (auto j : i) + for (std::vector& i : g_pEngine->gameBoard) + for (GameSpace& j : i) j.m_bVisited = false; + std::cout << "[TRACE - AI] Unvisited board" << std::endl; } void AISnake::UpdateAverage(const int size) { @@ -204,12 +221,24 @@ void AISnake::UpdateAverage(const int size) { } void AISnake::TrimPath(void) { + bool reachedSnake = false; + path.push(botPathUnsanitized.top()); // Push food location 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()); + 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(); } + std::cout << "[TRACE - AI] Trimmed path" << std::endl; +} + +void AISnake::EmptyPath(void) { + while (!botPathUnsanitized.empty()) + botPathUnsanitized.pop(); } diff --git a/src/botinterface.hpp b/src/botinterface.hpp index b1615cf..76a5331 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -9,7 +9,7 @@ class AISnake { public: std::stack path; AISnake(); - void GetNewPath(const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize); + void GetNewPath(const sf::Vector2f& source); PlayerDirection GetInput(const sf::Vector2f* source); void UpdateProbability(int snakeSize); void AdjustProbability(double amount); @@ -19,14 +19,16 @@ public: private: int totalLength = 0; double average = 0; - double probabilityBFS = 0.500; + double probabilityBFS = 0.800; + bool pathFailed = false; std::stack botPathUnsanitized; - void BFS(const sf::Vector2f& source, const sf::Vector2f& boundaries); - void DFS(const sf::Vector2f& source, const sf::Vector2f& boundaries); + void BFS(const sf::Vector2f& source); + void DFS(const sf::Vector2f& source); sf::Vector2f GetAnyOpenPath(const sf::Vector2f& source); void UnvisitBoard(void); void UpdateAverage(const int size); void TrimPath(void); + void EmptyPath(void); }; #endif diff --git a/src/gamestate.cpp b/src/gamestate.cpp index b246f6e..8454225 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -126,7 +126,7 @@ void GameEngine::UpdatePlayerSpeed(void) PlayerDirection controller; if (state.m_bIsBotControlled) { if (bot.path.empty()) { - bot.GetNewPath(player.headLocation, GetGameBoundaries(), player.body.size()); + bot.GetNewPath(player.headLocation); } controller = bot.GetInput(&player.headLocation); } -- 2.45.2 From 4a5fbe88d3ae320e9de00aab10071e0ab083071d Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:18:57 -0500 Subject: [PATCH 11/12] bot: various improvements --- src/botinterface.cpp | 38 +++++++++----------------------------- src/gamestate.cpp | 3 ++- src/snake.hpp | 1 - 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index f72d7f3..fd8da85 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -17,10 +17,8 @@ AISnake::AISnake() { PlayerDirection AISnake::GetInput(const sf::Vector2f* source) { sf::Vector2f directionDelta; - if (!source) { - std::cout << "[ERROR - AI] Source was borked, bailing" << std::endl; + if (!source) return kUp; - } while (*source == path.top() && !path.empty()) { path.pop(); } if (path.empty()) { path.push(GetAnyOpenPath(*source)); } directionDelta = *source - path.top(); @@ -51,7 +49,6 @@ 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; } @@ -64,8 +61,8 @@ void AISnake::AddIteration(const int size) 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; + std::cout << "[LOG - AI] Current average: " << average << std::endl; + std::cout << "[LOG - AI] Previous iteration size: " << size << std::endl; } void AISnake::ResetPath(void) { @@ -94,7 +91,6 @@ void AISnake::GetNewPath(const sf::Vector2f& source) } void AISnake::BFS(const sf::Vector2f& source) { - std::cout << "[Info - AI] Rolled BFS" << std::endl; std::queue search; search.push(source); while (!search.empty()) { @@ -114,15 +110,10 @@ void AISnake::BFS(const sf::Vector2f& source) { GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); if (space->m_bFood) { botPathUnsanitized.push(nearby); - std::cout << "[TRACE - BFS] Path successfully found food" << std::endl; return; } - if (nearby.x < 1 || nearby.y < 1 - || nearby.x > g_pEngine->GetGameBoundaries().x - 1 - || nearby.y > g_pEngine->GetGameBoundaries().y - 1) { - continue; - - } + if (nearby.x < 1 || nearby.y < 1) + continue; if (space->m_bVisited) continue; if (space->m_bSnake) @@ -134,12 +125,10 @@ void AISnake::BFS(const sf::Vector2f& source) { } g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true; } - std::cout << "[ERROR - BFS] Failed to get a path to food" << std::endl; pathFailed = true; } void AISnake::DFS(const sf::Vector2f& source) { - std::cout << "[Info - AI] Rolled DFS" << std::endl; std::stack search; search.push(source); while (!search.empty()) { @@ -159,15 +148,10 @@ void AISnake::DFS(const sf::Vector2f& source) { GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); if (space->m_bFood) { botPathUnsanitized.push(nearby); - std::cout << "[TRACE - DFS] Path successfully found food" << std::endl; return; } - if (nearby.x < 1 || nearby.y < 1 - || nearby.x > g_pEngine->GetGameBoundaries().x - 1 - || nearby.y > g_pEngine->GetGameBoundaries().y - 1) { - continue; - - } + if (nearby.x < 1 || nearby.x > g_pEngine->GetGameBoundaries().x - 2) + continue; if (space->m_bVisited) continue; if (space->m_bSnake) @@ -179,7 +163,6 @@ void AISnake::DFS(const sf::Vector2f& source) { } g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true; } - std::cout << "[ERROR - DFS] Failed to get a path to food" << std::endl; pathFailed = true; } @@ -194,10 +177,9 @@ sf::Vector2f AISnake::GetAnyOpenPath(const sf::Vector2f& source) { for (auto path : paths) { try { - if (g_pEngine->gameBoard.at(path.y).at(path.x).m_bSnake) { - bail = path; + 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 @@ -211,7 +193,6 @@ void AISnake::UnvisitBoard(void) { for (std::vector& i : g_pEngine->gameBoard) for (GameSpace& j : i) j.m_bVisited = false; - std::cout << "[TRACE - AI] Unvisited board" << std::endl; } void AISnake::UpdateAverage(const int size) { @@ -235,7 +216,6 @@ void AISnake::TrimPath(void) { } botPathUnsanitized.pop(); } - std::cout << "[TRACE - AI] Trimmed path" << std::endl; } void AISnake::EmptyPath(void) { diff --git a/src/gamestate.cpp b/src/gamestate.cpp index 8454225..8e49b5b 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -25,6 +25,8 @@ void GameEngine::Reset() { if (!state.m_bIsBotControlled) graphics.CheckContinue(); + else + bot.AddIteration(player.body.size()); player.Reset(); PrepareGameBoard(); state.m_bIsGameOver = false; @@ -34,7 +36,6 @@ void GameEngine::Reset() if (state.m_bNoDisplay) graphics.SetShowGame(false); graphics.SetShowGame((bot.amountPlayed + 1) % 50 == 0); - bot.AddIteration(player.body.size()); } } diff --git a/src/snake.hpp b/src/snake.hpp index c6f145e..ef6a5c4 100755 --- a/src/snake.hpp +++ b/src/snake.hpp @@ -20,7 +20,6 @@ struct Food { public: sf::Vector2f location; - char* food; void GenerateNewFood(sf::Vector2f boundaries); }; -- 2.45.2 From 6fc3580102651663662b499236fefe5b73832695 Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Sat, 10 Aug 2024 14:59:32 -0500 Subject: [PATCH 12/12] options: add help option and rename bot argument for later --- src/main.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 14f389d..fd25643 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,19 +4,36 @@ #include #include +void Help(void) { + std::cout << "Usage: snakeplusplus [OPTIONS]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << "\t--server\tRun snake in server mode (also sets --bot)" << std::endl; + std::cout << "\t--auto\t\tControl snake using a bot or AI" << std::endl; + std::cout << "\t-h, --help\tPrint this help message and exit" << std::endl; + std::cout << std::endl; + std::cout << "Autoplay options (requires --auto):" << std::endl; + std::cout << "\t--dumb\t\tPlays using basic search algorithms BFS and DFS" << std::endl; + std::cout << "\t--smart\t\tTrains an algorithm using unsupervised learning" << std::endl; + std::cout << std::endl; +} + int main(int argc, char* argv[]) { std::vector args(argv, argv + argc); g_pEngine = std::make_unique(); for (int i = 1; i < args.size(); ++i) { - if (args[i].compare("--no-gui") == 0) { + if (args[i].compare("--server") == 0) { g_pEngine->state.m_bNoDisplay = true; g_pEngine->state.m_bIsBotControlled = true; std::cout << "[LOG - Main] Disabling display" << std::endl; - } else if (args[i].compare("--bot") == 0) { + } else if (args[i].compare("--auto") == 0) { g_pEngine->state.m_bIsBotControlled = true; std::cout << "[LOG - Main] Bot control enabled" << std::endl; + } else if (args[i].compare("-h") == 0 || args[i].compare("--help") == 0) { + Help(); + return 0; } else { - std::cerr << "[ERROR] Argument " << args[i].c_str() << "not found, exiting..." << std::endl; + std::cout << "[LOG - Main] Argument `" << args[i] << "` unrecognized, printing help and exiting..."<< std::endl; + Help(); return 1; } } -- 2.45.2