Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 78 additions & 32 deletions src/snake.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
#include <string.h>
#include <SDL3/SDL_log.h>

static const SDL_Color k_color_empty = {0, 0, 0, 255};
static const SDL_Color k_color_wall = {50, 50, 50, 255};
static const SDL_Color k_color_food = {255, 0, 0, 255};
static const SDL_Color k_color_snake_head = {0, 255, 0, 255};

static bool get_random_empty_position(snake_t* snake, vector2i_t* out_position) {
SDL_assert(snake != NULL);
SDL_assert(out_position != NULL);
Expand All @@ -18,15 +23,15 @@ static bool get_random_empty_position(snake_t* snake, vector2i_t* out_position)
for (int attempt = 0; attempt < max_attempts; ++attempt) {
vector2i_random(&position, interior_width, interior_height);
snake_cell_t* cell = &snake->cells[position.x][position.y];
if (cell->color == SNAKE_COLOR_BLACK) {
if (cell->state == SNAKE_CELL_EMPTY) {
*out_position = position;
return true;
}
}

for (int x = 1; x < SNAKE_GRID_X - 1; ++x) {
for (int y = 1; y < SNAKE_GRID_Y - 1; ++y) {
if (snake->cells[x][y].color == SNAKE_COLOR_BLACK) {
if (snake->cells[x][y].state == SNAKE_CELL_EMPTY) {
vector2i_set(out_position, x, y);
return true;
}
Expand All @@ -37,11 +42,16 @@ static bool get_random_empty_position(snake_t* snake, vector2i_t* out_position)
return false;
}

static void cell_set_color(snake_t* snake, const vector2i_t* position, snake_colors_t color) {
static void cell_set_state_and_color(snake_t* snake, const vector2i_t* position, snake_cell_state_t state,
const SDL_Color* color) {
SDL_assert(snake != NULL);
SDL_assert(position != NULL);

snake->cells[position->x][position->y].color = color;
snake_cell_t* const cell = &snake->cells[position->x][position->y];
cell->state = state;
if (color != NULL) {
cell->render_color = *color;
}
}

static bool update_score_text(snake_t* snake) {
Expand Down Expand Up @@ -82,9 +92,9 @@ static bool reset(snake_t* snake) {

// Set the border cells to gray and the rest to black.
if (x == 0 || x == SNAKE_GRID_X - 1 || y == 0 || y == SNAKE_GRID_Y - 1) {
cell->color = SNAKE_COLOR_GRAY;
cell_set_state_and_color(snake, &cell->position, SNAKE_CELL_WALL, &k_color_wall);
} else {
cell->color = SNAKE_COLOR_BLACK;
cell_set_state_and_color(snake, &cell->position, SNAKE_CELL_EMPTY, &k_color_empty);
}
}
}
Expand All @@ -96,7 +106,7 @@ static bool reset(snake_t* snake) {
}

snake->position_head = head_position;
cell_set_color(snake, &snake->position_head, SNAKE_COLOR_GREEN);
cell_set_state_and_color(snake, &snake->position_head, SNAKE_CELL_SNAKE, &k_color_snake_head);
snake->previous_position_head = snake->position_head;
snake->previous_position_tail = snake->position_head;
snake->current_direction = SNAKE_DIRECTION_UP;
Expand All @@ -110,7 +120,7 @@ static bool reset(snake_t* snake) {
}

dynamic_array_append(&snake->array_food, &food_position);
cell_set_color(snake, &food_position, SNAKE_COLOR_RED);
cell_set_state_and_color(snake, &food_position, SNAKE_CELL_FOOD, &k_color_food);
}

dynamic_array_create(&snake->array_body, sizeof(vector2i_t), 8);
Expand Down Expand Up @@ -158,8 +168,7 @@ bool snake_create(snake_t* snake, const char* title) {
dynamic_array_init(&snake->array_food);
dynamic_array_init(&snake->array_body);

const int written =
snprintf(snake->text_score_buffer, sizeof(snake->text_score_buffer), "Score: 0");
const int written = snprintf(snake->text_score_buffer, sizeof(snake->text_score_buffer), "Score: 0");
if (written < 0 || (size_t)written >= sizeof(snake->text_score_buffer)) {
SDL_Log("Failed to format initial score text");
goto fail;
Expand Down Expand Up @@ -294,11 +303,11 @@ static void move_head_and_body(snake_t* snake) {

// Move the snake's head.
snake->position_head = new_head_position;
cell_set_color(snake, &snake->position_head, SNAKE_COLOR_GREEN);
cell_set_state_and_color(snake, &snake->position_head, SNAKE_CELL_SNAKE, &k_color_snake_head);

if (dynamic_array_is_empty(&snake->array_body) == true) {
// Snake has no array_body, so clear the previous head position.
cell_set_color(snake, &snake->previous_position_head, SNAKE_COLOR_BLACK);
cell_set_state_and_color(snake, &snake->previous_position_head, SNAKE_CELL_EMPTY, &k_color_empty);
} else {
vector2i_set(&snake->previous_position_tail, 0, 0);

Expand All @@ -316,12 +325,62 @@ static void move_head_and_body(snake_t* snake) {
*current_body_position = saved_position;
}

cell_set_color(snake, current_body_position, SNAKE_COLOR_DARK_GREEN);
cell_set_color(snake, &snake->previous_position_tail, SNAKE_COLOR_BLACK);
cell_set_state_and_color(snake, current_body_position, SNAKE_CELL_SNAKE, NULL);
cell_set_state_and_color(snake, &snake->previous_position_tail, SNAKE_CELL_EMPTY, &k_color_empty);
}
}
}

static void update_snake_gradient(snake_t* snake) {
SDL_assert(snake != NULL);

const Uint8 head_green = 255;
const Uint8 tail_green = 120;
const float knee = 0.3f;
const float knee_weight = 0.7f;

snake_cell_t* const head_cell = &snake->cells[snake->position_head.x][snake->position_head.y];
head_cell->state = SNAKE_CELL_SNAKE;
head_cell->render_color.r = 0;
head_cell->render_color.g = head_green;
head_cell->render_color.b = 0;
head_cell->render_color.a = 255;

if (snake->array_body.size == 0) {
return;
}

const float start = (float)head_green;
const float end = (float)tail_green;
const float length = (float)snake->array_body.size;

for (size_t i = 0; i < snake->array_body.size; ++i) {
vector2i_t* const body_position = (vector2i_t* const)dynamic_array_get(&snake->array_body, i);
snake_cell_t* const cell = &snake->cells[body_position->x][body_position->y];

const float t = (float)(i + 1) / length;
float eased_t;
if (t <= knee) {
eased_t = (t / knee) * knee_weight;
} else {
eased_t = knee_weight + ((t - knee) / (1.0f - knee)) * (1.0f - knee_weight);
}

float green_value = start + (end - start) * eased_t;
if (green_value < 0.f) {
green_value = 0.f;
} else if (green_value > 255.f) {
green_value = 255.f;
}

cell->state = SNAKE_CELL_SNAKE;
cell->render_color.r = 0;
cell->render_color.g = (Uint8)(green_value + 0.5f);
cell->render_color.b = 0;
cell->render_color.a = 255;
}
}

bool test_body_collision(snake_t* snake) {
SDL_assert(snake != NULL);

Expand All @@ -348,7 +407,7 @@ static bool test_food_collision(snake_t* snake) {
vector2i_t new_food_position;
if (get_random_empty_position(snake, &new_food_position) == true) {
dynamic_array_append(&snake->array_food, &new_food_position);
cell_set_color(snake, &new_food_position, SNAKE_COLOR_RED);
cell_set_state_and_color(snake, &new_food_position, SNAKE_CELL_FOOD, &k_color_food);
}

return true;
Expand Down Expand Up @@ -394,6 +453,8 @@ void snake_update_fixed(snake_t* snake) {
snake->window.is_running = false;
}
}

update_snake_gradient(snake);
}

void snake_handle_events(snake_t* snake) {
Expand Down Expand Up @@ -432,23 +493,8 @@ void snake_render_frame(snake_t* snake) {
const snake_cell_t* const cell = &snake->cells[x][y];

// TODO: Optimize the use of SDL_SetRenderDrawColor by rendering all tiles of the same color at once.
switch (cell->color) {
case SNAKE_COLOR_BLACK:
SDL_SetRenderDrawColor(snake->window.sdl_renderer, 0, 0, 0, 255);
break;
case SNAKE_COLOR_GRAY:
SDL_SetRenderDrawColor(snake->window.sdl_renderer, 50, 50, 50, 255);
break;
case SNAKE_COLOR_GREEN:
SDL_SetRenderDrawColor(snake->window.sdl_renderer, 0, 255, 0, 255);
break;
case SNAKE_COLOR_DARK_GREEN:
SDL_SetRenderDrawColor(snake->window.sdl_renderer, 0, 180, 0, 255);
break;
case SNAKE_COLOR_RED:
SDL_SetRenderDrawColor(snake->window.sdl_renderer, 255, 0, 0, 255);
break;
}
SDL_SetRenderDrawColor(snake->window.sdl_renderer, cell->render_color.r, cell->render_color.g,
cell->render_color.b, cell->render_color.a);

SDL_FRect rect;
rect.x = (float)(cell->position.x * SNAKE_CELL_SIZE);
Expand Down
14 changes: 7 additions & 7 deletions src/snake.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ typedef enum {
} snake_direction_t;

typedef enum {
SNAKE_COLOR_BLACK,
SNAKE_COLOR_GRAY,
SNAKE_COLOR_GREEN,
SNAKE_COLOR_DARK_GREEN,
SNAKE_COLOR_RED
} snake_colors_t;
SNAKE_CELL_EMPTY,
SNAKE_CELL_WALL,
SNAKE_CELL_FOOD,
SNAKE_CELL_SNAKE
} snake_cell_state_t;

typedef struct {
vector2i_t position;
snake_colors_t color;
snake_cell_state_t state;
SDL_Color render_color;
} snake_cell_t;

typedef struct {
Expand Down