This commit is contained in:
2026-05-10 15:10:25 +02:00
commit 63b661a538
23 changed files with 5765 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
# Build directories
cmake-build-*/
build/
out/
# CMake
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
compile_commands.json
Makefile
# Ninja
.ninja_deps
.ninja_log
# CLion / IntelliJ
.idea/
# macOS
.DS_Store
# Linux
*.swp
# Windows
*.exe
*.dll
*.pdb
# Compiled objects
*.o
*.obj
*.a
*.lib
*.so
*.dylib
# Executables
*.out
# Raylib generated assets/cache if needed
*.rres

35
CMakeLists.txt Normal file
View File

@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 4.2)
project(snake)
set(CMAKE_CXX_STANDARD 20)
add_executable(snake src/main.cpp
src/core/App.cpp
src/core/App.h
src/external/External.cpp
src/external/External.h
src/core/Game.cpp
src/core/Game.h
src/external/Config.cpp
src/external/Config.h
src/external/Time.cpp
src/external/Time.h
src/core/Board.cpp
src/core/Board.h
src/core/Renderer.cpp
src/core/Renderer.cpp
src/core/Renderer.h
src/core/Direction.h
src/core/Snake.cpp
src/core/Snake.h
src/core/Direction.cpp
)
target_include_directories(snake PRIVATE include)
target_link_directories(snake PRIVATE lib)
target_link_libraries(snake PRIVATE
raylib
X11
)

1743
include/raylib.h Normal file

File diff suppressed because it is too large Load Diff

3139
include/raymath.h Normal file

File diff suppressed because it is too large Load Diff

63
src/core/App.cpp Normal file
View File

@@ -0,0 +1,63 @@
//
// Created by stewki on 09/05/2026.
//
#include "App.h"
#include <raylib.h>
#include <cstdlib>
App::App() {
m_external = new External();
m_game = new Game(
config()->getSizeX(),
config()->getSizeY(),
m_external
);
}
App::~App() {
delete m_game;
delete m_external;
}
void App::run(int argc, char **argv) {
this->init();
this->runLoop();
this->close();
}
void App::init() {
const int sizeField = config()->getSizeField();
const int sizeX = config()->getSizeX();
const int sizeY = config()->getSizeY();
InitWindow(sizeX * sizeField, sizeY * sizeField, "Snake");
time()->reset();
std::srand(std::time(0));
}
void App::close() {
CloseWindow();
}
void App::runLoop() {
while (!WindowShouldClose()) {
m_game->update(time()->getDeltaTime());
m_game->render();
}
}
Config * App::config() const {
return m_external->getConfig();
}
Time * App::time() const {
return m_external->getTime();
}
Game * App::game() const {
return m_game;
}

36
src/core/App.h Normal file
View File

@@ -0,0 +1,36 @@
//
// Created by stewki on 09/05/2026.
//
#ifndef SNAKE_APP_H
#define SNAKE_APP_H
#include "Game.h"
#include "../external/External.h"
class App {
public:
App();
~App();
void run(int argc, char **argv);
private:
void init();
void close();
void runLoop();
private:
[[nodiscard]] Game *game() const;
[[nodiscard]] Config *config() const;
[[nodiscard]] Time *time() const;
private:
Game* m_game;
External* m_external;
};
#endif //SNAKE_APP_H

137
src/core/Board.cpp Normal file
View File

