From the menu screen, when the user presses play, the game initializes (the snake body, food generator, and game clock are dynamically allocated). I keep these game elements in an anonymous namespace but I'm not sure if that's appropriate. After initialization, the game loop runs. Basically, Check for Collisions -> Check if Eating -> Update Snake Nodes -> Render and Display. I left out the supporting source files for food generation, the snakebody, etc for the sake of brevity, but let me know if you'd like me to share that.
#include "Game.h"
namespace
{
SnakeBody *snakebody;
FoodGenerator *foodgenerator;
sf::Clock *gameclock;
}
void Game::Start()
{
if (mGameState != UNINITIALIZED)
return;
mMainWindow.create(sf::VideoMode(windowparameters::RESOLUTION_X, windowparameters::RESOLUTION_Y, windowparameters::COLOR_DEPTH), "Snake!");
mGameState = SHOWING_MENU;
while (mGameState != EXITING)
GameLoop();
mMainWindow.close();
}
void Game::ShowMenuScreen()
{
} MainMenu menuScreen;
MainMenu::MenuResult result = menuScreen.Show(mMainWindow);
voidswitch Game::ShowMenuScreen(result)
{
MainMenu menuScreen;
MainMenu::MenuResult result = menuScreen.Show(mMainWindow);
switch (result)
{
case MainMenu::Exit:
mGameState = EXITING;
break;
case MainMenu::Play:
mGameState = RUNNING;
break;
}
}
}
Game::GameState Game::WaitForEnterOrExit()
{
GameState nextstate = GAMEOVER;
Gamesf::GameStateEvent Game::WaitForEnterOrExitcurrentevent;
while (nextstate != EXITING && nextstate != RUNNING)
{
GameState nextstate = GAMEOVER;
sf::Event currentevent;
while (nextstate != EXITING && nextstate != RUNNINGmMainWindow.pollEvent(currentevent))
{
whileif (mMainWindowcurrentevent.pollEventtype == sf::Event::EventType::KeyPressed &&
sf::Keyboard::isKeyPressed(currenteventsf::Keyboard::Enter))
{
if (currentevent.type == sf::Event::EventType::KeyPressed &&
sf::Keyboard::isKeyPressed(sf::Keyboard::Enter))
{
nextstate = RUNNING;
}
else if (currentevent.type == sf::Event::EventType::Closed)
{
nextstate = EXITING;
}
}
else if (currentevent.type == sf::Event::EventType::Closed)
{
nextstate = EXITING;
}
}
return nextstate;
}
void Game::InitializeGameElements()
{
snakebody = new SnakeBody();
foodgenerator = new FoodGenerator(windowparameters::RESOLUTION_X, windowparameters::RESOLUTION_Y, windowparameters::UNIT_SPACING);
gameclock = new sf::Clock();
}
void Game::CleanupGameElements()
{
delete(gameclock);
delete(snakebody);
delete(foodgenerator);
}
return nextstate;
}
void Game::InitializeGameElements()
{
snakebody = new SnakeBody();
foodgenerator = new FoodGenerator(windowparameters::RESOLUTION_X, windowparameters::RESOLUTION_Y, windowparameters::UNIT_SPACING);
gameclock = new sf::Clock();
}
void Game::CleanupGameElements()
{
delete(gameclock);
delete(snakebody);
delete(foodgenerator);
}
void Game::HandleEvents()
{
sf::Event currentevent;
while (mMainWindow.pollEvent(currentevent))
{
if (currentevent.type == sf::Event currentevent;
while (mMainWindow.pollEvent(currentevent)::EventType::KeyPressed)
{
if (currentevent.type == sf::EventKeyboard::EventTypeisKeyPressed(sf::KeyPressedKeyboard::Left))
{
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
snakebody->RedirectHead(SnakeBody::LEFT);
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
snakebody->RedirectHead(SnakeBody::RIGHT);
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
{
snakebody->RedirectHead(SnakeBody::UP);
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
{
snakebody->RedirectHead(SnakeBody::DOWN);
}
break;
}
else if (currentevent.type == sf::EventKeyboard::EventTypeisKeyPressed(sf::ClosedKeyboard::Right))
{
mGameStatesnakebody->RedirectHead(SnakeBody::RIGHT);
= EXITING; }
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
{
mMainWindow.close snakebody->RedirectHead(SnakeBody::UP);
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
{
snakebody->RedirectHead(SnakeBody::DOWN);
}
break;
}
else if (currentevent.type == sf::Event::EventType::Closed)
{
mGameState = EXITING;
mMainWindow.close();
}
}
}
void Game::GameTick()
{
// tick scene
voidif Game(gameclock->getElapsedTime().asMilliseconds() >= windowparameters::GameTick(TIC_RATE_IN_MS)
{
// tickCheck sceneCollision with body
if (gameclocksnakebody->getElapsedTime>CheckCollision().asMilliseconds()
>= windowparameters::TIC_RATE_IN_MS mGameState = GAMEOVER;
else if (snakebody->CheckEating(foodgenerator->mGraphic))
{
// Check Collision with body
if (snakebody->CheckCollision())
mGameState = GAMEOVER;
else if (snakebody->CheckEating(foodgenerator->mGraphic))
{
snakebody->IncrementSegments();
foodgenerator->mUneaten = false;
std::cout << "SCORE = " << snakebody->mNumSegments << std::endl;
}
// update snake
snakebody->UpdateSegments(0, windowparameters::RESOLUTION_X, 0, windowparameters::RESOLUTION_Y);
// update food
if (!foodgenerator->mUneaten)
foodgenerator->Generate(snakebody);
// reset screen, render, display
mMainWindow.clear(sf::Color(230, 230, 230));
mMainWindow.draw(foodgenerator->mGraphic);
snakebody->DrawSegments(mMainWindow);
mMainWindow.display();
gameclock->restart();
}
// update snake
snakebody->UpdateSegments(0, windowparameters::RESOLUTION_X, 0, windowparameters::RESOLUTION_Y);
// update food
if (!foodgenerator->mUneaten)
foodgenerator->Generate(snakebody);
// reset screen, render, display
mMainWindow.clear(sf::Color(230, 230, 230));
mMainWindow.draw(foodgenerator->mGraphic);
snakebody->DrawSegments(mMainWindow);
mMainWindow.display();
gameclock->restart();
}
}
void Game::GameLoop()
{
while (true)
{
whileswitch (truemGameState)
{
case SHOWING_MENU:
switch (mGameState)
ShowMenuScreen();
{
break;
case SHOWING_MENUGAMEOVER:
mGameState = ShowMenuScreenWaitForEnterOrExit();
break;
case GAMEOVERRUNNING:
mGameState = WaitForEnterOrExitInitializeGameElements();
break;
case RUNNING:
InitializeGameElements();
// run game loop
while (mMainWindow.isOpen() && mGameState == RUNNING)
{
HandleEvents();
GameTick();
}
CleanupGameElements();
break;
case EXITING:
mMainWindow.close();
break;
default:
mMainWindow.close();
break;
}
CleanupGameElements();
break;
case EXITING:
mMainWindow.close();
break;
default:
mMainWindow.close();
break;
}
}
}
// Because Game is a static class, the member variables need to be instantiated MANUALLY
Game::GameState Game::mGameState = Game::UNINITIALIZED;
sf::RenderWindow Game::mMainWindow;
#pragma once
#include <cstdint>
#include <iostream>
#include "FoodGenerator.h"
#include "MainMenu.h"
#include "SFML\Window.hpp"
#include "SFML\Graphics.hpp"
#include "SnakeBody.h"
namespace windowparameters
{
const uint16_t RESOLUTION_X = 1024;
const uint16_t RESOLUTION_Y = 768;
const uint8_t COLOR_DEPTH = 32;
const uint16_t TIC_RATE_IN_MS = 60;
const uint8_t UNIT_SPACING = 32;
}
class Game
{
public:
static void Start();
private:
enum GameState { UNINITIALIZED, SHOWING_MENU, RUNNING, EXITING, GAMEOVER };
static void GameLoop();
static void ShowMenuScreen();
static void InitializeGameElements();
static void CleanupGameElements();
static void HandleEvents();
static void GameTick();
static GameState WaitForEnterOrExit();
static GameState mGameState;
static sf::RenderWindow mMainWindow;
};
#pragma once
#include <random>
#include "Coordinate.h"
#include "SnakeBody.h"
class FoodGenerator
{
public:
FoodGenerator::FoodGenerator(int xmax, int ymax, int spacing);
Coordinate Generate(SnakeBody *snakeBody);
bool mUneaten;
int mXMax;
int mYMax;
int mSpacing;
Coordinate mCurrentLocation;
sf::RectangleShape mGraphic;
private:
std::uniform_int_distribution<int> uniX;
std::uniform_int_distribution<int> uniY;
std::random_device rd;
std::mt19937 rng;
};
#include "FoodGenerator.h"
FoodGenerator::FoodGenerator(int xmax, int ymax, int spacing)
{
rng = std::mt19937(rd()); // random-number engine used (Mersenne-Twister in this case)
uniX = std::uniform_int_distribution<int>(1, xmax/spacing - 1); // guaranteed unbiased
uniY = std::uniform_int_distribution<int>(1, ymax/spacing - 1); // guaranteed unbiased
mGraphic = sf::RectangleShape(sf::Vector2f(spacing, spacing));
mGraphic.setFillColor(sf::Color(0, 0, 128));
mGraphic.setOrigin(0, 0);
mUneaten = false;
mXMax = xmax;
mYMax = ymax;
mSpacing = spacing;
}
Coordinate FoodGenerator::Generate(SnakeBody *snakeBody)
{
bool freePosFound = false;
int xPos, yPos;
std::list<SnakeBody::SnakeSegment>::iterator it, head, end;
it = snakeBody->mSegments.begin();
head = snakeBody->mSegments.begin();
end = snakeBody->mSegments.end();
while (!freePosFound)
{
xPos = uniX(rng);
yPos = uniY(rng);
mGraphic.setPosition(xPos*mSpacing, yPos*mSpacing);
while (it != end)
{
if (it->mGraphic.getGlobalBounds().intersects(mGraphic.getGlobalBounds()))
{
it = head;
break;
}
it++;
}
if (it == end)
freePosFound = true;
}
mUneaten = true;
return Coordinate(xPos, yPos);
}
#pragma once
#include <list>
#include "SFML\Graphics.hpp"
class MainMenu
{
public:
enum MenuResult {Nothing, Exit, Play};
struct MenuItem
{
MenuResult action;
sf::Rect<int> rect;
};
MenuResult Show(sf::RenderWindow& window);
private:
MenuResult GetMenuResponse(sf::RenderWindow& window);
MenuResult HandleClick(int x, int y);
std::list<MenuItem> mMenuItems;
};
#include "MainMenu.h"
MainMenu::MenuResult MainMenu::Show(sf::RenderWindow& window)
{
sf::Texture image;
image.loadFromFile("C:/Users/Carter/Pictures/snake_menu.jpg");
sf::Sprite sprite(image);
MenuItem playButton;
playButton.rect.left = 200;
playButton.rect.top = 525;
playButton.rect.width = 600;
playButton.rect.height = 100;
playButton.action = Play;
MenuItem exitButton;
exitButton.rect.left = 200;
exitButton.rect.top = 630;
exitButton.rect.width = 600;
exitButton.rect.height = 100;
exitButton.action = Exit;
mMenuItems.push_back(playButton);
mMenuItems.push_back(exitButton);
window.draw(sprite);
window.display();
return GetMenuResponse(window);
}
MainMenu::MenuResult MainMenu::HandleClick(int x, int y)
{
std::list<MenuItem>::iterator it;
for (it = mMenuItems.begin(); it != mMenuItems.end(); it++)
{
sf::Rect<int> menuItemRect = (*it).rect;
if((x > menuItemRect.left) &&
(x < (menuItemRect.left + menuItemRect.width)) &&
(y > menuItemRect.top) &&
(y < (menuItemRect.top + menuItemRect.height)))
{
return (*it).action;
}
}
return Nothing;
}
MainMenu::MenuResult MainMenu::GetMenuResponse(sf::RenderWindow& window)
{
sf::Event menuEvent;
while (true)
{
while (window.pollEvent(menuEvent))
{
if (menuEvent.type == sf::Event::EventType::MouseButtonPressed)
return HandleClick(menuEvent.mouseButton.x, menuEvent.mouseButton.y);
if (menuEvent.type == sf::Event::EventType::Closed)
return Exit;
}
}
}
#pragma once
#include <cstdint>
#include <list>
#include "Coordinate.h"
#include "SFML\Graphics.hpp"
class SnakeBody
{
public:
SnakeBody();
enum SnakeDirection { LEFT, RIGHT, UP, DOWN };
class SnakeSegment
{
public:
SnakeSegment(int x, int y, SnakeDirection dir);
void UpdatePosition();
bool CheckBounds(int xmin, int xmax, int ymin, int ymax);
Coordinate GetPosition();
void SetPosition(int x, int y);
SnakeDirection GetDirection();
void SetDirection(SnakeDirection dir);
sf::RectangleShape mGraphic;
private:
Coordinate mPosition;
SnakeDirection mDirection;
};
void UpdateSegments(int xmin, int xmax, int ymin, int ymax);
void DrawSegments(sf::RenderWindow &window);
void RedirectHead(SnakeDirection newDir);
void IncrementSegments();
bool CheckCollision();
bool CheckEating(sf::RectangleShape foodGraphic);
int mNumSegments;
std::list<SnakeSegment> mSegments;
};
#include "SnakeBody.h"
namespace
{
const uint8_t SNAKE_MOVE_PER_TICK = 32;
const uint8_t BODY_DIM = 32;
}
SnakeBody::SnakeSegment::SnakeSegment(int x, int y, SnakeBody::SnakeDirection dir)
{
SetPosition(x, y);
SetDirection(dir);
mGraphic = sf::RectangleShape(sf::Vector2f(BODY_DIM, BODY_DIM));
mGraphic.setFillColor(sf::Color(34, 139, 34));
mGraphic.setOrigin(BODY_DIM / 2, BODY_DIM / 2);
mGraphic.setPosition(sf::Vector2f(x, y));
}
Coordinate SnakeBody::SnakeSegment::GetPosition()
{
return mPosition;
}
void SnakeBody::SnakeSegment::SetPosition(int x, int y)
{
mPosition.mXCoord = x;
mPosition.mYCoord = y;
mGraphic.setPosition(sf::Vector2f(x, y));
}
SnakeBody::SnakeDirection SnakeBody::SnakeSegment::GetDirection()
{
return mDirection;
}
void SnakeBody::SnakeDirection SnakeBodySnakeSegment::SnakeSegmentSetDirection(SnakeBody::GetDirectionSnakeDirection dir)
{
// prevent 180 degree turns about the head
switch (dir)
{
case LEFT:
return mDirection; if (mDirection == RIGHT)
return;
break;
case RIGHT:
if (mDirection == LEFT)
return;
break;
case UP:
if (mDirection == DOWN)
return;
break;
case DOWN:
if (mDirection == UP)
return;
break;
}
SnakeSegment::mDirection = voiddir;
}
bool SnakeBody::SnakeSegment::SetDirectionCheckBounds(SnakeBody::SnakeDirectionint dirxmin, int xmax, int ymin, int ymax)
{
bool wrapped = false;
int xrange = xmax - xmin;
int yrange = ymax - ymin;
// check bounds and wrap
if (mPosition.mXCoord < xmin)
{
// prevent 180 degree turns about the head
switch (dir)
{
case LEFT:
if (mDirection == RIGHT)
return;
break;
case RIGHT:
if (mDirection == LEFT)
return;
break;
case UP:
if (mDirection == DOWN)
return;
break;
case DOWN:
if (mDirection == UP)
return;
break;
}
mPosition.mXCoord += xrange;
SnakeSegment::mDirectionwrapped = dir;true;
}
else boolif SnakeBody::SnakeSegment::CheckBounds(int xmin,mPosition.mXCoord int> xmax, int ymin, int ymax)
{
bool wrapped = false;
int xrange = xmax - xmin;
int yrange = ymax - ymin;
// check bounds and wrap
if (mPosition.mXCoord < xmin)
{
mPosition.mXCoord += xrange;
wrapped = true;
}
else if (mPosition.mXCoord > xmax)
{
mPosition.mXCoord %= xrange;
wrapped = true;
}
else if (mPosition.mYCoord < ymin)
{
mPosition.mYCoord += yrange;
wrapped = true;
}
else if (mPosition.mYCoord > ymax)
{
mPosition.mYCoord %= yrange;
wrapped = true;
}
if(wrapped)
mGraphic.setPosition(mPosition.mXCoord, mPosition.mYCoord);
return wrapped;
}
else if (mPosition.mYCoord void< SnakeBody::SnakeSegment::UpdatePosition(ymin)
{
// check direction and increment
switch (mDirection)
{
case LEFT:
mPosition.IncrementX(-SNAKE_MOVE_PER_TICK);
break;
case RIGHT:
mPosition.IncrementX(SNAKE_MOVE_PER_TICK);
break;
case UP:
mPosition.IncrementY(-SNAKE_MOVE_PER_TICK);
break;
case DOWN:
mPosition.IncrementY(SNAKE_MOVE_PER_TICK);
break;
mYCoord += }yrange;
wrapped mGraphic.setPosition(sf::Vector2f(mPosition.mXCoord,= mPosition.mYCoord));true;
}
else if (mPosition.mYCoord > SnakeBody::SnakeBody(ymax)
{
SnakeBody::SnakeSegment headSegment(BODY_DIM/2, BODY_DIM/2, RIGHT);
//SnakeBody::SnakeSegment testSegment(100 - BODY_DIM,mPosition.mYCoord 100,%= RIGHT);yrange;
mNumSegmentswrapped = 1;
mSegments.push_back(headSegment);
//_segments.push_back(testSegment);true;
}
if(wrapped)
mGraphic.setPosition(mPosition.mXCoord, mPosition.mYCoord);
return wrapped;
}
void SnakeBody::UpdateSegmentsSnakeSegment::UpdatePosition(int)
{
xmin, int xmax, int// ymin,check intdirection ymaxand increment
switch (mDirection)
{
// update segments starting at tail
case std::list<SnakeSegment>:LEFT:iterator front, it, next, end;
it = --mSegments.end();
end = mSegmentsmPosition.end();
if (mNumSegments > 1)
next = --IncrementX(--mSegments.end()SNAKE_MOVE_PER_TICK);
elsebreak;
next = end;
case RIGHT:
front = mSegmentsmPosition.begin();
for(int i=0; i < mNumSegments; i++)
{
// increment position
it->UpdatePosition();
it->CheckBoundsIncrementX(xmin, xmax, ymin, ymaxSNAKE_MOVE_PER_TICK);
// update direction for non-head nodesbreak;
if ((it != front) && (it->GetDirection() !=case next->GetDirection())){UP:
it->SetDirectionmPosition.IncrementY(next->GetDirection()SNAKE_MOVE_PER_TICK);
}
if ((next != front) && next != end)break;
next--;
case DOWN:
if mPosition.IncrementY(it != frontSNAKE_MOVE_PER_TICK)
it--;
}break;
}
mGraphic.setPosition(sf::Vector2f(mPosition.mXCoord, mPosition.mYCoord));
}
SnakeBody::SnakeBody()
{
SnakeBody::SnakeSegment headSegment(BODY_DIM/2, BODY_DIM/2, RIGHT);
//SnakeBody::SnakeSegment testSegment(100 - BODY_DIM, 100, RIGHT);
mNumSegments = 1;
mSegments.push_back(headSegment);
//_segments.push_back(testSegment);
}
void SnakeBody::DrawSegmentsUpdateSegments(sfint xmin, int xmax, int ymin, int ymax)
{
// update segments starting at tail
std::RenderWindowlist<SnakeSegment>::iterator &windowfront, it, next, end;
it = --mSegments.end();
end = mSegments.end();
if (mNumSegments > 1)
next = --(--mSegments.end());
else
next = end;
front = mSegments.begin();
for(int i=0; i < mNumSegments; i++)
{
std::list<SnakeSegment>::iterator it// =increment mSegments.begin();position
std::list<SnakeSegment>::iterator end = mSegments.endit->UpdatePosition();
it->CheckBounds(xmin, xmax, ymin, ymax);
while (it != end)
// update direction for {non-head nodes
if ((it != front) && window.draw(it->mGraphic>GetDirection(); != next->GetDirection())){
it++;it->SetDirection(next->GetDirection());
}
if ((next != }
front) && next != end)
void SnakeBody::RedirectHead(SnakeBody::SnakeDirection newDir)
{ next--;
std::list<SnakeSegment>::iteratorif head(it != mSegments.begin(front);
head it->SetDirection(newDir)-;
}
}
void SnakeBody::DrawSegments(sf::RenderWindow &window)
{
std::list<SnakeSegment>::iterator it void= SnakeBodymSegments.begin();
std::IncrementSegmentslist<SnakeSegment>::iterator end = mSegments.end();
while (it != end)
{
// find location of last node
std::list<SnakeSegment>::iterator tail = --mSegments.end();
// spawn at offset location
int newX, newY;
newX = (tail->GetPosition()).mXCoord;
newY = (tail->GetPosition())window.mYCoord;
switch (tail->GetDirection())
{
case LEFT:
newX += BODY_DIM;
break;
case RIGHT:
newX -= BODY_DIM;
break;
case UP:
newY += BODY_DIM;
break;
case DOWN:
newY -= BODY_DIM;
break;
}
SnakeSegment newSegmentdraw(newX, newY, tailit->GetDirection());
mSegments.push_back(newSegment>mGraphic);
mNumSegments++;it++;
}
}
void SnakeBody::RedirectHead(SnakeBody::SnakeDirection newDir)
{
bool std::list<SnakeSegment>::iterator head = mSegments.begin();
head->SetDirection(newDir);
}
void SnakeBody::CheckCollisionIncrementSegments()
{
// find location of last node
std::list<SnakeSegment>::iterator tail = --mSegments.end();
// spawn at offset location
int newX, newY;
newX = (tail->GetPosition()).mXCoord;
newY = (tail->GetPosition()).mYCoord;
switch (tail->GetDirection())
{
case sf:LEFT:RectangleShape
headRect = (mSegments.begin())->mGraphic;
newX += BODY_DIM;
std::list<SnakeSegment>::iterator it = ++mSegments.begin();
break;
case RIGHT:
for (int i = 1; i < mNumSegments;newX i++,-= it++)BODY_DIM;
{break;
case UP:
if (headRect.getGlobalBounds().intersects(it->mGraphic.getGlobalBounds()))
newY += BODY_DIM;
break;
case DOWN:
return true;
newY -= }BODY_DIM;
return false;break;
}
SnakeSegment newSegment(newX, newY, tail->GetDirection());
mSegments.push_back(newSegment);
mNumSegments++;
}
bool SnakeBody::CheckEatingCheckCollision()
{
sf::RectangleShape foodGraphicheadRect = (mSegments.begin())->mGraphic;
std::list<SnakeSegment>::iterator it = ++mSegments.begin();
for (int i = 1; i < mNumSegments; i++, it++)
{
std::list<SnakeSegment>::iterator head =if mSegments(headRect.begingetGlobalBounds();.intersects(it->mGraphic.getGlobalBounds()))
return head->mGraphic.getGlobalBounds().intersects(foodGraphic.getGlobalBounds());true;
}
return false;
}
bool SnakeBody::CheckEating(sf::RectangleShape foodGraphic)
{
std::list<SnakeSegment>::iterator head = mSegments.begin();
return head->mGraphic.getGlobalBounds().intersects(foodGraphic.getGlobalBounds());
}
#include "Game.h"
int main()
{
Game::Start();
return 0;
}