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
*.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
.vs*
.cache
build
bin

View File

@ -4,7 +4,7 @@ project(
snakeplusplus
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_EXTENSIONS OFF)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

View File

@ -1,224 +1,149 @@
#include "botinterface.hpp"
#include "common.hpp"
#include "gamestate.hpp"
#include <array>
#include <cstdlib>
#include <iostream>
#include <queue>
#include <stdexcept>
#include <SFML/System/Vector2.hpp>
PlayerDirection lastKnownDirection = kNone;
AISnake::AISnake() {
;
}
PlayerDirection AISnake::GetInput(const sf::Vector2f* source)
namespace snakeplusplus
{
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;
}
PlayerDirection lastKnownDirection = kNone;
void AISnake::UpdateProbability(int snakeSize)
{
probabilityBFS = 1 - ((double) snakeSize) / 1000;
return;
}
AISnake::AISnake() {
;
}
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)
PlayerDirection AISnake::GetInput(const sf::Vector2f* source)
{
UpdateAverage(size);
double adjustmentAmount = 0.002;
if (average > size) { AdjustProbability(adjustmentAmount); }
else { AdjustProbability(-adjustmentAmount); }
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;
}
std::cout << "[LOG - AI] Current average: " << average << std::endl;
std::cout << "[LOG - AI] Previous iteration size: " << size << std::endl;
}
void AISnake::ResetPath(void) {
while (!path.empty()) { path.pop(); }
}
// 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
}
// 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
if (snakeSize < 135) {
BFS(gameBoard, source, boundaries);
} else {
DFS(gameBoard, source, boundaries);
}
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();
// 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(location);
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 ((!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 <stack>
#include <vector>
#include <SFML/System/Vector2.hpp>
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);
};
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);
private:
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);
};
}
#endif

View File

@ -2,27 +2,20 @@
#include <random>
#include "common.hpp"
std::default_random_engine generator;
void InitializeGenerator(void)
namespace snakeplusplus
{
generator.seed(std::random_device{}());
}
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(generator);
return generatedNumber;
}
GameSpace::GameSpace(void) {
Reset();
}
void GameSpace::Reset(void) {
m_bFood = 0;
m_bSnake = 0;
m_bVisited = 0;
// 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;
}
}

View File

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

View File

@ -6,155 +6,150 @@
#include "playerinterface.hpp"
#include "gamestate.hpp"
GameEngine::GameEngine()
namespace snakeplusplus
{
InitializeGenerator();
return;
}
GameEngine::GameEngine()
{
InitializeGenerator();
return;
}
void GameEngine::Start()
{
PrepareGameBoard();
if (!state.m_bNoDisplay)
void GameEngine::Start()
{
PrepareGameBoard();
graphics.StartGameWindow();
Loop();
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);
Loop();
return;
}
}
void GameEngine::Loop(void)
{
int currentScore = 0;
while (graphics.IsOpen() || state.m_bNoDisplay)
void GameEngine::Reset()
{
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)
graphics.CheckContinue(isBotControlled);
player.Reset();
if (isBotControlled) {
while (!bot.path.empty()) { bot.path.pop(); }
}
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());
PrepareGameBoard();
isGameOver = false;
return;
}
// 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);
void GameEngine::Loop(void)
{
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);
while (graphics.IsOpen())
{
if (isGameOver) { Reset(); }
UpdatePlayerSpeed();
PlaceNewSnakePart(MovePlayer());
RegenerateFood();
graphics.DisplayGameState(gameBoard);
}
controller = bot.GetInput(&player.headLocation);
return;
}
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;
sf::Vector2f GameEngine::MovePlayer(void)
{
sf::Vector2f newHeadPosition;
newHeadPosition.x = player.headLocation.x + player.speed.x;
newHeadPosition.y = player.headLocation.y + player.speed.y;
return newHeadPosition;
}
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;
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(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
#include <SFML/Graphics.hpp>
#include <memory>
#include "botinterface.hpp"
#include "snake.hpp"
#include "playerinterface.hpp"
const int kUnitSpeed = 1;
class GameEngine
namespace snakeplusplus
{
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();
};
const int kUnitSpeed = 1;
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

View File

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

View File

@ -1,167 +1,145 @@
#include "playerinterface.hpp"
#include <SFML/System/Vector2.hpp>
#include <SFML/Window/Keyboard.hpp>
#include <iostream>
PlayerDirection GetPlayerInput(void)
namespace snakeplusplus
{
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 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)
PlayerDirection GetPlayerInput(void)
{
gameWindow.pollEvent(event);
if ((event.type == sf::Event::Closed)
|| (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)))
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();
}
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();
isWindowAlive = false;
return;
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);
}
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);
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++)
void PlayerOutput::StartGameWindow(void)
{
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)
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));
if ((event.type == sf::Event::Closed)
|| (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)))
gameWindow.close();
}
}
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))
void PlayerOutput::DrawEmpty(sf::Vector2f location)
{
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);
}
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::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;
PlayerDirection GetPlayerInput(void);
class PlayerOutput
namespace snakeplusplus
{
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);
};
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);
void StartGameWindow(void);
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(15);
};
}
#endif

View File

@ -4,25 +4,28 @@
#include "common.hpp"
#include "snake.hpp"
void Snake::Pop(void)
namespace snakeplusplus
{
body.front()->m_bSnake = false;
body.pop();
return;
}
void Snake::Pop(void)
{
*(body.front()) = ' ';
body.pop();
return;
}
void Snake::Reset(void)
{
while (!body.empty()) Pop();
speed.x = 0;
speed.y = 0;
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;
// 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;
}
}

View File

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