snake.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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. #include "snake.h"
  8. #include <limits.h> /* CHAR_BIT, CHAR_MAX */
  9. #include <string.h> /* memcpy() */
  10. #define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */
  11. #define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS)
  12. static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct)
  13. {
  14. const int shift = SHIFT(x, y);
  15. const int adjust = shift % CHAR_BIT;
  16. unsigned char *const pos = ctx->cells + (shift / CHAR_BIT);
  17. unsigned short range;
  18. memcpy(&range, pos, sizeof(range));
  19. range &= ~(THREE_BITS << adjust); /* clear bits */
  20. range |= (ct & THREE_BITS) << adjust;
  21. memcpy(pos, &range, sizeof(range));
  22. }
  23. static int are_cells_full_(SnakeContext *ctx)
  24. {
  25. return ctx->occupied_cells == SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT;
  26. }
  27. static void new_food_pos_(SnakeContext *ctx, RandFunc rand)
  28. {
  29. char x;
  30. char y;
  31. for (;;) {
  32. x = (char) rand(SNAKE_GAME_WIDTH);
  33. y = (char) rand(SNAKE_GAME_HEIGHT);
  34. if (snake_cell_at(ctx, x, y) == SNAKE_CELL_NOTHING) {
  35. put_cell_at_(ctx, x, y, SNAKE_CELL_FOOD);
  36. break;
  37. }
  38. }
  39. }
  40. void snake_initialize(SnakeContext *ctx, RandFunc rand)
  41. {
  42. int i;
  43. memset(ctx, 0, sizeof ctx->cells);
  44. ctx->head_xpos = ctx->tail_xpos = SNAKE_GAME_WIDTH / 2;
  45. ctx->head_ypos = ctx->tail_ypos = SNAKE_GAME_HEIGHT / 2;
  46. ctx->next_dir = SNAKE_DIR_RIGHT;
  47. ctx->inhibit_tail_step = ctx->occupied_cells = 4;
  48. --ctx->occupied_cells;
  49. put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_SRIGHT);
  50. for (i = 0; i < 4; i++) {
  51. new_food_pos_(ctx, rand);
  52. ++ctx->occupied_cells;
  53. }
  54. }
  55. void snake_redir(SnakeContext *ctx, SnakeDirection dir)
  56. {
  57. SnakeCell ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
  58. if ((dir == SNAKE_DIR_RIGHT && ct != SNAKE_CELL_SLEFT) ||
  59. (dir == SNAKE_DIR_UP && ct != SNAKE_CELL_SDOWN) ||
  60. (dir == SNAKE_DIR_LEFT && ct != SNAKE_CELL_SRIGHT) ||
  61. (dir == SNAKE_DIR_DOWN && ct != SNAKE_CELL_SUP))
  62. ctx->next_dir = dir;
  63. }
  64. static void wrap_around_(char *val, char max)
  65. {
  66. if (*val < 0)
  67. *val = max - 1;
  68. if (*val > max - 1)
  69. *val = 0;
  70. }
  71. void snake_step(SnakeContext *ctx, RandFunc rand)
  72. {
  73. const SnakeCell dir_as_cell = (SnakeCell)(ctx->next_dir + 1);
  74. SnakeCell ct;
  75. char prev_xpos;
  76. char prev_ypos;
  77. /* Move tail forward */
  78. if (--ctx->inhibit_tail_step == 0) {
  79. ++ctx->inhibit_tail_step;
  80. ct = snake_cell_at(ctx, ctx->tail_xpos, ctx->tail_ypos);
  81. put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_NOTHING);
  82. switch (ct) {
  83. case SNAKE_CELL_SRIGHT:
  84. ctx->tail_xpos++;
  85. break;
  86. case SNAKE_CELL_SUP:
  87. ctx->tail_ypos--;
  88. break;
  89. case SNAKE_CELL_SLEFT:
  90. ctx->tail_xpos--;
  91. break;
  92. case SNAKE_CELL_SDOWN:
  93. ctx->tail_ypos++;
  94. break;
  95. default:
  96. break;
  97. }
  98. wrap_around_(&ctx->tail_xpos, SNAKE_GAME_WIDTH);
  99. wrap_around_(&ctx->tail_ypos, SNAKE_GAME_HEIGHT);
  100. }
  101. /* Move head forward */
  102. prev_xpos = ctx->head_xpos;
  103. prev_ypos = ctx->head_ypos;
  104. switch (ctx->next_dir) {
  105. case SNAKE_DIR_RIGHT:
  106. ++ctx->head_xpos;
  107. break;
  108. case SNAKE_DIR_UP:
  109. --ctx->head_ypos;
  110. break;
  111. case SNAKE_DIR_LEFT:
  112. --ctx->head_xpos;
  113. break;
  114. case SNAKE_DIR_DOWN:
  115. ++ctx->head_ypos;
  116. break;
  117. }
  118. wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH);
  119. wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT);
  120. /* Collisions */
  121. ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
  122. if (ct != SNAKE_CELL_NOTHING && ct != SNAKE_CELL_FOOD) {
  123. snake_initialize(ctx, rand);
  124. return;
  125. }
  126. put_cell_at_(ctx, prev_xpos, prev_ypos, dir_as_cell);
  127. put_cell_at_(ctx, ctx->head_xpos, ctx->head_ypos, dir_as_cell);
  128. if (ct == SNAKE_CELL_FOOD) {
  129. if (are_cells_full_(ctx)) {
  130. snake_initialize(ctx, rand);
  131. return;
  132. }
  133. new_food_pos_(ctx, rand);
  134. ++ctx->inhibit_tail_step;
  135. ++ctx->occupied_cells;
  136. }
  137. }
  138. SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y)
  139. {
  140. const int shift = SHIFT(x, y);
  141. unsigned short range;
  142. memcpy(&range, ctx->cells + (shift / CHAR_BIT), sizeof(range));
  143. return (SnakeCell)((range >> (shift % CHAR_BIT)) & THREE_BITS);
  144. }