nupogodi.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. #include <furi.h>
  2. #include <furi_hal_random.h>
  3. #include <gui/gui.h>
  4. #include <gui/elements.h>
  5. #include <gui/view_dispatcher.h>
  6. #include <gui/modules/dialog_ex.h>
  7. #include <gui/modules/widget.h>
  8. #include <storage/storage.h>
  9. #include <stdlib.h>
  10. #include <power/power_service/power.h>
  11. #include <nupogodi_icons.h>
  12. #include "notifications.h"
  13. #include <dolphin/dolphin.h>
  14. #define WIN_SCORES 100
  15. #define EGGS_2_SCORES 10
  16. #define EGGS_3_SCORES 20
  17. #define EGGS_4_SCORES 50
  18. typedef struct NuPogodiModel NuPogodiModel;
  19. typedef struct {
  20. Gui* gui;
  21. NotificationApp* notification;
  22. ViewDispatcher* view_dispatcher;
  23. View* view;
  24. Widget* widget;
  25. FuriThread* worker_thread;
  26. FuriTimer* timer;
  27. NuPogodiModel* model;
  28. } NuPogodiApp;
  29. typedef enum {
  30. Logo,
  31. Ready,
  32. Play,
  33. Pause,
  34. Fail,
  35. Over,
  36. } NuPogodiMode;
  37. struct NuPogodiModel {
  38. bool sound;
  39. NuPogodiMode mode;
  40. uint8_t fail_pause;
  41. uint8_t tick;
  42. bool top;
  43. bool left;
  44. uint8_t missed;
  45. uint16_t scores;
  46. uint8_t eggs[4];
  47. };
  48. typedef enum {
  49. WorkerEventReserved = (1 << 0),
  50. WorkerEventStop = (1 << 1),
  51. WorkerEventTick = (1 << 2),
  52. } WorkerEventFlags;
  53. typedef enum {
  54. NuPogodiAppViewGame,
  55. NuPogodiAppViewPause,
  56. } NuPogodiAppView;
  57. #define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventTick)
  58. static void nupogodi_view_draw_wolf(Canvas* canvas, NuPogodiModel* model) {
  59. if(model->mode == Play) {
  60. if(model->left) {
  61. if(model->top) {
  62. canvas_draw_icon(canvas, 41, 16, &I_WolfLeftUp);
  63. } else {
  64. canvas_draw_icon(canvas, 41, 16, &I_WolfLeftDown);
  65. }
  66. } else {
  67. if(model->top) {
  68. canvas_draw_icon(canvas, 41, 16, &I_WolfRightUp);
  69. } else {
  70. canvas_draw_icon(canvas, 41, 16, &I_WolfRightDown);
  71. }
  72. }
  73. } else if(model->mode == Fail) {
  74. if(model->left) {
  75. canvas_draw_icon(canvas, 46, 0, &I_WolfLeftFail);
  76. } else {
  77. canvas_draw_icon(canvas, 36, 0, &I_WolfRightFail);
  78. }
  79. }
  80. }
  81. static void nupogodi_view_draw_eggs(Canvas* canvas, NuPogodiModel* model) {
  82. // Top Right
  83. if(model->eggs[0] > 0) {
  84. if((model->eggs[0] - 1) / 4 == 0) {
  85. canvas_draw_icon(canvas, 108, 12, &I_Egg1);
  86. } else if((model->eggs[0] - 1) / 4 == 1) {
  87. canvas_draw_icon(canvas, 103, 13, &I_Egg2);
  88. } else if((model->eggs[0] - 1) / 4 == 2) {
  89. canvas_draw_icon(canvas, 98, 14, &I_Egg3);
  90. } else if((model->eggs[0] - 1) / 4 == 3) {
  91. canvas_draw_icon(canvas, 93, 16, &I_Egg4);
  92. } else if((model->eggs[0] - 1) / 4 == 4) {
  93. canvas_draw_icon(canvas, 88, 17, &I_Egg5);
  94. }
  95. }
  96. // Bottom Right
  97. if(model->eggs[1] > 0) {
  98. if((model->eggs[1] - 1) / 4 == 0) {
  99. canvas_draw_icon_ex(canvas, 108, 36, &I_Egg1, IconRotation90);
  100. } else if((model->eggs[1] - 1) / 4 == 1) {
  101. canvas_draw_icon_ex(canvas, 103, 37, &I_Egg2, IconRotation90);
  102. } else if((model->eggs[1] - 1) / 4 == 2) {
  103. canvas_draw_icon_ex(canvas, 98, 38, &I_Egg3, IconRotation90);
  104. } else if((model->eggs[1] - 1) / 4 == 3) {
  105. canvas_draw_icon_ex(canvas, 93, 39, &I_Egg4, IconRotation90);
  106. } else if((model->eggs[1] - 1) / 4 == 4) {
  107. canvas_draw_icon_ex(canvas, 88, 41, &I_Egg5, IconRotation90);
  108. }
  109. }
  110. // Top Left
  111. if(model->eggs[2] > 0) {
  112. if((model->eggs[2] - 1) / 4 == 0) {
  113. canvas_draw_icon_ex(canvas, 10, 13, &I_Egg1, IconRotation180);
  114. } else if((model->eggs[2] - 1) / 4 == 1) {
  115. canvas_draw_icon_ex(canvas, 15, 13, &I_Egg2, IconRotation180);
  116. } else if((model->eggs[2] - 1) / 4 == 2) {
  117. canvas_draw_icon_ex(canvas, 20, 15, &I_Egg3, IconRotation180);
  118. } else if((model->eggs[2] - 1) / 4 == 3) {
  119. canvas_draw_icon_ex(canvas, 25, 17, &I_Egg4, IconRotation180);
  120. } else if((model->eggs[2] - 1) / 4 == 4) {
  121. canvas_draw_icon_ex(canvas, 30, 18, &I_Egg5, IconRotation180);
  122. }
  123. }
  124. // Bottom Left
  125. if(model->eggs[3] > 0) {
  126. if((model->eggs[3] - 1) / 4 == 0) {
  127. canvas_draw_icon_ex(canvas, 10, 35, &I_Egg1, IconRotation270);
  128. } else if((model->eggs[3] - 1) / 4 == 1) {
  129. canvas_draw_icon_ex(canvas, 15, 38, &I_Egg2, IconRotation270);
  130. } else if((model->eggs[3] - 1) / 4 == 2) {
  131. canvas_draw_icon_ex(canvas, 20, 39, &I_Egg3, IconRotation270);
  132. } else if((model->eggs[3] - 1) / 4 == 3) {
  133. canvas_draw_icon_ex(canvas, 25, 40, &I_Egg4, IconRotation270);
  134. } else if((model->eggs[3] - 1) / 4 == 4) {
  135. canvas_draw_icon_ex(canvas, 30, 42, &I_Egg5, IconRotation270);
  136. }
  137. }
  138. }
  139. static void nupogodi_view_draw_scores(Canvas* canvas, NuPogodiModel* model) {
  140. if(model->mode == Play) {
  141. if(model->missed >= 1) {
  142. canvas_draw_icon(canvas, 78, 0, &I_Chick);
  143. }
  144. if(model->missed >= 2) {
  145. canvas_draw_icon(canvas, 92, 0, &I_Chick);
  146. }
  147. if(model->missed >= 3) {
  148. canvas_draw_icon(canvas, 106, 0, &I_Chick);
  149. }
  150. canvas_draw_icon(canvas, 22, 0, &I_Egg3);
  151. FuriString* scores_str =
  152. model->scores < WIN_SCORES ?
  153. furi_string_alloc_printf("%1d/%1d", model->scores, WIN_SCORES) :
  154. furi_string_alloc_printf("%1d", model->scores);
  155. canvas_set_font(canvas, FontSecondary);
  156. canvas_draw_str(canvas, 38, 10, furi_string_get_cstr(scores_str));
  157. furi_string_free(scores_str);
  158. }
  159. }
  160. static void nupogodi_view_draw_scene(Canvas* canvas) {
  161. canvas_draw_icon(canvas, 0, 0, &I_ChickenL);
  162. canvas_draw_icon(canvas, 0, 24, &I_ChickenL);
  163. canvas_draw_icon(canvas, 120, 0, &I_ChickenR);
  164. canvas_draw_icon(canvas, 120, 24, &I_ChickenR);
  165. canvas_draw_line(canvas, 0, 21, 8, 21);
  166. canvas_draw_line(canvas, 0, 22, 8, 22);
  167. canvas_draw_line(canvas, 0, 23, 8, 23);
  168. canvas_draw_line(canvas, 8, 21, 36, 29);
  169. canvas_draw_line(canvas, 8, 22, 36, 30);
  170. canvas_draw_line(canvas, 8, 23, 36, 31);
  171. canvas_draw_line(canvas, 0, 45, 10, 45);
  172. canvas_draw_line(canvas, 0, 46, 10, 46);
  173. canvas_draw_line(canvas, 0, 47, 10, 47);
  174. canvas_draw_line(canvas, 10, 45, 36, 53);
  175. canvas_draw_line(canvas, 10, 46, 36, 54);
  176. canvas_draw_line(canvas, 10, 47, 36, 55);
  177. canvas_draw_line(canvas, 120, 21, 127, 21);
  178. canvas_draw_line(canvas, 120, 22, 127, 22);
  179. canvas_draw_line(canvas, 120, 23, 127, 23);
  180. canvas_draw_line(canvas, 92, 29, 120, 21);
  181. canvas_draw_line(canvas, 92, 30, 120, 22);
  182. canvas_draw_line(canvas, 92, 31, 120, 23);
  183. canvas_draw_line(canvas, 120, 45, 127, 45);
  184. canvas_draw_line(canvas, 120, 46, 127, 46);
  185. canvas_draw_line(canvas, 120, 47, 127, 47);
  186. canvas_draw_line(canvas, 92, 53, 120, 45);
  187. canvas_draw_line(canvas, 92, 54, 120, 46);
  188. canvas_draw_line(canvas, 92, 55, 120, 47);
  189. }
  190. static void nupogodi_view_draw_logo(Canvas* canvas) {
  191. canvas_draw_icon(canvas, 0, 0, &I_NuPogodi);
  192. }
  193. static void nupogodi_view_draw_over(Canvas* canvas, NuPogodiModel* model) {
  194. canvas_draw_icon(canvas, 30, 0, &I_Egg3);
  195. FuriString* scores_str = furi_string_alloc_printf("%1d", model->scores);
  196. canvas_set_font(canvas, FontSecondary);
  197. canvas_draw_str(canvas, 46, 10, furi_string_get_cstr(scores_str));
  198. furi_string_free(scores_str);
  199. if(model->scores >= WIN_SCORES) {
  200. canvas_draw_icon(canvas, 22, 12, &I_OverWin);
  201. } else {
  202. canvas_draw_icon(canvas, 22, 12, &I_OverLose);
  203. }
  204. }
  205. static void nupogodi_view_draw_callback(Canvas* canvas, void* _model) {
  206. NuPogodiModel* model = _model;
  207. furi_assert(model);
  208. canvas_clear(canvas);
  209. canvas_set_color(canvas, ColorBlack);
  210. switch(model->mode) {
  211. case Logo:
  212. nupogodi_view_draw_logo(canvas);
  213. break;
  214. case Play:
  215. case Fail:
  216. nupogodi_view_draw_eggs(canvas, model);
  217. nupogodi_view_draw_scene(canvas);
  218. nupogodi_view_draw_scores(canvas, model);
  219. nupogodi_view_draw_wolf(canvas, model);
  220. break;
  221. case Pause:
  222. nupogodi_view_draw_scene(canvas);
  223. nupogodi_view_draw_scores(canvas, model);
  224. break;
  225. case Over:
  226. nupogodi_view_draw_over(canvas, model);
  227. break;
  228. default:
  229. break;
  230. }
  231. }
  232. static bool nupogodi_view_input_callback(InputEvent* event, void* context) {
  233. NuPogodiApp* app = context;
  234. furi_assert(app);
  235. bool consumed = false;
  236. if(app->model->mode == Fail) {
  237. return false;
  238. }
  239. if(event->type == InputTypeShort) {
  240. switch(event->key) {
  241. case InputKeyUp:
  242. consumed = true;
  243. app->model->top = true;
  244. break;
  245. case InputKeyDown:
  246. consumed = true;
  247. app->model->top = false;
  248. break;
  249. case InputKeyLeft:
  250. consumed = true;
  251. app->model->left = true;
  252. break;
  253. case InputKeyRight:
  254. consumed = true;
  255. app->model->left = false;
  256. break;
  257. case InputKeyOk:
  258. consumed = true;
  259. if(app->model->mode == Play) {
  260. app->model->sound = !app->model->sound;
  261. } else if(app->model->mode == Over) {
  262. app->model->mode = Ready;
  263. }
  264. break;
  265. case InputKeyBack:
  266. if(app->model->mode == Play) {
  267. consumed = true;
  268. app->model->mode = Pause;
  269. view_dispatcher_switch_to_view(app->view_dispatcher, NuPogodiAppViewPause);
  270. }
  271. default:
  272. break;
  273. }
  274. }
  275. return consumed;
  276. }
  277. static uint32_t nupogodi_exit(void* context) {
  278. NuPogodiApp* app = context;
  279. furi_assert(app);
  280. if(app->model->mode == Over) {
  281. return VIEW_NONE;
  282. } else {
  283. return VIEW_IGNORE;
  284. }
  285. }
  286. static void nupogodi_pause_exit(GuiButtonType result, InputType type, void* context) {
  287. UNUSED(result);
  288. furi_assert(context);
  289. NuPogodiApp* app = context;
  290. if(type == InputTypeShort) {
  291. view_dispatcher_stop(app->view_dispatcher);
  292. }
  293. }
  294. static void nupogodi_pause_go(GuiButtonType result, InputType type, void* context) {
  295. UNUSED(result);
  296. furi_assert(context);
  297. NuPogodiApp* app = context;
  298. if(type == InputTypeShort) {
  299. view_dispatcher_switch_to_view(app->view_dispatcher, NuPogodiAppViewGame);
  300. app->model->mode = Play;
  301. }
  302. }
  303. static int32_t nupogodi_worker(void* context) {
  304. furi_assert(context);
  305. NuPogodiApp* app = context;
  306. UNUSED(app);
  307. while(1) {
  308. uint32_t events =
  309. furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever);
  310. furi_check((events & FuriFlagError) == 0);
  311. if(events & WorkerEventStop) break;
  312. if(events & WorkerEventTick) {
  313. with_view_model(
  314. app->view,
  315. NuPogodiModel * model,
  316. {
  317. uint8_t sound = model->sound ? 0 : 1;
  318. switch(model->mode) {
  319. case Logo:
  320. if(model->fail_pause > 0) {
  321. model->fail_pause--;
  322. } else {
  323. model->mode = Play;
  324. dolphin_deed(DolphinDeedPluginGameStart);
  325. }
  326. break;
  327. case Ready:
  328. model->eggs[0] = 0;
  329. model->eggs[1] = 0;
  330. model->eggs[2] = 0;
  331. model->eggs[3] = 0;
  332. model->scores = 0;
  333. model->missed = 0;
  334. model->tick = 0;
  335. model->mode = Play;
  336. dolphin_deed(DolphinDeedPluginGameStart);
  337. break;
  338. case Play:
  339. // Прокатываем яйцо текущего такста на одно деление вперед
  340. for(uint8_t i = 0; i < 4; i++) {
  341. if((model->eggs[i] > 0) && ((model->eggs[i] - 1) % 4 == model->tick)) {
  342. model->eggs[i] += 4;
  343. if((model->eggs[i] - 1) / 4 < 5) {
  344. notification_message(
  345. app->notification, notification_eggs[i][sound]);
  346. }
  347. }
  348. }
  349. // Есть ли на текущем такте яйцо
  350. uint8_t tick_egg = 0;
  351. for(uint8_t i = 0; i < 4; i++) {
  352. if((model->eggs[i] > 0) && ((model->eggs[i] - 1) % 4 == model->tick)) {
  353. tick_egg = 1;
  354. }
  355. }
  356. // Определяем положение яйца нулевого такта
  357. uint8_t first_egg_pos = 0;
  358. for(uint8_t i = 0; i < 4; i++) {
  359. if((model->eggs[i] > 0) && ((model->eggs[i] - 1) % 4 == 0)) {
  360. first_egg_pos = (model->eggs[i] - 1) / 4;
  361. }
  362. }
  363. // В зависимости от количества собраных яиц добавляем новые
  364. if(tick_egg == 0) {
  365. if((model->tick == 0) ||
  366. ((model->tick == 2) && (model->scores > EGGS_2_SCORES)) ||
  367. ((model->tick == 1) && (model->scores > EGGS_3_SCORES)) ||
  368. ((model->tick == 3) && (model->scores > EGGS_4_SCORES))) {
  369. // Добавляем яйцо второго только тогда, когда яйцо нулевого такта уже немного прокатилось, для равномерности
  370. // Яйца первого и третьего тактов так же добавляем в зависимости от положения нулевого.
  371. // Это дает ровную последовательность появления яиц
  372. if((model->tick == 0) || (model->tick + 1 == first_egg_pos)) {
  373. uint32_t rnd;
  374. // Ищем случайное, гарантированно свободное место
  375. do {
  376. rnd = furi_hal_random_get() % 4;
  377. } while(model->eggs[rnd] != 0);
  378. // Добавляем яйцо
  379. // Положение яйца это значение в ячейке деленное на 4
  380. // Такт яйца - его остаток от деления ни 4
  381. // Т.к. 0 это значит, что в яцейке нет яйца, поэтому всегда добавляется/отнимается единичка
  382. model->eggs[rnd] = model->tick + 1;
  383. notification_message(
  384. app->notification, notification_eggs[rnd][sound]);
  385. }
  386. }
  387. }
  388. // Проверяем те яйца, которые должны попасть в корзину
  389. if((model->eggs[0] > 0) && ((model->eggs[0] - 1) % 4 == model->tick) &&
  390. ((model->eggs[0] - 1) / 4 == 5) && (model->top) && (!model->left)) {
  391. model->eggs[0] = 0;
  392. model->scores++;
  393. notification_message(app->notification, notification_done[sound]);
  394. } else if(
  395. (model->eggs[1] > 0) && ((model->eggs[1] - 1) % 4 == model->tick) &&
  396. ((model->eggs[1] - 1) / 4 == 5) && (!model->top) && (!model->left)) {
  397. model->eggs[1] = 0;
  398. model->scores++;
  399. notification_message(app->notification, notification_done[sound]);
  400. } else if(
  401. (model->eggs[2] > 0) && ((model->eggs[2] - 1) % 4 == model->tick) &&
  402. ((model->eggs[2] - 1) / 4 == 5) && (model->top) && (model->left)) {
  403. model->eggs[2] = 0;
  404. model->scores++;
  405. notification_message(app->notification, notification_done[sound]);
  406. } else if(
  407. (model->eggs[3] > 0) && ((model->eggs[3] - 1) % 4 == model->tick) &&
  408. ((model->eggs[3] - 1) / 4 == 5) && (!model->top) && (model->left)) {
  409. model->eggs[3] = 0;
  410. model->scores++;
  411. notification_message(app->notification, notification_done[sound]);
  412. } else {
  413. // Если яйцо было не поймано - зануляем все, запускаем анимацию разбития
  414. for(uint8_t i = 0; i < 4; i++) {
  415. if((model->eggs[i] > 0) &&
  416. ((model->eggs[i] - 1) % 4 == model->tick) &&
  417. ((model->eggs[i] - 1) / 4 == 5)) {
  418. model->eggs[0] = 0;
  419. model->eggs[1] = 0;
  420. model->eggs[2] = 0;
  421. model->eggs[3] = 0;
  422. model->left = i >= 2;
  423. model->missed++;
  424. if(model->missed < 4) {
  425. model->mode = Fail;
  426. model->fail_pause = 3 * 4;
  427. } else {
  428. model->mode = Over;
  429. if(model->scores >= WIN_SCORES) {
  430. dolphin_deed(DolphinDeedPluginGameWin);
  431. }
  432. }
  433. notification_message(
  434. app->notification, notification_fail[sound]);
  435. }
  436. }
  437. }
  438. break;
  439. case Pause:
  440. // nop
  441. break;
  442. case Fail:
  443. if(model->fail_pause > 0) {
  444. model->fail_pause--;
  445. } else {
  446. model->mode = Play;
  447. }
  448. break;
  449. case Over:
  450. break;
  451. }
  452. model->tick++;
  453. if(model->tick > 3) {
  454. model->tick = 0;
  455. }
  456. },
  457. true);
  458. }
  459. }
  460. return 0;
  461. }
  462. static void nupogodi_timer_callback(void* context) {
  463. furi_assert(context);
  464. NuPogodiApp* app = context;
  465. furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventTick);
  466. }
  467. static Widget* nupogodi_widget_alloc(NuPogodiApp* app) {
  468. Widget* widget = widget_alloc();
  469. widget_add_string_multiline_element(
  470. widget, 64, 20, AlignCenter, AlignTop, FontSecondary, "Game Paused");
  471. widget_add_button_element(widget, GuiButtonTypeLeft, "Exit", nupogodi_pause_exit, app);
  472. widget_add_button_element(widget, GuiButtonTypeRight, "Go", nupogodi_pause_go, app);
  473. return widget;
  474. }
  475. static NuPogodiApp* nupogodi_app_alloc() {
  476. NuPogodiApp* app = malloc(sizeof(NuPogodiApp));
  477. // Gui
  478. app->gui = furi_record_open(RECORD_GUI);
  479. app->notification = furi_record_open(RECORD_NOTIFICATION);
  480. // View dispatcher
  481. app->view_dispatcher = view_dispatcher_alloc();
  482. view_dispatcher_enable_queue(app->view_dispatcher);
  483. view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
  484. // Views
  485. app->view = view_alloc();
  486. view_set_context(app->view, app);
  487. view_set_draw_callback(app->view, nupogodi_view_draw_callback);
  488. view_set_input_callback(app->view, nupogodi_view_input_callback);
  489. view_allocate_model(app->view, ViewModelTypeLocking, sizeof(NuPogodiModel));
  490. with_view_model(
  491. app->view,
  492. NuPogodiModel * model,
  493. {
  494. app->model = model;
  495. model->mode = Logo;
  496. model->tick = 0;
  497. model->fail_pause = 4; // Пропускаем 4 такта перед началом игры - показываем лого
  498. model->top = false;
  499. model->left = true;
  500. model->missed = 0;
  501. model->scores = 0;
  502. model->eggs[0] = 0;
  503. model->eggs[1] = 0;
  504. model->eggs[2] = 0;
  505. model->eggs[3] = 0;
  506. },
  507. true);
  508. view_set_previous_callback(app->view, nupogodi_exit);
  509. view_dispatcher_add_view(app->view_dispatcher, 0, app->view);
  510. view_dispatcher_switch_to_view(app->view_dispatcher, NuPogodiAppViewGame);
  511. app->widget = nupogodi_widget_alloc(app);
  512. view_dispatcher_add_view(
  513. app->view_dispatcher, NuPogodiAppViewPause, widget_get_view(app->widget));
  514. app->worker_thread = furi_thread_alloc_ex("NuPogodiWorker", 1024, nupogodi_worker, app);
  515. furi_thread_start(app->worker_thread);
  516. app->timer = furi_timer_alloc(nupogodi_timer_callback, FuriTimerTypePeriodic, app);
  517. furi_timer_start(app->timer, furi_ms_to_ticks(250));
  518. notification_message(app->notification, &sequence_display_backlight_enforce_on);
  519. return app;
  520. }
  521. static void nupogodi_app_free(NuPogodiApp* app) {
  522. furi_assert(app);
  523. notification_message(app->notification, &sequence_display_backlight_enforce_auto);
  524. furi_timer_stop(app->timer);
  525. furi_timer_free(app->timer);
  526. furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop);
  527. furi_thread_join(app->worker_thread);
  528. furi_thread_free(app->worker_thread);
  529. // Free views
  530. view_dispatcher_remove_view(app->view_dispatcher, NuPogodiAppViewGame);
  531. view_dispatcher_remove_view(app->view_dispatcher, NuPogodiAppViewPause);
  532. view_free(app->view);
  533. widget_free(app->widget);
  534. view_dispatcher_free(app->view_dispatcher);
  535. // Close gui record
  536. furi_record_close(RECORD_GUI);
  537. app->gui = NULL;
  538. furi_record_close(RECORD_NOTIFICATION);
  539. app->notification = NULL;
  540. // Free rest
  541. free(app);
  542. }
  543. int32_t nupogodi_app(void* p) {
  544. UNUSED(p);
  545. NuPogodiApp* app = nupogodi_app_alloc();
  546. view_dispatcher_run(app->view_dispatcher);
  547. nupogodi_app_free(app);
  548. return 0;
  549. }