roots_of_life_game.c 21 KB

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