diff --git a/src/botinterface.cpp b/src/botinterface.cpp index 6b4ed21..9843479 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -1,26 +1,149 @@ #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, const int snakeSize) + { + // Search for food + if (snakeSize < 135) { + 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 ((!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 3f98ab7..5664235 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -2,11 +2,23 @@ #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, 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); + }; } #endif diff --git a/src/gamestate.cpp b/src/gamestate.cpp index af3899d..5e37107 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(), player.body.size()); + } + 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..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(50); + sf::Time delay = sf::milliseconds(15); }; }