From 02799ddc54fec6a82e4806727e61c4afdafb91d9 Mon Sep 17 00:00:00 2001 From: Geralt <84459734+MathisMartin31@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:22:48 +0100 Subject: [PATCH] Optimize "whole hand" conditions checks for Joker effects (#213) * Introduce a ContainedHandTypes type * Separate computing of hand_type and _contained_hand_types variables --------- Co-authored-by: MathisMartin31 --- include/game.h | 33 +++++- source/game.c | 248 ++++++++++++++++++++++++----------------- source/joker_effects.c | 94 +++------------- 3 files changed, 193 insertions(+), 182 deletions(-) diff --git a/include/game.h b/include/game.h index 8f05534..859152a 100644 --- a/include/game.h +++ b/include/game.h @@ -84,10 +84,10 @@ enum HandType PAIR, TWO_PAIR, THREE_OF_A_KIND, - FOUR_OF_A_KIND, STRAIGHT, FLUSH, FULL_HOUSE, + FOUR_OF_A_KIND, STRAIGHT_FLUSH, ROYAL_FLUSH, FIVE_OF_A_KIND, @@ -95,6 +95,34 @@ enum HandType FLUSH_FIVE }; +// clang-format off +// Store all contained hands to optimize "whole hand condition" Jokers +typedef struct ContainedHandTypes +{ + union + { + struct + { + u16 HIGH_CARD : 1; + u16 PAIR : 1; + u16 TWO_PAIR : 1; + u16 THREE_OF_A_KIND : 1; + u16 STRAIGHT : 1; + u16 FLUSH : 1; + u16 FULL_HOUSE : 1; + u16 FOUR_OF_A_KIND : 1; + u16 STRAIGHT_FLUSH : 1; + u16 ROYAL_FLUSH : 1; + u16 FIVE_OF_A_KIND : 1; + u16 FLUSH_HOUSE : 1; + u16 FLUSH_FIVE : 1; + u16 : 3; + }; + u16 value; + }; +} ContainedHandTypes; +// clang-format on + typedef struct { int substate; @@ -119,6 +147,9 @@ bool card_is_face(Card* card); List* get_jokers_list(void); List* get_expired_jokers_list(void); +ContainedHandTypes* get_contained_hands(void); +enum HandType* get_hand_type(void); + int get_deck_top(void); int get_num_discards_remaining(void); int get_num_hands_remaining(void); diff --git a/source/game.c b/source/game.c index a8fbb8e..e34818c 100644 --- a/source/game.c +++ b/source/game.c @@ -496,10 +496,10 @@ static const HandValues hand_base_values[] = { {.chips = 10, .mult = 2, .display_name = "PAIR" }, // PAIR {.chips = 20, .mult = 2, .display_name = "2 PAIR" }, // TWO_PAIR {.chips = 30, .mult = 3, .display_name = "3 OAK" }, // THREE_OF_A_KIND - {.chips = 60, .mult = 7, .display_name = "4 OAK" }, // FOUR_OF_A_KIND {.chips = 30, .mult = 4, .display_name = "STRT" }, // STRAIGHT {.chips = 35, .mult = 4, .display_name = "FLUSH" }, // FLUSH {.chips = 40, .mult = 4, .display_name = "FULL H" }, // FULL_HOUSE + {.chips = 60, .mult = 7, .display_name = "4 OAK" }, // FOUR_OF_A_KIND {.chips = 100, .mult = 8, .display_name = "STRT F" }, // STRAIGHT_FLUSH {.chips = 100, .mult = 8, .display_name = "ROYAL F"}, // ROYAL_FLUSH {.chips = 120, .mult = 12, .display_name = "5 OAK" }, // FIVE_OF_A_KIND @@ -540,6 +540,7 @@ static enum HandState hand_state = HAND_DRAW; static enum PlayState play_state = PLAY_STARTING; static enum HandType hand_type = NONE; +static ContainedHandTypes _contained_hands = {0}; static CardObject* main_menu_ace = NULL; @@ -921,15 +922,6 @@ bool is_joker_owned(int joker_id) return false; } -bool card_is_face(Card* card) -{ - // Card is a face card, or Pareidolia is present - return ( - card->rank == JACK || card->rank == QUEEN || card->rank == KING || - is_joker_owned(PAREIDOLIA_JOKER_ID) - ); -} - List* get_jokers_list(void) { return &_owned_jokers_list; @@ -1254,6 +1246,148 @@ static void sort_cards(void) reorder_card_sprites_layers(); } +static ContainedHandTypes compute_contained_hand_types(void) +{ + ContainedHandTypes hand_types = {0}; + + // Idk if this is how Balatro does it but this is how I'm doing it + if (hand_selections == 0 || hand_state == HAND_DISCARD) + { + return hand_types; + } + + hand_types.HIGH_CARD = 1; + + u8 suits[NUM_SUITS]; + u8 ranks[NUM_RANKS]; + get_hand_distribution(ranks, suits); + + // The following can be optimized better but not sure how much it matters + u8 n_of_a_kind = hand_contains_n_of_a_kind(ranks); + + // Pair and 2 Pair + if (n_of_a_kind >= 2) + { + hand_types.PAIR = 1; + + if (hand_contains_two_pair(ranks)) + { + hand_types.TWO_PAIR = 1; + } + } + + // 3 OAK + if (n_of_a_kind >= 3) + { + hand_types.THREE_OF_A_KIND = 1; + } + + // Straight + if (hand_contains_straight(ranks)) + { + hand_types.STRAIGHT = 1; + } + + // Flush + if (hand_contains_flush(suits)) + { + hand_types.FLUSH = 1; + } + + // Full House + if (n_of_a_kind >= 3 && hand_contains_full_house(ranks)) + { + hand_types.FULL_HOUSE = 1; + } + + // 4 OAK + if (n_of_a_kind >= 4) + { + hand_types.FOUR_OF_A_KIND = 1; + } + + // Straight Flush + if (hand_types.STRAIGHT && hand_types.FLUSH) + { + hand_types.STRAIGHT_FLUSH = 1; + } + + // Royal Flush + if (hand_types.STRAIGHT_FLUSH) + { + if (ranks[TEN] && ranks[JACK] && ranks[QUEEN] && ranks[KING] && ranks[ACE]) + { + hand_types.ROYAL_FLUSH = 1; + } + } + + // 5 OAK + if (n_of_a_kind >= 5) + { + hand_types.FIVE_OF_A_KIND = 1; + } + + // Flush House and Five + if (hand_types.FLUSH) + { + if (hand_types.FULL_HOUSE) + { + hand_types.FLUSH_HOUSE = 1; + } + + if (hand_types.FIVE_OF_A_KIND) + { + hand_types.FLUSH_FIVE = 1; + } + } + + return hand_types; +} + +ContainedHandTypes* get_contained_hands(void) +{ + return &_contained_hands; +} + +enum HandType compute_hand_type(struct ContainedHandTypes contained_types) +{ + enum HandType ret; + + // test each pit see if it's set to 1, and return the first one + for (ret = FLUSH_FIVE; ret > NONE; ret--) + { + // Shift the bit we want to check to the front and mask it with 1 to keep only that + // Since the ContainedHandTypes is ordered the same way as the HandType enum, we + // can shift right by ret-1 to have the bit we want at the front + if ((contained_types.value >> (ret - 1)) & 0x1) + { + break; + } + } + + // If we broke early, ret contains the value of the HandType enum corresponding to + // the position of the highest bit set to 1 in contained_types.value, which is the + // most powerful poker hand contained in the current Hand + // If not, then it contains NONE, which is what we're supposed to return when there + // are no Hands contained in what we played + return ret; +} + +enum HandType* get_hand_type(void) +{ + return &hand_type; +} + +// Returns true if the card is *considered* a face card +bool card_is_face(Card* card) +{ + // Card is a face card, or Pareidolia is present + return ( + card->rank == JACK || card->rank == QUEEN || card->rank == KING || + is_joker_owned(PAREIDOLIA_JOKER_ID) + ); +} + /* Copies the appropriate item into the top left panel (blind/shop icon) * from where it was put outside the screenview */ @@ -1686,97 +1820,6 @@ static void display_discards(int value) ); } -static inline enum HandType hand_get_type(void) -{ - enum HandType res_hand_type = NONE; - - // Idk if this is how Balatro does it but this is how I'm doing it - if (hand_selections == 0 || hand_state == HAND_DISCARD) - { - res_hand_type = NONE; - return res_hand_type; - } - - res_hand_type = HIGH_CARD; - - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_hand_distribution(ranks, suits); - - // Check for flush - if (hand_contains_flush(suits)) - res_hand_type = FLUSH; - - // Check for straight - if (hand_contains_straight(ranks)) - { - if (res_hand_type == FLUSH) - res_hand_type = STRAIGHT_FLUSH; - else - res_hand_type = STRAIGHT; - } - - // The following can be optimized better but not sure how much it matters - u8 n_of_a_kind = hand_contains_n_of_a_kind(ranks); - - if (n_of_a_kind >= 5) - { - if (res_hand_type == FLUSH) - { - return FLUSH_FIVE; - } - return FIVE_OF_A_KIND; - } - - // Check for royal flush vs regular straight flush - if (res_hand_type == STRAIGHT_FLUSH) - { - if (ranks[TEN] && ranks[JACK] && ranks[QUEEN] && ranks[KING] && ranks[ACE]) - return ROYAL_FLUSH; - return STRAIGHT_FLUSH; - } - - if (n_of_a_kind == 4) - { - return FOUR_OF_A_KIND; - } - - if (n_of_a_kind == 3 && hand_contains_full_house(ranks)) - { - return FULL_HOUSE; - } - - // Flush and Straight are more valuable than the remaining hand types, so return them now - if (res_hand_type == FLUSH) - { - if (n_of_a_kind >= 5) - { - return FLUSH_HOUSE; - } - return FLUSH; - } - if (res_hand_type == STRAIGHT) - { - return STRAIGHT; - } - - if (n_of_a_kind == 3) - { - return THREE_OF_A_KIND; - } - - if (n_of_a_kind == 2) - { - if (hand_contains_two_pair(ranks)) - { - return TWO_PAIR; - } - return PAIR; - } - - return res_hand_type; // should be HIGH_CARD -} - static void print_hand_type(const char* hand_type_str) { if (hand_type_str == NULL) @@ -1793,7 +1836,8 @@ static void print_hand_type(const char* hand_type_str) static void set_hand(void) { tte_erase_rect_wrapper(HAND_TYPE_RECT); - hand_type = hand_get_type(); + _contained_hands = compute_contained_hand_types(); + hand_type = compute_hand_type(_contained_hands); HandValues hand = hand_base_values[hand_type]; diff --git a/source/joker_effects.c b/source/joker_effects.c index 485ce4e..9a0340a 100644 --- a/source/joker_effects.c +++ b/source/joker_effects.c @@ -516,12 +516,8 @@ static u32 jolly_joker_effect( SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event) u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - // This is really inefficient but the only way at the moment to check for whole-hand conditions - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - if (hand_contains_n_of_a_kind(ranks) >= 2) + if (get_contained_hands()->PAIR) { *joker_effect = &shared_joker_effect; @@ -543,12 +539,7 @@ static u32 zany_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - // This is really inefficient but the only way at the moment to check for whole-hand conditions - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_n_of_a_kind(ranks) >= 3) + if (get_contained_hands()->THREE_OF_A_KIND) { *joker_effect = &shared_joker_effect; @@ -570,11 +561,7 @@ static u32 mad_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_two_pair(ranks)) + if (get_contained_hands()->TWO_PAIR) { *joker_effect = &shared_joker_effect; @@ -596,11 +583,7 @@ static u32 crazy_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_straight(ranks)) + if (get_contained_hands()->STRAIGHT) { *joker_effect = &shared_joker_effect; @@ -622,11 +605,7 @@ static u32 droll_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_flush(suits)) + if (get_contained_hands()->FLUSH) { *joker_effect = &shared_joker_effect; @@ -648,11 +627,7 @@ static u32 sly_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_n_of_a_kind(ranks) >= 2) + if (get_contained_hands()->PAIR) { *joker_effect = &shared_joker_effect; @@ -674,11 +649,7 @@ static u32 wily_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_n_of_a_kind(ranks) >= 3) + if (get_contained_hands()->THREE_OF_A_KIND) { *joker_effect = &shared_joker_effect; @@ -700,11 +671,7 @@ static u32 clever_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_two_pair(ranks)) + if (get_contained_hands()->TWO_PAIR) { *joker_effect = &shared_joker_effect; @@ -726,11 +693,7 @@ static u32 devious_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_straight(ranks)) + if (get_contained_hands()->STRAIGHT) { *joker_effect = &shared_joker_effect; @@ -752,11 +715,7 @@ static u32 crafty_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_flush(suits)) + if (get_contained_hands()->FLUSH) { *joker_effect = &shared_joker_effect; @@ -1314,12 +1273,7 @@ static u32 the_duo_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - // This is really inefficient but the only way at the moment to check for whole-hand conditions - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_n_of_a_kind(ranks) >= 2) + if (get_contained_hands()->PAIR) { *joker_effect = &shared_joker_effect; @@ -1341,12 +1295,7 @@ static u32 the_trio_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - // This is really inefficient but the only way at the moment to check for whole-hand conditions - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_n_of_a_kind(ranks) >= 3) + if (get_contained_hands()->THREE_OF_A_KIND) { *joker_effect = &shared_joker_effect; @@ -1368,12 +1317,7 @@ static u32 the_family_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - // This is really inefficient but the only way at the moment to check for whole-hand conditions - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_n_of_a_kind(ranks) >= 4) + if (get_contained_hands()->FOUR_OF_A_KIND) { *joker_effect = &shared_joker_effect; @@ -1395,11 +1339,7 @@ static u32 the_order_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_straight(ranks)) + if (get_contained_hands()->STRAIGHT) { *joker_effect = &shared_joker_effect; @@ -1421,11 +1361,7 @@ static u32 the_tribe_joker_effect( u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE; - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_played_distribution(ranks, suits); - - if (hand_contains_flush(suits)) + if (get_contained_hands()->FLUSH) { *joker_effect = &shared_joker_effect;