0

The game works fine, but its using rectangle shapes and I want to render the snake parts using Sprites. Here is the code (Yeah It's not that clean I just wanted to get this working):

#include <SFML/Graphics.hpp>  // Graphics module
#include <SFML/Audio.hpp>     // Sound module
#include <iostream>           // Basic input/output (for errors)
#include <vector>             // Dynamic arrays (for snake body)
#include <random>             // For random food positions

class Game {
private:
    sf::Vector2u windowSize;        // Size of the game window
    int a, b;                       // Length and width of a block
    sf::RenderWindow window;        // Window object for drawing
    sf::Font font;                  // Text font
    sf::Clock clock;                // For time measurement
    std::vector<sf::Vector2i> body; // Snake body (segments as coordinates)
    sf::Vector2i food;              // Food position
    sf::Vector2i direction;         // Snake direction
    int score;                      // Player's score
    bool gameOver;                  // Game state: finished or not
    int n, m;                       // Number of rows and columns
    float delay, timer;             // Update delay and elapsed time
    sf::Music eat;                  // Sound effect for eating

public:
    Game(unsigned short x, unsigned short y); // Constructor
    void start();                   // Start the game
private:
    void loop();                    // Main game loop
    void events();                  // Process events (keyboard, etc.)
    void update();                  // Update game logic
    void render();                  // Render elements on screen
    void gameOverScreen();          // Show "Game Over" screen
    sf::Vector2i getFoodPosition(); // Generate new food position
};

int WinMain() {
    Game game(800, 600);
    game.start();
}

int main() {
    Game game(800, 600);    // Create object with window size 800x600
    game.start();           // Call the start function
}

Game::Game(uint16_t x, uint16_t y) {
    windowSize = { x, y };  // Save window width and height
    window.create(sf::VideoMode(windowSize.x, windowSize.y, 1), "Snake");
    // Create the window
    a = 50;                 // Set block width
    b = 50;                 // Set block height
    n = windowSize.x / a;   // Calculate number of horizontal blocks
    m = windowSize.y / b;   // Calculate number of vertical blocks
    font.loadFromFile("resources/Fonts/sfpro_bold.OTF"); // Load font for text
    eat.openFromFile("resources/Audio/eating_apple.mp3");
}

void Game::start() {
    body.clear();            // Clear the snake body
    body.push_back({ 5,3 }); // Head
    body.push_back({ 4,3 }); // Body segment
    body.push_back({ 3,3 }); // Tail
    direction = { 0, 0 };    // Direction
    food = { getFoodPosition() }; // Initial food position
    gameOver = false;        // Game not over (false)
    score = 0;               // Start score from 0
    loop();                  // Main loop
}

void Game::loop() {
    timer = 0.f;             // Accumulated time
    delay = 0.125f;          // Game update delay
    while (window.isOpen()) {
        events();            // Handle user inputs (keyboard and mouse)
        timer += clock.getElapsedTime().asSeconds(); // Add elapsed time in seconds to the timer
        if (timer > delay) {
            update();        // Update the game (move snake, etc.)
            render();        // Render the screen (blocks, snake, food, text, etc.)
            timer = 0;       // Reset timer
        }
        clock.restart();    // Restart the clock for the next cycle
    }
}

void Game::events() {
    sf::Event event;
    while (window.pollEvent(event)) {
        if (event.type == sf::Event::Closed)
            window.close();
        if (event.type == sf::Event::KeyPressed) {
            if (event.key.code == sf::Keyboard::Escape) window.close();
            else if (event.key.code == sf::Keyboard::Up && (direction.y != 1)) direction = { 0, -1 };
            else if (event.key.code == sf::Keyboard::Down && (direction.y != -1)) direction = { 0, 1 };
            else if (event.key.code == sf::Keyboard::Left && (direction.x != 1)) direction = { -1, 0 };
            else if (event.key.code == sf::Keyboard::Right && (direction.x != -1)) direction = { 1, 0 };
            else if (event.key.code == sf::Keyboard::R) /*if (gameOver)*/ start();
        }
    }
}

void Game::update() {
    if (gameOver || direction == sf::Vector2i{ 0,0 }) return;
    sf::Vector2i newHead = body[0] + direction;
    for (size_t i = 1; i < body.size(); i++) {
        if (newHead == body[i]) {
            gameOver = true;
            return;
        }
    }
    if (newHead.x > n - 1 || newHead.x < 0 || newHead.y > m - 1 || newHead.y < 0) {
        gameOver = true;
        return;
    }
    if (newHead == food) {
        body.insert(body.begin(), newHead);
        food = getFoodPosition();
        score++;
        eat.play();
    }
    else {
        body.insert(body.begin(), newHead);
        body.pop_back();
    }
}

void Game::render() {
    window.clear();
    sf::RectangleShape block(sf::Vector2f(a, b));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            block.setPosition(i * a, j * b);
            int p = (i + j) % 2;
            block.setFillColor(sf::Color(172 - p * 7, 214 - p * 7, 67 - p * 7, 255));
            window.draw(block);
        }
    }

    block.setPosition(food.x * a, food.y * b);
    block.setFillColor(sf::Color::Red);
    window.draw(block);

    for (int i = 0; i < body.size(); i++) {
        if (i == 0)
            block.setFillColor(sf::Color::Magenta);
        else
            block.setFillColor(sf::Color(0, 71, 181));
        block.setPosition(body[i].x * a, body[i].y * b);
        window.draw(block);
    }

    sf::Text scr("Score: " + std::to_string(score), font, 32);
    scr.setFillColor(sf::Color::White);
    scr.setPosition(4, 0);
    window.draw(scr);
    if (gameOver) gameOverScreen();

    window.display();
}

