Refactor of scoring flow, Joker Callbacks and Card Retriggers (#160)

* Remove Baseball Card for now, will have to wait a future PR

* Removed cards held in hand joker scoring stage - should be in another PR

---------

Co-authored-by: MonteCrysto <mathis.martin31@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: MeirGavish <meir.gavish@gmail.com>
This commit is contained in:
Geralt
2025-11-01 15:50:52 +02:00
committed by GitHub
co-authored by MonteCrysto Copilot MeirGavish
parent 2c532b5bd8
commit eb815b7820
14 changed files with 906 additions and 373 deletions
+164 -90
View File
@@ -210,11 +210,19 @@ static FIXED lerped_temp_score = 0;
static int chips = 0;
static int mult = 0;
static bool retrigger = false;
static int hand_size = 8; // Default hand size is 8
static int cards_drawn = 0;
static int hand_selections = 0;
// Keeping track of cards scored
static int scored_card_index = 0;
// Keeping track of what Jokers are scored at each step
static int joker_scored_index = 0;
static int joker_round_end_index = 0;
static int selection_x = 0;
static int selection_y = 0;
@@ -291,11 +299,13 @@ static inline void reset_top_left_panel_bottom_row()
}
// get-functions, for other files to view game state (mainly for jokers)
CardObject **get_hand_array(void) {
CardObject **get_hand_array(void)
{
return hand;
}
int get_hand_top(void) {
int get_hand_top(void)
{
return hand_top;
}
@@ -304,19 +314,28 @@ int hand_get_size(void)
return hand_top + 1;
}
CardObject **get_played_array(void) {
CardObject **get_played_array(void)
{
return played;
}
int get_played_top(void) {
int get_played_top(void)
{
return played_top;
}
List *get_jokers(void) {
int get_scored_card_index(void)
{
return scored_card_index;
}
List *get_jokers(void)
{
return jokers;
}
bool is_joker_owned(int joker_id) {
bool is_joker_owned(int joker_id)
{
for (int k = 0; k < list_get_size(jokers); k++)
{
JokerObject *joker = list_get(jokers, k);
@@ -368,6 +387,7 @@ int get_money(void)
return money;
}
// Consts
// Rects left top right bottom
@@ -634,7 +654,8 @@ enum HandType hand_get_type()
res_hand_type = FLUSH;
// Check for straight
if (hand_contains_straight(ranks)) {
if (hand_contains_straight(ranks))
{
if (res_hand_type == FLUSH)
res_hand_type = STRAIGHT_FLUSH;
else
@@ -642,7 +663,8 @@ enum HandType hand_get_type()
}
// Check for royal flush vs regular straight flush
if (res_hand_type == 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;
@@ -651,32 +673,40 @@ enum HandType hand_get_type()
// 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) {
if (n_of_a_kind == 4)
{
return FOUR_OF_A_KIND;
}
if (n_of_a_kind == 3 && hand_contains_full_house(ranks)) {
if (n_of_a_kind == 3 && hand_contains_full_house(ranks))
{
return FULL_HOUSE;
}
// Flush is more valuable than the remaining hand types, so return now
if (res_hand_type == FLUSH) {
if (res_hand_type == FLUSH)
{
return FLUSH;
}
if (n_of_a_kind == 3) {
if (n_of_a_kind == 3)
{
return THREE_OF_A_KIND;
}
if (n_of_a_kind == 2) {
if (hand_contains_two_pair(ranks)) {
if (n_of_a_kind == 2)
{
if (hand_contains_two_pair(ranks))
{
return TWO_PAIR;
}
return PAIR;
@@ -686,7 +716,8 @@ enum HandType hand_get_type()
}
// Returns true if the card is *considered* a face card
bool card_is_face(Card *card) {
bool card_is_face(Card *card)
{
// Card is a face card, or Pareidolia is present
return (
card->rank == JACK ||
@@ -856,7 +887,8 @@ void change_background(int id)
main_bg_se_copy_rect(skip_blind_btn_rect_src, skip_blind_btn_pos_dest);
}
switch(blinds[i]) {
switch(blinds[i])
{
case BLIND_STATE_CURRENT: // Raise the blind panel up a bit
{
// TODO: Replace copies with main_bg_se_copy_rect() of named rects
@@ -1925,6 +1957,25 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio
}
}
// returns true if a joker was scored, false otherwise
static bool check_and_score_joker_for_event(int* iteration_start, Card* played_card, enum JokerEvent joker_event)
{
for (int k = *iteration_start; k < list_get_size(jokers); k++)
{
(*iteration_start)++;
JokerObject *joker = list_get(jokers, k);
if (joker_object_score(joker, played_card, joker_event, &chips, &mult, &money, &retrigger))
{
display_chips(chips);
display_mult(mult);
display_money(money);
return true;
}
}
return false;
}
static void played_cards_update_loop(bool* discarded_card, int* played_selections, bool* sound_played)
{
// 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
@@ -1948,13 +1999,14 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
switch (play_state)
{
case PLAY_PLAYING:
if (i == 0 && (timer % FRAMES(10) == 0 || !card_object_is_selected(played[played_top - *played_selections])) && timer > FRAMES(40))
{
(*played_selections)--;
if (*played_selections == 0)
{
play_state = PLAY_SCORING;
play_state = PLAY_SCORING_CARDS;
timer = TM_ZERO;
}
}
@@ -1964,96 +2016,80 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
played_y -= int2fx(10);
}
break;
case PLAY_SCORING:
case PLAY_SCORING_CARDS:
if (i == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40))
{
// So pretend "played_selections" is now called "scored_cards" and it counts the number of cards that have been scored
int scored_cards = 0;
for (int j = 0; j <= played_top; j++)
// We are about to score played Cards, then Jokers.
// If we need to retrigger, then we have scored a card previously
// and thus have incremented scored_card_index by 1.
// Take out this increment to score the previous card again
// and reset the scored Joker index to 0 to go back to the beginning
if (retrigger)
{
retrigger = false;
scored_card_index--;
(*played_selections)--;
joker_scored_index = 0;
}
// So pretend "played_selections" is now called "scored_card_index" and it counts the number of cards that have been scored
for (int j = scored_card_index; j <= played_top+1; j++) // allow past played_top to score jokers for last played card
{
tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
// Trigger all Jokers after each card scored
if (*played_selections > 0)
{
for (int k = 0; k < list_get_size(jokers); k++)
if (check_and_score_joker_for_event(&joker_scored_index, played[*played_selections - 1]->card, JOKER_EVENT_ON_CARD_SCORED))
{
JokerObject *joker = list_get(jokers, k);
if (joker_object_score(joker, played[*played_selections - 1]->card, &chips, &mult, NULL, &money, NULL)) // NULLs aren't implemented yet
{
display_chips(chips);
display_mult(mult);
display_money(money);
return;
}
return;
}
// Trigger all Jokers that have an effect when a card finishes scoring
// (e.g. retriggers) after activating all the other scored_card Jokers normally
joker_scored_index = 0;
if (check_and_score_joker_for_event(&joker_scored_index, played[*played_selections - 1]->card, JOKER_EVENT_ON_CARD_SCORED_END))
{
return;
}
}
if (card_object_is_selected(played[j]))
// Score card
scored_card_index++; // Count the number of cards that have been scored
if (j <= played_top && card_object_is_selected(played[j]))
{
scored_cards = j + 1; // Count the number of cards that have been scored
if (scored_cards > *played_selections)
{
for (int k = 0; k < list_get_size(jokers); k++)
{
JokerObject *joker = list_get(jokers, k);
if (joker != NULL)
{
joker->joker->processed = false; // Reset the joker's processed state for the next score
}
}
tte_set_pos(fx2int(played[j]->sprite_object->x) + 8, SCORED_CARD_TEXT_Y); // Offset of 16 pixels to center the text on the card
tte_set_special(TTE_BLUE_PB * TTE_SPECIAL_PB_MULT_OFFSET); // Set text color to blue from background memory
tte_set_pos(fx2int(played[j]->sprite_object->x) + 8, SCORED_CARD_TEXT_Y); // Offset of 16 pixels to center the text on the card
tte_set_special(0xD000); // Set text color to blue from background memory
// Write the score to a character buffer variable
char score_buffer[INT_MAX_DIGITS + 2]; // for '+' and null terminator
snprintf(score_buffer, sizeof(score_buffer), "+%d", card_get_value(played[j]->card));
tte_write(score_buffer);
// Write the score to a character buffer variable
char score_buffer[INT_MAX_DIGITS + 2]; // for '+' and null terminator
snprintf(score_buffer, sizeof(score_buffer), "+%d", card_get_value(played[j]->card));
tte_write(score_buffer);
*played_selections = scored_card_index;
card_object_shake(played[j], SFX_CARD_SELECT);
*played_selections = scored_cards;
card_object_shake(played[j], SFX_CARD_SELECT);
// Relocated card scoring logic here
chips += card_get_value(played[j]->card);
display_chips(chips);
// Relocated card scoring logic here
chips += card_get_value(played[j]->card);
display_chips(chips);
// Allow Joker scoring
joker_scored_index = 0;
break;
}
}
if (j == played_top && scored_cards == *played_selections) // Check if it's the last card
{
tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
for (int k = 0; k <= list_get_size(jokers); k++) // Independent joker scoring loop
{
JokerObject *joker = list_get(jokers, k);
if (joker_object_score(joker, NULL, &chips, &mult, NULL, &money, NULL)) // NULLs aren't implemented yet
{
display_chips(chips);
display_mult(mult);
display_money(money);
return; // Returning was just the easiest way to break out of the loop
}
}
for (int k = 0; k <= list_get_size(jokers); k++)
{
JokerObject *joker = list_get(jokers, k);
if (joker != NULL)
{
joker->joker->processed = false; // Reset the joker's processed state for the next round
}
}
play_state = PLAY_ENDING;
timer = TM_ZERO;
*played_selections = played_top + 1; // Reset the played selections to the top of the played stack
break;
return;
}
}
// advance state after going past the last card (exited the loop without returning)
play_state = PLAY_SCORING_JOKERS;
joker_scored_index = 0;
scored_card_index = 0; // reuse this variable for held cards
return;
}
if (card_object_is_selected(played[i]))
@@ -2061,7 +2097,40 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
played_y -= int2fx(10);
}
break;
// Score Jokers normally
case PLAY_SCORING_JOKERS:
if (i == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40))
{
tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
if (check_and_score_joker_for_event(&joker_scored_index, NULL, JOKER_EVENT_INDEPENDENT))
{
return;
}
// Trigger hand end effect for all jokers once they are done scoring
if (check_and_score_joker_for_event(&joker_round_end_index, NULL, JOKER_EVENT_ON_HAND_SCORED_END))
{
return;
}
play_state = PLAY_ENDING;
timer = TM_ZERO;
*played_selections = played_top + 1; // Reset the played selections to the top of the played stack
break;
}
if (card_object_is_selected(played[i]))
{
played_y -= int2fx(10);
}
break;
case PLAY_ENDING: // This is the reverse of PLAY_PLAYING. The cards get reset back to their neutral position sequentially
if (i == 0 && (timer % FRAMES(10) == 0 || !card_object_is_selected(played[played_top - *played_selections])) && timer > FRAMES(40))
{
(*played_selections)--;
@@ -2078,7 +2147,9 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
played_y -= int2fx(10);
}
break;
case PLAY_ENDED: // Basically a copy of HAND_DISCARD
if (!*discarded_card && played[i] != NULL && timer > FRAMES(40))
{
played_x = int2fx(240);
@@ -2115,6 +2186,9 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
hand_selections = 0;
*played_selections = 0;
played_top = -1; // Reset the played stack
scored_card_index = 0;
joker_scored_index = 0;
joker_round_end_index = 0;
timer = TM_ZERO;
break; // Break out of the loop to avoid accessing an invalid index
}
+69 -60
View File
@@ -144,7 +144,8 @@ Joker *joker_new(u8 id)
joker->modifier = BASE_EDITION; // TODO: Make this a parameter
joker->value = jinfo->base_value + edition_price_lut[joker->modifier];
joker->rarity = jinfo->rarity;
joker->processed = false;
joker->data = 0;
jinfo->on_joker_created(joker);
return joker;
}
@@ -155,12 +156,12 @@ void joker_destroy(Joker **joker)
*joker = NULL;
}
JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card)
JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event)
{
const JokerInfo *jinfo = get_joker_registry_entry(joker->id);
if (!jinfo || jinfo->effect == NULL) return (JokerEffect){0};
if (!jinfo || jinfo->joker_effect == NULL) return (JokerEffect){0};
return jinfo->effect(joker, scored_card);
return jinfo->joker_effect(joker, scored_card, joker_event);
}
int joker_get_sell_value(const Joker* joker)
@@ -248,68 +249,76 @@ void joker_object_shake(JokerObject *joker_object, mm_word sound_id)
sprite_object_shake(joker_object->sprite_object, sound_id);
}
bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips, int *mult, int *xmult, int *money, bool *retrigger)
void set_and_shift_text(char* str, int* cursor_pos_x, int color_pb)
{
if (joker_object->joker->processed == true) return false; // If the joker has already been processed, return false
tte_set_pos(*cursor_pos_x, JOKER_SCORE_TEXT_Y);
tte_set_special(color_pb * TTE_SPECIAL_PB_MULT_OFFSET);
tte_write(str);
JokerEffect joker_effect = joker_get_score_effect(joker_object->joker, scored_card);
// + 1 For space
const int joker_score_display_offset_px = (MAX_CARD_SCORE_STR_LEN + 1)*TTE_CHAR_SIZE;
*cursor_pos_x += joker_score_display_offset_px;
}
if (memcmp(&joker_effect, &(JokerEffect){0}, sizeof(JokerEffect)) != 0)
bool joker_object_score(JokerObject *joker_object, Card* scored_card, enum JokerEvent joker_event, int *chips, int *mult, int *money, bool *retrigger)
{
if (joker_object == NULL)
{
*chips += joker_effect.chips;
*mult += joker_effect.mult;
*mult *= joker_effect.xmult > 0 ? joker_effect.xmult : 1; // if xmult is zero, DO NOT multiply by it
*money += joker_effect.money;
// TODO: Retrigger
const int joker_score_display_offset_px = (MAX_CARD_SCORE_STR_LEN + 1)*TTE_CHAR_SIZE;
// + 1 For space
int cursorPosX = fx2int(joker_object->sprite_object->x) + 8; // Offset of 16 pixels to center the text on the card
if (joker_effect.chips > 0)
{
char score_buffer[INT_MAX_DIGITS + 2]; // For '+' and null terminator
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
tte_set_special(0xD000); // Blue
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.chips);
tte_write(score_buffer);
cursorPosX += joker_score_display_offset_px;
}
if (joker_effect.mult > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
tte_set_special(0xE000); // Red
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.mult);
tte_write(score_buffer);
cursorPosX += joker_score_display_offset_px;
}
if (joker_effect.xmult > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
tte_set_special(0xE000); // Red
snprintf(score_buffer, sizeof(score_buffer), "X%d", joker_effect.xmult);
tte_write(score_buffer);
cursorPosX += joker_score_display_offset_px;
}
if (joker_effect.money > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
tte_set_special(0xC000); // Yellow
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.money);
tte_write(score_buffer);
cursorPosX += joker_score_display_offset_px;
}
joker_object->joker->processed = true; // Mark the joker as processed
joker_object_shake(joker_object, SFX_CARD_SELECT); // TODO: Add a sound effect for scoring the joker
return true;
return false;
}
return false;
JokerEffect joker_effect = joker_get_score_effect(joker_object->joker, scored_card, joker_event);
if (memcmp(&joker_effect, &(JokerEffect){0}, sizeof(JokerEffect)) == 0)
{
return false;
}
*chips += joker_effect.chips;
*mult += joker_effect.mult;
*mult *= joker_effect.xmult > 0 ? joker_effect.xmult : 1; // if xmult is zero, DO NOT multiply by it
*money += joker_effect.money;
*retrigger = joker_effect.retrigger;
// joker_effect.message will have been set if the Joker had anything custom to say
int cursorPosX = fx2int(joker_object->sprite_object->x) + 8; // Offset of 16 pixels to center the text on the card
if (joker_effect.chips > 0)
{
char score_buffer[INT_MAX_DIGITS + 2]; // For '+' and null terminator
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.chips);
set_and_shift_text(score_buffer, &cursorPosX, TTE_BLUE_PB);
}
if (joker_effect.mult > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.mult);
set_and_shift_text(score_buffer, &cursorPosX, TTE_RED_PB);
}
if (joker_effect.xmult > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
snprintf(score_buffer, sizeof(score_buffer), "X%d", joker_effect.xmult);
set_and_shift_text(score_buffer, &cursorPosX, TTE_RED_PB);
}
if (joker_effect.money > 0)
{
char score_buffer[INT_MAX_DIGITS + 2];
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.money);
set_and_shift_text(score_buffer, &cursorPosX, TTE_YELLOW_PB);
}
// custom message for Jokers (including retriggers where Jokers will say "Again!")
if (joker_effect.message[0] != '\0') // Message is not empty
{
set_and_shift_text(joker_effect.message, &cursorPosX, TTE_WHITE_PB);
}
if (joker_effect.expire)
{
// TODO make Jokers expire
}
joker_object_shake(joker_object, SFX_CARD_SELECT); // TODO: Add a sound effect for scoring the joker
return true;
}
void joker_object_set_selected(JokerObject* joker_object, bool selected)
+627 -214
View File
File diff suppressed because it is too large Load Diff