food.c 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #include <furi.h>
  2. #include <gui/gui.h>
  3. #include "dolphin/dolphin_state.h"
  4. #define MAX_TRIES 3
  5. #define DISHES_TOTAL 3
  6. #define LID_POS_MAX 20
  7. #define TRY_TIMEOUT 10
  8. typedef enum {
  9. EventTypeTick,
  10. EventTypeKey,
  11. EventTypeDeed,
  12. } EventType;
  13. typedef struct {
  14. union {
  15. InputEvent input;
  16. } value;
  17. EventType type;
  18. } AppEvent;
  19. typedef enum {
  20. PlayerChoiceEvent,
  21. OpenLootEvent,
  22. WinEvent,
  23. LooseEvent,
  24. FinishedEvent,
  25. ExitGameEvent,
  26. GameEventTotal,
  27. } GameEventType;
  28. typedef enum {
  29. LootSkeleton,
  30. LootFish,
  31. LootShit,
  32. LootTotalNum,
  33. } LootIdEnum;
  34. typedef struct {
  35. GameEventType current_event;
  36. osMessageQueueId_t event_queue;
  37. LootIdEnum loot_list[DISHES_TOTAL];
  38. uint8_t cursor_pos;
  39. uint8_t lid_pos;
  40. uint8_t timeout;
  41. uint8_t try;
  42. bool selected;
  43. bool deed;
  44. } GameState;
  45. typedef struct {
  46. const Icon* f;
  47. const Icon* b;
  48. } LootGfx;
  49. static const Icon* letters[DISHES_TOTAL] = {&I_letterA_10x10, &I_letterB_10x10, &I_letterC_10x10};
  50. static const LootGfx loot[LootTotalNum] = {
  51. [LootSkeleton] =
  52. {
  53. .f = &I_skeleton_25x17,
  54. .b = &I_blackskeleton_25x17,
  55. },
  56. [LootFish] =
  57. {
  58. .f = &I_fish_25x17,
  59. .b = &I_blackfish_25x17,
  60. },
  61. [LootShit] =
  62. {
  63. .f = &I_shit_25x17,
  64. .b = &I_blackshit_25x17,
  65. },
  66. };
  67. static void input_callback(InputEvent* input_event, void* ctx) {
  68. osMessageQueueId_t event_queue = ctx;
  69. AppEvent event;
  70. event.type = EventTypeKey;
  71. event.value.input = *input_event;
  72. osMessageQueuePut(event_queue, &event, 0, osWaitForever);
  73. }
  74. static void draw_dish(Canvas* canvas, GameState* state, uint8_t x, uint8_t y, uint8_t id) {
  75. bool active = state->cursor_pos == id;
  76. bool opened = state->current_event == OpenLootEvent && active;
  77. canvas_set_bitmap_mode(canvas, true);
  78. canvas_set_color(canvas, ColorBlack);
  79. if(active) {
  80. canvas_draw_icon(canvas, x, y, &I_active_plate_48x18);
  81. }
  82. if(opened) {
  83. state->lid_pos = CLAMP(state->lid_pos + 1, LID_POS_MAX, 0);
  84. }
  85. uint8_t lid_pos = (y - 17) - (opened ? state->lid_pos : 0);
  86. canvas_draw_icon(canvas, x + 3, y - 6, &I_plate_42x19);
  87. canvas_set_color(canvas, ColorWhite);
  88. canvas_draw_icon(canvas, x + 11, y - 10, loot[state->loot_list[id]].b);
  89. canvas_set_color(canvas, ColorBlack);
  90. canvas_draw_icon(canvas, x + 11, y - 10, loot[state->loot_list[id]].f);
  91. canvas_set_color(canvas, ColorWhite);
  92. canvas_draw_icon(canvas, x + 6, lid_pos, &I_blacklid_36x27);
  93. canvas_set_color(canvas, ColorBlack);
  94. canvas_draw_icon(canvas, x + 6, lid_pos, &I_lid_36x27);
  95. canvas_set_bitmap_mode(canvas, false);
  96. canvas_draw_icon(canvas, x + 19, y + 8, letters[id]);
  97. }
  98. static void draw_dishes_scene(Canvas* canvas, GameState* state) {
  99. uint8_t tries_left = MAX_TRIES - state->try;
  100. for(size_t i = 0; i < MAX_TRIES; i++) {
  101. if(i < tries_left) {
  102. canvas_draw_disc(canvas, 5 + i * 8, 5, 2);
  103. } else {
  104. canvas_draw_circle(canvas, 5 + i * 8, 5, 2);
  105. }
  106. }
  107. for(size_t i = 0; i < DISHES_TOTAL; i++) {
  108. draw_dish(canvas, state, i * 40, i % 2 ? 26 : 44, i);
  109. }
  110. }
  111. static void render_callback(Canvas* canvas, void* ctx) {
  112. GameState* state = (GameState*)acquire_mutex((ValueMutex*)ctx, 25);
  113. canvas_clear(canvas);
  114. switch(state->current_event) {
  115. case WinEvent:
  116. canvas_draw_str(canvas, 30, 30, "Dolphin_happy.png");
  117. break;
  118. case LooseEvent:
  119. canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignCenter, "Try again!");
  120. break;
  121. case ExitGameEvent:
  122. break;
  123. case FinishedEvent:
  124. break;
  125. default:
  126. draw_dishes_scene(canvas, state);
  127. break;
  128. }
  129. release_mutex((ValueMutex*)ctx, state);
  130. }
  131. static void reset_lid_pos(GameState* state) {
  132. state->selected = false;
  133. state->lid_pos = 0;
  134. }
  135. void dolphin_food_deed(GameState* state) {
  136. furi_assert(state);
  137. AppEvent event;
  138. event.type = EventTypeDeed;
  139. furi_check(osMessageQueuePut(state->event_queue, &event, 0, osWaitForever) == osOK);
  140. }
  141. static void reset_loot_array(GameState* state) {
  142. for(size_t i = 0; i < LootTotalNum; i++) {
  143. state->loot_list[i] = i;
  144. }
  145. for(size_t i = 0; i < LootTotalNum; i++) {
  146. int temp = state->loot_list[i];
  147. int r_idx = rand() % LootTotalNum;
  148. state->loot_list[i] = state->loot_list[r_idx];
  149. state->loot_list[r_idx] = temp;
  150. }
  151. }
  152. static bool selected_is_food(GameState* state) {
  153. return state->loot_list[state->cursor_pos] == LootFish;
  154. }
  155. static bool tries_exceed(GameState* state) {
  156. return state->try == MAX_TRIES;
  157. }
  158. static bool timeout_exceed(GameState* state) {
  159. return state->timeout == TRY_TIMEOUT;
  160. }
  161. static void gamestate_update(GameState* state, DolphinState* dolphin_state) {
  162. switch(state->current_event) {
  163. case PlayerChoiceEvent:
  164. if(state->selected) {
  165. state->current_event = OpenLootEvent;
  166. }
  167. break;
  168. case OpenLootEvent:
  169. state->timeout = CLAMP(state->timeout + 1, TRY_TIMEOUT, 0);
  170. if(timeout_exceed(state)) {
  171. state->timeout = 0;
  172. state->current_event = selected_is_food(state) ? WinEvent : LooseEvent;
  173. state->deed = selected_is_food(state);
  174. }
  175. break;
  176. case LooseEvent:
  177. state->timeout = CLAMP(state->timeout + 1, TRY_TIMEOUT, 0);
  178. if(timeout_exceed(state)) {
  179. state->timeout = 0;
  180. state->current_event = FinishedEvent;
  181. }
  182. break;
  183. case WinEvent:
  184. if(state->deed) {
  185. dolphin_food_deed(state);
  186. }
  187. break;
  188. case FinishedEvent:
  189. reset_lid_pos(state);
  190. reset_loot_array(state);
  191. state->try++;
  192. state->current_event = tries_exceed(state) ? ExitGameEvent : PlayerChoiceEvent;
  193. break;
  194. default:
  195. break;
  196. }
  197. }
  198. static void food_minigame_controls(GameState* state, AppEvent* event) {
  199. furi_assert(state);
  200. furi_assert(event);
  201. if(event->value.input.key == InputKeyRight) {
  202. if(state->current_event == PlayerChoiceEvent) {
  203. state->cursor_pos = CLAMP(state->cursor_pos + 1, DISHES_TOTAL - 1, 0);
  204. }
  205. } else if(event->value.input.key == InputKeyLeft) {
  206. if(state->current_event == PlayerChoiceEvent) {
  207. state->cursor_pos = CLAMP(state->cursor_pos - 1, DISHES_TOTAL - 1, 0);
  208. }
  209. } else if(event->value.input.key == InputKeyOk) {
  210. switch(state->current_event) {
  211. case PlayerChoiceEvent:
  212. state->selected = true;
  213. break;
  214. case WinEvent:
  215. state->current_event = FinishedEvent;
  216. break;
  217. default:
  218. break;
  219. }
  220. }
  221. }
  222. int32_t food_minigame_app(void* p) {
  223. GameState* state = furi_alloc(sizeof(GameState));
  224. DolphinState* dolphin_state = dolphin_state_alloc();
  225. dolphin_state_load(dolphin_state);
  226. ValueMutex state_mutex;
  227. state->event_queue = osMessageQueueNew(2, sizeof(AppEvent), NULL);
  228. furi_check(state->event_queue);
  229. if(!init_mutex(&state_mutex, state, sizeof(GameState*))) {
  230. printf("[Food minigame] cannot create mutex\r\n");
  231. return 0;
  232. }
  233. ViewPort* view_port = view_port_alloc();
  234. view_port_draw_callback_set(view_port, render_callback, &state_mutex);
  235. view_port_input_callback_set(view_port, input_callback, state->event_queue);
  236. Gui* gui = furi_record_open("gui");
  237. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  238. reset_loot_array(state);
  239. AppEvent event;
  240. while(1) {
  241. osStatus_t event_status = osMessageQueueGet(state->event_queue, &event, NULL, 100);
  242. if(event_status == osOK) {
  243. if(event.type == EventTypeKey && event.value.input.type == InputTypeShort) {
  244. food_minigame_controls(state, &event);
  245. if(event.value.input.key == InputKeyBack) {
  246. break;
  247. }
  248. } else if(event.type == EventTypeDeed) {
  249. dolphin_state_on_deed(dolphin_state, DolphinDeedIButtonRead);
  250. dolphin_state_save(dolphin_state);
  251. state->deed = false;
  252. }
  253. }
  254. if(state->current_event == ExitGameEvent) {
  255. break;
  256. }
  257. gamestate_update(state, dolphin_state);
  258. view_port_update(view_port);
  259. }
  260. gui_remove_view_port(gui, view_port);
  261. view_port_free(view_port);
  262. furi_record_close("gui");
  263. delete_mutex(&state_mutex);
  264. osMessageQueueDelete(state->event_queue);
  265. dolphin_state_free(dolphin_state);
  266. free(state);
  267. return 0;
  268. }