snake_20.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <gui/gui.h>
  4. #include <input/input.h>
  5. #include <stdlib.h>
  6. #include <dolphin/dolphin.h>
  7. #include <notification/notification.h>
  8. #include <notification/notification_messages.h>
  9. #include <storage/storage.h>
  10. typedef struct {
  11. // +-----x
  12. // |
  13. // |
  14. // y
  15. uint8_t x;
  16. uint8_t y;
  17. } Point;
  18. typedef enum {
  19. GameStateLife,
  20. GameStatePause,
  21. GameStateLastChance,
  22. GameStateGameOver,
  23. } GameState;
  24. typedef enum {
  25. DirectionUp,
  26. DirectionRight,
  27. DirectionDown,
  28. DirectionLeft,
  29. } Direction;
  30. #define MAX_SNAKE_LEN 15 * 31 //128 * 64 / 4 - 1px border line
  31. #define x_back_symbol 50
  32. #define y_back_symbol 9
  33. #define x_arrow_left 81
  34. #define y_arrow_left 20
  35. #define x_arrow_right 104
  36. #define y_arrow_right 20
  37. #define SAVING_FILENAME APP_DATA_PATH("snake2.save")
  38. typedef struct {
  39. FuriMutex* mutex;
  40. Point points[MAX_SNAKE_LEN];
  41. uint16_t len;
  42. Direction currentMovement;
  43. Direction nextMovement; // if backward of currentMovement, ignore
  44. Point fruit;
  45. GameState state;
  46. bool Endlessmode;
  47. uint32_t timer_start_timestamp;
  48. uint32_t timer_stopped_seconds;
  49. } SnakeState;
  50. typedef enum {
  51. EventTypeTick,
  52. EventTypeKey,
  53. } EventType;
  54. typedef struct {
  55. EventType type;
  56. InputEvent input;
  57. } SnakeEvent;
  58. const NotificationSequence sequence_fail = {
  59. &message_vibro_on,
  60. &message_note_ds4,
  61. &message_delay_10,
  62. &message_sound_off,
  63. &message_delay_10,
  64. &message_note_ds4,
  65. &message_delay_10,
  66. &message_sound_off,
  67. &message_delay_10,
  68. &message_note_ds4,
  69. &message_delay_10,
  70. &message_sound_off,
  71. &message_delay_10,
  72. &message_vibro_off,
  73. NULL,
  74. };
  75. const NotificationSequence sequence_eat = {
  76. &message_vibro_on,
  77. &message_note_c7,
  78. &message_delay_50,
  79. &message_sound_off,
  80. &message_vibro_off,
  81. NULL,
  82. };
  83. static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
  84. furi_assert(ctx);
  85. const SnakeState* snake_state = ctx;
  86. furi_mutex_acquire(snake_state->mutex, FuriWaitForever);
  87. // Before the function is called, the state is set with the canvas_reset(canvas)
  88. // Frame
  89. canvas_draw_frame(canvas, 0, 0, 128, 64);
  90. // Fruit
  91. Point f = snake_state->fruit;
  92. f.x = f.x * 4 + 1;
  93. f.y = f.y * 4 + 1;
  94. canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2);
  95. canvas_draw_dot(canvas, f.x + 3, f.y - 1);
  96. canvas_draw_dot(canvas, f.x + 4, f.y - 2);
  97. //Dot in the middle of an apple (just to know if the Endless mode is turn off)
  98. if(!snake_state->Endlessmode) {
  99. canvas_draw_dot(canvas, f.x + 2, f.y + 2);
  100. canvas_draw_dot(canvas, f.x + 2, f.y + 3);
  101. canvas_draw_dot(canvas, f.x + 3, f.y + 2);
  102. canvas_draw_dot(canvas, f.x + 3, f.y + 3);
  103. }
  104. // Snake
  105. for(uint16_t i = 0; i < snake_state->len; i++) {
  106. Point p = snake_state->points[i];
  107. p.x = p.x * 4 + 2;
  108. p.y = p.y * 4 + 2;
  109. canvas_draw_box(canvas, p.x, p.y, 4, 4);
  110. if(i == 0) {
  111. canvas_set_color(canvas, ColorWhite);
  112. canvas_draw_box(canvas, p.x + 1, p.y + 1, 2, 2);
  113. canvas_set_color(canvas, ColorBlack);
  114. }
  115. }
  116. // Pause and GameOver banner
  117. if(snake_state->state == GameStatePause || snake_state->state == GameStateGameOver) {
  118. // Screen is 128x64 px
  119. canvas_set_color(canvas, ColorWhite);
  120. canvas_draw_box(canvas, 33, 23, 64, 26);
  121. canvas_set_color(canvas, ColorBlack);
  122. canvas_draw_frame(canvas, 34, 24, 62, 24);
  123. canvas_set_font(canvas, FontPrimary);
  124. if(snake_state->state == GameStateGameOver) {
  125. if(snake_state->len >= MAX_SNAKE_LEN-1) {
  126. canvas_draw_str_aligned(canvas, 65, 35, AlignCenter, AlignBottom, "You WON!");
  127. } else {
  128. canvas_draw_str_aligned(canvas, 65, 35, AlignCenter, AlignBottom, "Game Over");
  129. }
  130. }
  131. if(snake_state->state == GameStatePause) {
  132. canvas_draw_str_aligned(canvas, 65, 35, AlignCenter, AlignBottom, "Pause");
  133. }
  134. canvas_set_font(canvas, FontSecondary);
  135. char buffer[40];
  136. snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U);
  137. canvas_draw_str_aligned(canvas, 65, 45, AlignCenter, AlignBottom, buffer);
  138. // Painting "back"-symbol, Help message for Exit App, ProgressBar (Complete %)
  139. canvas_set_color(canvas, ColorWhite);
  140. canvas_draw_box(canvas, 22, 1, 87, 22);
  141. canvas_draw_box(canvas, 24, 54, 83, 9);
  142. canvas_set_color(canvas, ColorBlack);
  143. canvas_draw_str_aligned(
  144. canvas, 65, 10, AlignCenter, AlignBottom, "Hold to Exit App");
  145. //Endless mode ON/OFF
  146. if(snake_state->Endlessmode == false) {
  147. canvas_draw_str_aligned(canvas, 24, 21, AlignLeft, AlignBottom, "Endless mode OFF");
  148. } else {
  149. canvas_draw_str_aligned(canvas, 24, 21, AlignLeft, AlignBottom, "Endless mode");
  150. canvas_draw_str_aligned(canvas, 89, 21, AlignLeft, AlignBottom, "ON");
  151. }
  152. snprintf(
  153. buffer,
  154. sizeof(buffer),
  155. "%-5.1f%% (%.2ld:%.2ld:%.2ld)",
  156. (double)((snake_state->len - 7U) / 4.57),
  157. snake_state->timer_stopped_seconds / 60 / 60,
  158. snake_state->timer_stopped_seconds / 60 % 60,
  159. snake_state->timer_stopped_seconds % 60);
  160. //Back symbol
  161. {
  162. canvas_draw_dot(canvas, x_back_symbol + 0, y_back_symbol);
  163. canvas_draw_dot(canvas, x_back_symbol + 1, y_back_symbol);
  164. canvas_draw_dot(canvas, x_back_symbol + 2, y_back_symbol);
  165. canvas_draw_dot(canvas, x_back_symbol + 3, y_back_symbol);
  166. canvas_draw_dot(canvas, x_back_symbol + 4, y_back_symbol);
  167. canvas_draw_dot(canvas, x_back_symbol + 5, y_back_symbol - 1);
  168. canvas_draw_dot(canvas, x_back_symbol + 6, y_back_symbol - 2);
  169. canvas_draw_dot(canvas, x_back_symbol + 6, y_back_symbol - 3);
  170. canvas_draw_dot(canvas, x_back_symbol + 5, y_back_symbol - 4);
  171. canvas_draw_dot(canvas, x_back_symbol + 4, y_back_symbol - 5);
  172. canvas_draw_dot(canvas, x_back_symbol + 3, y_back_symbol - 5);
  173. canvas_draw_dot(canvas, x_back_symbol + 2, y_back_symbol - 5);
  174. canvas_draw_dot(canvas, x_back_symbol + 1, y_back_symbol - 5);
  175. canvas_draw_dot(canvas, x_back_symbol + 0, y_back_symbol - 5);
  176. canvas_draw_dot(canvas, x_back_symbol - 1, y_back_symbol - 5);
  177. canvas_draw_dot(canvas, x_back_symbol - 2, y_back_symbol - 5);
  178. canvas_draw_dot(canvas, x_back_symbol - 3, y_back_symbol - 5);
  179. canvas_draw_dot(canvas, x_back_symbol - 2, y_back_symbol - 6);
  180. canvas_draw_dot(canvas, x_back_symbol - 2, y_back_symbol - 4);
  181. canvas_draw_dot(canvas, x_back_symbol - 1, y_back_symbol - 6);
  182. canvas_draw_dot(canvas, x_back_symbol - 1, y_back_symbol - 4);
  183. canvas_draw_dot(canvas, x_back_symbol - 1, y_back_symbol - 7);
  184. canvas_draw_dot(canvas, x_back_symbol - 1, y_back_symbol - 3);
  185. }
  186. //Left Arrow
  187. canvas_draw_str_aligned(canvas, 65, 62, AlignCenter, AlignBottom, buffer);
  188. {
  189. canvas_draw_dot(canvas, x_arrow_left + 0, y_arrow_left - 3);
  190. canvas_draw_dot(canvas, x_arrow_left + 1, y_arrow_left - 2);
  191. canvas_draw_dot(canvas, x_arrow_left + 1, y_arrow_left - 3);
  192. canvas_draw_dot(canvas, x_arrow_left + 1, y_arrow_left - 4);
  193. canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 1);
  194. canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 2);
  195. canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 3);
  196. canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 4);
  197. canvas_draw_dot(canvas, x_arrow_left + 2, y_arrow_left - 5);
  198. canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 0);
  199. canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 1);
  200. canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 2);
  201. canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 3);
  202. canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 4);
  203. canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 5);
  204. canvas_draw_dot(canvas, x_arrow_left + 3, y_arrow_left - 6);
  205. }
  206. //Right Arrow
  207. canvas_draw_str_aligned(canvas, 65, 62, AlignCenter, AlignBottom, buffer);
  208. {
  209. canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 0);
  210. canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 1);
  211. canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 2);
  212. canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 3);
  213. canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 4);
  214. canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 5);
  215. canvas_draw_dot(canvas, x_arrow_right + 0, y_arrow_right - 6);
  216. canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 1);
  217. canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 2);
  218. canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 3);
  219. canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 4);
  220. canvas_draw_dot(canvas, x_arrow_right + 1, y_arrow_right - 5);
  221. canvas_draw_dot(canvas, x_arrow_right + 2, y_arrow_right - 2);
  222. canvas_draw_dot(canvas, x_arrow_right + 2, y_arrow_right - 3);
  223. canvas_draw_dot(canvas, x_arrow_right + 2, y_arrow_right - 4);
  224. canvas_draw_dot(canvas, x_arrow_right + 3, y_arrow_right - 3);
  225. }
  226. }
  227. furi_mutex_release(snake_state->mutex);
  228. }
  229. bool load_game(SnakeState* snake_state) {
  230. Storage* storage = furi_record_open(RECORD_STORAGE);
  231. File* file = storage_file_alloc(storage);
  232. uint16_t bytes_readed = 0;
  233. if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
  234. bytes_readed = storage_file_read(file, snake_state, sizeof(SnakeState));
  235. }
  236. storage_file_close(file);
  237. storage_file_free(file);
  238. furi_record_close(RECORD_STORAGE);
  239. return bytes_readed == sizeof(SnakeState);
  240. }
  241. void save_game(const SnakeState* snake_state) {
  242. Storage* storage = furi_record_open(RECORD_STORAGE);
  243. File* file = storage_file_alloc(storage);
  244. if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  245. storage_file_write(file, snake_state, sizeof(SnakeState));
  246. }
  247. storage_file_close(file);
  248. storage_file_free(file);
  249. furi_record_close(RECORD_STORAGE);
  250. }
  251. static void snake_game_input_callback(InputEvent* input_event, void* ctx) {
  252. furi_assert(ctx);
  253. FuriMessageQueue* event_queue = ctx;
  254. SnakeEvent event = {.type = EventTypeKey, .input = *input_event};
  255. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  256. }
  257. static void snake_game_update_timer_callback(void* ctx) {
  258. furi_assert(ctx);
  259. FuriMessageQueue* event_queue = ctx;
  260. SnakeEvent event = {.type = EventTypeTick};
  261. furi_message_queue_put(event_queue, &event, 0);
  262. }
  263. static void snake_game_init_game(SnakeState* const snake_state) {
  264. Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}};
  265. memcpy(snake_state->points, p, sizeof(p)); //-V1086
  266. snake_state->len = 7;
  267. snake_state->currentMovement = DirectionRight;
  268. snake_state->nextMovement = DirectionRight;
  269. Point f = {18, 6};
  270. snake_state->fruit = f;
  271. DateTime curr_dt;
  272. furi_hal_rtc_get_datetime(&curr_dt);
  273. uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt);
  274. snake_state->timer_stopped_seconds = 0;
  275. snake_state->timer_start_timestamp = curr_ts;
  276. snake_state->state = GameStateLife;
  277. save_game(snake_state);
  278. }
  279. static Point snake_game_get_new_fruit(SnakeState const* const snake_state) {
  280. // Max number of fruits on x axis = (16 * 2) - 1 = 31 (0<=x=>30)
  281. // Max number of fruits on y axis = (8 * 2) - 1 = 15 (0<=y=>14)
  282. // Total fields for fruits and snake body = 31 * 15 = 465
  283. // Empty fields for next random fruit = 465 - len(snake)
  284. bool* all_fields;
  285. int* empty_fields;
  286. all_fields = (bool*)malloc(MAX_SNAKE_LEN * sizeof(bool));
  287. empty_fields = (int*)malloc(MAX_SNAKE_LEN * sizeof(int));
  288. for(uint16_t j = 0; j < MAX_SNAKE_LEN; j++) {
  289. all_fields[j] = false;
  290. }
  291. for(uint16_t j = 0; j < snake_state->len; j++) {
  292. Point p = snake_state->points[j];
  293. all_fields[p.x + 31 * p.y] = true;
  294. }
  295. int empty_counter = 0;
  296. for(uint16_t j = 0; j < MAX_SNAKE_LEN; j++) {
  297. if(!all_fields[j]) {
  298. empty_fields[empty_counter] = j;
  299. empty_counter++;
  300. }
  301. }
  302. int newFruit = rand() % empty_counter;
  303. Point p = {
  304. .x = empty_fields[newFruit] % 31,
  305. .y = empty_fields[newFruit] / 31,
  306. };
  307. free(all_fields);
  308. free(empty_fields);
  309. return p;
  310. }
  311. static bool snake_game_collision_with_frame(Point const next_step) {
  312. // if x == 0 && currentMovement == left then x - 1 == 255 ,
  313. // so check only x > right border
  314. return next_step.x > 30 || next_step.y > 14;
  315. }
  316. static bool
  317. snake_game_collision_with_tail(SnakeState const* const snake_state, Point const next_step) {
  318. for(uint16_t i = 0; i < snake_state->len; i++) {
  319. Point p = snake_state->points[i];
  320. if(p.x == next_step.x && p.y == next_step.y) {
  321. return true;
  322. }
  323. }
  324. return false;
  325. }
  326. static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
  327. // Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality.
  328. bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1;
  329. return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement;
  330. }
  331. static Point snake_game_get_next_step(SnakeState const* const snake_state) {
  332. Point next_step = snake_state->points[0];
  333. switch(snake_state->currentMovement) {
  334. // +-----x
  335. // |
  336. // |
  337. // y
  338. case DirectionUp:
  339. next_step.y--;
  340. break;
  341. case DirectionRight:
  342. next_step.x++;
  343. break;
  344. case DirectionDown:
  345. next_step.y++;
  346. break;
  347. case DirectionLeft:
  348. next_step.x--;
  349. break;
  350. }
  351. return next_step;
  352. }
  353. static void snake_game_move_snake(SnakeState* const snake_state, Point const next_step) {
  354. memmove(snake_state->points + 1, snake_state->points, snake_state->len * sizeof(Point));
  355. snake_state->points[0] = next_step;
  356. }
  357. static void
  358. snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) {
  359. if(snake_state->state == GameStateGameOver) {
  360. return;
  361. }
  362. snake_state->currentMovement = snake_game_get_turn_snake(snake_state);
  363. Point next_step = snake_game_get_next_step(snake_state);
  364. bool crush = snake_game_collision_with_frame(next_step);
  365. if(crush) {
  366. if(snake_state->state == GameStateLife) {
  367. snake_state->state = GameStateLastChance;
  368. return;
  369. } else if(snake_state->state == GameStateLastChance) {
  370. if(snake_state->Endlessmode) {
  371. snake_state->state = GameStateLastChance;
  372. } else {
  373. DateTime curr_dt;
  374. furi_hal_rtc_get_datetime(&curr_dt);
  375. uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt);
  376. snake_state->timer_stopped_seconds = curr_ts - snake_state->timer_start_timestamp;
  377. snake_state->state = GameStateGameOver;
  378. }
  379. notification_message_block(notification, &sequence_fail);
  380. return;
  381. }
  382. } else {
  383. if(snake_state->state == GameStateLastChance) {
  384. snake_state->state = GameStateLife;
  385. }
  386. }
  387. crush = snake_game_collision_with_tail(snake_state, next_step);
  388. if(crush) {
  389. if(snake_state->Endlessmode) {
  390. snake_state->state = GameStateLastChance;
  391. } else {
  392. DateTime curr_dt;
  393. furi_hal_rtc_get_datetime(&curr_dt);
  394. uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt);
  395. snake_state->timer_stopped_seconds = curr_ts - snake_state->timer_start_timestamp;
  396. snake_state->state = GameStateGameOver;
  397. }
  398. notification_message_block(notification, &sequence_fail);
  399. return;
  400. }
  401. bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
  402. if(eatFruit) {
  403. snake_state->len++;
  404. if(snake_state->len >= MAX_SNAKE_LEN-1) {
  405. //You win!!!
  406. //It's impossible to collect ALL fruits, because
  407. //the number of rows is odd (15),
  408. //the number of columnss is odd too (31).
  409. //You just can't locate the snake's body
  410. //on the odd number of cells.
  411. //Because of it you win when you collect
  412. //all but one fruits.
  413. DateTime curr_dt;
  414. furi_hal_rtc_get_datetime(&curr_dt);
  415. uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt);
  416. snake_state->timer_stopped_seconds = curr_ts - snake_state->timer_start_timestamp;
  417. snake_state->state = GameStateGameOver;
  418. notification_message_block(notification, &sequence_fail);
  419. return;
  420. }
  421. }
  422. snake_game_move_snake(snake_state, next_step);
  423. if(eatFruit) {
  424. snake_state->fruit = snake_game_get_new_fruit(snake_state);
  425. notification_message(notification, &sequence_eat);
  426. notification_message(notification, &sequence_blink_red_100);
  427. }
  428. }
  429. int32_t snake_20_app(void* p) {
  430. UNUSED(p);
  431. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
  432. SnakeState* snake_state = malloc(sizeof(SnakeState));
  433. if(!load_game(snake_state)) {
  434. snake_game_init_game(snake_state);
  435. } else {
  436. DateTime curr_dt;
  437. furi_hal_rtc_get_datetime(&curr_dt);
  438. uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt);
  439. snake_state->timer_start_timestamp = curr_ts - snake_state->timer_stopped_seconds;
  440. snake_state->state = GameStateLife;
  441. }
  442. snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  443. if(!snake_state->mutex) {
  444. FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
  445. furi_message_queue_free(event_queue);
  446. free(snake_state);
  447. return 255;
  448. }
  449. ViewPort* view_port = view_port_alloc();
  450. view_port_draw_callback_set(view_port, snake_game_render_callback, snake_state);
  451. view_port_input_callback_set(view_port, snake_game_input_callback, event_queue);
  452. FuriTimer* timer =
  453. furi_timer_alloc(snake_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
  454. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
  455. // Open GUI and register view_port
  456. Gui* gui = furi_record_open(RECORD_GUI);
  457. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  458. NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
  459. notification_message_block(notification, &sequence_display_backlight_enforce_on);
  460. dolphin_deed(DolphinDeedPluginGameStart);
  461. SnakeEvent event;
  462. for(bool processing = true; processing;) {
  463. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  464. furi_mutex_acquire(snake_state->mutex, FuriWaitForever);
  465. if(event_status == FuriStatusOk) {
  466. if(event.type == EventTypeKey) {
  467. // press events
  468. if(event.input.type == InputTypePress) {
  469. switch(event.input.key) {
  470. case InputKeyUp:
  471. if(snake_state->state != GameStatePause) {
  472. snake_state->nextMovement = DirectionUp;
  473. }
  474. break;
  475. case InputKeyDown:
  476. if(snake_state->state != GameStatePause) {
  477. snake_state->nextMovement = DirectionDown;
  478. }
  479. break;
  480. case InputKeyRight:
  481. if(snake_state->state == GameStatePause ||
  482. snake_state->state == GameStateGameOver) {
  483. snake_state->Endlessmode = !snake_state->Endlessmode;
  484. } else {
  485. snake_state->nextMovement = DirectionRight;
  486. }
  487. break;
  488. case InputKeyLeft:
  489. if(snake_state->state == GameStatePause ||
  490. snake_state->state == GameStateGameOver) {
  491. snake_state->Endlessmode = !snake_state->Endlessmode;
  492. } else {
  493. snake_state->nextMovement = DirectionLeft;
  494. }
  495. break;
  496. case InputKeyOk:
  497. if(snake_state->state == GameStateGameOver) {
  498. snake_game_init_game(snake_state);
  499. }
  500. if(snake_state->state == GameStatePause) {
  501. DateTime curr_dt;
  502. furi_hal_rtc_get_datetime(&curr_dt);
  503. uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt);
  504. snake_state->timer_start_timestamp =
  505. curr_ts - snake_state->timer_stopped_seconds;
  506. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
  507. snake_state->state = GameStateLife;
  508. }
  509. break;
  510. case InputKeyBack:
  511. if(snake_state->state == GameStateLife) {
  512. furi_timer_stop(timer);
  513. snake_state->state = GameStatePause;
  514. DateTime curr_dt;
  515. furi_hal_rtc_get_datetime(&curr_dt);
  516. uint32_t curr_ts = datetime_datetime_to_timestamp(&curr_dt);
  517. snake_state->timer_stopped_seconds =
  518. curr_ts - snake_state->timer_start_timestamp;
  519. break;
  520. }
  521. default:
  522. break;
  523. }
  524. }
  525. //LongPress Events
  526. if(event.input.type == InputTypeLong) {
  527. switch(event.input.key) {
  528. case InputKeyUp:
  529. if(snake_state->state != GameStatePause) {
  530. snake_state->nextMovement = DirectionUp;
  531. //Speed Up
  532. if(snake_state->currentMovement == DirectionUp) {
  533. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
  534. }
  535. //Breaking
  536. if(snake_state->currentMovement == DirectionDown) {
  537. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 2);
  538. }
  539. }
  540. break;
  541. case InputKeyDown:
  542. if(snake_state->state != GameStatePause) {
  543. snake_state->nextMovement = DirectionDown;
  544. //Speed Up
  545. if(snake_state->currentMovement == DirectionDown) {
  546. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
  547. }
  548. //Breaking
  549. if(snake_state->currentMovement == DirectionUp) {
  550. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 2);
  551. }
  552. }
  553. break;
  554. case InputKeyRight:
  555. if(snake_state->state != GameStatePause) {
  556. snake_state->nextMovement = DirectionRight;
  557. //Speed Up
  558. if(snake_state->currentMovement == DirectionRight) {
  559. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
  560. }
  561. //Breaking
  562. if(snake_state->currentMovement == DirectionLeft) {
  563. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 2);
  564. }
  565. }
  566. break;
  567. case InputKeyLeft:
  568. if(snake_state->state != GameStatePause) {
  569. snake_state->nextMovement = DirectionLeft;
  570. //Speed Up
  571. if(snake_state->currentMovement == DirectionLeft) {
  572. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
  573. }
  574. //Breaking
  575. if(snake_state->currentMovement == DirectionRight) {
  576. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 2);
  577. }
  578. }
  579. break;
  580. case InputKeyBack:
  581. if(snake_state->state == GameStatePause ||
  582. snake_state->state == GameStateGameOver) {
  583. save_game(snake_state);
  584. processing = false;
  585. } else {
  586. snake_state->state = GameStateGameOver;
  587. }
  588. break;
  589. default:
  590. break;
  591. }
  592. }
  593. //ReleaseKey Event
  594. if(event.input.type == InputTypeRelease) {
  595. if(snake_state->state != GameStatePause) {
  596. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
  597. }
  598. }
  599. } else if(event.type == EventTypeTick) {
  600. snake_game_process_game_step(snake_state, notification);
  601. }
  602. } else {
  603. // event timeout
  604. }
  605. furi_mutex_release(snake_state->mutex);
  606. view_port_update(view_port);
  607. }
  608. // Wait for all notifications to be played and return backlight to normal state
  609. notification_message_block(notification, &sequence_display_backlight_enforce_auto);
  610. furi_timer_free(timer);
  611. view_port_enabled_set(view_port, false);
  612. gui_remove_view_port(gui, view_port);
  613. furi_record_close(RECORD_GUI);
  614. furi_record_close(RECORD_NOTIFICATION);
  615. view_port_free(view_port);
  616. furi_message_queue_free(event_queue);
  617. furi_mutex_free(snake_state->mutex);
  618. free(snake_state);
  619. return 0;
  620. }