Atlas - snake.c

Home / ext / SDL / examples / demo / 01-snake Lines: 1 | Size: 11542 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 * Logic implementation of the Snake game. It is designed to efficiently 3 * represent the state of the game in memory. 4 * 5 * This code is public domain. Feel free to use it for any purpose! 6 */ 7 8#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ 9#include <SDL3/SDL.h> 10#include <SDL3/SDL_main.h> 11 12#define STEP_RATE_IN_MILLISECONDS 125 13#define SNAKE_BLOCK_SIZE_IN_PIXELS 24 14#define SDL_WINDOW_WIDTH (SNAKE_BLOCK_SIZE_IN_PIXELS * SNAKE_GAME_WIDTH) 15#define SDL_WINDOW_HEIGHT (SNAKE_BLOCK_SIZE_IN_PIXELS * SNAKE_GAME_HEIGHT) 16 17#define SNAKE_GAME_WIDTH 24U 18#define SNAKE_GAME_HEIGHT 18U 19#define SNAKE_MATRIX_SIZE (SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT) 20 21#define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */ 22#define SNAKE_CELL_SET_BITS (~(~0u << SNAKE_CELL_MAX_BITS)) 23#define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS) 24 25static SDL_Joystick *joystick = NULL; 26 27typedef enum 28{ 29 SNAKE_CELL_NOTHING = 0U, 30 SNAKE_CELL_SRIGHT = 1U, 31 SNAKE_CELL_SUP = 2U, 32 SNAKE_CELL_SLEFT = 3U, 33 SNAKE_CELL_SDOWN = 4U, 34 SNAKE_CELL_FOOD = 5U 35} SnakeCell; 36 37typedef enum 38{ 39 SNAKE_DIR_RIGHT, 40 SNAKE_DIR_UP, 41 SNAKE_DIR_LEFT, 42 SNAKE_DIR_DOWN 43} SnakeDirection; 44 45typedef struct 46{ 47 unsigned char cells[(SNAKE_MATRIX_SIZE * SNAKE_CELL_MAX_BITS) / 8U]; 48 char head_xpos; 49 char head_ypos; 50 char tail_xpos; 51 char tail_ypos; 52 char next_dir; 53 char inhibit_tail_step; 54 unsigned occupied_cells; 55} SnakeContext; 56 57typedef struct 58{ 59 SDL_Window *window; 60 SDL_Renderer *renderer; 61 SnakeContext snake_ctx; 62 Uint64 last_step; 63} AppState; 64 65SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y) 66{ 67 const int shift = SHIFT(x, y); 68 unsigned short range; 69 SDL_memcpy(&range, ctx->cells + (shift / 8), sizeof(range)); 70 return (SnakeCell)((range >> (shift % 8)) & SNAKE_CELL_SET_BITS); 71} 72 73static void set_rect_xy_(SDL_FRect *r, short x, short y) 74{ 75 r->x = (float)(x * SNAKE_BLOCK_SIZE_IN_PIXELS); 76 r->y = (float)(y * SNAKE_BLOCK_SIZE_IN_PIXELS); 77} 78 79static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct) 80{ 81 const int shift = SHIFT(x, y); 82 const int adjust = shift % 8; 83 unsigned char *const pos = ctx->cells + (shift / 8); 84 unsigned short range; 85 SDL_memcpy(&range, pos, sizeof(range)); 86 range &= ~(SNAKE_CELL_SET_BITS << adjust); /* clear bits */ 87 range |= (ct & SNAKE_CELL_SET_BITS) << adjust; 88 SDL_memcpy(pos, &range, sizeof(range)); 89} 90 91static int are_cells_full_(SnakeContext *ctx) 92{ 93 return ctx->occupied_cells == SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT; 94} 95 96static void new_food_pos_(SnakeContext *ctx) 97{ 98 while (true) { 99 const char x = (char) SDL_rand(SNAKE_GAME_WIDTH); 100 const char y = (char) SDL_rand(SNAKE_GAME_HEIGHT); 101 if (snake_cell_at(ctx, x, y) == SNAKE_CELL_NOTHING) { 102 put_cell_at_(ctx, x, y, SNAKE_CELL_FOOD); 103 break; 104 } 105 } 106} 107 108void snake_initialize(SnakeContext *ctx) 109{ 110 int i; 111 SDL_zeroa(ctx->cells); 112 ctx->head_xpos = ctx->tail_xpos = SNAKE_GAME_WIDTH / 2; 113 ctx->head_ypos = ctx->tail_ypos = SNAKE_GAME_HEIGHT / 2; 114 ctx->next_dir = SNAKE_DIR_RIGHT; 115 ctx->inhibit_tail_step = ctx->occupied_cells = 4; 116 --ctx->occupied_cells; 117 put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_SRIGHT); 118 for (i = 0; i < 4; i++) { 119 new_food_pos_(ctx); 120 ++ctx->occupied_cells; 121 } 122} 123 124void snake_redir(SnakeContext *ctx, SnakeDirection dir) 125{ 126 SnakeCell ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos); 127 if ((dir == SNAKE_DIR_RIGHT && ct != SNAKE_CELL_SLEFT) || 128 (dir == SNAKE_DIR_UP && ct != SNAKE_CELL_SDOWN) || 129 (dir == SNAKE_DIR_LEFT && ct != SNAKE_CELL_SRIGHT) || 130 (dir == SNAKE_DIR_DOWN && ct != SNAKE_CELL_SUP)) { 131 ctx->next_dir = dir; 132 } 133} 134 135static void wrap_around_(char *val, char max) 136{ 137 if (*val < 0) { 138 *val = max - 1; 139 } else if (*val > max - 1) { 140 *val = 0; 141 } 142} 143 144void snake_step(SnakeContext *ctx) 145{ 146 const SnakeCell dir_as_cell = (SnakeCell)(ctx->next_dir + 1); 147 SnakeCell ct; 148 char prev_xpos; 149 char prev_ypos; 150 /* Move tail forward */ 151 if (--ctx->inhibit_tail_step == 0) { 152 ++ctx->inhibit_tail_step; 153 ct = snake_cell_at(ctx, ctx->tail_xpos, ctx->tail_ypos); 154 put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_NOTHING); 155 switch (ct) { 156 case SNAKE_CELL_SRIGHT: 157 ctx->tail_xpos++; 158 break; 159 case SNAKE_CELL_SUP: 160 ctx->tail_ypos--; 161 break; 162 case SNAKE_CELL_SLEFT: 163 ctx->tail_xpos--; 164 break; 165 case SNAKE_CELL_SDOWN: 166 ctx->tail_ypos++; 167 break; 168 default: 169 break; 170 } 171 wrap_around_(&ctx->tail_xpos, SNAKE_GAME_WIDTH); 172 wrap_around_(&ctx->tail_ypos, SNAKE_GAME_HEIGHT); 173 } 174 /* Move head forward */ 175 prev_xpos = ctx->head_xpos; 176 prev_ypos = ctx->head_ypos; 177 switch (ctx->next_dir) { 178 case SNAKE_DIR_RIGHT: 179 ++ctx->head_xpos; 180 break; 181 case SNAKE_DIR_UP: 182 --ctx->head_ypos; 183 break; 184 case SNAKE_DIR_LEFT: 185 --ctx->head_xpos; 186 break; 187 case SNAKE_DIR_DOWN: 188 ++ctx->head_ypos; 189 break; 190 default: 191 break; 192 } 193 wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH); 194 wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT); 195 /* Collisions */ 196 ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos); 197 if (ct != SNAKE_CELL_NOTHING && ct != SNAKE_CELL_FOOD) { 198 snake_initialize(ctx); 199 return; 200 } 201 put_cell_at_(ctx, prev_xpos, prev_ypos, dir_as_cell); 202 put_cell_at_(ctx, ctx->head_xpos, ctx->head_ypos, dir_as_cell); 203 if (ct == SNAKE_CELL_FOOD) { 204 if (are_cells_full_(ctx)) { 205 snake_initialize(ctx); 206 return; 207 } 208 new_food_pos_(ctx); 209 ++ctx->inhibit_tail_step; 210 ++ctx->occupied_cells; 211 } 212} 213 214static SDL_AppResult handle_key_event_(SnakeContext *ctx, SDL_Scancode key_code) 215{ 216 switch (key_code) { 217 /* Quit. */ 218 case SDL_SCANCODE_ESCAPE: 219 case SDL_SCANCODE_Q: 220 return SDL_APP_SUCCESS; 221 /* Restart the game as if the program was launched. */ 222 case SDL_SCANCODE_R: 223 snake_initialize(ctx); 224 break; 225 /* Decide new direction of the snake. */ 226 case SDL_SCANCODE_RIGHT: 227 snake_redir(ctx, SNAKE_DIR_RIGHT); 228 break; 229 case SDL_SCANCODE_UP: 230 snake_redir(ctx, SNAKE_DIR_UP); 231 break; 232 case SDL_SCANCODE_LEFT: 233 snake_redir(ctx, SNAKE_DIR_LEFT); 234 break; 235 case SDL_SCANCODE_DOWN: 236 snake_redir(ctx, SNAKE_DIR_DOWN); 237 break; 238 default: 239 break; 240 } 241 return SDL_APP_CONTINUE; 242} 243 244static SDL_AppResult handle_hat_event_(SnakeContext *ctx, Uint8 hat) { 245 switch (hat) { 246 case SDL_HAT_RIGHT: 247 snake_redir(ctx, SNAKE_DIR_RIGHT); 248 break; 249 case SDL_HAT_UP: 250 snake_redir(ctx, SNAKE_DIR_UP); 251 break; 252 case SDL_HAT_LEFT: 253 snake_redir(ctx, SNAKE_DIR_LEFT); 254 break; 255 case SDL_HAT_DOWN: 256 snake_redir(ctx, SNAKE_DIR_DOWN); 257 break; 258 default: 259 break; 260 } 261 return SDL_APP_CONTINUE; 262} 263 264SDL_AppResult SDL_AppIterate(void *appstate) 265{ 266 AppState *as = (AppState *)appstate; 267 SnakeContext *ctx = &as->snake_ctx; 268 const Uint64 now = SDL_GetTicks(); 269 SDL_FRect r; 270 unsigned i; 271 unsigned j; 272 int ct; 273 274 // run game logic if we're at or past the time to run it. 275 // if we're _really_ behind the time to run it, run it 276 // several times. 277 while ((now - as->last_step) >= STEP_RATE_IN_MILLISECONDS) { 278 snake_step(ctx); 279 as->last_step += STEP_RATE_IN_MILLISECONDS; 280 } 281 282 r.w = r.h = SNAKE_BLOCK_SIZE_IN_PIXELS; 283 SDL_SetRenderDrawColor(as->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 284 SDL_RenderClear(as->renderer); 285 for (i = 0; i < SNAKE_GAME_WIDTH; i++) { 286 for (j = 0; j < SNAKE_GAME_HEIGHT; j++) { 287 ct = snake_cell_at(ctx, i, j); 288 if (ct == SNAKE_CELL_NOTHING) 289 continue; 290 set_rect_xy_(&r, i, j); 291 if (ct == SNAKE_CELL_FOOD) 292 SDL_SetRenderDrawColor(as->renderer, 80, 80, 255, SDL_ALPHA_OPAQUE); 293 else /* body */ 294 SDL_SetRenderDrawColor(as->renderer, 0, 128, 0, SDL_ALPHA_OPAQUE); 295 SDL_RenderFillRect(as->renderer, &r); 296 } 297 } 298 SDL_SetRenderDrawColor(as->renderer, 255, 255, 0, SDL_ALPHA_OPAQUE); /*head*/ 299 set_rect_xy_(&r, ctx->head_xpos, ctx->head_ypos); 300 SDL_RenderFillRect(as->renderer, &r); 301 SDL_RenderPresent(as->renderer); 302 return SDL_APP_CONTINUE; 303} 304 305static const struct 306{ 307 const char *key; 308 const char *value; 309} extended_metadata[] = 310{ 311 { SDL_PROP_APP_METADATA_URL_STRING, "https://examples.libsdl.org/SDL3/demo/01-snake/" }, 312 { SDL_PROP_APP_METADATA_CREATOR_STRING, "SDL team" }, 313 { SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Placed in the public domain" }, 314 { SDL_PROP_APP_METADATA_TYPE_STRING, "game" } 315}; 316 317SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) 318{ 319 size_t i; 320 321 if (!SDL_SetAppMetadata("Example Snake game", "1.0", "com.example.Snake")) { 322 return SDL_APP_FAILURE; 323 } 324 325 for (i = 0; i < SDL_arraysize(extended_metadata); i++) { 326 if (!SDL_SetAppMetadataProperty(extended_metadata[i].key, extended_metadata[i].value)) { 327 return SDL_APP_FAILURE; 328 } 329 } 330 331 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK)) { 332 SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); 333 return SDL_APP_FAILURE; 334 } 335 336 AppState *as = (AppState *)SDL_calloc(1, sizeof(AppState)); 337 if (!as) { 338 return SDL_APP_FAILURE; 339 } 340 341 *appstate = as; 342 343 if (!SDL_CreateWindowAndRenderer("examples/demo/snake", SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &as->window, &as->renderer)) { 344 return SDL_APP_FAILURE; 345 } 346 SDL_SetRenderLogicalPresentation(as->renderer, SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); 347 348 snake_initialize(&as->snake_ctx); 349 350 as->last_step = SDL_GetTicks(); 351 352 return SDL_APP_CONTINUE; 353} 354 355SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) 356{ 357 SnakeContext *ctx = &((AppState *)appstate)->snake_ctx; 358 switch (event->type) { 359 case SDL_EVENT_QUIT: 360 return SDL_APP_SUCCESS; 361 case SDL_EVENT_JOYSTICK_ADDED: 362 if (joystick == NULL) { 363 joystick = SDL_OpenJoystick(event->jdevice.which); 364 if (!joystick) { 365 SDL_Log("Failed to open joystick ID %u: %s", (unsigned int) event->jdevice.which, SDL_GetError()); 366 } 367 } 368 break; 369 case SDL_EVENT_JOYSTICK_REMOVED: 370 if (joystick && (SDL_GetJoystickID(joystick) == event->jdevice.which)) { 371 SDL_CloseJoystick(joystick); 372 joystick = NULL; 373 } 374 break; 375 case SDL_EVENT_JOYSTICK_HAT_MOTION: 376 return handle_hat_event_(ctx, event->jhat.value); 377 case SDL_EVENT_KEY_DOWN: 378 return handle_key_event_(ctx, event->key.scancode); 379 default: 380 break; 381 } 382 return SDL_APP_CONTINUE; 383} 384 385void SDL_AppQuit(void *appstate, SDL_AppResult result) 386{ 387 if (joystick) { 388 SDL_CloseJoystick(joystick); 389 } 390 if (appstate != NULL) { 391 AppState *as = (AppState *)appstate; 392 SDL_DestroyRenderer(as->renderer); 393 SDL_DestroyWindow(as->window); 394 SDL_free(as); 395 } 396} 397
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.