From 3d79b8c199783143563149b6a2e7e1382ade0d60 Mon Sep 17 00:00:00 2001 From: Darkheim Date: Tue, 8 Jul 2025 21:50:28 -0500 Subject: [PATCH] Initial commit: Console Snake game --- .gitignore | 26 +++ CMakeLists.txt | 22 +++ Direction.h | 6 + Food.cpp | 20 ++ Food.h | 17 ++ Game.cpp | 492 +++++++++++++++++++++++++++++++++++++++++++++++++ Game.h | 63 +++++++ Point.h | 17 ++ README.md | 42 +++++ Snake.cpp | 70 +++++++ Snake.h | 50 +++++ best_score.txt | 2 + main.cpp | 9 + 13 files changed, 836 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Direction.h create mode 100644 Food.cpp create mode 100644 Food.h create mode 100644 Game.cpp create mode 100644 Game.h create mode 100644 Point.h create mode 100644 README.md create mode 100644 Snake.cpp create mode 100644 Snake.h create mode 100644 best_score.txt create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fdf9154 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Build directories +build/ +cmake-build-*/ + +# CMake files +CMakeCache.txt +CMakeFiles/ +Makefile +cmake_install.cmake +build.ninja +rules.ninja + +# Executables +Snake +*.exe + +# IDE files +.idea/ +.vscode/ +*.user + +# Testing +Testing/ + +# OS files +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..73cf1f3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.10) +project(Snake) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Find SFML +find_package(SFML 2.5 COMPONENTS graphics window system REQUIRED) + +# Add executable +add_executable(Snake + main.cpp + Game.cpp + Snake.cpp + Food.cpp +) + +# Include headers +target_include_directories(Snake PRIVATE .) + +# Link SFML libraries +target_link_libraries(Snake sfml-graphics sfml-window sfml-system) diff --git a/Direction.h b/Direction.h new file mode 100644 index 0000000..2708bf6 --- /dev/null +++ b/Direction.h @@ -0,0 +1,6 @@ +#ifndef DIRECTION_H +#define DIRECTION_H + +enum class Direction { UP, DOWN, LEFT, RIGHT }; + +#endif //DIRECTION_H diff --git a/Food.cpp b/Food.cpp new file mode 100644 index 0000000..defce49 --- /dev/null +++ b/Food.cpp @@ -0,0 +1,20 @@ +#include "Food.h" +#include +#include + +void Food::generateNew(int maxX, int maxY, const std::vector& occupiedPositions) +{ + std::random_device rd; + std::mt19937 gen(rd()); + + std::uniform_int_distribution<> disX(0, maxX - 1); + std::uniform_int_distribution<> disY(0, maxY - 1); + + Point newPos; + do + { + newPos = Point(disX(gen), disY(gen)); + } while (std::find(occupiedPositions.begin(), occupiedPositions.end(), newPos) != occupiedPositions.end()); + + position = newPos; +} diff --git a/Food.h b/Food.h new file mode 100644 index 0000000..6fa632a --- /dev/null +++ b/Food.h @@ -0,0 +1,17 @@ +#ifndef FOOD_H +#define FOOD_H + +#include "Point.h" +#include + +class Food +{ + Point position; + +public: + Food() : position(0, 0) {} + + void generateNew(int maxX, int maxY, const std::vector& occupiedPositions); + Point getPosition() const { return position; } +}; +#endif //FOOD_H diff --git a/Game.cpp b/Game.cpp new file mode 100644 index 0000000..47f56e7 --- /dev/null +++ b/Game.cpp @@ -0,0 +1,492 @@ +#include "Game.h" +#include +#include +#include +#include +#include + +Game::Game() + : snake(Point(BOARD_WIDTH / 2, BOARD_HEIGHT / 2)), + score(0), + bestScore(0), + gameOver(false), + paused(false), + inMenu(true) +{ + loadBestScore(); + generateFood(); +} + +void Game::run() +{ + sf::RenderWindow window(sf::VideoMode(BOARD_WIDTH * CELL_SIZE, BOARD_HEIGHT * CELL_SIZE + 60), "Snake Game"); + sf::Clock clock; + float timer = 0.0f, delay = 0.15f; + + // Menu buttons initialization + MenuButton startBtn, exitBtn, restartBtn, menuBtn; + startBtn.rect = sf::FloatRect(CELL_SIZE * BOARD_WIDTH / 2 - 110, 180, 220, 48); + startBtn.label = "Start"; + startBtn.color = sf::Color(80, 180, 80); + startBtn.hoverColor = sf::Color(120, 220, 120); + + exitBtn.rect = sf::FloatRect(BOARD_WIDTH * CELL_SIZE / 2 - 110, 250, 220, 40); + exitBtn.label = "Exit"; + exitBtn.color = sf::Color(180, 80, 80); + exitBtn.hoverColor = sf::Color(220, 120, 120); + + restartBtn.rect = sf::FloatRect(BOARD_WIDTH * CELL_SIZE / 2 - 110, 180, 220, 48); + restartBtn.label = "Restart"; + restartBtn.color = sf::Color(80, 180, 80); + restartBtn.hoverColor = sf::Color(120, 220, 120); + + menuBtn.rect = sf::FloatRect(BOARD_WIDTH * CELL_SIZE / 2 - 110, 250, 220, 40); + menuBtn.label = "Menu"; + menuBtn.color = sf::Color(180, 80, 80); + menuBtn.hoverColor = sf::Color(220, 120, 120); + + while (window.isOpen()) + { + sf::Event event; + while (window.pollEvent(event)) + { + if (event.type == sf::Event::Closed) + window.close(); + processEvent(event, &window, &startBtn, &exitBtn, &restartBtn, &menuBtn); + } + + if (!paused && !gameOver && !inMenu) + { + timer += clock.restart().asSeconds(); + if (timer > delay) + { + update(); + timer = 0; + } + } + else + { + clock.restart(); + } + + render(window, &startBtn, &exitBtn, &restartBtn, &menuBtn); + } +} + +// Handles all events, including mouse support for menu buttons +void Game::processEvent(const sf::Event& event, sf::RenderWindow* window, + MenuButton* startBtn, MenuButton* exitBtn, + MenuButton* restartBtn, MenuButton* menuBtn) +{ + sf::Vector2f mousePos; + if (window) + mousePos = window->mapPixelToCoords(sf::Mouse::getPosition(*window)); + + // Reset hover state for all buttons + if (startBtn) startBtn->hovered = false; + if (exitBtn) exitBtn->hovered = false; + if (restartBtn) restartBtn->hovered = false; + if (menuBtn) menuBtn->hovered = false; + + if (inMenu) + { + if (startBtn && startBtn->rect.contains(mousePos)) + startBtn->hovered = true; + if (exitBtn && exitBtn->rect.contains(mousePos)) + exitBtn->hovered = true; + + if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) + { + if (startBtn && startBtn->rect.contains(mousePos)) + { + inMenu = false; + gameOver = false; + paused = false; + reset(); + return; + } + if (exitBtn && exitBtn->rect.contains(mousePos)) + { + if (window) window->close(); + return; + } + } + if (event.type == sf::Event::KeyPressed) + { + if (event.key.code == sf::Keyboard::Enter) + { + inMenu = false; + gameOver = false; + paused = false; + reset(); + } + else if (event.key.code == sf::Keyboard::Escape) + { + if (window) window->close(); + } + } + return; + } + + if (gameOver) + { + if (restartBtn && restartBtn->rect.contains(mousePos)) + restartBtn->hovered = true; + if (menuBtn && menuBtn->rect.contains(mousePos)) + menuBtn->hovered = true; + + if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) + { + if (restartBtn && restartBtn->rect.contains(mousePos)) + { + reset(); + gameOver = false; + paused = false; + inMenu = false; + return; + } + if (menuBtn && menuBtn->rect.contains(mousePos)) + { + inMenu = true; + return; + } + } + if (event.type == sf::Event::KeyPressed) + { + if (event.key.code == sf::Keyboard::Enter) + { + reset(); + gameOver = false; + paused = false; + inMenu = false; + } + else if (event.key.code == sf::Keyboard::Escape) + { + inMenu = true; + } + } + return; + } + + // Snake controls + if (event.type == sf::Event::KeyPressed) + { + switch (event.key.code) + { + case sf::Keyboard::W: + case sf::Keyboard::Up: + if (snake.getDirection() != Direction::DOWN) + snake.setDirection(Direction::UP); + paused = false; + break; + case sf::Keyboard::S: + case sf::Keyboard::Down: + if (snake.getDirection() != Direction::UP) + snake.setDirection(Direction::DOWN); + paused = false; + break; + case sf::Keyboard::A: + case sf::Keyboard::Left: + if (snake.getDirection() != Direction::RIGHT) + snake.setDirection(Direction::LEFT); + paused = false; + break; + case sf::Keyboard::D: + case sf::Keyboard::Right: + if (snake.getDirection() != Direction::LEFT) + snake.setDirection(Direction::RIGHT); + paused = false; + break; + case sf::Keyboard::Space: + paused = !paused; + break; + case sf::Keyboard::Q: + case sf::Keyboard::Escape: + inMenu = true; + break; + default: + break; + } + } +} + +// Rendering with interactive menu buttons and centered text +void Game::render(sf::RenderWindow& window, + MenuButton* startBtn, MenuButton* exitBtn, + MenuButton* restartBtn, MenuButton* menuBtn) +{ + // Draw gradient background + sf::VertexArray background(sf::Quads, 4); + background[0].position = sf::Vector2f(0, 0); + background[1].position = sf::Vector2f(window.getSize().x, 0); + background[2].position = sf::Vector2f(window.getSize().x, window.getSize().y); + background[3].position = sf::Vector2f(0, window.getSize().y); + background[0].color = sf::Color(40, 40, 60); + background[1].color = sf::Color(40, 40, 60); + background[2].color = sf::Color(20, 20, 30); + background[3].color = sf::Color(20, 20, 30); + window.draw(background); + + // Draw game border + sf::RectangleShape border(sf::Vector2f(BOARD_WIDTH * CELL_SIZE, BOARD_HEIGHT * CELL_SIZE)); + border.setPosition(0, 0); + border.setFillColor(sf::Color::Transparent); + border.setOutlineThickness(3); + border.setOutlineColor(sf::Color(180, 180, 220)); + window.draw(border); + + // Draw food with highlight + sf::CircleShape foodCircle(CELL_SIZE / 2 - 3); + foodCircle.setFillColor(sf::Color(255, 80, 80)); + foodCircle.setOutlineThickness(2); + foodCircle.setOutlineColor(sf::Color(255, 180, 180)); + foodCircle.setOrigin(foodCircle.getRadius(), foodCircle.getRadius()); + foodCircle.setPosition(food.getPosition().x * CELL_SIZE + CELL_SIZE / 2, food.getPosition().y * CELL_SIZE + CELL_SIZE / 2); + window.draw(foodCircle); + + // Draw snake + for (const auto& segment : snake.getBody()) + { + sf::RectangleShape cell(sf::Vector2f(CELL_SIZE - 4, CELL_SIZE - 4)); + cell.setPosition(segment.x * CELL_SIZE + 2, segment.y * CELL_SIZE + 2); + if (segment == snake.getHead()) { + cell.setFillColor(sf::Color(80, 255, 80)); + cell.setOutlineThickness(2); + cell.setOutlineColor(sf::Color(40, 180, 40)); + } else { + cell.setFillColor(sf::Color(100, 200, 100)); + cell.setOutlineThickness(1); + cell.setOutlineColor(sf::Color(60, 120, 60)); + } + window.draw(cell); + } + + // Helper for drawing shadowed text + auto drawShadowedText = [&](sf::RenderTarget& target, const sf::Text& text, sf::Color shadowColor = sf::Color(0,0,0,120), float offset = 2.f) { + sf::Text shadow = text; + shadow.setFillColor(shadowColor); + shadow.move(offset, offset); + target.draw(shadow); + target.draw(text); + }; + + static sf::Font font; + static bool fontLoaded = false; + if (!fontLoaded) { + fontLoaded = font.loadFromFile("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); + } + + if (fontLoaded) + { + sf::Text text; + text.setFont(font); + text.setCharacterSize(22); + text.setFillColor(sf::Color::White); + + if (inMenu) + { + // Title + text.setString("SNAKE GAME"); + text.setCharacterSize(38); + text.setStyle(sf::Text::Bold); + sf::FloatRect titleRect = text.getLocalBounds(); + text.setOrigin(titleRect.left + titleRect.width / 2.0f, titleRect.top + titleRect.height / 2.0f); + text.setPosition(BOARD_WIDTH * CELL_SIZE / 2.0f, 80); + drawShadowedText(window, text); + + // Best score + text.setString("Best score: " + std::to_string(bestScore)); + text.setCharacterSize(22); + text.setStyle(sf::Text::Regular); + sf::FloatRect bestRect = text.getLocalBounds(); + text.setOrigin(bestRect.left + bestRect.width / 2.0f, bestRect.top + bestRect.height / 2.0f); + text.setPosition(BOARD_WIDTH * CELL_SIZE / 2.0f, 130); + drawShadowedText(window, text); + + // Start button + sf::RectangleShape startShape(sf::Vector2f(startBtn->rect.width, startBtn->rect.height)); + startShape.setPosition(startBtn->rect.left, startBtn->rect.top); + startShape.setFillColor(startBtn->hovered ? startBtn->hoverColor : startBtn->color); + startShape.setOutlineThickness(3); + startShape.setOutlineColor(sf::Color(40, 120, 40)); + window.draw(startShape); + + text.setString("Start"); + text.setCharacterSize(24); + text.setFillColor(sf::Color::White); + sf::FloatRect startTextRect = text.getLocalBounds(); + text.setOrigin(startTextRect.left + startTextRect.width / 2.0f, startTextRect.top + startTextRect.height / 2.0f); + text.setPosition( + startBtn->rect.left + startBtn->rect.width / 2.0f, + startBtn->rect.top + startBtn->rect.height / 2.0f + ); + drawShadowedText(window, text); + + // Exit button + sf::RectangleShape exitShape(sf::Vector2f(exitBtn->rect.width, exitBtn->rect.height)); + exitShape.setPosition(exitBtn->rect.left, exitBtn->rect.top); + exitShape.setFillColor(exitBtn->hovered ? exitBtn->hoverColor : exitBtn->color); + exitShape.setOutlineThickness(3); + exitShape.setOutlineColor(sf::Color(120, 40, 40)); + window.draw(exitShape); + + text.setString("Exit"); + text.setCharacterSize(22); + text.setFillColor(sf::Color::White); + sf::FloatRect exitTextRect = text.getLocalBounds(); + text.setOrigin(exitTextRect.left + exitTextRect.width / 2.0f, exitTextRect.top + exitTextRect.height / 2.0f); + text.setPosition( + exitBtn->rect.left + exitBtn->rect.width / 2.0f, + exitBtn->rect.top + exitBtn->rect.height / 2.0f + ); + drawShadowedText(window, text); + } + else if (gameOver) + { + // Game Over title + text.setString("GAME OVER"); + text.setCharacterSize(38); + text.setStyle(sf::Text::Bold); + text.setFillColor(sf::Color(255, 80, 80)); + sf::FloatRect overRect = text.getLocalBounds(); + text.setOrigin(overRect.left + overRect.width / 2.0f, overRect.top + overRect.height / 2.0f); + text.setPosition(BOARD_WIDTH * CELL_SIZE / 2.0f, 80); + drawShadowedText(window, text, sf::Color(0,0,0,180), 3.f); + + // Score info + text.setString("Score: " + std::to_string(score) + " Best: " + std::to_string(bestScore)); + text.setCharacterSize(22); + text.setStyle(sf::Text::Regular); + text.setFillColor(sf::Color::White); + sf::FloatRect scoreRect = text.getLocalBounds(); + text.setOrigin(scoreRect.left + scoreRect.width / 2.0f, scoreRect.top + scoreRect.height / 2.0f); + text.setPosition(BOARD_WIDTH * CELL_SIZE / 2.0f, 130); + drawShadowedText(window, text); + + // Restart button + sf::RectangleShape restartShape(sf::Vector2f(restartBtn->rect.width, restartBtn->rect.height)); + restartShape.setPosition(restartBtn->rect.left, restartBtn->rect.top); + restartShape.setFillColor(restartBtn->hovered ? restartBtn->hoverColor : restartBtn->color); + restartShape.setOutlineThickness(3); + restartShape.setOutlineColor(sf::Color(40, 120, 40)); + window.draw(restartShape); + + text.setString("Restart"); + text.setCharacterSize(24); + text.setFillColor(sf::Color::White); + sf::FloatRect restartTextRect = text.getLocalBounds(); + text.setOrigin(restartTextRect.left + restartTextRect.width / 2.0f, restartTextRect.top + restartTextRect.height / 2.0f); + text.setPosition( + restartBtn->rect.left + restartBtn->rect.width / 2.0f, + restartBtn->rect.top + restartBtn->rect.height / 2.0f + ); + drawShadowedText(window, text); + + // Menu button + sf::RectangleShape menuShape(sf::Vector2f(menuBtn->rect.width, menuBtn->rect.height)); + menuShape.setPosition(menuBtn->rect.left, menuBtn->rect.top); + menuShape.setFillColor(menuBtn->hovered ? menuBtn->hoverColor : menuBtn->color); + menuShape.setOutlineThickness(3); + menuShape.setOutlineColor(sf::Color(120, 40, 40)); + window.draw(menuShape); + + text.setString("Menu"); + text.setCharacterSize(22); + text.setFillColor(sf::Color::White); + sf::FloatRect menuTextRect = text.getLocalBounds(); + text.setOrigin(menuTextRect.left + menuTextRect.width / 2.0f, menuTextRect.top + menuTextRect.height / 2.0f); + text.setPosition( + menuBtn->rect.left + menuBtn->rect.width / 2.0f, + menuBtn->rect.top + menuBtn->rect.height / 2.0f + ); + drawShadowedText(window, text); + } + else + { + // Score bar + text.setString("Score: " + std::to_string(score) + " Best: " + std::to_string(bestScore) + + (paused ? " [PAUSED]" : "")); + text.setCharacterSize(22); + text.setFillColor(sf::Color::White); + text.setStyle(sf::Text::Regular); + text.setPosition(5, BOARD_HEIGHT * CELL_SIZE + 8); + drawShadowedText(window, text); + + // Controls + text.setString("WASD / Arrows - Move SPACE - Pause ESC - Menu"); + text.setCharacterSize(16); + text.setFillColor(sf::Color(180, 180, 220)); + text.setPosition(5, BOARD_HEIGHT * CELL_SIZE + 36); + drawShadowedText(window, text, sf::Color(0,0,0,100), 1.5f); + } + } + + window.display(); +} + +void Game::update() +{ + snake.updateDirection(); + Point newHead = getTeleportedHead(); + + if (snake.checkSelfCollision(newHead)) + { + gameOver = true; + if (score > bestScore) + { + bestScore = score; + saveBestScore(); + } + return; + } + + if (newHead == food.getPosition()) + { + snake.grow(newHead); + score += 10; + generateFood(); + } + else + { + snake.move(newHead); + } +} + +Point Game::getTeleportedHead() const +{ + Point newHead = snake.getNextHeadPosition(); + if (newHead.x < 0) newHead.x = BOARD_WIDTH - 1; + else if (newHead.x >= BOARD_WIDTH) newHead.x = 0; + if (newHead.y < 0) newHead.y = BOARD_HEIGHT - 1; + else if (newHead.y >= BOARD_HEIGHT) newHead.y = 0; + return newHead; +} + +void Game::generateFood() +{ + std::vector occupiedPositions(snake.getBody().begin(), snake.getBody().end()); + food.generateNew(BOARD_WIDTH, BOARD_HEIGHT, occupiedPositions); +} + +void Game::loadBestScore() +{ + std::ifstream in(BEST_SCORE_FILE); + if (in) + in >> bestScore; + else + bestScore = 0; +} + +void Game::saveBestScore() +{ + std::ofstream out(BEST_SCORE_FILE); + if (out) + out << bestScore; +} + +void Game::reset() +{ + snake = Snake(Point(BOARD_WIDTH / 2, BOARD_HEIGHT / 2)); + score = 0; + generateFood(); +} diff --git a/Game.h b/Game.h new file mode 100644 index 0000000..43cf2d3 --- /dev/null +++ b/Game.h @@ -0,0 +1,63 @@ +#ifndef GAME_H +#define GAME_H + +#include "Snake.h" +#include "Food.h" +#include "Direction.h" +#include +#include +#include + +class Game +{ + Snake snake; + Food food; + int score; + int bestScore; + bool gameOver; + bool paused; + bool inMenu; + + // Menu button struct + struct MenuButton { + sf::FloatRect rect; + std::string label; + sf::Color color; + sf::Color hoverColor; + bool hovered = false; + }; + + void generateFood(); + Point getTeleportedHead() const; + void loadBestScore(); + void saveBestScore(); + void reset(); + +public: + static const int BOARD_WIDTH = 20; + static const int BOARD_HEIGHT = 20; + static const int CELL_SIZE = 24; + static constexpr const char* BEST_SCORE_FILE = "best_score.txt"; + + Game(); + + void run(); + void processEvent(const sf::Event& event, + sf::RenderWindow* window = nullptr, + MenuButton* startBtn = nullptr, + MenuButton* exitBtn = nullptr, + MenuButton* restartBtn = nullptr, + MenuButton* menuBtn = nullptr); + void update(); + void render(sf::RenderWindow& window, + MenuButton* startBtn = nullptr, + MenuButton* exitBtn = nullptr, + MenuButton* restartBtn = nullptr, + MenuButton* menuBtn = nullptr); + + bool isGameOver() const { return gameOver; } + int getScore() const { return score; } + int getBestScore() const { return bestScore; } +}; + +#endif //GAME_H diff --git a/Point.h b/Point.h new file mode 100644 index 0000000..4c1a285 --- /dev/null +++ b/Point.h @@ -0,0 +1,17 @@ +#ifndef POINT_H +#define POINT_H + +class Point +{ +public: + int x, y; + + explicit Point(int x = 0, int y = 0) : x(x), y(y){} + + bool operator==(const Point& other) const + { + return x == other.x && y == other.y; + } +}; + +#endif //POINT_H diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a160e9 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# 🐍 Snake Game + +Classic Snake game implemented in C++ with a console interface. + +## 📁 Project Structure +- `main.cpp` — Entry point +- `Game.*` — Game logic and rendering +- `Snake.*` — Snake class implementation +- `Food.*` — Food generation system +- `Point.h` — Coordinate structure +- `Direction.h` — Direction enumeration +- `best_score.txt` — High score storage +- `CMakeLists.txt` — Build configuration + +## 🛠️ Requirements +- C++11 or higher +- CMake 3.10+ +- Linux/Unix terminal + +## 🎮 Features +- Console-based graphics +- WASD controls +- Food generation +- Collision detection +- Growing snake mechanics +- Score tracking +- Best score persistence + +## 🎯 Controls +- W — Move up +- S — Move down +- A — Move left +- D — Move right +- Q — Quit game + +## 📦 Build and Run +```bash +mkdir build && cd build +cmake .. +make +./Snake +``` diff --git a/Snake.cpp b/Snake.cpp new file mode 100644 index 0000000..0c8e2e7 --- /dev/null +++ b/Snake.cpp @@ -0,0 +1,70 @@ +#include "Snake.h" + +// Constructor +Snake::Snake(Point startPos) + : currentDirection(Direction::RIGHT), nextDirection(Direction::RIGHT) +{ + body.push_back(startPos); +} + +// Set the movement direction +void Snake::setDirection(Direction dir) +{ + if ((currentDirection == Direction::UP && dir == Direction::DOWN) || + (currentDirection == Direction::DOWN && dir == Direction::UP) || + (currentDirection == Direction::LEFT && dir == Direction::RIGHT) || + (currentDirection == Direction::RIGHT && dir == Direction::LEFT)) + return; + nextDirection = dir; +} + +// Check if the snake's body contains the point pos +bool Snake::contains(Point pos) const +{ + for (const Point& segment : body) + if (segment == pos) + return true; + return false; +} + +// Move the snake (without growing) +void Snake::move(const Point& newHead) +{ + body.push_front(newHead); + body.pop_back(); +} + +// Grow the snake (add a head) +void Snake::grow(const Point& newHead) +{ + body.push_front(newHead); +} + +// Update the current direction +void Snake::updateDirection() +{ + currentDirection = nextDirection; +} + +// Get the next head position +Point Snake::getNextHeadPosition() const +{ + Point head = body.front(); + switch (currentDirection) + { + case Direction::UP: return Point(head.x, head.y - 1); + case Direction::DOWN: return Point(head.x, head.y + 1); + case Direction::LEFT: return Point(head.x - 1, head.y); + case Direction::RIGHT: return Point(head.x + 1, head.y); + default: return head; + } +} + +// Check for self-collision +bool Snake::checkSelfCollision(const Point& newHead) const +{ + for (const Point& segment : body) + if (newHead == segment) + return true; + return false; +} diff --git a/Snake.h b/Snake.h new file mode 100644 index 0000000..22d19f5 --- /dev/null +++ b/Snake.h @@ -0,0 +1,50 @@ +#ifndef SNAKE_H +#define SNAKE_H + +#include +#include +#include "Point.h" +#include "Direction.h" + +class Snake +{ + std::deque body; // The segments of the snake's body + Direction currentDirection; // The current direction of the snake + Direction nextDirection; // The next direction the snake will move towards + +public: + // Constructor to initialize the snake's position + Snake(Point startPos); + + // Set the snake's direction + void setDirection(Direction dir); + + // Move the snake to a new head position + void move(const Point& newHead); + + // Grow the snake by adding a new head segment + void grow(const Point& newHead); + + // Update the snake's direction based on the next direction + void updateDirection(); + + // Get the position of the snake's next head + Point getNextHeadPosition() const; + + // Check if moving to the new head position will cause the snake to collide with itself + bool checkSelfCollision(const Point& newHead) const; + + // Get the current direction of the snake + Direction getDirection() const { return currentDirection; } + + // Get the segments of the snake's body + const std::deque& getBody() const { return body; } + + // Get the position of the snake's head + Point getHead() const { return body.front(); } + + // Check if the snake's body contains a specific point + bool contains(Point pos) const; +}; + +#endif // SNAKE_H diff --git a/best_score.txt b/best_score.txt new file mode 100644 index 0000000..77ac542 --- /dev/null +++ b/best_score.txt @@ -0,0 +1,2 @@ +0 + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..760b792 --- /dev/null +++ b/main.cpp @@ -0,0 +1,9 @@ +#include "Game.h" + +int main() +{ + + Game game; + game.run(); + return 0; +} \ No newline at end of file