roots_of_life_game.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. #include <furi.h>
  2. #include <gui/gui.h>
  3. #include <input/input.h>
  4. #include <stdlib.h>
  5. #include <gui/view.h>
  6. #include <notification/notification.h>
  7. #include <notification/notification_messages.h>
  8. #include "roots_of_life_game_icons.h"
  9. #define TAG "RootsOfLife"
  10. // Flipper
  11. #define FLIPPER_LCD_WIDTH 128
  12. #define FLIPPER_LCD_HEIGHT 64
  13. // General
  14. #define GROUND_HEIGHT 10
  15. #define CELL_SIZE 3
  16. #define FIELD_START_X 0
  17. #define FIELD_START_Y (GROUND_HEIGHT + 1)
  18. #define CELLS_X (FLIPPER_LCD_WIDTH / CELL_SIZE)
  19. #define CELLS_Y ((FLIPPER_LCD_HEIGHT - GROUND_HEIGHT) / CELL_SIZE)
  20. #define CELLS_TOTAL (CELLS_Y * CELLS_X)
  21. #define CELL(Y, X) (Y * CELLS_X + X)
  22. // Root Spawn
  23. #define ROOT_SIZE_X 4
  24. #define ROOT_SIZE_Y 3
  25. #define ROOT(Y, X) ((Y)*ROOT_SIZE_X + (X))
  26. #define SPAWN_DIRECTIONS 2
  27. #define GROW_STEPS 3
  28. #define RANDOM_GROW_ATTEMPTS 4
  29. #define RANDOM_GROW_CHANCE 50
  30. // UI
  31. #define BLINK_PERIOD 12
  32. #define BLINK_HIDE_FRAMES 5
  33. #define TREE_HEIGHT 10
  34. // Game
  35. #define REROLLS_MAX 5
  36. #define SCORE_FACTOR 10
  37. typedef enum { EventTypeTick, EventTypeKey } EventType;
  38. typedef enum {
  39. R_NONE = 0,
  40. R_UP = 0b1000,
  41. R_DOWN = 0b0100,
  42. R_LEFT = 0b0010,
  43. R_RIGHT = 0b0001
  44. } Direction;
  45. typedef enum { StageStart, StageRun, StageOver } GameStage;
  46. typedef struct {
  47. bool initialDraw;
  48. GameStage stage;
  49. int tick;
  50. bool* filledCells;
  51. char* cells;
  52. bool* filledRootBase;
  53. char* rootBase;
  54. int rootSizeX;
  55. int rootSizeY;
  56. bool* filledRoot;
  57. char* root;
  58. int pX, pY;
  59. int rerolls;
  60. int score;
  61. } GameState;
  62. typedef struct {
  63. EventType type;
  64. InputEvent input;
  65. } GameEvent;
  66. static Direction rand_dir() {
  67. int r = rand() % 4;
  68. return 1 << r;
  69. }
  70. static Direction reverse_dir(Direction dir) {
  71. switch(dir) {
  72. case R_UP:
  73. return R_DOWN;
  74. case R_DOWN:
  75. return R_UP;
  76. case R_LEFT:
  77. return R_RIGHT;
  78. case R_RIGHT:
  79. return R_LEFT;
  80. default:
  81. return R_NONE;
  82. }
  83. }
  84. static bool rand_chance(int chance) {
  85. return (rand() % 100) < chance;
  86. }
  87. static bool has_intersection(char cellA, char cellB) {
  88. return cellA & cellB;
  89. }
  90. static int root_index(GameState* state, int y, int x) {
  91. return y * state->rootSizeX + x;
  92. }
  93. static void set_cell(GameState* state, int y, int x, char cellRoot) {
  94. int c = CELL(y, x);
  95. state->filledCells[c] = true;
  96. state->cells[c] = cellRoot;
  97. }
  98. static void game_state_init(GameState* state) {
  99. state->initialDraw = false;
  100. state->tick = 0;
  101. // Init field arrays
  102. state->filledCells = (bool*)malloc(CELLS_TOTAL * sizeof(bool));
  103. state->cells = (char*)malloc(CELLS_TOTAL * sizeof(char));
  104. state->rootBase = (char*)malloc(ROOT_SIZE_X * ROOT_SIZE_Y * sizeof(char));
  105. state->filledRootBase = (bool*)malloc(ROOT_SIZE_X * ROOT_SIZE_Y * sizeof(bool));
  106. state->root = NULL;
  107. state->filledRoot = NULL;
  108. for(int i = 0; i < CELLS_TOTAL; i++) {
  109. state->filledCells[i] = false;
  110. state->cells[i] = R_NONE;
  111. }
  112. }
  113. static void free_root(GameState* state) {
  114. if(state->root) free(state->root);
  115. if(state->filledRoot) free(state->filledRoot);
  116. }
  117. static void game_state_free(GameState* state) {
  118. free(state->filledCells);
  119. free(state->cells);
  120. free(state->rootBase);
  121. free(state->filledRootBase);
  122. free_root(state);
  123. }
  124. /*static bool has_root(GameState* state, int x, int y) {
  125. return x >= 0 && x < ROOT_SIZE_X && y >= 0 && y < ROOT_SIZE_Y &&
  126. state->filledRootBase[ROOT(y, x)];
  127. }*/
  128. static void generate_new_root(GameState* state) {
  129. for(int i = 0; i < ROOT_SIZE_X * ROOT_SIZE_Y; i++) {
  130. state->filledRootBase[i] = false;
  131. state->rootBase[i] = R_NONE;
  132. }
  133. int cX = ROOT_SIZE_X / 2;
  134. int cY = ROOT_SIZE_Y / 2;
  135. int c = ROOT(cY, cX);
  136. state->filledRootBase[c] = true;
  137. for(int i = 0; i < SPAWN_DIRECTIONS; i++) {
  138. int pX = cX, pY = cY;
  139. for(int g = 0; g < GROW_STEPS; g++) {
  140. Direction dir = rand_dir();
  141. int nX = pX - (dir & R_LEFT ? 1 : 0) + (dir & R_RIGHT ? 1 : 0);
  142. int nY = pY - (dir & R_UP ? 1 : 0) + (dir & R_DOWN ? 1 : 0);
  143. if(nX < 0 || nY < 0 || nX >= ROOT_SIZE_X || nY >= ROOT_SIZE_Y) continue;
  144. int n = ROOT(nY, nX);
  145. state->filledRootBase[n] = true;
  146. // Connect points
  147. int p = ROOT(pY, pX);
  148. state->rootBase[p] |= dir;
  149. state->rootBase[n] |= reverse_dir(dir);
  150. // Grow from new point
  151. pX = nX;
  152. pY = nY;
  153. }
  154. }
  155. for(int y = 0; y < ROOT_SIZE_Y; y++) {
  156. for(int x = 0; x < ROOT_SIZE_X; x++) {
  157. int c = ROOT(y, x);
  158. if(!state->filledRootBase[c]) continue;
  159. /*
  160. if(has_root(state, x - 1, y)) state->rootBase[c] |= R_LEFT;
  161. if(has_root(state, x + 1, y)) state->rootBase[c] |= R_RIGHT;
  162. if(has_root(state, x, y - 1)) state->rootBase[c] |= R_UP;
  163. if(has_root(state, x, y + 1)) state->rootBase[c] |= R_DOWN;
  164. */
  165. for(int r = 0; r < RANDOM_GROW_ATTEMPTS; r++) {
  166. if(!rand_chance(RANDOM_GROW_CHANCE)) continue;
  167. state->rootBase[c] |= rand_dir();
  168. }
  169. }
  170. }
  171. // Copy root to real root
  172. int minX = cX, maxX = cX, minY = cY, maxY = cY;
  173. for(int y = 0; y < ROOT_SIZE_Y; y++) {
  174. for(int x = 0; x < ROOT_SIZE_X; x++) {
  175. int r = ROOT(y, x);
  176. if(!state->filledRootBase[r]) continue;
  177. minX = MIN(minX, x);
  178. maxX = MAX(maxX, x);
  179. minY = MIN(minY, y);
  180. maxY = MAX(maxY, y);
  181. }
  182. }
  183. // Clone to real root
  184. state->rootSizeX = maxX - minX + 1;
  185. state->rootSizeY = maxY - minY + 1;
  186. free_root(state);
  187. state->root = (char*)malloc(state->rootSizeX * state->rootSizeY * sizeof(char));
  188. state->filledRoot = (bool*)malloc(state->rootSizeX * state->rootSizeY * sizeof(bool));
  189. for(int y = 0; y < state->rootSizeY; y++) {
  190. for(int x = 0; x < state->rootSizeX; x++) {
  191. int c = root_index(state, y, x);
  192. int r = ROOT(y + minY, x + minX);
  193. state->filledRoot[c] = state->filledRootBase[r];
  194. state->root[c] = state->rootBase[r];
  195. }
  196. }
  197. }
  198. static bool in_borders(GameState* state, int x, int y) {
  199. return x >= 0 && y >= 0 && x < CELLS_X && y < CELLS_Y
  200. }
  201. static char get_cell(GameState* state, int x, int y) {
  202. if(!in_borders(state, x, y)) return R_NONE;
  203. return state->cells[CELL(y, x)];
  204. }
  205. static bool get_filled_cell(GameState* state, int x, int y) {
  206. if(!in_borders(state, x, y)) return false;
  207. return state->filledCells[CELL(y, x)];
  208. }
  209. static bool can_place_root(GameState* state) {
  210. bool hasConnection = false;
  211. for(int y = 0; y < state->rootSizeY; y++) {
  212. for(int x = 0; x < state->rootSizeX; x++) {
  213. int r = root_index(state, y, x);
  214. if(!state->filledRoot[r]) {
  215. continue;
  216. }
  217. char root = state->root[r];
  218. int rY = y + state->pY;
  219. int rX = x + state->pX;
  220. // Check if colliding
  221. if(get_filled_cell(state, rX, rY)) {
  222. char cell = get_cell(state, rX, rY);
  223. if(has_intersection(cell, root)) {
  224. return false;
  225. }
  226. hasConnection = true;
  227. }
  228. // Check neighbours
  229. hasConnection |= (root & R_RIGHT) && (get_cell(state, rX + 1, rY) & R_LEFT);
  230. hasConnection |= (root & R_LEFT) && (get_cell(state, rX - 1, rY) & R_RIGHT);
  231. hasConnection |= (root & R_UP) && (get_cell(state, rX, rY - 1) & R_DOWN);
  232. hasConnection |= (root & R_DOWN) && (get_cell(state, rX, rY + 1) & R_UP);
  233. }
  234. }
  235. return hasConnection;
  236. }
  237. static bool try_place_root(GameState* state) {
  238. if(!can_place_root(state)) return false;
  239. for(int y = 0; y < state->rootSizeY; y++) {
  240. for(int x = 0; x < state->rootSizeX; x++) {
  241. int r = root_index(state, y, x);
  242. if(!state->filledRoot[r]) continue;
  243. int rY = y + state->pY;
  244. int rX = x + state->pX;
  245. // Root may be out of borders in rare cases (after new cpawn changed its size), just ignore that part
  246. if(in_borders(state, rX, rY)) {
  247. int c = CELL(rY, rX);
  248. state->filledCells[c] = true;
  249. state->cells[c] |= state->root[r];
  250. }
  251. }
  252. }
  253. return true;
  254. }
  255. static void reset_level(GameState* state) {
  256. state->stage = StageStart;
  257. state->tick = 0;
  258. for(int i = 0; i < CELLS_TOTAL; i++) {
  259. state->filledCells[i] = false;
  260. state->cells[i] = R_NONE;
  261. }
  262. generate_new_root(state);
  263. // Starting cells
  264. int midX = CELLS_X / 2;
  265. set_cell(state, 0, midX, R_UP | R_DOWN);
  266. set_cell(state, 1, midX, R_UP | R_DOWN | R_LEFT | R_RIGHT);
  267. set_cell(state, 1, midX - 1, R_RIGHT | R_DOWN);
  268. set_cell(state, 1, midX + 1, R_LEFT | R_DOWN);
  269. set_cell(state, 2, midX, R_UP);
  270. state->pX = midX;
  271. state->pY = 4;
  272. state->rerolls = REROLLS_MAX;
  273. state->score = 0;
  274. }
  275. static void recalculate_score(GameState* state) {
  276. int score = 0;
  277. for(int i = 0; i < CELLS_TOTAL; i++) {
  278. if(state->filledCells[i]) score++;
  279. }
  280. state->score = score * SCORE_FACTOR;
  281. }
  282. static void draw_root_cell(Canvas* canvas, char root, int y, int x, bool isHidden) {
  283. int posX = FIELD_START_X + x * CELL_SIZE + 1, posY = FIELD_START_Y + y * CELL_SIZE + 1;
  284. canvas_draw_dot(canvas, posX, posY);
  285. if(isHidden) {
  286. canvas_set_color(canvas, ColorXOR);
  287. }
  288. if(root & R_UP) canvas_draw_dot(canvas, posX, posY - 1);
  289. if(root & R_DOWN) canvas_draw_dot(canvas, posX, posY + 1);
  290. if(root & R_LEFT) canvas_draw_dot(canvas, posX - 1, posY);
  291. if(root & R_RIGHT) canvas_draw_dot(canvas, posX + 1, posY);
  292. if(isHidden) {
  293. canvas_set_color(canvas, ColorBlack);
  294. }
  295. }
  296. static void draw_placed_roots(Canvas* canvas, GameState* state) {
  297. for(int y = 0; y < CELLS_Y; y++) {
  298. for(int x = 0; x < CELLS_X; x++) {
  299. int c = CELL(y, x);
  300. if(!state->filledCells[c]) continue;
  301. draw_root_cell(canvas, state->cells[c], y, x, false);
  302. }
  303. }
  304. }
  305. static void draw_active_root(Canvas* canvas, GameState* state) {
  306. bool isHidden = (state->tick % BLINK_PERIOD) < BLINK_HIDE_FRAMES;
  307. for(int y = 0; y < state->rootSizeY; y++) {
  308. for(int x = 0; x < state->rootSizeX; x++) {
  309. int c = root_index(state, y, x);
  310. if(!state->filledRoot[c]) continue;
  311. int realX = x + state->pX;
  312. int realY = y + state->pY;
  313. draw_root_cell(canvas, state->root[c], realY, realX, isHidden);
  314. }
  315. }
  316. }
  317. static void draw_generated_root(Canvas* canvas, GameState* state) {
  318. bool isHidden = (state->tick % BLINK_PERIOD) < BLINK_HIDE_FRAMES;
  319. for(int y = 0; y < ROOT_SIZE_Y; y++) {
  320. for(int x = 0; x < ROOT_SIZE_X; x++) {
  321. int c = ROOT(y, x);
  322. if(!state->filledRootBase[c]) continue;
  323. int realX = x + 1;
  324. int realY = y + 1;
  325. draw_root_cell(canvas, state->rootBase[c], realY, realX, isHidden);
  326. }
  327. }
  328. }
  329. static void draw_ground(Canvas* canvas, GameState* state) {
  330. canvas_draw_line(canvas, 0, GROUND_HEIGHT, FLIPPER_LCD_WIDTH, GROUND_HEIGHT);
  331. UNUSED(state);
  332. }
  333. static void draw_tree(Canvas* canvas, GameState* state) {
  334. canvas_draw_icon(canvas, FLIPPER_LCD_WIDTH / 2 - 5, GROUND_HEIGHT - TREE_HEIGHT, &I_tree);
  335. UNUSED(state);
  336. }
  337. static void draw_placement(Canvas* canvas, GameState* state) {
  338. bool canPlace = can_place_root(state);
  339. canvas_draw_icon(canvas, FLIPPER_LCD_WIDTH - 10, 0, canPlace ? &I_place_ok : &I_place_error);
  340. }
  341. static void draw_rerolls(Canvas* canvas, GameState* state) {
  342. UNUSED(canvas);
  343. UNUSED(state);
  344. canvas_draw_icon(canvas, 0, 0, &I_root_reroll);
  345. // Ugh
  346. FuriString* tmp_string = furi_string_alloc();
  347. furi_string_printf(tmp_string, "%d", MAX(0, state->rerolls));
  348. canvas_draw_str(canvas, 11, 9, furi_string_get_cstr(tmp_string));
  349. furi_string_free(tmp_string);
  350. }
  351. static void draw_score(Canvas* canvas, GameState* state) {
  352. UNUSED(canvas);
  353. UNUSED(state);
  354. int x = FLIPPER_LCD_WIDTH / 2 + 15;
  355. canvas_draw_icon(canvas, x, 0, &I_score);
  356. // Ugh
  357. FuriString* tmp_string = furi_string_alloc();
  358. furi_string_printf(tmp_string, "%d", MAX(0, state->score));
  359. canvas_draw_str(canvas, x + 11, 9, furi_string_get_cstr(tmp_string));
  360. furi_string_free(tmp_string);
  361. }
  362. static void draw_gui(Canvas* canvas, GameState* state) {
  363. draw_ground(canvas, state);
  364. draw_tree(canvas, state);
  365. draw_placement(canvas, state);
  366. draw_rerolls(canvas, state);
  367. draw_score(canvas, state);
  368. }
  369. static void draw_center_box(Canvas* canvas, int w2, int h2, int margin) {
  370. int x = FLIPPER_LCD_WIDTH / 2 - w2;
  371. int y = FLIPPER_LCD_HEIGHT / 2 - h2;
  372. canvas_set_color(canvas, ColorWhite);
  373. canvas_draw_box(
  374. canvas, x - margin - 1, y - margin - 1, (w2 + margin + 1) * 2, (h2 + margin + 1) * 2);
  375. canvas_set_color(canvas, ColorBlack);
  376. canvas_draw_frame(canvas, x - margin, y - margin, (w2 + margin) * 2, (h2 + margin) * 2);
  377. }
  378. static void draw_start_ui(Canvas* canvas, GameState* state) {
  379. int w2 = 40;
  380. int margin = 3;
  381. int h2 = 10;
  382. draw_center_box(canvas, w2, h2, margin);
  383. int x = FLIPPER_LCD_WIDTH / 2 - w2;
  384. int y = FLIPPER_LCD_HEIGHT / 2 - h2;
  385. canvas_draw_str(canvas, x + 1, y + 9, " Grow your roots ");
  386. canvas_draw_str(canvas, x + 1, y + 18, "Press [OK] to start");
  387. UNUSED(state);
  388. }
  389. static void draw_end_ui(Canvas* canvas, GameState* state) {
  390. int w2 = 46;
  391. int margin = 3;
  392. int h2 = 15;
  393. draw_center_box(canvas, w2, h2, margin);
  394. int x = FLIPPER_LCD_WIDTH / 2 - w2;
  395. int y = FLIPPER_LCD_HEIGHT / 2 - h2;
  396. canvas_draw_str(canvas, x + 1, y + 9, " Game Over ");
  397. FuriString* tmp_string = furi_string_alloc();
  398. furi_string_printf(tmp_string, "You've got %d points", MAX(0, state->score));
  399. canvas_draw_str(canvas, x + 1, y + 19, furi_string_get_cstr(tmp_string));
  400. furi_string_free(tmp_string);
  401. canvas_draw_str(canvas, x + 2, y + 29, "Press [OK] to restart");
  402. UNUSED(state);
  403. }
  404. static void roots_draw_callback(Canvas* const canvas, void* ctx) {
  405. GameState* state = acquire_mutex((ValueMutex*)ctx, 25);
  406. if(state == NULL) {
  407. return;
  408. }
  409. if(!state->initialDraw) {
  410. state->initialDraw = true;
  411. canvas_set_font(canvas, FontSecondary);
  412. reset_level(state);
  413. }
  414. state->tick++;
  415. draw_gui(canvas, state);
  416. draw_placed_roots(canvas, state);
  417. switch(state->stage) {
  418. case StageStart:
  419. draw_start_ui(canvas, state);
  420. break;
  421. case StageRun:
  422. draw_active_root(canvas, state);
  423. draw_generated_root(canvas, state);
  424. break;
  425. case StageOver:
  426. draw_end_ui(canvas, state);
  427. break;
  428. }
  429. release_mutex((ValueMutex*)ctx, state);
  430. }
  431. static void roots_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  432. furi_assert(event_queue);
  433. GameEvent event = {.type = EventTypeKey, .input = *input_event};
  434. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  435. }
  436. static void roots_update_timer_callback(FuriMessageQueue* event_queue) {
  437. furi_assert(event_queue);
  438. GameEvent event = {.type = EventTypeTick};
  439. furi_message_queue_put(event_queue, &event, 0);
  440. }
  441. static void ProcessStartInput(GameState* state, InputKey key) {
  442. if(key == InputKeyOk) {
  443. state->stage = StageRun;
  444. }
  445. }
  446. static void ProcessRunInput(GameState* state, InputKey key) {
  447. switch(key) {
  448. case InputKeyRight:
  449. state->pX = MIN(state->pX + 1, CELLS_X - state->rootSizeX);
  450. break;
  451. case InputKeyLeft:
  452. state->pX = MAX(state->pX - 1, 0);
  453. break;
  454. case InputKeyUp:
  455. state->pY = MAX(state->pY - 1, 0);
  456. break;
  457. case InputKeyDown:
  458. state->pY = MIN(state->pY + 1, CELLS_Y - state->rootSizeY);
  459. break;
  460. case InputKeyOk: {
  461. bool rootPlaced = try_place_root(state);
  462. if(rootPlaced) {
  463. recalculate_score(state);
  464. generate_new_root(state);
  465. } else {
  466. state->rerolls--;
  467. if(state->rerolls >= 0) {
  468. generate_new_root(state);
  469. } else {
  470. state->stage = StageOver;
  471. }
  472. }
  473. break;
  474. }
  475. default:
  476. break;
  477. }
  478. }
  479. static void ProcessOverInput(GameState* state, InputKey key) {
  480. if(key == InputKeyOk) {
  481. state->stage = StageStart;
  482. reset_level(state);
  483. }
  484. }
  485. int32_t roots_of_life_game_app(void* p) {
  486. FURI_LOG_D(TAG, "Starting game...");
  487. UNUSED(p);
  488. int32_t return_code = 0;
  489. // Set random seed from interrR_UPts
  490. srand(DWT->CYCCNT);
  491. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
  492. GameState* state = malloc(sizeof(GameState));
  493. game_state_init(state);
  494. ValueMutex state_mutex;
  495. if(!init_mutex(&state_mutex, state, sizeof(GameState))) {
  496. FURI_LOG_E(TAG, "Cannot create mutex\r\n");
  497. return_code = 255;
  498. goto free_and_exit;
  499. }
  500. // Set system callbacks
  501. ViewPort* view_port = view_port_alloc();
  502. view_port_draw_callback_set(view_port, roots_draw_callback, &state_mutex);
  503. view_port_input_callback_set(view_port, roots_input_callback, event_queue);
  504. FuriTimer* timer =
  505. furi_timer_alloc(roots_update_timer_callback, FuriTimerTypePeriodic, event_queue);
  506. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
  507. // Open GUI and register view_port
  508. Gui* gui = furi_record_open(RECORD_GUI);
  509. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  510. FURI_LOG_D(TAG, "Entering game loop...");
  511. GameEvent event;
  512. for(bool processing = true; processing;) {
  513. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  514. GameState* state = (GameState*)acquire_mutex_block(&state_mutex);
  515. if(event_status == FuriStatusOk) {
  516. // Key events
  517. if(event.type == EventTypeKey) {
  518. FURI_LOG_D(TAG, "Got key: %d", event.input.key);
  519. if(event.input.type == InputTypePress || event.input.type == InputTypeLong ||
  520. event.input.type == InputTypeRepeat) {
  521. if(event.input.key == InputKeyBack) {
  522. processing = false;
  523. }
  524. switch(state->stage) {
  525. case StageStart:
  526. ProcessStartInput(state, event.input.key);
  527. break;
  528. case StageRun:
  529. ProcessRunInput(state, event.input.key);
  530. break;
  531. case StageOver:
  532. ProcessOverInput(state, event.input.key);
  533. break;
  534. }
  535. }
  536. }
  537. } else {
  538. // Event timeout
  539. FURI_LOG_D(TAG, "furi_message_queue: Event timeout");
  540. }
  541. view_port_update(view_port);
  542. release_mutex(&state_mutex, state);
  543. }
  544. furi_timer_free(timer);
  545. view_port_enabled_set(view_port, false);
  546. gui_remove_view_port(gui, view_port);
  547. furi_record_close(RECORD_GUI);
  548. furi_record_close(RECORD_NOTIFICATION);
  549. view_port_free(view_port);
  550. delete_mutex(&state_mutex);
  551. free_and_exit:
  552. FURI_LOG_D(TAG, "Quitting game...");
  553. game_state_free(state);
  554. free(state);
  555. furi_message_queue_free(event_queue);
  556. return return_code;
  557. }