blackjack.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. #include <gui/gui.h>
  2. #include <stdlib.h>
  3. #include <dolphin/dolphin.h>
  4. #include <dialogs/dialogs.h>
  5. #include <gui/canvas_i.h>
  6. #include <math.h>
  7. #include "util.h"
  8. #include "defines.h"
  9. #include "common/card.h"
  10. #include "common/dml.h"
  11. #include "common/queue.h"
  12. #include "util.h"
  13. #include "ui.h"
  14. #include "blackjack_icons.h"
  15. #define DEALER_MAX 17
  16. void start_round(GameState* game_state);
  17. void init(GameState* game_state);
  18. static void draw_ui(Canvas* const canvas, const GameState* game_state) {
  19. draw_money(canvas, game_state->player_score);
  20. draw_score(canvas, true, hand_count(game_state->player_cards, game_state->player_card_count));
  21. if(!game_state->queue_state.running && game_state->state == GameStatePlay) {
  22. render_menu(game_state->menu, canvas, 2, 47);
  23. }
  24. }
  25. static void render_callback(Canvas* const canvas, void* ctx) {
  26. furi_assert(ctx);
  27. const GameState* game_state = ctx;
  28. furi_mutex_acquire(game_state->mutex, FuriWaitForever);
  29. canvas_set_color(canvas, ColorBlack);
  30. canvas_draw_frame(canvas, 0, 0, 128, 64);
  31. if(game_state->state == GameStateStart) {
  32. canvas_draw_icon(canvas, 0, 0, &I_blackjack);
  33. }
  34. if(game_state->state == GameStateGameOver) {
  35. canvas_draw_icon(canvas, 0, 0, &I_endscreen);
  36. }
  37. if(game_state->state == GameStatePlay || game_state->state == GameStateDealer) {
  38. if(game_state->state == GameStatePlay)
  39. draw_player_scene(canvas, game_state);
  40. else
  41. draw_dealer_scene(canvas, game_state);
  42. render_queue(&(game_state->queue_state), game_state, canvas);
  43. draw_ui(canvas, game_state);
  44. } else if(game_state->state == GameStateSettings) {
  45. settings_page(canvas, game_state);
  46. }
  47. furi_mutex_release(game_state->mutex);
  48. }
  49. //region card draw
  50. Card draw_card(GameState* game_state) {
  51. Card c = game_state->deck.cards[game_state->deck.index];
  52. game_state->deck.index++;
  53. return c;
  54. }
  55. void drawPlayerCard(void* ctx) {
  56. GameState* game_state = ctx;
  57. Card c = draw_card(game_state);
  58. game_state->player_cards[game_state->player_card_count] = c;
  59. game_state->player_card_count++;
  60. if(game_state->player_score < game_state->settings.round_price || game_state->doubled) {
  61. set_menu_state(game_state->menu, 0, false);
  62. }
  63. }
  64. void drawDealerCard(void* ctx) {
  65. GameState* game_state = ctx;
  66. Card c = draw_card(game_state);
  67. game_state->dealer_cards[game_state->dealer_card_count] = c;
  68. game_state->dealer_card_count++;
  69. }
  70. //endregion
  71. //region queue callbacks
  72. void to_lose_state(const void* ctx, Canvas* const canvas) {
  73. const GameState* game_state = ctx;
  74. if(game_state->settings.message_duration == 0) return;
  75. popup_frame(canvas);
  76. elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost");
  77. }
  78. void to_bust_state(const void* ctx, Canvas* const canvas) {
  79. const GameState* game_state = ctx;
  80. if(game_state->settings.message_duration == 0) return;
  81. popup_frame(canvas);
  82. elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!");
  83. }
  84. void to_draw_state(const void* ctx, Canvas* const canvas) {
  85. const GameState* game_state = ctx;
  86. if(game_state->settings.message_duration == 0) return;
  87. popup_frame(canvas);
  88. elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw");
  89. }
  90. void to_dealer_turn(const void* ctx, Canvas* const canvas) {
  91. const GameState* game_state = ctx;
  92. if(game_state->settings.message_duration == 0) return;
  93. popup_frame(canvas);
  94. elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn");
  95. }
  96. void to_win_state(const void* ctx, Canvas* const canvas) {
  97. const GameState* game_state = ctx;
  98. if(game_state->settings.message_duration == 0) return;
  99. popup_frame(canvas);
  100. elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win");
  101. }
  102. void to_start(const void* ctx, Canvas* const canvas) {
  103. const GameState* game_state = ctx;
  104. if(game_state->settings.message_duration == 0) return;
  105. popup_frame(canvas);
  106. elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started");
  107. }
  108. void before_start(void* ctx) {
  109. GameState* game_state = ctx;
  110. game_state->dealer_card_count = 0;
  111. game_state->player_card_count = 0;
  112. }
  113. void start(void* ctx) {
  114. GameState* game_state = ctx;
  115. start_round(game_state);
  116. }
  117. void draw(void* ctx) {
  118. GameState* game_state = ctx;
  119. game_state->player_score += game_state->bet;
  120. game_state->bet = 0;
  121. enqueue(
  122. &(game_state->queue_state),
  123. game_state,
  124. start,
  125. before_start,
  126. to_start,
  127. game_state->settings.message_duration);
  128. }
  129. void game_over(void* ctx) {
  130. GameState* game_state = ctx;
  131. game_state->state = GameStateGameOver;
  132. }
  133. void lose(void* ctx) {
  134. GameState* game_state = ctx;
  135. game_state->state = GameStatePlay;
  136. game_state->bet = 0;
  137. if(game_state->player_score >= game_state->settings.round_price) {
  138. enqueue(
  139. &(game_state->queue_state),
  140. game_state,
  141. start,
  142. before_start,
  143. to_start,
  144. game_state->settings.message_duration);
  145. } else {
  146. enqueue(&(game_state->queue_state), game_state, game_over, NULL, NULL, 0);
  147. }
  148. }
  149. void win(void* ctx) {
  150. GameState* game_state = ctx;
  151. game_state->state = GameStatePlay;
  152. game_state->player_score += game_state->bet * 2;
  153. game_state->bet = 0;
  154. enqueue(
  155. &(game_state->queue_state),
  156. game_state,
  157. start,
  158. before_start,
  159. to_start,
  160. game_state->settings.message_duration);
  161. }
  162. void dealerTurn(void* ctx) {
  163. GameState* game_state = ctx;
  164. game_state->state = GameStateDealer;
  165. }
  166. float animationTime(const GameState* game_state) {
  167. return (float)(furi_get_tick() - game_state->queue_state.start) /
  168. (float)(game_state->settings.animation_duration);
  169. }
  170. void dealer_card_animation(const void* ctx, Canvas* const canvas) {
  171. const GameState* game_state = ctx;
  172. float t = animationTime(game_state);
  173. Card animatingCard = game_state->deck.cards[game_state->deck.index];
  174. if(game_state->dealer_card_count > 1) {
  175. Vector end = card_pos_at_index(game_state->dealer_card_count);
  176. draw_card_animation(animatingCard, (Vector){0, 64}, (Vector){0, 32}, end, t, true, canvas);
  177. } else {
  178. draw_card_animation(
  179. animatingCard,
  180. (Vector){32, -CARD_HEIGHT},
  181. (Vector){64, 32},
  182. (Vector){2, 2},
  183. t,
  184. false,
  185. canvas);
  186. }
  187. }
  188. void dealer_back_card_animation(const void* ctx, Canvas* const canvas) {
  189. const GameState* game_state = ctx;
  190. float t = animationTime(game_state);
  191. Vector currentPos =
  192. quadratic_2d((Vector){32, -CARD_HEIGHT}, (Vector){64, 32}, (Vector){13, 5}, t);
  193. draw_card_back_at(currentPos.x, currentPos.y, canvas);
  194. }
  195. void player_card_animation(const void* ctx, Canvas* const canvas) {
  196. const GameState* game_state = ctx;
  197. float t = animationTime(game_state);
  198. Card animatingCard = game_state->deck.cards[game_state->deck.index];
  199. Vector end = card_pos_at_index(game_state->player_card_count);
  200. draw_card_animation(
  201. animatingCard, (Vector){32, -CARD_HEIGHT}, (Vector){0, 32}, end, t, true, canvas);
  202. }
  203. //endregion
  204. void player_tick(GameState* game_state) {
  205. uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count);
  206. if((game_state->doubled && score <= 21) || score == 21) {
  207. enqueue(
  208. &(game_state->queue_state),
  209. game_state,
  210. dealerTurn,
  211. NULL,
  212. to_dealer_turn,
  213. game_state->settings.message_duration);
  214. } else if(score > 21) {
  215. enqueue(
  216. &(game_state->queue_state),
  217. game_state,
  218. lose,
  219. NULL,
  220. to_bust_state,
  221. game_state->settings.message_duration);
  222. } else {
  223. if(game_state->selectDirection == DirectionUp ||
  224. game_state->selectDirection == DirectionDown) {
  225. move_menu(game_state->menu, game_state->selectDirection == DirectionUp ? -1 : 1);
  226. }
  227. if(game_state->selectDirection == Select) {
  228. activate_menu(game_state->menu, game_state);
  229. }
  230. }
  231. }
  232. void dealer_tick(GameState* game_state) {
  233. uint8_t dealer_score = hand_count(game_state->dealer_cards, game_state->dealer_card_count);
  234. uint8_t player_score = hand_count(game_state->player_cards, game_state->player_card_count);
  235. if(dealer_score >= DEALER_MAX) {
  236. if(dealer_score > 21 || dealer_score < player_score) {
  237. dolphin_deed(DolphinDeedPluginGameWin);
  238. enqueue(
  239. &(game_state->queue_state),
  240. game_state,
  241. win,
  242. NULL,
  243. to_win_state,
  244. game_state->settings.message_duration);
  245. } else if(dealer_score > player_score) {
  246. enqueue(
  247. &(game_state->queue_state),
  248. game_state,
  249. lose,
  250. NULL,
  251. to_lose_state,
  252. game_state->settings.message_duration);
  253. } else if(dealer_score == player_score) {
  254. enqueue(
  255. &(game_state->queue_state),
  256. game_state,
  257. draw,
  258. NULL,
  259. to_draw_state,
  260. game_state->settings.message_duration);
  261. }
  262. } else {
  263. enqueue(
  264. &(game_state->queue_state),
  265. game_state,
  266. drawDealerCard,
  267. NULL,
  268. dealer_card_animation,
  269. game_state->settings.animation_duration);
  270. }
  271. }
  272. void settings_tick(GameState* game_state) {
  273. if(game_state->selectDirection == DirectionDown && game_state->selectedMenu < 4) {
  274. game_state->selectedMenu++;
  275. }
  276. if(game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) {
  277. game_state->selectedMenu--;
  278. }
  279. if(game_state->selectDirection == DirectionLeft ||
  280. game_state->selectDirection == DirectionRight) {
  281. int nextScore = 0;
  282. switch(game_state->selectedMenu) {
  283. case 0:
  284. nextScore = game_state->settings.starting_money;
  285. if(game_state->selectDirection == DirectionLeft)
  286. nextScore -= 10;
  287. else
  288. nextScore += 10;
  289. if(nextScore >= (int)game_state->settings.round_price && nextScore < 400)
  290. game_state->settings.starting_money = nextScore;
  291. break;
  292. case 1:
  293. nextScore = game_state->settings.round_price;
  294. if(game_state->selectDirection == DirectionLeft)
  295. nextScore -= 10;
  296. else
  297. nextScore += 10;
  298. if(nextScore >= 5 && nextScore <= (int)game_state->settings.starting_money)
  299. game_state->settings.round_price = nextScore;
  300. break;
  301. case 2:
  302. nextScore = game_state->settings.animation_duration;
  303. if(game_state->selectDirection == DirectionLeft)
  304. nextScore -= 100;
  305. else
  306. nextScore += 100;
  307. if(nextScore >= 0 && nextScore < 2000)
  308. game_state->settings.animation_duration = nextScore;
  309. break;
  310. case 3:
  311. nextScore = game_state->settings.message_duration;
  312. if(game_state->selectDirection == DirectionLeft)
  313. nextScore -= 100;
  314. else
  315. nextScore += 100;
  316. if(nextScore >= 0 && nextScore < 2000)
  317. game_state->settings.message_duration = nextScore;
  318. break;
  319. case 4:
  320. game_state->settings.sound_effects = !game_state->settings.sound_effects;
  321. default:
  322. break;
  323. }
  324. }
  325. }
  326. void tick(GameState* game_state) {
  327. game_state->last_tick = furi_get_tick();
  328. bool queue_ran = run_queue(&(game_state->queue_state), game_state);
  329. switch(game_state->state) {
  330. case GameStateGameOver:
  331. case GameStateStart:
  332. if(game_state->selectDirection == Select)
  333. init(game_state);
  334. else if(game_state->selectDirection == DirectionRight) {
  335. game_state->selectedMenu = 0;
  336. game_state->state = GameStateSettings;
  337. }
  338. break;
  339. case GameStatePlay:
  340. if(!game_state->started) {
  341. game_state->selectedMenu = 0;
  342. game_state->started = true;
  343. enqueue(
  344. &(game_state->queue_state),
  345. game_state,
  346. drawDealerCard,
  347. NULL,
  348. dealer_back_card_animation,
  349. game_state->settings.animation_duration);
  350. enqueue(
  351. &(game_state->queue_state),
  352. game_state,
  353. drawPlayerCard,
  354. NULL,
  355. player_card_animation,
  356. game_state->settings.animation_duration);
  357. enqueue(
  358. &(game_state->queue_state),
  359. game_state,
  360. drawDealerCard,
  361. NULL,
  362. dealer_card_animation,
  363. game_state->settings.animation_duration);
  364. enqueue(
  365. &(game_state->queue_state),
  366. game_state,
  367. drawPlayerCard,
  368. NULL,
  369. player_card_animation,
  370. game_state->settings.animation_duration);
  371. }
  372. if(!queue_ran) player_tick(game_state);
  373. break;
  374. case GameStateDealer:
  375. if(!queue_ran) dealer_tick(game_state);
  376. break;
  377. case GameStateSettings:
  378. settings_tick(game_state);
  379. break;
  380. default:
  381. break;
  382. }
  383. game_state->selectDirection = None;
  384. }
  385. void start_round(GameState* game_state) {
  386. game_state->menu->current_menu = 1;
  387. game_state->player_card_count = 0;
  388. game_state->dealer_card_count = 0;
  389. set_menu_state(game_state->menu, 0, true);
  390. game_state->menu->enabled = true;
  391. game_state->started = false;
  392. game_state->doubled = false;
  393. game_state->queue_state.running = true;
  394. shuffle_deck(&(game_state->deck));
  395. game_state->doubled = false;
  396. game_state->bet = game_state->settings.round_price;
  397. if(game_state->player_score < game_state->settings.round_price) {
  398. game_state->state = GameStateGameOver;
  399. } else {
  400. game_state->player_score -= game_state->settings.round_price;
  401. }
  402. game_state->state = GameStatePlay;
  403. }
  404. void init(GameState* game_state) {
  405. set_menu_state(game_state->menu, 0, true);
  406. game_state->menu->enabled = true;
  407. game_state->menu->current_menu = 1;
  408. game_state->settings = load_settings();
  409. game_state->last_tick = 0;
  410. game_state->processing = true;
  411. game_state->selectedMenu = 0;
  412. game_state->player_score = game_state->settings.starting_money;
  413. generate_deck(&(game_state->deck), 6);
  414. start_round(game_state);
  415. }
  416. static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  417. furi_assert(event_queue);
  418. AppEvent event = {.type = EventTypeKey, .input = *input_event};
  419. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  420. }
  421. static void update_timer_callback(FuriMessageQueue* event_queue) {
  422. furi_assert(event_queue);
  423. AppEvent event = {.type = EventTypeTick};
  424. furi_message_queue_put(event_queue, &event, 0);
  425. }
  426. void doubleAction(void* state) {
  427. GameState* game_state = state;
  428. if(!game_state->doubled && game_state->player_score >= game_state->settings.round_price) {
  429. game_state->player_score -= game_state->settings.round_price;
  430. game_state->bet += game_state->settings.round_price;
  431. game_state->doubled = true;
  432. enqueue(
  433. &(game_state->queue_state),
  434. game_state,
  435. drawPlayerCard,
  436. NULL,
  437. player_card_animation,
  438. game_state->settings.animation_duration);
  439. game_state->player_cards[game_state->player_card_count] =
  440. game_state->deck.cards[game_state->deck.index];
  441. uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count + 1);
  442. if(score > 21) {
  443. enqueue(
  444. &(game_state->queue_state),
  445. game_state,
  446. lose,
  447. NULL,
  448. to_bust_state,
  449. game_state->settings.message_duration);
  450. } else {
  451. enqueue(
  452. &(game_state->queue_state),
  453. game_state,
  454. dealerTurn,
  455. NULL,
  456. to_dealer_turn,
  457. game_state->settings.message_duration);
  458. }
  459. set_menu_state(game_state->menu, 0, false);
  460. }
  461. }
  462. void hitAction(void* state) {
  463. GameState* game_state = state;
  464. enqueue(
  465. &(game_state->queue_state),
  466. game_state,
  467. drawPlayerCard,
  468. NULL,
  469. player_card_animation,
  470. game_state->settings.animation_duration);
  471. }
  472. void stayAction(void* state) {
  473. GameState* game_state = state;
  474. enqueue(
  475. &(game_state->queue_state),
  476. game_state,
  477. dealerTurn,
  478. NULL,
  479. to_dealer_turn,
  480. game_state->settings.message_duration);
  481. }
  482. int32_t blackjack_app(void* p) {
  483. UNUSED(p);
  484. int32_t return_code = 0;
  485. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
  486. GameState* game_state = malloc(sizeof(GameState));
  487. game_state->menu = malloc(sizeof(Menu));
  488. game_state->menu->menu_width = 40;
  489. init(game_state);
  490. add_menu(game_state->menu, "Double", doubleAction);
  491. add_menu(game_state->menu, "Hit", hitAction);
  492. add_menu(game_state->menu, "Stay", stayAction);
  493. set_card_graphics(&I_card_graphics);
  494. game_state->state = GameStateStart;
  495. game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  496. if(!game_state->mutex) {
  497. FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
  498. return_code = 255;
  499. goto free_and_exit;
  500. }
  501. ViewPort* view_port = view_port_alloc();
  502. view_port_draw_callback_set(view_port, render_callback, game_state);
  503. view_port_input_callback_set(view_port, input_callback, event_queue);
  504. FuriTimer* timer = furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
  505. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
  506. Gui* gui = furi_record_open(RECORD_GUI);
  507. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  508. AppEvent event;
  509. // Call dolphin deed on game start
  510. dolphin_deed(DolphinDeedPluginGameStart);
  511. for(bool processing = true; processing;) {
  512. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  513. furi_mutex_acquire(game_state->mutex, FuriWaitForever);
  514. if(event_status == FuriStatusOk) {
  515. if(event.type == EventTypeKey) {
  516. if(event.input.type == InputTypePress) {
  517. switch(event.input.key) {
  518. case InputKeyUp:
  519. game_state->selectDirection = DirectionUp;
  520. break;
  521. case InputKeyDown:
  522. game_state->selectDirection = DirectionDown;
  523. break;
  524. case InputKeyRight:
  525. game_state->selectDirection = DirectionRight;
  526. break;
  527. case InputKeyLeft:
  528. game_state->selectDirection = DirectionLeft;
  529. break;
  530. case InputKeyBack:
  531. if(game_state->state == GameStateSettings) {
  532. game_state->state = GameStateStart;
  533. save_settings(game_state->settings);
  534. } else
  535. processing = false;
  536. break;
  537. case InputKeyOk:
  538. game_state->selectDirection = Select;
  539. break;
  540. default:
  541. break;
  542. }
  543. }
  544. } else if(event.type == EventTypeTick) {
  545. tick(game_state);
  546. processing = game_state->processing;
  547. }
  548. }
  549. furi_mutex_release(game_state->mutex);
  550. view_port_update(view_port);
  551. }
  552. furi_timer_free(timer);
  553. view_port_enabled_set(view_port, false);
  554. gui_remove_view_port(gui, view_port);
  555. furi_record_close(RECORD_GUI);
  556. view_port_free(view_port);
  557. furi_mutex_free(game_state->mutex);
  558. free_and_exit:
  559. free(game_state->deck.cards);
  560. free_menu(game_state->menu);
  561. queue_clear(&(game_state->queue_state));
  562. free(game_state);
  563. furi_message_queue_free(event_queue);
  564. return return_code;
  565. }