@@ -0,0 +1,137 @@
//
// Created by stewki on 09/05/2026.
//
#include "Board.h"
#include <cstdlib>
#include "raymath.h"
Board::Board(const int sizeX, const int sizeY, External *external)
: m_sizeX(sizeX), m_sizeY(sizeY), m_external(external)
{
m_fieldsMatrix = new Field * [m_sizeY];
for (int i = 0; i < m_sizeY; i++) {
m_fieldsMatrix[i] = new Field[m_sizeX];
}
m_snake = new Snake();
}
Board::~Board() {
for (int i = 0; i < m_sizeY; i++) {
delete[] m_fieldsMatrix[i];
}
delete[] m_fieldsMatrix;
delete m_snake;
}
void Board::reset() {
clear();
initSnake();
putAppleOnRandomPlace();
}
void Board::clear() {
for (int i = 0; i<m_sizeY; i++) {
for (int j = 0; j<m_sizeX; j++) {
m_fieldsMatrix[i][j] = Field::Empty;
}
}
m_appleX = 0;
m_appleY = 0;
}
void Board::initSnake() {
const int initSize = 3;
int x = std::rand() % (m_sizeX - initSize * 2) + initSize;
int y = std::rand() % (m_sizeY - initSize * 2) + initSize;
Vector2 headPosition = {static_cast<float>(x), static_cast<float>(y)};
auto direction = static_cast<Direction>(std::rand() % 4);
m_snake->init(headPosition, initSize, direction);
setField(headPosition, Field::Head);
Vector2 tailPosition = headPosition;
Vector2 directionVector = DirectionUnitVector(DirectionInvert(direction));
for (int i = 1; i < initSize; i++) {
tailPosition = Vector2Add(tailPosition, directionVector);
setField(tailPosition, Field::Tail);
}
}
void Board::moveSnake(Direction direction) {
const Vector2 oldHeadPosition = m_snake->getHeadPosition();
m_snake->move(direction);
const Vector2 newHeadPosition = m_snake->getHeadPosition();
Field bumpedField = getField(newHeadPosition);
if (bumpedField == Field::Apple) {
putAppleOnRandomPlace();
}
else if (bumpedField == Field::Tail) {
exit(-1);
}
else if (isPositionOutside(newHeadPosition)) {
exit(-2);
}
else {
Vector2 lastInTailPosition = m_snake->removeLastFromTail();
setField(lastInTailPosition, Field::Empty);
}
setField(oldHeadPosition, Field::Tail);
setField(newHeadPosition, Field::Head);
}
void Board::putAppleOnRandomPlace() {
int x, y;
do {
x = std::rand() % m_sizeX;
y = std::rand() % m_sizeY;
}
while (m_fieldsMatrix[y][x] != Field::Empty);
repositionApple(x, y);
}
void Board::repositionApple(int x, int y) {
if (m_fieldsMatrix[m_appleY][m_appleX] == Field::Apple) {
m_fieldsMatrix[m_appleY][m_appleX] = Field::Empty;
}
m_appleX = x;
m_appleY = y;
m_fieldsMatrix[y][x] = Field::Apple;
}
Field ** Board::getFields() const {
return m_fieldsMatrix;
}
void Board::swapFields(const int x1, const int y1, const int x2, const int y2) const {
const auto tempField = m_fieldsMatrix[y1][x1];
m_fieldsMatrix[y1][x1] = m_fieldsMatrix[y2][x2];
m_fieldsMatrix[y2][x2] = tempField;
}
Field Board::getField(const Vector2 position) const {
return m_fieldsMatrix[static_cast<int>(position.y)][static_cast<int>(position.x)];
}
void Board::setField(const Vector2 position, const Field field) const {
m_fieldsMatrix[static_cast<int>(position.y)][static_cast<int>(position.x)] = field;
}
bool Board::isPositionOutside(Vector2 position) const {
return position.y < 0 || position.y >= m_sizeY
|| position.x < 0 || position.x >= m_sizeX;
}

57
src/core/Board.h Normal file
View File

