nupogodi.c 17 KB

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