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,26 +1,24 @@
#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)
{ {
PlayerDirection lastKnownDirection = kNone;
AISnake::AISnake() {
;
}
PlayerDirection AISnake::GetInput(const sf::Vector2f* source)
{
sf::Vector2f directionDelta; sf::Vector2f directionDelta;
if (!source) if (*source == path.top()) { path.pop(); }
return kUp; if (path.empty()) { return kUp; } // Snake is trapped
while (*source == path.top() && !path.empty()) { path.pop(); }
if (path.empty()) { path.push(GetAnyOpenPath(*source)); }
directionDelta = *source - path.top(); directionDelta = *source - path.top();
path.pop(); path.pop();
if ((directionDelta.y == 1) if ((directionDelta.y == 1)
@ -36,68 +34,44 @@ PlayerDirection AISnake::GetInput(const sf::Vector2f* source)
&& (lastKnownDirection != kLeft)) && (lastKnownDirection != kLeft))
{ lastKnownDirection = kRight; } { lastKnownDirection = kRight; }
return lastKnownDirection; return lastKnownDirection;
} }
void AISnake::UpdateProbability(int snakeSize) // Gets a new path for the bot to follow
{ // Uses DFS algorithm
probabilityBFS = 1 - ((double) snakeSize) / 1000; void AISnake::GetNewPath(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize)
return;
}
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)
{ {
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::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 // Search for food
// Probability-based approach for fun if (snakeSize < 135) {
double roll = ((double) GenerateRandomNumber(RAND_MAX)) / ((double) RAND_MAX); BFS(gameBoard, source, boundaries);
if (roll <= probabilityBFS) { BFS(source); }
else { DFS(source); }
UnvisitBoard();
if (pathFailed) {
pathFailed = false;
EmptyPath();
path.push(GetAnyOpenPath(source));
} else { } else {
TrimPath(); DFS(gameBoard, source, boundaries);
if (path.empty()) }
path.push(GetAnyOpenPath(source)); // 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 sf::Vector2f& source) { void AISnake::BFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) {
std::queue<sf::Vector2f> search; 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); search.push(source);
while (!search.empty()) { while (!search.empty()) {
sf::Vector2f currentLocation = search.front(); sf::Vector2f currentLocation = search.front();
search.pop(); search.pop();
if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited) if (foodFound) { break; }
continue; if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; }
if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') {
foodFound = true;
}
botPathUnsanitized.push(currentLocation); botPathUnsanitized.push(currentLocation);
std::array<sf::Vector2f, 4> localLocations; std::array<sf::Vector2f, 4> localLocations;
localLocations.fill(currentLocation); localLocations.fill(currentLocation);
@ -105,120 +79,71 @@ void AISnake::BFS(const sf::Vector2f& source) {
localLocations[1].x += 1; localLocations[1].x += 1;
localLocations[2].y -= 1; localLocations[2].y -= 1;
localLocations[3].x -= 1; localLocations[3].x -= 1;
for (sf::Vector2f nearby : localLocations) { for (auto i : localLocations) {
try { try {
GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); if (gameBoard.at(i.y).at(i.x) == 'X') {
if (space->m_bFood) { botPathUnsanitized.push(i);
botPathUnsanitized.push(nearby); foodFound = true;
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) { } catch (const std::out_of_range& error) {
continue; // Out of bounds continue; // Out of bounds
} }
} }
g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true; 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;
}
} }
pathFailed = true;
}
void AISnake::DFS(const sf::Vector2f& source) { void AISnake::DFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) {
std::stack<sf::Vector2f> search; 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); search.push(source);
while (!search.empty()) { while (!search.empty()) {
sf::Vector2f currentLocation = search.top(); sf::Vector2f currentLocation = search.top();
search.pop(); search.pop();
if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited) if (foodFound) { break; }
continue; if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; }
if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') {
foodFound = true;
}
botPathUnsanitized.push(currentLocation); botPathUnsanitized.push(currentLocation);
std::array<sf::Vector2f, 4> localLocations; std::array<sf::Vector2f, 4> localLocations;
localLocations.fill(currentLocation); localLocations.fill(currentLocation);
localLocations.at(0).y += 1; localLocations[0].y += 1;
localLocations.at(1).x += 1; localLocations[1].x += 1;
localLocations.at(2).y -= 1; localLocations[2].y -= 1;
localLocations.at(3).x -= 1; localLocations[3].x -= 1;
for (sf::Vector2f nearby : localLocations) { for (auto i : localLocations) {
try { try {
GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x); if (gameBoard.at(i.y).at(i.x) == 'X') {
if (space->m_bFood) { botPathUnsanitized.push(i);
botPathUnsanitized.push(nearby); foodFound = true;
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) { } catch (const std::out_of_range& error) {
continue; // Out of bounds continue; // Out of bounds
} }
} }
g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true; for (sf::Vector2f newLocation : localLocations) {
}
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 { try {
bail = path; if ((!visited.at(newLocation.y).at(newLocation.x))
if (g_pEngine->gameBoard.at(path.y).at(path.x).m_bSnake) && (gameBoard.at(newLocation.y).at(newLocation.x) == ' ')) {
continue; search.push(newLocation);
return path; }
} catch (const std::out_of_range& error) { } catch (const std::out_of_range& error) {
continue; // Out of bounds continue; // Out of bounds
} }
} }
visited.at(currentLocation.y).at(currentLocation.x) = true;
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();
}

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: {
class AISnake {
public:
std::stack<sf::Vector2f> path; std::stack<sf::Vector2f> path;
AISnake(); AISnake();
void GetNewPath(const sf::Vector2f& source); 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); PlayerDirection GetInput(const sf::Vector2f* source);
void UpdateProbability(int snakeSize); private:
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; std::stack<sf::Vector2f> botPathUnsanitized;
void BFS(const sf::Vector2f& source); void BFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries);
void DFS(const sf::Vector2f& source); void DFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries);
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)
{ {
std::default_random_engine generator;
void InitializeGenerator(void)
{
generator.seed(std::random_device{}()); 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);
enum PlayerDirection
{ {
void InitializeGenerator(void);
int GenerateRandomNumber(int generationLimit);
enum PlayerDirection
{
kNone = 0, kNone = 0,
kLeft = 1, kLeft = 1,
kUp = 2, kUp = 2,
kDown = 3, kDown = 3,
kRight = 4 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 #endif

View File

@ -6,128 +6,122 @@
#include "playerinterface.hpp" #include "playerinterface.hpp"
#include "gamestate.hpp" #include "gamestate.hpp"
GameEngine::GameEngine() namespace snakeplusplus
{ {
GameEngine::GameEngine()
{
InitializeGenerator(); InitializeGenerator();
return; 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);
player.Reset();
if (isBotControlled) {
while (!bot.path.empty()) { bot.path.pop(); }
}
PrepareGameBoard();
isGameOver = false;
return;
}
void GameEngine::Loop(void)
{
while (graphics.IsOpen())
{
if (isGameOver) { Reset(); }
UpdatePlayerSpeed(); UpdatePlayerSpeed();
PlaceNewSnakePart(MovePlayer()); PlaceNewSnakePart(MovePlayer());
RegenerateFood(); RegenerateFood();
currentScore = player.body.size() * 100; graphics.DisplayGameState(gameBoard);
if (!state.m_bNoDisplay)
graphics.DisplayGameState(gameBoard, currentScore);
} }
return; 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;
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.body.push(locationState);
player.headLocation = location; player.headLocation = location;
if (playerFood.location != location) if (playerFood.location != location)
player.Pop(); player.Pop();
else {
locationState->m_bFood = false;
if (state.m_bIsBotControlled)
bot.ResetPath();
}
} catch (const std::out_of_range& error) { } catch (const std::out_of_range& error) {
state.m_bIsGameOver = true; // Snake ran into edge isGameOver = true; // Snake ran into edge
} }
return; 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 // Generates new food until not colliding with player
gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bFood = 1; 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)
void GameEngine::PrepareGameBoard(void) {
{
gameBoard.clear(); gameBoard.clear();
sf::Vector2f boardDimensions = GetGameBoundaries(); sf::Vector2f boardDimensions = GetGameBoundaries();
gameBoard.resize(boardDimensions.y, std::vector<GameSpace>(boardDimensions.x)); gameBoard.resize(boardDimensions.y, std::vector<char> (boardDimensions.x, ' '));
// Snake setup // Snake setup
player.headLocation.x = GenerateRandomNumber(boardDimensions.x); player.headLocation.x = GenerateRandomNumber(boardDimensions.x);
player.headLocation.y = GenerateRandomNumber(boardDimensions.y); player.headLocation.y = GenerateRandomNumber(boardDimensions.y);
{ {
GameSpace* locationState = &gameBoard.at(player.headLocation.y).at(player.headLocation.x); char* locationState = &gameBoard.at(player.headLocation.y).at(player.headLocation.x);
player.body.push(locationState); player.body.push(locationState);
locationState->m_bSnake = true; *locationState = 'O';
} }
// Food setup // Food setup
playerFood.GenerateNewFood(boardDimensions); playerFood.GenerateNewFood(boardDimensions);
gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bFood = true; gameBoard.at(playerFood.location.y).at(playerFood.location.x) = 'X';
return; return;
} }
void GameEngine::UpdatePlayerSpeed(void) void GameEngine::UpdatePlayerSpeed(void)
{ {
PlayerDirection controller; PlayerDirection controller;
if (state.m_bIsBotControlled) { if (isBotControlled) {
if (bot.path.empty()) { if (bot.path.empty()) {
bot.GetNewPath(player.headLocation); bot.GetNewPath(gameBoard, player.headLocation, GetGameBoundaries(), player.body.size());
} }
controller = bot.GetInput(&player.headLocation); controller = bot.GetInput(&player.headLocation);
} }
@ -157,4 +151,5 @@ void GameEngine::UpdatePlayerSpeed(void)
break; 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;
class GameEngine
{
public:
GameEngine(); GameEngine();
void Start(void); void Start(void);
void Reset(void); void Reset(void);
sf::Vector2f GetGameBoundaries(void); sf::Vector2f GetGameBoundaries(void);
struct GameState { private:
unsigned char m_bIsGameOver : 1 = 0; std::vector< std::vector<char> > gameBoard;
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; PlayerOutput graphics;
Snake player; Snake player;
Food playerFood; Food playerFood;
AISnake bot; AISnake bot;
bool isGameOver = 0;
bool isBotControlled = 1;
void DisplayEndScreen(void);
void Loop(void); void Loop(void);
sf::Vector2f MovePlayer(void); sf::Vector2f MovePlayer(void);
void PlaceNewSnakePart(sf::Vector2f location); void PlaceNewSnakePart(sf::Vector2f location);
void RegenerateFood(void); void RegenerateFood(void);
void PrepareGameBoard(void); void PrepareGameBoard(void);
void UpdatePlayerSpeed(); void UpdatePlayerSpeed();
}; };
}
inline std::unique_ptr<GameEngine> g_pEngine;
#endif #endif

View File

@ -1,42 +1,8 @@
#include "gamestate.hpp" #include "gamestate.hpp"
#include <memory>
#include <string>
#include <vector>
#include <iostream>
void Help(void) { int main(void)
std::cout << "Usage: snakeplusplus [OPTIONS]" << std::endl; {
std::cout << "Options:" << std::endl; snakeplusplus::GameEngine game;
std::cout << "\t--server\tRun snake in server mode (also sets --bot)" << std::endl; game.Start();
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; return 0;
} }

View File

@ -1,10 +1,11 @@
#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
{ {
PlayerDirection GetPlayerInput(void)
{
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)
|| sf::Keyboard::isKeyPressed(sf::Keyboard::A)) || sf::Keyboard::isKeyPressed(sf::Keyboard::A))
return kLeft; return kLeft;
@ -18,15 +19,15 @@ PlayerDirection GetPlayerInput(void)
|| sf::Keyboard::isKeyPressed(sf::Keyboard::D)) || sf::Keyboard::isKeyPressed(sf::Keyboard::D))
return kRight; return kRight;
return kNone; return kNone;
} }
bool PlayerOutput::IsOpen(void) bool PlayerOutput::IsOpen(void)
{ {
return isWindowAlive; return gameWindow.isOpen();
} }
PlayerOutput::PlayerOutput(void) PlayerOutput::PlayerOutput(void)
{ {
float kWidth = 1025; float kWidth = 1025;
float kHeight = 725; float kHeight = 725;
float kBoardWidth = kWidth / kGridSize; float kBoardWidth = kWidth / kGridSize;
@ -35,10 +36,11 @@ PlayerOutput::PlayerOutput(void)
gameVideoSettings = sf::VideoMode(kWidth, kHeight); gameVideoSettings = sf::VideoMode(kWidth, kHeight);
drawObject.setSize(sf::Vector2f(kGridSize, kGridSize)); drawObject.setSize(sf::Vector2f(kGridSize, kGridSize));
return; return;
} }
void PlayerOutput::CheckContinue() void PlayerOutput::CheckContinue(bool isBotControlled)
{ {
if (isBotControlled) { return; }
DisplayEndScreen(); DisplayEndScreen();
while (true) while (true)
{ {
@ -47,16 +49,15 @@ void PlayerOutput::CheckContinue()
|| (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)))
{ {
gameWindow.close(); gameWindow.close();
isWindowAlive = false;
return; return;
} }
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Enter)) { return; } if (sf::Keyboard::isKeyPressed(sf::Keyboard::Enter)) { return; }
sf::sleep(delay); sf::sleep(delay);
} }
} }
void PlayerOutput::DisplayEndScreen(void) void PlayerOutput::DisplayEndScreen(void)
{ {
gameWindow.clear(); gameWindow.clear();
sf::Vector2f textPosition(gameBoundaries); sf::Vector2f textPosition(gameBoundaries);
textPosition.x = textPosition.x / 2; textPosition.x = textPosition.x / 2;
@ -68,100 +69,77 @@ void PlayerOutput::DisplayEndScreen(void)
gameWindow.draw(gameOverText); gameWindow.draw(gameOverText);
gameWindow.display(); gameWindow.display();
return; return;
} }
void PlayerOutput::DisplayScore(int score) { void PlayerOutput::DisplayGameState(std::vector< std::vector<char> >& gameBoard)
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(); CheckWindowEvents();
if (delay == sf::milliseconds(0)) { return; }
char* letterOnBoard; char* letterOnBoard;
for (float y = 0; y < gameBoundaries.y; y++) for (float y = 0; y < gameBoundaries.y; y++)
{ {
for (float x = 0; x < gameBoundaries.x; x++) for (float x = 0; x < gameBoundaries.x; x++)
{ {
if (gameBoard.at(y).at(x).m_bSnake) letterOnBoard = &gameBoard.at(y).at(x);
switch (*letterOnBoard)
{
case 'O':
DrawSnake(sf::Vector2f(x, y)); DrawSnake(sf::Vector2f(x, y));
else if (gameBoard.at(y).at(x).m_bFood) break;
case 'X':
DrawFood(sf::Vector2f(x,y)); DrawFood(sf::Vector2f(x,y));
else break;
default:
DrawEmpty(sf::Vector2f(x,y)); DrawEmpty(sf::Vector2f(x,y));
break;
}
} }
} }
DisplayScore(score);
gameWindow.display(); gameWindow.display();
sf::sleep(delay); sf::sleep(delay);
return; return;
} }
void PlayerOutput::StartGameWindow(void) void PlayerOutput::StartGameWindow(void)
{ {
gameWindow.create(gameVideoSettings, "SnakePlusPlus"); gameWindow.create(gameVideoSettings, "SnakePlusPlus");
isWindowAlive = true; isWindowAlive = true;
return; return;
} }
void PlayerOutput::SetShowGame(bool isShowing) { void PlayerOutput::CheckWindowEvents(void)
if (isShowing) { delay = sf::milliseconds(5); } {
else { delay = sf::milliseconds(0); }
return;
}
void PlayerOutput::CheckWindowEvents(void)
{
while (gameWindow.pollEvent(event)) while (gameWindow.pollEvent(event))
{ {
if ((event.type == sf::Event::Closed) if ((event.type == sf::Event::Closed)
|| (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))) { || (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)))
gameWindow.close(); 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) void PlayerOutput::DrawEmpty(sf::Vector2f location)
{ {
location *= static_cast<float>(kGridSize); location *= static_cast<float>(kGridSize);
drawObject.setPosition(location); drawObject.setPosition(location);
drawObject.setFillColor(sf::Color::Black); drawObject.setFillColor(sf::Color::Black);
gameWindow.draw(drawObject); gameWindow.draw(drawObject);
return; return;
} }
void PlayerOutput::DrawFood(sf::Vector2f location) void PlayerOutput::DrawFood(sf::Vector2f location)
{ {
location *= static_cast<float>(kGridSize); location *= static_cast<float>(kGridSize);
drawObject.setPosition(location); drawObject.setPosition(location);
drawObject.setFillColor(sf::Color::Red); drawObject.setFillColor(sf::Color::Red);
gameWindow.draw(drawObject); gameWindow.draw(drawObject);
return; return;
} }
void PlayerOutput::DrawSnake(sf::Vector2f location) void PlayerOutput::DrawSnake(sf::Vector2f location)
{ {
location *= static_cast<float>(kGridSize); location *= static_cast<float>(kGridSize);
drawObject.setPosition(location); drawObject.setPosition(location);
drawObject.setFillColor(sf::Color::Green); drawObject.setFillColor(sf::Color::Green);
gameWindow.draw(drawObject); gameWindow.draw(drawObject);
return; return;
}
} }

View File

@ -6,20 +6,20 @@
const int kGridSize = 25; const int kGridSize = 25;
PlayerDirection GetPlayerInput(void); namespace snakeplusplus
class PlayerOutput
{ {
public: PlayerDirection GetPlayerInput(void);
class PlayerOutput
{
public:
sf::Vector2f gameBoundaries; sf::Vector2f gameBoundaries;
PlayerOutput(void); PlayerOutput(void);
bool IsOpen(void); bool IsOpen(void);
void CheckContinue(); void CheckContinue(bool isBotControlled);
void DisplayGameState(std::vector< std::vector<GameSpace> >& gameBoard, int score); void DisplayGameState(std::vector< std::vector<char> >& gameBoard);
void DisplayScore(int score);
void StartGameWindow(void); void StartGameWindow(void);
void SetShowGame(bool isShowing); private:
private:
void CheckWindowEvents(void); void CheckWindowEvents(void);
void DisplayEndScreen(void); void DisplayEndScreen(void);
void DrawEmpty(sf::Vector2f location); void DrawEmpty(sf::Vector2f location);
@ -29,8 +29,9 @@ private:
sf::VideoMode gameVideoSettings; sf::VideoMode gameVideoSettings;
sf::RectangleShape drawObject; sf::RectangleShape drawObject;
sf::Event event; sf::Event event;
bool isWindowAlive = false; bool isWindowAlive;
sf::Time delay = sf::milliseconds(12); 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.front()) = ' ';
body.pop(); body.pop();
return; 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
{
public:
sf::Vector2f headLocation; sf::Vector2f headLocation;
sf::Vector2f speed; sf::Vector2f speed;
std::queue<GameSpace*> body; std::queue<char*> body;
void Pop(void); void Pop(void);
void Reset(void); void Reset(void);
}; };
struct Food struct Food
{ {
public: public:
sf::Vector2f location; sf::Vector2f location;
char* food;
void GenerateNewFood(sf::Vector2f boundaries); void GenerateNewFood(sf::Vector2f boundaries);
}; };
}
#endif #endif