GBAlatro
A Demake of Balatro for the GBA
Loading...
Searching...
No Matches
round.c
Go to the documentation of this file.
1
6#include "game/round.h"
7
8#include "audio_utils.h"
9#include "background_gfx.h"
10#include "button.h"
11#include "game.h"
12#include "game/joker_row.h"
13#include "graphic_utils.h"
14#include "hand.h"
15#include "joker.h"
16#include "layout.h"
17#include "list.h"
18#include "selection_grid.h"
19#include "soundbank.h"
20#include "timer.h"
21#include "util.h"
22
23#include <stdlib.h>
24#include <tonc.h>
25
26/*******************************************************************************
27 * CONSTS
28 ******************************************************************************/
29
30// Palette IDs
31#define PLAY_HAND_BTN_PAL_IDX 6
32#define PLAY_HAND_BTN_BORDER_PAL_IDX 7
33#define DISCARD_BTN_PAL_IDX 13
34#define DISCARD_BTN_BORDER_PAL_IDX 8
35#define SORT_BTNS_PAL_IDX 9
36#define SORT_BY_RANK_BTN_BORDER_PAL_IDX 22
37#define SORT_BY_SUIT_BTN_BORDER_PAL_IDX 23
38#define BLIND_BG_SHADOW_PAL_IDX 5
39#define BLIND_BG_SECONDARY_PAL_IDX 18
40#define BLIND_BG_PRIMARY_PAL_IDX 19
41
42// Naming the stage where cards return from the discard pile to the deck "undiscard"
43#define PITCH_STEP_DISCARD_SFX (-64)
44#define PITCH_STEP_DRAW_SFX 24
45#define PITCH_STEP_UNDISCARD_SFX (2 * PITCH_STEP_DRAW_SFX)
46
47/* This needs to stay a power of 2 and small enough
48 * for the lerping to be done before the next hand is drawn.
49 */
50#define NUM_SCORE_LERP_STEPS 16
51#define TM_SCORE_LERP_INTERVAL 2
52
53#define GAME_PLAYING_HAND_SEL_Y 1
54
55// Pixel sizes
56#define CARD_FOCUSED_UNSEL_Y 10
57#define CARD_UNFOCUSED_SEL_Y 15
58#define CARD_FOCUSED_SEL_Y 20
59#define SCORED_CARD_TEXT_Y 48
60
61// clang-format off
62
63// Flaming score animation frames
64#define SCORE_FLAMES_ANIM_FREQ 5 // animation will run at 12FPS
65#define NUM_SCORE_FLAMES_FRAMES 8 // Chips and Mult flame frames are next to one another
66#define SCORE_FLAME_FRAME_WIDTH 3 // so we only need to offset to get the next ones
67static const Rect SCORE_FLAME_RESET = {26, 20, 28, 20};
68static const Rect SCORE_FLAME_FRAMES_START = {26, 21, 28, 21};
69static const BG_POINT SCORE_FLAME_CHIPS_POS = {1, 9};
70static const BG_POINT SCORE_FLAME_MULT_POS = {5, 9};
71
72/* Contains the shop icon/current blind etc.
73 * The difference between TOP_LEFT_PANEL_ANIM_RECT and TOP_LEFT_PANEL_RECT
74 * is due to an overlap between the bottom of the top left panel
75 * and the top of the score panel in the tiles connecting them.
76 * TOP_LEFT_PANEL_ANIM_RECT should be used for animations,
77 * TOP_LEFT_PANEL_RECT for copies etc. but mind the overlap
78 */
79static const BG_POINT TOP_LEFT_BLIND_TITLE_POINT = {0, 21};
80static const Rect BIG_BLIND_TITLE_SRC_RECT = {0, 26, 8, 26 };
81static const Rect BOSS_BLIND_TITLE_SRC_RECT = {0, 27, 8, 27 };
82
83// Rects for TTE (in pixels)
84
85// The rect for popping menu animations (round end, shop, blinds)
86// - extends beyond the visible screen to the end of the screenblock
87// It includes both the target and source position rects.
88// This is because when popping, the target position is blank so we just animate
89// the whole rect so we don't have to track its position
90static const Rect HAND_BG_RECT_SELECTING = {9, 11, 24, 17 };
91static const Rect HAND_SIZE_RECT_SELECT = {120, 128, 160, 136 };
92static const Rect HAND_SIZE_RECT_PLAYING = {120, 152, 160, 160 };
93static const Rect PLAYED_CARDS_SCORES_RECT = {72, 48, 240, 56 };
94static const Rect HELD_CARDS_SCORES_RECT = {72, 108, 240, 116 };
95
96static const BG_POINT CARD_DRAW_POS = {208, 110};
97static const BG_POINT CARD_DISCARD_PNT = {240, 70};
98static const BG_POINT HAND_START_POS = {120, 90};
99static const BG_POINT HAND_PLAY_POS = {120, 70};
100// clang-format on
101
102/*******************************************************************************
103 * ROUND SELECTIONGRID
104 ******************************************************************************/
105
106static int game_round_button_row_get_size(void);
107static bool game_round_button_row_on_selection_changed(
108 SelectionGrid* selection_grid,
109 int row_idx,
110 const Selection* prev_selection,
111 const Selection* new_selection
112);
113static void game_round_button_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection);
114
115static void game_round_hand_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection);
116
117static bool game_round_hand_row_on_selection_changed(
118 SelectionGrid* selection_grid,
119 int row_idx,
120 const Selection* prev_selection,
121 const Selection* new_selection
122);
123
124static int game_round_hand_row_get_size(void);
125
126static int hand_sel_idx_to_card_idx(int selection_index);
127
128// clang-format off
129static SelectionGridRow game_round_selection_rows[] = {
130 {
131 0,
135 {.wrap = false}
136 },
137 {
138 1,
139 game_round_hand_row_get_size,
140 game_round_hand_row_on_selection_changed,
141 game_round_hand_row_on_key_transit,
142 {.wrap = true}
143 },
144 {
145 2,
146 game_round_button_row_get_size,
147 game_round_button_row_on_selection_changed,
148 game_round_button_row_on_key_hit,
149 {.wrap = false}
150 }
151};
152// clang-format on
153
154static const Selection GAME_PLAYING_INIT_SEL = {0, 1};
155
156static SelectionGrid game_round_selection_grid = {
157 game_round_selection_rows,
158 NUM_ELEM_IN_ARR(game_round_selection_rows),
159 GAME_PLAYING_INIT_SEL
160};
161
162/*******************************************************************************
163 * ROUND BUTTONS
164 ******************************************************************************/
165
166static void game_round_discard_on_pressed(void);
167static void game_round_play_hand_on_pressed(void);
168static void game_round_sort_by_rank_on_pressed(void);
169static void game_round_sort_by_suit_on_pressed(void);
170
171static bool can_discard_hand(void);
172static bool can_play_hand(void);
173
174// clang-format off
175// Array of buttons by horizontal selection index (x)
176static Button game_round_buttons[] = {
177 {PLAY_HAND_BTN_BORDER_PAL_IDX, PLAY_HAND_BTN_PAL_IDX, game_round_play_hand_on_pressed, can_play_hand },
178 {SORT_BY_RANK_BTN_BORDER_PAL_IDX, SORT_BTNS_PAL_IDX, game_round_sort_by_rank_on_pressed, NULL },
179 {SORT_BY_SUIT_BTN_BORDER_PAL_IDX, SORT_BTNS_PAL_IDX, game_round_sort_by_suit_on_pressed, NULL },
180 {DISCARD_BTN_BORDER_PAL_IDX, DISCARD_BTN_PAL_IDX, game_round_discard_on_pressed, can_discard_hand},
181};
182// clang-format on
183
184/*******************************************************************************
185 * CARD PLAYING STATE
186 ******************************************************************************/
187
188enum PlayState
189{
190 PLAY_STARTING,
191 PLAY_BEFORE_SCORING,
192 PLAY_SCORING_CARDS,
193 PLAY_SCORING_CARD_JOKERS,
194 PLAY_SCORING_HELD_CARDS,
195 PLAY_SCORING_INDEPENDENT_JOKERS,
196 PLAY_SCORING_HAND_SCORED_END,
197 PLAY_ENDING,
198 PLAY_ENDED
199};
200
201static enum PlayState play_state = PLAY_STARTING;
202
203/*******************************************************************************
204 * INTERNAL VARIABLES
205 ******************************************************************************/
206
207// This is a stupid way to do this but I don't care
208static const int HAND_SPACING_LUT[MAX_HAND_SIZE] =
209 {28, 28, 28, 28, 27, 21, 18, 15, 13, 12, 10, 9, 9, 8, 8, 7};
210
211// Stacks
212static CardObject* s_played_hand[MAX_SELECTION_SIZE] = {NULL};
213static int s_played_top = -1;
214
215static Card* s_discard_pile[MAX_DECK_SIZE] = {NULL};
216static int s_discard_top = -1;
217
218// Keeping track of cards scored
219static int s_scored_card_index = 0;
220static bool s_retrigger = false;
221
222// Keeping track of what Jokers are scored at each step
223static ListItr s_joker_scored_itr;
224static ListItr s_joker_card_scored_end_itr;
225static ListItr s_joker_round_end_itr;
226
227static u32 s_temp_score = 0; // This is the score that shows in the same spot as the hand type.
228static bool s_score_flames_active = false;
229static FIXED s_lerped_score = 0;
230static FIXED s_lerped_temp_score = 0;
231
232// discarded cards specific
233static bool s_sound_played = false;
234static bool s_discarded_card = false;
235
236static int s_cards_drawn = 0;
237static int s_cards_discarded = 0;
238
239/*******************************************************************************
240 * UTILS FUNCTIONS
241 ******************************************************************************/
242
244{
245 return s_played_top;
246}
247
249{
250 return s_played_top + 1;
251}
252
258static inline void played_push(CardObject* card_object)
259{
260 if (s_played_top >= MAX_SELECTION_SIZE - 1)
261 return;
262 s_played_hand[++s_played_top] = card_object;
263}
264
266{
267 return s_discard_top;
268}
269
277static inline void discard_push(Card* card)
278{
279 if (s_discard_top >= MAX_DECK_SIZE - 1)
280 return;
281 s_discard_pile[++s_discard_top] = card;
282}
283
289static inline Card* discard_pop()
290{
291 if (s_discard_top < 0)
292 return NULL;
293 return s_discard_pile[s_discard_top--];
294}
295
297{
298 return s_scored_card_index;
299}
300
301void set_retrigger(bool new_retrigger)
302{
303 s_retrigger = new_retrigger;
304}
305
314static void get_played_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
315{
316 for (int i = 0; i < NUM_RANKS; i++)
317 ranks_out[i] = 0;
318 for (int i = 0; i < NUM_SUITS; i++)
319 suits_out[i] = 0;
320
321 for (int i = 0; i <= s_played_top; i++)
322 {
323 /* The difference from get_hand_distribution() (not checking if card is selected)
324 * is in line Balatro behavior,
325 * see https://github.com/GBALATRO/balatro-gba/issues/341#issuecomment-3691363488
326 */
327 if (!s_played_hand[i])
328 continue;
329 ranks_out[s_played_hand[i]->card->rank]++;
330 suits_out[s_played_hand[i]->card->suit]++;
331 }
332}
333
334/*******************************************************************************
335 * BACKGROUND MANIPULATION
336 ******************************************************************************/
337
339{
340 tte_erase_rect_wrapper(HAND_SIZE_RECT_PLAYING);
341 REG_WIN0V = (REG_WIN0V << 8) | 0x80; // Set window 0 top to 128
342
343 if (get_current_background() == BG_CARD_PLAYING)
344 {
345 int offset = 11;
346 memcpy16(
347 &se_mem[MAIN_BG_SBB][SE_ROW_LEN * offset],
348 &background_gfxMap[SE_ROW_LEN * offset],
349 SE_ROW_LEN * 8
350 );
351 }
352 else
353 {
354 toggle_windows(true, true); // Enable window 0 for the hand shadow
355
356 // Load the tiles and palette
357 // Background
358 GRIT_CPY(pal_bg_mem, background_gfxPal);
359 GRIT_CPY(&tile8_mem[MAIN_BG_CBB], background_gfxTiles);
360 GRIT_CPY(&se_mem[MAIN_BG_SBB], background_gfxMap);
361
362 if (g_game_vars.current_blind ==
363 BLIND_TYPE_BIG) // Change text and palette depending on blind type
364 {
365 main_bg_se_copy_rect(BIG_BLIND_TITLE_SRC_RECT, TOP_LEFT_BLIND_TITLE_POINT);
366 }
367 else if (g_game_vars.current_blind >= BLIND_TYPE_BOSS)
368 {
369 main_bg_se_copy_rect(BOSS_BLIND_TITLE_SRC_RECT, TOP_LEFT_BLIND_TITLE_POINT);
370 }
371
372 // Copies the Blind item into the top left panel
373 main_bg_se_copy_rect(TOP_LEFT_ITEM_SRC_RECT, TOP_LEFT_PANEL_POINT);
374
375 // This would change the palette of the background to match the blind, but the backgroun
376 // doesn't use the blind token's exact colors so a different approach is required
377 memset16(
378 &pal_bg_mem[BLIND_BG_PRIMARY_PAL_IDX],
379 blind_get_color(g_game_vars.current_blind, BLIND_BACKGROUND_MAIN_COLOR_INDEX),
380 1
381 );
382 memset16(
383 &pal_bg_mem[BLIND_BG_SECONDARY_PAL_IDX],
384 blind_get_color(g_game_vars.current_blind, BLIND_BACKGROUND_SECONDARY_COLOR_INDEX),
385 1
386 );
387 memset16(
388 &pal_bg_mem[BLIND_BG_SHADOW_PAL_IDX],
389 blind_get_color(g_game_vars.current_blind, BLIND_BACKGROUND_SHADOW_COLOR_INDEX),
390 1
391 );
392
393 for (int i = 0; i < NUM_ELEM_IN_ARR(game_round_buttons); i++)
394 {
395 button_set_highlight(&game_round_buttons[i], false);
396 }
397 }
398}
399
401{
402 if (get_current_background() != BG_CARD_SELECTING)
403 {
404 change_background(BG_CARD_SELECTING, false);
405 }
406
407 REG_WIN0V = (REG_WIN0V << 8) | 0xA0; // Set window 0 bottom to 160
408 toggle_windows(true, true);
409
410 for (int i = 0; i <= 2; i++)
411 {
412 main_bg_se_move_rect_1_tile_vert(HAND_BG_RECT_SELECTING, SCREEN_DOWN);
413 }
414
415 tte_erase_rect_wrapper(HAND_SIZE_RECT_SELECT);
416}
417
418/*******************************************************************************
419 * BUTTONS IMPLEMENTATION
420 ******************************************************************************/
421
427static bool can_discard_hand(void)
428{
429 return (
430 g_game_vars.discards > 0 && get_hand_state() == HAND_SELECT &&
432 );
433}
434
440static bool can_play_hand(void)
441{
442 return (
443 g_game_vars.hands > 0 && get_hand_state() == HAND_SELECT && hand_get_nb_selected_cards() > 0
444 );
445}
446
450static inline void game_round_execute_discard(void)
451{
452 if (!can_discard_hand())
453 return;
454
455 set_hand_state(HAND_DISCARD);
456 --g_game_vars.discards;
457 display_discards();
459}
460
465{
466 if (!can_discard_hand())
467 return;
468
470
471 // Move back to hand selection
472 selection_grid_move_selection_vert(&game_round_selection_grid, -1);
473}
474
479{
480 hand_change_sort(false);
481}
482
487{
488 hand_change_sort(true);
489}
490
494static inline void game_round_execute_play_hand(void)
495{
496 if (!can_play_hand())
497 return;
498
499 set_hand_state(HAND_PLAY);
500
501 --g_game_vars.hands;
502 display_hands();
503
504 g_game_vars.nb_played_hands[get_hand_type() - 1]++;
505}
506
511{
512 if (!can_play_hand())
513 return;
514
516
517 // Move back to hand selection
518 selection_grid_move_selection_vert(&game_round_selection_grid, -1);
519}
520
521static int game_round_hand_row_get_size(void)
522{
523 return hand_nb_held_cards();
524}
525
526/*******************************************************************************
527 * CARD MOVING LOGIC
528 ******************************************************************************/
529
530// true if and only if we are currently moving a card around
531static bool s_moving_card = false;
532
533// This will prevent us from moving cards around if we selected one
534// by moving too fast after pressing the A button
535static bool s_card_moved_too_fast = false;
536static bool s_card_selected_instead_of_moved = false;
537
538// After pressing A, if we press Left/Right too fast, we should select the card
539// and change focus to the next one, instead of swapping them
540// This should fix inputs sometimes not registering when quickly selecting cards
541static const int CARD_SWAP_TIME_THRESHOLD = 6;
542static int s_selection_hit_timer = UNDEFINED;
543
544static bool game_round_hand_row_on_selection_changed(
545 SelectionGrid* selection_grid,
546 int row_idx,
547 const Selection* prev_selection,
548 const Selection* new_selection
549)
550{
551 int prev_card_idx = UNDEFINED;
552 int next_card_idx = UNDEFINED;
553
554 // Do not use FRAMES(x) here as we are counting real frames ignoring game speed
555 s_card_moved_too_fast = (s_selection_hit_timer != UNDEFINED) &&
556 (g_game_vars.timer - s_selection_hit_timer) < CARD_SWAP_TIME_THRESHOLD;
557
558 if (prev_selection->y == GAME_PLAYING_HAND_SEL_Y)
559 {
560 prev_card_idx = hand_sel_idx_to_card_idx(prev_selection->x);
561 }
562
563 if (new_selection->y == GAME_PLAYING_HAND_SEL_Y)
564 {
565 next_card_idx = hand_sel_idx_to_card_idx(new_selection->x);
566 }
567
568 bool on_the_same_row = new_selection->y == prev_selection->y; // == GAME_PLAYING_HAND_SEL_Y
569
570 if (on_the_same_row && key_is_down(SELECT_CARD) && !s_card_moved_too_fast &&
571 !s_card_selected_instead_of_moved)
572 {
573 bool moved_by_one_tile = abs(new_selection->x - prev_selection->x) == 1;
574
575 // Avoid swapping when selection wraps
576 if (!moved_by_one_tile)
577 {
578 // Abort the selection if swapping so it doesn't wrap
579 return false;
580 }
581 else
582 {
583 swap_cards_in_hand(prev_card_idx, next_card_idx);
584 s_moving_card = true;
586
587 /* Not calling sprite_object_set_focus() because focus is handled by
588 * cards_in_hand_update_loop() based on the selection grid value...
589 */
590 play_sfx(
591 SFX_CARD_FOCUS,
592 MM_BASE_PITCH_RATE + rng_get_u32(RNG_SEQ_MISC) % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
594 );
595 }
596 }
597 else
598 {
599 // select current card if we tried moving it too fast
600 if (key_released(SELECT_CARD) || (s_card_moved_too_fast && !s_moving_card))
601 {
602 hand_select_card(prev_card_idx);
603 s_card_selected_instead_of_moved = true;
604 }
605 if (next_card_idx != UNDEFINED)
606 {
607 /* Not calling sprite_object_set_focus() because focus is handled by
608 * cards_in_hand_update_loop() based on the selection grid value...
609 */
610 play_sfx(
611 SFX_CARD_FOCUS,
612 MM_BASE_PITCH_RATE + rng_get_u32(RNG_SEQ_MISC) % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
614 );
615 }
616 }
617
618 return true;
619}
620
621static void game_round_hand_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection)
622{
623 if (key_hit(SELECT_CARD))
624 {
625 s_selection_hit_timer = g_game_vars.timer;
626 }
627 else if (key_released(SELECT_CARD))
628 {
629 if (!s_moving_card && !s_card_selected_instead_of_moved)
630 {
632 }
633 s_moving_card = false;
634 s_card_moved_too_fast = false;
635 s_card_selected_instead_of_moved = false;
636 s_selection_hit_timer = UNDEFINED;
637 }
638 else if (key_hit(DESELECT_CARDS))
639 {
642 }
643 else if (key_hit(PLAY_HAND_KEY))
644 {
646 }
647 else if (key_hit(DISCARD_HAND_KEY))
648 {
650 }
651}
652
653/*******************************************************************************
654 * OTHER SELECTIONGRID IMPLEMENTATION
655 ******************************************************************************/
656
657static int game_round_button_row_get_size(void)
658{
659 return NUM_ELEM_IN_ARR(game_round_buttons);
660}
661
662static inline void game_round_button_set_highlight(int btn_idx, bool highlight)
663{
664 button_set_highlight(&game_round_buttons[btn_idx], highlight);
665}
666
667static bool game_round_button_row_on_selection_changed(
668 SelectionGrid* selection_grid,
669 int row_idx,
670 const Selection* prev_selection,
671 const Selection* new_selection
672)
673{
674 // The selection grid system only guarantees that the new selection is within bounds
675 // but not the previous one...
676 // As of writing (PR #348), this check is not strictly needed for this row but it is
677 // left in, in case that ever changes. It can be reconsidered and removed.
678 if (prev_selection->y == row_idx && prev_selection->x >= 0 &&
679 prev_selection->x < game_round_button_row_get_size())
680 {
681 game_round_button_set_highlight(prev_selection->x, false);
682 }
683
684 if (new_selection->y == row_idx)
685 {
686 game_round_button_set_highlight(new_selection->x, true);
687 }
688
689 return true;
690}
691
692static void game_round_button_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection)
693{
694 if (key_hit(SELECT_CARD))
695 {
696 button_press(&game_round_buttons[selection->x]);
697 }
698}
699
706static inline int hand_sel_idx_to_card_idx(int selection_index)
707{
708 // This is because the hand is drawn from right to left.
709 // There is no particular reason for why that was done, it's just how it was done.
710 // Maybe one day it can be reverted and made consistent so this conversion is not needed.
711 return hand_nb_held_cards() - selection_index - 1;
712}
713
714static inline void game_round_process_hand_select_input(void)
715{
716 selection_grid_process_input(&game_round_selection_grid);
717}
718
723static inline void game_round_handle_round_over(void)
724{
725 enum GameState next_state = GAME_STATE_ROUND_END;
726
727 if (g_game_vars.score >= blind_get_requirement(g_game_vars.current_blind, g_game_vars.ante))
728 {
729 if (g_game_vars.current_blind > BLIND_TYPE_BIG)
730 {
731 if (g_game_vars.ante < MAX_ANTE)
732 {
733 g_game_vars.ante++;
734 display_ante();
735
736 // mark current boss blind as beaten and allow for reroll
737 set_blind_beaten(g_game_vars.next_boss_blind);
738 }
739 else
740 {
741 next_state = GAME_STATE_WIN;
742 }
743 }
744 }
745 else if (g_game_vars.hands == 0)
746 {
747 next_state = GAME_STATE_LOSE;
748 }
749
750 game_change_state(next_state);
751}
752
753static inline void card_in_hand_loop_handle_discard_and_shuffling(
754 int card_idx,
755 FIXED* hand_x,
756 FIXED* hand_y,
757 bool* break_loop
758)
759{
760 if (get_hand_state() != HAND_DISCARD && get_hand_state() != HAND_SHUFFLING)
761 {
762 // Assumes hand_state is one of these
763 return;
764 }
765
766 CardObject** hand = get_hand_array();
767
768 *break_loop = false;
769 if (card_object_is_selected(hand[card_idx]) || get_hand_state() == HAND_SHUFFLING)
770 {
771 if (!s_discarded_card)
772 {
773 *hand_x = int2fx(CARD_DISCARD_PNT.x);
774 *hand_y = int2fx(CARD_DISCARD_PNT.y);
775
776 if (!s_sound_played)
777 {
778 play_sfx(
779 SFX_CARD_DRAW,
780 MM_BASE_PITCH_RATE + s_cards_discarded * PITCH_STEP_DISCARD_SFX,
782 );
783 s_sound_played = true;
784 }
785
786 if (hand[card_idx]->x >= *hand_x)
787 {
788 discard_push(hand[card_idx]->card);
789
790 // Remove discarded card from hand and shift the ones after it
791 card_object_destroy(&hand[card_idx]);
794
795 s_cards_discarded++;
796 s_sound_played = false;
797 g_game_vars.timer = TM_ZERO;
798
799 // Protect against an edge case where `card_idx == hand_top` before discarding. In
800 // that case, calling `reorder_card_sprites_layers` cannot shift the discarded card,
801 // and we end up with an element at `card_idx` that is still NULL.
802 if (hand[card_idx] != NULL)
803 {
804 *hand_y = hand[card_idx]->y;
805 *hand_x = hand[card_idx]->x;
806 }
807 }
808
809 s_discarded_card = true;
810 }
811 else
812 {
813 if (get_hand_state() == HAND_DISCARD)
814 {
815 // Don't raise the card if we're mass discarding, it looks stupid.
816 *hand_y -= int2fx(15);
817 }
818 else // hand_state == HAND_SHUFFLING
819 {
820 *hand_y += int2fx(24);
821 }
822 *hand_x = *hand_x + (int2fx(card_idx) - int2fx(get_hand_top()) / 2) *
823 -HAND_SPACING_LUT[get_hand_top()];
824 }
825 }
826 else
827 {
828 *hand_x = *hand_x + (int2fx(card_idx) - int2fx(get_hand_top()) / 2) *
829 -HAND_SPACING_LUT[get_hand_top()];
830 }
831
832 if (card_idx == 0 && s_discarded_card == false && g_game_vars.timer % FRAMES(10) == 0)
833 {
834 // This is never reached in the case of HAND_SHUFFLING. Not sure why but that's how it's
835 // supposed to be.
836 set_hand_state(HAND_DRAW);
837 s_sound_played = false;
838 s_cards_discarded = 0;
840 g_game_vars.timer = TM_ZERO;
841 *break_loop = true;
842 return;
843 };
844}
845
846static inline void select_flush_and_straight_cards_in_played_hand(void)
847{
848 // Special handling because Four Fingers might be active
849 bool final_selection[MAX_SELECTION_SIZE] = {false};
850
851 // Will be 4 if Four Fingers is in effect, otherwise 5
852 int min_len = get_straight_and_flush_size();
853
854 // if we have a flush in our hand
855 if (get_hand_type() == FLUSH || get_hand_type() == STRAIGHT_FLUSH ||
856 get_hand_type() == ROYAL_FLUSH)
857 {
858 bool flush_selection[MAX_HAND_SIZE] = {false};
859 find_flush_in_played_cards(s_played_hand, s_played_top, min_len, flush_selection);
860 // Add the results into the final selection
861 for (int i = 0; i <= s_played_top; i++)
862 {
863 final_selection[i] = flush_selection[i];
864 }
865 }
866
867 // If we have a straight in our hand
868 if (get_hand_type() == STRAIGHT || get_hand_type() == STRAIGHT_FLUSH ||
869 get_hand_type() == ROYAL_FLUSH)
870 {
871 bool straight_selection[MAX_HAND_SIZE] = {false};
872 find_straight_in_played_cards(s_played_hand, s_played_top, min_len, straight_selection);
873 // Add the results into the final selection
874 for (int i = 0; i <= s_played_top; i++)
875 {
876 final_selection[i] = final_selection[i] || straight_selection[i];
877 }
878 // If Four Fingers is active, pairs can happen in a valid straight
879 // If Four Fingers is not active, pairs are impossible so this will not affect things
880 select_paired_cards_in_hand(s_played_hand, s_played_top, final_selection);
881 }
882
883 // Finally, set mark the cards as selected based final_selection
884 for (int i = 0; i <= s_played_top; i++)
885 {
886 if (final_selection[i])
887 {
888 card_object_set_selected(s_played_hand[i], true);
889 }
890 }
891}
892
893static inline void select_all_five_cards_in_played_hand(void)
894{
895 for (int i = 0; i <= s_played_top; i++)
896 {
897 card_object_set_selected(s_played_hand[i], true);
898 }
899}
900
901static inline void select_four_of_a_kind_cards_in_played_hand(void)
902{
903 int played_size = get_played_size();
904
905 // find four cards with the same rank
906 // If there are 5 cards selected we just need to find the one card that doesn't match, and
907 // select the others
908 if (played_size >= 5)
909 {
910 int unmatched_index = -1;
911
912 for (int i = 0; i <= played_size; i++)
913 {
914 if (s_played_hand[i]->card->rank != s_played_hand[(i + 1) % played_size]->card->rank &&
915 s_played_hand[i]->card->rank != s_played_hand[(i + 2) % played_size]->card->rank)
916 {
917 unmatched_index = i;
918 break;
919 }
920 }
921
922 for (int i = 0; i <= played_size; i++)
923 {
924 if (i != unmatched_index)
925 {
926 card_object_set_selected(s_played_hand[i], true);
927 }
928 }
929 }
930 else // If there are only 4 cards selected we know they match
931 {
932 for (int i = 0; i <= played_size; i++)
933 {
934 card_object_set_selected(s_played_hand[i], true);
935 }
936 }
937}
938
939static inline void select_three_of_a_kind_cards_in_played_hand(void)
940{
941 // find three cards with the same rank
942 for (int i = 0; i <= s_played_top - 1; i++)
943 {
944 for (int j = i + 1; j <= s_played_top; j++)
945 {
946 if (s_played_hand[i]->card->rank == s_played_hand[j]->card->rank)
947 {
948 card_object_set_selected(s_played_hand[i], true);
949 card_object_set_selected(s_played_hand[j], true);
950
951 for (int k = j + 1; k <= s_played_top; k++)
952 {
953 if (s_played_hand[i]->card->rank == s_played_hand[k]->card->rank &&
954 !card_object_is_selected(s_played_hand[k]))
955 {
956 card_object_set_selected(s_played_hand[k], true);
957 break;
958 }
959 }
960
961 break;
962 }
963 }
964
965 if (card_object_is_selected(s_played_hand[i]))
966 break;
967 }
968}
969
970static inline void select_two_pair_cards_in_played_hand(void)
971{
972 // find two pairs of cards with the same rank
973 int i;
974
975 for (i = 0; i <= s_played_top - 1; i++)
976 {
977 for (int j = i + 1; j <= s_played_top; j++)
978 {
979 if (s_played_hand[i]->card->rank == s_played_hand[j]->card->rank)
980 {
981 card_object_set_selected(s_played_hand[i], true);
982 card_object_set_selected(s_played_hand[j], true);
983
984 break;
985 }
986 }
987
988 if (card_object_is_selected(s_played_hand[i]))
989 break;
990 }
991
992 for (; i <= s_played_top - 1; i++) // Find second pair
993 {
994 for (int j = i + 1; j <= s_played_top; j++)
995 {
996 if (s_played_hand[i]->card->rank == s_played_hand[j]->card->rank &&
997 !card_object_is_selected(s_played_hand[i]) &&
998 !card_object_is_selected(s_played_hand[j]))
999 {
1000 card_object_set_selected(s_played_hand[i], true);
1001 card_object_set_selected(s_played_hand[j], true);
1002 break;
1003 }
1004 }
1005 }
1006}
1007
1008static inline void select_pair_cards_in_played_hand(void)
1009{
1010 // find two cards with the same rank
1011 for (int i = 0; i <= s_played_top - 1; i++)
1012 {
1013 for (int j = i + 1; j <= s_played_top; j++)
1014 {
1015 if (s_played_hand[i]->card->rank == s_played_hand[j]->card->rank)
1016 {
1017 card_object_set_selected(s_played_hand[i], true);
1018 card_object_set_selected(s_played_hand[j], true);
1019 break;
1020 }
1021 }
1022
1023 if (card_object_is_selected(s_played_hand[i]))
1024 break;
1025 }
1026}
1027
1028static inline void select_highcard_cards_in_played_hand(void)
1029{
1030 // find the card with the highest rank in the hand
1031 int highest_rank_index = 0;
1032
1033 for (int i = 0; i <= s_played_top; i++)
1034 {
1035 if (s_played_hand[i]->card->rank > s_played_hand[highest_rank_index]->card->rank)
1036 {
1037 highest_rank_index = i;
1038 }
1039 }
1040
1041 card_object_set_selected(s_played_hand[highest_rank_index], true);
1042}
1043
1049static inline bool game_round_is_over(void)
1050{
1051 return g_game_vars.hands == 0 ||
1052 g_game_vars.score >= blind_get_requirement(g_game_vars.current_blind, g_game_vars.ante);
1053}
1054
1055static inline void game_round_process_input_and_state(void)
1056{
1057 if (get_hand_state() == HAND_SELECT)
1058 {
1059 game_round_process_hand_select_input();
1060 }
1061 else if (play_state == PLAY_ENDING)
1062 {
1063 if (g_game_vars.mult > 0)
1064 {
1065 // protect against score overflow
1066 s_temp_score = u32_protected_mult(g_game_vars.chips, g_game_vars.mult);
1067 s_lerped_temp_score = int2fx(s_temp_score);
1068 s_lerped_score = int2fx(g_game_vars.score);
1069
1070 if (s_temp_score > g_game_vars.best_hand_score)
1071 g_game_vars.best_hand_score = s_temp_score;
1072
1073 display_temp_score(s_temp_score);
1074
1075 g_game_vars.chips = 0;
1076 g_game_vars.mult = 0;
1077 display_mult();
1078 display_chips();
1079
1080 static const int SCORE_CALC_SFX_PITCH_SHIFT = -102; // -10% OF MM_BASE_PITCH_RATE
1081 static const int SCORE_CALC_SFX_VOLUME = 204; // 80% MM_SFX_FULL_VOLUME
1082
1083 // The chips calculation SFX is the same as button
1084 play_sfx(
1085 SFX_BUTTON,
1086 MM_BASE_PITCH_RATE + SCORE_CALC_SFX_PITCH_SHIFT,
1087 SCORE_CALC_SFX_VOLUME
1088 );
1089 }
1090 }
1091 else if (play_state == PLAY_ENDED && g_game_vars.timer % FRAMES(TM_SCORE_LERP_INTERVAL) == 0)
1092 {
1093 /* Using fixed point in case the score is lower than NUM_SCORE_LERP_STEPS and then
1094 * then the division rounds it down to 0 and it's never added to the total.
1095 * The operation is equivalent to
1096 * fxdiv(int2fx(temp_score * g_game_vars.game_speed), int2fx(NUM_SCORE_LERP_STEPS))
1097 */
1098 s_lerped_temp_score -= int2fx(s_temp_score * g_game_vars.game_speed) / NUM_SCORE_LERP_STEPS;
1099 s_lerped_score += int2fx(s_temp_score * g_game_vars.game_speed) / NUM_SCORE_LERP_STEPS;
1100
1101 if (s_lerped_temp_score > 0)
1102 {
1103 // Set the score display first because it's more important
1104 // in case there isn't enough time within the frame to display both
1105 display_score(fx2uint(s_lerped_score));
1106 display_temp_score(fx2uint(s_lerped_temp_score));
1107 }
1108 else
1109 {
1110 g_game_vars.score = u32_protected_add(g_game_vars.score, s_temp_score);
1111 s_temp_score = 0;
1112 s_lerped_temp_score = 0;
1113 s_lerped_score = 0;
1114
1115 erase_temp_score();
1116 display_score(g_game_vars.score);
1117 }
1118 }
1119}
1120
1125static inline void card_draw(void)
1126{
1127 if (get_deck_top() < 0 || get_hand_top() >= g_game_vars.hand_size - 1 ||
1128 get_hand_top() >= MAX_HAND_SIZE - 1)
1129 return;
1130
1131 CardObject* card_object = card_object_new(deck_pop());
1132
1133 const FIXED deck_x = int2fx(CARD_DRAW_POS.x);
1134 const FIXED deck_y = int2fx(CARD_DRAW_POS.y);
1135
1136 card_object->x = deck_x;
1137 card_object->y = deck_y;
1138
1140 get_hand_array()[get_hand_top()] = card_object;
1141
1142 // Sort the hand after drawing a card
1143 sort_cards();
1144
1145 play_sfx(
1146 SFX_CARD_DRAW,
1147 MM_BASE_PITCH_RATE + s_cards_drawn * PITCH_STEP_DRAW_SFX,
1149 );
1150}
1151
1152static inline void game_round_process_card_draw(void)
1153{
1154 if (get_hand_state() == HAND_DRAW && s_cards_drawn < g_game_vars.hand_size)
1155 {
1156 if (g_game_vars.timer % FRAMES(10) == 0) // Draw a card every 10 frames
1157 {
1158 s_cards_drawn++;
1159 card_draw();
1160 }
1161 }
1162 else if (get_hand_state() == HAND_DRAW)
1163 {
1164 set_hand_state(HAND_SELECT); // Change the hand state to select after drawing all the cards
1165 s_cards_drawn = 0;
1166 g_game_vars.timer = TM_ZERO;
1167 }
1168}
1169
1170static inline void game_round_discarded_cards_loop(void)
1171{
1172 // Discarded cards loop (mainly for shuffling)
1173 if (hand_nb_held_cards() == 0 && get_hand_state() == HAND_SHUFFLING && s_discard_top >= -1 &&
1174 g_game_vars.timer > FRAMES(10))
1175 {
1176 // Change the background to the round end background. This is how it works in Balatro, so
1177 // I'm doing it this way too.
1178 change_background(BG_ROUND_END, false);
1179
1180 // We take each discarded card and put it back into the deck with a short animation
1181 static CardObject* discarded_card_object = NULL;
1182 if (s_discard_top >= 0 && discarded_card_object == NULL)
1183 {
1184 discarded_card_object = card_object_new(discard_pop());
1185
1186 // Set the sprite for the discarded card object
1187 card_object_set_sprite(discarded_card_object, 0);
1188 sprite_object_reset_transform((SpriteObject*)discarded_card_object);
1189
1190 discarded_card_object->tx = int2fx(204);
1191 discarded_card_object->ty = int2fx(112);
1192 discarded_card_object->x = int2fx(240);
1193 discarded_card_object->y = int2fx(80);
1194 }
1195 else
1196 {
1197 if (discarded_card_object->y >= discarded_card_object->ty)
1198 {
1199 deck_push(discarded_card_object->card); // Put the card back into the deck
1200 card_object_destroy(&discarded_card_object);
1201
1202 play_sfx(
1203 SFX_CARD_DRAW,
1204 MM_BASE_PITCH_RATE + PITCH_STEP_UNDISCARD_SFX,
1206 );
1207 }
1208 }
1209
1210 // If there are no more discarded cards, stop shuffling
1211 if (s_discard_top == -1 && discarded_card_object == NULL)
1212 {
1213 // After HAND_SHUFFLING the round is over
1215 }
1216 }
1217}
1218
1219static inline void select_cards_in_played_hand(void)
1220{
1221 switch (get_hand_type()) // select the cards that apply to the hand type
1222 {
1223 case NONE:
1224 break;
1225 case HIGH_CARD:
1226 select_highcard_cards_in_played_hand();
1227 break;
1228 case PAIR:
1229 select_pair_cards_in_played_hand();
1230 break;
1231 case TWO_PAIR:
1232 select_two_pair_cards_in_played_hand();
1233 break;
1234 case THREE_OF_A_KIND:
1235 select_three_of_a_kind_cards_in_played_hand();
1236 break;
1237 case FOUR_OF_A_KIND:
1238 select_four_of_a_kind_cards_in_played_hand();
1239 break;
1240 case STRAIGHT:
1241 /* FALL THROUGH */
1242 case FLUSH:
1243 /* FALL THROUGH */
1244 case STRAIGHT_FLUSH:
1245 /* FALL THROUGH */
1246 case ROYAL_FLUSH:
1247 select_flush_and_straight_cards_in_played_hand();
1248 break;
1249 case FULL_HOUSE:
1250 /* FALL THROUGH */
1251 case FIVE_OF_A_KIND:
1252 /* FALL THROUGH */
1253 case FLUSH_HOUSE:
1254 /* FALL THROUGH */
1255 case FLUSH_FIVE: // Select all played cards in the hand
1256 select_all_five_cards_in_played_hand();
1257 break;
1258 }
1259}
1260
1261static inline void cards_in_hand_update_loop(void)
1262{
1263 int selected_card_idx = hand_sel_idx_to_card_idx(game_round_selection_grid.selection.x);
1264
1265 // TODO: Break this function up into smaller ones, Gods be good
1266 // Start from the end of the hand and work backwards because that's how Balatro does it
1267 CardObject** hand = get_hand_array();
1268
1269 for (int i = get_hand_top(); i >= 0; i--)
1270 {
1271 if (hand[i] != NULL)
1272 {
1273 FIXED hand_x = int2fx(HAND_START_POS.x);
1274 FIXED hand_y = int2fx(HAND_START_POS.y);
1275
1276 switch (get_hand_state())
1277 {
1278 case HAND_DRAW:
1279 hand_x = hand_x + (int2fx(i) - int2fx(get_hand_top()) / 2) *
1280 -HAND_SPACING_LUT[get_hand_top()];
1281 break;
1282 case HAND_SELECT:
1283 bool is_focused =
1284 (i == selected_card_idx &&
1285 game_round_selection_grid.selection.y == GAME_PLAYING_HAND_SEL_Y);
1286
1287 if (is_focused && !card_object_is_selected(hand[i]))
1288 {
1289 hand_y -= int2fx(CARD_FOCUSED_UNSEL_Y);
1290 }
1291 else if (!is_focused && card_object_is_selected(hand[i]))
1292 {
1293 hand_y -= int2fx(CARD_UNFOCUSED_SEL_Y);
1294 }
1295 else if (is_focused && card_object_is_selected(hand[i]))
1296 {
1297 hand_y -= int2fx(CARD_FOCUSED_SEL_Y);
1298 }
1299 if (i != selected_card_idx && hand[i]->y > hand_y)
1300 {
1301 hand[i]->y = hand_y;
1302 // Set target y to match y. Ensures target is updated even when vy becomes
1303 // 0, preventing immediate snap back.
1304 hand[i]->ty = hand_y;
1305 hand[i]->vy = 0;
1306 }
1307
1308 hand_x = hand_x + (int2fx(i) - int2fx(get_hand_top()) / 2) *
1309 -HAND_SPACING_LUT[get_hand_top()]; // TODO: Change this
1310 // later to reference a
1311 // 2D LUT of positions
1312 break;
1313 case HAND_SHUFFLING:
1314 /* FALL THROUGH */
1315 case HAND_DISCARD: // TODO: Add sound
1316 bool break_loop;
1317 card_in_hand_loop_handle_discard_and_shuffling(
1318 i,
1319 &hand_x,
1320 &hand_y,
1321 &break_loop
1322 );
1323 if (break_loop)
1324 break;
1325
1326 break;
1327 case HAND_PLAY:
1328 hand_x = hand_x + (int2fx(i) - int2fx(get_hand_top()) / 2) *
1329 -HAND_SPACING_LUT[get_hand_top()];
1330 hand_y += int2fx(24);
1331
1332 if (card_object_is_selected(hand[i]) && s_discarded_card == false &&
1333 g_game_vars.timer % FRAMES(10) == 0)
1334 {
1335 card_object_set_selected(hand[i], false);
1336 played_push(hand[i]);
1337 sprite_destroy(&hand[i]->sprite);
1338 hand[i] = NULL;
1340
1341 play_sfx(
1342 SFX_CARD_DRAW,
1343 MM_BASE_PITCH_RATE + s_cards_drawn * PITCH_STEP_DISCARD_SFX,
1345 );
1346
1349 s_cards_drawn++;
1350
1351 s_discarded_card = true;
1352 }
1353
1354 if (i == 0 && s_discarded_card == false && g_game_vars.timer % FRAMES(10) == 0)
1355 {
1356 set_hand_state(HAND_PLAYING);
1357 s_cards_drawn = 0;
1359 g_game_vars.timer = TM_ZERO;
1360 s_scored_card_index = get_played_size();
1361
1362 select_cards_in_played_hand();
1363 }
1364
1365 break;
1366 // Don't need to do anything here, just wait for the player to select cards
1367 case HAND_PLAYING:
1368 hand_x = hand_x + (int2fx(i) - int2fx(get_hand_top()) / 2) *
1369 -HAND_SPACING_LUT[get_hand_top()];
1370 hand_y += int2fx(24);
1371 break;
1372 }
1373
1374 hand[i]->tx = hand_x;
1375 hand[i]->ty = hand_y;
1376 }
1377 }
1378}
1379
1380static inline void game_round_ui_text_update(void)
1381{
1382 static int s_last_hand_size = 0;
1383 static int s_last_deck_size = 0;
1384
1385 if (s_last_hand_size != hand_nb_held_cards() || s_last_deck_size != deck_get_size())
1386 {
1387 switch (get_current_background())
1388 {
1389 case BG_CARD_SELECTING:
1390 // Hand size/max size
1391 tte_printf(
1392 "#{P:%d,%d; cx:0x%X000}%2d/%-2ld",
1393 HAND_SIZE_RECT_SELECT.left,
1394 HAND_SIZE_RECT_SELECT.top,
1395 TTE_WHITE_PB,
1397 g_game_vars.hand_size
1398 );
1399 break;
1400
1401 case BG_CARD_PLAYING:
1402 // Hand size/max size
1403 tte_printf(
1404 "#{P:%d,%d; cx:0x%X000}%2d/%-2ld",
1405 HAND_SIZE_RECT_PLAYING.left,
1406 HAND_SIZE_RECT_PLAYING.top,
1407 TTE_WHITE_PB,
1409 g_game_vars.hand_size
1410 );
1411 break;
1412
1413 default:
1414 break;
1415 }
1416
1417 // Deck size/max size
1418 display_deck_size_max();
1419
1420 s_last_hand_size = hand_nb_held_cards();
1421 s_last_deck_size = deck_get_size();
1422 }
1423}
1424
1426{
1427 u32 curr_score = u32_protected_mult(g_game_vars.chips, g_game_vars.mult);
1428 u32 required_score = blind_get_requirement(g_game_vars.current_blind, g_game_vars.ante);
1429 if (curr_score >= required_score && !s_score_flames_active)
1430 {
1431 // start flaming score
1432 s_score_flames_active = true;
1433 return;
1434 }
1435 if (curr_score < required_score && s_score_flames_active)
1436 {
1437 // stop flaming score and clear rect
1438 s_score_flames_active = false;
1439
1440 Rect reset_rect = SCORE_FLAME_RESET;
1441 main_bg_se_copy_rect(reset_rect, SCORE_FLAME_CHIPS_POS);
1442 reset_rect.left += SCORE_FLAME_FRAME_WIDTH;
1443 reset_rect.right += SCORE_FLAME_FRAME_WIDTH;
1444 main_bg_se_copy_rect(reset_rect, SCORE_FLAME_MULT_POS);
1445 }
1446}
1447
1448static inline void game_round_process_flaming_score(void)
1449{
1450 static u8 flame_score_frame = 0;
1451
1452 if (s_score_flames_active)
1453 {
1454 if (g_game_vars.timer % SCORE_FLAMES_ANIM_FREQ == 0)
1455 {
1456 Rect frame_rect = SCORE_FLAME_FRAMES_START;
1457 flame_score_frame = (flame_score_frame + 1) % NUM_SCORE_FLAMES_FRAMES;
1458
1459 // chips flame (blue)
1460 frame_rect.top += flame_score_frame;
1461 frame_rect.bottom += flame_score_frame;
1462 main_bg_se_copy_rect(frame_rect, SCORE_FLAME_CHIPS_POS);
1463
1464 // mult flame (red)
1465 frame_rect.left += SCORE_FLAME_FRAME_WIDTH;
1466 frame_rect.right += SCORE_FLAME_FRAME_WIDTH;
1467 main_bg_se_copy_rect(frame_rect, SCORE_FLAME_MULT_POS);
1468 }
1469 }
1470}
1471
1472/*******************************************************************************
1473 * CARD/JOKER SCORING LOGIC
1474 ******************************************************************************/
1475
1487 ListItr* starting_joker_itr,
1488 CardObject* card_object,
1489 enum JokerEvent joker_event
1490)
1491{
1492 JokerObject* joker;
1493
1494 while ((joker = list_itr_next(starting_joker_itr)))
1495 {
1496 if (joker_object_score(joker, card_object, joker_event))
1497 {
1498 return true;
1499 }
1500 }
1501 return false;
1502}
1503
1504static inline void play_starting_played_cards_update(int played_idx)
1505{
1506 // Protect against out of bounds read in `s_played_hand` array
1507 bool card_selected =
1508 (s_played_top < s_scored_card_index)
1509 ? false
1510 : card_object_is_selected(s_played_hand[s_played_top - s_scored_card_index]);
1511
1512 if (played_idx == s_played_top && (g_game_vars.timer % FRAMES(10) == 0 || !card_selected) &&
1513 g_game_vars.timer > FRAMES(40))
1514 {
1515 s_scored_card_index--;
1516
1517 if (s_scored_card_index == 0)
1518 {
1519 s_joker_scored_itr = list_itr_create(get_jokers_list());
1520 g_game_vars.timer = TM_ZERO;
1521 play_state = PLAY_BEFORE_SCORING;
1522 }
1523 }
1524
1525 s_played_hand[played_idx]->tx =
1526 int2fx(HAND_PLAY_POS.x) +
1527 (int2fx(s_played_top - played_idx) - int2fx(s_played_top) / 2) * -27;
1528 s_played_hand[played_idx]->ty = int2fx(HAND_PLAY_POS.y);
1529
1530 card_selected = card_object_is_selected(s_played_hand[played_idx]);
1531 if (card_selected && s_played_top - played_idx >= s_scored_card_index)
1532 {
1533 s_played_hand[played_idx]->ty -= int2fx(10);
1534 }
1535}
1536
1544static inline bool play_before_scoring_cards_update(void)
1545{
1546 // Activate Jokers with an effect just before the hand is scored
1547 if (check_and_score_joker_for_event(&s_joker_scored_itr, NULL, JOKER_EVENT_ON_HAND_PLAYED))
1548 {
1549 return true;
1550 }
1551
1552 play_state = PLAY_SCORING_CARDS;
1553 return false;
1554}
1555
1561static inline bool play_scoring_cards_update(void)
1562{
1563 if (g_game_vars.timer % FRAMES(30) == 0 && g_game_vars.timer > FRAMES(40))
1564 {
1565 // We are about to score played Cards.
1566 // Start from the current card index
1567 // and seek the next scoring card
1568 while (s_scored_card_index <= s_played_top &&
1569 !card_object_is_selected(s_played_hand[s_scored_card_index]))
1570 {
1571 s_scored_card_index++;
1572 }
1573
1574 // go to the next state if there are no cards left to score
1575 if (s_scored_card_index > s_played_top)
1576 {
1577 // reuse these variables for held cards
1578 s_joker_scored_itr = list_itr_create(get_jokers_list());
1579 s_scored_card_index = get_hand_top();
1580
1581 play_state = PLAY_SCORING_HELD_CARDS;
1582
1583 return false;
1584 }
1585
1586 tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
1587
1588 CardObject* scored_card_object = s_played_hand[s_scored_card_index];
1589
1590 if (card_object_is_selected(scored_card_object))
1591 {
1592 // Offset of 1 tile to keep the text on the card
1593 tte_set_pos(fx2int(scored_card_object->x) + TILE_SIZE, SCORED_CARD_TEXT_Y);
1594
1595 // Set text color to blue from background memory
1596 tte_set_special(TTE_BLUE_PB * TTE_SPECIAL_PB_MULT_OFFSET);
1597
1598 u8 card_value = card_get_value(scored_card_object->card);
1599
1600 // Write the score to a character buffer variable
1601 char score_buffer[INT_MAX_DIGITS + 2]; // for '+' and null terminator
1602 snprintf(score_buffer, sizeof(score_buffer), "+%hhu", card_value);
1603 tte_write(score_buffer);
1604
1605 card_object_shake(scored_card_object, SFX_CHIPS_CARD);
1606
1607 // Relocated card scoring logic here
1608 g_game_vars.chips = u32_protected_add(g_game_vars.chips, card_value);
1609 display_chips();
1610
1611 // Allow Joker scoring
1612 s_joker_scored_itr = list_itr_create(get_jokers_list());
1613 s_joker_card_scored_end_itr = list_itr_create(get_jokers_list());
1614 }
1615
1616 play_state = PLAY_SCORING_CARD_JOKERS;
1617 return true;
1618 }
1619
1620 return false;
1621}
1622
1630static inline bool play_scoring_card_jokers_update(void)
1631{
1632 if (g_game_vars.timer % FRAMES(30) == 0 && g_game_vars.timer > FRAMES(40))
1633 {
1634 tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
1635
1636 // since we sought the next scoring card index in the previous state,
1637 // scored_card_index is guaranteed to be a scoring card
1639 &s_joker_scored_itr,
1640 s_played_hand[s_scored_card_index],
1641 JOKER_EVENT_ON_CARD_SCORED
1642 ))
1643 {
1644 return true;
1645 }
1646
1647 // Trigger all Jokers that have an effect when a card finishes scoring
1648 // (e.g. retriggers) after activating all the other scored_card Jokers normally
1650 &s_joker_card_scored_end_itr,
1651 s_played_hand[s_scored_card_index],
1652 JOKER_EVENT_ON_CARD_SCORED_END
1653 ))
1654 {
1655 // If we just scored a retrigger, return early and go back to the
1656 // previous state score the same card again without incrementing
1657 // scored_card_index to score the current card again
1658 if (s_retrigger)
1659 {
1660 s_retrigger = false;
1661 play_state = PLAY_SCORING_CARDS;
1662 }
1663 return true;
1664 }
1665
1666 // increment index to start seeking the next scoring card from the next card
1667 s_scored_card_index++;
1668 play_state = PLAY_SCORING_CARDS;
1669 }
1670
1671 return false;
1672}
1673
1680static inline bool play_scoring_held_cards_update(int played_idx)
1681{
1682 if (played_idx == 0 && (g_game_vars.timer % FRAMES(30) == 0) && g_game_vars.timer > FRAMES(40))
1683 {
1684 tte_erase_rect_wrapper(HELD_CARDS_SCORES_RECT);
1685
1686 CardObject** hand = get_hand_array();
1687
1688 // Go through all held cards and see if they activate Jokers
1689 for (; s_scored_card_index >= 0; s_scored_card_index--)
1690 {
1692 &s_joker_scored_itr,
1693 hand[s_scored_card_index],
1694 JOKER_EVENT_ON_CARD_HELD
1695 ))
1696 {
1697 card_object_shake(hand[s_scored_card_index], SFX_CARD_SELECT);
1698 return true;
1699 }
1700 s_joker_scored_itr = list_itr_create(get_jokers_list());
1701 }
1702
1703 s_scored_card_index = 0;
1704 s_joker_round_end_itr = list_itr_create(get_jokers_list());
1705 play_state = PLAY_SCORING_INDEPENDENT_JOKERS;
1706 }
1707
1708 return false;
1709}
1710
1717static inline bool play_scoring_independent_jokers_update(int played_idx)
1718{
1719 if (played_idx == 0 && (g_game_vars.timer % FRAMES(30) == 0) && g_game_vars.timer > FRAMES(40))
1720 {
1721
1722 tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
1723
1724 if (check_and_score_joker_for_event(&s_joker_scored_itr, NULL, JOKER_EVENT_INDEPENDENT))
1725 {
1726 return true;
1727 }
1728
1729 // Reset the scored card index to past the top of the played stack
1730 s_scored_card_index = get_played_size();
1731
1732 play_state = PLAY_SCORING_HAND_SCORED_END;
1733 }
1734
1735 return false;
1736}
1737
1744static inline bool play_scoring_hand_scored_end_update(int played_idx)
1745{
1746 if (played_idx == 0 && (g_game_vars.timer % FRAMES(30) == 0) && g_game_vars.timer > FRAMES(40))
1747 {
1748
1749 tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
1750
1751 bool scored = check_and_score_joker_for_event(
1752 &s_joker_round_end_itr,
1753 NULL,
1754 JOKER_EVENT_ON_HAND_SCORED_END
1755 );
1756
1757 if (scored)
1758 {
1759 return true;
1760 }
1761
1762 g_game_vars.timer = TM_ZERO;
1763 play_state = PLAY_ENDING;
1764 }
1765
1766 return false;
1767}
1768
1775static inline void play_ending_played_cards_update(int played_idx)
1776{
1777 // Same protection against out of bounds access as `play_starting_played_cards_update`
1778 bool card_selected =
1779 (s_played_top < s_scored_card_index)
1780 ? false
1781 : card_object_is_selected(s_played_hand[s_played_top - s_scored_card_index]);
1782
1783 if (played_idx == s_played_top && (g_game_vars.timer % FRAMES(10) == 0 || !card_selected) &&
1784 g_game_vars.timer > FRAMES(40))
1785 {
1786 s_scored_card_index--;
1787
1788 /* SFX_CHIPS_ACCUM has been pitch shifted to perserve high frequencies in downsampling.
1789 * Now it needs to be pitch shifted back to the original frequency.
1790 */
1791 int static const CHIPS_ACCUM_SFX_PITCH_RATIO = 2;
1792
1793 if (s_scored_card_index == 0)
1794 {
1795 play_sfx(
1796 SFX_CHIPS_ACCUM,
1797 CHIPS_ACCUM_SFX_PITCH_RATIO * MM_BASE_PITCH_RATE,
1799 );
1800 g_game_vars.timer = TM_ZERO;
1801 play_state = PLAY_ENDED;
1802 }
1803 }
1804
1805 if (card_object_is_selected(s_played_hand[played_idx]) &&
1806 s_played_top - played_idx >= s_scored_card_index)
1807 {
1808 s_played_hand[played_idx]->ty = int2fx(HAND_PLAY_POS.y);
1809 }
1810}
1811
1820static bool play_ended_played_cards_update(int played_idx)
1821{
1822 if (!s_discarded_card && g_game_vars.timer > FRAMES(40))
1823 {
1824 // play the sound only once per card, when it is pushed off-screen to the right
1825 if (!s_sound_played)
1826 {
1827 play_sfx(
1828 SFX_CARD_DRAW,
1829 MM_BASE_PITCH_RATE + s_cards_drawn * PITCH_STEP_DISCARD_SFX,
1831 );
1832 s_sound_played = true;
1833 }
1834
1835 // card has exited the screen, now discard it and set it to NULL
1836 if (s_played_hand[played_idx]->x >= int2fx(CARD_DISCARD_PNT.x))
1837 {
1838 discard_push(s_played_hand[played_idx]->card); // Push the card to the discard pile
1839 card_object_destroy(&s_played_hand[played_idx]);
1840
1841 s_cards_drawn++; // This technically isn't drawing cards, I'm just reusing the variable
1842 s_sound_played = false; // Allow for the sound for the next card to be played
1843
1844 // we reached hand_top, all cards have been discarded
1845 if (played_idx == s_played_top)
1846 {
1847 if (game_round_is_over())
1848 {
1849 set_hand_state(HAND_SHUFFLING);
1850 }
1851 else
1852 {
1853 set_hand_state(HAND_DRAW);
1854 }
1855
1856 play_state = PLAY_STARTING;
1857 s_cards_drawn = 0;
1859 s_played_top = -1; // Reset the played stack
1860 s_scored_card_index = 0;
1861 s_joker_scored_itr = list_itr_create(get_jokers_list());
1862 g_game_vars.timer = TM_ZERO;
1863 }
1864
1865 return true; // return early to avoid accessing played[played_idx] == NULL
1866 }
1867
1868 // put target X position off screen to the right
1869 s_played_hand[played_idx]->tx = int2fx(CARD_DISCARD_PNT.x);
1870 s_discarded_card = true;
1871 }
1872
1873 return false;
1874}
1875
1876static inline void played_cards_update_loop(void)
1877{
1878 // So this one is a bit fucking weird because I have to work kinda backwards for everything
1879 // because of the order of the pushed cards from the hand to the play stack (also crazy that the
1880 // company that published Balatro is called "Playstack" and this is a play stack, but I digress)
1881 for (int played_idx = 0; played_idx <= s_played_top; played_idx++)
1882 {
1883 if (s_played_hand[played_idx] == NULL)
1884 {
1885 continue;
1886 }
1887
1888 if (card_object_get_sprite(s_played_hand[played_idx]) == NULL)
1889 {
1890 // Set the sprite for the played card object
1891 card_object_set_sprite(s_played_hand[played_idx], played_idx + MAX_HAND_SIZE);
1892 }
1893
1894 switch (play_state)
1895 {
1896 case PLAY_STARTING:
1897
1898 play_starting_played_cards_update(played_idx);
1899 break;
1900
1901 case PLAY_BEFORE_SCORING:
1902
1904 {
1905 return;
1906 }
1907 break;
1908
1909 case PLAY_SCORING_CARDS:
1910
1912 {
1913 return;
1914 }
1915 break;
1916
1917 case PLAY_SCORING_CARD_JOKERS:
1918
1920 {
1921 return;
1922 }
1923 break;
1924
1925 case PLAY_SCORING_HELD_CARDS:
1926
1927 if (play_scoring_held_cards_update(played_idx))
1928 {
1929 return;
1930 }
1931 break;
1932
1933 case PLAY_SCORING_INDEPENDENT_JOKERS:
1934
1936 {
1937 return;
1938 }
1939 break;
1940
1941 case PLAY_SCORING_HAND_SCORED_END:
1942
1944 {
1945 return;
1946 }
1947 break;
1948
1949 case PLAY_ENDING:
1950
1952 break;
1953
1954 case PLAY_ENDED:
1955
1956 if (play_ended_played_cards_update(played_idx))
1957 {
1958 // we continue here instead of returning for performance
1959 // to instantly go to the next card to discard at played_idx+1,
1960 // instead of starting over from index 0 and going up
1961 // to that card again
1962 continue;
1963 }
1964 break;
1965 }
1966
1967 s_played_hand[played_idx]->tscale = FIX_ONE;
1968 }
1969}
1970
1971/*******************************************************************************
1972 * FOUND STATE FUNCTIONS
1973 ******************************************************************************/
1974
1976{
1977 s_joker_scored_itr = list_itr_create(get_jokers_list());
1978
1979 set_hand_state(HAND_DRAW);
1981 s_cards_drawn = 0;
1982
1983 sprite_destroy(&g_game_vars.playing_blind_token);
1984 g_game_vars.playing_blind_token = blind_token_new(
1985 g_game_vars.current_blind,
1986 CUR_BLIND_TOKEN_POS.x,
1987 CUR_BLIND_TOKEN_POS.y,
1988 PLAYING_BLIND_TOKEN_LAYER
1989 ); // Create the blind token sprite at the top left corner
1990 // TODO: Hide blind token and display it after sliding blind rect animation
1991 // if (g_game_vars.playing_blind_token != NULL)
1992 //{
1993 // sprite_hide(g_game_vars.playing_blind_token); // Hide the blind token sprite for now
1994 //}
1995 sprite_destroy(&g_game_vars.round_end_blind_token);
1996 g_game_vars.round_end_blind_token = blind_token_new(
1997 g_game_vars.current_blind,
1998 81,
1999 86,
2000 ROUND_END_BLIND_TOKEN_LAYER
2001 ); // Create the blind token sprite for round end
2002
2003 if (g_game_vars.round_end_blind_token != NULL)
2004 {
2005 sprite_hide(g_game_vars.round_end_blind_token); // Hide the blind token sprite for now
2006 }
2007
2008 Rect blind_req_text_rect = BLIND_REQ_TEXT_RECT;
2009 u32 blind_requirement = blind_get_requirement(g_game_vars.current_blind, g_game_vars.ante);
2010
2011 char blind_req_str_buff[UINT_MAX_DIGITS + 1];
2012
2014 blind_requirement,
2015 rect_width(&BLIND_REQ_TEXT_RECT) / TTE_CHAR_SIZE,
2016 blind_req_str_buff
2017 );
2018
2019 // Update text rect for right alignment AFTER shortening the number
2020 update_text_rect_to_right_align_str(&blind_req_text_rect, blind_req_str_buff, OVERFLOW_RIGHT);
2021
2022 tte_printf(
2023 "#{P:%d,%d; cx:0x%X000}%s",
2024 blind_req_text_rect.left,
2025 blind_req_text_rect.top,
2026 TTE_RED_PB,
2027 blind_req_str_buff
2028 );
2029 tte_printf(
2030 "#{P:%d,%d; cx:0x%X000}$%d",
2031 BLIND_REWARD_RECT.left,
2032 BLIND_REWARD_RECT.top,
2033 TTE_YELLOW_PB,
2034 blind_get_reward(g_game_vars.current_blind)
2035 ); // Blind reward
2036
2037 deck_shuffle(); // Shuffle the deck at the start of the round
2038
2039 /* Note that since cards_in_hand_update_loop() handles card highlight there's no need
2040 * to call a selection changed callback to highlight the initial card, this wouldn't work
2041 * otherwise or for the buttons.
2042 */
2043 game_round_selection_grid.selection = GAME_PLAYING_INIT_SEL;
2044}
2045
2047{
2048 // Background logic (thissss might be moved to the card'ssss logic later. I'm a sssssnake)
2049 if (get_hand_state() == HAND_DRAW || get_hand_state() == HAND_DISCARD ||
2050 get_hand_state() == HAND_SELECT)
2051 {
2052 change_background(BG_CARD_SELECTING, false);
2053 }
2054 else if (get_hand_state() != HAND_SHUFFLING)
2055 {
2056 change_background(BG_CARD_PLAYING, false);
2057 }
2058
2059 game_round_process_input_and_state();
2060
2061 // Card logic
2062
2063 game_round_process_card_draw();
2064
2065 game_round_discarded_cards_loop();
2066
2067 s_discarded_card = false;
2068
2069 cards_in_hand_update_loop();
2070 played_cards_update_loop();
2071
2072 game_round_ui_text_update();
2073
2074 // animate score flames if we exceed the score requirement
2075 game_round_process_flaming_score();
2076}
Utilities for using maxmod to play sound effects.
#define MM_BASE_PITCH_RATE
The default pitch rate for the sound effect played.
Definition audio_utils.h:37
void play_sfx(mm_word id, mm_word rate, mm_byte volume)
Play a sound effect, wrapper for mmEffectEx() See https://maxmod.org/ref/functions/mm_sound_effect....
#define SFX_DEFAULT_VOLUME
Default volume for sound effects.
Definition audio_utils.h:43
u16 blind_get_color(enum BlindType type, enum BlindColorIndex index)
Get the color associated with a given Blind.
Definition blind.c:218
u32 blind_get_requirement(enum BlindType type, int ante)
Get the score required to beat a certain blind (either the Small, Big, or any Boss/Showdown blind) at...
Definition blind.c:106
Sprite * blind_token_new(enum BlindType type, int x, int y, enum BlindTokenLayers sprite_index)
Create a new BlindToken sprite.
Definition blind.c:281
void set_blind_beaten(enum BlindType type)
Remove the given Blind from the corresponding List so that we don't roll it again in the future.
Definition blind.c:180
int blind_get_reward(enum BlindType type)
Get the amount of money gained from beating a certain Blind.
Definition blind.c:115
A button structure containing the common button functionalities Including highlight and pressing func...
void button_press(Button *button)
Execute a button press, by calling the button's on_pressed function. This checks the button's can_be_...
Definition button.c:18
void button_set_highlight(Button *button, bool highlight)
Set the button highlight on or off.
Definition button.c:8
void change_background(enum BackgroundId id, bool force_redraw)
Definition common_ui.c:35
Graphic utility functions.
void main_bg_se_move_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction)
Moves a rect in the main background vertically in direction by a single tile.
void main_bg_se_copy_rect(Rect se_rect, BG_POINT dest_pos)
Copies a rect in the main background from se_rect to the position (x, y).
void toggle_windows(bool win0, bool win1)
Toggles the visibility of the window layers.
void update_text_rect_to_right_align_str(Rect *rect, const char *str, enum OverflowDir overflow_direction)
Changes rect->left so it fits the digits of num exactly when right aligned to rect->right....
#define TILE_SIZE
Tile size in pixels, both height and width as tiles are square.
#define TTE_CHAR_SIZE
By default TTE characters occupy a single tile.
void tte_erase_rect_wrapper(Rect rect)
A wrapper for tte_erase_rect that would use the rect struct.
INLINE int rect_width(const Rect *rect)
Get the width of a rectangle.
Functions relative to manipulating and analyzing the contents of the Hand, a.k.a. the Cards we hold a...
void set_hand_state(enum HandState)
Set the hand state. Primarily used by the GAME_PLAYING game state.
Definition hand.c:95
void hand_set_nb_selected_cards(int new_selections)
Set the current number of selected Cards.
Definition hand.c:125
int find_flush_in_played_cards(CardObject **played, int top, int min_len, bool *out_selection)
Finds the best flush (set of cards with the same suit) in the given array of played cards.
Definition hand.c:544
enum HandType get_hand_type(void)
Get the current hand type.
Definition hand.c:130
int get_hand_top(void)
Get the position in hand array of the last card obtained.
Definition hand.c:105
enum HandState get_hand_state(void)
Get the hand state.
Definition hand.c:90
void swap_cards_in_hand(int idx_a, int idx_b)
Swaps the order of two cards in hand.
Definition hand.c:174
int find_straight_in_played_cards(CardObject **played, int top, int min_len, bool *out_selection)
Finds the best straight in the given array of played cards.
Definition hand.c:587
CardObject ** get_hand_array(void)
Get the hand array of Cards currently held in hand.
Definition hand.c:100
int hand_get_nb_selected_cards(void)
Get the current number of selected Cards.
Definition hand.c:120
void select_paired_cards_in_hand(CardObject **played, int top, bool *selection)
This is used for the special case in "Four Fingers" where you can add a pair into a straight (e....
Definition hand.c:731
void reorder_card_sprites_layers(void)
Destroy the sprites of the Cards held in hand and recreate then in the same order as the Cards in the...
Definition hand.c:244
void set_hand_top(int new_hand_top)
Set the position in hand array of the last card obtained.
Definition hand.c:110
void hand_select_card(int index)
Set the card at a given index in Hand as selected.
Definition hand.c:303
void hand_change_sort(bool to_sort_by_suit)
Switch to the given sort method. Can sort playing cards in two ways: by rank and suit....
Definition hand.c:294
void compute_hand_value_info(void)
Determine the HandType and ContainedHandTypes of the currently selected Cards, then print the Hand's ...
Definition hand.c:156
void sort_cards(void)
Sort the hand array according to the selected method to do that.
Definition hand.c:280
void hand_deselect_all_cards(void)
Deselect all cards in hand.
Definition hand.c:324
int hand_nb_held_cards(void)
Get the current number of Cards in hand.
Definition hand.c:115
Functions relative to the handling of Jokers.
All the functions used specifically to interact with Jokers. This includes browsing,...
bool jokers_sel_row_on_selection_changed(SelectionGrid *selection_grid, int row_idx, const Selection *prev_selection, const Selection *new_selection)
Handle directional inputs on the Jokers' row at the top of the game screen.
Definition joker_row.c:16
void jokers_sel_row_on_key_transit(SelectionGrid *selection_grid, Selection *selection)
Handle button inputs on the Jokers' row at the top of the game screen.
Definition joker_row.c:100
int jokers_sel_row_get_size(void)
Returns the size of the Jokers' row at the top of the game screen. Is equal to the number of Jokers o...
Definition joker_row.c:11
Header of shared rects and points needed for ui.
A doubly-linked list.
void * list_itr_next(ListItr *itr)
Definition list.c:281
ListItr list_itr_create(List *list)
Definition list.c:257
u32 rng_get_u32(enum RngSequence key)
Get the next "randomly" generated number in the sequence corresponding to the given type.
Definition random.c:56
static bool game_round_is_over(void)
Determines if the round is over, be it because we lost or won the round.
Definition round.c:1049
static void card_draw(void)
Draw the next card at the top of the Deck and play a little Sprite animation to position it in our ha...
Definition round.c:1125
static void discard_push(Card *card)
Push a new card to discard at the end of the associated array.
Definition round.c:277
static void game_round_handle_round_over(void)
Evaluates if we have won or lost when we can no longer play so that we land on the correct Game Over ...
Definition round.c:723
static bool play_scoring_independent_jokers_update(int played_idx)
Score Jokers normally for event JOKER_EVENT_INDEPENDENT.
Definition round.c:1717
static void game_round_play_hand_on_pressed(void)
"Play" button implementation.
Definition round.c:510
int get_scored_card_index(void)
Get the index of the card that is currently being scored.
Definition round.c:296
static void played_push(CardObject *card_object)
Push a new card to play at the end of the associated array.
Definition round.c:258
int get_discard_top(void)
Get the index of the last discarded card.
Definition round.c:265
static bool play_before_scoring_cards_update(void)
Returns true if the Jokers scoring loop has returned early for event JOKER_EVENT_ON_HAND_PLAYED.
Definition round.c:1544
int get_played_size(void)
Get the number of cards played.
Definition round.c:248
void set_retrigger(bool new_retrigger)
Set whether the current card should get triggered again.
Definition round.c:301
static void game_round_sort_by_suit_on_pressed(void)
"Suit" sorting button implementation.
Definition round.c:486
static bool check_and_score_joker_for_event(ListItr *starting_joker_itr, CardObject *card_object, enum JokerEvent joker_event)
Iterate over the Jokers List until we encounter one that scores for the specified event.
Definition round.c:1486
void game_round_on_init(void)
Round state initialization.
Definition round.c:1975
static void play_ending_played_cards_update(int played_idx)
This is the reverse of PLAY_STARTING. The cards get reset back to their neutral position sequentially...
Definition round.c:1775
static void game_round_execute_discard(void)
Triggers the discard of the currently selected cards in hand.
Definition round.c:450
static void game_round_execute_play_hand(void)
Triggers the playing of the currently selected cards in hand.
Definition round.c:494
void game_round_on_update(void)
Round state update.
Definition round.c:2046
static Card * discard_pop()
Remove and recover the last discarded card.
Definition round.c:289
void toggle_flaming_score(void)
Checks whether the score that would result from the current Chips and Mult exceeds the current Blind'...
Definition round.c:1425
static bool can_play_hand(void)
Determines whether the "Play" button can be pressed or not.
Definition round.c:440
static bool play_scoring_card_jokers_update(void)
Activate Jokers for event JOKER_EVENT_ON_CARD_SCORED_END for the previous scored card,...
Definition round.c:1630
void game_round_change_background_playing(void)
Change to the round card playing background.
Definition round.c:400
static bool can_discard_hand(void)
Determines whether the "Discard" button can be pressed or not.
Definition round.c:427
static void game_round_discard_on_pressed(void)
"Discard" button implementation.
Definition round.c:464
static bool play_scoring_held_cards_update(int played_idx)
Activate Jokers for event JOKER_EVENT_ON_CARD_HELD for the previous scored card, if any.
Definition round.c:1680
int get_played_top(void)
Get the index of the last played card.
Definition round.c:243
static int hand_sel_idx_to_card_idx(int selection_index)
Converts a selection index from the selection grid into a card index within the hand array.
Definition round.c:706
static bool play_scoring_hand_scored_end_update(int played_idx)
Trigger all Jokers for event JOKER_EVENT_ON_HAND_SCORED_END once they are done scoring.
Definition round.c:1744
static GBAL_UNUSED void get_played_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
Outputs the distribution of ranks and suits in the played stack.
Definition round.c:314
static void game_round_sort_by_rank_on_pressed(void)
"Rank" sorting button implementation.
Definition round.c:478
void game_round_change_background_selecting(void)
Change to the round card selection background.
Definition round.c:338
static bool play_scoring_cards_update(void)
Score all played cards in order, then Jokers after each one.
Definition round.c:1561
static bool play_ended_played_cards_update(int played_idx)
Returns true if the card at index played_idx has been discarded. Basically a copy of HAND_DISCARD.
Definition round.c:1820
API relative to the Rounds we play.
An implementation for a selection grid that handles directional selection.
void selection_grid_move_selection_vert(SelectionGrid *selection_grid, int direction_tribool)
Moves the selection vertically within the selection grid.
void selection_grid_process_input(SelectionGrid *selection_grid)
Processes user input for the selection grid.
void sprite_object_reset_transform(SpriteObject *sprite_object)
Reset SpriteObject's transform back to default values.
Definition sprite.c:227
void sprite_destroy(Sprite **sprite)
Destroy Sprite.
Definition sprite.c:93
void sprite_hide(Sprite *sprite)
Hides the sprite by manipulating ATTR0_HIDE in OAM.
Definition sprite.c:171
A button representation.
Definition button.h:31
Definition card.h:50
An iterator into a list.
Definition list.h:91
The core selection grid struct, represents the grid itself and its state.
A sprite object is a sprite that is focusable and movable in animation.
Definition sprite.h:61
Timer constants.
Utilities relating around number string representation and protected arithmatic helper functions.
uint32_t u32_protected_add(uint32_t a, uint32_t b)
Avoid overflow when adding two u32 integers.
Definition util.c:175
#define NUM_ELEM_IN_ARR(arr)
Get the number of elements in an array.
Definition util.h:43
void truncate_uint_to_suffixed_str(uint32_t num, int num_req_chars, char out_str_buff[UINT_MAX_DIGITS+1])
Truncate an unsigned number into a suffixed string representation e.g. 12000 -> "12K" The least signi...
Definition util.c:116
#define GBAL_UNUSED
A friendly wrapper around the not so friendly looking attribute syntax for ((unused))
Definition util.h:19
uint32_t u32_protected_mult(uint32_t a, uint32_t b)
Avoid overflow when multiplying two u32 integers.
Definition util.c:185