Compare commits

..

No commits in common. "master" and "V1.0" have entirely different histories.
master ... V1.0

13 changed files with 540 additions and 715 deletions

17
.gitignore vendored
View File

@ -33,20 +33,7 @@
*.json *.json
*.ps1 *.ps1
# ---> CMake
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
build
# Extras # Extras
.vs* .vs*
.cache build
bin

View File

@ -4,7 +4,7 @@ project(
snakeplusplus snakeplusplus
LANGUAGES CXX) LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23 CACHE STRING "The C++ standard to use") set(CMAKE_CXX_STANDARD 11 CACHE STRING "The C++ standard to use")
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

View File

@ -1,224 +1,149 @@
#include "botinterface.hpp" #include "botinterface.hpp"
#include "common.hpp" #include "common.hpp"
#include "gamestate.hpp"
#include <array> #include <array>
#include <cstdlib> #include <cstdlib>
#include <iostream>
#include <queue> #include <queue>
#include <stdexcept> #include <stdexcept>
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
PlayerDirection lastKnownDirection = kNone; namespace snakeplusplus
AISnake::AISnake() {
;
}
PlayerDirection AISnake::GetInput(const sf::Vector2f* source)
{ {
sf::Vector2f directionDelta; PlayerDirection lastKnownDirection = kNone;
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;
}
void AISnake::UpdateProbability(int snakeSize) AISnake::AISnake() {
{ ;
probabilityBFS = 1 - ((double) snakeSize) / 1000; }
return;
}
void AISnake::AdjustProbability(double amount) PlayerDirection AISnake::GetInput(const sf::Vector2f* source)
{
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)
{ {
UpdateAverage(size); sf::Vector2f directionDelta;
double adjustmentAmount = 0.002; if (*source == path.top()) { path.pop(); }
if (average > size) { AdjustProbability(adjustmentAmount); } if (path.empty()) { return kUp; } // Snake is trapped
else { AdjustProbability(-adjustmentAmount); } 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;
} }
std::cout << "[LOG - AI] Current average: " << average << std::endl;
std::cout << "[LOG - AI] Previous iteration size: " << size << std::endl;
}
void AISnake::ResetPath(void) { // Gets a new path for the bot to follow
while (!path.empty()) { path.pop(); } // Uses DFS algorithm
} void AISnake::GetNewPath(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize)
{
// Gets a new path for the bot to follow // Search for food
// Uses DFS algorithm if (snakeSize < 135) {
void AISnake::GetNewPath(const sf::Vector2f& source) BFS(gameBoard, source, boundaries);
{ } else {
// Search for food DFS(gameBoard, source, boundaries);
// 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; // Create path for food
} path.push(botPathUnsanitized.top());
pathFailed = true; botPathUnsanitized.pop();
} while (!botPathUnsanitized.empty()) {
sf::Vector2f deltaVector = botPathUnsanitized.top() - path.top();
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); int delta = abs(deltaVector.x) + abs(deltaVector.y);
if (delta == 1) if (delta == 1) {
path.push(location); 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 ((!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;
} }
botPathUnsanitized.pop();
} }
} }
void AISnake::EmptyPath(void) {
while (!botPathUnsanitized.empty())
botPathUnsanitized.pop();
}

View File

