diff --git a/graphics/joker_gfx21.png b/graphics/joker_gfx21.png index 9138213..9ee842c 100644 Binary files a/graphics/joker_gfx21.png and b/graphics/joker_gfx21.png differ diff --git a/graphics/joker_gfx22.grit b/graphics/joker_gfx22.grit new file mode 100644 index 0000000..a8ac3f6 --- /dev/null +++ b/graphics/joker_gfx22.grit @@ -0,0 +1 @@ +-gB4 -Mw4 -Mh4 -m! -pn 16 \ No newline at end of file diff --git a/graphics/joker_gfx22.png b/graphics/joker_gfx22.png new file mode 100644 index 0000000..2ca7374 Binary files /dev/null and b/graphics/joker_gfx22.png differ diff --git a/graphics/joker_gfx23.grit b/graphics/joker_gfx23.grit new file mode 100644 index 0000000..a8ac3f6 --- /dev/null +++ b/graphics/joker_gfx23.grit @@ -0,0 +1 @@ +-gB4 -Mw4 -Mh4 -m! -pn 16 \ No newline at end of file diff --git a/graphics/joker_gfx23.png b/graphics/joker_gfx23.png new file mode 100644 index 0000000..3f81288 Binary files /dev/null and b/graphics/joker_gfx23.png differ diff --git a/include/def_joker_gfx_table.h b/include/def_joker_gfx_table.h index 0abdacc..6bf2f3a 100644 --- a/include/def_joker_gfx_table.h +++ b/include/def_joker_gfx_table.h @@ -19,4 +19,6 @@ DEF_JOKER_GFX(17) DEF_JOKER_GFX(18) DEF_JOKER_GFX(19) DEF_JOKER_GFX(20) -DEF_JOKER_GFX(21) \ No newline at end of file +DEF_JOKER_GFX(21) +DEF_JOKER_GFX(22) +DEF_JOKER_GFX(23) \ No newline at end of file diff --git a/include/game.h b/include/game.h index 7faf579..a4eb99c 100644 --- a/include/game.h +++ b/include/game.h @@ -53,7 +53,8 @@ enum HandState enum PlayState { PLAY_PLAYING, - PLAY_SCORING, + PLAY_SCORING_CARDS, + PLAY_SCORING_JOKERS, PLAY_ENDING, PLAY_ENDED }; @@ -104,6 +105,7 @@ int get_hand_top(void); int hand_get_size(void); CardObject** get_played_array(void); int get_played_top(void); +int get_scored_card_index(void); List* get_jokers(void); bool is_joker_owned(int joker_id); bool card_is_face(Card *card); diff --git a/include/graphic_utils.h b/include/graphic_utils.h index edd1214..f0da0f1 100644 --- a/include/graphic_utils.h +++ b/include/graphic_utils.h @@ -33,6 +33,7 @@ #define TTE_BLUE_PB 13 // 0xD #define TTE_RED_PB 14 // 0xE #define TTE_WHITE_PB 15 // 0xF +#define TTE_SPECIAL_PB_MULT_OFFSET 0x1000 //TODO: Change to a better name? #define TEXT_CLR_YELLOW RGB15(31, 20, 0) // 0x029F #define TEXT_CLR_BLUE RGB15(0, 18, 31) // 0x7E40 diff --git a/include/joker.h b/include/joker.h index 9a21249..d69f6c3 100644 --- a/include/joker.h +++ b/include/joker.h @@ -43,21 +43,45 @@ #define RARE_JOKER_CHANCE 5 #define LEGENDARY_JOKER_CHANCE 0 +// These are the common Joker Events. Special Joker behaviour will be checked on a +// Joker per Joker basis (see if it's there, then do something, e.g. Pareidolia, Baseball Card) +enum JokerEvent +{ + JOKER_EVENT_ON_HAND_PLAYED, // Triggers only once when the hand is played + JOKER_EVENT_ON_CARD_SCORED, // Triggers when a played card scores (e.g. Walkie Talkie, Fibonnacci...) + JOKER_EVENT_ON_CARD_SCORED_END, // Triggers after the card has finishd scoring (e.g. retrigger Jokers) + JOKER_EVENT_INDEPENDENT, // Joker will trigger normally, when Jokers are scored (e.g. base Joker) + JOKER_EVENT_ON_HAND_SCORED_END, // Triggers when entire hand has finished scoring (e.g. food Jokers) + JOKER_EVENT_ON_HAND_DISCARDED, // Triggers when discarding a hand + JOKER_EVENT_ON_ROUND_END, // Triggers at the end of the round (e.g. Rocket) + JOKER_EVENT_ON_BLIND_SELECTED, // Triggers when selecting a blind (e.g. Dagger, Riff Raff, Madness..) +}; + #define MAX_JOKER_OBJECTS 32 // The maximum number of joker objects that can be created at once +// Jokers in the game #define DEFAULT_JOKER_ID 0 -#define GREEDY_JOKER_ID 1 // This is just an example to show the patern of making joker IDs +#define GREEDY_JOKER_ID 1 #define JOKER_STENCIL_ID 16 #define PAREIDOLIA_JOKER_ID 30 #define JOKER_BRAINSTORM_ID 40 + typedef struct { u8 id; // Unique ID for the joker, used to identify different jokers u8 modifier; // base, foil, holo, poly, negative u8 value; u8 rarity; - bool processed; + // General purpose values that are interpreted differently for each Joker (scaling, last retriggered card, etc...) + union + { + s32 data; + struct { + s16 data0; + s16 data1; + } halves; + }; } Joker; typedef struct JokerObject @@ -73,13 +97,17 @@ typedef struct // These jokers are triggered after the played hand has finished int xmult; int money; bool retrigger; // Retrigger played hand (e.g. "Dusk" joker, even though on the wiki it says "On Scored" it makes more sense to have it here) + bool expire; // Joker is destroyed (food jokers) + char* message; // Used to send custom messages e.g. "Extinct" or "-1" (Bananas and food Jokers) } JokerEffect; -typedef JokerEffect (*JokerEffectFunc)(Joker *joker, Card *scored_card); +typedef JokerEffect (*JokerEffectFunc)(Joker *joker, Card *scored_card, enum JokerEvent joker_event); +typedef void (*JokerCallbackOnCreated)(Joker *joker); typedef struct { u8 rarity; u8 base_value; - JokerEffectFunc effect; + JokerEffectFunc joker_effect; + JokerCallbackOnCreated on_joker_created; } JokerInfo; const JokerInfo* get_joker_registry_entry(int joker_id); size_t get_joker_registry_size(void); @@ -91,14 +119,14 @@ void joker_destroy(Joker **joker); // Unique effects like "Four Fingers" or "Credit Card" will be hard coded into game.c with a conditional check for the joker ID from the players owned jokers // game.c should probably be restructured so most of the variables in it are moved to some sort of global variable header file so they can be easily accessed and modified for the jokers -JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card); +JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event); int joker_get_sell_value(const Joker* joker); JokerObject *joker_object_new(Joker *joker); void joker_object_destroy(JokerObject **joker_object); void joker_object_update(JokerObject *joker_object); void joker_object_shake(JokerObject *joker_object, mm_word sound_id); // This doesn't actually score anything, it just performs an animation and plays a sound effect -bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips, int *mult, int *xmult, int *money, bool *retrigger); // This scores the joker and returns true if it was scored successfully (Card = NULL means the joker is independent and not scored by a card) +bool joker_object_score(JokerObject *joker_object, Card* scored_card, enum JokerEvent joker_event, int *chips, int *mult, int *money, bool *retrigger); // This scores the joker and returns true if it was scored successfully (Card = NULL means the joker is independent and not scored by a card) void joker_object_set_selected(JokerObject* joker_object, bool selected); bool joker_object_is_selected(JokerObject* joker_object); diff --git a/include/joker_gfx.h b/include/joker_gfx.h index f7c1155..bb4a046 100644 --- a/include/joker_gfx.h +++ b/include/joker_gfx.h @@ -23,5 +23,7 @@ #include "joker_gfx19.h" #include "joker_gfx20.h" #include "joker_gfx21.h" +#include "joker_gfx22.h" +#include "joker_gfx23.h" #endif \ No newline at end of file diff --git a/include/util.h b/include/util.h index 7d9b915..3899a68 100644 --- a/include/util.h +++ b/include/util.h @@ -37,7 +37,7 @@ static inline int get_digits_even(int n) #define NUM_ELEM_IN_ARR(arr) (sizeof(arr) / sizeof((arr)[0])) -#define INT_MAX_DIGITS 10 // strlen(str(INT_MAX)) = strlen("2147483647") +#define INT_MAX_DIGITS 11 // strlen(str(INT_MAX)) = strlen("-2147483647") int int_arr_max(int int_arr[], int size); diff --git a/source/game.c b/source/game.c index 7c78d4d..91a61b3 100644 --- a/source/game.c +++ b/source/game.c @@ -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 } diff --git a/source/joker.c b/source/joker.c index b248a87..da64276 100644 --- a/source/joker.c +++ b/source/joker.c @@ -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) diff --git a/source/joker_effects.c b/source/joker_effects.c index f731f9b..054891d 100644 --- a/source/joker_effects.c +++ b/source/joker_effects.c @@ -5,39 +5,69 @@ #include "list.h" #include -static JokerEffect default_joker_effect(Joker *joker, Card *scored_card) { + +#define SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, restricted_event, checked_event, effect) \ +if (checked_event != restricted_event || scored_card == NULL) \ +{ \ + return effect; \ +} +#define SCORE_ON_EVENT_ONLY(restricted_event, checked_event, effect) \ +if (checked_event != restricted_event) \ +{ \ + return effect; \ +} + +// Joker Effect functions + +static JokerEffect default_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) effect.mult = 4; + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) + + effect.mult = 4; return effect; } -static JokerEffect sinful_joker_effect(Card *scored_card, u8 sinful_suit) { +static JokerEffect sinful_joker_effect(Card *scored_card, u8 sinful_suit, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL && scored_card->suit == sinful_suit) + + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + if (scored_card->suit == sinful_suit) + { effect.mult = 3; + } + return effect; } -static JokerEffect greedy_joker_effect(Joker *joker, Card *scored_card) { - return sinful_joker_effect(scored_card, DIAMONDS); +static JokerEffect greedy_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + return sinful_joker_effect(scored_card, DIAMONDS, joker_event); } -static JokerEffect lusty_joker_effect(Joker *joker, Card *scored_card) { - return sinful_joker_effect(scored_card, HEARTS); +static JokerEffect lusty_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + return sinful_joker_effect(scored_card, HEARTS, joker_event); } -static JokerEffect wrathful_joker_effect(Joker *joker, Card *scored_card) { - return sinful_joker_effect(scored_card, SPADES); +static JokerEffect wrathful_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + return sinful_joker_effect(scored_card, SPADES, joker_event); } -static JokerEffect gluttonous_joker_effect(Joker *joker, Card *scored_card) { - return sinful_joker_effect(scored_card, CLUBS); +static JokerEffect gluttonous_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + return sinful_joker_effect(scored_card, CLUBS, joker_event); } -static JokerEffect jolly_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect jolly_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) // This is really inefficient but the only way at the moment to check for whole-hand conditions u8 suits[NUM_SUITS]; @@ -45,152 +75,196 @@ static JokerEffect jolly_joker_effect(Joker *joker, Card *scored_card) { get_played_distribution(ranks, suits); if (hand_contains_n_of_a_kind(ranks) >= 2) + { effect.mult = 8; + } + return effect; } -static JokerEffect zany_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect zany_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) + + // 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) + { effect.mult = 12; + } + return effect; } -static JokerEffect mad_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect mad_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_two_pair(ranks)) + { effect.mult = 10; + } + return effect; } -static JokerEffect crazy_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect crazy_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_straight(ranks)) + { effect.mult = 12; + } + return effect; } -static JokerEffect droll_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect droll_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_flush(suits)) + { effect.mult = 10; + } + return effect; } -static JokerEffect sly_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect sly_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_n_of_a_kind(ranks) >= 2) + { effect.chips = 50; + } + return effect; } -static JokerEffect wily_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect wily_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_n_of_a_kind(ranks) >= 3) + { effect.chips = 100; + } + return effect; } -static JokerEffect clever_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect clever_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_two_pair(ranks)) + { effect.chips = 80; + } + return effect; } -static JokerEffect devious_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect devious_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_straight(ranks)) + { effect.chips = 100; + } + return effect; } -static JokerEffect crafty_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect crafty_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_flush(suits)) + { effect.chips = 80; + } + return effect; } -static JokerEffect half_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect half_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) int played_size = get_played_top() + 1; if (played_size <= 3) + { effect.mult = 20; + } return effect; } -static JokerEffect joker_stencil_effect(Joker *joker, Card *scored_card) { +static JokerEffect joker_stencil_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) List* jokers = get_jokers(); @@ -205,44 +279,56 @@ static JokerEffect joker_stencil_effect(Joker *joker, Card *scored_card) { { JokerObject* joker_object = list_get(jokers, i); if (joker_object->joker->id == JOKER_STENCIL_ID) + { effect.xmult++; + } } return effect; } #define MISPRINT_MAX_MULT 23 -static JokerEffect misprint_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect misprint_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) effect.mult = random() % (MISPRINT_MAX_MULT + 1); return effect; } -static JokerEffect walkie_talkie_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect walkie_talkie_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; - if (scored_card->rank == TEN || scored_card->rank == FOUR) { - effect.chips = 10; - effect.mult = 4; + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + if (scored_card->rank == TEN || scored_card->rank == FOUR) + { + effect.chips = 10; + effect.mult = 4; } - + return effect; } -static JokerEffect fibonnaci_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect fibonnaci_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; - switch (scored_card->rank) { - case ACE: case TWO: case THREE: case FIVE: case EIGHT: + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + switch (scored_card->rank) + { + case ACE: + case TWO: + case THREE: + case FIVE: + case EIGHT: effect.mult = 8; + break; default: break; } @@ -250,31 +336,36 @@ static JokerEffect fibonnaci_joker_effect(Joker *joker, Card *scored_card) { return effect; } -static JokerEffect banner_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect banner_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) effect.chips = 30 * get_num_discards_remaining(); return effect; } -static JokerEffect mystic_summit_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect mystic_summit_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) if (get_num_discards_remaining() == 0) + { effect.mult = 15; + } return effect; } -static JokerEffect blackboard_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect blackboard_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) bool all_cards_are_spades_or_clubs = true; CardObject** hand = get_hand_array(); @@ -282,7 +373,8 @@ static JokerEffect blackboard_joker_effect(Joker *joker, Card *scored_card) { for (int i = 0; i < hand_size; i++ ) { u8 suit = hand[i]->card->suit; - if (suit == HEARTS || suit == DIAMONDS) { + if (suit == HEARTS || suit == DIAMONDS) + { all_cards_are_spades_or_clubs = false; break; } @@ -294,21 +386,22 @@ static JokerEffect blackboard_joker_effect(Joker *joker, Card *scored_card) { return effect; } -static JokerEffect blue_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect blue_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) effect.chips = (get_deck_top() + 1) * 2; return effect; } -static JokerEffect raised_fist_joker_effect(Joker *joker, Card *scored_card) +static JokerEffect raised_fist_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) { JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) // Find the lowest rank card in hand // Aces are always considered high value, even in an ace-low straight @@ -328,41 +421,48 @@ static JokerEffect raised_fist_joker_effect(Joker *joker, Card *scored_card) return effect; } -static JokerEffect reserved_parking_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect reserved_parking_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + // TODO: switch from INDEPENDENT to CARD_HELD when it's implemented + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) CardObject** hand = get_hand_array(); int hand_size = hand_get_size(); for (int i = 0; i < hand_size; i++ ) { - if ((random() % 2 == 0) && card_is_face(hand[i]->card)) { + if ((random() % 2 == 0) && card_is_face(hand[i]->card)) + { effect.money += 1; } } - + return effect; }; -static JokerEffect business_card_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect business_card_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; - if ((random() % 2 == 0) && card_is_face(scored_card)) { + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + if ((random() % 2 == 0) && card_is_face(scored_card)) + { effect.money = 2; } return effect; } -static JokerEffect scholar_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect scholar_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; - if (scored_card->rank == ACE) { + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + if (scored_card->rank == ACE) + { effect.chips = 20; effect.mult = 4; } @@ -370,22 +470,25 @@ static JokerEffect scholar_joker_effect(Joker *joker, Card *scored_card) { return effect; } -static JokerEffect scary_face_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect scary_face_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; - if (card_is_face(scored_card)) { + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + if (card_is_face(scored_card)) + { effect.chips = 30; } return effect; } -static JokerEffect abstract_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect abstract_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) // +1 xmult per occupied joker slot int num_jokers = list_get_size(get_jokers()); @@ -394,183 +497,295 @@ static JokerEffect abstract_joker_effect(Joker *joker, Card *scored_card) { return effect; } -static JokerEffect bull_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect bull_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) + effect.chips = get_money() * 2; return effect; } -static JokerEffect smiley_face_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect smiley_face_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; - if (card_is_face(scored_card)) { + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + if (card_is_face(scored_card)) + { effect.mult = 5; } return effect; } -static JokerEffect even_steven_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect even_steven_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; - if (card_get_value(scored_card) % 2 == 0) { - switch (scored_card->rank) { - case KING: case QUEEN: case JACK: - break; - default: + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + switch (scored_card->rank) + { + case KING: + case QUEEN: + case JACK: + break; + default: + if (card_get_value(scored_card) % 2 == 0) + { effect.mult = 4; - } + } + break; } return effect; } -static JokerEffect odd_todd_joker_effect(Joker *joker, Card *scored_card) { + +static JokerEffect odd_todd_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; + + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) if (card_get_value(scored_card) % 2 == 1) // todo test ace + { effect.chips = 31; + } return effect; } -__attribute__((unused)) -static JokerEffect acrobat_joker_effect(Joker *joker, Card *scored_card) { - JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; +static JokerEffect acrobat_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + JokerEffect effect = {0}; + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) + // 0 remaining hands mean we're scoring the last hand - if (get_num_hands_remaining() == 0) { + if (get_num_hands_remaining() == 0) + { effect.xmult = 3; } return effect; } -static JokerEffect the_duo_joker_effect(Joker *joker, Card *scored_card) { + +static JokerEffect hanging_chad_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + s32* p_remaining_retriggers = &(joker->data); + + switch (joker_event) + { + case JOKER_EVENT_ON_HAND_SCORED_END: + *p_remaining_retriggers = 2; + break; - // This is really inefficient but the only way at the moment to check for whole-hand conditions + // No need to check if this is the first card scored or not + // p_remaining_retriggers will always reach 0 on the first card, then retrigger + // will be false and scoring will go onto the next card + case JOKER_EVENT_ON_CARD_SCORED_END: + effect.retrigger = (*p_remaining_retriggers > 0); + *p_remaining_retriggers -= 1; + if (effect.retrigger) + { + effect.message = "Again!"; + } + break; + + default: + break; + } + + return effect; +} + + +static JokerEffect the_duo_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + JokerEffect effect = {0}; + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) + + // 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) + { effect.xmult = 2; - return effect; - } + } -static JokerEffect the_trio_joker_effect(Joker *joker, Card *scored_card) { + return effect; +} + + +static JokerEffect the_trio_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet - - // This is really inefficient but the only way at the moment to check for whole-hand conditions + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) + + // 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) + { effect.xmult = 3; + } return effect; - } +} -static JokerEffect the_family_joker_effect(Joker *joker, Card *scored_card) { + +static JokerEffect the_family_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) - // This is really inefficient but the only way at the moment to check for whole-hand conditions + // 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) + { effect.xmult = 4; + } + return effect; } -static JokerEffect the_order_joker_effect(Joker *joker, Card *scored_card) { + +static JokerEffect the_order_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_straight(ranks)) + { effect.xmult = 3; + } + return effect; } -static JokerEffect the_tribe_joker_effect(Joker *joker, Card *scored_card) { + +static JokerEffect the_tribe_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) u8 suits[NUM_SUITS]; u8 ranks[NUM_RANKS]; get_played_distribution(ranks, suits); if (hand_contains_flush(suits)) + { effect.xmult = 2; + } + return effect; } -static JokerEffect bootstraps_joker_effect(Joker *joker, Card *scored_card) { + +static JokerEffect bootstraps_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet + + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) effect.mult = (get_money() / 5) * 2; return effect; } + // Using __attribute__((unused)) for jokers with no sprites yet to avoid warning // Remove the attribute once they have sprites // no graphics available but ready to be used if wanted when graphics available __attribute__((unused)) -static JokerEffect shoot_the_moon_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect shoot_the_moon_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card != NULL) - return effect; // if card != null, we are not at the end-phase of scoring yet - + + // TODO: switch from CARD_SCORED to CARD_HELD when triggering held cards is implemented + SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) + CardObject** hand = get_hand_array(); int hand_size = hand_get_size(); for (int i = 0; i < hand_size; i++ ) { if (hand[i]->card->rank == QUEEN) { - effect.mult += 13; + effect.mult += 13; } } return effect; } + +__attribute__((unused)) +static JokerEffect photograph_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + JokerEffect effect = {0}; + s32* p_first_face_index = &(joker->data); + + switch (joker_event) + { + case JOKER_EVENT_ON_HAND_SCORED_END: + *p_first_face_index = UNDEFINED; + break; + + case JOKER_EVENT_ON_CARD_SCORED: + // has a face card been encountered already, and if not, is the current scoring card a face card? + if (*p_first_face_index == UNDEFINED && card_is_face(scored_card)) + { + *p_first_face_index = get_scored_card_index(); + } + // if we have a face card index saved, check against it and give mult accordingly + // Doing this now will trigger the effect the first time we encounter the face card, + // and we will catch potential retriggers + if (*p_first_face_index == get_scored_card_index()) + { + effect.xmult = 2; + } + break; + default: + break; + } + + return effect; +} + + // no graphics available but ready to be used if wanted when graphics available __attribute__((unused)) -static JokerEffect triboulet_joker_effect(Joker *joker, Card *scored_card) { +static JokerEffect triboulet_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; - if (scored_card == NULL) - return effect; - switch (scored_card->rank) { + SCORE_ON_EVENT_ONLY_WITH_CARD(scored_card, JOKER_EVENT_ON_CARD_SCORED, joker_event, effect) + + switch (scored_card->rank) + { case KING: case QUEEN: effect.xmult = 2; default: @@ -580,16 +795,53 @@ static JokerEffect triboulet_joker_effect(Joker *joker, Card *scored_card) { return effect; } -static JokerEffect blueprint_joker_effect(Joker *joker, Card *scored_card) { + +static JokerEffect dusk_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + JokerEffect effect = {0}; + s32* p_last_retriggered_index = &(joker->data); + + switch (joker_event) + { + case JOKER_EVENT_ON_HAND_SCORED_END: + *p_last_retriggered_index = UNDEFINED; + break; + + case JOKER_EVENT_ON_CARD_SCORED_END: + // Only retrigger current card if it's strictly after the last one we retriggered + if (get_num_hands_remaining() == 0) + { + effect.retrigger = (*p_last_retriggered_index < get_scored_card_index()); + if (effect.retrigger) + { + *p_last_retriggered_index = get_scored_card_index(); + effect.message = "Again!"; + } + } + + break; + + default: + break; + } + + return effect; +} + + +static JokerEffect blueprint_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; List* jokers = get_jokers(); int list_size = list_get_size(jokers); - for (int i = 0; i < list_size - 1; i++ ) { + for (int i = 0; i < list_size - 1; i++ ) + { JokerObject* curr_joker_object = list_get(jokers, i); - if (curr_joker_object->joker == joker) { + if (curr_joker_object->joker == joker) + { JokerObject* next_joker_object = list_get(jokers, i + 1); - effect = joker_get_score_effect(next_joker_object->joker, scored_card); + effect = joker_get_score_effect(next_joker_object->joker, scored_card, joker_event); break; } } @@ -597,25 +849,178 @@ static JokerEffect blueprint_joker_effect(Joker *joker, Card *scored_card) { return effect; } -static JokerEffect brainstorm_joker_effect(Joker *joker, Card *scored_card) { + +static JokerEffect brainstorm_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ JokerEffect effect = {0}; static bool in_brainstorm = false; if (in_brainstorm) + { return effect; + } List* jokers = get_jokers(); JokerObject* first_joker = list_get(jokers, 0); - if (first_joker != NULL && first_joker->joker->id != JOKER_BRAINSTORM_ID) { + if (first_joker != NULL && first_joker->joker->id != JOKER_BRAINSTORM_ID) + { // Static var to avoid infinite blueprint + brainstorm loops in_brainstorm = true; - effect = joker_get_score_effect(first_joker->joker, scored_card); + effect = joker_get_score_effect(first_joker->joker, scored_card, joker_event); in_brainstorm = false; } return effect; } + +static JokerEffect hack_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + JokerEffect effect = {0}; + s32* p_last_retriggered_index = &(joker->data); + + switch (joker_event) + { + case JOKER_EVENT_ON_HAND_SCORED_END: + *p_last_retriggered_index = UNDEFINED; + break; + + case JOKER_EVENT_ON_CARD_SCORED_END: + // Works the same way as Dusk, but check what rank the card is + switch (scored_card->rank) + { + case TWO: + case THREE: + case FOUR: + case FIVE: + effect.retrigger = (*p_last_retriggered_index < get_scored_card_index()); + if (effect.retrigger) + { + *p_last_retriggered_index = get_scored_card_index(); + effect.message = "Again!"; + } + break; + } + break; + + default: + break; + } + + return effect; +} + + +// Note: Joker expiration is not yet implemented so Seltzer cannot be made active before it does. +__attribute__((unused)) +static JokerEffect seltzer_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + JokerEffect effect = {0}; + s16* p_last_retriggered_idx = &(joker->halves.data0); + s16* p_hands_left_until_exp = &(joker->halves.data1); + + switch (joker_event) + { + case JOKER_EVENT_ON_HAND_SCORED_END: + *p_last_retriggered_idx = UNDEFINED; + *p_hands_left_until_exp -= 1; + if (*p_hands_left_until_exp <= 0) + { + effect.expire = true; + effect.message = "Drank!"; + } + else + { + effect.message = "-1"; + } + break; + + case JOKER_EVENT_ON_CARD_SCORED_END: + // Works the same way as Dusk, but if it can still trigger + if (*p_hands_left_until_exp > 0) + { + effect.retrigger = (*p_last_retriggered_idx < get_scored_card_index()); + if (effect.retrigger) + { + *p_last_retriggered_idx = get_scored_card_index(); + effect.message = "Again!"; + } + } + break; + + default: + break; + } + + return effect; +} + + +static JokerEffect sock_and_buskin_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) +{ + JokerEffect effect = {0}; + s32* p_last_retriggered_face_index = &(joker->data); + + switch (joker_event) + { + case JOKER_EVENT_ON_HAND_SCORED_END: + *p_last_retriggered_face_index = UNDEFINED; + break; + + case JOKER_EVENT_ON_CARD_SCORED_END: + // Works the same way as Dusk, but for face cards + effect.retrigger = (*p_last_retriggered_face_index < get_scored_card_index() && card_is_face(scored_card)); + if (effect.retrigger) + { + *p_last_retriggered_face_index = get_scored_card_index(); + effect.message = "Again!"; + } + break; + + default: + break; + } + + return effect; +} + +// ON JOKER CREATED Callbacks + +// For Jokers that don't need to do anything when created +void on_joker_created_noop(Joker *joker) {} + +void hanging_chad_on_joker_created(Joker *joker) +{ + joker->data = 2; // retriggers left, reset to 2 at round end +} + +void dusk_on_joker_created(Joker *joker) +{ + joker->data = UNDEFINED; // previously retriggered card index +} + +void hack_on_joker_created(Joker *joker) +{ + joker->data = UNDEFINED; // previously retriggered card index +} + +void photograph_on_joker_created(Joker *joker) +{ + joker->data = UNDEFINED; // First scoring face card index +} + +void sock_and_buskin_on_joker_created(Joker *joker) +{ + joker->data = UNDEFINED; // previously retriggered face card index +} + +void seltzer_on_joker_created(Joker *joker) +{ + joker->halves.data0 = UNDEFINED; // previously retriggered card index + joker->halves.data1 = 10; // remaining retriggered hands +} + + /* The index of a joker in the registry matches its ID. * The joker sprites are matched by ID so the position in the registry * determines the joker's sprite. @@ -626,69 +1031,77 @@ static JokerEffect brainstorm_joker_effect(Joker *joker, Card *scored_card) { * Otherwise the order is similar to the wiki. */ const JokerInfo joker_registry[] = { - { COMMON_JOKER, 2, default_joker_effect }, // DEFAULT_JOKER_ID = 0 - { COMMON_JOKER, 5, greedy_joker_effect }, // GREEDY_JOKER_ID = 1 - { COMMON_JOKER, 5, lusty_joker_effect }, // etc... 2 - { COMMON_JOKER, 5, wrathful_joker_effect }, // 3 - { COMMON_JOKER, 5, gluttonous_joker_effect }, // 4 - { COMMON_JOKER, 3, jolly_joker_effect }, // 5 - { COMMON_JOKER, 4, zany_joker_effect }, // 6 - { COMMON_JOKER, 4, mad_joker_effect }, // 7 - { COMMON_JOKER, 4, crazy_joker_effect }, // 8 - { COMMON_JOKER, 4, droll_joker_effect }, // 9 - { COMMON_JOKER, 3, sly_joker_effect }, // 10 - { COMMON_JOKER, 4, wily_joker_effect }, // 11 - { COMMON_JOKER, 4, clever_joker_effect }, // 12 - { COMMON_JOKER, 4, devious_joker_effect }, // 13 - { COMMON_JOKER, 4, crafty_joker_effect }, // 14 - { COMMON_JOKER, 5, half_joker_effect }, // 15 - { UNCOMMON_JOKER, 8, joker_stencil_effect }, // 16 - { COMMON_JOKER, 5, banner_joker_effect }, // 17 - { COMMON_JOKER, 4, walkie_talkie_joker_effect }, // 18 - { UNCOMMON_JOKER, 8, fibonnaci_joker_effect }, // 19 - { UNCOMMON_JOKER, 6, blackboard_joker_effect }, // 20 - { COMMON_JOKER, 5, mystic_summit_joker_effect }, // 21 - { COMMON_JOKER, 4, misprint_joker_effect }, // 22 - { COMMON_JOKER, 4, even_steven_joker_effect }, // 23 - { COMMON_JOKER, 5, blue_joker_effect }, // 24 - { COMMON_JOKER, 4, odd_todd_joker_effect }, // 25 - { COMMON_JOKER, 4, scholar_joker_effect }, // 26 - { COMMON_JOKER, 4, business_card_joker_effect }, // 27 + { COMMON_JOKER, 2, default_joker_effect, on_joker_created_noop }, // DEFAULT_JOKER_ID = 0 + { COMMON_JOKER, 5, greedy_joker_effect, on_joker_created_noop }, // GREEDY_JOKER_ID = 1 + { COMMON_JOKER, 5, lusty_joker_effect, on_joker_created_noop }, // etc... 2 + { COMMON_JOKER, 5, wrathful_joker_effect, on_joker_created_noop }, // 3 + { COMMON_JOKER, 5, gluttonous_joker_effect, on_joker_created_noop }, // 4 + { COMMON_JOKER, 3, jolly_joker_effect, on_joker_created_noop }, // 5 + { COMMON_JOKER, 4, zany_joker_effect, on_joker_created_noop }, // 6 + { COMMON_JOKER, 4, mad_joker_effect, on_joker_created_noop }, // 7 + { COMMON_JOKER, 4, crazy_joker_effect, on_joker_created_noop }, // 8 + { COMMON_JOKER, 4, droll_joker_effect, on_joker_created_noop }, // 9 + { COMMON_JOKER, 3, sly_joker_effect, on_joker_created_noop }, // 10 + { COMMON_JOKER, 4, wily_joker_effect, on_joker_created_noop }, // 11 + { COMMON_JOKER, 4, clever_joker_effect, on_joker_created_noop }, // 12 + { COMMON_JOKER, 4, devious_joker_effect, on_joker_created_noop }, // 13 + { COMMON_JOKER, 4, crafty_joker_effect, on_joker_created_noop }, // 14 + { COMMON_JOKER, 5, half_joker_effect, on_joker_created_noop }, // 15 + { UNCOMMON_JOKER, 8, joker_stencil_effect, on_joker_created_noop }, // 16 + { COMMON_JOKER, 5, banner_joker_effect, on_joker_created_noop }, // 17 + { COMMON_JOKER, 4, walkie_talkie_joker_effect, on_joker_created_noop }, // 18 + { UNCOMMON_JOKER, 8, fibonnaci_joker_effect, on_joker_created_noop }, // 19 + { UNCOMMON_JOKER, 6, blackboard_joker_effect, on_joker_created_noop }, // 20 + { COMMON_JOKER, 5, mystic_summit_joker_effect, on_joker_created_noop }, // 21 + { COMMON_JOKER, 4, misprint_joker_effect, on_joker_created_noop }, // 22 + { 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 + { 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 }, // 28 - { UNCOMMON_JOKER, 7, bootstraps_joker_effect}, // 29 - { UNCOMMON_JOKER, 5, NULL /* Pareidolia */ }, // 30 - { COMMON_JOKER, 6, reserved_parking_joker_effect }, // 31 - { COMMON_JOKER, 4, abstract_joker_effect }, // 32 - { UNCOMMON_JOKER, 6, bull_joker_effect}, // 33 - { RARE_JOKER, 8, the_duo_joker_effect}, // 34 - { RARE_JOKER, 8, the_trio_joker_effect}, // 35 - { RARE_JOKER, 8, the_family_joker_effect}, // 36 - { RARE_JOKER, 8, the_order_joker_effect}, // 37 - { RARE_JOKER, 8, the_tribe_joker_effect}, // 38 - { RARE_JOKER, 10, blueprint_joker_effect }, // 39 - { RARE_JOKER, 10, brainstorm_joker_effect }, // 40 - { COMMON_JOKER, 5, raised_fist_joker_effect }, // 41 - { COMMON_JOKER, 4, smiley_face_joker_effect }, // 42 + { COMMON_JOKER, 4, scary_face_joker_effect, on_joker_created_noop }, // 28 + { UNCOMMON_JOKER, 7, bootstraps_joker_effect, on_joker_created_noop }, // 29 + { UNCOMMON_JOKER, 5, NULL, on_joker_created_noop }, // 30 Pareidolia + { COMMON_JOKER, 6, reserved_parking_joker_effect, on_joker_created_noop }, // 31 + { COMMON_JOKER, 4, abstract_joker_effect, on_joker_created_noop }, // 32 + { UNCOMMON_JOKER, 6, bull_joker_effect, on_joker_created_noop }, // 33 + { RARE_JOKER, 8, the_duo_joker_effect, on_joker_created_noop }, // 34 + { RARE_JOKER, 8, the_trio_joker_effect, on_joker_created_noop }, // 35 + { RARE_JOKER, 8, the_family_joker_effect, on_joker_created_noop }, // 36 + { RARE_JOKER, 8, the_order_joker_effect, on_joker_created_noop }, // 37 + { RARE_JOKER, 8, the_tribe_joker_effect, on_joker_created_noop }, // 38 + { RARE_JOKER, 10, blueprint_joker_effect, on_joker_created_noop }, // 39 + { RARE_JOKER, 10, brainstorm_joker_effect, on_joker_created_noop }, // 40 + { COMMON_JOKER, 5, raised_fist_joker_effect, on_joker_created_noop }, // 41 + { COMMON_JOKER, 4, smiley_face_joker_effect, on_joker_created_noop }, // 42 + { UNCOMMON_JOKER, 6, acrobat_joker_effect, on_joker_created_noop }, // 43 + { UNCOMMON_JOKER, 5, dusk_joker_effect, dusk_on_joker_created }, // 44 + { 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 // The following jokers don't have sprites yet, // uncomment them when their sprites are added. #if 0 - - { UNCOMMON_JOKER, 6, acrobat_joker_effect }, - { COMMON_JOKER, 5, shoot_the_moon_joker_effect}, + { COMMON_JOKER, 5, photograph_joker_effect, photograph_on_joker_created }, + { UNCOMMON_JOKER, 6, seltzer_joker_effect, seltzer_on_joker_created }, + { COMMON_JOKER, 5, shoot_the_moon_joker_effect, on_joker_created_noop}, #endif }; static const size_t joker_registry_size = NUM_ELEM_IN_ARR(joker_registry); -const JokerInfo* get_joker_registry_entry(int joker_id) { - if (joker_id < 0 || (size_t)joker_id >= joker_registry_size) { +const JokerInfo* get_joker_registry_entry(int joker_id) +{ + if (joker_id < 0 || (size_t)joker_id >= joker_registry_size) + { return NULL; } return &joker_registry[joker_id]; } -size_t get_joker_registry_size(void) { +size_t get_joker_registry_size(void) +{ return joker_registry_size; }