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:
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 |
@@ -21,4 +21,5 @@ DEF_JOKER_GFX(19)
|
||||
DEF_JOKER_GFX(20)
|
||||
DEF_JOKER_GFX(21)
|
||||
DEF_JOKER_GFX(22)
|
||||
DEF_JOKER_GFX(23)
|
||||
DEF_JOKER_GFX(23)
|
||||
DEF_JOKER_GFX(24)
|
||||
+5
-1
@@ -118,4 +118,8 @@ int get_money(void);
|
||||
int get_game_speed(void);
|
||||
void set_game_speed(int new_game_speed);
|
||||
|
||||
#endif // GAME_H
|
||||
// joker specific functions
|
||||
bool is_shortcut_joker_active(void);
|
||||
int get_straight_and_flush_size(void);
|
||||
|
||||
#endif // GAME_H
|
||||
@@ -13,4 +13,8 @@ bool hand_contains_full_house(u8 *ranks);
|
||||
bool hand_contains_straight(u8 *ranks);
|
||||
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
|
||||
@@ -65,6 +65,8 @@ enum JokerEvent
|
||||
#define JOKER_STENCIL_ID 16
|
||||
#define PAREIDOLIA_JOKER_ID 30
|
||||
#define JOKER_BRAINSTORM_ID 40
|
||||
#define SHORTCUT_JOKER_ID 26
|
||||
#define FOUR_FINGERS_JOKER_ID 48
|
||||
|
||||
|
||||
typedef struct
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
#include "joker_gfx21.h"
|
||||
#include "joker_gfx22.h"
|
||||
#include "joker_gfx23.h"
|
||||
#include "joker_gfx24.h"
|
||||
|
||||
#endif
|
||||
+276
-137
@@ -245,6 +245,23 @@ static int deck_top = -1;
|
||||
static Card *discard_pile[MAX_DECK_SIZE] = {NULL};
|
||||
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
|
||||
static inline void played_push(CardObject *card_object)
|
||||
{
|
||||
@@ -350,11 +367,46 @@ bool is_joker_owned(int joker_id)
|
||||
void add_joker(JokerObject *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)
|
||||
{
|
||||
list_remove_by_idx(jokers, 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);
|
||||
}
|
||||
|
||||
int get_deck_top(void)
|
||||
@@ -662,28 +714,24 @@ enum HandType hand_get_type()
|
||||
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
|
||||
u8 n_of_a_kind = hand_contains_n_of_a_kind(ranks);
|
||||
|
||||
if (n_of_a_kind >= 5)
|
||||
{
|
||||
if (res_hand_type == FLUSH)
|
||||
{
|
||||
if (n_of_a_kind >= 5) {
|
||||
if (res_hand_type == FLUSH) {
|
||||
return FLUSH_FIVE;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -692,11 +740,16 @@ enum HandType hand_get_type()
|
||||
return FULL_HOUSE;
|
||||
}
|
||||
|
||||
// Flush is more valuable than the remaining hand types, so return now
|
||||
if (res_hand_type == FLUSH)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
if (hand[i] != NULL)
|
||||
@@ -1803,142 +2043,39 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio
|
||||
{
|
||||
case NONE:
|
||||
break;
|
||||
case HIGH_CARD: // 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);
|
||||
case HIGH_CARD:
|
||||
select_highcard_cards_in_played_hand();
|
||||
break;
|
||||
case PAIR: // find two cards with the same rank (Requires recursion)
|
||||
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;
|
||||
}
|
||||
case PAIR:
|
||||
select_pair_cards_in_played_hand();
|
||||
break;
|
||||
case TWO_PAIR: // find two pairs of cards with the same rank (Requires recursion)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
case TWO_PAIR:
|
||||
select_two_pair_cards_in_played_hand();
|
||||
break;
|
||||
case THREE_OF_A_KIND: // find three cards with the same rank (requires recursion)
|
||||
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;
|
||||
}
|
||||
case THREE_OF_A_KIND:
|
||||
select_three_of_a_kind_cards_in_played_hand();
|
||||
break;
|
||||
case FOUR_OF_A_KIND: // find four cards with the same rank (requires recursion)
|
||||
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);
|
||||
}
|
||||
}
|
||||
case FOUR_OF_A_KIND:
|
||||
select_four_of_a_kind_cards_in_played_hand();
|
||||
break;
|
||||
case STRAIGHT:
|
||||
/* FALL THROUGH */
|
||||
case FLUSH:
|
||||
/* FALL THROUGH */
|
||||
case FULL_HOUSE:
|
||||
/* FALL THROUGH */
|
||||
case STRAIGHT_FLUSH:
|
||||
/* FALL THROUGH */
|
||||
case ROYAL_FLUSH:
|
||||
select_flush_and_straight_cards_in_played_hand();
|
||||
break;
|
||||
// ELSE FALL THROUGH
|
||||
case FULL_HOUSE:
|
||||
/* FALL THROUGH */
|
||||
case FIVE_OF_A_KIND:
|
||||
/* FALL THROUGH */
|
||||
case FLUSH_HOUSE:
|
||||
/* FALL THROUGH */
|
||||
case FLUSH_FIVE: // Select all played cards in the hand (This is functionally identical as the above hand types)
|
||||
for (int i = 0; i <= played_top; i++)
|
||||
{
|
||||
card_object_set_selected(played[i], true);
|
||||
}
|
||||
case FLUSH_FIVE: // Select all played cards in the hand
|
||||
select_all_five_cards_in_played_hand();
|
||||
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)
|
||||
{
|
||||
// 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
|
||||
// (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++)
|
||||
|
||||
+259
-13
@@ -2,10 +2,13 @@
|
||||
#include "card.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_SUITS; i++) suits_out[i] = 0;
|
||||
|
||||
CardObject **cards = get_hand_array();
|
||||
int top = get_hand_top();
|
||||
for (int i = 0; i <= top; i++) {
|
||||
if (cards[i] && card_object_is_selected(cards[i])) {
|
||||
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) {
|
||||
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.
|
||||
@@ -63,15 +71,103 @@ bool hand_contains_full_house(u8* ranks) {
|
||||
return (count_three >= 2 || (count_three && count_pair));
|
||||
}
|
||||
|
||||
// This is mostly from Google Gemini
|
||||
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])
|
||||
return true;
|
||||
int straight_size = get_straight_and_flush_size();
|
||||
// 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;
|
||||
}
|
||||
@@ -79,10 +175,160 @@ bool hand_contains_straight(u8 *ranks) {
|
||||
bool hand_contains_flush(u8 *suits) {
|
||||
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 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, 5, blue_joker_effect, on_joker_created_noop }, // 24
|
||||
{ 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
|
||||
// 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
|
||||
@@ -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, hack_joker_effect, hack_on_joker_created }, // 46
|
||||
{ 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,
|
||||
// uncomment them when their sprites are added.
|
||||
|
||||
Reference in New Issue
Block a user