Merge pull request 'refactor: merge final cleanups before working on AI' (#6) from refactor into master
Reviewed-on: Trianta/snakeplusplus#6
This commit is contained in:
		
						commit
						f055ab4078
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -49,3 +49,4 @@ build | ||||
| 
 | ||||
| # Extras | ||||
| .vs* | ||||
| .cache | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| #include "botinterface.hpp" | ||||
| #include "common.hpp" | ||||
| #include "gamestate.hpp" | ||||
| #include <array> | ||||
| #include <cstdlib> | ||||
| #include <iostream> | ||||
| @ -7,172 +8,217 @@ | ||||
| #include <stdexcept> | ||||
| #include <SFML/System/Vector2.hpp> | ||||
| 
 | ||||
| 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<char> >& 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<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) { | ||||
|         std::queue<sf::Vector2f> search; | ||||
|         std::vector<std::vector<bool>> visited(boundaries.y, std::vector<bool> (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<sf::Vector2f, 4> 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<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) { | ||||
|         std::stack<sf::Vector2f> search; | ||||
|         std::vector<std::vector<bool>> visited(boundaries.y, std::vector<bool> (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<sf::Vector2f, 4> 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<sf::Vector2f> 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<sf::Vector2f, 4> 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<sf::Vector2f> 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<sf::Vector2f, 4> 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<sf::Vector2f, 4> 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<GameSpace>& 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(); | ||||
| } | ||||
|  | ||||
| @ -3,25 +3,32 @@ | ||||
| 
 | ||||
| #include "common.hpp" | ||||
| #include <stack> | ||||
| #include <vector> | ||||
| #include <SFML/System/Vector2.hpp> | ||||
| 
 | ||||
| namespace snakeplusplus | ||||
| { | ||||
|     class AISnake { | ||||
|     public: | ||||
|         std::stack<sf::Vector2f> path; | ||||
|         AISnake(); | ||||
|         void GetNewPath(const std::vector< std::vector<char> >& 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<sf::Vector2f> botPathUnsanitized; | ||||
|         void BFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries); | ||||
|         void DFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries); | ||||
|     }; | ||||
| } | ||||
| class AISnake { | ||||
| public: | ||||
|     std::stack<sf::Vector2f> 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<sf::Vector2f> 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  | ||||
|  | ||||
| @ -2,20 +2,27 @@ | ||||
| #include <random> | ||||
| #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; | ||||
| } | ||||
|  | ||||
| @ -1,20 +1,29 @@ | ||||
| #ifndef COMMON_HPP | ||||
| #define COMMON_HPP | ||||
| 
 | ||||
| namespace snakeplusplus | ||||
| void InitializeGenerator(void); | ||||
| int GenerateRandomNumber(int generationLimit); | ||||
| 
 | ||||
| enum PlayerDirection | ||||
| { | ||||
|     void InitializeGenerator(void); | ||||
|     int GenerateRandomNumber(int generationLimit); | ||||
|     kNone = 0, | ||||
|     kLeft = 1, | ||||
|     kUp = 2, | ||||
|     kDown = 3, | ||||
|     kRight = 4 | ||||
| }; | ||||
| 
 | ||||
|     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 | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| // GameState.cpp
 | ||||
| #include <iostream> | ||||
| #include <stdexcept> | ||||
| #include <SFML/Graphics.hpp> | ||||
| #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<char> (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<GameSpace>(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; | ||||
| } | ||||
|  | ||||
| @ -3,42 +3,44 @@ | ||||
| #define GAMESTATE_HPP | ||||
| 
 | ||||
| #include <SFML/Graphics.hpp> | ||||
| #include <memory> | ||||
| #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<char> > 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<GameSpace> > 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<GameEngine> g_pEngine; | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										42
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								src/main.cpp
									
									
									
									
									
								
							| @ -1,8 +1,42 @@ | ||||
| #include "gamestate.hpp" | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <iostream> | ||||
| 
 | ||||
| int main(void) | ||||
| { | ||||
|     snakeplusplus::GameEngine game; | ||||
|     game.Start(); | ||||
| 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<std::string> args(argv, argv + argc); | ||||
|     g_pEngine = std::make_unique<GameEngine>(); | ||||
|     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; | ||||
| } | ||||
|  | ||||
| @ -1,174 +1,167 @@ | ||||
| #include "playerinterface.hpp" | ||||
| #include <SFML/System/Vector2.hpp> | ||||
| #include <SFML/Window/Keyboard.hpp> | ||||
| #include <iostream> | ||||
| 
 | ||||
| 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<char> >& 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<float>(kGridSize); | ||||
|         drawObject.setPosition(location); | ||||
|         drawObject.setFillColor(sf::Color::Black); | ||||
|         gameWindow.draw(drawObject); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     void PlayerOutput::DrawFood(sf::Vector2f location) | ||||
|     { | ||||
|         location *= static_cast<float>(kGridSize); | ||||
|         drawObject.setPosition(location); | ||||
|         drawObject.setFillColor(sf::Color::Red); | ||||
|         gameWindow.draw(drawObject); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     void PlayerOutput::DrawSnake(sf::Vector2f location) | ||||
|     { | ||||
|         location *= static_cast<float>(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<GameSpace> >& 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<float>(kGridSize); | ||||
|     drawObject.setPosition(location); | ||||
|     drawObject.setFillColor(sf::Color::Black); | ||||
|     gameWindow.draw(drawObject); | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| void PlayerOutput::DrawFood(sf::Vector2f location) | ||||
| { | ||||
|     location *= static_cast<float>(kGridSize); | ||||
|     drawObject.setPosition(location); | ||||
|     drawObject.setFillColor(sf::Color::Red); | ||||
|     gameWindow.draw(drawObject); | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| void PlayerOutput::DrawSnake(sf::Vector2f location) | ||||
| { | ||||
|     location *= static_cast<float>(kGridSize); | ||||
|     drawObject.setPosition(location); | ||||
|     drawObject.setFillColor(sf::Color::Green); | ||||
|     gameWindow.draw(drawObject); | ||||
|     return; | ||||
| } | ||||
|  | ||||
| @ -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<char> >& 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<GameSpace> >& 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 | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
|  | ||||
| @ -4,26 +4,23 @@ | ||||
| 
 | ||||
| #include <SFML/System/Vector2.hpp> | ||||
| #include <queue> | ||||
| #include "common.hpp" | ||||
| 
 | ||||
| namespace snakeplusplus | ||||
| struct Snake | ||||
| { | ||||
|     struct Snake | ||||
|     { | ||||
|     public: | ||||
|         sf::Vector2f headLocation; | ||||
|         sf::Vector2f speed; | ||||
|         std::queue<char*> body; | ||||
|         void Pop(void); | ||||
|         void Reset(void); | ||||
|     }; | ||||
| public: | ||||
|     sf::Vector2f headLocation; | ||||
|     sf::Vector2f speed; | ||||
|     std::queue<GameSpace*> 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 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user