snake.c 11 KB

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