I made a snake game in sfml and i'm kind of proud of the code's structure, but proud doesn't mean it's good, so i'm putting it here so that if there is something that could be bettered, you would know.
main.cpp
#ifndef UNICODE
#define UNICODE
#endif
#include "app.h"
int main() {
app game(800, 600, L"Test");
game.start();
game.end();
}
app.h
#pragma once
#include <SFML/Graphics.hpp>
#include "Snake.h"
#include "Board.h"
class app {
public:
app(int windowWidth, int windowHeight, const wchar_t* name);
~app() = default;
// Runs the app
void start();
void end();
private:
// MEMBER VARIABLES
const int winWidth, winHeight;
int score;
bool play;
sf::RenderWindow window;
Snake snake;
Board board;
// MEMBER FUNCTIONS
// Draws the objects
void drawWindow();
// Handles events
void handleEvents();
// Updates the window
void updateWindow();
};
app.cpp
#include "app.h"
#include <iostream>
#include <thread>
#include <chrono>
app::app(int windowWidth, int windowHeight, const wchar_t* name)
: winWidth{ windowWidth }, winHeight{ windowHeight }, score{ 0 },
play{ false } {
while (true) {
int choice;
std::wcout << L"Choose: " << std::endl;
std::wcout << L"1: Play " << std::endl;
std::wcout << L"2: Quit " << std::endl;
std::cin >> choice;
if (choice == 1) {
play = true;
break;
}
else break;
}
// Clears screen
for (size_t i = 0; i < 10; ++i)
std::wcout << L"\n\n\n\n\n\n\n\n\n\n\n\n" << std::endl;
if (play) {
window.create(sf::VideoMode(winWidth, winHeight), name);
window.setFramerateLimit(5);
}
}
// Handles any game event
void app::handleEvents() {
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
window.close();
break;
case sf::Event::TextEntered:
snake.changeDirection(static_cast<char>(event.text.unicode));
}
}
}
// Draws all game objects
void app::drawWindow() {
for (size_t i = 0, h = board.height(); i < h; ++i) {
for (size_t j = 0, w = board.width(); j < w; ++j) {
// Draws walls
if (board[i * w + j] == 2) {
sf::RectangleShape rect;
rect.setSize({ static_cast<float>(board.divisor()), static_cast<float>(board.divisor()) });
rect.setPosition({ static_cast<float>(board.divisor() * j), static_cast<float>(board.divisor() * i)});
window.draw(rect);
}
// Draws snake
else if (board[i * w + j] == 3) {
sf::RectangleShape rect;
rect.setFillColor(sf::Color::Green);
rect.setSize({ static_cast<float>(board.divisor()), static_cast<float>(board.divisor()) });
rect.setPosition({ static_cast<float>(board.divisor() * j), static_cast<float>(board.divisor() * i) });
window.draw(rect);
}
// Draws food
else if (board[i * w + j] == 4) {
sf::RectangleShape rect;
rect.setFillColor(sf::Color::Red);
rect.setSize({ static_cast<float>(board.divisor()), static_cast<float>(board.divisor()) });
rect.setPosition({ static_cast<float>(board.divisor() * j), static_cast<float>(board.divisor() * i) });
window.draw(rect);
}
}
}
}
// Updates the render window
void app::updateWindow() {
window.clear(sf::Color::Black);
drawWindow();
window.display();
}
// Starts the app
void app::start() {
while (window.isOpen()) {
handleEvents();
snake.move();
board.update(window, snake, &score);
updateWindow();
}
}
void app::end() {
if (play) {
std::wcout << L"You lose!" << std::endl;
std::wcout << L"Score: " << score << std::endl;
std::this_thread::sleep_for((std::chrono::milliseconds)3000);
}
}
Snake.h
#pragma once
#include <SFML/Graphics.hpp>
#include <vector>
class Snake {
public:
Snake();
~Snake() = default;
// Changes the dir value based on the input
void changeDirection(char input);
// Adds a piece to the snake
void add();
// Returns the size of snakeContainer
size_t getSnakeSize();
// Moves the snake
void move();
private:
// MEMBER VARIABLES
struct Snake_segment
{
int xPos, yPos, prevxPos, prevyPos;
};
const enum direction {
UP = 0,
RIGHT,
DOWN,
LEFT
};
std::vector<Snake_segment> snakeContainer;
direction dir;
// MEMBER FUNCTIONS
// Makes the segments follow the head
void follow();
// Moves the snake's head
void moveHead();
public:
// Operator overloading (i wasn't able to declare it in .cpp file)
Snake_segment operator[](int i) { return snakeContainer[i]; }
};
Snake.cpp
#include "Snake.h"
// Initializes a two-piece snake
Snake::Snake()
: dir { RIGHT } {
Snake_segment head { 10, 7, 9, 7 };
snakeContainer.push_back(head);
--head.xPos;
snakeContainer.push_back(head);
}
void Snake::add() {
Snake_segment newSegment;
newSegment.xPos = snakeContainer[snakeContainer.size() - 1].prevxPos;
newSegment.yPos = snakeContainer[snakeContainer.size() - 1].prevyPos;
snakeContainer.push_back(newSegment);
}
size_t Snake::getSnakeSize() {
return snakeContainer.size();
}
// Changes the direction based on input
void Snake::changeDirection(char input) {
switch (input) {
case 'w':
if (dir != DOWN) dir = UP;
break;
case 'd':
if (dir != LEFT) dir = RIGHT;
break;
case 's':
if (dir != UP) dir = DOWN;
break;
case 'a':
if (dir != RIGHT) dir = LEFT;
}
}
// All the pieces follow the head
void Snake::follow() {
for (size_t i = 1, n = snakeContainer.size(); i < n; ++i) {
snakeContainer[i].prevxPos = snakeContainer[i].xPos;
snakeContainer[i].prevyPos = snakeContainer[i].yPos;
snakeContainer[i].xPos = snakeContainer[i - 1].prevxPos;
snakeContainer[i].yPos = snakeContainer[i - 1].prevyPos;
}
}
// Moves the snake's head
void Snake::moveHead() {
snakeContainer[0].prevxPos = snakeContainer[0].xPos;
snakeContainer[0].prevyPos = snakeContainer[0].yPos;
switch (dir) {
case UP:
--snakeContainer[0].yPos;
break;
case RIGHT:
++snakeContainer[0].xPos;
break;
case DOWN:
++snakeContainer[0].yPos;
break;
case LEFT:
--snakeContainer[0].xPos;
}
}
// Moves the snake
void Snake::move() {
moveHead();
follow();
}
Board.h
#pragma once
#include <SFML/Graphics.hpp>
#include "Snake.h"
class Board {
public:
Board();
~Board() = default;
void update(sf::RenderWindow& win, Snake& snek, int* scor);
int width() const;
int height() const;
int divisor() const;
char operator[](int i) const;
private:
// MEMBER VARIABLES
std::string map;
const size_t mapWidth = 20;
const size_t mapHeight = 15;
// Is used to divide the screen in a grid
const int common_divisor = 40;
// MEMBER FUNCTIONS
// Checks if snek has collided with something
void genFood();
void checkCollisions(sf::RenderWindow& win, Snake& snek, int* scor);
};
Board.cpp
#include "Board.h"
#include <random>
Board::Board() {
// Creates a 20x15 grid
map = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
};
/*
REMINDER:
1 = FREE SPACE
2 = WALL
3 = SNAKE
4 = FOOD
*/
genFood();
}
void Board::genFood() {
int fx, fy;
do {
std::random_device gen;
std::uniform_int_distribution<int> disX(1, mapWidth - 1);
std::uniform_int_distribution<int> disY(1, mapHeight - 1);
fx = disX(gen);
fy = disY(gen);
} while (map[fy * mapWidth + fx] != 1);
map[fy * mapWidth + fx] = 4;
}
void Board::update(sf::RenderWindow& win, Snake& snek, int* scor) {
checkCollisions(win, snek, scor);
// Iterates through the whole map
for (size_t i = 0; i < mapHeight; ++i) {
for (size_t j = 0; j < mapWidth; ++j) {
// Makes walls
if (i == 0 || i == mapHeight - 1) map[i * mapWidth + j] = 2;
else if (j == 0 || j == mapWidth - 1) map[i * mapWidth + j] = 2;
// Sets free space
else if (map[i * mapWidth + j] != 4) map[i * mapWidth + j] = 1;
// Sets snek
for (size_t k = 0, n = snek.getSnakeSize(); k < n; ++k) {
if (snek[k].yPos == i && snek[k].xPos == j)
map[i * mapWidth + j] = 3;
}
}
}
}
void Board::checkCollisions(sf::RenderWindow& win, Snake& snek, int* scor) {
for (size_t i = 0; i < mapHeight; ++i) {
for (size_t j = 0; j < mapWidth; ++j) {
// Checks snek and wall collisions
if (map[snek[0].yPos * mapWidth + snek[0].xPos] == 2 ||
map[snek[0].yPos * mapWidth + snek[0].xPos] == 3 ) win.close();
// Checks snek and food collisions
else if (map[snek[0].yPos * mapWidth + snek[0].xPos] == 4) {
map[snek[0].yPos * mapWidth + snek[0].xPos] = 1;
snek.add();
*scor += 100;
genFood();
}
}
}
}
int Board::width() const { return mapWidth; }
int Board::height() const { return mapHeight; }
int Board::divisor() const { return common_divisor; }
char Board::operator[](int i) const { return map[i]; }