@@ -0,0 +1,57 @@
//
// Created by stewki on 09/05/2026.
//
#ifndef SNAKE_BOARD_H
#define SNAKE_BOARD_H
#include <vector>
#include "Direction.h"
#include "Snake.h"
#include "../external/External.h"
enum Field {
Empty = 0,
Head = 1,
Tail = 2,
Apple = 3
};
class Board {
public:
Board(int sizeX, int sizeY, External *external);
~Board();
public:
void reset();
void clear();
// Snake
void initSnake();
void moveSnake(Direction direction);
// Apple
void putAppleOnRandomPlace();
void repositionApple(int x, int y);
// Fields
Field ** getFields() const;
void swapFields(int x1, int y1, int x2, int y2) const;
Field getField(Vector2 position) const;
void setField(Vector2 position, Field field) const;
bool isPositionOutside(Vector2 position) const;
private:
int m_sizeX, m_sizeY;
Field ** m_fieldsMatrix;
Snake *m_snake;
int m_appleX = 0, m_appleY = 0;
External *m_external;
};
#endif //SNAKE_BOARD_H

50
src/core/Direction.cpp Normal file
View File

@@ -0,0 +1,50 @@
//
// Created by stewki on 10/05/2026.
//
#include "Direction.h"
Vector2 DirectionUnitVector(Direction direction) {
switch (direction) {
case Right:
return {1, 0};
break;
case Up:
return {0, -1};
break;
case Left:
return {-1, 0};
break;
case Down:
return {0, 1};
break;
}
return {0, 0};
}
Direction DirectionInvert(Direction direction) {
const int directionNumber = direction;
return static_cast<Direction>((directionNumber + 2) % 4); // Rotate 180 deg
}
std::optional<Direction> DirectionFrom2VectorsInLine(Vector2 first, Vector2 second) {
if (first.x == second.x) {
if (first.y > second.y) {
return Direction::Up;
}
if (first.y < second.y) {
return Direction::Down;
}
}
if (first.y == second.y) {
if (first.x > second.x) {
return Direction::Left;
}
if (first.x < second.x) {
return Direction::Right;
}
}
return std::nullopt;
}

22
src/core/Direction.h Normal file
View File

@@ -0,0 +1,22 @@
//
// Created by stewki on 09/05/2026.
//
#ifndef SNAKE_DIRECTION_H
#define SNAKE_DIRECTION_H
#include <optional>
#include "raylib.h"
enum Direction {
Right = 0,
Up = 1,
Left = 2,
Down = 3
};
Vector2 DirectionUnitVector(Direction direction);
Direction DirectionInvert(Direction direction);
std::optional<Direction> DirectionFrom2VectorsInLine(Vector2 first, Vector2 second);
#endif //SNAKE_DIRECTION_H

70
src/core/Game.cpp Normal file
View File

@@ -0,0 +1,70 @@
//
// Created by stewki on 09/05/2026.
//
#include "Game.h"
#include "raylib.h"
Game::Game(const int sizeX, const int sizeY, External *external)
: m_sizeX(sizeX), m_sizeY(sizeY), m_external(external)
{
m_board = new Board(m_sizeX, m_sizeY, external);
m_board->reset();
m_currentDirection = Direction::Left;
m_turnTime = m_external->getConfig()->getTurnTimeSeconds();
m_timeElapsedSinceLastTurn = 0.0;
}
Game::~Game() {
delete m_board;
}
void Game::update(double deltaTime) {
m_timeElapsedSinceLastTurn += deltaTime;
catchInput();
if (checkTurnPassed()) {
// m_board->putAppleOnRandomPlace();
m_board->moveSnake(m_currentDirection);
}
}
void Game::render() {
BeginDrawing();
ClearBackground(WHITE);
int sizeField = m_external->getConfig()->getSizeField();
m_renderer.renderFields(m_board->getFields(), m_sizeX, m_sizeY, sizeField);
EndDrawing();
}
void Game::catchInput() {
switch (GetKeyPressed()) {
case KEY_RIGHT:
m_currentDirection = Direction::Right;
return;
case KEY_UP:
m_currentDirection = Direction::Up;
return;
case KEY_LEFT:
m_currentDirection = Direction::Left;
return;
case KEY_DOWN:
m_currentDirection = Direction::Down;
return;
default:;
}
}
bool Game::checkTurnPassed() {
if (m_timeElapsedSinceLastTurn > m_turnTime) {
m_timeElapsedSinceLastTurn = 0.0;
return true;
}
return false;
}

