From b8a6a8d195cace795c5f6602e3baeb6229ff94f0 Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:07:08 -0500 Subject: [PATCH 1/3] Replaced simple snake bot with AI --- src/botinterface.cpp | 86 ++++++++++++++++++++++++++++++++++++++--- src/botinterface.hpp | 10 ++++- src/gamestate.cpp | 14 ++++++- src/gamestate.hpp | 2 + src/playerinterface.hpp | 2 +- 5 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index 6b4ed21..ff38e6c 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -1,26 +1,100 @@ #include "botinterface.hpp" #include "common.hpp" +#include +#include +#include +#include #include namespace snakeplusplus { PlayerDirection lastKnownDirection = kNone; - PlayerDirection GetBotInput(const sf::Vector2f* snakeHeadLocation, const sf::Vector2f* foodLocation) + + AISnake::AISnake() { + ; + } + + PlayerDirection AISnake::GetInput(const sf::Vector2f* source) { sf::Vector2f directionDelta; - directionDelta = *snakeHeadLocation - *foodLocation; - if ((directionDelta.y > 0) + 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 < 0) + else if ((directionDelta.y == -1) && (lastKnownDirection != kUp)) { lastKnownDirection = kDown; } - else if ((directionDelta.x > 0) + else if ((directionDelta.x == 1) && (lastKnownDirection != kRight)) { lastKnownDirection = kLeft; } - else if ((directionDelta.x < 0) + else if ((directionDelta.x == -1) && (lastKnownDirection != kLeft)) { lastKnownDirection = kRight; } return lastKnownDirection; } + + // 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) + { + std::queue search; + std::stack botPathUnsanitized; + std::vector> visited(boundaries.y, std::vector (boundaries.x, false)); + sf::Vector2f currentLocation; + std::array localLocations; + bool foodFound = false; + search.push(source); + // Search for food + while (!search.empty()) { + currentLocation = search.front(); + search.pop(); + if (foodFound) { break; } + if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; } + botPathUnsanitized.push(currentLocation); + if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') { + foodFound = true; + } + 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; + } + // Create path for food + path.push(botPathUnsanitized.top()); + botPathUnsanitized.pop(); + while (!botPathUnsanitized.empty()) { + currentLocation = botPathUnsanitized.top(); + sf::Vector2f deltaVector = currentLocation - path.top(); + int delta = abs(deltaVector.x) + abs(deltaVector.y); + if (delta == 1) { + path.push(botPathUnsanitized.top()); + } + botPathUnsanitized.pop(); + } + } } diff --git a/src/botinterface.hpp b/src/botinterface.hpp index 3f98ab7..152cbb2 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -2,11 +2,19 @@ #define BOTINTERFACE_HPP #include "common.hpp" +#include +#include #include namespace snakeplusplus { - PlayerDirection GetBotInput(const sf::Vector2f* snakeHeadLocation, const sf::Vector2f* foodLocation); + class AISnake { + public: + std::stack path; + AISnake(); + void GetNewPath(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries); + PlayerDirection GetInput(const sf::Vector2f* source); + }; } #endif diff --git a/src/gamestate.cpp b/src/gamestate.cpp index af3899d..95b069f 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -26,6 +26,9 @@ namespace snakeplusplus { graphics.CheckContinue(isBotControlled); player.Reset(); + if (isBotControlled) { + while (!bot.path.empty()) { bot.path.pop(); } + } PrepareGameBoard(); isGameOver = false; return; @@ -88,7 +91,9 @@ namespace snakeplusplus playerFood.GenerateNewFood(GetGameBoundaries()); newLocation = playerFood.location; } - if (isUpdated) { gameBoard.at(newLocation.y).at(newLocation.x) = 'X'; } + if (isUpdated) { + gameBoard.at(newLocation.y).at(newLocation.x) = 'X'; + } return; } @@ -114,7 +119,12 @@ namespace snakeplusplus void GameEngine::UpdatePlayerSpeed(void) { PlayerDirection controller; - if (isBotControlled) { controller = GetBotInput(&player.headLocation, &playerFood.location); } + if (isBotControlled) { + if (bot.path.empty()) { + bot.GetNewPath(gameBoard, player.headLocation, GetGameBoundaries()); + } + controller = bot.GetInput(&player.headLocation); + } else { controller = GetPlayerInput(); } switch (controller) { case kUp: diff --git a/src/gamestate.hpp b/src/gamestate.hpp index 7665477..19d8350 100755 --- a/src/gamestate.hpp +++ b/src/gamestate.hpp @@ -3,6 +3,7 @@ #define GAMESTATE_HPP #include +#include "botinterface.hpp" #include "snake.hpp" #include "playerinterface.hpp" @@ -22,6 +23,7 @@ namespace snakeplusplus PlayerOutput graphics; Snake player; Food playerFood; + AISnake bot; bool isGameOver = 0; bool isBotControlled = 1; void DisplayEndScreen(void); diff --git a/src/playerinterface.hpp b/src/playerinterface.hpp index f397a14..adcebe1 100755 --- a/src/playerinterface.hpp +++ b/src/playerinterface.hpp @@ -30,7 +30,7 @@ namespace snakeplusplus sf::RectangleShape drawObject; sf::Event event; bool isWindowAlive; - sf::Time delay = sf::milliseconds(50); + sf::Time delay = sf::milliseconds(5); }; } -- 2.45.3 From e6797fa193c63983c98dca8fcf04459cf10de25a Mon Sep 17 00:00:00 2001 From: Trianta <56975502+Trimutex@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:22:11 -0500 Subject: [PATCH 2/3] Split into BFS at first and DFS after --- src/botinterface.cpp | 83 +++++++++++++++++++++++++++++++++++--------- src/botinterface.hpp | 6 +++- src/gamestate.cpp | 2 +- 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index ff38e6c..6545e41 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -38,25 +38,42 @@ namespace snakeplusplus // 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) + void AISnake::GetNewPath(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize) { + // Search for food + if (snakeSize < 160) { + 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::stack botPathUnsanitized; std::vector> visited(boundaries.y, std::vector (boundaries.x, false)); - sf::Vector2f currentLocation; - std::array localLocations; bool foodFound = false; search.push(source); - // Search for food while (!search.empty()) { - currentLocation = search.front(); + sf::Vector2f currentLocation = search.front(); search.pop(); if (foodFound) { break; } if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; } - botPathUnsanitized.push(currentLocation); 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; @@ -84,17 +101,49 @@ namespace snakeplusplus } visited.at(currentLocation.y).at(currentLocation.x) = true; } - // Create path for food - path.push(botPathUnsanitized.top()); - botPathUnsanitized.pop(); - while (!botPathUnsanitized.empty()) { - currentLocation = botPathUnsanitized.top(); - sf::Vector2f deltaVector = currentLocation - path.top(); - int delta = abs(deltaVector.x) + abs(deltaVector.y); - if (delta == 1) { - path.push(botPathUnsanitized.top()); + } + + 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.pop(); + 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; } } } diff --git a/src/botinterface.hpp b/src/botinterface.hpp index 152cbb2..5664235 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -12,8 +12,12 @@ namespace snakeplusplus public: std::stack path; AISnake(); - void GetNewPath(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries); + 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); + private: + 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); }; } diff --git a/src/gamestate.cpp b/src/gamestate.cpp index 95b069f..5e37107 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -121,7 +121,7 @@ namespace snakeplusplus PlayerDirection controller; if (isBotControlled) { if (bot.path.empty()) { - bot.GetNewPath(gameBoard, player.headLocation, GetGameBoundaries()); + bot.GetNewPath(gameBoard, player.headLocation, GetGameBoundaries(), player.body.size()); } controller = bot.GetInput(&player.headLocation); } -- 2.45.3 From 0ce07fa88d4a34042f3bb251ca2ae1b66d7c834c Mon Sep 17 00:00:00 2001 From: Gregory <56975502+Trimutex@users.noreply.github.com> Date: Sat, 14 Oct 2023 00:35:23 -0500 Subject: [PATCH 3/3] Minor settings adjustments --- src/botinterface.cpp | 2 +- src/playerinterface.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index 6545e41..9843479 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -41,7 +41,7 @@ namespace snakeplusplus void AISnake::GetNewPath(const std::vector< std::vector >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize) { // Search for food - if (snakeSize < 160) { + if (snakeSize < 135) { BFS(gameBoard, source, boundaries); } else { DFS(gameBoard, source, boundaries); diff --git a/src/playerinterface.hpp b/src/playerinterface.hpp index adcebe1..861a2b6 100755 --- a/src/playerinterface.hpp +++ b/src/playerinterface.hpp @@ -30,7 +30,7 @@ namespace snakeplusplus sf::RectangleShape drawObject; sf::Event event; bool isWindowAlive; - sf::Time delay = sf::milliseconds(5); + sf::Time delay = sf::milliseconds(15); }; } -- 2.45.3