snake_game.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. #include <furi.h>
  2. #include <gui/gui.h>
  3. #include <input/input.h>
  4. #include <stdlib.h>
  5. typedef struct {
  6. // +-----x
  7. // |
  8. // |
  9. // y
  10. uint8_t x;
  11. uint8_t y;
  12. } Point;
  13. typedef enum {
  14. GameStateLife,
  15. // https://melmagazine.com/en-us/story/snake-nokia-6110-oral-history-taneli-armanto
  16. // Armanto: While testing the early versions of the game, I noticed it was hard
  17. // to control the snake upon getting close to and edge but not crashing — especially
  18. // in the highest speed levels. I wanted the highest level to be as fast as I could
  19. // possibly make the device "run," but on the other hand, I wanted to be friendly
  20. // and help the player manage that level. Otherwise it might not be fun to play. So
  21. // I implemented a little delay. A few milliseconds of extra time right before
  22. // the player crashes, during which she can still change the directions. And if
  23. // she does, the game continues.
  24. GameStateLastChance,
  25. GameStateGameOver,
  26. } GameState;
  27. typedef enum {
  28. DirectionUp,
  29. DirectionRight,
  30. DirectionDown,
  31. DirectionLeft,
  32. } Direction;
  33. #define MAX_SNAKE_LEN 253
  34. typedef struct {
  35. Point points[MAX_SNAKE_LEN];
  36. uint16_t len;
  37. Direction currentMovement;
  38. Direction nextMovement; // if backward of currentMovement, ignore
  39. Point fruit;
  40. GameState state;
  41. } SnakeState;
  42. typedef enum {
  43. EventTypeTick,
  44. EventTypeKey,
  45. } EventType;
  46. typedef struct {
  47. EventType type;
  48. InputEvent input;
  49. } SnakeEvent;
  50. static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
  51. const SnakeState* snake_state = acquire_mutex((ValueMutex*)ctx, 25);
  52. if(snake_state == NULL) {
  53. return;
  54. }
  55. // Before the function is called, the state is set with the canvas_reset(canvas)
  56. // Frame
  57. canvas_draw_frame(canvas, 0, 0, 128, 64);
  58. // Fruit
  59. Point f = snake_state->fruit;
  60. f.x = f.x * 4 + 1;
  61. f.y = f.y * 4 + 1;
  62. canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2);
  63. // Snake
  64. for(uint16_t i = 0; i < snake_state->len; i++) {
  65. Point p = snake_state->points[i];
  66. p.x = p.x * 4 + 2;
  67. p.y = p.y * 4 + 2;
  68. canvas_draw_box(canvas, p.x, p.y, 4, 4);
  69. }
  70. // Game Over banner
  71. if(snake_state->state == GameStateGameOver) {
  72. // Screen is 128x64 px
  73. canvas_set_color(canvas, ColorWhite);
  74. canvas_draw_box(canvas, 34, 20, 62, 24);
  75. canvas_set_color(canvas, ColorBlack);
  76. canvas_draw_frame(canvas, 34, 20, 62, 24);
  77. canvas_set_font(canvas, FontPrimary);
  78. canvas_draw_str(canvas, 37, 31, "Game Over");
  79. canvas_set_font(canvas, FontSecondary);
  80. char buffer[12];
  81. snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7);
  82. canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
  83. }
  84. release_mutex((ValueMutex*)ctx, snake_state);
  85. }
  86. static void snake_game_input_callback(InputEvent* input_event, osMessageQueueId_t event_queue) {
  87. furi_assert(event_queue);
  88. SnakeEvent event = {.type = EventTypeKey, .input = *input_event};
  89. osMessageQueuePut(event_queue, &event, 0, osWaitForever);
  90. }
  91. static void snake_game_update_timer_callback(osMessageQueueId_t event_queue) {
  92. furi_assert(event_queue);
  93. SnakeEvent event = {.type = EventTypeTick};
  94. osMessageQueuePut(event_queue, &event, 0, 0);
  95. }
  96. static void snake_game_init_game(SnakeState* const snake_state) {
  97. Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}};
  98. memcpy(snake_state->points, p, sizeof(p));
  99. snake_state->len = 7;
  100. snake_state->currentMovement = DirectionRight;
  101. snake_state->nextMovement = DirectionRight;
  102. Point f = {18, 6};
  103. snake_state->fruit = f;
  104. snake_state->state = GameStateLife;
  105. }
  106. static Point snake_game_get_new_fruit(SnakeState const* const snake_state) {
  107. // 1 bit for each point on the playing field where the snake can turn
  108. // and where the fruit can appear
  109. uint16_t buffer[8];
  110. memset(buffer, 0, sizeof(buffer));
  111. uint8_t empty = 8 * 16;
  112. for(uint16_t i = 0; i < snake_state->len; i++) {
  113. Point p = snake_state->points[i];
  114. if(p.x % 2 != 0 || p.y % 2 != 0) {
  115. continue;
  116. }
  117. p.x /= 2;
  118. p.y /= 2;
  119. buffer[p.y] |= 1 << p.x;
  120. empty--;
  121. }
  122. // Bit set if snake use that playing field
  123. uint16_t newFruit = rand() % empty;
  124. // Skip random number of _empty_ fields
  125. for(uint8_t y = 0; y < 8; y++) {
  126. for(uint16_t x = 0, mask = 1; x < 16; x += 1, mask <<= 1) {
  127. if((buffer[y] & mask) == 0) {
  128. if(newFruit == 0) {
  129. Point p = {
  130. .x = x * 2,
  131. .y = y * 2,
  132. };
  133. return p;
  134. }
  135. newFruit--;
  136. }
  137. }
  138. }
  139. // We will never be here
  140. Point p = {0, 0};
  141. return p;
  142. }
  143. static bool snake_game_collision_with_frame(Point const next_step) {
  144. // if x == 0 && currentMovement == left then x - 1 == 255 ,
  145. // so check only x > right border
  146. return next_step.x > 30 || next_step.y > 14;
  147. }
  148. static bool
  149. snake_game_collision_with_tail(SnakeState const* const snake_state, Point const next_step) {
  150. for(uint16_t i = 0; i < snake_state->len; i++) {
  151. Point p = snake_state->points[i];
  152. if(p.x == next_step.x && p.y == next_step.y) {
  153. return true;
  154. }
  155. }
  156. return false;
  157. }
  158. static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
  159. switch(snake_state->currentMovement) {
  160. case DirectionUp:
  161. switch(snake_state->nextMovement) {
  162. case DirectionRight:
  163. return DirectionRight;
  164. case DirectionLeft:
  165. return DirectionLeft;
  166. default:
  167. return snake_state->currentMovement;
  168. }
  169. case DirectionRight:
  170. switch(snake_state->nextMovement) {
  171. case DirectionUp:
  172. return DirectionUp;
  173. case DirectionDown:
  174. return DirectionDown;
  175. default:
  176. return snake_state->currentMovement;
  177. }
  178. case DirectionDown:
  179. switch(snake_state->nextMovement) {
  180. case DirectionRight:
  181. return DirectionRight;
  182. case DirectionLeft:
  183. return DirectionLeft;
  184. default:
  185. return snake_state->currentMovement;
  186. }
  187. default: // case DirectionLeft:
  188. switch(snake_state->nextMovement) {
  189. case DirectionUp:
  190. return DirectionUp;
  191. case DirectionDown:
  192. return DirectionDown;
  193. default:
  194. return snake_state->currentMovement;
  195. }
  196. }
  197. }
  198. static Point snake_game_get_next_step(SnakeState const* const snake_state) {
  199. Point next_step = snake_state->points[0];
  200. switch(snake_state->currentMovement) {
  201. // +-----x
  202. // |
  203. // |
  204. // y
  205. case DirectionUp:
  206. next_step.y--;
  207. break;
  208. case DirectionRight:
  209. next_step.x++;
  210. break;
  211. case DirectionDown:
  212. next_step.y++;
  213. break;
  214. case DirectionLeft:
  215. next_step.x--;
  216. break;
  217. }
  218. return next_step;
  219. }
  220. static void snake_game_move_snake(SnakeState* const snake_state, Point const next_step) {
  221. memmove(snake_state->points + 1, snake_state->points, snake_state->len * sizeof(Point));
  222. snake_state->points[0] = next_step;
  223. }
  224. static void snake_game_process_game_step(SnakeState* const snake_state) {
  225. if(snake_state->state == GameStateGameOver) {
  226. return;
  227. }
  228. bool can_turn = (snake_state->points[0].x % 2 == 0) && (snake_state->points[0].y % 2 == 0);
  229. if(can_turn) {
  230. snake_state->currentMovement = snake_game_get_turn_snake(snake_state);
  231. }
  232. Point next_step = snake_game_get_next_step(snake_state);
  233. bool crush = snake_game_collision_with_frame(next_step);
  234. if(crush) {
  235. if(snake_state->state == GameStateLife) {
  236. snake_state->state = GameStateLastChance;
  237. return;
  238. } else if(snake_state->state == GameStateLastChance) {
  239. snake_state->state = GameStateGameOver;
  240. return;
  241. }
  242. } else {
  243. if(snake_state->state == GameStateLastChance) {
  244. snake_state->state = GameStateLife;
  245. }
  246. }
  247. crush = snake_game_collision_with_tail(snake_state, next_step);
  248. if(crush) {
  249. snake_state->state = GameStateGameOver;
  250. return;
  251. }
  252. bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
  253. if(eatFruit) {
  254. snake_state->len++;
  255. if(snake_state->len >= MAX_SNAKE_LEN) {
  256. snake_state->state = GameStateGameOver;
  257. return;
  258. }
  259. }
  260. snake_game_move_snake(snake_state, next_step);
  261. if(eatFruit) {
  262. snake_state->fruit = snake_game_get_new_fruit(snake_state);
  263. }
  264. }
  265. int32_t snake_game_app(void* p) {
  266. srand(DWT->CYCCNT);
  267. osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(SnakeEvent), NULL);
  268. SnakeState* snake_state = malloc(sizeof(SnakeState));
  269. snake_game_init_game(snake_state);
  270. ValueMutex state_mutex;
  271. if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) {
  272. FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
  273. free(snake_state);
  274. return 255;
  275. }
  276. ViewPort* view_port = view_port_alloc();
  277. view_port_draw_callback_set(view_port, snake_game_render_callback, &state_mutex);
  278. view_port_input_callback_set(view_port, snake_game_input_callback, event_queue);
  279. osTimerId_t timer =
  280. osTimerNew(snake_game_update_timer_callback, osTimerPeriodic, event_queue, NULL);
  281. osTimerStart(timer, osKernelGetTickFreq() / 4);
  282. // Open GUI and register view_port
  283. Gui* gui = furi_record_open("gui");
  284. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  285. SnakeEvent event;
  286. for(bool processing = true; processing;) {
  287. osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100);
  288. SnakeState* snake_state = (SnakeState*)acquire_mutex_block(&state_mutex);
  289. if(event_status == osOK) {
  290. // press events
  291. if(event.type == EventTypeKey) {
  292. if(event.input.type == InputTypePress) {
  293. switch(event.input.key) {
  294. case InputKeyUp:
  295. snake_state->nextMovement = DirectionUp;
  296. break;
  297. case InputKeyDown:
  298. snake_state->nextMovement = DirectionDown;
  299. break;
  300. case InputKeyRight:
  301. snake_state->nextMovement = DirectionRight;
  302. break;
  303. case InputKeyLeft:
  304. snake_state->nextMovement = DirectionLeft;
  305. break;
  306. case InputKeyOk:
  307. if(snake_state->state == GameStateGameOver) {
  308. snake_game_init_game(snake_state);
  309. }
  310. break;
  311. case InputKeyBack:
  312. processing = false;
  313. break;
  314. }
  315. }
  316. } else if(event.type == EventTypeTick) {
  317. snake_game_process_game_step(snake_state);
  318. }
  319. } else {
  320. // event timeout
  321. }
  322. view_port_update(view_port);
  323. release_mutex(&state_mutex, snake_state);
  324. }
  325. osTimerDelete(timer);
  326. view_port_enabled_set(view_port, false);
  327. gui_remove_view_port(gui, view_port);
  328. furi_record_close("gui");
  329. view_port_free(view_port);
  330. osMessageQueueDelete(event_queue);
  331. delete_mutex(&state_mutex);
  332. free(snake_state);
  333. return 0;
  334. }
  335. // Screen is 128x64 px
  336. // (4 + 4) * 16 - 4 + 2 + 2border == 128
  337. // (4 + 4) * 8 - 4 + 2 + 2border == 64
  338. // Game field from point{x: 0, y: 0} to point{x: 30, y: 14}.
  339. // The snake turns only in even cells - intersections.
  340. // ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
  341. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  342. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  343. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  344. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  345. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  346. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  347. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  348. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  349. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  350. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  351. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  352. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  353. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  354. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  355. // ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
  356. // └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