Adds Shortcut and Four Fingers (#95)
* adds shortcut joker * adds Four Fingers joker, and combination support for Four Fingers + Shortcut --------- Co-authored-by: MeirGavish <meir.gavish@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
MeirGavish
Copilot
parent
eb815b7820
commit
03273de829
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -22,3 +22,4 @@ DEF_JOKER_GFX(20)
|
|||||||
DEF_JOKER_GFX(21)
|
DEF_JOKER_GFX(21)
|
||||||
DEF_JOKER_GFX(22)
|
DEF_JOKER_GFX(22)
|
||||||
DEF_JOKER_GFX(23)
|
DEF_JOKER_GFX(23)
|
||||||
|
DEF_JOKER_GFX(24)
|
||||||
@@ -118,4 +118,8 @@ int get_money(void);
|
|||||||
int get_game_speed(void);
|
int get_game_speed(void);
|
||||||
void set_game_speed(int new_game_speed);
|
void set_game_speed(int new_game_speed);
|
||||||
|
|
||||||
|
// joker specific functions
|
||||||
|
bool is_shortcut_joker_active(void);
|
||||||
|
int get_straight_and_flush_size(void);
|
||||||
|
|
||||||
#endif // GAME_H
|
#endif // GAME_H
|
||||||
@@ -13,4 +13,8 @@ bool hand_contains_full_house(u8 *ranks);
|
|||||||
bool hand_contains_straight(u8 *ranks);
|
bool hand_contains_straight(u8 *ranks);
|
||||||
bool hand_contains_flush(u8 *suits);
|
bool hand_contains_flush(u8 *suits);
|
||||||
|
|
||||||
|
int find_flush_in_played_cards(CardObject** played, int top, int min_len, bool* out_selection);
|
||||||
|
int find_straight_in_played_cards(CardObject** played, int top, bool shortcut_active, int min_len, bool* out_selection);
|
||||||
|
void select_paired_cards_in_hand(CardObject** played, int top, bool* selection);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -65,6 +65,8 @@ enum JokerEvent
|
|||||||
#define JOKER_STENCIL_ID 16
|
#define JOKER_STENCIL_ID 16
|
||||||
#define PAREIDOLIA_JOKER_ID 30
|
#define PAREIDOLIA_JOKER_ID 30
|
||||||
#define JOKER_BRAINSTORM_ID 40
|
#define JOKER_BRAINSTORM_ID 40
|
||||||
|
#define SHORTCUT_JOKER_ID 26
|
||||||
|
#define FOUR_FINGERS_JOKER_ID 48
|
||||||
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
|
|||||||
@@ -25,5 +25,6 @@
|
|||||||
#include "joker_gfx21.h"
|
#include "joker_gfx21.h"
|
||||||
#include "joker_gfx22.h"
|
#include "joker_gfx22.h"
|
||||||
#include "joker_gfx23.h"
|
#include "joker_gfx23.h"
|
||||||
|
#include "joker_gfx24.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+275
-136
@@ -245,6 +245,23 @@ static int deck_top = -1;
|
|||||||
static Card *discard_pile[MAX_DECK_SIZE] = {NULL};
|
static Card *discard_pile[MAX_DECK_SIZE] = {NULL};
|
||||||
static int discard_top = -1;
|
static int discard_top = -1;
|
||||||
|
|
||||||
|
// Joker Special Variables
|
||||||
|
static int shortcut_joker_count = 0;
|
||||||
|
|
||||||
|
bool is_shortcut_joker_active(void)
|
||||||
|
{
|
||||||
|
return shortcut_joker_count > 0;
|
||||||
|
}
|
||||||
|
#define STRAIGHT_AND_FLUSH_SIZE_FOUR_FINGERS 4
|
||||||
|
#define STRAIGHT_AND_FLUSH_SIZE_DEFAULT 5
|
||||||
|
static int four_fingers_joker_count = 0;
|
||||||
|
static int straight_and_flush_size = STRAIGHT_AND_FLUSH_SIZE_DEFAULT;
|
||||||
|
|
||||||
|
int get_straight_and_flush_size(void)
|
||||||
|
{
|
||||||
|
return straight_and_flush_size;
|
||||||
|
}
|
||||||
|
|
||||||
// Played stack
|
// Played stack
|
||||||
static inline void played_push(CardObject *card_object)
|
static inline void played_push(CardObject *card_object)
|
||||||
{
|
{
|
||||||
@@ -350,10 +367,45 @@ bool is_joker_owned(int joker_id)
|
|||||||
void add_joker(JokerObject *joker_object)
|
void add_joker(JokerObject *joker_object)
|
||||||
{
|
{
|
||||||
list_append(jokers, joker_object);
|
list_append(jokers, joker_object);
|
||||||
|
|
||||||
|
// TODO: Extract to on_joker_added() callback
|
||||||
|
// In case the player gets multiple Four Fingers Jokers,
|
||||||
|
// only change size when the first one is added
|
||||||
|
if (joker_object->joker->id == FOUR_FINGERS_JOKER_ID)
|
||||||
|
{
|
||||||
|
if (four_fingers_joker_count == 0)
|
||||||
|
{
|
||||||
|
straight_and_flush_size = STRAIGHT_AND_FLUSH_SIZE_FOUR_FINGERS;
|
||||||
|
}
|
||||||
|
four_fingers_joker_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (joker_object->joker->id == SHORTCUT_JOKER_ID)
|
||||||
|
{
|
||||||
|
shortcut_joker_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove_held_joker(int joker_idx)
|
void remove_held_joker(int joker_idx)
|
||||||
{
|
{
|
||||||
|
// TODO: Extract to on_joker_removed() callback
|
||||||
|
JokerObject* joker_object = list_get(jokers, joker_idx);
|
||||||
|
// In case the player gets multiple Four Fingers Jokers,
|
||||||
|
// and only reset the size when all of them have been removed
|
||||||
|
if (joker_object->joker->id == FOUR_FINGERS_JOKER_ID)
|
||||||
|
{
|
||||||
|
four_fingers_joker_count--;
|
||||||
|
if (four_fingers_joker_count == 0)
|
||||||
|
{
|
||||||
|
straight_and_flush_size = STRAIGHT_AND_FLUSH_SIZE_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (joker_object->joker->id == SHORTCUT_JOKER_ID)
|
||||||
|
{
|
||||||
|
shortcut_joker_count--;
|
||||||
|
}
|
||||||
|
|
||||||
list_remove_by_idx(jokers, joker_idx);
|
list_remove_by_idx(jokers, joker_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,28 +714,24 @@ enum HandType hand_get_type()
|
|||||||
res_hand_type = STRAIGHT;
|
res_hand_type = STRAIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following can be optimized better but not sure how much it matters
|
// 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);
|
u8 n_of_a_kind = hand_contains_n_of_a_kind(ranks);
|
||||||
|
|
||||||
if (n_of_a_kind >= 5)
|
if (n_of_a_kind >= 5) {
|
||||||
{
|
if (res_hand_type == FLUSH) {
|
||||||
if (res_hand_type == FLUSH)
|
|
||||||
{
|
|
||||||
return FLUSH_FIVE;
|
return FLUSH_FIVE;
|
||||||
}
|
}
|
||||||
return FIVE_OF_A_KIND;
|
return FIVE_OF_A_KIND;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n_of_a_kind == 4)
|
// 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;
|
return FOUR_OF_A_KIND;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,11 +740,16 @@ enum HandType hand_get_type()
|
|||||||
return FULL_HOUSE;
|
return FULL_HOUSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush is more valuable than the remaining hand types, so return now
|
// Flush and Straight are more valuable than the remaining hand types, so return them now
|
||||||
if (res_hand_type == FLUSH)
|
if (res_hand_type == FLUSH) {
|
||||||
{
|
if (n_of_a_kind >= 5) {
|
||||||
|
return FLUSH_HOUSE;
|
||||||
|
}
|
||||||
return FLUSH;
|
return FLUSH;
|
||||||
}
|
}
|
||||||
|
if (res_hand_type == STRAIGHT) {
|
||||||
|
return STRAIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
if (n_of_a_kind == 3)
|
if (n_of_a_kind == 3)
|
||||||
{
|
{
|
||||||
@@ -1724,8 +1777,195 @@ void card_in_hand_loop_handle_discard_and_shuffling(int card_idx, bool* discarde
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void select_flush_and_straight_cards_in_played_hand()
|
||||||
|
{
|
||||||
|
// Special handling because Four Fingers might be active
|
||||||
|
bool final_selection[MAX_SELECTION_SIZE] = {false};
|
||||||
|
int min_len = get_straight_and_flush_size(); // Will be 4 if Four Fingers is in effect, otherwise 5
|
||||||
|
|
||||||
|
// if we have a flush in our hand
|
||||||
|
if (hand_type == FLUSH || hand_type == STRAIGHT_FLUSH || hand_type == ROYAL_FLUSH)
|
||||||
|
{
|
||||||
|
bool flush_selection[MAX_HAND_SIZE] = {false};
|
||||||
|
find_flush_in_played_cards(played, played_top, min_len, flush_selection);
|
||||||
|
// Add the results into the final selection
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
final_selection[i] = flush_selection[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a straight in our hand
|
||||||
|
if (hand_type == STRAIGHT || hand_type == STRAIGHT_FLUSH || hand_type == ROYAL_FLUSH)
|
||||||
|
{
|
||||||
|
bool straight_selection[MAX_HAND_SIZE] = {false};
|
||||||
|
find_straight_in_played_cards(played, played_top, is_shortcut_joker_active(), min_len, straight_selection);
|
||||||
|
// Add the results into the final selection
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
final_selection[i] = final_selection[i] || straight_selection[i];
|
||||||
|
}
|
||||||
|
// If Four Fingers is active, pairs can happen in a valid straight
|
||||||
|
// If Four Fingers is not active, pairs are impossible so this will not affect things
|
||||||
|
select_paired_cards_in_hand(played, played_top, final_selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, set mark the cards as selected based final_selection
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
if (final_selection[i])
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[i], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void select_all_five_cards_in_played_hand()
|
||||||
|
{
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[i], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void select_four_of_a_kind_cards_in_played_hand()
|
||||||
|
{
|
||||||
|
// find four cards with the same rank
|
||||||
|
if (played_top >= 3) // If there are 5 cards selected we just need to find the one card that doesn't match, and select the others
|
||||||
|
{
|
||||||
|
int unmatched_index = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
if (played[i]->card->rank != played[(i + 1) % played_top]->card->rank && played[i]->card->rank != played[(i + 2) % played_top]->card->rank)
|
||||||
|
{
|
||||||
|
unmatched_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
if (i != unmatched_index)
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[i], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // If there are only 4 cards selected we know they match
|
||||||
|
{
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[i], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void select_three_of_a_kind_cards_in_played_hand()
|
||||||
|
{
|
||||||
|
// find three cards with the same rank
|
||||||
|
for (int i = 0; i <= played_top - 1; i++)
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j <= played_top; j++)
|
||||||
|
{
|
||||||
|
if (played[i]->card->rank == played[j]->card->rank)
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[i], true);
|
||||||
|
card_object_set_selected(played[j], true);
|
||||||
|
|
||||||
|
for (int k = j + 1; k <= played_top; k++)
|
||||||
|
{
|
||||||
|
if (played[i]->card->rank == played[k]->card->rank && !card_object_is_selected(played[k]))
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[k], true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card_object_is_selected(played[i]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void select_two_pair_cards_in_played_hand()
|
||||||
|
{
|
||||||
|
// find two pairs of cards with the same rank
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i <= played_top - 1; i++)
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j <= played_top; j++)
|
||||||
|
{
|
||||||
|
if (played[i]->card->rank == played[j]->card->rank)
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[i], true);
|
||||||
|
card_object_set_selected(played[j], true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card_object_is_selected(played[i]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i <= played_top - 1; i++) // Find second pair
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j <= played_top; j++)
|
||||||
|
{
|
||||||
|
if (played[i]->card->rank == played[j]->card->rank && !card_object_is_selected(played[i]) && !card_object_is_selected(played[j]))
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[i], true);
|
||||||
|
card_object_set_selected(played[j], true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void select_pair_cards_in_played_hand()
|
||||||
|
{
|
||||||
|
// find two cards with the same rank
|
||||||
|
for (int i = 0; i <= played_top - 1; i++)
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j <= played_top; j++)
|
||||||
|
{
|
||||||
|
if (played[i]->card->rank == played[j]->card->rank)
|
||||||
|
{
|
||||||
|
card_object_set_selected(played[i], true);
|
||||||
|
card_object_set_selected(played[j], true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card_object_is_selected(played[i]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void select_highcard_cards_in_played_hand()
|
||||||
|
{
|
||||||
|
// find the card with the highest rank in the hand
|
||||||
|
int highest_rank_index = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
if (played[i]->card->rank > played[highest_rank_index]->card->rank)
|
||||||
|
{
|
||||||
|
highest_rank_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
card_object_set_selected(played[highest_rank_index], true);
|
||||||
|
}
|
||||||
|
|
||||||
static void cards_in_hand_update_loop(bool* discarded_card, int* played_selections, bool* sound_played)
|
static void cards_in_hand_update_loop(bool* discarded_card, int* played_selections, bool* sound_played)
|
||||||
{
|
{
|
||||||
|
// TODO: Break this function up into smaller ones, Gods be good
|
||||||
for (int i = hand_top + 1; i >= 0; i--) // Start from the end of the hand and work backwards because that's how Balatro does it
|
for (int i = hand_top + 1; i >= 0; i--) // Start from the end of the hand and work backwards because that's how Balatro does it
|
||||||
{
|
{
|
||||||
if (hand[i] != NULL)
|
if (hand[i] != NULL)
|
||||||
@@ -1803,142 +2043,39 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio
|
|||||||
{
|
{
|
||||||
case NONE:
|
case NONE:
|
||||||
break;
|
break;
|
||||||
case HIGH_CARD: // find the card with the highest rank in the hand
|
case HIGH_CARD:
|
||||||
int highest_rank_index = 0;
|
select_highcard_cards_in_played_hand();
|
||||||
|
|
||||||
for (int i = 0; i <= played_top; i++)
|
|
||||||
{
|
|
||||||
if (played[i]->card->rank > played[highest_rank_index]->card->rank)
|
|
||||||
{
|
|
||||||
highest_rank_index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
card_object_set_selected(played[highest_rank_index], true);
|
|
||||||
break;
|
break;
|
||||||
case PAIR: // find two cards with the same rank (Requires recursion)
|
case PAIR:
|
||||||
for (int i = 0; i <= played_top - 1; i++)
|
select_pair_cards_in_played_hand();
|
||||||
{
|
|
||||||
for (int j = i + 1; j <= played_top; j++)
|
|
||||||
{
|
|
||||||
if (played[i]->card->rank == played[j]->card->rank)
|
|
||||||
{
|
|
||||||
card_object_set_selected(played[i], true);
|
|
||||||
card_object_set_selected(played[j], true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (card_object_is_selected(played[i])) break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case TWO_PAIR: // find two pairs of cards with the same rank (Requires recursion)
|
case TWO_PAIR:
|
||||||
int i;
|
select_two_pair_cards_in_played_hand();
|
||||||
|
|
||||||
for (i = 0; i <= played_top - 1; i++)
|
|
||||||
{
|
|
||||||
for (int j = i + 1; j <= played_top; j++)
|
|
||||||
{
|
|
||||||
if (played[i]->card->rank == played[j]->card->rank)
|
|
||||||
{
|
|
||||||
card_object_set_selected(played[i], true);
|
|
||||||
card_object_set_selected(played[j], true);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (card_object_is_selected(played[i])) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; i <= played_top - 1; i++) // Find second pair
|
|
||||||
{
|
|
||||||
for (int j = i + 1; j <= played_top; j++)
|
|
||||||
{
|
|
||||||
if (played[i]->card->rank == played[j]->card->rank && !card_object_is_selected(played[i]) && !card_object_is_selected(played[j]))
|
|
||||||
{
|
|
||||||
card_object_set_selected(played[i], true);
|
|
||||||
card_object_set_selected(played[j], true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case THREE_OF_A_KIND: // find three cards with the same rank (requires recursion)
|
case THREE_OF_A_KIND:
|
||||||
for (int i = 0; i <= played_top - 1; i++)
|
select_three_of_a_kind_cards_in_played_hand();
|
||||||
{
|
|
||||||
for (int j = i + 1; j <= played_top; j++)
|
|
||||||
{
|
|
||||||
if (played[i]->card->rank == played[j]->card->rank)
|
|
||||||
{
|
|
||||||
card_object_set_selected(played[i], true);
|
|
||||||
card_object_set_selected(played[j], true);
|
|
||||||
|
|
||||||
for (int k = j + 1; k <= played_top; k++)
|
|
||||||
{
|
|
||||||
if (played[i]->card->rank == played[k]->card->rank && !card_object_is_selected(played[k]))
|
|
||||||
{
|
|
||||||
card_object_set_selected(played[k], true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (card_object_is_selected(played[i])) break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case FOUR_OF_A_KIND: // find four cards with the same rank (requires recursion)
|
case FOUR_OF_A_KIND:
|
||||||
if (played_top >= 3) // If there are 5 cards selected we just need to find the one card that doesn't match, and select the others
|
select_four_of_a_kind_cards_in_played_hand();
|
||||||
{
|
|
||||||
int unmatched_index = -1;
|
|
||||||
|
|
||||||
for (int i = 0; i <= played_top; i++)
|
|
||||||
{
|
|
||||||
if (played[i]->card->rank != played[(i + 1) % played_top]->card->rank && played[i]->card->rank != played[(i + 2) % played_top]->card->rank)
|
|
||||||
{
|
|
||||||
unmatched_index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i <= played_top; i++)
|
|
||||||
{
|
|
||||||
if (i != unmatched_index)
|
|
||||||
{
|
|
||||||
card_object_set_selected(played[i], true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // If there are only 4 cards selected we know they match
|
|
||||||
{
|
|
||||||
for (int i = 0; i <= played_top; i++)
|
|
||||||
{
|
|
||||||
card_object_set_selected(played[i], true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case STRAIGHT:
|
case STRAIGHT:
|
||||||
/* FALL THROUGH */
|
/* FALL THROUGH */
|
||||||
case FLUSH:
|
case FLUSH:
|
||||||
/* FALL THROUGH */
|
/* FALL THROUGH */
|
||||||
case FULL_HOUSE:
|
|
||||||
/* FALL THROUGH */
|
|
||||||
case STRAIGHT_FLUSH:
|
case STRAIGHT_FLUSH:
|
||||||
/* FALL THROUGH */
|
/* FALL THROUGH */
|
||||||
case ROYAL_FLUSH:
|
case ROYAL_FLUSH:
|
||||||
|
select_flush_and_straight_cards_in_played_hand();
|
||||||
|
break;
|
||||||
|
// ELSE FALL THROUGH
|
||||||
|
case FULL_HOUSE:
|
||||||
/* FALL THROUGH */
|
/* FALL THROUGH */
|
||||||
case FIVE_OF_A_KIND:
|
case FIVE_OF_A_KIND:
|
||||||
/* FALL THROUGH */
|
/* FALL THROUGH */
|
||||||
case FLUSH_HOUSE:
|
case FLUSH_HOUSE:
|
||||||
/* FALL THROUGH */
|
/* FALL THROUGH */
|
||||||
case FLUSH_FIVE: // Select all played cards in the hand (This is functionally identical as the above hand types)
|
case FLUSH_FIVE: // Select all played cards in the hand
|
||||||
for (int i = 0; i <= played_top; i++)
|
select_all_five_cards_in_played_hand();
|
||||||
{
|
|
||||||
card_object_set_selected(played[i], true);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1978,6 +2115,8 @@ static bool check_and_score_joker_for_event(int* iteration_start, Card* played_c
|
|||||||
|
|
||||||
static void played_cards_update_loop(bool* discarded_card, int* played_selections, bool* sound_played)
|
static void played_cards_update_loop(bool* discarded_card, int* played_selections, bool* sound_played)
|
||||||
{
|
{
|
||||||
|
// TODO: Break this function up into smaller ones.
|
||||||
|
|
||||||
// So this one is a bit fucking weird because I have to work kinda backwards for everything because of the order of the pushed cards from the hand to the play stack
|
// So this one is a bit fucking weird because I have to work kinda backwards for everything because of the order of the pushed cards from the hand to the play stack
|
||||||
// (also crazy that the company that published Balatro is called "Playstack" and this is a play stack, but I digress)
|
// (also crazy that the company that published Balatro is called "Playstack" and this is a play stack, but I digress)
|
||||||
for (int i = 0; i <= played_top; i++)
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
|||||||
+259
-13
@@ -2,10 +2,13 @@
|
|||||||
#include "card.h"
|
#include "card.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
|
||||||
static void get_distribution(CardObject **cards, int top, u8 *ranks_out, u8 *suits_out) {
|
|
||||||
|
void get_hand_distribution(u8 *ranks_out, u8 *suits_out) {
|
||||||
for (int i = 0; i < NUM_RANKS; i++) ranks_out[i] = 0;
|
for (int i = 0; i < NUM_RANKS; i++) ranks_out[i] = 0;
|
||||||
for (int i = 0; i < NUM_SUITS; i++) suits_out[i] = 0;
|
for (int i = 0; i < NUM_SUITS; i++) suits_out[i] = 0;
|
||||||
|
|
||||||
|
CardObject **cards = get_hand_array();
|
||||||
|
int top = get_hand_top();
|
||||||
for (int i = 0; i <= top; i++) {
|
for (int i = 0; i <= top; i++) {
|
||||||
if (cards[i] && card_object_is_selected(cards[i])) {
|
if (cards[i] && card_object_is_selected(cards[i])) {
|
||||||
ranks_out[cards[i]->card->rank]++;
|
ranks_out[cards[i]->card->rank]++;
|
||||||
@@ -14,12 +17,17 @@ static void get_distribution(CardObject **cards, int top, u8 *ranks_out, u8 *sui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_hand_distribution(u8 *ranks_out, u8 *suits_out) {
|
|
||||||
get_distribution(get_hand_array(), get_hand_top(), ranks_out, suits_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
void get_played_distribution(u8 *ranks_out, u8 *suits_out) {
|
void get_played_distribution(u8 *ranks_out, u8 *suits_out) {
|
||||||
get_distribution(get_played_array(), get_played_top(), ranks_out, suits_out);
|
for (int i = 0; i < NUM_RANKS; i++) ranks_out[i] = 0;
|
||||||
|
for (int i = 0; i < NUM_SUITS; i++) suits_out[i] = 0;
|
||||||
|
|
||||||
|
CardObject **played = get_played_array();
|
||||||
|
int top = get_played_top();
|
||||||
|
for (int i = 0; i <= top; i++) {
|
||||||
|
if (!played[i]) continue;
|
||||||
|
ranks_out[played[i]->card->rank]++;
|
||||||
|
suits_out[played[i]->card->suit]++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the highest N of a kind. So a full-house would return 3.
|
// Returns the highest N of a kind. So a full-house would return 3.
|
||||||
@@ -63,15 +71,103 @@ bool hand_contains_full_house(u8* ranks) {
|
|||||||
return (count_three >= 2 || (count_three && count_pair));
|
return (count_three >= 2 || (count_three && count_pair));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is mostly from Google Gemini
|
||||||
bool hand_contains_straight(u8 *ranks) {
|
bool hand_contains_straight(u8 *ranks) {
|
||||||
for (int i = 0; i < NUM_RANKS - 4; i++)
|
if (!is_shortcut_joker_active())
|
||||||
{
|
{
|
||||||
if (ranks[i] && ranks[i + 1] && ranks[i + 2] && ranks[i + 3] && ranks[i + 4])
|
int straight_size = get_straight_and_flush_size();
|
||||||
return true;
|
// This is the regular case of detecting straights
|
||||||
|
int run = 0;
|
||||||
|
for (int i = 0; i < NUM_RANKS; ++i)
|
||||||
|
{
|
||||||
|
if (ranks[i]) {
|
||||||
|
if (++run >= straight_size)
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
run = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for ace low straight
|
||||||
|
if (straight_size >= 2 && ranks[ACE]) {
|
||||||
|
// With A as low, the highest rank you can use is FIVE.
|
||||||
|
int last_needed = TWO + (straight_size - 2); // -1 for inclusive integer distance and another -1 for the Ace e.g. need=5 -> need 2..5
|
||||||
|
if (last_needed <= FIVE) {
|
||||||
|
bool ok = true;
|
||||||
|
for (int r = TWO; r <= last_needed; ++r)
|
||||||
|
{
|
||||||
|
if (!ranks[r]) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
// Shortcut Joker is active, we have to detect straights where any card may "skip" 1 rank
|
||||||
|
// We do this with a dynamic programming algorithm that calculates
|
||||||
|
// the longest possible straight that can end on each rank
|
||||||
|
// and stopping when we find one that is {straight-size} cards long
|
||||||
|
u8 longest_short_cut_at[NUM_RANKS] = {0};
|
||||||
|
|
||||||
|
// A low ace can start a sequence. 'ace_low_len' is 1 if an ace is present,
|
||||||
|
// acting as a potential predecessor for TWO and THREE.
|
||||||
|
int ace_low_len = ranks[ACE] ? 1 : 0;
|
||||||
|
|
||||||
|
// Iterate through all ranks from TWO up to ACE.
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
{
|
||||||
|
// No cards in this rank, no straight can end here, continue
|
||||||
|
if (ranks[i] == 0)
|
||||||
|
{
|
||||||
|
longest_short_cut_at[i] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int prev_len1 = 0;
|
||||||
|
int prev_len2 = 0;
|
||||||
|
|
||||||
|
// This logic handles the special connections for ace-low straights.
|
||||||
|
if (i == TWO)
|
||||||
|
{
|
||||||
|
// A TWO can be preceded by a low ACE (no skip).
|
||||||
|
prev_len1 = ace_low_len;
|
||||||
|
}
|
||||||
|
else if (i == THREE)
|
||||||
|
{
|
||||||
|
// A THREE can be preceded by a TWO (no skip) or a low ACE (skip).
|
||||||
|
prev_len1 = longest_short_cut_at[TWO];
|
||||||
|
prev_len2 = ace_low_len;
|
||||||
|
}
|
||||||
|
else if (i == ACE)
|
||||||
|
{
|
||||||
|
// An ACE (as the highest card) can be preceded by a KING or a QUEEN.
|
||||||
|
prev_len1 = longest_short_cut_at[KING];
|
||||||
|
prev_len2 = longest_short_cut_at[QUEEN];
|
||||||
|
}
|
||||||
|
else // For all other cards (FOUR through KING).
|
||||||
|
{
|
||||||
|
// A card can be preceded by the rank directly below or two ranks below.
|
||||||
|
prev_len1 = longest_short_cut_at[i - 1];
|
||||||
|
prev_len2 = longest_short_cut_at[i - 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The length of the straight ending at rank 'i' is 1 (for the card itself)
|
||||||
|
// plus the length of the longest valid preceding straight.
|
||||||
|
longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
|
||||||
|
|
||||||
|
// If we've formed a sequence of {straight-size} or more cards, we have a straight.
|
||||||
|
if (longest_short_cut_at[i] >= get_straight_and_flush_size())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Check for ace low straight
|
|
||||||
if (ranks[ACE] && ranks[TWO] && ranks[THREE] && ranks[FOUR] && ranks[FIVE])
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -79,10 +175,160 @@ bool hand_contains_straight(u8 *ranks) {
|
|||||||
bool hand_contains_flush(u8 *suits) {
|
bool hand_contains_flush(u8 *suits) {
|
||||||
for (int i = 0; i < NUM_SUITS; i++)
|
for (int i = 0; i < NUM_SUITS; i++)
|
||||||
{
|
{
|
||||||
if (suits[i] >= MAX_SELECTION_SIZE) // this allows MAX_SELECTION_SIZE - 1 for four fingers joker
|
if (suits[i] >= get_straight_and_flush_size())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the number of cards in the best flush found or 0 if no flush of min_len is found, and marks them in out_selection.
|
||||||
|
/**
|
||||||
|
* Finds the largest flush (set of cards with the same suit) in the given array of played cards.
|
||||||
|
* Marks the cards belonging to the best flush in the out_selection array.
|
||||||
|
*
|
||||||
|
* @param played Array of pointers to CardObject representing played cards.
|
||||||
|
* @param top Index of the top of the played stack.
|
||||||
|
* @param min_len Minimum number of cards required for a flush.
|
||||||
|
* @param out_selection Output array of bools; set to true for cards in the best flush, false otherwise.
|
||||||
|
* @return The number of cards in the best flush found, or 0 if no flush meets min_len.
|
||||||
|
*/
|
||||||
|
int find_flush_in_played_cards(CardObject** played, int top, int min_len, bool* out_selection) {
|
||||||
|
if (top < 0) return 0;
|
||||||
|
for (int i = 0; i <= top; i++) out_selection[i] = false;
|
||||||
|
|
||||||
|
int suit_counts[NUM_SUITS] = {0};
|
||||||
|
for (int i = 0; i <= top; i++) {
|
||||||
|
if (played[i] && played[i]->card) {
|
||||||
|
suit_counts[played[i]->card->suit]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int best_suit = -1;
|
||||||
|
int best_count = 0;
|
||||||
|
for (int i = 0; i < NUM_SUITS; i++) {
|
||||||
|
if (suit_counts[i] > best_count) {
|
||||||
|
best_count = suit_counts[i];
|
||||||
|
best_suit = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_count >= min_len) {
|
||||||
|
for (int i = 0; i <= top; i++) {
|
||||||
|
if (played[i] && played[i]->card && played[i]->card->suit == best_suit) {
|
||||||
|
out_selection[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best_count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of cards in the best straight or 0 if no straight of min_len is found, marks as true them in out_selection[].
|
||||||
|
// This is mostly from Google Gemini
|
||||||
|
int find_straight_in_played_cards(CardObject** played, int top, bool shortcut_active, int min_len, bool* out_selection) {
|
||||||
|
if (top < 0) return 0;
|
||||||
|
for (int i = 0; i <= top; i++) out_selection[i] = false;
|
||||||
|
|
||||||
|
// --- Setup for Backtracking DP ---
|
||||||
|
u8 longest_straight_at[NUM_RANKS] = {0};
|
||||||
|
int parent[NUM_RANKS];
|
||||||
|
for(int i=0; i<NUM_RANKS; i++) parent[i] = -1;
|
||||||
|
|
||||||
|
u8 ranks[NUM_RANKS] = {0};
|
||||||
|
for (int i = 0; i <= top; i++) {
|
||||||
|
if (played[i] && played[i]->card) {
|
||||||
|
ranks[played[i]->card->rank]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Run DP to find longest straight ---
|
||||||
|
// This is nearly identical to hand_contains_straight() logic
|
||||||
|
// TODO: Consolidate functions to avoid code duplication?
|
||||||
|
// Might cost performance because this does a little more
|
||||||
|
int ace_low_len = ranks[ACE] ? 1 : 0;
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++) {
|
||||||
|
if (ranks[i] > 0) {
|
||||||
|
int prev1 = 0, prev2 = 0;
|
||||||
|
int parent1 = -1, parent2 = -1;
|
||||||
|
|
||||||
|
if (shortcut_active) {
|
||||||
|
if (i == TWO) { prev1 = ace_low_len; parent1 = ACE; }
|
||||||
|
else if (i == THREE) { prev1 = longest_straight_at[TWO]; parent1 = TWO; prev2 = ace_low_len; parent2 = ACE; }
|
||||||
|
else if (i == ACE) { prev1 = longest_straight_at[KING]; parent1 = KING; prev2 = longest_straight_at[QUEEN]; parent2 = QUEEN; }
|
||||||
|
else { prev1 = longest_straight_at[i-1]; parent1 = i-1; if (i > 1) { prev2 = longest_straight_at[i-2]; parent2 = i-2; }}
|
||||||
|
} else {
|
||||||
|
if (i == TWO) { prev1 = ace_low_len; parent1 = ACE; }
|
||||||
|
else if (i == ACE) { prev1 = longest_straight_at[KING]; parent1 = KING; }
|
||||||
|
else { prev1 = longest_straight_at[i-1]; parent1 = i-1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallels longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
|
||||||
|
// in hand_contains_straight()
|
||||||
|
if(prev1 >= prev2) { longest_straight_at[i] = 1 + prev1; parent[i] = parent1; }
|
||||||
|
else { longest_straight_at[i] = 1 + prev2; parent[i] = parent2; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Find best straight and backtrack ---
|
||||||
|
int best_len = 0;
|
||||||
|
int end_rank = -1;
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++) {
|
||||||
|
if (longest_straight_at[i] >= best_len) {
|
||||||
|
best_len = longest_straight_at[i];
|
||||||
|
end_rank = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_len >= min_len) {
|
||||||
|
u8 needed_ranks[NUM_RANKS] = {0};
|
||||||
|
int current_rank = end_rank;
|
||||||
|
while (current_rank != -1 && best_len > 0) {
|
||||||
|
needed_ranks[current_rank]++;
|
||||||
|
current_rank = parent[current_rank];
|
||||||
|
best_len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i <= top; i++) {
|
||||||
|
if (played[i] && played[i]->card && needed_ranks[played[i]->card->rank] > 0) {
|
||||||
|
out_selection[i] = true;
|
||||||
|
needed_ranks[played[i]->card->rank]--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int final_card_count = 0;
|
||||||
|
for(int i=0; i<=top; i++) {
|
||||||
|
if(out_selection[i]) final_card_count++;
|
||||||
|
}
|
||||||
|
return final_card_count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is used for the special case in "Four Fingers" where you can add a pair into a straight
|
||||||
|
// (e.g. AA234 should score all 5 cards)
|
||||||
|
void select_paired_cards_in_hand(CardObject** played, int played_top, bool* selection) {
|
||||||
|
// Build a set of ranks that are already selected
|
||||||
|
bool rank_selected[NUM_RANKS] = {0};
|
||||||
|
bool any_selected_rank = false;
|
||||||
|
|
||||||
|
for (int i = 0; i <= played_top; i++) {
|
||||||
|
if (selection[i] && played[i] && played[i]->card) {
|
||||||
|
rank_selected[played[i]->card->rank] = true;
|
||||||
|
any_selected_rank = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no ranks were selected initially, nothing to do
|
||||||
|
if (!any_selected_rank) return;
|
||||||
|
|
||||||
|
// Add any unselected card to the selection if if shares a rank with the selected ranks
|
||||||
|
for (int i = 0; i <= played_top; i++) {
|
||||||
|
if (played[i] && played[i]->card && !selection[i]) {
|
||||||
|
if (rank_selected[played[i]->card->rank]) {
|
||||||
|
selection[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1057,7 +1057,7 @@ const JokerInfo joker_registry[] = {
|
|||||||
{ COMMON_JOKER, 4, even_steven_joker_effect, on_joker_created_noop }, // 23
|
{ COMMON_JOKER, 4, even_steven_joker_effect, on_joker_created_noop }, // 23
|
||||||
{ COMMON_JOKER, 5, blue_joker_effect, on_joker_created_noop }, // 24
|
{ COMMON_JOKER, 5, blue_joker_effect, on_joker_created_noop }, // 24
|
||||||
{ COMMON_JOKER, 4, odd_todd_joker_effect, on_joker_created_noop }, // 25
|
{ COMMON_JOKER, 4, odd_todd_joker_effect, on_joker_created_noop }, // 25
|
||||||
{ COMMON_JOKER, 4, scholar_joker_effect, on_joker_created_noop }, // 26
|
{ UNCOMMON_JOKER, 7, NULL, /* Shortcut */ on_joker_created_noop }, // 26
|
||||||
{ COMMON_JOKER, 4, business_card_joker_effect, on_joker_created_noop }, // 27
|
{ COMMON_JOKER, 4, business_card_joker_effect, on_joker_created_noop }, // 27
|
||||||
// Business card should be paired with Shortcut for palette optimization when it's added
|
// Business card should be paired with Shortcut for palette optimization when it's added
|
||||||
{ COMMON_JOKER, 4, scary_face_joker_effect, on_joker_created_noop }, // 28
|
{ COMMON_JOKER, 4, scary_face_joker_effect, on_joker_created_noop }, // 28
|
||||||
@@ -1080,6 +1080,8 @@ const JokerInfo joker_registry[] = {
|
|||||||
{ UNCOMMON_JOKER, 6, sock_and_buskin_joker_effect, sock_and_buskin_on_joker_created }, // 45
|
{ UNCOMMON_JOKER, 6, sock_and_buskin_joker_effect, sock_and_buskin_on_joker_created }, // 45
|
||||||
{ UNCOMMON_JOKER, 6, hack_joker_effect, hack_on_joker_created }, // 46
|
{ UNCOMMON_JOKER, 6, hack_joker_effect, hack_on_joker_created }, // 46
|
||||||
{ COMMON_JOKER, 4, hanging_chad_joker_effect, hanging_chad_on_joker_created }, // 47
|
{ COMMON_JOKER, 4, hanging_chad_joker_effect, hanging_chad_on_joker_created }, // 47
|
||||||
|
{ UNCOMMON_JOKER, 7, NULL,/* Four Fingers */ on_joker_created_noop }, // 48
|
||||||
|
{ COMMON_JOKER, 4, scholar_joker_effect, on_joker_created_noop }, // 49
|
||||||
|
|
||||||
// The following jokers don't have sprites yet,
|
// The following jokers don't have sprites yet,
|
||||||
// uncomment them when their sprites are added.
|
// uncomment them when their sprites are added.
|
||||||
|
|||||||
Reference in New Issue
Block a user