void Game::gameOverScreen() {
    eat.stop();
    sf::RectangleShape screen({ float(windowSize.x), float(windowSize.y) });
    screen.setPosition({ 0,0 });
    screen.setFillColor(sf::Color(0, 0, 0, 100));
    sf::Text gameOverText("      Game over!\nPress R to restart", font, 38);
    gameOverText.setFillColor(sf::Color::White);
    gameOverText.setPosition((windowSize.x / 2) - 150, (windowSize.y / 2) - 20);
    window.draw(screen);
    window.draw(gameOverText);
}

sf::Vector2i Game::getFoodPosition() {                  
    std::random_device randomDevice;                     
    std::mt19937 randomNumberGenerator(randomDevice());  

    std::uniform_int_distribution<int> distX(0, n - 1);
    std::uniform_int_distribution<int> distY(0, m - 1);
    sf::Vector2i position;                               
    do {
        position.x = distX(randomNumberGenerator);       
        position.y = distY(randomNumberGenerator);       
        if (std::find(body.begin(), body.end(), position) == body.end()) {   
            return position;                             
        }
    } while (true);                                      
}

The code above does not have sprites functionality currently, but here is an approach I have tried (which worked for the rendering the sprites): Created a map of Textures and Sprites, loaded the textures from the files, mapped them to the Sprites, and then I had a map of Sprites ready. This part was not that hard I had flaws in my rendering logic most likely. Code was taken from when I had the sprite logic, Im basically using a string to determine which sprite I should load.

sf::Vector2i prev = body[i + 1]; // 
sf::Vector2i next = body[i - 1]; //because i pop the tail and insert new part at the beginning
if (next.x == prev.x) {
    spriteName = "body_vertical"; //vertical sprite |
}
else if (next.y == prev.y) {
    spriteName = "body_horizontal"; // horizontal   --
}
else {
    if (prev.x < next.x) {
        if (prev.y > next.y) 
                spriteName = "body_topright"; // start top curve right downwards 
            else 
                spriteName = "body_bottomleft"; // bottom -> left
        }
        else if (prev.y < next.y) {
            spriteName = "body_topleft"; // top -> left
            
        else 
                spriteName = "body_bottomright"; // bottom -> right
        }
    }
    else{
        if (prev.y > next.y) {
                spriteName = "body_topleft"; // top -> left
            else
                spriteName = "body_bottomright"; // bottom -> right
        }
        else if (prev.y < next.y) {
                spriteName = "body_topright"; // top -> right
            else 
                spriteName = "body_bottomleft"; // bottom -> left
        }
    }
}

Link for sprites: opengameart.org/content/snake-game-assets. Basically, what I want is some feedback on how to choose the correct sprite to load and for which situation (head and tail would be appreciated too 🥹). Thanks!

1 Answer 1

0

here's a slightly cleaner code,

// choosing is based on the assumption
// that the head is the last element in the body array
// if not, you can simply reverse the conditions
if (prev == /*last in body*/) {
  spriteName == "head_sprite";
} else if (prev == /*first in body*/) {
  spriteName = "tail_sprite";
} else if (next.x == prev.x || next.y == prev.y) {
  spriteName == "body_sprite";
} else {
  spriteName = "curved_sprite";
}
// now rotate and reverse
if (prev.x == next.x) {
  // rotate 90deg
} else if (prev.x > next.x) {
  // reverse horizontally
}

if (prev.y > next.y) {
  // reverse vertically
}

it's much more convenient to rotate/reverse the sprite than choosing individual sprites based on each case.

i have selected from the snake_graphics.zip you attached those sprites

["head_down", "tail_down", "body_vertical", "body_bottomright"]

and renamed them to

["head_sprite", "tail_sprite", "body_sprite", "curved_sprite" ]

respectively. i possibly might have mistaken in rotating/reversing the sprite as i don't know for sure whether prev or next is the current ("assumed prev is"), but that's the basic idea

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.