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 <mathis.martin31@gmail.com>
This commit is contained in:
Geralt
2026-01-22 13:22:48 +01:00
committed by GitHub
parent 8e1867d235
commit 02799ddc54
3 changed files with 193 additions and 182 deletions
+32 -1
View File
@@ -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);
+146 -102
View File
@@ -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];
+15 -79
View File
@@ -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;