123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- /*
- * Logic implementation of the Snake game. It is designed to efficiently
- * represent the state of the game in memory.
- *
- * This code is public domain. Feel free to use it for any purpose!
- */
- #define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
- #include <SDL3/SDL.h>
- #include <SDL3/SDL_main.h>
- #define STEP_RATE_IN_MILLISECONDS 125
- #define SNAKE_BLOCK_SIZE_IN_PIXELS 24
- #define SDL_WINDOW_WIDTH (SNAKE_BLOCK_SIZE_IN_PIXELS * SNAKE_GAME_WIDTH)
- #define SDL_WINDOW_HEIGHT (SNAKE_BLOCK_SIZE_IN_PIXELS * SNAKE_GAME_HEIGHT)
- #define SNAKE_GAME_WIDTH 24U
- #define SNAKE_GAME_HEIGHT 18U
- #define SNAKE_MATRIX_SIZE (SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT)
- #define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */
- #define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS)
- typedef enum
- {
- SNAKE_CELL_NOTHING = 0U,
- SNAKE_CELL_SRIGHT = 1U,
- SNAKE_CELL_SUP = 2U,
- SNAKE_CELL_SLEFT = 3U,
- SNAKE_CELL_SDOWN = 4U,
- SNAKE_CELL_FOOD = 5U
- } SnakeCell;
- #define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */
- typedef enum
- {
- SNAKE_DIR_RIGHT,
- SNAKE_DIR_UP,
- SNAKE_DIR_LEFT,
- SNAKE_DIR_DOWN
- } SnakeDirection;
- typedef struct
- {
- unsigned char cells[(SNAKE_MATRIX_SIZE * SNAKE_CELL_MAX_BITS) / 8U];
- char head_xpos;
- char head_ypos;
- char tail_xpos;
- char tail_ypos;
- char next_dir;
- char inhibit_tail_step;
- unsigned occupied_cells;
- } SnakeContext;
- typedef struct
- {
- SDL_Window *window;
- SDL_Renderer *renderer;
- SnakeContext snake_ctx;
- Uint64 last_step;
- } AppState;
- SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y)
- {
- const int shift = SHIFT(x, y);
- unsigned short range;
- SDL_memcpy(&range, ctx->cells + (shift / 8), sizeof(range));
- return (SnakeCell)((range >> (shift % 8)) & THREE_BITS);
- }
- static void set_rect_xy_(SDL_FRect *r, short x, short y)
- {
- r->x = (float)(x * SNAKE_BLOCK_SIZE_IN_PIXELS);
- r->y = (float)(y * SNAKE_BLOCK_SIZE_IN_PIXELS);
- }
- static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct)
- {
- const int shift = SHIFT(x, y);
- const int adjust = shift % 8;
- unsigned char *const pos = ctx->cells + (shift / 8);
- unsigned short range;
- SDL_memcpy(&range, pos, sizeof(range));
- range &= ~(THREE_BITS << adjust); /* clear bits */
- range |= (ct & THREE_BITS) << adjust;
- SDL_memcpy(pos, &range, sizeof(range));
- }
- static int are_cells_full_(SnakeContext *ctx)
- {
- return ctx->occupied_cells == SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT;
- }
- static void new_food_pos_(SnakeContext *ctx)
- {
- while (true) {
- const char x = (char) SDL_rand(SNAKE_GAME_WIDTH);
- const char y = (char) SDL_rand(SNAKE_GAME_HEIGHT);
- if (snake_cell_at(ctx, x, y) == SNAKE_CELL_NOTHING) {
- put_cell_at_(ctx, x, y, SNAKE_CELL_FOOD);
- break;
- }
- }
- }
- void snake_initialize(SnakeContext *ctx)
- {
- int i;
- SDL_zeroa(ctx->cells);
- ctx->head_xpos = ctx->tail_xpos = SNAKE_GAME_WIDTH / 2;
- ctx->head_ypos = ctx->tail_ypos = SNAKE_GAME_HEIGHT / 2;
- ctx->next_dir = SNAKE_DIR_RIGHT;
- ctx->inhibit_tail_step = ctx->occupied_cells = 4;
- --ctx->occupied_cells;
- put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_SRIGHT);
- for (i = 0; i < 4; i++) {
- new_food_pos_(ctx);
- ++ctx->occupied_cells;
- }
- }
- void snake_redir(SnakeContext *ctx, SnakeDirection dir)
- {
- SnakeCell ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
- if ((dir == SNAKE_DIR_RIGHT && ct != SNAKE_CELL_SLEFT) ||
- (dir == SNAKE_DIR_UP && ct != SNAKE_CELL_SDOWN) ||
- (dir == SNAKE_DIR_LEFT && ct != SNAKE_CELL_SRIGHT) ||
- (dir == SNAKE_DIR_DOWN && ct != SNAKE_CELL_SUP)) {
- ctx->next_dir = dir;
- }
- }
- static void wrap_around_(char *val, char max)
- {
- if (*val < 0) {
- *val = max - 1;
- } else if (*val > max - 1) {
- *val = 0;
- }
- }
- void snake_step(SnakeContext *ctx)
- {
- const SnakeCell dir_as_cell = (SnakeCell)(ctx->next_dir + 1);
- SnakeCell ct;
- char prev_xpos;
- char prev_ypos;
- /* Move tail forward */
- if (--ctx->inhibit_tail_step == 0) {
- ++ctx->inhibit_tail_step;
- ct = snake_cell_at(ctx, ctx->tail_xpos, ctx->tail_ypos);
- put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_NOTHING);
- switch (ct) {
- case SNAKE_CELL_SRIGHT:
- ctx->tail_xpos++;
- break;
- case SNAKE_CELL_SUP:
- ctx->tail_ypos--;
- break;
- case SNAKE_CELL_SLEFT:
- ctx->tail_xpos--;
- break;
- case SNAKE_CELL_SDOWN:
- ctx->tail_ypos++;
- break;
- default:
- break;
- }
- wrap_around_(&ctx->tail_xpos, SNAKE_GAME_WIDTH);
- wrap_around_(&ctx->tail_ypos, SNAKE_GAME_HEIGHT);
- }
- /* Move head forward */
- prev_xpos = ctx->head_xpos;
- prev_ypos = ctx->head_ypos;
- switch (ctx->next_dir) {
- case SNAKE_DIR_RIGHT:
- ++ctx->head_xpos;
- break;
- case SNAKE_DIR_UP:
- --ctx->head_ypos;
- break;
- case SNAKE_DIR_LEFT:
- --ctx->head_xpos;
- break;
- case SNAKE_DIR_DOWN:
- ++ctx->head_ypos;
- break;
- }
- wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH);
- wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT);
- /* Collisions */
- ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
- if (ct != SNAKE_CELL_NOTHING && ct != SNAKE_CELL_FOOD) {
- snake_initialize(ctx);
- return;
- }
- put_cell_at_(ctx, prev_xpos, prev_ypos, dir_as_cell);
- put_cell_at_(ctx, ctx->head_xpos, ctx->head_ypos, dir_as_cell);
- if (ct == SNAKE_CELL_FOOD) {
- if (are_cells_full_(ctx)) {
- snake_initialize(ctx);
- return;
- }
- new_food_pos_(ctx);
- ++ctx->inhibit_tail_step;
- ++ctx->occupied_cells;
- }
- }
- static SDL_AppResult handle_key_event_(SnakeContext *ctx, SDL_Scancode key_code)
- {
- switch (key_code) {
- /* Quit. */
- case SDL_SCANCODE_ESCAPE:
- case SDL_SCANCODE_Q:
- return SDL_APP_SUCCESS;
- /* Restart the game as if the program was launched. */
- case SDL_SCANCODE_R:
- snake_initialize(ctx);
- break;
- /* Decide new direction of the snake. */
- case SDL_SCANCODE_RIGHT:
- snake_redir(ctx, SNAKE_DIR_RIGHT);
- break;
- case SDL_SCANCODE_UP:
- snake_redir(ctx, SNAKE_DIR_UP);
- break;
- case SDL_SCANCODE_LEFT:
- snake_redir(ctx, SNAKE_DIR_LEFT);
- break;
- case SDL_SCANCODE_DOWN:
- snake_redir(ctx, SNAKE_DIR_DOWN);
- break;
- default:
- break;
- }
- return SDL_APP_CONTINUE;
- }
- SDL_AppResult SDL_AppIterate(void *appstate)
- {
- AppState *as = (AppState *)appstate;
- SnakeContext *ctx = &as->snake_ctx;
- const Uint64 now = SDL_GetTicks();
- SDL_FRect r;
- unsigned i;
- unsigned j;
- int ct;
- // run game logic if we're at or past the time to run it.
- // if we're _really_ behind the time to run it, run it
- // several times.
- while ((now - as->last_step) >= STEP_RATE_IN_MILLISECONDS) {
- snake_step(ctx);
- as->last_step += STEP_RATE_IN_MILLISECONDS;
- }
- r.w = r.h = SNAKE_BLOCK_SIZE_IN_PIXELS;
- SDL_SetRenderDrawColor(as->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
- SDL_RenderClear(as->renderer);
- for (i = 0; i < SNAKE_GAME_WIDTH; i++) {
- for (j = 0; j < SNAKE_GAME_HEIGHT; j++) {
- ct = snake_cell_at(ctx, i, j);
- if (ct == SNAKE_CELL_NOTHING)
- continue;
- set_rect_xy_(&r, i, j);
- if (ct == SNAKE_CELL_FOOD)
- SDL_SetRenderDrawColor(as->renderer, 80, 80, 255, SDL_ALPHA_OPAQUE);
- else /* body */
- SDL_SetRenderDrawColor(as->renderer, 0, 128, 0, SDL_ALPHA_OPAQUE);
- SDL_RenderFillRect(as->renderer, &r);
- }
- }
- SDL_SetRenderDrawColor(as->renderer, 255, 255, 0, SDL_ALPHA_OPAQUE); /*head*/
- set_rect_xy_(&r, ctx->head_xpos, ctx->head_ypos);
- SDL_RenderFillRect(as->renderer, &r);
- SDL_RenderPresent(as->renderer);
- return SDL_APP_CONTINUE;
- }
- static const struct
- {
- const char *key;
- const char *value;
- } extended_metadata[] =
- {
- { SDL_PROP_APP_METADATA_URL_STRING, "https://examples.libsdl.org/SDL3/demo/01-snake/" },
- { SDL_PROP_APP_METADATA_CREATOR_STRING, "SDL team" },
- { SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Placed in the public domain" },
- { SDL_PROP_APP_METADATA_TYPE_STRING, "game" }
- };
- SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
- {
- size_t i;
- if (!SDL_SetAppMetadata("Example Snake game", "1.0", "com.example.Snake")) {
- return SDL_APP_FAILURE;
- }
- for (i = 0; i < SDL_arraysize(extended_metadata); i++) {
- if (!SDL_SetAppMetadataProperty(extended_metadata[i].key, extended_metadata[i].value)) {
- return SDL_APP_FAILURE;
- }
- }
- if (!SDL_Init(SDL_INIT_VIDEO)) {
- return SDL_APP_FAILURE;
- }
- AppState *as = (AppState *)SDL_calloc(1, sizeof(AppState));
- if (!as) {
- return SDL_APP_FAILURE;
- }
- *appstate = as;
- if (!SDL_CreateWindowAndRenderer("examples/demo/snake", SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, 0, &as->window, &as->renderer)) {
- return SDL_APP_FAILURE;
- }
- snake_initialize(&as->snake_ctx);
- as->last_step = SDL_GetTicks();
- return SDL_APP_CONTINUE;
- }
- SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
- {
- SnakeContext *ctx = &((AppState *)appstate)->snake_ctx;
- switch (event->type) {
- case SDL_EVENT_QUIT:
- return SDL_APP_SUCCESS;
- case SDL_EVENT_KEY_DOWN:
- return handle_key_event_(ctx, event->key.scancode);
- }
- return SDL_APP_CONTINUE;
- }
- void SDL_AppQuit(void *appstate, SDL_AppResult result)
- {
- if (appstate != NULL) {
- AppState *as = (AppState *)appstate;
- SDL_DestroyRenderer(as->renderer);
- SDL_DestroyWindow(as->window);
- SDL_free(as);
- }
- }
|