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]; }