dice_app.c 14 KB

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