42
src/core/Game.h Normal file
View File

@@ -0,0 +1,42 @@
//
// Created by stewki on 09/05/2026.
//
#ifndef SNAKE_GAME_H
#define SNAKE_GAME_H
#include "Direction.h"
#include "Renderer.h"
#include "../external/External.h"
class Game {
public:
Game(int sizeX, int sizeY, External *external);
~Game();
public:
void update(double deltaTime);
void render();
private:
void catchInput();
private:
bool checkTurnPassed();
private:
int m_sizeX, m_sizeY;
Direction m_currentDirection;
double m_turnTime;
double m_timeElapsedSinceLastTurn;
Board *m_board;
Renderer m_renderer;
External *m_external;
};
#endif //SNAKE_GAME_H

39
src/core/Renderer.cpp Normal file
View File

@@ -0,0 +1,39 @@
//
// Created by stewki on 09/05/2026.
//
#include "Renderer.h"
void Renderer::renderFields(Field **fieldsMatrix, int sizeX, int sizeY, int sizeField) {
for (int i = 0; i < sizeY; i++) {
for (int j = 0; j < sizeX; j++) {
int x = j * sizeField;
int y = i * sizeField;
renderField(x, y, fieldsMatrix[i][j], sizeField);
}
}
}
void Renderer::renderField(int x, int y, Field field, int sizeField) {
int pad = 1;
DrawRectangle(x+pad, y+pad, sizeField-2*pad, sizeField-2*pad, getFieldColor(field));
}
Color Renderer::getFieldColor(const Field field) {
auto color = GRAY;
switch (field) {
case Empty:
color = WHITE;
break;
case Head:
color = DARKGREEN;
break;
case Tail:
color = GREEN;
break;
case Apple:
color = RED;
break;
}
return color;
}

22
src/core/Renderer.h Normal file
View File

@@ -0,0 +1,22 @@
//
// Created by stewki on 09/05/2026.
//
#ifndef SNAKE_RENDERER_H
#define SNAKE_RENDERER_H
#include "Board.h"
#include "raylib.h"
class Renderer {
public:
void renderFields(Field ** fieldsMatrix, int sizeX, int sizeY, int sizeField);
private:
static void renderField(int x, int y, Field field, int sizeField);
static Color getFieldColor(Field field);
};
#endif //SNAKE_RENDERER_H

69
src/core/Snake.cpp Normal file
View File

@@ -0,0 +1,69 @@
//
// Created by stewki on 10/05/2026.
//
#include "Snake.h"
#include <raymath.h>
Snake::Snake()
: m_size(0), m_head({0, 0})
{
}
void Snake::init(Vector2 head, int size, Direction initialDirection) {
m_tail.clear();
m_head = head;
m_size = size;
expandTail(DirectionInvert(initialDirection), size-1);
}
void Snake::move(Direction direction) {
std::optional<Direction> currentDirection = getCurrentDirection();
if (currentDirection.has_value() && currentDirection.value() == DirectionInvert(direction)) {
direction = currentDirection.value();
}
m_size++;
m_tail.push_front(m_head);
Vector2 directionVector = DirectionUnitVector(direction);
m_head = Vector2Add(m_head, directionVector);
}
std::optional<Direction> Snake::getCurrentDirection() const {
if (m_tail.empty()) {
return std::nullopt;
}
return DirectionFrom2VectorsInLine(m_tail.front(), m_head);
}
Vector2 Snake::getHeadPosition() {
return m_head;
}
Vector2 Snake::removeLastFromTail() {
Vector2 lastInTail = m_tail.back();
m_tail.pop_back();
m_size--;
return lastInTail;
}
void Snake::expandTail(const Direction direction, const int count) {
const Vector2 directionVector = DirectionUnitVector(direction);
Vector2 lastInTail = getLastInTail();
for (int i = 0; i < count; i++) {
lastInTail = Vector2Add(lastInTail, directionVector);
m_tail.push_back(lastInTail);
}
}
Vector2 Snake::getLastInTail() const {
if (m_tail.empty()) {
return m_head;
}
return m_tail.back();
}

