dice_app.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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, FuriMessageQueue* event_queue) {
  226. furi_assert(event_queue);
  227. AppEvent event = {.type = EventTypeKey, .input = *input_event};
  228. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  229. }
  230. static void timer_callback(FuriMessageQueue* event_queue) {
  231. furi_assert(event_queue);
  232. AppEvent event = {.type = EventTypeTick};
  233. furi_message_queue_put(event_queue, &event, 0);
  234. }
  235. int32_t dice_dnd_app(void* p) {
  236. UNUSED(p);
  237. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
  238. State* state = malloc(sizeof(State));
  239. init(state);
  240. state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  241. if(!state->mutex) {
  242. FURI_LOG_E(TAG, "cannot create mutex\r\n");
  243. free(state);
  244. return 255;
  245. }
  246. // Set callbacks
  247. ViewPort* view_port = view_port_alloc();
  248. view_port_draw_callback_set(view_port, draw_callback, state);
  249. view_port_input_callback_set(view_port, input_callback, event_queue);
  250. FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
  251. furi_timer_start(timer, furi_kernel_get_tick_frequency() * 0.2);
  252. // Create GUI, register view port
  253. Gui* gui = furi_record_open(RECORD_GUI);
  254. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  255. AppEvent event;
  256. for(bool processing = true; processing;) {
  257. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  258. furi_mutex_acquire(state->mutex, FuriWaitForever);
  259. if(event_status == FuriStatusOk) {
  260. // timer evetn
  261. if(event.type == EventTypeTick) {
  262. update(state);
  263. }
  264. // button events
  265. if(event.type == EventTypeKey) {
  266. if(event.input.type == InputTypePress) {
  267. // dice type
  268. if(isDiceButtonsVisible(state->app_state)) {
  269. if(event.input.key == InputKeyRight) {
  270. if(state->dice_index < DICE_TYPES - 1) {
  271. state->dice_index += 1;
  272. state->app_state = SwipeLeftState;
  273. }
  274. } else if(event.input.key == InputKeyLeft) {
  275. if(state->dice_index > 0) {
  276. state->dice_index -= 1;
  277. state->app_state = SwipeRightState;
  278. }
  279. }
  280. if(isOneDice(state->dice_index)) state->dice_count = 1;
  281. }
  282. // dice count
  283. if(isDiceSettingsDisabled(state->app_state, state->dice_index) == false &&
  284. isAnimState(state->app_state) == false) {
  285. if(event.input.key == InputKeyUp) {
  286. if(state->dice_index != 0) {
  287. state->dice_count += 1;
  288. if(state->dice_count > MAX_DICE_COUNT) {
  289. state->dice_count = 1;
  290. }
  291. }
  292. } else if(event.input.key == InputKeyDown) {
  293. state->dice_count -= 1;
  294. if(state->dice_count < 1) {
  295. state->dice_count = MAX_DICE_COUNT;
  296. }
  297. }
  298. }
  299. // roll
  300. if(event.input.key == InputKeyOk && isAnimState(state->app_state) == false) {
  301. roll(state);
  302. }
  303. }
  304. // back button handlers
  305. if(event.input.key == InputKeyBack){
  306. // switch states
  307. if(event.input.type == InputTypeShort) {
  308. if(state->app_state == SelectState){
  309. state->app_state = HistoryState;
  310. }
  311. else if(state->app_state == HistoryState) {
  312. state->app_state = SelectState;
  313. }
  314. else if(state->app_state == ResultState || state->app_state == AnimResultState) {
  315. state->anim_frame = 0;
  316. state->app_state = SelectState;
  317. }
  318. }
  319. // exit
  320. else if(event.input.type == InputTypeLong) {
  321. processing = false;
  322. }
  323. }
  324. }
  325. }
  326. view_port_update(view_port);
  327. furi_mutex_release(state->mutex);
  328. }
  329. // Clear
  330. furi_timer_free(timer);
  331. furi_message_queue_free(event_queue);
  332. view_port_enabled_set(view_port, false);
  333. gui_remove_view_port(gui, view_port);
  334. furi_record_close(RECORD_GUI);
  335. view_port_free(view_port);
  336. furi_mutex_free(state->mutex);
  337. free(state);
  338. return 0;
  339. }