From 6aaedb813fa11ba0679c3051bc2eb28646b9506c Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 30 Aug 2025 16:53:58 -0700 Subject: Update to SDL3 --- .../SDL-3.2.20/examples/demo/01-snake/snake.c | 350 +++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c (limited to 'src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c') diff --git a/src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c b/src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c new file mode 100644 index 0000000..0aca862 --- /dev/null +++ b/src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c @@ -0,0 +1,350 @@ +/* + * 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 +#include + +#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); + } +} -- cgit v1.2.3