Made a simple snake game using C and raylib for graphics, Would like to be critiqued on the clearity and efficiency of the code.
#include <raylib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define DIRECTION_UP (Vec2i){0, -1}
#define DIRECTION_DOWN (Vec2i){0, 1}
#define DIRECTION_LEFT (Vec2i){1, 0}
#define DIRECTION_RIGHT (Vec2i){-1, 0}
enum GameState
{
GAME_START,
GAME_RUNNING,
GAME_OVER
};
enum CellState
{
CELL_EMPTY,
CELL_SNAKE,
CELL_APPLE
};
typedef unsigned uint;
typedef struct
{
int x, y;
} Vec2i;
typedef struct
{
uint *cells;
uint height, width;
} Board;
typedef struct
{
bool collided;
uint tailLength;
Vec2i headPosition;
Vec2i headDirection;
Vec2i *tail;
} Snake;
static uint gameState = GAME_START;
static uint gameScore = 0;
static Board board;
static Snake snake;
static Vec2i applePosition;
void board_set(Vec2i v, uint c)
{
#ifdef DEBUG
if (v.x > board.width || v.y > board.height)
{
fprintf(stderr, "Can not get board at (%d,%d).\n", v.x, v.y);
exit(1);
}
#endif
*(board.cells + v.x * board.width + v.y) = c;
}
uint board_get(Vec2i v)
{
#ifdef DEBUG
if (v.x > board.width || v.y > board.height)
{
fprintf(stderr, "Can not set board at (%d,%d).\n", v.x, v.y);
exit(1);
}
#endif
return *(board.cells + v.x * board.width + v.y);
}
void apple_spawn()
{
while (true)
{
//Spawn within bounds of the board
int x = (rand() % (board.width - 2)) + 1;
int y = (rand() % (board.height - 2)) + 1;
//Do not spawn on snake cells
if (board_get((Vec2i){x, y}) != CELL_SNAKE)
{
applePosition.x = x;
applePosition.y = y;
break;
}
}
}
void snake_tail_grow()
{
snake.tailLength++;
if (snake.tailLength == 1)
{
snake.tail[snake.tailLength].x = snake.headPosition.x ;
snake.tail[snake.tailLength].y = snake.headPosition.y ;
return;
}
//Previous tail
snake.tail[snake.tailLength] = snake.tail[snake.tailLength - 1];
}
bool apple_eaten()
{
//Check if snake head is on the same cell as the apple
if (snake.headPosition.x == applePosition.x && snake.headPosition.y == applePosition.y)
return true;
return false;
}
void game_create(uint w, uint h)
{
board.cells = calloc(h * w, sizeof *(board.cells));
snake.tail = calloc(h * w, sizeof (Vec2i));//Allocate for longest tail length possible on the board
if (board.cells == NULL || snake.tail == NULL)
{
fprintf(stderr, "Failed to allocate memory.\n");
exit(1);
}
board.width = w;
board.height = h;
snake.tailLength = 0;
snake.headDirection.x = 0;
snake.headDirection.y = 0;
//Spawn snake at the center of the board
uint centerX = board.width / 2;
uint centerY = board.height / 2;
snake.headPosition.x = centerX;
snake.headPosition.y = centerY;
}
void game_destroy()
{
free(snake.tail);
free(board.cells);
}
void game_update()
{
//Clear board
memset(board.cells, CELL_EMPTY, board.height * board.width * sizeof *(board.cells));
//Update cells to be on the new positions
board_set(snake.headPosition, CELL_SNAKE);
board_set(applePosition, CELL_APPLE);
for (uint i = 0; i < snake.tailLength; i++)
board_set(snake.tail[i], CELL_SNAKE);
//Snake collision with board boundary
if (snake.headPosition.x >= board.width || snake.headPosition.x <= 0)
snake.collided = true;
if (snake.headPosition.y >= board.height || snake.headPosition.y <= 0)
snake.collided = true;
//Shift to last to make space for new tail
memmove(snake.tail + 1, snake.tail, snake.tailLength * sizeof *(snake.tail));
snake.tail[0] = snake.headPosition;
//Move snake in the direction
snake.headPosition.x += snake.headDirection.x;
snake.headPosition.y += snake.headDirection.y;
if (apple_eaten())
{
gameScore++;
snake_tail_grow();
apple_spawn();
}
if (snake.collided)
gameState = GAME_OVER;
}
void game_input()
{
if (IsKeyDown(KEY_W))
snake.headDirection = DIRECTION_UP;
if (IsKeyDown(KEY_S))
snake.headDirection = DIRECTION_DOWN;
if (IsKeyDown(KEY_D))
snake.headDirection = DIRECTION_LEFT;
if (IsKeyDown(KEY_A))
snake.headDirection = DIRECTION_RIGHT;
}
void game_draw()
{
uint widthRatio = GetScreenWidth() / board.width;
uint heightRatio = GetScreenHeight() / board.height;
for (uint x = 0; x < board.width; x++)
for (uint y = 0; y < board.height; y++)
{
switch (board_get((Vec2i){x, y}))
{
case CELL_SNAKE :
DrawRectangle(x * widthRatio, y * heightRatio, widthRatio, heightRatio, GREEN);
break;
case CELL_APPLE :
DrawRectangle(x * widthRatio, y * heightRatio, widthRatio, heightRatio, RED);
break;
}
}
//Draw score
DrawText(TextFormat("Score : %u", gameScore), 0, 0, 20, WHITE);
}
int main()
{
InitWindow(640, 640, "Snake");
SetTargetFPS(10);
game_create(20, 20);
apple_spawn();
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(BLACK);
switch (gameState)
{
case GAME_START :
DrawText("Press space to start.", 0, 0, 40, WHITE);
DrawText("W,A,S,D keys for movement.", 0, 40, 25, WHITE);
if (IsKeyPressed(KEY_SPACE))
gameState = GAME_RUNNING;
break;
case GAME_RUNNING :
game_input();
game_update();
game_draw();
break;
case GAME_OVER :
DrawText("Game Over :(", 0, 0, 40, RED);
DrawText(TextFormat("Score : %u", gameScore), 0, 40, 40, WHITE);
DrawText("Press escape to exit.", 0, 80, 40, WHITE);
}
EndDrawing();
}
game_destroy();
CloseWindow();
return 0;
}
How did I do?