44
src/core/Snake.h Normal file
View File

@@ -0,0 +1,44 @@
//
// Created by stewki on 10/05/2026.
//
#ifndef SNAKE_SNAKE_H
#define SNAKE_SNAKE_H
#include <optional>
#include <queue>
#include "Direction.h"
#include "raylib.h"
class Snake {
public:
Snake();
void init(Vector2 head, int size, Direction initialDirection);
// Movement
public:
void move(Direction direction);
private:
[[nodiscard]] std::optional<Direction> getCurrentDirection() const;
// Head
public:
Vector2 getHeadPosition();
// Tail
public:
Vector2 removeLastFromTail();
private:
void expandTail(Direction direction, int count);
[[nodiscard]] Vector2 getLastInTail() const;
private:
int m_size;
Vector2 m_head;
std::deque<Vector2> m_tail;
};
#endif //SNAKE_SNAKE_H

21
src/external/Config.cpp vendored Normal file
View File

@@ -0,0 +1,21 @@
//
// Created by stewki on 09/05/2026.
//
#include "Config.h"
int Config::getSizeX() {
return 20;
}
int Config::getSizeY() {
return 15;
}
int Config::getSizeField() {
return 50;
}
double Config::getTurnTimeSeconds() {
return 0.2;
}

18
src/external/Config.h vendored Normal file
View File

@@ -0,0 +1,18 @@
//
// Created by stewki on 09/05/2026.
//
#ifndef SNAKE_CONFIG_H
#define SNAKE_CONFIG_H
class Config {
public:
int getSizeX();
int getSizeY();
int getSizeField();
double getTurnTimeSeconds();
};
#endif //SNAKE_CONFIG_H

23
src/external/External.cpp vendored Normal file
View File

@@ -0,0 +1,23 @@
//
// Created by stewki on 09/05/2026.
//
#include "External.h"
External::External() {
m_config = new Config();
m_time = new Time();
}
External::~External() {
delete m_config;
delete m_time;
}
Config * External::getConfig() const {
return m_config;
}
Time * External::getTime() const {
return m_time;
}

24
src/external/External.h vendored Normal file
View File

@@ -0,0 +1,24 @@
//
// Created by stewki on 09/05/2026.
//
#ifndef SNAKE_EXTERNAL_H
#define SNAKE_EXTERNAL_H
#include "Config.h"
#include "Time.h"
class External {
public:
External();
~External();
Config* getConfig() const;
Time* getTime() const;
private:
Config *m_config;
Time *m_time;
};
#endif //SNAKE_EXTERNAL_H

29
src/external/Time.cpp vendored Normal file
View File

@@ -0,0 +1,29 @@
//
// Created by stewki on 09/05/2026.
//
#include "Time.h"
#include <chrono>
Time::Time() {
reset();
}
void Time::reset() {
m_previousCurrentTime = getCurrentTime();
}
double Time::getDeltaTime() {
timeAliased now = getCurrentTime();
auto secondsElapsed = now - m_previousCurrentTime;
m_previousCurrentTime = now;
return secondsElapsed.count() / 1000000000.0;
}
timeAliased Time::getCurrentTime() {
return std::chrono::system_clock::now();
}

29
src/external/Time.h vendored Normal file
View File

@@ -0,0 +1,29 @@
//
// Created by stewki on 09/05/2026.
//
#ifndef SNAKE_TIME_H
#define SNAKE_TIME_H
#include <chrono>
typedef std::chrono::time_point<std::chrono::system_clock> timeAliased;
class Time {
public:
Time();
void reset();
double getDeltaTime();
private:
static timeAliased getCurrentTime();
private:
timeAliased m_previousCurrentTime;
};
#endif //SNAKE_TIME_H

10
src/main.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "core/App.h"
int main(int argc, char **argv) {
App app;
app.run(argc, argv);
return 0;
}