#include "engine.h" #include #include #include #include bool left = true; typedef struct { int32_t x; int32_t y; } v2; #define SNAKE_MAX_LENGTH 10 typedef struct { // position of the head of the snake in the framebuffer v2 head_pos; // -1 or +1 for both x and y v2 dir; // tail length + 1 for the head uint16_t length; // width and/or height uint8_t size; v2 body[SNAKE_MAX_LENGTH]; uint64_t move_interval; uint64_t next_move_time; bool is_apple_alive; bool direction_pending; v2 pending_dir; v2 apple_pos; } snake_state_t; snake_state_t snake; bool put_string_game_over; bool put_string_win; bool redraw = false; bool is_opposite_direction(v2 a, v2 b) { return a.x == -b.x && a.y == -b.y; } void queue_direction(v2 new_dir) { if (is_opposite_direction(new_dir, snake.dir)) { return; } // ignore additional direction inputs until the next movement if (snake.direction_pending) { return; } snake.pending_dir = new_dir; snake.direction_pending = true; } // polling events every frame void client_poll_event() { SDL_Event ev = game_state->event; if (ev.type == SDL_EVENT_QUIT) { game_state->is_running = false; } if (ev.type == SDL_EVENT_KEY_DOWN) { switch (ev.key.key) { case SDLK_Q: game_state->is_running = false; break; case SDLK_W: queue_direction((v2){0, -1}); break; case SDLK_S: queue_direction((v2){0, 1}); break; case SDLK_A: queue_direction((v2){-1, 0}); break; case SDLK_D: queue_direction((v2){1, 0}); break; default: break; } } } // a wrapper function for put_rect using a v2 struct void put_rect_v2( v2 pos, unsigned int w, unsigned int h, uint32_t color ) { put_rect(pos.x, pos.y, w, h, color); } bool v2_eq( v2 x, v2 y ) { return x.x == y.x && x.y == y.y; } void advance_snake_pos() { if (snake.direction_pending) { snake.dir = snake.pending_dir; snake.direction_pending = false; } int32_t new_head_x = (snake.body[0].x + snake.dir.x * snake.size) % game_state->width; if (new_head_x < 0) new_head_x = game_state->width - snake.size; int32_t new_head_y = (snake.body[0].y + snake.dir.y * snake.size) % game_state->height; if (new_head_y < 0) new_head_y = game_state->height - snake.size; for (int i = snake.length - 1; i > 0; i--) { snake.body[i] = snake.body[i - 1]; } snake.body[0] = (v2){new_head_x, new_head_y}; } void spawn_apple_at_random_pos() { snake.apple_pos = (v2){ snake.size * (rand() % (game_state->width / snake.size)), snake.size * (rand() % (game_state->height / snake.size)) }; snake.is_apple_alive = true; } void calculate_apple_collisions() { if (snake.is_apple_alive && v2_eq(snake.body[0], snake.apple_pos) ) { snake.is_apple_alive = false; if (snake.length < SNAKE_MAX_LENGTH) { snake.length++; //snake.body[snake.length] = snake.body[snake.length - 1]; } } } void calculate_snake_death_collisions() { for (int i = 1; i < snake.length; i++) { if (v2_eq(snake.body[0], snake.body[i])) { put_string_game_over = true; } } } void evaluate_win() { put_string_win = (snake.length >= SNAKE_MAX_LENGTH); } /** * performed every frame * runs on a separate worker thread - you can take advantage * of sleep or usleep from to create delays */ void client_update_framebuffer() { uint64_t now = SDL_GetTicks(); if (now >= snake.next_move_time && !put_string_game_over) { // ------- business logic -------- if (!snake.is_apple_alive) { spawn_apple_at_random_pos(); } calculate_apple_collisions(); calculate_snake_death_collisions(); evaluate_win(); v2 old_head = snake.body[0]; v2 old_tail = snake.body[snake.length - 1]; advance_snake_pos(); v2 new_head = snake.body[0]; clear_rect( old_tail.x, old_tail.y, snake.size, snake.size, 0x000000 ); if (snake.length > 1) { put_rect( old_head.x, old_head.y, snake.size, snake.size, 0x00AA00 ); } put_rect( new_head.x, new_head.y, snake.size, snake.size, 0x00FF00 ); mark_dirty_rect( old_tail.x, old_tail.y, snake.size, snake.size ); mark_dirty_rect( old_head.x, old_head.y, snake.size, snake.size ); mark_dirty_rect( new_head.x, new_head.y, snake.size, snake.size ); // ------------------------------- snake.next_move_time += snake.move_interval; if (snake.next_move_time < now) { snake.next_move_time = now + snake.move_interval; } redraw = true; game_state->framebuffer_dirty = true; } if (redraw) { // draw the apple if (snake.is_apple_alive) { put_rect_v2( snake.apple_pos, snake.size, snake.size, 0xFF0000 ); } // draw the head put_rect( snake.body[0].x, snake.body[0].y, snake.size, snake.size, 0x00FF00 ); // draw the tail for (int i = 1; i < snake.length; i++) { put_rect( snake.body[i].x, snake.body[i].y, snake.size, snake.size, 0x00AA00 ); } // draw the splash texts if (put_string_game_over) { put_string("GAME OVER!\n to quit", 0, 0, 5, 2, 3, 0xFF0000, 0x000000 ); } if (put_string_win) { put_string("YOU WIN!\n to quit", 0, 100, 5, 2, 3, 0x00FF00, 0x000000 ); } redraw = false; } } void snake_init() { srand(time(NULL)); // start at the left-top corner and go right snake.body[0] = (v2){0, 0}; snake.dir = (v2){1, 0}; snake.length = 1; snake.size = 20; snake.move_interval = 50; snake.next_move_time = snake.move_interval; snake.is_apple_alive = false; snake.pending_dir = snake.dir; snake.direction_pending = false; put_string_game_over = false; put_string_win = false; } int main() { // initialize the snake vars snake_init(); // initialize the engine if (engine_init( client_poll_event, client_update_framebuffer )) { engine_start(); engine_stop(); return 0; } else { return 1; } }