@ -3,32 +3,22 @@
#include "common.hpp" #include "common.hpp"
#include <stack> #include <stack>
#include <vector>
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
class AISnake { namespace snakeplusplus
public: {
std::stack<sf::Vector2f> path; class AISnake {
AISnake(); public:
void GetNewPath(const sf::Vector2f& source); std::stack<sf::Vector2f> path;
PlayerDirection GetInput(const sf::Vector2f* source); AISnake();
void UpdateProbability(int snakeSize); void GetNewPath(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize);
void AdjustProbability(double amount); PlayerDirection GetInput(const sf::Vector2f* source);
void AddIteration(const int size); private:
void ResetPath(void); std::stack<sf::Vector2f> botPathUnsanitized;
int amountPlayed = 0; void BFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries);
private: void DFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries);
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 #endif

View File

@ -2,27 +2,20 @@
#include <random> #include <random>
#include "common.hpp" #include "common.hpp"
std::default_random_engine generator; namespace snakeplusplus
void InitializeGenerator(void)
{ {
generator.seed(std::random_device{}()); std::default_random_engine generator;
} void InitializeGenerator(void)
{
generator.seed(std::random_device{}());
}
// Returns a newly generated number // Returns a newly generated number
int GenerateRandomNumber(int generationLimit) int GenerateRandomNumber(int generationLimit)
{ {
int generatedNumber; int generatedNumber;
std::uniform_int_distribution<> distribution(0, generationLimit - 1); std::uniform_int_distribution<> distribution(0, generationLimit - 1);
generatedNumber = distribution(generator); generatedNumber = distribution(snakeplusplus::generator);
return generatedNumber; return generatedNumber;
} }
GameSpace::GameSpace(void) {
Reset();
}
void GameSpace::Reset(void) {
m_bFood = 0;
m_bSnake = 0;
m_bVisited = 0;
} }

View File

@ -1,29 +1,20 @@
#ifndef COMMON_HPP #ifndef COMMON_HPP
#define COMMON_HPP #define COMMON_HPP
void InitializeGenerator(void); namespace snakeplusplus
int GenerateRandomNumber(int generationLimit); {
void InitializeGenerator(void);
enum PlayerDirection int GenerateRandomNumber(int generationLimit);
{
kNone = 0, enum PlayerDirection
kLeft = 1, {
kUp = 2, kNone = 0,
kDown = 3, kLeft = 1,
kRight = 4 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 #endif
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

View File

@ -6,155 +6,150 @@
#include "playerinterface.hpp" #include "playerinterface.hpp"
#include "gamestate.hpp" #include "gamestate.hpp"
GameEngine::GameEngine() namespace snakeplusplus
{ {
InitializeGenerator(); GameEngine::GameEngine()
return; {
} InitializeGenerator();
return;
}
void GameEngine::Start() void GameEngine::Start()
{ {
PrepareGameBoard(); PrepareGameBoard();
if (!state.m_bNoDisplay)
graphics.StartGameWindow(); graphics.StartGameWindow();
Loop(); Loop();
return; return;
}
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) void GameEngine::Reset()
{
int currentScore = 0;
while (graphics.IsOpen() || state.m_bNoDisplay)
{ {
if (state.m_bIsGameOver) { Reset(); } graphics.CheckContinue(isBotControlled);
UpdatePlayerSpeed(); player.Reset();
PlaceNewSnakePart(MovePlayer()); if (isBotControlled) {
RegenerateFood(); while (!bot.path.empty()) { bot.path.pop(); }
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; PrepareGameBoard();
player.body.push(locationState); isGameOver = false;
player.headLocation = location; return;
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 void GameEngine::Loop(void)
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); while (graphics.IsOpen())
player.body.push(locationState); {
locationState->m_bSnake = true; if (isGameOver) { Reset(); }
} UpdatePlayerSpeed();
// Food setup PlaceNewSnakePart(MovePlayer());
playerFood.GenerateNewFood(boardDimensions); RegenerateFood();
gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bFood = true; graphics.DisplayGameState(gameBoard);
return;
}
void GameEngine::UpdatePlayerSpeed(void)
{
PlayerDirection controller;
if (state.m_bIsBotControlled) {
if (bot.path.empty()) {
bot.GetNewPath(player.headLocation);
} }
controller = bot.GetInput(&player.headLocation); return;
} }
else { controller = GetPlayerInput(); }
switch (controller) { sf::Vector2f GameEngine::MovePlayer(void)
case kUp: {
if (player.speed.y == kUnitSpeed) { break; } sf::Vector2f newHeadPosition;
player.speed.x = 0; newHeadPosition.x = player.headLocation.x + player.speed.x;
player.speed.y = -kUnitSpeed; newHeadPosition.y = player.headLocation.y + player.speed.y;
break; return newHeadPosition;
case kLeft: }
if (player.speed.x == kUnitSpeed) { break; }
player.speed.x = -kUnitSpeed; sf::Vector2f GameEngine::GetGameBoundaries(void)
player.speed.y = 0; {
break; return graphics.gameBoundaries;
case kRight: }
if (player.speed.x == -kUnitSpeed) { break; }
player.speed.x = kUnitSpeed; void GameEngine::PlaceNewSnakePart(sf::Vector2f location)
player.speed.y = 0; {
break; if (!player.speed.x && !player.speed.y) { return; }
case kDown: try
if (player.speed.y == -kUnitSpeed) { break; } {
player.speed.x = 0; char* locationState;
player.speed.y = kUnitSpeed; locationState = &gameBoard.at(location.y).at(location.x);
break; if (*locationState == 'O' && (player.body.size() > 1))
default: isGameOver = true; // Game should end (Snake touching snake)
break; *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(void)
{
sf::Vector2f newLocation = playerFood.location;
bool isUpdated = false;
while (gameBoard.at(newLocation.y).at(newLocation.x) == 'O')
{
isUpdated = true;
playerFood.GenerateNewFood(GetGameBoundaries());
newLocation = playerFood.location;
}
if (isUpdated) {
gameBoard.at(newLocation.y).at(newLocation.x) = 'X';
}
return;
}
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;
} }
return;
} }

View File

@ -3,44 +3,37 @@
#define GAMESTATE_HPP #define GAMESTATE_HPP
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
#include <memory>
#include "botinterface.hpp" #include "botinterface.hpp"
#include "snake.hpp" #include "snake.hpp"
#include "playerinterface.hpp" #include "playerinterface.hpp"
const int kUnitSpeed = 1; namespace snakeplusplus
class GameEngine
{ {
public: const int kUnitSpeed = 1;
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; class GameEngine
{
public:
GameEngine();
void Start(void);
void Reset(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();
};
}
#endif #endif

View File

@ -1,42 +1,8 @@
#include "gamestate.hpp" #include "gamestate.hpp"
#include <memory>
#include <string> int main(void)
#include <vector> {
#include <iostream> snakeplusplus::GameEngine game;
game.Start();
void Help(void) { return 0;
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;
}

View File

@ -1,167 +1,145 @@
#include "playerinterface.hpp" #include "playerinterface.hpp"
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
#include <SFML/Window/Keyboard.hpp> #include <SFML/Window/Keyboard.hpp>
#include <iostream>
PlayerDirection GetPlayerInput(void) namespace snakeplusplus
{ {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) PlayerDirection GetPlayerInput(void)
|| 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 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;
}
void PlayerOutput::CheckContinue()
{
DisplayEndScreen();
while (true)
{ {
gameWindow.pollEvent(event); if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)
if ((event.type == sf::Event::Closed) || sf::Keyboard::isKeyPressed(sf::Keyboard::A))
|| (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) 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();
}
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)
{
if (isBotControlled) { return; }
DisplayEndScreen();
while (true)
{ {
gameWindow.close(); gameWindow.pollEvent(event);
isWindowAlive = false; if ((event.type == sf::Event::Closed)
return; || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)))
{
gameWindow.close();
return;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Enter)) { return; }
sf::sleep(delay);
} }
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Enter)) { 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::DisplayGameState(std::vector< std::vector<char> >& gameBoard)
{
CheckWindowEvents();
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;
}
}
}
gameWindow.display();
sf::sleep(delay); sf::sleep(delay);
return;
} }
}
void PlayerOutput::DisplayEndScreen(void) void PlayerOutput::StartGameWindow(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++) gameWindow.create(gameVideoSettings, "SnakePlusPlus");
isWindowAlive = true;
return;
}
void PlayerOutput::CheckWindowEvents(void)
{
while (gameWindow.pollEvent(event))
{ {
if (gameBoard.at(y).at(x).m_bSnake) if ((event.type == sf::Event::Closed)
DrawSnake(sf::Vector2f(x, y)); || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)))
else if (gameBoard.at(y).at(x).m_bFood) gameWindow.close();
DrawFood(sf::Vector2f(x,y));
else
DrawEmpty(sf::Vector2f(x,y));
} }
} }
DisplayScore(score);
gameWindow.display();
sf::sleep(delay);
return;
}
void PlayerOutput::StartGameWindow(void) void PlayerOutput::DrawEmpty(sf::Vector2f location)
{
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) location *= static_cast<float>(kGridSize);
|| (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) { drawObject.setPosition(location);
gameWindow.close(); drawObject.setFillColor(sf::Color::Black);
isWindowAlive = false; gameWindow.draw(drawObject);
} return;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Equal)) { }
if (delay > sf::milliseconds(16)) { continue; }
delay += sf::milliseconds(1); void PlayerOutput::DrawFood(sf::Vector2f location)
} {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Hyphen)) { location *= static_cast<float>(kGridSize);
if (delay == sf::milliseconds(0)) { continue; } drawObject.setPosition(location);
delay -= sf::milliseconds(1); 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::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;
}

