diff --git a/.gitignore b/.gitignore index 99352dd..9a179cb 100755 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ build # Extras .vs* +.cache 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) diff --git a/src/botinterface.cpp b/src/botinterface.cpp index 15bfbba..fd8da85 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 @@ -7,172 +8,217 @@ #include #include -namespace snakeplusplus +PlayerDirection lastKnownDirection = kNone; + +AISnake::AISnake() { + ; +} + +PlayerDirection AISnake::GetInput(const sf::Vector2f* source) { - PlayerDirection lastKnownDirection = kNone; + sf::Vector2f directionDelta; + if (!source) + 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) + && (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; +} - PlayerDirection AISnake::GetInput(const sf::Vector2f* source) +void AISnake::AdjustProbability(double amount) +{ + probabilityBFS += amount; + if (probabilityBFS > 1.0) { probabilityBFS = 1.0; } + if (probabilityBFS < 0.0) { probabilityBFS = 0.0; } + return; +} + +void AISnake::AddIteration(const int size) +{ + if (size > 40) { - 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; + UpdateAverage(size); + double adjustmentAmount = 0.002; + if (average > size) { AdjustProbability(adjustmentAmount); } + else { AdjustProbability(-adjustmentAmount); } } + std::cout << "[LOG - AI] Current average: " << average << std::endl; + std::cout << "[LOG - AI] Previous iteration size: " << size << std::endl; +} - void AISnake::UpdateProbability(int snakeSize) - { - probabilityBFS = 1 - ((double) snakeSize) / 1000; - return; - } +void AISnake::ResetPath(void) { + while (!path.empty()) { path.pop(); } +} - 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()); - } - 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; - } +// Gets a new path for the bot to follow +// Uses DFS algorithm +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); } + else { DFS(source); } + UnvisitBoard(); + 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) { + std::queue search; + search.push(source); + while (!search.empty()) { + sf::Vector2f currentLocation = search.front(); + search.pop(); + 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 { + GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); + if (space->m_bFood) { + botPathUnsanitized.push(nearby); + return; + } + if (nearby.x < 1 || nearby.y < 1) + continue; + if (space->m_bVisited) + continue; + if (space->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; + } + pathFailed = true; +} + +void AISnake::DFS(const sf::Vector2f& source) { + std::stack search; + search.push(source); + while (!search.empty()) { + sf::Vector2f currentLocation = search.top(); + search.pop(); + if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited) + continue; + 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 { + GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); + if (space->m_bFood) { + botPathUnsanitized.push(nearby); + return; + } + if (nearby.x < 1 || nearby.x > g_pEngine->GetGameBoundaries().x - 2) + continue; + if (space->m_bVisited) + continue; + if (space->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; + } + pathFailed = true; +} + +sf::Vector2f AISnake::GetAnyOpenPath(const sf::Vector2f& source) { + sf::Vector2f bail; + std::array paths; + paths.fill(source); + paths[0].x -= 1; + paths[1].x += 1; + paths[2].y -= 1; + paths[3].y += 1; + + for (auto path : paths) { + try { + 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 + } + } + + return bail; // Snake is trapped, give up and die +} + +void AISnake::UnvisitBoard(void) { + for (std::vector& i : g_pEngine->gameBoard) + for (GameSpace& j : i) + j.m_bVisited = false; +} + +void AISnake::UpdateAverage(const int size) { + totalLength += size; + amountPlayed += 1; + average = (double)totalLength / amountPlayed; +} + +void AISnake::TrimPath(void) { + bool reachedSnake = false; + path.push(botPathUnsanitized.top()); // Push food location + while (!botPathUnsanitized.empty()) { + 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(); + } +} + +void AISnake::EmptyPath(void) { + while (!botPathUnsanitized.empty()) + botPathUnsanitized.pop(); +} diff --git a/src/botinterface.hpp b/src/botinterface.hpp index 7bf29b7..76a5331 100755 --- a/src/botinterface.hpp +++ b/src/botinterface.hpp @@ -3,25 +3,32 @@ #include "common.hpp" #include -#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 sf::Vector2f& source); + 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.800; + bool pathFailed = false; + std::stack botPathUnsanitized; + 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/common.cpp b/src/common.cpp index 62088ee..975b28a 100755 --- a/src/common.cpp +++ b/src/common.cpp @@ -2,20 +2,27 @@ #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; +} + +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 82f60e2..dc133a9 100755 --- a/src/common.hpp +++ b/src/common.hpp @@ -1,20 +1,29 @@ -#ifndef COMMON_HPP -#define COMMON_HPP - -namespace snakeplusplus -{ - 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 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 98840d0..8e49b5b 100755 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -1,5 +1,4 @@ // GameState.cpp -#include #include #include #include "botinterface.hpp" @@ -7,165 +6,155 @@ #include "playerinterface.hpp" #include "gamestate.hpp" -namespace snakeplusplus +GameEngine::GameEngine() { - GameEngine::GameEngine() - { - InitializeGenerator(); - return; - } + InitializeGenerator(); + return; +} - void GameEngine::Start() - { - PrepareGameBoard(); +void GameEngine::Start() +{ + PrepareGameBoard(); + if (!state.m_bNoDisplay) graphics.StartGameWindow(); - Loop(); - return; - } + 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; +void GameEngine::Reset() +{ + if (!state.m_bIsBotControlled) + graphics.CheckContinue(); + else + bot.AddIteration(player.body.size()); + player.Reset(); + PrepareGameBoard(); + state.m_bIsGameOver = false; + 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); } } + +void GameEngine::Loop(void) +{ + int currentScore = 0; + while (graphics.IsOpen() || state.m_bNoDisplay) + { + if (state.m_bIsGameOver) { Reset(); } + UpdatePlayerSpeed(); + PlaceNewSnakePart(MovePlayer()); + RegenerateFood(); + currentScore = player.body.size() * 100; + if (!state.m_bNoDisplay) + 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 { + 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->m_bSnake = true; + player.body.push(locationState); + player.headLocation = location; + if (playerFood.location != location) + player.Pop(); + 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 + } + 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).m_bSnake) { + playerFood.GenerateNewFood(GetGameBoundaries()); + } + + // Update the game board with the new food location + gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bFood = 1; +} + + +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); + { + 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.at(playerFood.location.y).at(playerFood.location.x).m_bFood = true; + return; +} + +void GameEngine::UpdatePlayerSpeed(void) +{ + PlayerDirection controller; + if (state.m_bIsBotControlled) { + if (bot.path.empty()) { + bot.GetNewPath(player.headLocation); + } + 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; +} diff --git a/src/gamestate.hpp b/src/gamestate.hpp index e71949d..8998dad 100755 --- a/src/gamestate.hpp +++ b/src/gamestate.hpp @@ -3,42 +3,44 @@ #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); + sf::Vector2f GetGameBoundaries(void); + struct GameState { + 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 _5 : 1 = 0; + unsigned char _6 : 1 = 0; + unsigned char _7 : 1 = 0; + } state; + std::vector< std::vector > gameBoard; +private: + PlayerOutput graphics; + Snake player; + Food playerFood; + AISnake bot; + void Loop(void); + sf::Vector2f MovePlayer(void); + void PlaceNewSnakePart(sf::Vector2f location); + void RegenerateFood(void); + void PrepareGameBoard(void); + void UpdatePlayerSpeed(); +}; + +inline std::unique_ptr g_pEngine; #endif diff --git a/src/main.cpp b/src/main.cpp index 2d529fb..fd25643 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,42 @@ -#include "gamestate.hpp" - -int main(void) -{ - snakeplusplus::GameEngine game; - game.Start(); - return 0; -} +#include "gamestate.hpp" +#include +#include +#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("--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("--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::cout << "[LOG - Main] Argument `" << args[i] << "` unrecognized, printing help and exiting..."<< std::endl; + Help(); + return 1; + } + } + g_pEngine->Start(); + return 0; +} diff --git a/src/playerinterface.cpp b/src/playerinterface.cpp index fe4a5f4..6d6311a 100755 --- a/src/playerinterface.cpp +++ b/src/playerinterface.cpp @@ -1,174 +1,167 @@ #include "playerinterface.hpp" #include #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 isWindowAlive; +} - 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() +{ + 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(); + isWindowAlive = false; + 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++) + { + if (gameBoard.at(y).at(x).m_bSnake) + DrawSnake(sf::Vector2f(x, y)); + else if (gameBoard.at(y).at(x).m_bFood) + DrawFood(sf::Vector2f(x,y)); + else + DrawEmpty(sf::Vector2f(x,y)); + } + } + 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(5); } + 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(); + isWindowAlive = false; + } + 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..55084e3 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(); + 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 = false; + sf::Time delay = sf::milliseconds(12); +}; #endif diff --git a/src/snake.cpp b/src/snake.cpp index c9602a5..3ec32ab 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()->m_bSnake = false; + 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..ef6a5c4 100755 --- a/src/snake.hpp +++ b/src/snake.hpp @@ -4,26 +4,23 @@ #include #include +#include "common.hpp" -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; + void GenerateNewFood(sf::Vector2f boundaries); +}; #endif