#include #include #include #include #include // Set namespaces for SFML and STD using namespace sf; using namespace std; // Init window / sprite variables ( constant and will never change ) constexpr int WIDTH = 640; constexpr int HEIGHT = 480; constexpr int GRID_SIZE = 20; constexpr int GRID_WIDTH = WIDTH / GRID_SIZE; constexpr int GRID_HEIGHT = HEIGHT / GRID_SIZE; constexpr int SPRITE_SIZE = GRID_SIZE; struct SnakeSegment { int x, y; }; class NumericSprite { private: Texture texture; Sprite sprite; public: NumericSprite() { texture.create(SPRITE_SIZE, SPRITE_SIZE); sprite.setTexture(texture); } void createFromPattern(const array, 5>& pattern, Color color) { Image img; img.create(SPRITE_SIZE, SPRITE_SIZE, Color::Transparent); int scale = SPRITE_SIZE / 5; for (int y = 0; y < 5; y++) { for (int x = 0; x < 5; x++) { if (pattern[y][x] == 1) { for (int sy = 0; sy < scale; sy++) { for (int sx = 0; sx < scale; sx++) { img.setPixel(x * scale + sx, y * scale + sy, color); } } } } } texture.update(img); } const Sprite& getSprite() const { return sprite; } void setPosition(float x, float y) { sprite.setPosition(x, y); } }; class SoundManager { private: SoundBuffer moveBuffer; SoundBuffer eatBuffer; SoundBuffer gameOverBuffer; Sound moveSound; Sound eatSound; Sound gameOverSound; bool soundLoaded; public: SoundManager() : soundLoaded(true) { // Create simple beep sounds programmatically createMoveSound(); createEatSound(); createGameOverSound(); // Set volumes to be quieter moveSound.setVolume(15.0f); // Very quiet for movement eatSound.setVolume(25.0f); // Bit louder for eating gameOverSound.setVolume(30.0f); // Louder for game over } void createMoveSound() { const unsigned SAMPLE_RATE = 44100; const unsigned DURATION = 5000; // Very short duration (in samples) std::vector samples(DURATION); // Generate a simple beep for movement (higher pitch, shorter) for (unsigned i = 0; i < DURATION; i++) { double time = i / static_cast(SAMPLE_RATE); samples[i] = 10000 * sin(2 * 3.14159 * 880 * time); // 880 Hz tone } // Apply fade-out for (unsigned i = DURATION / 2; i < DURATION; i++) { double fadeOut = 1.0 - (static_cast(i - DURATION / 2) / (DURATION / 2)); samples[i] = static_cast(samples[i] * fadeOut); } if (!moveBuffer.loadFromSamples(&samples[0], samples.size(), 1, SAMPLE_RATE)) { soundLoaded = false; } moveSound.setBuffer(moveBuffer); } void createEatSound() { const unsigned SAMPLE_RATE = 44100; const unsigned DURATION = 8000; // Slightly longer duration for eat sound std::vector samples(DURATION); // Generate eat sound (rising pitch) for (unsigned i = 0; i < DURATION; i++) { double time = i / static_cast(SAMPLE_RATE); double frequency = 440 + (i * 400 / DURATION); // Rising from 440Hz to 840Hz samples[i] = 12000 * sin(2 * 3.14159 * frequency * time); } // Apply fade-in and fade-out for (unsigned i = 0; i < DURATION / 4; i++) { double fadeIn = static_cast(i) / (DURATION / 4); samples[i] = static_cast(samples[i] * fadeIn); } for (unsigned i = 3 * DURATION / 4; i < DURATION; i++) { double fadeOut = 1.0 - (static_cast(i - 3 * DURATION / 4) / (DURATION / 4)); samples[i] = static_cast(samples[i] * fadeOut); } if (!eatBuffer.loadFromSamples(&samples[0], samples.size(), 1, SAMPLE_RATE)) { soundLoaded = false; } eatSound.setBuffer(eatBuffer); } void createGameOverSound() { const unsigned SAMPLE_RATE = 44100; const unsigned DURATION = 44100 / 2; // Half second std::vector samples(DURATION); // Generate game over sound (descending pitch) for (unsigned i = 0; i < DURATION; i++) { double time = i / static_cast(SAMPLE_RATE); double frequency = 440 - (i * 200 / DURATION); // Falling from 440Hz to 240Hz samples[i] = 15000 * sin(2 * 3.14159 * frequency * time); } // Apply "wah wah" effect with fading for (unsigned i = 0; i < DURATION; i++) { double envelope = 1.0 - 0.8 * (static_cast(i) / DURATION); samples[i] = static_cast(samples[i] * envelope); } if (!gameOverBuffer.loadFromSamples(&samples[0], samples.size(), 1, SAMPLE_RATE)) { soundLoaded = false; } gameOverSound.setBuffer(gameOverBuffer); } void playMoveSound() { if (soundLoaded) { moveSound.play(); } } void playEatSound() { if (soundLoaded) { eatSound.play(); } } void playGameOverSound() { if (soundLoaded) { gameOverSound.play(); } } }; class Game { private: RenderWindow window; vector snake; Vector2f fruit; Vector2f direction; bool isMoving; Clock clock; float speed; int score; Font font; Text scoreText; SoundManager soundManager; Vector2f lastDirection; // Numeric sprites NumericSprite snakeHeadSprite; NumericSprite snakeBodySprite; NumericSprite fruitSprite; // Grid vertex array VertexArray grid; // Patterns const array, 5> HEAD_PATTERN = {{ {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1} }}; const array, 5> BODY_PATTERN = {{ {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1} }}; // Apple-like pattern const array, 5> FRUIT_PATTERN = {{ {0, 1, 1, 1, 0}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {0, 1, 1, 1, 0} }}; public: Game() : window(VideoMode(WIDTH, HEIGHT), "SFML CPPSNEK V1.3 With Sound"), direction(1, 0), isMoving(false), speed(0.1f), score(0), grid(Lines), lastDirection(1, 0) { window.setFramerateLimit(60); // Initialize snake snake.push_back({GRID_WIDTH / 2, GRID_HEIGHT / 2}); snake.push_back({GRID_WIDTH / 2 - 1, GRID_HEIGHT / 2}); snake.push_back({GRID_WIDTH / 2 - 2, GRID_HEIGHT / 2}); // Create sprites snakeHeadSprite.createFromPattern(HEAD_PATTERN, Color(200, 200, 0)); // Yellow head snakeBodySprite.createFromPattern(BODY_PATTERN, Color(0, 200, 0)); // Green body fruitSprite.createFromPattern(FRUIT_PATTERN, Color(200, 0, 0)); // Red apple // Create grid createGrid(); spawnFruit(); // Setup font if (!font.loadFromFile("/usr/share/fonts/gnu-free/FreeSans.ttf")) { font.loadFromFile("/usr/share/fonts/liberation/LiberationSans-Regular.ttf"); } scoreText.setFont(font); scoreText.setCharacterSize(24); scoreText.setFillColor(Color::White); scoreText.setPosition(10, 10); updateScoreText(); } void createGrid() { // Vertical lines for (int x = 0; x <= WIDTH; x += GRID_SIZE) { grid.append(Vertex(Vector2f(x, 0), Color(50, 50, 50))); grid.append(Vertex(Vector2f(x, HEIGHT), Color(50, 50, 50))); } // Horizontal lines for (int y = 0; y <= HEIGHT; y += GRID_SIZE) { grid.append(Vertex(Vector2f(0, y), Color(50, 50, 50))); grid.append(Vertex(Vector2f(WIDTH, y), Color(50, 50, 50))); } } void spawnFruit() { static random_device rd; static mt19937 gen(rd()); uniform_int_distribution<> distX(0, GRID_WIDTH - 1); uniform_int_distribution<> distY(0, GRID_HEIGHT - 1); fruit.x = distX(gen); fruit.y = distY(gen); for (const auto& segment : snake) { if (segment.x == fruit.x && segment.y == fruit.y) { spawnFruit(); return; } } } void updateScoreText() { scoreText.setString("Score: " + to_string(score)); } void handleInput() { bool directionChanged = false; if (Keyboard::isKeyPressed(Keyboard::Left) && direction.x == 0) { direction = Vector2f(-1, 0); isMoving = true; directionChanged = true; } else if (Keyboard::isKeyPressed(Keyboard::Right) && direction.x == 0) { direction = Vector2f(1, 0); isMoving = true; directionChanged = true; } else if (Keyboard::isKeyPressed(Keyboard::Up) && direction.y == 0) { direction = Vector2f(0, -1); isMoving = true; directionChanged = true; } else if (Keyboard::isKeyPressed(Keyboard::Down) && direction.y == 0) { direction = Vector2f(0, 1); isMoving = true; directionChanged = true; } if (directionChanged && (direction.x != lastDirection.x || direction.y != lastDirection.y)) { soundManager.playMoveSound(); lastDirection = direction; } } void update() { if (clock.getElapsedTime().asSeconds() < speed || !isMoving) return; clock.restart(); SnakeSegment newHead = {snake[0].x + direction.x, snake[0].y + direction.y}; if (newHead.x < 0 || newHead.x >= GRID_WIDTH || newHead.y < 0 || newHead.y >= GRID_HEIGHT) { soundManager.playGameOverSound(); reset(); return; } for (size_t i = 1; i < snake.size(); i++) { if (newHead.x == snake[i].x && newHead.y == snake[i].y) { soundManager.playGameOverSound(); reset(); return; } } if (newHead.x == fruit.x && newHead.y == fruit.y) { snake.insert(snake.begin(), newHead); spawnFruit(); score += 10; updateScoreText(); soundManager.playEatSound(); if (score % 50 == 0 && speed > 0.05f) { speed -= 0.01f; } } else { snake.insert(snake.begin(), newHead); snake.pop_back(); } } void reset() { snake.clear(); snake.push_back({GRID_WIDTH / 2, GRID_HEIGHT / 2}); snake.push_back({GRID_WIDTH / 2 - 1, GRID_HEIGHT / 2}); snake.push_back({GRID_WIDTH / 2 - 2, GRID_HEIGHT / 2}); direction = Vector2f(1, 0); lastDirection = Vector2f(1, 0); isMoving = false; speed = 0.1f; score = 0; updateScoreText(); spawnFruit(); } void render() { window.clear(Color(30, 30, 30)); // Dark gray background // Draw grid first window.draw(grid); // Draw snake for (size_t i = 0; i < snake.size(); i++) { if (i == 0) { snakeHeadSprite.setPosition(snake[i].x * GRID_SIZE, snake[i].y * GRID_SIZE); window.draw(snakeHeadSprite.getSprite()); } else { snakeBodySprite.setPosition(snake[i].x * GRID_SIZE, snake[i].y * GRID_SIZE); window.draw(snakeBodySprite.getSprite()); } } // Draw fruit (apple) fruitSprite.setPosition(fruit.x * GRID_SIZE, fruit.y * GRID_SIZE); window.draw(fruitSprite.getSprite()); // Draw score window.draw(scoreText); window.display(); } void run() { while (window.isOpen()) { Event event; while (window.pollEvent(event)) { if (event.type == Event::Closed) window.close(); } handleInput(); update(); render(); } } }; int main() { Game game; game.run(); return 0; }