dice_app.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. #include <furi.h>
  2. #include <input/input.h>
  3. #include <gui/gui.h>
  4. #include "constants.h"
  5. const Icon* draw_dice_frame;
  6. static void update(State* const state) {
  7. if(state->app_state == SwipeLeftState) {
  8. for(uint8_t i = 0; i < DICE_TYPES; i++) {
  9. state->dices[i].x -= SWIPE_DIST;
  10. state->dices[i].y = DICE_Y;
  11. }
  12. if(state->dices[state->dice_index].x == DICE_X) {
  13. state->app_state = SelectState;
  14. state->dices[state->dice_index].y = DICE_Y_T;
  15. }
  16. } else if(state->app_state == SwipeRightState) {
  17. for(uint8_t i = 0; i < DICE_TYPES; i++) {
  18. state->dices[i].x += SWIPE_DIST;
  19. state->dices[i].y = DICE_Y;
  20. }
  21. if(state->dices[state->dice_index].x == DICE_X) {
  22. state->app_state = SelectState;
  23. state->dices[state->dice_index].y = DICE_Y_T;
  24. }
  25. } else if(state->app_state == AnimState) {
  26. state->anim_frame += 1;
  27. if(state->dice_index == 0) {
  28. if(state->anim_frame == 3) coin_set_start(state->roll_result); // change coin anim
  29. if(state->anim_frame >= MAX_COIN_FRAMES) {
  30. state->anim_frame = 0;
  31. state->app_state = AnimResultState;
  32. }
  33. } else {
  34. if(state->anim_frame >= MAX_DICE_FRAMES) {
  35. state->anim_frame = 0;
  36. state->app_state = AnimResultState;
  37. }
  38. }
  39. } else if(state->app_state == AnimResultState) {
  40. if(state->dice_index == 0) { // no extra animations for coin
  41. state->anim_frame = 0;
  42. state->app_state = ResultState;
  43. return;
  44. }
  45. state->result_pos = result_frame_pos_y[state->anim_frame];
  46. state->anim_frame += 1;
  47. // end animation
  48. if(state->result_pos == 0) {
  49. state->anim_frame = 0;
  50. state->app_state = ResultState;
  51. }
  52. }
  53. }
  54. static void roll(State* const state) {
  55. state->roll_result = 0;
  56. state->result_pos = result_frame_pos_y[0];
  57. for(uint8_t i = 0; i < MAX_DICE_COUNT; i++) {
  58. if(i < state->dice_count) {
  59. state->rolled_dices[i] = (rand() % dice_types[state->dice_index].type) + 1;
  60. state->roll_result += state->rolled_dices[i];
  61. } else {
  62. state->rolled_dices[i] = 0;
  63. }
  64. }
  65. if(state->dice_index == 0) coin_set_end(state->roll_result); // change coin anim
  66. add_to_history(state, state->dice_index, state->dice_count, state->roll_result);
  67. state->app_state = AnimState;
  68. }
  69. static void draw_main_menu(const State* state, Canvas* canvas) {
  70. canvas_set_font(canvas, FontSecondary);
  71. FuriString* count = furi_string_alloc();
  72. furi_string_printf(count, "%01d", state->dice_count);
  73. // dice name
  74. if(isDiceNameVisible(state->app_state)) {
  75. canvas_draw_str_aligned(
  76. canvas, 63, 50, AlignCenter, AlignBottom, dice_types[state->dice_index].name);
  77. }
  78. // dice arrow buttons
  79. if(isDiceButtonsVisible(state->app_state)) {
  80. if(state->dice_index > 0) canvas_draw_icon(canvas, 44, 44, &I_ui_button_left);
  81. if(state->dice_index < DICE_TYPES - 1)
  82. canvas_draw_icon(canvas, 78, 44, &I_ui_button_right);
  83. }
  84. // dice count settings
  85. if(isDiceSettingsDisabled(state->app_state, state->dice_index))
  86. canvas_draw_icon(canvas, 48, 51, &I_ui_count_1);
  87. else
  88. canvas_draw_icon(canvas, 48, 51, &I_ui_count);
  89. canvas_draw_str_aligned(canvas, 58, 61, AlignCenter, AlignBottom, furi_string_get_cstr(count));
  90. // buttons
  91. if(isAnimState(state->app_state) == false) {
  92. canvas_draw_icon(canvas, 92, 54, &I_ui_button_roll);
  93. canvas_draw_icon(canvas, 0, 54, &I_ui_button_history);
  94. }
  95. if(state->app_state == AnimResultState || state->app_state == ResultState) {
  96. canvas_draw_icon(canvas, 0, 54, &I_ui_button_back);
  97. }
  98. furi_string_free(count);
  99. }
  100. static void draw_history(const State* state, Canvas* canvas) {
  101. canvas_set_font(canvas, FontSecondary);
  102. FuriString* hist = furi_string_alloc();
  103. uint8_t x = HISTORY_START_POST_X;
  104. uint8_t y = HISTORY_START_POST_Y;
  105. for(uint8_t i = 0; i < HISTORY_COL; i++) {
  106. // left side
  107. furi_string_printf(hist, "%01d.", i + 1);
  108. canvas_draw_str_aligned(canvas, x, y, AlignLeft, AlignBottom, furi_string_get_cstr(hist));
  109. if (state->history[i].index < 0) {
  110. furi_string_printf(hist, "--------");
  111. } else {
  112. if (state->history[i].index == 0){
  113. furi_string_printf(hist, state->history[i].result == 1 ? "Heads" : "Tails");
  114. } else {
  115. furi_string_printf(hist, "%01d%s: %01d", state->history[i].count, dice_types[state->history[i].index].name, state->history[i].result);
  116. }
  117. }
  118. canvas_draw_str_aligned(canvas, x + HISTORY_X_GAP, y, AlignLeft, AlignBottom, furi_string_get_cstr(hist));
  119. // right side
  120. uint8_t r_index = i + HISTORY_COL;
  121. furi_string_printf(hist, "%01d.", r_index + 1);
  122. canvas_draw_str_aligned(canvas, x + HISTORY_STEP_X, y, AlignLeft, AlignBottom, furi_string_get_cstr(hist));
  123. if (state->history[r_index].index < 0){
  124. furi_string_printf(hist, "--------");
  125. } else {
  126. if (state->history[r_index].index == 0){
  127. furi_string_printf(hist, state->history[r_index].result == 1 ? "Heads" : "Tails");
  128. } else {
  129. furi_string_printf(hist, "%01d%s: %01d", state->history[r_index].count, dice_types[state->history[r_index].index].name, state->history[r_index].result);
  130. }
  131. }
  132. canvas_draw_str_aligned(canvas, x + HISTORY_STEP_X + HISTORY_X_GAP, y, AlignLeft, AlignBottom, furi_string_get_cstr(hist));
  133. y += HISTORY_STEP_Y;
  134. }
  135. canvas_draw_icon(canvas, 0, 54, &I_ui_button_back);
  136. canvas_draw_icon(canvas, 75, 54, &I_ui_button_exit);
  137. furi_string_free(hist);
  138. }
  139. static void draw_dice(const State* state, Canvas* canvas) {
  140. if(isMenuState(state->app_state) == false) { // draw only selected dice
  141. if(state->dice_index == 0) { // coin
  142. draw_dice_frame = coin_frames[state->anim_frame];
  143. } else { // dices
  144. draw_dice_frame =
  145. dice_frames[(state->dice_index - 1) * MAX_DICE_FRAMES + state->anim_frame];
  146. }
  147. canvas_draw_icon(
  148. canvas,
  149. state->dices[state->dice_index].x,
  150. state->dices[state->dice_index].y,
  151. draw_dice_frame);
  152. return;
  153. }
  154. for(uint8_t i = 0; i < DICE_TYPES; i++) {
  155. if(state->app_state == ResultState && state->dice_index == i && state->dice_index != 0)
  156. continue; // draw results except coin
  157. if(state->dices[i].x > 128 || state->dices[i].x < -35) continue; // outside the screen
  158. if(i == 0) { // coin
  159. draw_dice_frame = coin_frames[0];
  160. } else { // dices
  161. draw_dice_frame = dice_frames[(i - 1) * MAX_DICE_FRAMES];
  162. }
  163. canvas_draw_icon(canvas, state->dices[i].x, state->dices[i].y, draw_dice_frame);
  164. }
  165. }
  166. static void draw_results(const State* state, Canvas* canvas) {
  167. canvas_set_font(canvas, FontPrimary);
  168. FuriString* sum = furi_string_alloc();
  169. furi_string_printf(sum, "%01d", state->roll_result);
  170. // ui frame
  171. if(state->app_state == AnimResultState)
  172. canvas_draw_icon(canvas, RESULT_BORDER_X, state->result_pos, &I_ui_result_border);
  173. else
  174. canvas_draw_icon(
  175. canvas, RESULT_BORDER_X, result_frame_pos_y[MAX_DICE_FRAMES - 1], &I_ui_result_border);
  176. // result text
  177. canvas_draw_str_aligned(
  178. canvas,
  179. 64,
  180. state->result_pos + RESULT_OFFSET,
  181. AlignCenter,
  182. AlignCenter,
  183. furi_string_get_cstr(sum));
  184. if(state->app_state == ResultState && isOneDice(state->dice_index) == false) {
  185. canvas_set_font(canvas, FontSecondary);
  186. FuriString* dices = furi_string_alloc();
  187. for(uint8_t i = 0; i < state->dice_count; i++) {
  188. furi_string_cat_printf(dices, "%01d", state->rolled_dices[i]);
  189. if(i != state->dice_count - 1) furi_string_cat_printf(dices, "%s", ", ");
  190. }
  191. canvas_draw_str_aligned(
  192. canvas, 63, 37, AlignCenter, AlignCenter, furi_string_get_cstr(dices));
  193. furi_string_free(dices);
  194. }
  195. furi_string_free(sum);
  196. }
  197. static void draw_callback(Canvas* canvas, void* ctx) {
  198. furi_assert(ctx);
  199. const State* state = ctx;
  200. furi_mutex_acquire(state->mutex, FuriWaitForever);
  201. if(state == NULL) {
  202. return;
  203. }
  204. canvas_clear(canvas);
  205. if (state->app_state == HistoryState) {
  206. draw_history(state, canvas);
  207. } else {
  208. draw_main_menu(state, canvas);
  209. if(isResultVisible(state->app_state, state->dice_index)) {
  210. draw_results(state, canvas);
  211. } else {
  212. draw_dice(state, canvas);
  213. }
  214. }
  215. furi_mutex_release(state->mutex);
  216. }
  217. static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  218. furi_assert(event_queue);
  219. AppEvent event = {.type = EventTypeKey, .input = *input_event};
  220. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  221. }
  222. static void timer_callback(FuriMessageQueue* event_queue) {
  223. furi_assert(event_queue);
  224. AppEvent event = {.type = EventTypeTick};
  225. furi_message_queue_put(event_queue, &event, 0);
  226. }
  227. int32_t dice_dnd_app(void* p) {
  228. UNUSED(p);
  229. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
  230. State* state = malloc(sizeof(State));
  231. init(state);
  232. state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  233. if(!state->mutex) {
  234. FURI_LOG_E(TAG, "cannot create mutex\r\n");
  235. free(state);
  236. return 255;
  237. }
  238. // Set callbacks
  239. ViewPort* view_port = view_port_alloc();
  240. view_port_draw_callback_set(view_port, draw_callback, state);
  241. view_port_input_callback_set(view_port, input_callback, event_queue);
  242. FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
  243. furi_timer_start(timer, furi_kernel_get_tick_frequency() * 0.2);
  244. // Create GUI, register view port
  245. Gui* gui = furi_record_open(RECORD_GUI);
  246. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  247. AppEvent event;
  248. for(bool processing = true; processing;) {
  249. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  250. furi_mutex_acquire(state->mutex, FuriWaitForever);
  251. if(event_status == FuriStatusOk) {
  252. // timer evetn
  253. if(event.type == EventTypeTick) {
  254. update(state);
  255. }
  256. // button events
  257. if(event.type == EventTypeKey) {
  258. if(event.input.type == InputTypePress) {
  259. // dice type
  260. if(isDiceButtonsVisible(state->app_state)) {
  261. if(event.input.key == InputKeyRight) {
  262. if(state->dice_index < DICE_TYPES - 1) {
  263. state->dice_index += 1;
  264. state->app_state = SwipeLeftState;
  265. }
  266. } else if(event.input.key == InputKeyLeft) {
  267. if(state->dice_index > 0) {
  268. state->dice_index -= 1;
  269. state->app_state = SwipeRightState;
  270. }
  271. }
  272. if(isOneDice(state->dice_index)) state->dice_count = 1;
  273. }
  274. // dice count
  275. if(isDiceSettingsDisabled(state->app_state, state->dice_index) == false &&
  276. isAnimState(state->app_state) == false) {
  277. if(event.input.key == InputKeyUp) {
  278. if(state->dice_index != 0) {
  279. state->dice_count += 1;
  280. if(state->dice_count > MAX_DICE_COUNT) {
  281. state->dice_count = 1;
  282. }
  283. }
  284. } else if(event.input.key == InputKeyDown) {
  285. state->dice_count -= 1;
  286. if(state->dice_count < 1) {
  287. state->dice_count = MAX_DICE_COUNT;
  288. }
  289. }
  290. }
  291. // roll
  292. if(event.input.key == InputKeyOk && isAnimState(state->app_state) == false) {
  293. roll(state);
  294. }
  295. }
  296. // back button handlers
  297. if(event.input.key == InputKeyBack){
  298. // switch states
  299. if(event.input.type == InputTypeShort) {
  300. if(state->app_state == SelectState){
  301. state->app_state = HistoryState;
  302. }
  303. else if(state->app_state == HistoryState) {
  304. state->app_state = SelectState;
  305. }
  306. else if(state->app_state == ResultState || state->app_state == AnimResultState) {
  307. state->anim_frame = 0;
  308. state->app_state = SelectState;
  309. }
  310. }
  311. // exit
  312. else if(event.input.type == InputTypeLong) {
  313. processing = false;
  314. }
  315. }
  316. }
  317. }
  318. view_port_update(view_port);
  319. furi_mutex_release(state->mutex);
  320. }
  321. // Clear
  322. furi_timer_free(timer);
  323. furi_message_queue_free(event_queue);
  324. view_port_enabled_set(view_port, false);
  325. gui_remove_view_port(gui, view_port);
  326. furi_record_close(RECORD_GUI);
  327. view_port_free(view_port);
  328. furi_mutex_free(state->mutex);
  329. free(state);
  330. return 0;
  331. }