snake.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /*
  2. * Logic implementation of the Snake game. It is designed to efficiently
  3. * represent in memory the state of the game.
  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 THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */
  18. #define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS)
  19. typedef enum
  20. {
  21. SNAKE_CELL_NOTHING = 0U,
  22. SNAKE_CELL_SRIGHT = 1U,
  23. SNAKE_CELL_SUP = 2U,
  24. SNAKE_CELL_SLEFT = 3U,
  25. SNAKE_CELL_SDOWN = 4U,
  26. SNAKE_CELL_FOOD = 5U
  27. } SnakeCell;
  28. #define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */
  29. typedef enum
  30. {
  31. SNAKE_DIR_RIGHT,
  32. SNAKE_DIR_UP,
  33. SNAKE_DIR_LEFT,
  34. SNAKE_DIR_DOWN
  35. } SnakeDirection;
  36. typedef struct
  37. {
  38. unsigned char cells[(SNAKE_MATRIX_SIZE * SNAKE_CELL_MAX_BITS) / 8U];
  39. char head_xpos;
  40. char head_ypos;
  41. char tail_xpos;
  42. char tail_ypos;
  43. char next_dir;
  44. char inhibit_tail_step;
  45. unsigned occupied_cells;
  46. } SnakeContext;
  47. typedef struct
  48. {
  49. SDL_Window *window;
  50. SDL_Renderer *renderer;
  51. SDL_TimerID step_timer;
  52. SnakeContext snake_ctx;
  53. } AppState;
  54. SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y)
  55. {
  56. const int shift = SHIFT(x, y);
  57. unsigned short range;
  58. SDL_memcpy(&range, ctx->cells + (shift / 8), sizeof(range));
  59. return (SnakeCell)((range >> (shift % 8)) & THREE_BITS);
  60. }
  61. static void set_rect_xy_(SDL_FRect *r, short x, short y)
  62. {
  63. r->x = (float)(x * SNAKE_BLOCK_SIZE_IN_PIXELS);
  64. r->y = (float)(y * SNAKE_BLOCK_SIZE_IN_PIXELS);
  65. }
  66. static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct)
  67. {
  68. const int shift = SHIFT(x, y);
  69. const int adjust = shift % 8;
  70. unsigned char *const pos = ctx->cells + (shift / 8);
  71. unsigned short range;
  72. SDL_memcpy(&range, pos, sizeof(range));
  73. range &= ~(THREE_BITS << adjust); /* clear bits */
  74. range |= (ct & THREE_BITS) << adjust;
  75. SDL_memcpy(pos, &range, sizeof(range));
  76. }
  77. static int are_cells_full_(SnakeContext *ctx)
  78. {
  79. return ctx->occupied_cells == SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT;
  80. }
  81. static void new_food_pos_(SnakeContext *ctx)
  82. {
  83. while (true) {
  84. const char x = (char) SDL_rand(SNAKE_GAME_WIDTH);
  85. const char y = (char) SDL_rand(SNAKE_GAME_HEIGHT);
  86. if (snake_cell_at(ctx, x, y) == SNAKE_CELL_NOTHING) {
  87. put_cell_at_(ctx, x, y, SNAKE_CELL_FOOD);
  88. break;
  89. }
  90. }
  91. }
  92. void snake_initialize(SnakeContext *ctx)
  93. {
  94. int i;
  95. SDL_zeroa(ctx->cells);
  96. ctx->head_xpos = ctx->tail_xpos = SNAKE_GAME_WIDTH / 2;
  97. ctx->head_ypos = ctx->tail_ypos = SNAKE_GAME_HEIGHT / 2;
  98. ctx->next_dir = SNAKE_DIR_RIGHT;
  99. ctx->inhibit_tail_step = ctx->occupied_cells = 4;
  100. --ctx->occupied_cells;
  101. put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_SRIGHT);
  102. for (i = 0; i < 4; i++) {
  103. new_food_pos_(ctx);
  104. ++ctx->occupied_cells;
  105. }
  106. }
  107. void snake_redir(SnakeContext *ctx, SnakeDirection dir)
  108. {
  109. SnakeCell ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
  110. if ((dir == SNAKE_DIR_RIGHT && ct != SNAKE_CELL_SLEFT) ||
  111. (dir == SNAKE_DIR_UP && ct != SNAKE_CELL_SDOWN) ||
  112. (dir == SNAKE_DIR_LEFT && ct != SNAKE_CELL_SRIGHT) ||
  113. (dir == SNAKE_DIR_DOWN && ct != SNAKE_CELL_SUP)) {
  114. ctx->next_dir = dir;
  115. }
  116. }
  117. static void wrap_around_(char *val, char max)
  118. {
  119. if (*val < 0) {
  120. *val = max - 1;
  121. } else if (*val > max - 1) {
  122. *val = 0;
  123. }
  124. }
  125. void snake_step(SnakeContext *ctx)
  126. {
  127. const SnakeCell dir_as_cell = (SnakeCell)(ctx->next_dir + 1);
  128. SnakeCell ct;
  129. char prev_xpos;
  130. char prev_ypos;
  131. /* Move tail forward */
  132. if (--ctx->inhibit_tail_step == 0) {
  133. ++ctx->inhibit_tail_step;
  134. ct = snake_cell_at(ctx, ctx->tail_xpos, ctx->tail_ypos);
  135. put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_NOTHING);
  136. switch (ct) {
  137. case SNAKE_CELL_SRIGHT:
  138. ctx->tail_xpos++;
  139. break;
  140. case SNAKE_CELL_SUP:
  141. ctx->tail_ypos--;
  142. break;
  143. case SNAKE_CELL_SLEFT:
  144. ctx->tail_xpos--;
  145. break;
  146. case SNAKE_CELL_SDOWN:
  147. ctx->tail_ypos++;
  148. break;
  149. default:
  150. break;
  151. }
  152. wrap_around_(&ctx->tail_xpos, SNAKE_GAME_WIDTH);
  153. wrap_around_(&ctx->tail_ypos, SNAKE_GAME_HEIGHT);
  154. }
  155. /* Move head forward */
  156. prev_xpos = ctx->head_xpos;
  157. prev_ypos = ctx->head_ypos;
  158. switch (ctx->next_dir) {
  159. case SNAKE_DIR_RIGHT:
  160. ++ctx->head_xpos;
  161. break;
  162. case SNAKE_DIR_UP:
  163. --ctx->head_ypos;
  164. break;
  165. case SNAKE_DIR_LEFT:
  166. --ctx->head_xpos;
  167. break;
  168. case SNAKE_DIR_DOWN:
  169. ++ctx->head_ypos;
  170. break;
  171. }
  172. wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH);
  173. wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT);
  174. /* Collisions */
  175. ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
  176. if (ct != SNAKE_CELL_NOTHING && ct != SNAKE_CELL_FOOD) {
  177. snake_initialize(ctx);
  178. return;
  179. }
  180. put_cell_at_(ctx, prev_xpos, prev_ypos, dir_as_cell);
  181. put_cell_at_(ctx, ctx->head_xpos, ctx->head_ypos, dir_as_cell);
  182. if (ct == SNAKE_CELL_FOOD) {
  183. if (are_cells_full_(ctx)) {
  184. snake_initialize(ctx);
  185. return;
  186. }
  187. new_food_pos_(ctx);
  188. ++ctx->inhibit_tail_step;
  189. ++ctx->occupied_cells;
  190. }
  191. }
  192. static Uint32 sdl_timer_callback_(void *payload, SDL_TimerID timer_id, Uint32 interval)
  193. {
  194. /* NOTE: snake_step is not called here directly for multithreaded concerns. */
  195. SDL_Event event;
  196. SDL_zero(event);
  197. event.type = SDL_EVENT_USER;
  198. SDL_PushEvent(&event);
  199. return interval;
  200. }
  201. static int handle_key_event_(SnakeContext *ctx, SDL_Scancode key_code)
  202. {
  203. switch (key_code) {
  204. /* Quit. */
  205. case SDL_SCANCODE_ESCAPE:
  206. case SDL_SCANCODE_Q:
  207. return SDL_APP_SUCCESS;
  208. /* Restart the game as if the program was launched. */
  209. case SDL_SCANCODE_R:
  210. snake_initialize(ctx);
  211. break;
  212. /* Decide new direction of the snake. */
  213. case SDL_SCANCODE_RIGHT:
  214. snake_redir(ctx, SNAKE_DIR_RIGHT);
  215. break;
  216. case SDL_SCANCODE_UP:
  217. snake_redir(ctx, SNAKE_DIR_UP);
  218. break;
  219. case SDL_SCANCODE_LEFT:
  220. snake_redir(ctx, SNAKE_DIR_LEFT);
  221. break;
  222. case SDL_SCANCODE_DOWN:
  223. snake_redir(ctx, SNAKE_DIR_DOWN);
  224. break;
  225. default:
  226. break;
  227. }
  228. return SDL_APP_CONTINUE;
  229. }
  230. SDL_AppResult SDL_AppIterate(void *appstate)
  231. {
  232. AppState *as;
  233. SnakeContext *ctx;
  234. SDL_FRect r;
  235. unsigned i;
  236. unsigned j;
  237. int ct;
  238. as = (AppState *)appstate;
  239. ctx = &as->snake_ctx;
  240. r.w = r.h = SNAKE_BLOCK_SIZE_IN_PIXELS;
  241. SDL_SetRenderDrawColor(as->renderer, 0, 0, 0, 255);
  242. SDL_RenderClear(as->renderer);
  243. for (i = 0; i < SNAKE_GAME_WIDTH; i++) {
  244. for (j = 0; j < SNAKE_GAME_HEIGHT; j++) {
  245. ct = snake_cell_at(ctx, i, j);
  246. if (ct == SNAKE_CELL_NOTHING)
  247. continue;
  248. set_rect_xy_(&r, i, j);
  249. if (ct == SNAKE_CELL_FOOD)
  250. SDL_SetRenderDrawColor(as->renderer, 80, 80, 255, 255);
  251. else /* body */
  252. SDL_SetRenderDrawColor(as->renderer, 0, 128, 0, 255);
  253. SDL_RenderFillRect(as->renderer, &r);
  254. }
  255. }
  256. SDL_SetRenderDrawColor(as->renderer, 255, 255, 0, 255); /*head*/
  257. set_rect_xy_(&r, ctx->head_xpos, ctx->head_ypos);
  258. SDL_RenderFillRect(as->renderer, &r);
  259. SDL_RenderPresent(as->renderer);
  260. return SDL_APP_CONTINUE;
  261. }
  262. SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
  263. {
  264. if (!SDL_Init(SDL_INIT_VIDEO)) {
  265. return SDL_APP_FAILURE;
  266. }
  267. AppState *as = SDL_calloc(1, sizeof(AppState));
  268. *appstate = as;
  269. if (!SDL_CreateWindowAndRenderer("examples/game/snake", SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, 0, &as->window, &as->renderer)) {
  270. return SDL_APP_FAILURE;
  271. }
  272. snake_initialize(&as->snake_ctx);
  273. as->step_timer = SDL_AddTimer(STEP_RATE_IN_MILLISECONDS, sdl_timer_callback_, NULL);
  274. if (as->step_timer == 0) {
  275. return SDL_APP_FAILURE;
  276. }
  277. return SDL_APP_CONTINUE;
  278. }
  279. SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
  280. {
  281. SnakeContext *ctx = &((AppState *)appstate)->snake_ctx;
  282. switch (event->type) {
  283. case SDL_EVENT_QUIT:
  284. return SDL_APP_SUCCESS;
  285. case SDL_EVENT_USER:
  286. snake_step(ctx);
  287. break;
  288. case SDL_EVENT_KEY_DOWN:
  289. return handle_key_event_(ctx, event->key.scancode);
  290. }
  291. return SDL_APP_CONTINUE;
  292. }
  293. void SDL_AppQuit(void *appstate, SDL_AppResult result)
  294. {
  295. if (appstate != NULL) {
  296. AppState *as = (AppState *)appstate;
  297. SDL_RemoveTimer(as->step_timer);
  298. SDL_DestroyRenderer(as->renderer);
  299. SDL_DestroyWindow(as->window);
  300. SDL_free(as);
  301. }
  302. }