View File

@ -6,31 +6,32 @@
const int kGridSize = 25; const int kGridSize = 25;
PlayerDirection GetPlayerInput(void); namespace snakeplusplus
class PlayerOutput
{ {
public: PlayerDirection GetPlayerInput(void);
sf::Vector2f gameBoundaries;
PlayerOutput(void); class PlayerOutput
bool IsOpen(void); {
void CheckContinue(); public:
void DisplayGameState(std::vector< std::vector<GameSpace> >& gameBoard, int score); sf::Vector2f gameBoundaries;
void DisplayScore(int score); PlayerOutput(void);
void StartGameWindow(void); bool IsOpen(void);
void SetShowGame(bool isShowing); void CheckContinue(bool isBotControlled);
private: void DisplayGameState(std::vector< std::vector<char> >& gameBoard);
void CheckWindowEvents(void); void StartGameWindow(void);
void DisplayEndScreen(void); private:
void DrawEmpty(sf::Vector2f location); void CheckWindowEvents(void);
void DrawFood(sf::Vector2f location); void DisplayEndScreen(void);
void DrawSnake(sf::Vector2f location); void DrawEmpty(sf::Vector2f location);
sf::RenderWindow gameWindow; void DrawFood(sf::Vector2f location);
sf::VideoMode gameVideoSettings; void DrawSnake(sf::Vector2f location);
sf::RectangleShape drawObject; sf::RenderWindow gameWindow;
sf::Event event; sf::VideoMode gameVideoSettings;
bool isWindowAlive = false; sf::RectangleShape drawObject;
sf::Time delay = sf::milliseconds(12); sf::Event event;
}; bool isWindowAlive;
sf::Time delay = sf::milliseconds(15);
};
}
#endif #endif

