Yes, It's called Snake++
. I build it to learn C++.
What features and techniques can be improved? I used SDL for some basic rendering, but my concern is more about the language use.
Things I'm very concerned about:
- Generating a new food means trying random positions over and over again till one is free. This is going to become a problem very soon. What data structures can I use here?
- Am I using references to their full potential and avoiding unnecessary copying?
Main.cpp
#include <iostream>
#include "Game.hpp"
using namespace std;
int main(int argc, char * argv[])
{
Game game = Game();
Game().Run();
cout << "Game has terminated successfully, score: " << game.GetScore()
<< ", size: " << game.GetSize() << endl;
return 0;
}
Game.hpp
#pragma once
#include <vector>
#include "SDL.h"
#include "SDL_image.h"
class Game
{
public:
Game();
void Run();
int GetScore();
int GetSize();
private:
bool running = false;
bool alive = false;
int fps = 0;
static const int FRAME_RATE = 1000 / 60;
static const int SCREEN_WIDTH = 640;
static const int SCREEN_HEIGHT = 640;
static const int GRID_WIDTH = 32;
static const int GRID_HEIGHT = 32;
SDL_Window * window = nullptr;
SDL_Renderer * renderer = nullptr;
enum class Block { head, body, food, empty };
enum class Move { up, down, left, right };
Move last_dir = Move::up;
Move dir = Move::up;
struct { float x = GRID_WIDTH / 2, y = GRID_HEIGHT / 2; } pos;
SDL_Point head = { static_cast<int>(pos.x), static_cast<int>(pos.y) };
SDL_Point food;
std::vector<SDL_Point> body;
Block grid[GRID_WIDTH][GRID_HEIGHT];
float speed = 0.5f;
int growing = 0;
int score = 0;
int size = 1;
void ReplaceFood();
void GrowBody(int quantity);
void UpdateWindowTitle();
void GameLoop();
void Render();
void Update();
void PollEvents();
void Close();
};
Game.cpp
#include <iostream>
#include <string>
#include <ctime>
#include "SDL.h"
#include "Game.hpp"
using namespace std;
Game::Game()
{
for (int i = 0; i < GRID_WIDTH; ++i)
for (int j = 0; j < GRID_HEIGHT; ++j)
{
grid[i][j] = Block::empty;
}
srand(static_cast<unsigned int>(time(0)));
}
void Game::Run()
{
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << endl;
exit(EXIT_FAILURE);
}
// Create Window
window = SDL_CreateWindow("Snake Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (window == NULL)
{
cout << "Window could not be created! SDL_Error: " << SDL_GetError() << endl;
exit(EXIT_FAILURE);
}
// Create renderer
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL)
{
cout << "Renderer could not be created! SDL_Error: " << SDL_GetError() << endl;
exit(EXIT_FAILURE);
}
alive = true;
running = true;
ReplaceFood();
GameLoop();
}
void Game::ReplaceFood()
{
int x, y;
while (true)
{
x = rand() % GRID_WIDTH;
y = rand() % GRID_HEIGHT;
if (grid[x][y] == Block::empty)
{
grid[x][y] = Block::food;
food.x = x;
food.y = y;
break;
}
}
}
void Game::GameLoop()
{
Uint32 before, second = SDL_GetTicks(), after;
int frame_time, frames = 0;
while (running)
{
before = SDL_GetTicks();
PollEvents();
Update();
Render();
frames++;
after = SDL_GetTicks();
frame_time = after - before;
if (after - second >= 1000)
{
fps = frames;
frames = 0;
second = after;
UpdateWindowTitle();
}
if (FRAME_RATE > frame_time)
{
SDL_Delay(FRAME_RATE - frame_time);
}
}
}
void Game::PollEvents()
{
SDL_Event e;
while (SDL_PollEvent(&e))
{
if (e.type == SDL_QUIT)
{
running = false;
}
else if (e.type == SDL_KEYDOWN)
{
switch (e.key.keysym.sym)
{
case SDLK_UP:
if (last_dir != Move::down || size == 1)
dir = Move::up;
break;
case SDLK_DOWN:
if (last_dir != Move::up || size == 1)
dir = Move::down;
break;
case SDLK_LEFT:
if (last_dir != Move::right || size == 1)
dir = Move::left;
break;
case SDLK_RIGHT:
if (last_dir != Move::left || size == 1)
dir = Move::right;
break;
}
}
}
}
int Game::GetSize()
{
return size;
}
void Game::GrowBody(int quantity)
{
growing += quantity;
}
void Game::Update()
{
if (!alive)
return;
switch (dir)
{
case Move::up:
pos.y -= speed;
pos.x = floorf(pos.x);
break;
case Move::down:
pos.y += speed;
pos.x = floorf(pos.x);
break;
case Move::left:
pos.x -= speed;
pos.y = floorf(pos.y);
break;
case Move::right:
pos.x += speed;
pos.y = floorf(pos.y);
break;
}
// Wrap
if (pos.x < 0) pos.x = GRID_WIDTH - 1;
else if (pos.x > GRID_WIDTH - 1) pos.x = 0;
if (pos.y < 0) pos.y = GRID_HEIGHT - 1;
else if (pos.y > GRID_HEIGHT - 1) pos.y = 0;
int new_x = static_cast<int>(pos.x);
int new_y = static_cast<int>(pos.y);
// Check if head position has changed
if (new_x != head.x || new_y != head.y)
{
last_dir = dir;
// If we are growing, just make a new neck
if (growing > 0)
{
size++;
body.push_back(head);
growing--;
grid[head.x][head.y] = Block::body;
}
else
{
// We need to shift the body
SDL_Point free = head;
vector<SDL_Point>::reverse_iterator rit = body.rbegin();
for ( ; rit != body.rend(); ++rit)
{
grid[free.x][free.y] = Block::body;
swap(*rit, free);
}
grid[free.x][free.y] = Block::empty;
}
}
head.x = new_x;
head.y = new_y;
Block & next = grid[head.x][head.y];
// Check if there's food over here
if (next == Block::food)
{
score++;
ReplaceFood();
GrowBody(1);
}
// Check if we're dead
else if (next == Block::body)
{
alive = false;
}
next = Block::head;
}
int Game::GetScore()
{
return score;
}
void Game::UpdateWindowTitle()
{
string title = "Snakle++ Score: " + to_string(score) + " FPS: " + to_string(fps);
SDL_SetWindowTitle(window, title.c_str());
}
void Game::Render()
{
SDL_Rect block;
block.w = SCREEN_WIDTH / GRID_WIDTH;
block.h = SCREEN_WIDTH / GRID_HEIGHT;
// Clear screen
SDL_SetRenderDrawColor(renderer, 0x1E, 0x1E, 0x1E, 0xFF);
SDL_RenderClear(renderer);
// Render food
SDL_SetRenderDrawColor(renderer, 0xFF, 0xCC, 0x00, 0xFF);
block.x = food.x * block.w;
block.y = food.y * block.h;
SDL_RenderFillRect(renderer, &block);
// Render snake's body
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
for (SDL_Point & point : body)
{
block.x = point.x * block.w;
block.y = point.y * block.h;
SDL_RenderFillRect(renderer, &block);
}
// Render snake's head
block.x = head.x * block.w;
block.y = head.y * block.h;
if (alive) SDL_SetRenderDrawColor(renderer, 0x00, 0x7A, 0xCC, 0xFF);
else SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF);
SDL_RenderFillRect(renderer, &block);
// Update Screen
SDL_RenderPresent(renderer);
}
void Game::Close()
{
SDL_DestroyWindow(window);
SDL_Quit();
}