diff --git a/src/botinterface.cpp b/src/botinterface.cpp index fd8da85..5b4dd25 100755 --- a/src/botinterface.cpp +++ b/src/botinterface.cpp @@ -16,6 +16,8 @@ AISnake::AISnake() { PlayerDirection AISnake::GetInput(const sf::Vector2f* source) { + if (g_pEngine->state.m_bSmart) + return CurrentBestDecision(); sf::Vector2f directionDelta; if (!source) return kUp; @@ -222,3 +224,129 @@ void AISnake::EmptyPath(void) { while (!botPathUnsanitized.empty()) botPathUnsanitized.pop(); } + +// Main method for using AI snake +PlayerDirection AISnake::CurrentBestDecision(void) { + // Reset probabilities + probabilityUp = 0.25; + probabilityDown = 0.25; + probabilityLeft = 0.25; + probabilityRight = 0.25; + + // Calculate options + CheckLocalFreedom(); + CheckFoodDirection(); + + // Deny impossible movement + RemoveImpossibleChoice(); + + // Make decision + if (probabilityUp > probabilityDown) { + if (probabilityUp < probabilityLeft) + return kLeft; + if (probabilityUp < probabilityRight) + return kRight; + return kUp; + } else { + if (probabilityDown < probabilityLeft) + return kLeft; + if (probabilityDown < probabilityRight) + return kRight; + return kDown; + } +} + +// Improves probability based on amount of local open spaces +// TODO: Add weights +void AISnake::CheckLocalFreedom(void) { + std::array choices; + std::array chances; + chances.fill(0); + choices.fill(g_pEngine->GetHeadLocation()); + choices[0].y += 1; + choices[1].x += 1; + choices[2].y -= 1; + choices[3].x -= 1; + for (int i = 0; i < 4; ++i) { + try { + if (g_pEngine->gameBoard.at(choices[i].y).at(choices[i].x).m_bSnake) { + chances[i] = 0; + continue; + } + if (g_pEngine->gameBoard.at(choices[i].y).at(choices[i].x).m_bFood) { + chances[0] = 0; + chances[1] = 0; + chances[2] = 0; + chances[3] = 0; + chances[i] = 1; + break; + } + } catch (const std::out_of_range& error) { + continue; // Out of bounds + } + double openSpaces = 0; + bool foodNearby = false; + for (int j = -1; j < 2; ++j) { + for (int k = -1; k < 2; ++k) { + try { + if (!g_pEngine->gameBoard.at(choices[i].y + j).at(choices[i].x + k).m_bSnake) + ++openSpaces; + if (g_pEngine->gameBoard.at(choices[i].y + j).at(choices[i].x + k).m_bFood) + foodNearby = true; + } catch (const std::out_of_range& error) { + continue; // Out of bounds + } + } + } + chances[i] = openSpaces * 0.11111111111; + if (foodNearby) + chances[i] = 1; // Ignore chance, be greedy because food + } + probabilityDown *= chances[0]; + probabilityRight *= chances[1]; + probabilityUp *= chances[2]; + probabilityLeft *= chances[3]; +} + +// Improves probability that direction of food is best option +// TODO: Add weights +void AISnake::CheckFoodDirection(void) { + sf::Vector2f delta = g_pEngine->GetHeadLocation() - g_pEngine->GetFoodLocation(); + if (delta.x > 0) + probabilityLeft *= 2; + if (delta.x < 0) + probabilityRight *= 2; + if (delta.y > 0) + probabilityUp *= 2; + if (delta.y < 0) + probabilityDown *= 2; + std::array choices; + choices.fill(g_pEngine->GetHeadLocation()); + choices[0].y += 1; + choices[1].x += 1; + choices[2].y -= 1; + choices[3].x -= 1; +} + +void AISnake::RemoveImpossibleChoice(void) { + if (g_pEngine->GetPlayerSize() == 1) + return; // Player can go any direction + PlayerDirection currentDirection = g_pEngine->GetCurrentDirection(); + switch (currentDirection) { + case (kUp): + probabilityDown = 0; + break; + case (kDown): + probabilityUp = 0; + break; + case (kLeft): + probabilityRight = 0; + break; + case (kRight): + probabilityLeft = 0; + break; + default: + std::cout << "[ERR - AI] Impossibility defaulted somehow??" << std::endl; + break; + } +} diff --git a/src/botinterface.hpp b/src/botinterface.hpp index 76a5331..feb9388 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -21,6 +21,8 @@ private: double average = 0; double probabilityBFS = 0.800; bool pathFailed = false; + + // Generic search algorithms std::stack botPathUnsanitized; void BFS(const sf::Vector2f& source); void DFS(const sf::Vector2f& source); @@ -29,6 +31,18 @@ private: void UpdateAverage(const int size); void TrimPath(void); void EmptyPath(void); + + // Unsupervised learning + // Make decisions about current state of board + double probabilityUp = 0.25; + double probabilityDown = 0.25; + double probabilityLeft = 0.25; + double probabilityRight = 0.25; + PlayerDirection CurrentBestDecision(void); + void CheckLocalFreedom(void); + void CheckFoodDirection(void); + void RemoveImpossibleChoice(void); + }; #endif diff --git a/src/gamestate.cpp b/src/gamestate.cpp index 8e49b5b..2a4d000 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -31,11 +31,15 @@ void GameEngine::Reset() PrepareGameBoard(); state.m_bIsGameOver = false; if (state.m_bIsBotControlled) { - while (!bot.path.empty()) - bot.path.pop(); + if (!state.m_bSmart) { + while (!bot.path.empty()) + bot.path.pop(); + } if (state.m_bNoDisplay) graphics.SetShowGame(false); - graphics.SetShowGame((bot.amountPlayed + 1) % 50 == 0); + if (state.m_bSkipIterations) + graphics.SetShowGame((bot.amountPlayed + 1) % 50 == 0); + graphics.SetShowGame(true); } } @@ -66,6 +70,32 @@ sf::Vector2f GameEngine::GetGameBoundaries(void) return graphics.gameBoundaries; } +PlayerDirection GameEngine::GetCurrentDirection(void) { + if (player.speed.x) { + if (player.speed.x > 0) + return kRight; + else + return kLeft; + } else { + if (player.speed.y > 0) + return kDown; + else + return kUp; + } +} + +int GameEngine::GetPlayerSize(void) { + return player.body.size(); +} + +sf::Vector2f GameEngine::GetHeadLocation(void) { + return player.headLocation; +} + +sf::Vector2f GameEngine::GetFoodLocation(void) { + return playerFood.location; +} + void GameEngine::PlaceNewSnakePart(sf::Vector2f location) { if (!player.speed.x && !player.speed.y) { return; } try { diff --git a/src/gamestate.hpp b/src/gamestate.hpp index 8998dad..4b3de0a 100755 --- a/src/gamestate.hpp +++ b/src/gamestate.hpp @@ -5,6 +5,7 @@ #include #include #include "botinterface.hpp" +#include "common.hpp" #include "snake.hpp" #include "playerinterface.hpp" @@ -21,13 +22,17 @@ public: unsigned char m_bIsGameOver : 1 = 0; unsigned char m_bIsBotControlled : 1 = 0; unsigned char m_bNoDisplay : 1 = 0; - unsigned char _3 : 1 = 0; - unsigned char _4 : 1 = 0; + unsigned char m_bSmart : 1 = 0; + unsigned char m_bSkipIterations : 1 = 0; unsigned char _5 : 1 = 0; unsigned char _6 : 1 = 0; unsigned char _7 : 1 = 0; } state; std::vector< std::vector > gameBoard; + PlayerDirection GetCurrentDirection(void); + int GetPlayerSize(void); + sf::Vector2f GetHeadLocation(void); + sf::Vector2f GetFoodLocation(void); private: PlayerOutput graphics; Snake player; diff --git a/src/main.cpp b/src/main.cpp index fd25643..da6bed8 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,12 @@ int main(int argc, char* argv[]) { } 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("--smart") == 0) { + g_pEngine->state.m_bSmart = true; + std::cout << "[LOG - Main] Using AI" << std::endl; + } else if (args[i].compare("--skip") == 0) { + g_pEngine->state.m_bSkipIterations = true; + std::cout << "[LOG - Main] Only showing every 50 epochs" << std::endl; } else if (args[i].compare("-h") == 0 || args[i].compare("--help") == 0) { Help(); return 0;