View File

@ -4,25 +4,28 @@
#include "common.hpp" #include "common.hpp"
#include "snake.hpp" #include "snake.hpp"
void Snake::Pop(void) namespace snakeplusplus
{ {
body.front()->m_bSnake = false; void Snake::Pop(void)
body.pop(); {
return; *(body.front()) = ' ';
} body.pop();
return;
}
void Snake::Reset(void) void Snake::Reset(void)
{ {
while (!body.empty()) Pop(); while (!body.empty()) Pop();
speed.x = 0; speed.x = 0;
speed.y = 0; speed.y = 0;
return; return;
} }
// Returns a new food object for the snakeFood // Returns a new food object for the snakeFood
void Food::GenerateNewFood(sf::Vector2f boundaries) void Food::GenerateNewFood(sf::Vector2f boundaries)
{ {
location.x = GenerateRandomNumber(boundaries.x); location.x = GenerateRandomNumber(boundaries.x);
location.y = GenerateRandomNumber(boundaries.y); location.y = GenerateRandomNumber(boundaries.y);
return; return;
}
} }

View File

@ -4,23 +4,26 @@
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
#include <queue> #include <queue>
#include "common.hpp"
struct Snake namespace snakeplusplus
{ {
public: struct Snake
sf::Vector2f headLocation; {
sf::Vector2f speed; public:
std::queue<GameSpace*> body; sf::Vector2f headLocation;
void Pop(void); sf::Vector2f speed;
void Reset(void); std::queue<char*> body;
}; void Pop(void);
void Reset(void);
};
struct Food struct Food
{ {
public: public:
sf::Vector2f location; sf::Vector2f location;
void GenerateNewFood(sf::Vector2f boundaries); char* food;
}; void GenerateNewFood(sf::Vector2f boundaries);
};
}
#endif #endif