nupogodi.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  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. }, consumed);
  262. return consumed;
  263. }
  264. static uint32_t nupogodi_exit(void* context) {
  265. UNUSED(context);
  266. return VIEW_NONE;
  267. //return VIEW_IGNORE;
  268. }
  269. static int32_t nupogodi_worker(void* context) {
  270. furi_assert(context);
  271. NuPogodiApp* app = context;
  272. UNUSED(app);
  273. while(1) {
  274. uint32_t events =
  275. furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever);
  276. furi_check((events & FuriFlagError) == 0);
  277. FURI_LOG_D(TAG, "Worker %lu", events);
  278. if(events & WorkerEventStop) break;
  279. if(events & WorkerEventTick) {
  280. with_view_model(
  281. app->view,
  282. NuPogodiModel * model,
  283. {
  284. uint8_t sound = model->sound ? 0 : 1;
  285. switch(model->mode) {
  286. case Logo:
  287. if(model->tick > 0) {
  288. model->tick--;
  289. } else {
  290. model->mode = Play;
  291. }
  292. break;
  293. case Ready:
  294. model->eggs[0] = 0;
  295. model->eggs[1] = 0;
  296. model->eggs[2] = 0;
  297. model->eggs[3] = 0;
  298. model->scores = 0;
  299. model->missed = 0;
  300. model->mode = Play;
  301. break;
  302. case Play:
  303. if((model->eggs[0] == 0) && (model->eggs[1] == 0) &&
  304. (model->eggs[2] == 0) && (model->eggs[3] == 0)) {
  305. // Если ни одного яйца нет - создаем
  306. uint32_t rnd = furi_hal_random_get() % 4;
  307. model->eggs[rnd] = 1;
  308. notification_message(app->notification, notification_eggs[rnd][sound]);
  309. } else {
  310. // Прокатываем все яйца на одно деление вперед
  311. for(uint8_t i = 0; i < 4; i++) {
  312. if(model->eggs[i] > 0) {
  313. model->eggs[i]++;
  314. if(model->eggs[i] < 6) {
  315. notification_message(
  316. app->notification, notification_eggs[i][sound]);
  317. }
  318. }
  319. }
  320. if((model->eggs[0] == 6) && (model->top) && (!model->left)) {
  321. model->eggs[0] = 0;
  322. model->scores++;
  323. notification_message(app->notification, notification_done[sound]);
  324. } else if((model->eggs[1] == 6) && (!model->top) && (!model->left)) {
  325. model->eggs[1] = 0;
  326. model->scores++;
  327. notification_message(app->notification, notification_done[sound]);
  328. } else if((model->eggs[2] == 6) && (model->top) && (model->left)) {
  329. model->eggs[2] = 0;
  330. model->scores++;
  331. notification_message(app->notification, notification_done[sound]);
  332. } else if((model->eggs[3] == 6) && (!model->top) && (model->left)) {
  333. model->eggs[3] = 0;
  334. model->scores++;
  335. notification_message(app->notification, notification_done[sound]);
  336. } else {
  337. // Если яйцо было не поймано - зануляем все, запускаем анимацию разбития
  338. for(uint8_t i = 0; i < 4; i++) {
  339. if(model->eggs[i] == 6) {
  340. model->eggs[0] = 0;
  341. model->eggs[1] = 0;
  342. model->eggs[2] = 0;
  343. model->eggs[3] = 0;
  344. model->left = i >= 2;
  345. model->missed++;
  346. if(model->missed < 4) {
  347. model->mode = Fail;
  348. model->tick = 3;
  349. } else {
  350. model->mode = Over;
  351. }
  352. notification_message(
  353. app->notification, notification_fail[sound]);
  354. }
  355. }
  356. }
  357. }
  358. break;
  359. case Pause:
  360. // nop
  361. break;
  362. case Fail:
  363. if(model->tick > 0) {
  364. model->tick--;
  365. } else {
  366. model->mode = Play;
  367. }
  368. break;
  369. case Over:
  370. break;
  371. }
  372. },
  373. true);
  374. }
  375. }
  376. return 0;
  377. }
  378. static void nupogodi_timer_callback(void* context) {
  379. furi_assert(context);
  380. NuPogodiApp* app = context;
  381. furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventTick);
  382. }
  383. static NuPogodiApp* nupogodi_app_alloc() {
  384. NuPogodiApp* app = malloc(sizeof(NuPogodiApp));
  385. // Gui
  386. app->gui = furi_record_open(RECORD_GUI);
  387. app->notification = furi_record_open(RECORD_NOTIFICATION);
  388. // View dispatcher
  389. app->view_dispatcher = view_dispatcher_alloc();
  390. view_dispatcher_enable_queue(app->view_dispatcher);
  391. view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
  392. // Views
  393. app->view = view_alloc();
  394. view_set_context(app->view, app);
  395. view_set_draw_callback(app->view, nupogodi_view_draw_callback);
  396. view_set_input_callback(app->view, nupogodi_view_input_callback);
  397. view_allocate_model(app->view, ViewModelTypeLocking, sizeof(NuPogodiModel));
  398. with_view_model(
  399. app->view,
  400. NuPogodiModel * model,
  401. {
  402. model->mode = Logo;
  403. model->tick = 1;
  404. model->top = false;
  405. model->left = true;
  406. model->missed = 0;
  407. model->scores = 0;
  408. model->eggs[0] = 0;
  409. model->eggs[1] = 0;
  410. model->eggs[2] = 0;
  411. model->eggs[3] = 0;
  412. },
  413. true);
  414. view_set_previous_callback(app->view, nupogodi_exit);
  415. view_dispatcher_add_view(app->view_dispatcher, 0, app->view);
  416. view_dispatcher_switch_to_view(app->view_dispatcher, 0);
  417. app->worker_thread = furi_thread_alloc_ex("NuPogodiWorker", 1024, nupogodi_worker, app);
  418. furi_thread_start(app->worker_thread);
  419. app->timer = furi_timer_alloc(nupogodi_timer_callback, FuriTimerTypePeriodic, app);
  420. furi_timer_start(app->timer, furi_ms_to_ticks(1000));
  421. return app;
  422. }
  423. static void nupogodi_app_free(NuPogodiApp* app) {
  424. furi_assert(app);
  425. furi_timer_stop(app->timer);
  426. furi_timer_free(app->timer);
  427. furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop);
  428. furi_thread_join(app->worker_thread);
  429. furi_thread_free(app->worker_thread);
  430. // Free views
  431. view_dispatcher_remove_view(app->view_dispatcher, 0);
  432. view_free(app->view);
  433. view_dispatcher_free(app->view_dispatcher);
  434. // Close gui record
  435. furi_record_close(RECORD_GUI);
  436. app->gui = NULL;
  437. furi_record_close(RECORD_NOTIFICATION);
  438. app->notification = NULL;
  439. // Free rest
  440. free(app);
  441. }
  442. int32_t nupogodi_app(void* p) {
  443. UNUSED(p);
  444. NuPogodiApp* app = nupogodi_app_alloc();
  445. view_dispatcher_run(app->view_dispatcher);
  446. nupogodi_app_free(app);
  447. return 0;
  448. }