diff --git a/.clang-format b/.clang-format index 80f4ddc..5bc0fca 100644 --- a/.clang-format +++ b/.clang-format @@ -13,7 +13,7 @@ AllowShortLoopsOnASingleLine: false PointerAlignment: Left # Column limit -ColumnLimit: 120 +ColumnLimit: 100 # Case labels IndentCaseLabels: true @@ -42,7 +42,8 @@ BinPackParameters: false AllowAllArgumentsOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false -AlignAfterOpenBracket: Align +AlignAfterOpenBracket: BlockIndent +PenaltyBreakBeforeFirstCallParameter: 0 ReflowComments: true SpacesInLineCommentPrefix: diff --git a/.github/workflows/run_tests_ci_workflow.yml b/.github/workflows/run_tests_ci_workflow.yml index 232b375..d807adc 100644 --- a/.github/workflows/run_tests_ci_workflow.yml +++ b/.github/workflows/run_tests_ci_workflow.yml @@ -22,6 +22,7 @@ jobs: include/bitset.h source/bitset.c include/pool.h source/pool.c include/card.h source/card.c + include/game.h source/game.c include/hand_analysis.h source/hand_analysis.c include/list.h source/list.c include/sprite.h source/sprite.c diff --git a/include/blind.h b/include/blind.h index 09a99c6..3538efa 100644 --- a/include/blind.h +++ b/include/blind.h @@ -2,7 +2,8 @@ #define BLIND_H #include "sprite.h" -// The GBA's max uint value is around 4 billion, so we're going to not add endless mode for simplicity's sake +// The GBA's max uint value is around 4 billion, so we're going to not add endless mode for +// simplicity's sake #define MAX_ANTE 8 #define SMALL_BLIND_PB 1 diff --git a/include/def_state_info_table.h b/include/def_state_info_table.h index 1b757d5..89693a2 100644 --- a/include/def_state_info_table.h +++ b/include/def_state_info_table.h @@ -1,9 +1,9 @@ // (stateEnum, on_init, on_update, on_exit) DEF_STATE_INFO(GAME_STATE_SPLASH_SCREEN, splash_screen_on_init, splash_screen_on_update, splash_screen_on_exit) -DEF_STATE_INFO(GAME_STATE_MAIN_MENU, game_main_menu_on_init, game_main_menu_on_update, _noop) -DEF_STATE_INFO(GAME_STATE_PLAYING, game_round_on_init, game_playing_on_update, _noop) -DEF_STATE_INFO(GAME_STATE_ROUND_END, _noop, game_round_end_on_update, game_round_end_on_exit) -DEF_STATE_INFO(GAME_STATE_SHOP, _noop, game_shop_on_update, game_shop_on_exit) -DEF_STATE_INFO(GAME_STATE_BLIND_SELECT, _noop, game_blind_select_on_update, game_blind_select_on_exit) +DEF_STATE_INFO(GAME_STATE_MAIN_MENU, game_main_menu_on_init, game_main_menu_on_update, noop) +DEF_STATE_INFO(GAME_STATE_PLAYING, game_round_on_init, game_playing_on_update, noop) +DEF_STATE_INFO(GAME_STATE_ROUND_END, noop, game_round_end_on_update, game_round_end_on_exit) +DEF_STATE_INFO(GAME_STATE_SHOP, noop, game_shop_on_update, game_shop_on_exit) +DEF_STATE_INFO(GAME_STATE_BLIND_SELECT, noop, game_blind_select_on_update, game_blind_select_on_exit) DEF_STATE_INFO(GAME_STATE_LOSE, game_lose_on_init, game_lose_on_update, game_over_on_exit) DEF_STATE_INFO(GAME_STATE_WIN, game_win_on_init, game_win_on_update, game_over_on_exit) diff --git a/include/game.h b/include/game.h index 141e284..8f05534 100644 --- a/include/game.h +++ b/include/game.h @@ -3,17 +3,33 @@ #include -#define MAX_HAND_SIZE 16 -#define MAX_DECK_SIZE 52 +#define MAX_HAND_SIZE 16 +#define MAX_DECK_SIZE 52 #define MAX_JOKERS_HELD_SIZE 5 // This doesn't account for negatives right now. -#define MAX_SHOP_JOKERS 2 // TODO: Make this dynamic and allow for other items besides jokers -#define MAX_SELECTION_SIZE 5 -#define FRAMES(x) (((x) + game_speed - 1) / game_speed) +#define MAX_SHOP_JOKERS 2 // TODO: Make this dynamic and allow for other items besides jokers +#define MAX_SELECTION_SIZE 5 +#define FRAMES(x) (((x) + game_speed - 1) / game_speed) - // TODO: Can make these dynamic to support interest-related jokers and vouchers -#define MAX_INTEREST 5 +// TODO: Can make these dynamic to support interest-related jokers and vouchers +#define MAX_INTEREST 5 #define INTEREST_PER_5 1 +// Input bindings +#define SELECT_CARD KEY_A +#define DESELECT_CARDS KEY_B +#define PEEK_DECK KEY_L // Not implemented +#define SORT_HAND KEY_R +#define PAUSE_GAME KEY_START // Not implemented +#define SELL_KEY KEY_L + +struct List; +typedef struct List List; + +// Utility functions for other files +typedef struct CardObject CardObject; +typedef struct Card Card; +typedef struct JokerObject JokerObject; + enum BackgroundId { BG_NONE, @@ -25,14 +41,6 @@ enum BackgroundId BG_MAIN_MENU }; -// Input bindings -#define SELECT_CARD KEY_A -#define DESELECT_CARDS KEY_B -#define PEEK_DECK KEY_L // Not implemented -#define SORT_HAND KEY_R -#define PAUSE_GAME KEY_START // Not implemented -#define SELL_KEY KEY_L - // Enum value names in ../include/def_state_info_table.h enum GameState { @@ -47,7 +55,9 @@ enum HandState { HAND_DRAW, HAND_SELECT, - HAND_SHUFFLING, // This is actually a misnomer because it's used for the deck, but it mechanically makes sense to be a state of the hand + // This is actually a misnomer because it's used for the deck + // but it mechanically makes sense to be a state of the hand + HAND_SHUFFLING, HAND_DISCARD, HAND_PLAY, HAND_PLAYING @@ -85,7 +95,7 @@ enum HandType FLUSH_FIVE }; -typedef struct +typedef struct { int substate; void (*on_init)(); @@ -98,40 +108,31 @@ void game_init(); void game_update(); void game_change_state(enum GameState new_game_state); -struct List; -typedef struct List List; - -// Utility functions for other files -typedef struct CardObject CardObject; // forward declaration, actually declared in card.h -typedef struct Card Card; -typedef struct JokerObject JokerObject; - -CardObject** get_hand_array(void); -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); -bool is_joker_owned(int joker_id); -bool card_is_face(Card *card); -List* get_jokers_list(void); -List* get_expired_jokers_list(void); +CardObject** get_hand_array(void); +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); +bool is_joker_owned(int joker_id); +bool card_is_face(Card* card); +List* get_jokers_list(void); +List* get_expired_jokers_list(void); int get_deck_top(void); int get_num_discards_remaining(void); int get_num_hands_remaining(void); -u32 get_chips(void); -void set_chips(u32 new_chips); -void display_chips(); -u32 get_mult(void); -void set_mult(u32 new_mult); -void display_mult(); -int get_money(void); -void set_money(int new_money); -void display_money(); -void set_retrigger(bool new_retrigger); - +u32 get_chips(void); +void set_chips(u32 new_chips); +void display_chips(); +u32 get_mult(void); +void set_mult(u32 new_mult); +void display_mult(); +int get_money(void); +void set_money(int new_money); +void display_money(); +void set_retrigger(bool new_retrigger); int get_game_speed(void); void set_game_speed(int new_game_speed); diff --git a/include/hand_analysis.h b/include/hand_analysis.h index b144ae8..4b7f2ec 100644 --- a/include/hand_analysis.h +++ b/include/hand_analysis.h @@ -15,7 +15,13 @@ 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); +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 diff --git a/include/list.h b/include/list.h index d96f1b4..21e0df7 100644 --- a/include/list.h +++ b/include/list.h @@ -5,8 +5,8 @@ * List Implementation * =================== * - * - This @ref List operates as a linked list @ref ListNodes. It operates as a regular doubly-linked list - * but doesn't allocate memory and rather gets @ref ListNodes from a pool. + * - This @ref List operates as a linked list @ref ListNodes. It operates as a regular + * doubly-linked list but doesn't allocate memory and rather gets @ref ListNodes from a pool. */ #ifndef LIST_H #define LIST_H @@ -23,7 +23,8 @@ typedef struct ListNode ListNode; struct ListNode { /** - * @brief The previous @ref ListNode in the associated @ref List, NULL if at the `head` of the list + * @brief The previous @ref ListNode in the associated @ref List, NULL if at the `head` of the + * list */ ListNode* prev; @@ -99,8 +100,9 @@ typedef struct /** * Create a list. * - * While this function does not allocate memory for the list itself, the list does allocate memory for each element. - * So every created list must be freed with @ref list_clear to ensure the list's nodes are deleted properly. + * While this function does not allocate memory for the list itself, the list does allocate memory + * for each element. So every created list must be freed with @ref list_clear to ensure the list's + * nodes are deleted properly. * * @return A @ref List with head and tail reset. */ diff --git a/include/selection_grid.h b/include/selection_grid.h index 9ddd2bf..40ea950 100644 --- a/include/selection_grid.h +++ b/include/selection_grid.h @@ -27,7 +27,7 @@ struct SelectionGridRow struct SelectionGrid { - SelectionGridRow* rows; + const SelectionGridRow* rows; const int num_rows; Selection selection; }; @@ -38,4 +38,4 @@ void selection_grid_process_input(SelectionGrid* selection_grid); void selection_grid_move_selection_horz(SelectionGrid* selection_grid, int direction_tribool); void selection_grid_move_selection_vert(SelectionGrid* selection_grid, int direction_tribool); -#endif \ No newline at end of file +#endif diff --git a/source/bitset.c b/source/bitset.c index 51dca2b..356bce7 100644 --- a/source/bitset.c +++ b/source/bitset.c @@ -34,10 +34,11 @@ int bitset_set_next_free_idx(Bitset* bitset) // https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html#index-_005f_005fbuiltin_005fctz // // By using the bitwise inverse of the word, you can skip words that are full - // quickly (where the value is 0 or 'false' since all bits are '1', or 'in use'). Any value greater - // than 0 indicates there is a free slot. Then, when counting the trailing 0's, you can test very quickly - // where the first free slot is. This operation prevents looping through every bit of filled flags, and - // will instead operate only on the first word with free slots. + // quickly (where the value is 0 or 'false' since all bits are '1', or 'in use'). Any value + // greater than 0 indicates there is a free slot. Then, when counting the trailing 0's, you + // can test very quickly where the first free slot is. This operation prevents looping + // through every bit of filled flags, and will instead operate only on the first word with + // free slots. if (inv) { int bit = __builtin_ctz(inv); @@ -100,7 +101,8 @@ int bitset_find_idx_of_nth_set(const Bitset* bitset, int n) if (tracker > n) { // The index is here somewhere - // this one is to count the 1's not the offset, underflow to -1 is good for finding the 0 index + // this one is to count the 1's not the offset, underflow to -1 is good for finding the + // 0 index int base = prev_tracker - 1; // this one is for the actual offset we want to map the id to int offset = bitset->nbits * i; diff --git a/source/card.c b/source/card.c index ec3c39c..cbfc2d6 100644 --- a/source/card.c +++ b/source/card.c @@ -10,7 +10,8 @@ #include "pool.h" #include "soundbank.h" -// Card sprites lookup table. First index is the suit, second index is the rank. The value is the tile index. +// Card sprites lookup table. First index is the suit, second index is the rank. The value is the +// tile index. const static u16 _card_sprite_lut[NUM_SUITS][NUM_RANKS] = { {0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192}, {208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400}, @@ -88,11 +89,19 @@ void card_object_update(CardObject* card_object) void card_object_set_sprite(CardObject* card_object, int layer) { int tile_index = CARD_TID + (layer * CARD_SPRITE_OFFSET); - memcpy32(&tile_mem[4][tile_index], - &deck_gfxTiles[_card_sprite_lut[card_object->card->suit][card_object->card->rank] * TILE_SIZE], - TILE_SIZE * CARD_SPRITE_OFFSET); - Sprite* sprite = - sprite_new(ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, ATTR1_SIZE_32, tile_index, 0, layer + CARD_STARTING_LAYER); + memcpy32( + &tile_mem[4][tile_index], + &deck_gfxTiles + [_card_sprite_lut[card_object->card->suit][card_object->card->rank] * TILE_SIZE], + TILE_SIZE * CARD_SPRITE_OFFSET + ); + Sprite* sprite = sprite_new( + ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, + ATTR1_SIZE_32, + tile_index, + 0, + layer + CARD_STARTING_LAYER + ); sprite_object_set_sprite(card_object->sprite_object, sprite); } diff --git a/source/game.c b/source/game.c index 5da5bca..76e5d57 100644 --- a/source/game.c +++ b/source/game.c @@ -1,42 +1,148 @@ #include "game.h" +#include "affine_background.h" +#include "affine_background_gfx.h" +#include "audio_utils.h" +#include "background_blind_select_gfx.h" +#include "background_gfx.h" +#include "background_main_menu_gfx.h" +#include "background_shop_gfx.h" +#include "bitset.h" +#include "blind.h" +#include "card.h" +#include "graphic_utils.h" +#include "hand_analysis.h" +#include "joker.h" +#include "list.h" +#include "selection_grid.h" +#include "soundbank.h" +#include "splash_screen.h" +#include "sprite.h" +#include "tonc_memdef.h" +#include "util.h" + #include #include #include -#include "bitset.h" -#include "tonc_memdef.h" -#include "util.h" -#include "sprite.h" -#include "card.h" -#include "hand_analysis.h" -#include "blind.h" -#include "joker.h" -#include "affine_background.h" -#include "graphic_utils.h" -#include "audio_utils.h" -#include "selection_grid.h" -#include "splash_screen.h" +#define STRAIGHT_AND_FLUSH_SIZE_FOUR_FINGERS 4 +#define STRAIGHT_AND_FLUSH_SIZE_DEFAULT 5 -#include "background_gfx.h" -#include "background_shop_gfx.h" -#include "background_blind_select_gfx.h" -#include "affine_background_gfx.h" -#include "background_main_menu_gfx.h" +// Pixel sizes +#define ITEM_SHOP_Y 71 +#define ROUND_END_REWARD_AMOUNT_X 168 +#define ROUND_END_REWARD_TEXT_X 88 +#define SCORED_CARD_TEXT_Y 48 -#include "soundbank.h" +// SE sizes +#define ROUND_END_BLACK_PANEL_INIT_BOTTOM_SE 12 -#include "list.h" +#define MAIN_MENU_BUTTONS 2 +#define MAIN_MENU_IMPLEMENTED_BUTTONS 1 // Remove this once all buttons are implemented -typedef enum +// TODO: Properly define and use +#define MENU_POP_OUT_ANIM_FRAMES 20 +#define GAME_OVER_ANIM_FRAMES 15 + +#define HIGHLIGHT_COLOR 0xFFFF +#define SHOP_LIGHTS_1_CLR 0xFFFF +#define SHOP_LIGHTS_2_CLR 0x32BE +#define SHOP_LIGHTS_3_CLR 0x4B5F +#define SHOP_LIGHTS_4_CLR 0x5F9F + +#define PITCH_STEP_DISCARD_SFX (-64) +#define PITCH_STEP_DRAW_SFX 24 +#define PITCH_STEP_UNDISCARD_SFX 2 * PITCH_STEP_DRAW_SFX + +#define STARTING_ROUND 0 +#define STARTING_ANTE 1 +#define STARTING_MONEY 4 +#define STARTING_SCORE 0 + +#define TEN_K 10000 +#define ONE_K 1000 + +#define CARD_FOCUSED_UNSEL_Y 10 +#define CARD_UNFOCUSED_SEL_Y 15 +#define CARD_FOCUSED_SEL_Y 20 + +// Timer defs +#define TM_ZERO 0 +#define TM_RESET_STATIC_VARS 30 +#define TM_END_POP_MENU_ANIM 13 +#define TM_START_ROUND_END_REWARDS_ANIM 1 +#define TM_END_DISPLAY_FIN_BLIND 30 +#define TM_END_DISPLAY_SCORE_MIN 4 +#define TM_REWARDS_ELLIPSIS_PRINT_START 2 +#define TM_REWARDS_ELLIPSIS_PRINT_END 16 +#define TM_REWARD_DISPLAY_INTERVAL 15 +#define TM_DISPLAY_REWARDS_CONT_WAIT TM_REWARDS_ELLIPSIS_PRINT_END + TM_REWARD_DISPLAY_INTERVAL +#define TM_HAND_REWARD_INCR_WAIT TM_DISPLAY_REWARDS_CONT_WAIT + TM_REWARD_DISPLAY_INTERVAL +#define TM_REWARD_INCREMENT_INTERVAL 20 +#define TM_DISMISS_ROUND_END_TM 20 +#define TM_CREATE_SHOP_ITEMS_WAIT 1 +#define TM_SHIFT_SHOP_ICON_WAIT 7 +#define TM_END_GAME_SHOP_INTRO 12 +#define TM_SHOP_PRC_INPUT_START 1 +#define TM_DISP_BLIND_PANEL_FINISH 7 +#define TM_DISP_BLIND_PANEL_START 1 +#define TM_BLIND_SELECT_START 1 +#define TM_END_ANIM_SEQ 12 + +// Palette IDs +#define BOSS_BLIND_PRIMARY_PID 1 +#define MAIN_MENU_PLAY_BUTTON_OUTLINE_PID 2 +#define REROLL_BTN_PID 3 +#define BLIND_SKIP_BTN_PID 5 +#define MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID 5 +#define NEXT_ROUND_BTN_SELECTED_BORDER_PID 5 +#define BLIND_BG_SHADOW_PID 5 +#define SHOP_PANEL_SHADOW_PID 6 +#define PLAY_HAND_BTN_PID 6 +#define BOSS_BLIND_SHADOW_PID 7 +#define PLAY_HAND_BTN_BORDER_PID 7 +#define REROLL_BTN_SELECTED_BORDER_PID 7 +#define SHOP_LIGHTS_1_PID 8 +#define DISCARD_BTN_BORDER_PID 8 +#define BLIND_SKIP_BTN_SELECTED_BORDER_PID 10 +#define DISCARD_BTN_PID 13 +#define SHOP_LIGHTS_2_PID 14 +#define BLIND_SELECT_BTN_PID 15 +#define NEXT_ROUND_BTN_PID 16 +#define SHOP_LIGHTS_3_PID 17 +#define BLIND_SELECT_BTN_SELECTED_BORDER_PID 18 +#define BLIND_BG_SECONDARY_PID 18 +#define BLIND_BG_PRIMARY_PID 19 +#define REWARD_PANEL_BORDER_PID 19 +#define SHOP_LIGHTS_4_PID 22 +#define SHOP_BOTTOM_PANEL_BORDER_PID 26 +// Naming the stage where cards return from the discard pile to the deck "undiscard" + +#define NUM_SCORE_LERP_STEPS 32 + +// Shop +#define REROLL_BASE_COST 5 // Base cost for rerolling the shop items + +#define NEXT_ROUND_BTN_SEL_X 0 + +#define REROLL_BTN_FRAME_PAL_IDX 7 +#define REROLL_BTN_PAL_IDX 3 + +#define EXPIRE_ANIMATION_FRAME_COUNT 3 + +#define CARD_FOCUSED_UNSEL_Y 10 +#define CARD_UNFOCUSED_SEL_Y 15 +#define CARD_FOCUSED_SEL_Y 20 + +enum GameShopStates { GAME_SHOP_INTRO, GAME_SHOP_ACTIVE, GAME_SHOP_EXIT, GAME_SHOP_MAX -} _GameShopStates; +}; -typedef enum +enum GameRoundEndStates { ROUND_END_START, START_EXPAND_POPUP, @@ -48,466 +154,127 @@ typedef enum DISPLAY_CASHOUT, DISMISS_ROUND_END_PANEL, ROUND_END_EXIT -} _GameRoundEndStates; +}; -typedef enum +enum BlindSelectStates { START_ANIM_SEQ, BLIND_SELECT, BLIND_SELECTED_ANIM_SEQ, DISPLAY_BLIND_PANEL, BLIND_SELECT_MAX -} _BlindSelectStates; +}; -typedef struct +typedef struct { u32 chips; u32 mult; - char *display_name; + char* display_name; } HandValues; -typedef void (*SubStateActionFn)(); - // Used as a No Operation for game states that have no init and/or exit function. -// ricfehr3 did the work of determining whether a noop or a NULL check was more +// ricfehr3 did the work of determining whether a noop or a NULL check was more // efficient. Well, this is the answer. // Thanks! // https://github.com/cellos51/balatro-gba/issues/137#issuecomment-3322485129 -static void _noop() {} +static void noop(void) +{ +} // These functions need to be forward declared // so they're visible to the state_info array, // and the sub-state function tables. // This could be done, and maybe should be done, -// with an X macro, but I'll leave that to the +// with an X macro, but I'll leave that to the // reviewer(s). -static void game_main_menu_on_init(); -static void game_main_menu_on_update(); -static void game_round_on_init(); -static void game_playing_on_update(); -static void game_round_end_on_update(); -static void game_round_end_on_exit(); -static void game_shop_on_update(); -static void game_shop_on_exit(); -static void game_blind_select_on_update(); -static void game_blind_select_on_exit(); -static void game_lose_on_init(); -static void game_lose_on_update(); -static void game_over_on_exit(); -static void game_win_on_init(); -static void game_win_on_update(); -static void game_shop_intro(); -static void game_shop_process_user_input(); -static void game_shop_outro(); -static void game_blind_select_start_anim_seq(); -static void game_blind_select_handle_input(); -static void game_blind_select_selected_anim_seq(); -static void game_blind_select_display_blind_panel(); -static void game_round_end_start(); -static void game_round_end_start_expand_popup(); -static void game_round_end_display_finished_blind(); -static void game_round_end_display_score_min(); -static void game_round_end_update_blind_reward(); -static void game_round_end_panel_exit(); -static void game_round_end_display_rewards(); -static void game_round_end_display_cashout(); -static void game_round_end_dismiss_round_end_panel(); - -static uint rng_seed = 0; - -static uint timer = 0; // This might already exist in libtonc but idk so i'm just making my own -static int game_speed = 1; // BY DEFAULT IS SET TO 1, but if changed to 2 or more, should speed up all (or most) of the game aspects that should be sped up by speed, as in the original game. -static enum BackgroundId background = BG_NONE; - -static StateInfo state_info[] = -{ -#define DEF_STATE_INFO(stateEnum, init_fn, update_fn, exit_fn) \ - { .on_init = init_fn, .on_update = update_fn, .on_exit = exit_fn, .substate = 0 }, -#include "../include/def_state_info_table.h" -#undef DEF_STATE_INFO -}; - -static const HandValues hand_base_values[] = -{ - { .chips = 0, .mult = 0, .display_name = NULL }, // NONE - { .chips = 5, .mult = 1, .display_name = "HIGH C" }, // HIGH_CARD - { .chips = 10, .mult = 2, .display_name = "PAIR" }, // PAIR - { .chips = 20, .mult = 2, .display_name = "2 PAIR" }, // TWO_PAIR - { .chips = 30, .mult = 3, .display_name = "3 OAK" }, // THREE_OF_A_KIND - { .chips = 60, .mult = 7, .display_name = "4 OAK" }, // FOUR_OF_A_KIND - { .chips = 30, .mult = 4, .display_name = "STRT" }, // STRAIGHT - { .chips = 35, .mult = 4, .display_name = "FLUSH" }, // FLUSH - { .chips = 40, .mult = 4, .display_name = "FULL H" }, // FULL_HOUSE - { .chips = 100, .mult = 8, .display_name = "STRT F" }, // STRAIGHT_FLUSH - { .chips = 100, .mult = 8, .display_name = "ROYAL F" }, // ROYAL_FLUSH - { .chips = 120, .mult = 12, .display_name = "5 OAK" }, // FIVE_OF_A_KIND - { .chips = 140, .mult = 14, .display_name = "FLUSH H" }, // FLUSH_HOUSE - { .chips = 160, .mult = 16, .display_name = "FLUSH 5" } // FLUSH_FIVE -}; - -static const SubStateActionFn shop_state_actions[] = -{ - game_shop_intro, game_shop_process_user_input, game_shop_outro -}; - -static const SubStateActionFn blind_select_state_actions[] = -{ - game_blind_select_start_anim_seq, game_blind_select_handle_input, - game_blind_select_selected_anim_seq, game_blind_select_display_blind_panel -}; - -static const SubStateActionFn round_end_state_actions[] = -{ - game_round_end_start, game_round_end_start_expand_popup, - game_round_end_display_finished_blind, game_round_end_display_score_min, - game_round_end_update_blind_reward, game_round_end_panel_exit, - game_round_end_display_rewards, game_round_end_display_cashout, - game_round_end_dismiss_round_end_panel -}; - -static enum GameState game_state = GAME_STATE_UNDEFINED; // The current game state, this is used to determine what the game is doing at any given time -static enum HandState hand_state = HAND_DRAW; -static enum PlayState play_state = PLAY_STARTING; - -static enum HandType hand_type = NONE; - -static CardObject *main_menu_ace = NULL; - -static Sprite *playing_blind_token = NULL; // The sprite that displays the blind when in "GAME_PLAYING/GAME_ROUND_END" state -static Sprite *round_end_blind_token = NULL; // The sprite that displays the blind when in "GAME_ROUND_END" state - -static Sprite *blind_select_tokens[BLIND_TYPE_MAX] = {NULL}; // The sprites that display the blinds when in "GAME_BLIND_SELECT" state - -static int current_blind = BLIND_TYPE_SMALL; - -// The current state of the blinds, this is used to determine what the game is doing at any given time -static enum BlindState blinds[BLIND_TYPE_MAX] = -{ - BLIND_STATE_CURRENT, - BLIND_STATE_UPCOMING, - BLIND_STATE_UPCOMING -}; // The current state of the blinds, this is used to determine what the game is doing at any given time - -static int blind_reward = 0; -static int hand_reward = 0; -static int interest_reward = 0; -static int interest_to_count = 0; -static int interest_start_time = UNDEFINED; - -// Red deck default (can later be moved to a deck.h file or something) -static int max_hands = 4; -static int max_discards = 4; -// Set in game_init and game_round_init -static int hands = 0; -static int discards = 0; - -static int round = 0; -static int ante = 0; -static int money = 0; -static u32 score = 0; -static u32 temp_score = 0; // This is the score that shows in the same spot as the hand type. -static bool score_flames_active = false; -static FIXED lerped_score = 0; -static FIXED lerped_temp_score = 0; - -static u32 chips = 0; -static u32 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; - -// discarded cards specific -static bool sound_played = false; -static bool discarded_card = false; - -// Keeping track of what Jokers are scored at each step -static ListItr _joker_scored_itr; -static ListItr _joker_card_scored_end_itr; -static ListItr _joker_round_end_itr; - -static int selection_x = 0; -static int selection_y = 0; - -static bool sort_by_suit = false; - -static List _owned_jokers_list; -static List _discarded_jokers_list; -static List _expired_jokers_list; - -BITSET_DEFINE(_avail_jokers_bitset, MAX_DEFINABLE_JOKERS) -static List _shop_jokers_list; - -// Stacks -static CardObject *played[MAX_SELECTION_SIZE] = {NULL}; -static int played_top = -1; - -static CardObject *hand[MAX_HAND_SIZE] = {NULL}; -static int hand_top = -1; - -static Card *deck[MAX_DECK_SIZE] = {NULL}; -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; -} - -static inline void _set_shop_joker_avail(int joker_id, bool avail) -{ - bitset_set_idx(&_avail_jokers_bitset, joker_id, avail); -} - -GBLA_UNUSED -static inline bool _get_shop_joker_avail(int joker_id) -{ - return bitset_get_idx(&_avail_jokers_bitset, joker_id); -} - -static inline int _get_num_shop_jokers_avail(void) -{ - return bitset_num_set_bits(&_avail_jokers_bitset); -} - -static inline void _reset_shop_jokers(void) -{ - int num_jokers = get_joker_registry_size(); - bitset_clear(&_avail_jokers_bitset); - for(int i = 0; i < num_jokers; i++) - { - bitset_set_idx(&_avail_jokers_bitset, i, true); - } -} - -static inline bool _no_avail_jokers(void) -{ - return bitset_is_empty(&_avail_jokers_bitset); -} - -// Played stack -static inline void played_push(CardObject *card_object) -{ - if (played_top >= MAX_SELECTION_SIZE - 1) return; - played[++played_top] = card_object; -} - -static inline CardObject *played_pop() -{ - if (played_top < 0) return NULL; - return played[played_top--]; -} - -// Deck stack -static inline void deck_push(Card *card) -{ - if (deck_top >= MAX_DECK_SIZE - 1) return; - deck[++deck_top] = card; -} - -static inline Card *deck_pop() -{ - if (deck_top < 0) return NULL; - return deck[deck_top--]; -} - -// Discard stack -static inline void discard_push(Card *card) -{ - if (discard_top >= MAX_DECK_SIZE - 1) return; - discard_pile[++discard_top] = card; -} - -static inline Card *discard_pop() -{ - if (discard_top < 0) return NULL; - return discard_pile[discard_top--]; -} - -// get-functions, for other files to view game state (mainly for jokers) -CardObject **get_hand_array(void) -{ - return hand; -} - -int get_hand_top(void) -{ - return hand_top; -} - -int hand_get_size(void) -{ - return hand_top + 1; -} - -CardObject **get_played_array(void) -{ - return played; -} - -int get_played_top(void) -{ - return played_top; -} - -int get_scored_card_index(void) -{ - return scored_card_index; -} - -List* get_jokers_list(void) -{ - return &_owned_jokers_list; -} - -List* get_expired_jokers_list(void) -{ - return &_expired_jokers_list; -} - -bool is_joker_owned(int joker_id) -{ - ListItr itr = list_itr_create(&_owned_jokers_list); - JokerObject* joker; - - while((joker = list_itr_next(&itr))) - { - if (joker->joker->id == joker_id) - { - return true; - } - } - return false; -} - -void add_joker(JokerObject *joker_object) -{ - list_push_back(&_owned_jokers_list, 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_owned_joker(int owned_joker_idx) -{ - // TODO: Extract to on_joker_removed() callback - JokerObject* joker_object = list_get_at_idx(&_owned_jokers_list, owned_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--; - } - - _set_shop_joker_avail(joker_object->joker->id, true); - list_remove_at_idx(&_owned_jokers_list, owned_joker_idx); -} - -int get_deck_top(void) -{ - return deck_top; -} - -int get_num_discards_remaining(void) -{ - return discards; -} - -int get_num_hands_remaining(void) -{ - return hands; -} -int get_game_speed(void) -{ - return game_speed; -} - -// for the future when a menu actually lets this variable be changed. -void set_game_speed(int new_game_speed) -{ - game_speed = new_game_speed; -} - - -u32 get_chips(void) -{ - return chips; -} - -void set_chips(u32 new_chips) -{ - chips = new_chips; -} - -u32 get_mult(void) -{ - return mult; -} - -void set_mult(u32 new_mult) -{ - mult = new_mult; -} - -int get_money(void) -{ - return money; -} - -void set_money(int new_money) -{ - money = new_money; -} - -void set_retrigger(bool new_retrigger) -{ - retrigger = new_retrigger; -} - - +static void game_main_menu_on_init(void); +static void game_main_menu_on_update(void); +static void game_round_on_init(void); +static void game_playing_on_update(void); +static void game_round_end_on_update(void); +static void game_round_end_on_exit(void); +static void game_shop_on_update(void); +static void game_shop_on_exit(void); +static void game_blind_select_on_update(void); +static void game_blind_select_on_exit(void); +static void game_lose_on_init(void); +static void game_lose_on_update(void); +static void game_over_on_exit(void); +static void game_win_on_init(void); +static void game_win_on_update(void); +static void game_shop_intro(void); +static void game_shop_process_user_input(void); +static void game_shop_outro(void); +static void game_blind_select_start_anim_seq(void); +static void game_blind_select_handle_input(void); +static void game_blind_select_selected_anim_seq(void); +static void game_blind_select_display_blind_panel(void); +static void game_round_end_start(void); +static void game_round_end_start_expand_popup(void); +static void game_round_end_display_finished_blind(void); +static void game_round_end_display_score_min(void); +static void game_round_end_update_blind_reward(void); +static void game_round_end_panel_exit(void); +static void game_round_end_display_rewards(void); +static void game_round_end_display_cashout(void); +static void game_round_end_dismiss_round_end_panel(void); + +static void sort_cards(void); +static void change_background(enum BackgroundId id); +static void display_temp_score(u32 value); +static void display_score(u32 value); +static void check_flaming_score(void); +static void display_round(int value); +static void display_hands(int value); +static void display_discards(int value); +static void set_hand(void); +static void hand_set_focus(int index); +static bool hand_discard(void); +static int deck_get_size(void); +static int deck_get_max_size(void); +static void increment_blind(enum BlindState increment_reason); +static void game_over_init(void); +static bool check_and_score_joker_for_event( + ListItr* starting_joker_itr, + CardObject* card_object, + enum JokerEvent joker_event +); +static int calculate_interest_reward(void); +static void game_over_anim_frame(void); +static void shop_reroll_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection); +static void shop_reroll_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +); +static int shop_reroll_row_get_size(void); +static void shop_top_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +); +static void shop_top_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection); +static int shop_top_row_get_size(void); +static void jokers_sel_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection); +static void jokers_sel_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +); +static int jokers_sel_row_get_size(void); +static void game_shop_create_items(void); +static void erase_price_under_sprite_object(SpriteObject* sprite_object); +static void print_price_under_sprite_object(SpriteObject* sprite_object, int price); +static void game_round_end_extend_black_panel_down(int black_panel_bottom); + +static void remove_owned_joker(int owned_joker_idx); // Consts +// clang-format off +// disable clang-format here to preserve the organization here // Rects left top right bottom // Screenblock rects static const Rect ROUND_END_MENU_RECT = {9, 7, 24, 20 }; @@ -602,112 +369,677 @@ static const BG_POINT CARD_DISCARD_PNT = {240, 70}; static const BG_POINT HAND_START_POS = {120, 90}; static const BG_POINT HAND_PLAY_POS = {120, 70}; static const BG_POINT MAIN_MENU_ACE_T = {88, 26}; +// clang-format on -// Pixel sizes -#define ITEM_SHOP_Y 71 -#define ROUND_END_REWARD_AMOUNT_X 168 -#define ROUND_END_REWARD_TEXT_X 88 -#define SCORED_CARD_TEXT_Y 48 +static uint rng_seed = 0; -// SE sizes -#define ROUND_END_BLACK_PANEL_INIT_BOTTOM_SE 12 +typedef void (*SubStateActionFn)(void); -#define MAIN_MENU_BUTTONS 2 -#define MAIN_MENU_IMPLEMENTED_BUTTONS 1 // Remove this once all buttons are implemented +static uint timer = 0; // This might already exist in libtonc but idk so i'm just making my own +// BY DEFAULT IS SET TO 1, but if changed to 2 or more, should speed up all (or most) of the game +// aspects that should be sped up by speed, as in the original game. +static int game_speed = 1; +static enum BackgroundId background = BG_NONE; -//TODO: Properly define and use -#define MENU_POP_OUT_ANIM_FRAMES 20 -#define GAME_OVER_ANIM_FRAMES 15 +static StateInfo state_info[] = { +#define DEF_STATE_INFO(stateEnum, init_fn, update_fn, exit_fn) \ + {.on_init = init_fn, .on_update = update_fn, .on_exit = exit_fn, .substate = 0}, +#include "../include/def_state_info_table.h" +#undef DEF_STATE_INFO +}; -#define HIGHLIGHT_COLOR 0xFFFF -#define SHOP_LIGHTS_1_CLR 0xFFFF -#define SHOP_LIGHTS_2_CLR 0x32BE -#define SHOP_LIGHTS_3_CLR 0x4B5F -#define SHOP_LIGHTS_4_CLR 0x5F9F +SelectionGridRow shop_selection_rows[] = { + {0, jokers_sel_row_get_size, jokers_sel_row_on_selection_changed, jokers_sel_row_on_key_hit }, + {1, shop_top_row_get_size, shop_top_row_on_selection_changed, shop_top_row_on_key_hit }, + {2, shop_reroll_row_get_size, shop_reroll_row_on_selection_changed, shop_reroll_row_on_key_hit} +}; -#define PITCH_STEP_DISCARD_SFX (-64) -#define PITCH_STEP_DRAW_SFX 24 -#define PITCH_STEP_UNDISCARD_SFX 2*PITCH_STEP_DRAW_SFX +static const Selection SHOP_INIT_SEL = {-1, 1}; -#define STARTING_ROUND 0 -#define STARTING_ANTE 1 -#define STARTING_MONEY 4 -#define STARTING_SCORE 0 +SelectionGrid shop_selection_grid = { + shop_selection_rows, + NUM_ELEM_IN_ARR(shop_selection_rows), + SHOP_INIT_SEL +}; -#define CARD_FOCUSED_UNSEL_Y 10 -#define CARD_UNFOCUSED_SEL_Y 15 -#define CARD_FOCUSED_SEL_Y 20 +// This is a stupid way to do this but I don't care +static const int HAND_SPACING_LUT[MAX_HAND_SIZE] = + {28, 28, 28, 28, 27, 21, 18, 15, 13, 12, 10, 9, 9, 8, 8, 7}; -// Timer defs -#define TM_ZERO 0 -#define TM_RESET_STATIC_VARS 30 -#define TM_END_POP_MENU_ANIM 13 -#define TM_START_ROUND_END_REWARDS_ANIM 1 -#define TM_END_DISPLAY_FIN_BLIND 30 -#define TM_END_DISPLAY_SCORE_MIN 4 -#define TM_REWARDS_ELLIPSIS_PRINT_START 2 -#define TM_REWARDS_ELLIPSIS_PRINT_END 16 -#define TM_REWARD_DISPLAY_INTERVAL 15 -#define TM_DISPLAY_REWARDS_CONT_WAIT TM_REWARDS_ELLIPSIS_PRINT_END + TM_REWARD_DISPLAY_INTERVAL -#define TM_HAND_REWARD_INCR_WAIT TM_DISPLAY_REWARDS_CONT_WAIT + TM_REWARD_DISPLAY_INTERVAL -#define TM_REWARD_INCREMENT_INTERVAL 20 -#define TM_DISMISS_ROUND_END_TM 20 -#define TM_CREATE_SHOP_ITEMS_WAIT 1 -#define TM_SHIFT_SHOP_ICON_WAIT 7 -#define TM_END_GAME_SHOP_INTRO 12 -#define TM_SHOP_PRC_INPUT_START 1 -#define TM_DISP_BLIND_PANEL_FINISH 7 -#define TM_DISP_BLIND_PANEL_START 1 -#define TM_BLIND_SELECT_START 1 -#define TM_END_ANIM_SEQ 12 +static const HandValues hand_base_values[] = { + {.chips = 0, .mult = 0, .display_name = NULL }, // NONE + {.chips = 5, .mult = 1, .display_name = "HIGH C" }, // HIGH_CARD + {.chips = 10, .mult = 2, .display_name = "PAIR" }, // PAIR + {.chips = 20, .mult = 2, .display_name = "2 PAIR" }, // TWO_PAIR + {.chips = 30, .mult = 3, .display_name = "3 OAK" }, // THREE_OF_A_KIND + {.chips = 60, .mult = 7, .display_name = "4 OAK" }, // FOUR_OF_A_KIND + {.chips = 30, .mult = 4, .display_name = "STRT" }, // STRAIGHT + {.chips = 35, .mult = 4, .display_name = "FLUSH" }, // FLUSH + {.chips = 40, .mult = 4, .display_name = "FULL H" }, // FULL_HOUSE + {.chips = 100, .mult = 8, .display_name = "STRT F" }, // STRAIGHT_FLUSH + {.chips = 100, .mult = 8, .display_name = "ROYAL F"}, // ROYAL_FLUSH + {.chips = 120, .mult = 12, .display_name = "5 OAK" }, // FIVE_OF_A_KIND + {.chips = 140, .mult = 14, .display_name = "FLUSH H"}, // FLUSH_HOUSE + {.chips = 160, .mult = 16, .display_name = "FLUSH 5"} // FLUSH_FIVE +}; +static const SubStateActionFn shop_state_actions[] = { + game_shop_intro, + game_shop_process_user_input, + game_shop_outro +}; +static const SubStateActionFn blind_select_state_actions[] = { + game_blind_select_start_anim_seq, + game_blind_select_handle_input, + game_blind_select_selected_anim_seq, + game_blind_select_display_blind_panel +}; -// Palette IDs -#define BOSS_BLIND_PRIMARY_PID 1 -#define MAIN_MENU_PLAY_BUTTON_OUTLINE_PID 2 -#define REROLL_BTN_PID 3 -#define BLIND_SKIP_BTN_PID 5 -#define MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID 5 -#define NEXT_ROUND_BTN_SELECTED_BORDER_PID 5 -#define BLIND_BG_SHADOW_PID 5 -#define SHOP_PANEL_SHADOW_PID 6 -#define PLAY_HAND_BTN_PID 6 -#define BOSS_BLIND_SHADOW_PID 7 -#define PLAY_HAND_BTN_BORDER_PID 7 -#define REROLL_BTN_SELECTED_BORDER_PID 7 -#define SHOP_LIGHTS_1_PID 8 -#define DISCARD_BTN_BORDER_PID 8 -#define BLIND_SKIP_BTN_SELECTED_BORDER_PID 10 -#define DISCARD_BTN_PID 13 -#define SHOP_LIGHTS_2_PID 14 -#define BLIND_SELECT_BTN_PID 15 -#define NEXT_ROUND_BTN_PID 16 -#define SHOP_LIGHTS_3_PID 17 -#define BLIND_SELECT_BTN_SELECTED_BORDER_PID 18 -#define BLIND_BG_SECONDARY_PID 18 -#define BLIND_BG_PRIMARY_PID 19 -#define REWARD_PANEL_BORDER_PID 19 -#define SHOP_LIGHTS_4_PID 22 -#define SHOP_BOTTOM_PANEL_BORDER_PID 26 +static const SubStateActionFn round_end_state_actions[] = { + game_round_end_start, + game_round_end_start_expand_popup, + game_round_end_display_finished_blind, + game_round_end_display_score_min, + game_round_end_update_blind_reward, + game_round_end_panel_exit, + game_round_end_display_rewards, + game_round_end_display_cashout, + game_round_end_dismiss_round_end_panel +}; +static int reroll_cost = REROLL_BASE_COST; -// Naming the stage where cards return from the discard pile to the deck "undiscard" +// The current game state, this is used to determine what the game is doing at any given time +static enum GameState game_state = GAME_STATE_UNDEFINED; +static enum HandState hand_state = HAND_DRAW; +static enum PlayState play_state = PLAY_STARTING; -// General functions -void set_seed(int seed) +static enum HandType hand_type = NONE; + +static CardObject* main_menu_ace = NULL; + +// The sprite that displays the blind when in "GAME_PLAYING/GAME_ROUND_END" state +static Sprite* playing_blind_token = NULL; + +// The sprite that displays the blind when in "GAME_ROUND_END" state +static Sprite* round_end_blind_token = NULL; + +// The sprites that display the blinds when in "GAME_BLIND_SELECT" state +static Sprite* blind_select_tokens[BLIND_TYPE_MAX] = {NULL}; + +static int current_blind = BLIND_TYPE_SMALL; + +// The current state of the blinds, this is used to determine what the game is doing at any given +// time +static enum BlindState blinds[BLIND_TYPE_MAX] = { + BLIND_STATE_CURRENT, + BLIND_STATE_UPCOMING, + BLIND_STATE_UPCOMING +}; + +static int blind_reward = 0; +static int hand_reward = 0; +static int interest_reward = 0; +static int interest_to_count = 0; +static int interest_start_time = UNDEFINED; + +// Red deck default (can later be moved to a deck.h file or something) +static int max_hands = 4; +static int max_discards = 4; +// Set in game_init and game_round_init +static int hands = 0; +static int discards = 0; + +static int round = 0; +static int ante = 0; +static int money = 0; +static u32 score = 0; +static u32 temp_score = 0; // This is the score that shows in the same spot as the hand type. +static bool score_flames_active = false; +static FIXED lerped_score = 0; +static FIXED lerped_temp_score = 0; + +static u32 chips = 0; +static u32 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; + +// discarded cards specific +static bool sound_played = false; +static bool discarded_card = false; + +// Keeping track of what Jokers are scored at each step +static ListItr _joker_scored_itr; +static ListItr _joker_card_scored_end_itr; +static ListItr _joker_round_end_itr; + +static int selection_x = 0; +static int selection_y = 0; + +static bool sort_by_suit = false; + +static List _owned_jokers_list; +static List _discarded_jokers_list; +static List _expired_jokers_list; + +BITSET_DEFINE(_avail_jokers_bitset, MAX_DEFINABLE_JOKERS) +static List _shop_jokers_list; + +// Stacks +static CardObject* played[MAX_SELECTION_SIZE] = {NULL}; +static int played_top = -1; + +static CardObject* hand[MAX_HAND_SIZE] = {NULL}; +static int hand_top = -1; + +static Card* deck[MAX_DECK_SIZE] = {NULL}; +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; + +static int four_fingers_joker_count = 0; +static int straight_and_flush_size = STRAIGHT_AND_FLUSH_SIZE_DEFAULT; + +GBLA_UNUSED +static inline void set_shop_joker_avail(int joker_id, bool avail) { - rng_seed = seed; - srand(rng_seed); + bitset_set_idx(&_avail_jokers_bitset, joker_id, avail); } -void sort_hand_by_suit() +static inline int get_num_shop_jokers_avail(void) +{ + return bitset_num_set_bits(&_avail_jokers_bitset); +} + +static inline void reset_shop_jokers(void) +{ + int num_jokers = get_joker_registry_size(); + bitset_clear(&_avail_jokers_bitset); + for (int i = 0; i < num_jokers; i++) + { + bitset_set_idx(&_avail_jokers_bitset, i, true); + } +} + +static inline bool no_avail_jokers(void) +{ + return bitset_is_empty(&_avail_jokers_bitset); +} + +static inline void played_push(CardObject* card_object) +{ + if (played_top >= MAX_SELECTION_SIZE - 1) + return; + played[++played_top] = card_object; +} + +static inline CardObject* played_pop() +{ + if (played_top < 0) + return NULL; + return played[played_top--]; +} + +static inline void deck_push(Card* card) +{ + if (deck_top >= MAX_DECK_SIZE - 1) + return; + deck[++deck_top] = card; +} + +static inline Card* deck_pop() +{ + if (deck_top < 0) + return NULL; + return deck[deck_top--]; +} + +static inline void discard_push(Card* card) +{ + if (discard_top >= MAX_DECK_SIZE - 1) + return; + discard_pile[++discard_top] = card; +} + +static inline Card* discard_pop() +{ + if (discard_top < 0) + return NULL; + return discard_pile[discard_top--]; +} + +static inline void jokers_available_to_shop_init(void) +{ + reset_shop_jokers(); +} + +void game_init() +{ + // Initialize all jokers list once + _owned_jokers_list = list_create(); + _discarded_jokers_list = list_create(); + _expired_jokers_list = list_create(); + _shop_jokers_list = list_create(); + // TODO: Move this to an initialization of the play scoring states + _joker_scored_itr = list_itr_create(&_owned_jokers_list); + + jokers_available_to_shop_init(); + + hands = max_hands; + discards = max_discards; + timer = TM_ZERO; + current_blind = BLIND_TYPE_SMALL; + blinds[0] = BLIND_STATE_CURRENT; + blinds[1] = BLIND_STATE_UPCOMING; + blinds[2] = BLIND_STATE_UPCOMING; + ante = STARTING_ANTE; + money = STARTING_MONEY; + score = STARTING_SCORE; + + blind_select_tokens[BLIND_TYPE_SMALL] = blind_token_new( + BLIND_TYPE_SMALL, + CUR_BLIND_TOKEN_POS.x, + CUR_BLIND_TOKEN_POS.y, + MAX_SELECTION_SIZE + MAX_HAND_SIZE + 3 + ); + blind_select_tokens[BLIND_TYPE_BIG] = blind_token_new( + BLIND_TYPE_BIG, + CUR_BLIND_TOKEN_POS.x, + CUR_BLIND_TOKEN_POS.y, + MAX_SELECTION_SIZE + MAX_HAND_SIZE + 4 + ); + blind_select_tokens[BLIND_TYPE_BOSS] = blind_token_new( + BLIND_TYPE_BOSS, + CUR_BLIND_TOKEN_POS.x, + CUR_BLIND_TOKEN_POS.y, + MAX_SELECTION_SIZE + MAX_HAND_SIZE + 5 + ); + + obj_hide(blind_select_tokens[BLIND_TYPE_SMALL]->obj); + obj_hide(blind_select_tokens[BLIND_TYPE_BIG]->obj); + obj_hide(blind_select_tokens[BLIND_TYPE_BOSS]->obj); +} + +static inline void discarded_jokers_update_loop(void) +{ + if (list_is_empty(&_discarded_jokers_list)) + { + return; + } + + ListItr itr = list_itr_create(&_discarded_jokers_list); + JokerObject* joker_object; + + while ((joker_object = list_itr_next(&itr))) + { + joker_object_update(joker_object); + if (joker_object->sprite_object->x == joker_object->sprite_object->tx && + joker_object->sprite_object->y == joker_object->sprite_object->ty) + { + list_itr_remove_current_node(&itr); + joker_object_destroy(&joker_object); + } + } +} + +static inline void held_jokers_update_loop(void) +{ + const int spacing_lut[MAX_JOKERS_HELD_SIZE][MAX_JOKERS_HELD_SIZE] = { + {0, 0, 0, 0, 0 }, + {13, -13, 0, 0, 0 }, + {26, 0, -26, 0, 0 }, + {39, 13, -13, -39, 0 }, + {40, 20, 0, -20, -40} + }; + + FIXED hand_x = int2fx(HELD_JOKERS_POS.x); + + ListItr itr = list_itr_create(&_owned_jokers_list); + JokerObject* joker; + int jokers_top = list_get_len(&_owned_jokers_list) - 1; + int i = 0; + while ((joker = list_itr_next(&itr))) + { + joker->sprite_object->tx = hand_x - int2fx(spacing_lut[jokers_top][i++]); + + joker_object_update(joker); + } +} + +static inline void expired_jokers_update_loop(void) +{ + if (list_is_empty(&_expired_jokers_list)) + { + return; + } + + ListItr itr = list_itr_create(&_expired_jokers_list); + JokerObject* joker_object; + + while ((joker_object = list_itr_next(&itr))) + { + joker_object_update(joker_object); + + // let just enough frames pass that we see it rotating and shrinking + if (timer % FRAMES(EXPIRE_ANIMATION_FRAME_COUNT) == 0) + { + // get joker idx + int expired_joker_idx = 0; + ListItr joker_itr = list_itr_create(&_owned_jokers_list); + JokerObject* expired_joker; + while ((expired_joker = list_itr_next(&joker_itr)) && expired_joker != joker_object) + { + expired_joker_idx++; + } + + // Removing expired Jokers here, instead of immediately like ones we + // sell or discard allow us to have a small shrink animation without + // the other owned Jokers rearranging themselves to fill the newly + // freed space, therefore obscuring the animation + remove_owned_joker(expired_joker_idx); + list_itr_remove_current_node(&itr); + joker_object_destroy(&joker_object); + } + } +} + +static inline void jokers_update_loop(void) +{ + held_jokers_update_loop(); + discarded_jokers_update_loop(); + expired_jokers_update_loop(); +} + +void game_update() +{ + timer++; + + jokers_update_loop(); + + state_info[game_state].on_update(); +} + +void game_change_state(enum GameState new_game_state) +{ + timer = TM_ZERO; // Reset the timer + + if (game_state >= 0 && game_state < GAME_STATE_MAX) + { + state_info[game_state].substate = 0; + state_info[game_state].on_exit(); + } + + if (new_game_state >= 0 && new_game_state < GAME_STATE_MAX) + { + state_info[new_game_state].on_init(); + + game_state = new_game_state; + } +} + +CardObject** get_hand_array(void) +{ + return hand; +} + +int get_hand_top(void) +{ + return hand_top; +} + +int hand_get_size(void) +{ + return hand_top + 1; +} + +CardObject** get_played_array(void) +{ + return played; +} + +int get_played_top(void) +{ + return played_top; +} + +int get_scored_card_index(void) +{ + return scored_card_index; +} + +bool is_joker_owned(int joker_id) +{ + ListItr itr = list_itr_create(&_owned_jokers_list); + JokerObject* joker; + + while ((joker = list_itr_next(&itr))) + { + if (joker->joker->id == joker_id) + { + return true; + } + } + return false; +} + +bool card_is_face(Card* card) +{ + // Card is a face card, or Pareidolia is present + return ( + card->rank == JACK || card->rank == QUEEN || card->rank == KING || + is_joker_owned(PAREIDOLIA_JOKER_ID) + ); +} + +List* get_jokers_list(void) +{ + return &_owned_jokers_list; +} + +List* get_expired_jokers_list(void) +{ + return &_expired_jokers_list; +} + +bool is_shortcut_joker_active(void) +{ + return shortcut_joker_count > 0; +} + +int get_straight_and_flush_size(void) +{ + return straight_and_flush_size; +} + +static void add_joker(JokerObject* joker_object) +{ + list_push_back(&_owned_jokers_list, 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++; + } +} + +static void remove_owned_joker(int owned_joker_idx) +{ + // TODO: Extract to on_joker_removed() callback + JokerObject* joker_object = list_get_at_idx(&_owned_jokers_list, owned_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--; + } + + set_shop_joker_avail(joker_object->joker->id, true); + list_remove_at_idx(&_owned_jokers_list, owned_joker_idx); +} + +int get_deck_top(void) +{ + return deck_top; +} + +int get_num_discards_remaining(void) +{ + return discards; +} + +int get_num_hands_remaining(void) +{ + return hands; +} + +int get_game_speed(void) +{ + return game_speed; +} + +// for the future when a menu actually lets this variable be changed. +void set_game_speed(int new_game_speed) +{ + game_speed = new_game_speed; +} + +u32 get_chips(void) +{ + return chips; +} + +void set_chips(u32 new_chips) +{ + chips = new_chips; +} + +u32 get_mult(void) +{ + return mult; +} + +void set_mult(u32 new_mult) +{ + mult = new_mult; +} + +int get_money(void) +{ + return money; +} + +void set_money(int new_money) +{ + money = new_money; +} + +void set_retrigger(bool new_retrigger) +{ + retrigger = new_retrigger; +} + +void display_money() +{ + Rect money_text_rect = MONEY_TEXT_RECT; + tte_erase_rect_wrapper(MONEY_TEXT_RECT); + + char money_str_buff[INT_MAX_DIGITS + 2]; // + 2 for null terminator and "$" sign + snprintf(money_str_buff, sizeof(money_str_buff), "$%d", money); + + // Bias left so the number is centered and the "$" sign is on the left + update_text_rect_to_center_str(&money_text_rect, money_str_buff, SCREEN_LEFT); + + tte_printf( + "#{P:%d,%d; cx:0x%X000}%s", + money_text_rect.left, + money_text_rect.top, + TTE_YELLOW_PB, + money_str_buff + ); +} + +void display_chips(void) +{ + Rect chips_text_rect = CHIPS_TEXT_RECT; + + // In case of overflow, the rect overflow left by 1 char + Rect chips_text_overflow_rect = chips_text_rect; + chips_text_overflow_rect.left -= TTE_CHAR_SIZE; + tte_erase_rect_wrapper(chips_text_overflow_rect); + + char chips_str_buff[UINT_MAX_DIGITS + 1]; + truncate_uint_to_suffixed_str( + chips, + rect_width(&chips_text_rect) / TTE_CHAR_SIZE, + chips_str_buff + ); + + update_text_rect_to_right_align_str(&chips_text_rect, chips_str_buff, OVERFLOW_LEFT); + + tte_printf( + "#{P:%d,%d; cx:0x%X000;}%s", + chips_text_rect.left, + chips_text_rect.top, + TTE_WHITE_PB, + chips_str_buff + ); + check_flaming_score(); +} + +void display_mult(void) +{ + tte_erase_rect_wrapper(MULT_TEXT_RECT); + tte_printf( + "#{P:%d,%d; cx:0x%X000;}%lu", + MULT_TEXT_RECT.left, + MULT_TEXT_RECT.top, + TTE_WHITE_PB, + mult + ); + check_flaming_score(); +} + +static inline void sort_hand_by_suit(void) { for (int a = 0; a < hand_top; a++) { for (int b = a + 1; b <= hand_top; b++) { - if (hand[a] == NULL || (hand[b] != NULL && (hand[a]->card->suit > hand[b]->card->suit || (hand[a]->card->suit == hand[b]->card->suit && hand[a]->card->rank > hand[b]->card->rank)))) + if (hand[a] == NULL || + (hand[b] != NULL && (hand[a]->card->suit > hand[b]->card->suit || + (hand[a]->card->suit == hand[b]->card->suit && + hand[a]->card->rank > hand[b]->card->rank)))) { CardObject* temp = hand[a]; hand[a] = hand[b]; @@ -717,7 +1049,7 @@ void sort_hand_by_suit() } } -void sort_hand_by_rank() +static inline void sort_hand_by_rank(void) { for (int a = 0; a < hand_top; a++) { @@ -733,7 +1065,7 @@ void sort_hand_by_rank() } } -void sort_cards() +static void sort_cards(void) { if (sort_by_suit) { @@ -759,113 +1091,22 @@ void sort_cards() { if (hand[i] != NULL) { - //hand[i]->sprite = sprite_new(ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, ATTR1_SIZE_32, card_sprite_lut[hand[i]->card->suit][hand[i]->card->rank], 0, i); + // hand[i]->sprite = sprite_new(ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, ATTR1_SIZE_32, + // card_sprite_lut[hand[i]->card->suit][hand[i]->card->rank], 0, i); card_object_set_sprite(hand[i], i); // Set the sprite for the card object - sprite_position(card_object_get_sprite(hand[i]), fx2int(hand[i]->sprite_object->x), fx2int(hand[i]->sprite_object->y)); + sprite_position( + card_object_get_sprite(hand[i]), + fx2int(hand[i]->sprite_object->x), + fx2int(hand[i]->sprite_object->y) + ); } } } -enum HandType hand_get_type() -{ - enum HandType res_hand_type = NONE; - - // Idk if this is how Balatro does it but this is how I'm doing it - if (hand_selections == 0 || hand_state == HAND_DISCARD) - { - res_hand_type = NONE; - return res_hand_type; - } - - res_hand_type = HIGH_CARD; - - u8 suits[NUM_SUITS]; - u8 ranks[NUM_RANKS]; - get_hand_distribution(ranks, suits); - - // Check for flush - if (hand_contains_flush(suits)) - res_hand_type = FLUSH; - - // Check for straight - if (hand_contains_straight(ranks)) - { - if (res_hand_type == FLUSH) - res_hand_type = STRAIGHT_FLUSH; - else - res_hand_type = STRAIGHT; - } - - // The following can be optimized better but not sure how much it matters - u8 n_of_a_kind = hand_contains_n_of_a_kind(ranks); - - if (n_of_a_kind >= 5) { - if (res_hand_type == FLUSH) { - return FLUSH_FIVE; - } - return FIVE_OF_A_KIND; - } - - // Check for royal flush vs regular straight flush - if (res_hand_type == STRAIGHT_FLUSH) { - if (ranks[TEN] && ranks[JACK] && ranks[QUEEN] && ranks[KING] && ranks[ACE]) - return ROYAL_FLUSH; - return STRAIGHT_FLUSH; - } - - if (n_of_a_kind == 4) { - return FOUR_OF_A_KIND; - } - - if (n_of_a_kind == 3 && hand_contains_full_house(ranks)) - { - return FULL_HOUSE; - } - - // Flush and Straight are more valuable than the remaining hand types, so return them now - if (res_hand_type == FLUSH) { - if (n_of_a_kind >= 5) { - return FLUSH_HOUSE; - } - return FLUSH; - } - if (res_hand_type == STRAIGHT) { - return STRAIGHT; - } - - if (n_of_a_kind == 3) - { - return THREE_OF_A_KIND; - } - - if (n_of_a_kind == 2) - { - if (hand_contains_two_pair(ranks)) - { - return TWO_PAIR; - } - return PAIR; - } - - return res_hand_type; // should be HIGH_CARD -} - -// Returns true if the card is *considered* a face card -bool card_is_face(Card *card) -{ - // Card is a face card, or Pareidolia is present - return ( - card->rank == JACK || - card->rank == QUEEN || - card->rank == KING || - is_joker_owned(PAREIDOLIA_JOKER_ID) - ); -} - /* Copies the appropriate item into the top left panel (blind/shop icon) * from where it was put outside the screenview */ -void bg_copy_current_item_to_top_left_panel() +static void bg_copy_current_item_to_top_left_panel(void) { main_bg_se_copy_rect(TOP_LEFT_ITEM_SRC_RECT, TOP_LEFT_PANEL_POINT); } @@ -880,7 +1121,7 @@ static inline void reset_top_left_panel_bottom_row() main_bg_se_copy_rect(TOP_LEFT_PANEL_BOTTOM_ROW_RESET_RECT, top_left_panel_bottom_row_pos); } -void change_background(enum BackgroundId id) +static void change_background(enum BackgroundId id) { if (background == id) { @@ -894,16 +1135,20 @@ void change_background(enum BackgroundId id) if (background == BG_CARD_PLAYING) { int offset = 11; - memcpy16(&se_mem[MAIN_BG_SBB][SE_ROW_LEN * offset], &background_gfxMap[SE_ROW_LEN * offset], SE_ROW_LEN * 8); + memcpy16( + &se_mem[MAIN_BG_SBB][SE_ROW_LEN * offset], + &background_gfxMap[SE_ROW_LEN * offset], + SE_ROW_LEN * 8 + ); } else { toggle_windows(true, true); // Enable window 0 for the hand shadow - + // Load the tiles and palette // Background GRIT_CPY(pal_bg_mem, background_gfxPal); - GRIT_CPY(&tile8_mem[MAIN_BG_CBB], background_gfxTiles); + GRIT_CPY(&tile8_mem[MAIN_BG_CBB], background_gfxTiles); GRIT_CPY(&se_mem[MAIN_BG_SBB], background_gfxMap); if (current_blind == BLIND_TYPE_BIG) // Change text and palette depending on blind type @@ -914,15 +1159,30 @@ void change_background(enum BackgroundId id) { main_bg_se_copy_rect(BOSS_BLIND_TITLE_SRC_RECT, TOP_LEFT_BLIND_TITLE_POINT); - affine_background_set_color(blind_get_color(BLIND_TYPE_BOSS, BLIND_SHADOW_COLOR_INDEX)); + affine_background_set_color( + blind_get_color(BLIND_TYPE_BOSS, BLIND_SHADOW_COLOR_INDEX) + ); } bg_copy_current_item_to_top_left_panel(); - // This would change the palette of the background to match the blind, but the backgroun doesn't use the blind token's exact colors so a different approach is required - memset16(&pal_bg_mem[BLIND_BG_PRIMARY_PID], blind_get_color(current_blind, BLIND_BACKGROUND_MAIN_COLOR_INDEX), 1); - memset16(&pal_bg_mem[BLIND_BG_SECONDARY_PID], blind_get_color(current_blind, BLIND_BACKGROUND_SECONDARY_COLOR_INDEX), 1); - memset16(&pal_bg_mem[BLIND_BG_SHADOW_PID], blind_get_color(current_blind, BLIND_BACKGROUND_SHADOW_COLOR_INDEX), 1); + // This would change the palette of the background to match the blind, but the backgroun + // doesn't use the blind token's exact colors so a different approach is required + memset16( + &pal_bg_mem[BLIND_BG_PRIMARY_PID], + blind_get_color(current_blind, BLIND_BACKGROUND_MAIN_COLOR_INDEX), + 1 + ); + memset16( + &pal_bg_mem[BLIND_BG_SECONDARY_PID], + blind_get_color(current_blind, BLIND_BACKGROUND_SECONDARY_COLOR_INDEX), + 1 + ); + memset16( + &pal_bg_mem[BLIND_BG_SHADOW_PID], + blind_get_color(current_blind, BLIND_BACKGROUND_SHADOW_COLOR_INDEX), + 1 + ); // Copy the Play Hand and Discard button colors to their selection highlights memcpy16(&pal_bg_mem[PLAY_HAND_BTN_BORDER_PID], &pal_bg_mem[PLAY_HAND_BTN_PID], 1); @@ -955,7 +1215,8 @@ void change_background(enum BackgroundId id) background = BG_ROUND_END; } - toggle_windows(false, true); // Disable window 0 so it doesn't make the cashout menu transparent + // Disable window 0 so it doesn't make the cashout menu transparent + toggle_windows(false, true); main_bg_se_clear_rect(ROUND_END_MENU_RECT); tte_erase_rect_wrapper(HAND_SIZE_RECT); @@ -968,26 +1229,35 @@ void change_background(enum BackgroundId id) GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_shop_gfxTiles); GRIT_CPY(&se_mem[MAIN_BG_SBB], background_shop_gfxMap); - // Set the outline colors for the shop background. This is used for the alternate shop palettes when opening packs + // Set the outline colors for the shop background. This is used for the alternate shop + // palettes when opening packs memset16(&pal_bg_mem[SHOP_BOTTOM_PANEL_BORDER_PID], 0x213D, 1); memset16(&pal_bg_mem[SHOP_PANEL_SHADOW_PID], 0x10B4, 1); - - memset16(&pal_bg_mem[SHOP_LIGHTS_2_PID], SHOP_LIGHTS_2_CLR, 1); // Reset the shop lights to correct colors + + // Reset the shop lights to correct colors + memset16(&pal_bg_mem[SHOP_LIGHTS_2_PID], SHOP_LIGHTS_2_CLR, 1); memset16(&pal_bg_mem[SHOP_LIGHTS_3_PID], SHOP_LIGHTS_3_CLR, 1); memset16(&pal_bg_mem[SHOP_LIGHTS_4_PID], SHOP_LIGHTS_4_CLR, 1); memset16(&pal_bg_mem[SHOP_LIGHTS_1_PID], SHOP_LIGHTS_1_CLR, 1); - memcpy16(&pal_bg_mem[REROLL_BTN_SELECTED_BORDER_PID], &pal_bg_mem[REROLL_BTN_PID], 1); // Disable the button highlight colors - memcpy16(&pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], &pal_bg_mem[NEXT_ROUND_BTN_PID], 1); + // Disable the button highlight colors + memcpy16(&pal_bg_mem[REROLL_BTN_SELECTED_BORDER_PID], &pal_bg_mem[REROLL_BTN_PID], 1); + memcpy16( + &pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], + &pal_bg_mem[NEXT_ROUND_BTN_PID], + 1 + ); } else if (id == BG_BLIND_SELECT) { - for(int i = 0; i < BLIND_TYPE_MAX; i++) + for (int i = 0; i < BLIND_TYPE_MAX; i++) { obj_unhide(blind_select_tokens[i]->obj, 0); } - const int default_y = 89 + (TILE_SIZE * 12); // Default y position for the blind select tokens. 12 is the amound of tiles the background is shifted down by + // Default y position for the blind select tokens. 12 is the amount of tiles the background + // is shifted down by + const int default_y = 89 + (TILE_SIZE * 12); // TODO refactor magic numbers '80/120/160' into a map to loop with sprite_position(blind_select_tokens[BLIND_TYPE_SMALL], 80, default_y); sprite_position(blind_select_tokens[BLIND_TYPE_BIG], 120, default_y); @@ -1000,17 +1270,33 @@ void change_background(enum BackgroundId id) GRIT_CPY(&se_mem[MAIN_BG_SBB], background_blind_select_gfxMap); // Copy boss blind colors to blind select palette - memset16(&pal_bg_mem[1], blind_get_color(BLIND_TYPE_BOSS, BLIND_BACKGROUND_MAIN_COLOR_INDEX), 1); - memset16(&pal_bg_mem[7], blind_get_color(BLIND_TYPE_BOSS, BLIND_BACKGROUND_SHADOW_COLOR_INDEX), 1); + memset16( + &pal_bg_mem[1], + blind_get_color(BLIND_TYPE_BOSS, BLIND_BACKGROUND_MAIN_COLOR_INDEX), + 1 + ); + memset16( + &pal_bg_mem[7], + blind_get_color(BLIND_TYPE_BOSS, BLIND_BACKGROUND_SHADOW_COLOR_INDEX), + 1 + ); // Disable the button highlight colors // Select button PID is 15 and the outline is 18 - memcpy16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SELECT_BTN_PID], 1); - // It seems the skip button (and score multiplier and deck) PB idx is - // actually 5, not 10. 10 is the selected border color - // Setting this palette value though doesn't seem to have an - // effect. - memcpy16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SKIP_BTN_PID], 1); + memcpy16( + &pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], + &pal_bg_mem[BLIND_SELECT_BTN_PID], + 1 + ); + // It seems the skip button (and score multiplier and deck) PB idx is + // actually 5, not 10. 10 is the selected border color + // Setting this palette value though doesn't seem to have an + // effect. + memcpy16( + &pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], + &pal_bg_mem[BLIND_SKIP_BTN_PID], + 1 + ); for (int i = 0; i < BLIND_TYPE_MAX; i++) { @@ -1020,9 +1306,13 @@ void change_background(enum BackgroundId id) curr_blind_rect.left += i * rect_width(&SINGLE_BLIND_SELECT_RECT); curr_blind_rect.right += i * rect_width(&SINGLE_BLIND_SELECT_RECT); - if (blinds[i] != BLIND_STATE_CURRENT && (i == BLIND_TYPE_SMALL || i == BLIND_TYPE_BIG)) // Make the skip button gray + if (blinds[i] != BLIND_STATE_CURRENT && + (i == BLIND_TYPE_SMALL || i == BLIND_TYPE_BIG)) // Make the skip button gray { - BG_POINT skip_blind_btn_pos_dest = { BLIND_SKIP_BTN_PREANIM_DEST_RECT.left, BLIND_SKIP_BTN_PREANIM_DEST_RECT.top }; + BG_POINT skip_blind_btn_pos_dest = { + BLIND_SKIP_BTN_PREANIM_DEST_RECT.left, + BLIND_SKIP_BTN_PREANIM_DEST_RECT.top + }; skip_blind_btn_pos_dest.x = curr_blind_rect.left; Rect skip_blind_btn_rect_src = BLIND_SKIP_BTN_GRAY_RECT; @@ -1032,7 +1322,7 @@ void change_background(enum BackgroundId 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 { @@ -1055,15 +1345,26 @@ void change_background(enum BackgroundId id) y_from = 30; } - // Copy plain tiles onto the bottom of the raised blind panel to fill the gap created by the raise - Rect gap_fill_rect = {x_from, y_from, x_from + rect_width(&SINGLE_BLIND_SELECT_RECT) - 1, y_from}; // - 1 to stay within rect boundaries + // Copy plain tiles onto the bottom of the raised blind panel to fill the gap + // created by the raise + Rect gap_fill_rect = { + x_from, + y_from, + x_from + rect_width(&SINGLE_BLIND_SELECT_RECT) - 1, + y_from + }; BG_POINT gap_fill_point = {x_to, y_to}; main_bg_se_copy_rect(gap_fill_rect, gap_fill_point); - sprite_position(blind_select_tokens[i], blind_select_tokens[i]->pos.x, blind_select_tokens[i]->pos.y - TILE_SIZE); // Move token up by a tile + // Move token up by a tile + sprite_position( + blind_select_tokens[i], + blind_select_tokens[i]->pos.x, + blind_select_tokens[i]->pos.y - TILE_SIZE + ); break; } - case BLIND_STATE_UPCOMING: // Change the select icon to "NEXT" + case BLIND_STATE_UPCOMING: // Change the select icon to "NEXT" { int x_from = 0; int y_from = 20; @@ -1071,7 +1372,11 @@ void change_background(enum BackgroundId id) int x_to = 10 + (i * rect_width(&SINGLE_BLIND_SELECT_RECT)); int y_to = 20; - memcpy16(&se_mem[MAIN_BG_SBB][x_to + 32 * y_to], &se_mem[MAIN_BG_SBB][x_from + 32 * y_from], 3); + memcpy16( + &se_mem[MAIN_BG_SBB][x_to + 32 * y_to], + &se_mem[MAIN_BG_SBB][x_from + 32 * y_from], + 3 + ); break; } case BLIND_STATE_SKIPPED: // Change the select icon to "SKIP" @@ -1082,7 +1387,11 @@ void change_background(enum BackgroundId id) int x_to = 10 + (i * 5); int y_to = 20; - memcpy16(&se_mem[MAIN_BG_SBB][x_to + 32 * y_to], &se_mem[MAIN_BG_SBB][x_from + 32 * y_from], 3); + memcpy16( + &se_mem[MAIN_BG_SBB][x_to + 32 * y_to], + &se_mem[MAIN_BG_SBB][x_from + 32 * y_from], + 3 + ); break; } case BLIND_STATE_DEFEATED: // Change the select icon to "DEFEATED" @@ -1093,7 +1402,11 @@ void change_background(enum BackgroundId id) int x_to = 10 + (i * 5); int y_to = 20; - memcpy16(&se_mem[MAIN_BG_SBB][x_to + 32 * y_to], &se_mem[MAIN_BG_SBB][x_from + 32 * y_from], 3); + memcpy16( + &se_mem[MAIN_BG_SBB][x_to + 32 * y_to], + &se_mem[MAIN_BG_SBB][x_from + 32 * y_from], + 3 + ); break; } default: @@ -1111,7 +1424,11 @@ void change_background(enum BackgroundId id) GRIT_CPY(&se_mem[MAIN_BG_SBB], background_main_menu_gfxMap); // Disable the button highlight colors - memcpy16(&pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_PID], &pal_bg_mem[MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID], 1); + memcpy16( + &pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_PID], + &pal_bg_mem[MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID], + 1 + ); } else { @@ -1121,48 +1438,50 @@ void change_background(enum BackgroundId id) background = id; } -void display_temp_score(u32 value) +static void display_temp_score(u32 value) { char temp_score_str_buff[UINT_MAX_DIGITS + 1]; Rect temp_score_rect = TEMP_SCORE_RECT; - truncate_uint_to_suffixed_str(value, rect_width(&temp_score_rect)/TTE_CHAR_SIZE, temp_score_str_buff); + truncate_uint_to_suffixed_str( + value, + rect_width(&temp_score_rect) / TTE_CHAR_SIZE, + temp_score_str_buff + ); update_text_rect_to_center_str(&temp_score_rect, temp_score_str_buff, SCREEN_RIGHT); tte_erase_rect_wrapper(TEMP_SCORE_RECT); - tte_printf("#{P:%d,%d; cx:0x%X000}%s", temp_score_rect.left, temp_score_rect.top, TTE_WHITE_PB, temp_score_str_buff); + tte_printf( + "#{P:%d,%d; cx:0x%X000}%s", + temp_score_rect.left, + temp_score_rect.top, + TTE_WHITE_PB, + temp_score_str_buff + ); } -void display_score(u32 value) +static void display_score(u32 value) { Rect score_rect = SCORE_RECT; // Clear the existing text before redrawing tte_erase_rect_wrapper(SCORE_RECT); - + char score_str_buff[UINT_MAX_DIGITS + 1]; - truncate_uint_to_suffixed_str(value, rect_width(&score_rect)/TTE_CHAR_SIZE, score_str_buff); + truncate_uint_to_suffixed_str(value, rect_width(&score_rect) / TTE_CHAR_SIZE, score_str_buff); update_text_rect_to_center_str(&score_rect, score_str_buff, SCREEN_RIGHT); - - tte_printf("#{P:%d,%d; cx:0x%X000}%s", score_rect.left, score_rect.top, TTE_WHITE_PB, score_str_buff); -} -void display_money() -{ - Rect money_text_rect = MONEY_TEXT_RECT; - tte_erase_rect_wrapper(MONEY_TEXT_RECT); - - char money_str_buff[INT_MAX_DIGITS + 2]; // + 2 for null terminator and "$" sign - snprintf(money_str_buff, sizeof(money_str_buff), "$%d", money); - - // Bias left so the number is centered and the "$" sign is on the left - update_text_rect_to_center_str(&money_text_rect, money_str_buff, SCREEN_LEFT); - - tte_printf("#{P:%d,%d; cx:0x%X000}%s", money_text_rect.left, money_text_rect.top, TTE_YELLOW_PB, money_str_buff); + tte_printf( + "#{P:%d,%d; cx:0x%X000}%s", + score_rect.left, + score_rect.top, + TTE_WHITE_PB, + score_str_buff + ); } // Show/Hide flaming score effect if we will score // more than the required amount or not -void check_flaming_score() +static void check_flaming_score(void) { u32 curr_score = u32_protected_mult(chips, mult); u32 required_score = blind_get_requirement(current_blind, ante); @@ -1179,75 +1498,147 @@ void check_flaming_score() Rect reset_rect = SCORE_FLAME_RESET; main_bg_se_copy_rect(reset_rect, SCORE_FLAME_CHIPS_POS); - reset_rect.left += SCORE_FLAME_FRAME_WIDTH; + reset_rect.left += SCORE_FLAME_FRAME_WIDTH; reset_rect.right += SCORE_FLAME_FRAME_WIDTH; main_bg_se_copy_rect(reset_rect, SCORE_FLAME_MULT_POS); } } -void display_chips() +static void display_round(int value) { - Rect chips_text_rect = CHIPS_TEXT_RECT; - - // In case of overflow, the rect overflow left by 1 char - Rect chips_text_overflow_rect = chips_text_rect; - chips_text_overflow_rect.left -= TTE_CHAR_SIZE; - tte_erase_rect_wrapper(chips_text_overflow_rect); - - char chips_str_buff[UINT_MAX_DIGITS + 1]; - truncate_uint_to_suffixed_str(chips, rect_width(&chips_text_rect)/TTE_CHAR_SIZE, chips_str_buff); - - update_text_rect_to_right_align_str(&chips_text_rect, chips_str_buff, OVERFLOW_LEFT); - - tte_printf("#{P:%d,%d; cx:0x%X000;}%s", chips_text_rect.left, chips_text_rect.top, TTE_WHITE_PB, chips_str_buff); - check_flaming_score(); + // tte_erase_rect_wrapper(ROUND_TEXT_RECT); + tte_printf( + "#{P:%d,%d; cx:0x%X000}%d", + ROUND_TEXT_RECT.left, + ROUND_TEXT_RECT.top, + TTE_YELLOW_PB, + round + ); } -void display_mult() +static void display_hands(int value) { - Rect mult_text_overflow_rect = MULT_TEXT_RECT; - // In case of overflow the rect will overflow right by 1 char - mult_text_overflow_rect.right += TTE_CHAR_SIZE; - tte_erase_rect_wrapper(mult_text_overflow_rect); - - char mult_str_buff[UINT_MAX_DIGITS + 1]; - truncate_uint_to_suffixed_str(mult, rect_width(&MULT_TEXT_RECT)/TTE_CHAR_SIZE, mult_str_buff); - - tte_printf("#{P:%d,%d; cx:0x%X000;}%s", MULT_TEXT_RECT.left, MULT_TEXT_RECT.top, TTE_WHITE_PB, mult_str_buff); - check_flaming_score(); -} - -void display_round(int value) -{ - //tte_erase_rect_wrapper(ROUND_TEXT_RECT); - tte_printf("#{P:%d,%d; cx:0x%X000}%d", ROUND_TEXT_RECT.left, ROUND_TEXT_RECT.top, TTE_YELLOW_PB, round); -} - -void display_ante(int value) -{ - tte_printf("#{P:%d,%d; cx:0xC000}%d#{cx:0xF000}/%d", ANTE_TEXT_RECT.left, ANTE_TEXT_RECT.top, value, MAX_ANTE); -} - -void display_hands(int value) -{ - //tte_erase_rect_wrapper(HANDS_TEXT_RECT); + // tte_erase_rect_wrapper(HANDS_TEXT_RECT); tte_printf("#{P:%d,%d; cx:0xD000}%d", HANDS_TEXT_RECT.left, HANDS_TEXT_RECT.top, hands); // Hand } -void display_discards(int value) +static void display_discards(int value) { - //tte_erase_rect_wrapper(DISCARDS_TEXT_RECT); - tte_printf("#{P:%d,%d; cx:0xE000}%d", DISCARDS_TEXT_RECT.left, DISCARDS_TEXT_RECT.top, discards); // Discard + // tte_erase_rect_wrapper(DISCARDS_TEXT_RECT); + // Discard + tte_printf( + "#{P:%d,%d; cx:0xE000}%d", + DISCARDS_TEXT_RECT.left, + DISCARDS_TEXT_RECT.top, + discards + ); +} + +static inline enum HandType hand_get_type(void) +{ + enum HandType res_hand_type = NONE; + + // Idk if this is how Balatro does it but this is how I'm doing it + if (hand_selections == 0 || hand_state == HAND_DISCARD) + { + res_hand_type = NONE; + return res_hand_type; + } + + res_hand_type = HIGH_CARD; + + u8 suits[NUM_SUITS]; + u8 ranks[NUM_RANKS]; + get_hand_distribution(ranks, suits); + + // Check for flush + if (hand_contains_flush(suits)) + res_hand_type = FLUSH; + + // Check for straight + if (hand_contains_straight(ranks)) + { + if (res_hand_type == FLUSH) + res_hand_type = STRAIGHT_FLUSH; + else + res_hand_type = STRAIGHT; + } + + // The following can be optimized better but not sure how much it matters + u8 n_of_a_kind = hand_contains_n_of_a_kind(ranks); + + if (n_of_a_kind >= 5) + { + if (res_hand_type == FLUSH) + { + return FLUSH_FIVE; + } + return FIVE_OF_A_KIND; + } + + // Check for royal flush vs regular straight flush + if (res_hand_type == STRAIGHT_FLUSH) + { + if (ranks[TEN] && ranks[JACK] && ranks[QUEEN] && ranks[KING] && ranks[ACE]) + return ROYAL_FLUSH; + return STRAIGHT_FLUSH; + } + + if (n_of_a_kind == 4) + { + return FOUR_OF_A_KIND; + } + + if (n_of_a_kind == 3 && hand_contains_full_house(ranks)) + { + return FULL_HOUSE; + } + + // Flush and Straight are more valuable than the remaining hand types, so return them now + if (res_hand_type == FLUSH) + { + if (n_of_a_kind >= 5) + { + return FLUSH_HOUSE; + } + return FLUSH; + } + if (res_hand_type == STRAIGHT) + { + return STRAIGHT; + } + + if (n_of_a_kind == 3) + { + return THREE_OF_A_KIND; + } + + if (n_of_a_kind == 2) + { + if (hand_contains_two_pair(ranks)) + { + return TWO_PAIR; + } + return PAIR; + } + + return res_hand_type; // should be HIGH_CARD } static void print_hand_type(const char* hand_type_str) { if (hand_type_str == NULL) return; // NULL-checking paranoia - tte_printf("#{P:%d,%d; cx:0x%X000}%s", HAND_TYPE_RECT.left, HAND_TYPE_RECT.top, TTE_WHITE_PB, hand_type_str); + tte_printf( + "#{P:%d,%d; cx:0x%X000}%s", + HAND_TYPE_RECT.left, + HAND_TYPE_RECT.top, + TTE_WHITE_PB, + hand_type_str + ); } -void set_hand() +static void set_hand(void) { tte_erase_rect_wrapper(HAND_TYPE_RECT); hand_type = hand_get_type(); @@ -1262,29 +1653,10 @@ void set_hand() display_mult(); } -void card_draw() +static void hand_set_focus(int index) { - if (deck_top < 0 || hand_top >= hand_size - 1 || hand_top >= MAX_HAND_SIZE - 1) return; - - CardObject *card_object = card_object_new(deck_pop()); - - const FIXED deck_x = int2fx(CARD_DRAW_POS.x); - const FIXED deck_y = int2fx(CARD_DRAW_POS.y); - - card_object->sprite_object->x = deck_x; - card_object->sprite_object->y = deck_y; - - hand[++hand_top] = card_object; - - // Sort the hand after drawing a card - sort_cards(); - - play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + cards_drawn*PITCH_STEP_DRAW_SFX); -} - -void hand_set_focus(int index) -{ - if (hand_state != HAND_SELECT) return; + if (hand_state != HAND_SELECT) + return; // Wrap around to the other side of the hand when going out of bounds on either side if (index < 0) @@ -1303,9 +1675,161 @@ void hand_set_focus(int index) play_sfx(SFX_CARD_FOCUS, MM_BASE_PITCH_RATE + rand() % 512); } -void hand_toggle_card_selection() +static bool hand_discard(void) { - if (hand_state != HAND_SELECT || hand[selection_x] == NULL) return; + if (hand_state != HAND_SELECT || hand_selections == 0) + return false; + return true; +} + +static int deck_get_size(void) +{ + return deck_top + 1; +} + +static int deck_get_max_size(void) +{ + // This is the max amount of cards that the player currently has in their possession + return hand_top + played_top + deck_top + discard_top + 4; +} + +static void increment_blind(enum BlindState increment_reason) +{ + current_blind++; + if (current_blind >= BLIND_TYPE_MAX) + { + current_blind = 0; + blinds[0] = BLIND_STATE_CURRENT; // Reset the blinds to the first one + blinds[1] = BLIND_STATE_UPCOMING; // Set the next blind to upcoming + blinds[2] = BLIND_STATE_UPCOMING; // Set the next blind to upcoming + } + else + { + blinds[current_blind] = BLIND_STATE_CURRENT; + blinds[current_blind - 1] = increment_reason; + } +} + +static inline void deck_shuffle(void) +{ + for (int i = deck_top; i > 0; i--) + { + int j = rand() % (i + 1); + Card* temp = deck[i]; + deck[i] = deck[j]; + deck[j] = temp; + } +} + +static void game_round_on_init() +{ + hand_state = HAND_DRAW; + cards_drawn = 0; + hand_selections = 0; + + playing_blind_token = blind_token_new( + current_blind, + CUR_BLIND_TOKEN_POS.x, + CUR_BLIND_TOKEN_POS.y, + MAX_SELECTION_SIZE + MAX_HAND_SIZE + 1 + ); // Create the blind token sprite at the top left corner + // TODO: Hide blind token and display it after sliding blind rect animation + // if (playing_blind_token != NULL) + //{ + // obj_hide(playing_blind_token->obj); // Hide the blind token sprite for now + //} + round_end_blind_token = blind_token_new( + current_blind, + 81, + 86, + MAX_SELECTION_SIZE + MAX_HAND_SIZE + 2 + ); // Create the blind token sprite for round end + + if (round_end_blind_token != NULL) + { + obj_hide(round_end_blind_token->obj); // Hide the blind token sprite for now + } + + Rect blind_req_text_rect = BLIND_REQ_TEXT_RECT; + u32 blind_requirement = blind_get_requirement(current_blind, ante); + + char blind_req_str_buff[UINT_MAX_DIGITS + 1]; + + truncate_uint_to_suffixed_str( + blind_requirement, + rect_width(&BLIND_REQ_TEXT_RECT) / TTE_CHAR_SIZE, + blind_req_str_buff + ); + + // Update text rect for right alignment AFTER shortening the number + update_text_rect_to_right_align_str(&blind_req_text_rect, blind_req_str_buff, OVERFLOW_RIGHT); + + tte_printf( + "#{P:%d,%d; cx:0x%X000}%s", + blind_req_text_rect.left, + blind_req_text_rect.top, + TTE_RED_PB, + blind_req_str_buff + ); + tte_printf( + "#{P:%d,%d; cx:0x%X000}$%d", + BLIND_REWARD_RECT.left, + BLIND_REWARD_RECT.top, + TTE_YELLOW_PB, + blind_get_reward(current_blind) + ); // Blind reward + + deck_shuffle(); // Shuffle the deck at the start of the round +} + +static void game_main_menu_on_init() +{ + affine_background_change_background(AFFINE_BG_MAIN_MENU); + change_background(BG_MAIN_MENU); + main_menu_ace = card_object_new(card_new(SPADES, ACE)); + card_object_set_sprite(main_menu_ace, 0); // Set the sprite for the ace of spades + main_menu_ace->sprite_object->sprite->obj->attr0 |= + ATTR0_AFF_DBL; // Make the sprite double sized + main_menu_ace->sprite_object->tx = int2fx(MAIN_MENU_ACE_T.x); + main_menu_ace->sprite_object->x = main_menu_ace->sprite_object->tx; + main_menu_ace->sprite_object->ty = int2fx(MAIN_MENU_ACE_T.y); + main_menu_ace->sprite_object->y = main_menu_ace->sprite_object->ty; + main_menu_ace->sprite_object->tscale = float2fx(0.8f); +} + +static void game_over_init(void) +{ + // Clears the round end menu + main_bg_se_clear_rect(POP_MENU_ANIM_RECT); + main_bg_se_copy_expand_3x3_rect(GAME_OVER_DIALOG_DEST_RECT, GAME_OVER_SRC_RECT_3X3_POS); + main_bg_se_copy_rect(NEW_RUN_BTN_SRC_RECT, NEW_RUN_BTN_DEST_POS); +} + +static void game_lose_on_init() +{ + game_over_init(); + // Using the text color to match the "Game Over" text + affine_background_set_color(TEXT_CLR_RED); +} + +static void game_win_on_init() +{ + game_over_init(); + // Using the text color to match the "You Win" text + affine_background_set_color(TEXT_CLR_BLUE); +} + +// General functions +static inline void set_seed(int seed) +{ + rng_seed = seed; + srand(rng_seed); +} + +static inline void hand_toggle_card_selection(void) +{ + if (hand_state != HAND_SELECT || hand[selection_x] == NULL) + return; if (card_object_is_selected(hand[selection_x])) { @@ -1321,7 +1845,7 @@ void hand_toggle_card_selection() } } -void hand_deselect_all_cards() +static void hand_deselect_all_cards(void) { bool any_cards_deselected = false; for (int i = 0; i <= get_hand_top(); i++) @@ -1340,249 +1864,31 @@ void hand_deselect_all_cards() } } -void hand_change_sort() +static inline void hand_change_sort(void) { sort_by_suit = !sort_by_suit; sort_cards(); } -int hand_get_max_size() +static inline bool hand_play(void) { - return hand_size; -} - -bool hand_discard() -{ - if (hand_state != HAND_SELECT || hand_selections == 0) return false; + if (hand_state != HAND_SELECT || hand_selections == 0) + return false; return true; } -bool hand_play() +static inline void game_playing_process_hand_select_input(void) { - if (hand_state != HAND_SELECT || hand_selections == 0) return false; - return true; -} - -int deck_get_size() -{ - return deck_top + 1; -} - -int deck_get_max_size() -{ - return hand_top + played_top + deck_top + discard_top + 4; // This is the max amount of cards that the player currently has in their possession -} - -void deck_shuffle() -{ - for (int i = deck_top; i > 0; i--) - { - int j = rand() % (i + 1); - Card *temp = deck[i]; - deck[i] = deck[j]; - deck[j] = temp; - } -} - -void increment_blind(enum BlindState increment_reason) -{ - current_blind++; - if (current_blind >= BLIND_TYPE_MAX) - { - current_blind = 0; - blinds[0] = BLIND_STATE_CURRENT; // Reset the blinds to the first one - blinds[1] = BLIND_STATE_UPCOMING; // Set the next blind to upcoming - blinds[2] = BLIND_STATE_UPCOMING; // Set the next blind to upcoming - } - else - { - blinds[current_blind] = BLIND_STATE_CURRENT; - blinds[current_blind - 1] = increment_reason; - } -} - -static void game_round_on_init() -{ - hand_state = HAND_DRAW; - cards_drawn = 0; - hand_selections = 0; - - playing_blind_token = blind_token_new(current_blind, CUR_BLIND_TOKEN_POS.x, CUR_BLIND_TOKEN_POS.y, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 1); // Create the blind token sprite at the top left corner - // TODO: Hide blind token and display it after sliding blind rect animation - //if (playing_blind_token != NULL) - //{ - // obj_hide(playing_blind_token->obj); // Hide the blind token sprite for now - //} - round_end_blind_token = blind_token_new(current_blind, 81, 86, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 2); // Create the blind token sprite for round end - - if (round_end_blind_token != NULL) - { - obj_hide(round_end_blind_token->obj); // Hide the blind token sprite for now - } - - Rect blind_req_text_rect = BLIND_REQ_TEXT_RECT; - u32 blind_requirement = blind_get_requirement(current_blind, ante); - - char blind_req_str_buff[UINT_MAX_DIGITS + 1]; - - truncate_uint_to_suffixed_str(blind_requirement, rect_width(&BLIND_REQ_TEXT_RECT)/TTE_CHAR_SIZE, blind_req_str_buff); - - // Update text rect for right alignment AFTER shortening the number - update_text_rect_to_right_align_str(&blind_req_text_rect, blind_req_str_buff, OVERFLOW_RIGHT); - - tte_printf("#{P:%d,%d; cx:0x%X000}%s", blind_req_text_rect.left, blind_req_text_rect.top, TTE_RED_PB, blind_req_str_buff); - tte_printf("#{P:%d,%d; cx:0x%X000}$%d", BLIND_REWARD_RECT.left, BLIND_REWARD_RECT.top, TTE_YELLOW_PB, blind_get_reward(current_blind)); // Blind reward - - deck_shuffle(); // Shuffle the deck at the start of the round -} - -static void game_main_menu_on_init() -{ - affine_background_change_background(AFFINE_BG_MAIN_MENU); - change_background(BG_MAIN_MENU); - main_menu_ace = card_object_new(card_new(SPADES, ACE)); - card_object_set_sprite(main_menu_ace, 0); // Set the sprite for the ace of spades - main_menu_ace->sprite_object->sprite->obj->attr0 |= ATTR0_AFF_DBL; // Make the sprite double sized - main_menu_ace->sprite_object->tx = int2fx(MAIN_MENU_ACE_T.x); - main_menu_ace->sprite_object->x = main_menu_ace->sprite_object->tx; - main_menu_ace->sprite_object->ty = int2fx(MAIN_MENU_ACE_T.y); - main_menu_ace->sprite_object->y = main_menu_ace->sprite_object->ty; - main_menu_ace->sprite_object->tscale = float2fx(0.8f); -} - -static void game_over_init() -{ - // Clears the round end menu - main_bg_se_clear_rect(POP_MENU_ANIM_RECT); - main_bg_se_copy_expand_3x3_rect(GAME_OVER_DIALOG_DEST_RECT, GAME_OVER_SRC_RECT_3X3_POS); - main_bg_se_copy_rect(NEW_RUN_BTN_SRC_RECT, NEW_RUN_BTN_DEST_POS); -} - -static void game_lose_on_init() -{ - game_over_init(); - // Using the text color to match the "Game Over" text - affine_background_set_color(TEXT_CLR_RED); - -} - -static void game_win_on_init() -{ - game_over_init(); - // Using the text color to match the "You Win" text - affine_background_set_color(TEXT_CLR_BLUE); -} - -// Game functions -void game_change_state(enum GameState new_game_state) -{ - timer = TM_ZERO; // Reset the timer - - if (game_state >= 0 && game_state < GAME_STATE_MAX) - { - state_info[game_state].substate = 0; - state_info[game_state].on_exit(); - } - - if (new_game_state >= 0 && new_game_state < GAME_STATE_MAX) - { - state_info[new_game_state].on_init(); - - game_state = new_game_state; - } -} - -void jokers_available_to_shop_init() -{ - _reset_shop_jokers(); -} - -void game_init() -{ - // Initialize all jokers list once - _owned_jokers_list = list_create(); - _discarded_jokers_list = list_create(); - _expired_jokers_list = list_create(); - _shop_jokers_list = list_create(); - // TODO: Move this to an initialization of the play scoring states - _joker_scored_itr = list_itr_create(&_owned_jokers_list); - - jokers_available_to_shop_init(); - - hands = max_hands; - discards = max_discards; - timer = TM_ZERO; - current_blind = BLIND_TYPE_SMALL; - blinds[0] = BLIND_STATE_CURRENT; - blinds[1] = BLIND_STATE_UPCOMING; - blinds[2] = BLIND_STATE_UPCOMING; - round = STARTING_ROUND; - ante = STARTING_ANTE; - money = STARTING_MONEY; - score = STARTING_SCORE; - - blind_select_tokens[BLIND_TYPE_SMALL] = blind_token_new(BLIND_TYPE_SMALL, CUR_BLIND_TOKEN_POS.x, CUR_BLIND_TOKEN_POS.y, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 3); - blind_select_tokens[BLIND_TYPE_BIG] = blind_token_new(BLIND_TYPE_BIG, CUR_BLIND_TOKEN_POS.x, CUR_BLIND_TOKEN_POS.y, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 4); - blind_select_tokens[BLIND_TYPE_BOSS] = blind_token_new(BLIND_TYPE_BOSS, CUR_BLIND_TOKEN_POS.x, CUR_BLIND_TOKEN_POS.y, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 5); - - obj_hide(blind_select_tokens[BLIND_TYPE_SMALL]->obj); - obj_hide(blind_select_tokens[BLIND_TYPE_BIG]->obj); - obj_hide(blind_select_tokens[BLIND_TYPE_BOSS]->obj); -} - -void game_start() -{ - set_seed(rng_seed); - //set_seed(9); // 9 is a full house - - affine_background_change_background(AFFINE_BG_GAME); - - // Normally I would just cache these and hide/unhide but I didn't feel like dealing with defining a layer for it - card_destroy(&main_menu_ace->card); - card_object_destroy(&main_menu_ace); - - hands = max_hands; - discards = max_discards; - - // Fill the deck with all the cards. Later on this can be replaced with a more dynamic system that allows for different decks and card types. - for (int suit = 0; suit < NUM_SUITS; suit++) - { - for (int rank = 0; rank < NUM_RANKS; rank++) - { - Card *card = card_new(suit, rank); - deck_push(card); - } - } - - change_background(BG_BLIND_SELECT); - - tte_printf("#{P:%d,%d; cx:0x%X000}%d/%d", DECK_SIZE_RECT.left, DECK_SIZE_RECT.top, TTE_WHITE_PB, deck_get_size(), deck_get_max_size()); // Deck size/max size - - display_round(round); // Set the round display - display_score(score); // Set the score display - - display_chips(); // Set the chips display - display_mult(); // Set the multiplier display - - display_hands(hands); // Hand - display_discards(discards); // Discard - - display_money(); // Set the money display - - tte_printf("#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", ANTE_TEXT_RECT.left, ANTE_TEXT_RECT.top, TTE_YELLOW_PB, ante, TTE_WHITE_PB, MAX_ANTE); // Ante - - game_change_state(GAME_STATE_BLIND_SELECT); -} - -static void game_playing_process_hand_select_input() -{ - static bool discard_button_highlighted = false; // true = play button highlighted, false = discard button highlighted + static bool discard_button_highlighted = + false; // true = play button highlighted, false = discard button highlighted if (key_hit(KEY_LEFT)) { if (selection_y == 0) { - hand_set_focus(selection_x + 1); // The reason why this adds 1 is because the hand is drawn from right to left. There is no particular reason for this, it's just how I did it. + // The reason why this adds 1 is because the hand is drawn from right to left. There is + // no particular reason for this, it's just how I did it. + hand_set_focus(selection_x + 1); } else { @@ -1644,15 +1950,23 @@ static void game_playing_process_hand_select_input() selection_y = 0; display_hands(--discards); set_hand(); - tte_printf("#{P:%d,%d; cx:0x%X000}%d", DISCARDS_TEXT_RECT.left, DISCARDS_TEXT_RECT.top, TTE_RED_PB, discards); + tte_printf( + "#{P:%d,%d; cx:0x%X000}%d", + DISCARDS_TEXT_RECT.left, + DISCARDS_TEXT_RECT.top, + TTE_RED_PB, + discards + ); } } } else if (selection_y == 0) // On row of cards { - memcpy16(&pal_bg_mem[PLAY_HAND_BTN_BORDER_PID], &pal_bg_mem[PLAY_HAND_BTN_PID], 1); // Play button highlight color - memcpy16(&pal_bg_mem[DISCARD_BTN_BORDER_PID], &pal_bg_mem[DISCARD_BTN_PID], 1); // Discard button highlight color - + // Play button highlight color + memcpy16(&pal_bg_mem[PLAY_HAND_BTN_BORDER_PID], &pal_bg_mem[PLAY_HAND_BTN_PID], 1); + // Discard button highlight color + memcpy16(&pal_bg_mem[DISCARD_BTN_BORDER_PID], &pal_bg_mem[DISCARD_BTN_PID], 1); + if (key_hit(SELECT_CARD)) { hand_toggle_card_selection(); @@ -1672,89 +1986,39 @@ static void game_playing_process_hand_select_input() } } -/* This needs to stay a power of 2 and small enough - * for the lerping to be done before the next hand is drawn. - */ -#define NUM_SCORE_LERP_STEPS 32 - -static void game_playing_process_input_and_state() +static inline void card_draw(void) { - if (hand_state == HAND_SELECT) - { - game_playing_process_hand_select_input(); - } - else if (play_state == PLAY_ENDING) - { - if (mult > 0) - { - // protect against score overflow - temp_score = u32_protected_mult(chips, mult); - lerped_temp_score = int2fx(temp_score); - lerped_score = int2fx(score); + if (deck_top < 0 || hand_top >= hand_size - 1 || hand_top >= MAX_HAND_SIZE - 1) + return; - display_temp_score(temp_score); + CardObject* card_object = card_object_new(deck_pop()); - chips = 0; - mult = 0; - display_mult(); - display_chips(); - } - } - else if (play_state == PLAY_ENDED) - { - /* Using fixed point in case the score is lower than NUM_SCORE_LERP_STEPS and then - * then the division rounds it down to 0 and it's never added to the total. - * The operation is equivalent to - * fxdiv(int2fx(temp_score * get_game_speed()), int2fx(NUM_SCORE_LERP_STEPS)) - */ - lerped_temp_score -= int2fx(temp_score * get_game_speed()) / NUM_SCORE_LERP_STEPS; - lerped_score += int2fx(temp_score * get_game_speed()) / NUM_SCORE_LERP_STEPS; + const FIXED deck_x = int2fx(CARD_DRAW_POS.x); + const FIXED deck_y = int2fx(CARD_DRAW_POS.y); - if (lerped_temp_score > 0) - { - display_temp_score(fx2uint(lerped_temp_score)); + card_object->sprite_object->x = deck_x; + card_object->sprite_object->y = deck_y; - // We actually don't need to erase this because the score only increases - display_score(fx2uint(lerped_score)); // Set the score display - } - else - { - score = u32_protected_add(score, temp_score); - temp_score = 0; - lerped_temp_score = 0; - lerped_score = 0; + hand[++hand_top] = card_object; - tte_erase_rect_wrapper(TEMP_SCORE_RECT); // Just erase the temp score + // Sort the hand after drawing a card + sort_cards(); - display_score(score); - } - } + play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + cards_drawn * PITCH_STEP_DRAW_SFX); } -static void game_playing_process_card_draw() +static inline void display_ante(int value) { - if (hand_state == HAND_DRAW && cards_drawn < hand_size) - { - if (timer % FRAMES(10) == 0) // Draw a card every 10 frames - { - cards_drawn++; - card_draw(); - } - } - else if (hand_state == HAND_DRAW) - { - hand_state = HAND_SELECT; // Change the hand state to select after drawing all the cards - cards_drawn = 0; - timer = TM_ZERO; - } + tte_printf( + "#{P:%d,%d; cx:0xC000}%d#{cx:0xF000}/%d", + ANTE_TEXT_RECT.left, + ANTE_TEXT_RECT.top, + value, + MAX_ANTE + ); } -static bool game_round_is_over() -{ - return hands == 0 || score >= blind_get_requirement(current_blind, ante); -} - -static void game_playing_handle_round_over() +static inline void game_playing_handle_round_over(void) { enum GameState next_state = GAME_STATE_ROUND_END; @@ -1780,53 +2044,12 @@ static void game_playing_handle_round_over() game_change_state(next_state); } -static void game_playing_discarded_cards_loop() -{ - // Discarded cards loop (mainly for shuffling) - if (hand_get_size() == 0 && hand_state == HAND_SHUFFLING && discard_top >= -1 && timer > FRAMES(10)) - { - change_background(BG_ROUND_END); // Change the background to the round end background. This is how it works in Balatro, so I'm doing it this way too. - - // We take each discarded card and put it back into the deck with a short animation - static CardObject* discarded_card_object = NULL; - if (discarded_card_object == NULL) - { - discarded_card_object = card_object_new(discard_pop()); - //discarded_card_object->sprite = sprite_new(ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, ATTR1_SIZE_32, card_sprite_lut[discarded_card_object->card->suit][discarded_card_object->card->rank], 0, 0); - card_object_set_sprite(discarded_card_object, 0); // Set the sprite for the discarded card object - sprite_object_reset_transform(discarded_card_object->sprite_object); - - discarded_card_object->sprite_object->tx = int2fx(204); - discarded_card_object->sprite_object->ty = int2fx(112); - discarded_card_object->sprite_object->x = int2fx(240); - discarded_card_object->sprite_object->y = int2fx(80); - - card_object_update(discarded_card_object); - } - else - { - card_object_update(discarded_card_object); - - if (discarded_card_object->sprite_object->y >= discarded_card_object->sprite_object->ty) - { - deck_push(discarded_card_object->card); // Put the card back into the deck - card_object_destroy(&discarded_card_object); - - play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + PITCH_STEP_UNDISCARD_SFX); - } - } - - if (discard_top == -1 && discarded_card_object == NULL) // If there are no more discarded cards, stop shuffling - { - // After HAND_SHUFFLING the round is over - game_playing_handle_round_over(); - } - } -} - -static const int HAND_SPACING_LUT[MAX_HAND_SIZE] = { 28, 28, 28, 28, 27, 21, 18, 15, 13, 12, 10, 9, 9, 8, 8, 7 }; // This is a stupid way to do this but I don't care - -void card_in_hand_loop_handle_discard_and_shuffling(int card_idx, FIXED* hand_x, FIXED* hand_y, bool* break_loop) +static inline void card_in_hand_loop_handle_discard_and_shuffling( + int card_idx, + FIXED* hand_x, + FIXED* hand_y, + bool* break_loop +) { if (hand_state != HAND_DISCARD && hand_state != HAND_SHUFFLING) { @@ -1855,7 +2078,8 @@ void card_in_hand_loop_handle_discard_and_shuffling(int card_idx, FIXED* hand_x, sort_cards(); hand_top--; - cards_drawn++; // This technically isn't drawing cards, I'm just reusing the variable + // This technically isn't drawing cards, I'm just reusing the variable + cards_drawn++; sound_played = false; timer = TM_ZERO; @@ -1869,13 +2093,15 @@ void card_in_hand_loop_handle_discard_and_shuffling(int card_idx, FIXED* hand_x, { if (hand_state == HAND_DISCARD) { - *hand_y -= int2fx(15); // Don't raise the card if we're mass discarding, it looks stupid. + // Don't raise the card if we're mass discarding, it looks stupid. + *hand_y -= int2fx(15); } else // hand_state == HAND_SHUFFLING { *hand_y += int2fx(24); } - *hand_x = *hand_x + (int2fx(card_idx) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; + *hand_x = + *hand_x + (int2fx(card_idx) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; } } else @@ -1885,7 +2111,8 @@ void card_in_hand_loop_handle_discard_and_shuffling(int card_idx, FIXED* hand_x, if (card_idx == 0 && discarded_card == false && timer % FRAMES(10) == 0) { - // This is never reached in the case of HAND_SHUFFLING. // Not sure why but that's how it's supposed to be. + // This is never reached in the case of HAND_SHUFFLING. Not sure why but that's how it's + // supposed to be. hand_state = HAND_DRAW; sound_played = false; cards_drawn = 0; @@ -1896,11 +2123,13 @@ void card_in_hand_loop_handle_discard_and_shuffling(int card_idx, FIXED* hand_x, }; } -static void select_flush_and_straight_cards_in_played_hand() +static inline void select_flush_and_straight_cards_in_played_hand(void) { // 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 + + // Will be 4 if Four Fingers is in effect, otherwise 5 + int min_len = get_straight_and_flush_size(); // if we have a flush in our hand if (hand_type == FLUSH || hand_type == STRAIGHT_FLUSH || hand_type == ROYAL_FLUSH) @@ -1918,7 +2147,13 @@ static void select_flush_and_straight_cards_in_played_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); + 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++) { @@ -1939,7 +2174,7 @@ static void select_flush_and_straight_cards_in_played_hand() } } -static void select_all_five_cards_in_played_hand() +static inline void select_all_five_cards_in_played_hand(void) { for (int i = 0; i <= played_top; i++) { @@ -1947,16 +2182,19 @@ static void select_all_five_cards_in_played_hand() } } -static void select_four_of_a_kind_cards_in_played_hand() +static inline void select_four_of_a_kind_cards_in_played_hand(void) { // 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 + // If there are 5 cards selected we just need to find the one card that doesn't match, and + // select the others + if (played_top >= 3) { 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) + 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; @@ -1980,7 +2218,7 @@ static void select_four_of_a_kind_cards_in_played_hand() } } -static void select_three_of_a_kind_cards_in_played_hand() +static inline void select_three_of_a_kind_cards_in_played_hand(void) { // find three cards with the same rank for (int i = 0; i <= played_top - 1; i++) @@ -1994,7 +2232,8 @@ static void select_three_of_a_kind_cards_in_played_hand() for (int k = j + 1; k <= played_top; k++) { - if (played[i]->card->rank == played[k]->card->rank && !card_object_is_selected(played[k])) + if (played[i]->card->rank == played[k]->card->rank && + !card_object_is_selected(played[k])) { card_object_set_selected(played[k], true); break; @@ -2010,7 +2249,7 @@ static void select_three_of_a_kind_cards_in_played_hand() } } -static void select_two_pair_cards_in_played_hand() +static inline void select_two_pair_cards_in_played_hand(void) { // find two pairs of cards with the same rank int i; @@ -2036,7 +2275,8 @@ static void select_two_pair_cards_in_played_hand() { for (int j = i + 1; j <= played_top; j++) { - if (played[i]->card->rank == played[j]->card->rank && !card_object_is_selected(played[i]) && !card_object_is_selected(played[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); @@ -2046,7 +2286,7 @@ static void select_two_pair_cards_in_played_hand() } } -static void select_pair_cards_in_played_hand() +static inline void select_pair_cards_in_played_hand(void) { // find two cards with the same rank for (int i = 0; i <= played_top - 1; i++) @@ -2066,7 +2306,7 @@ static void select_pair_cards_in_played_hand() } } -static void select_highcard_cards_in_played_hand() +static inline void select_highcard_cards_in_played_hand(void) { // find the card with the highest rank in the hand int highest_rank_index = 0; @@ -2082,143 +2322,16 @@ static void select_highcard_cards_in_played_hand() card_object_set_selected(played[highest_rank_index], true); } -static void cards_in_hand_update_loop() -{ - // 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) - { - FIXED hand_x = int2fx(HAND_START_POS.x); - FIXED hand_y = int2fx(HAND_START_POS.y); - - switch (hand_state) - { - case HAND_DRAW: - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; - break; - case HAND_SELECT: - bool is_focused = (i == selection_x && selection_y == 0); - - if (is_focused && !card_object_is_selected(hand[i])) - { - hand_y -= int2fx(CARD_FOCUSED_UNSEL_Y); - } - else if (!is_focused && card_object_is_selected(hand[i])) - { - hand_y -= int2fx(CARD_UNFOCUSED_SEL_Y); - } - else if (is_focused && card_object_is_selected(hand[i])) - { - hand_y -= int2fx(CARD_FOCUSED_SEL_Y); - } - - if (i != selection_x && hand[i]->sprite_object->y > hand_y) - { - hand[i]->sprite_object->y = hand_y; - hand[i]->sprite_object->vy = 0; - } - - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; // TODO: Change this later to reference a 2D LUT of positions - break; - case HAND_SHUFFLING: - /* FALL THROUGH */ - case HAND_DISCARD: // TODO: Add sound - bool break_loop; - card_in_hand_loop_handle_discard_and_shuffling(i, &hand_x, &hand_y, &break_loop); - if (break_loop) break; - - break; - case HAND_PLAY: - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; - hand_y += int2fx(24); - - if (card_object_is_selected(hand[i]) && discarded_card == false && timer % FRAMES(10) == 0) - { - card_object_set_selected(hand[i], false); - played_push(hand[i]); - sprite_destroy(&hand[i]->sprite_object->sprite); - hand[i] = NULL; - sort_cards(); - - play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + cards_drawn*PITCH_STEP_DISCARD_SFX); - - hand_top--; - hand_selections--; - cards_drawn++; - - discarded_card = true; - } - - if (i == 0 && discarded_card == false && timer % FRAMES(10) == 0) - { - hand_state = HAND_PLAYING; - cards_drawn = 0; - hand_selections = 0; - timer = TM_ZERO; - scored_card_index = played_top + 1; - - switch (hand_type) // select the cards that apply to the hand type - { - case NONE: - break; - case HIGH_CARD: - select_highcard_cards_in_played_hand(); - break; - case PAIR: - select_pair_cards_in_played_hand(); - break; - case TWO_PAIR: - select_two_pair_cards_in_played_hand(); - break; - case THREE_OF_A_KIND: - select_three_of_a_kind_cards_in_played_hand(); - break; - case FOUR_OF_A_KIND: - select_four_of_a_kind_cards_in_played_hand(); - break; - case STRAIGHT: - /* FALL THROUGH */ - case FLUSH: - /* 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 - select_all_five_cards_in_played_hand(); - break; - } - } - - break; - case HAND_PLAYING: // Don't need to do anything here, just wait for the player to select cards - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; - hand_y += int2fx(24); - break; - } - - hand[i]->sprite_object->tx = hand_x; - hand[i]->sprite_object->ty = hand_y; - card_object_update(hand[i]); - } - } -} - // returns true if a joker was scored, false otherwise -static bool check_and_score_joker_for_event(ListItr* starting_joker_itr, CardObject* card_object, enum JokerEvent joker_event) +static bool check_and_score_joker_for_event( + ListItr* starting_joker_itr, + CardObject* card_object, + enum JokerEvent joker_event +) { JokerObject* joker; - while((joker = list_itr_next(starting_joker_itr))) + while ((joker = list_itr_next(starting_joker_itr))) { if (joker_object_score(joker, card_object, joker_event)) { @@ -2228,226 +2341,9 @@ static bool check_and_score_joker_for_event(ListItr* starting_joker_itr, CardObj return false; } - -static void play_starting_played_cards_update(int played_idx) +static inline bool game_round_is_over(void) { - if (played_idx == played_top && (timer % FRAMES(10) == 0 || !card_object_is_selected(played[played_top - scored_card_index])) && timer > FRAMES(40)) - { - scored_card_index--; - - if (scored_card_index == 0) - { - _joker_scored_itr = list_itr_create(&_owned_jokers_list); - timer = TM_ZERO; - play_state = PLAY_BEFORE_SCORING; - } - } - - played[played_idx]->sprite_object->tx = int2fx(HAND_PLAY_POS.x) + (int2fx(played_top - played_idx) - int2fx(played_top) / 2) * -27; - played[played_idx]->sprite_object->ty = int2fx(HAND_PLAY_POS.y); - - if (card_object_is_selected(played[played_idx]) && played_top - played_idx >= scored_card_index) - { - played[played_idx]->sprite_object->ty -= int2fx(10); - } -} - -// returns true if the scoring loop has returned early -static bool play_before_scoring_cards_update() -{ - // Activate Jokers with an effect just before the hand is scored - if (check_and_score_joker_for_event(&_joker_scored_itr, NULL, JOKER_EVENT_ON_HAND_PLAYED)) - { - return true; - } - - play_state = PLAY_SCORING_CARDS; - return false; -} - -// returns true if the scoring loop has returned early -static bool play_scoring_cards_update(void) -{ - if (timer % FRAMES(30) == 0 && timer > FRAMES(40)) - { - // We are about to score played Cards. - // Start from the current card index - // and seek the next scoring card - while (scored_card_index <= played_top && !card_object_is_selected(played[scored_card_index])) - { - scored_card_index++; - } - - // go to the next state if there are no cards left to score - if (scored_card_index > played_top) - { - // reuse these variables for held cards - _joker_scored_itr = list_itr_create(&_owned_jokers_list); - scored_card_index = hand_top; - - play_state = PLAY_SCORING_HELD_CARDS; - - return false; - } - - tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT); - - CardObject* scored_card_object = played[scored_card_index]; - - if (card_object_is_selected(scored_card_object)) - { - tte_set_pos(fx2int(scored_card_object->sprite_object->x) + TILE_SIZE, SCORED_CARD_TEXT_Y); // Offset of 1 tile to keep the text on the card - tte_set_special(TTE_BLUE_PB * TTE_SPECIAL_PB_MULT_OFFSET); // Set text color to blue from background memory - - u8 card_value = card_get_value(scored_card_object->card); - - // 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), "+%hhu", card_value); - tte_write(score_buffer); - - card_object_shake(scored_card_object, SFX_CHIPS_CARD); - - // Relocated card scoring logic here - chips = u32_protected_add(chips, card_value); - display_chips(); - - // Allow Joker scoring - _joker_scored_itr = list_itr_create(&_owned_jokers_list); - _joker_card_scored_end_itr = list_itr_create(&_owned_jokers_list); - } - - play_state = PLAY_SCORING_CARD_JOKERS; - return true; - } - - return false; -} - -// Activate "on scored" Jokers for the previous scored card if any -// returns true if the scoring loop has returned early -static bool play_scoring_card_jokers_update() -{ - if (timer % FRAMES(30) == 0 && timer > FRAMES(40)) - { - tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT); - - // since we sought the next scoring card index in the previous state, - // scored_card_index is guaranteed to be a scoring card - if (check_and_score_joker_for_event(&_joker_scored_itr, played[scored_card_index], JOKER_EVENT_ON_CARD_SCORED)) - { - return true; - } - - // Trigger all Jokers that have an effect when a card finishes scoring - // (e.g. retriggers) after activating all the other scored_card Jokers normally - if (check_and_score_joker_for_event(&_joker_card_scored_end_itr, played[scored_card_index], JOKER_EVENT_ON_CARD_SCORED_END)) - { - // If we just scored a retrigger, return early and go back to the - // previous state score the same card again without incrementing - // scored_card_index to score the current card again - if (retrigger) - { - retrigger = false; - play_state = PLAY_SCORING_CARDS; - } - return true; - } - - // increment index to start seeking the next scoring card from the next card - scored_card_index++; - play_state = PLAY_SCORING_CARDS; - } - - return false; -} - -// returns true if the scoring loop has returned early -static bool play_scoring_held_cards_update(int played_idx) -{ - if (played_idx == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40)) - { - tte_erase_rect_wrapper(HELD_CARDS_SCORES_RECT); - - // Go through all held cards and see if they activate Jokers - for ( ; scored_card_index >= 0; scored_card_index--) - { - if (check_and_score_joker_for_event(&_joker_scored_itr, hand[scored_card_index], JOKER_EVENT_ON_CARD_HELD)) - { - card_object_shake(hand[scored_card_index], SFX_CARD_SELECT); - return true; - } - _joker_scored_itr = list_itr_create(&_owned_jokers_list); - } - - scored_card_index = 0; - _joker_round_end_itr = list_itr_create(&_owned_jokers_list); - play_state = PLAY_SCORING_INDEPENDENT_JOKERS; - } - - return false; -} - -// Score Jokers normally (independent) -// returns true if the scoring loop has returned early -static bool play_scoring_independent_jokers_update(int played_idx) -{ - if (played_idx == 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_itr, NULL, JOKER_EVENT_INDEPENDENT)) - { - return true; - } - - scored_card_index = played_top + 1; // Reset the scored card index to the top of the played stack - - play_state = PLAY_SCORING_HAND_SCORED_END; - } - - return false; -} - -// Trigger hand end effect for all jokers once they are done scoring -static bool play_scoring_hand_scored_end_update(int played_idx) -{ - if (played_idx == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40)) - { - - tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT); - - if (check_and_score_joker_for_event(&_joker_round_end_itr, NULL, JOKER_EVENT_ON_HAND_SCORED_END)) - { - return true; - } - - timer = TM_ZERO; - play_state = PLAY_ENDING; - } - - return false; -} - -// This is the reverse of PLAY_STARTING. The cards get reset back to their neutral position sequentially -static void play_ending_played_cards_update(int played_idx) -{ - if (played_idx == played_top && (timer % FRAMES(10) == 0 || !card_object_is_selected(played[played_top - scored_card_index])) && timer > FRAMES(40)) - { - scored_card_index--; - - if (scored_card_index == 0) - { - timer = TM_ZERO; - play_state = PLAY_ENDED; - } - } - - if (card_object_is_selected(played[played_idx]) && played_top - played_idx >= scored_card_index) - { - played[played_idx]->sprite_object->ty = int2fx(HAND_PLAY_POS.y); - } + return hands == 0 || score >= blind_get_requirement(current_blind, ante); } // Basically a copy of HAND_DISCARD @@ -2459,7 +2355,7 @@ static bool play_ended_played_cards_update(int played_idx) // play the sound only once per card, when it is pushed off-screen to the right if (!sound_played) { - play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + cards_drawn*PITCH_STEP_DISCARD_SFX); + play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + cards_drawn * PITCH_STEP_DISCARD_SFX); sound_played = true; } @@ -2469,7 +2365,7 @@ static bool play_ended_played_cards_update(int played_idx) discard_push(played[played_idx]->card); // Push the card to the discard pile card_object_destroy(&played[played_idx]); - //played_top--; + // played_top--; cards_drawn++; // This technically isn't drawing cards, I'm just reusing the variable sound_played = false; // Allow for the sound for the next card to be played @@ -2505,10 +2401,265 @@ static bool play_ended_played_cards_update(int played_idx) return false; } -static void played_cards_update_loop() +static inline void play_starting_played_cards_update(int played_idx) { - // 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) + bool card_selected = card_object_is_selected(played[played_top - scored_card_index]); + if (played_idx == played_top && (timer % FRAMES(10) == 0 || !card_selected) && + timer > FRAMES(40)) + { + scored_card_index--; + + if (scored_card_index == 0) + { + _joker_scored_itr = list_itr_create(&_owned_jokers_list); + timer = TM_ZERO; + play_state = PLAY_BEFORE_SCORING; + } + } + + played[played_idx]->sprite_object->tx = + int2fx(HAND_PLAY_POS.x) + (int2fx(played_top - played_idx) - int2fx(played_top) / 2) * -27; + played[played_idx]->sprite_object->ty = int2fx(HAND_PLAY_POS.y); + + card_selected = card_object_is_selected(played[played_idx]); + if (card_selected && played_top - played_idx >= scored_card_index) + { + played[played_idx]->sprite_object->ty -= int2fx(10); + } +} + +// returns true if the scoring loop has returned early +static inline bool play_before_scoring_cards_update(void) +{ + // Activate Jokers with an effect just before the hand is scored + if (check_and_score_joker_for_event(&_joker_scored_itr, NULL, JOKER_EVENT_ON_HAND_PLAYED)) + { + return true; + } + + play_state = PLAY_SCORING_CARDS; + return false; +} + +// returns true if the scoring loop has returned early +static inline bool play_scoring_cards_update(void) +{ + if (timer % FRAMES(30) == 0 && timer > FRAMES(40)) + { + // We are about to score played Cards. + // Start from the current card index + // and seek the next scoring card + while (scored_card_index <= played_top && + !card_object_is_selected(played[scored_card_index])) + { + scored_card_index++; + } + + // go to the next state if there are no cards left to score + if (scored_card_index > played_top) + { + // reuse these variables for held cards + _joker_scored_itr = list_itr_create(&_owned_jokers_list); + scored_card_index = hand_top; + + play_state = PLAY_SCORING_HELD_CARDS; + + return false; + } + + tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT); + + CardObject* scored_card_object = played[scored_card_index]; + + if (card_object_is_selected(scored_card_object)) + { + // Offset of 1 tile to keep the text on the card + tte_set_pos( + fx2int(scored_card_object->sprite_object->x) + TILE_SIZE, + SCORED_CARD_TEXT_Y + ); + + // Set text color to blue from background memory + tte_set_special(TTE_BLUE_PB * TTE_SPECIAL_PB_MULT_OFFSET); + + u8 card_value = card_get_value(scored_card_object->card); + + // 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), "+%hhu", card_value); + tte_write(score_buffer); + + card_object_shake(scored_card_object, SFX_CHIPS_CARD); + + // Relocated card scoring logic here + chips = u32_protected_add(chips, card_value); + display_chips(); + + // Allow Joker scoring + _joker_scored_itr = list_itr_create(&_owned_jokers_list); + _joker_card_scored_end_itr = list_itr_create(&_owned_jokers_list); + } + + play_state = PLAY_SCORING_CARD_JOKERS; + return true; + } + + return false; +} + +// Activate "on scored" Jokers for the previous scored card if any +// returns true if the scoring loop has returned early +static inline bool play_scoring_card_jokers_update(void) +{ + if (timer % FRAMES(30) == 0 && timer > FRAMES(40)) + { + tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT); + + // since we sought the next scoring card index in the previous state, + // scored_card_index is guaranteed to be a scoring card + if (check_and_score_joker_for_event( + &_joker_scored_itr, + played[scored_card_index], + JOKER_EVENT_ON_CARD_SCORED + )) + { + return true; + } + + // Trigger all Jokers that have an effect when a card finishes scoring + // (e.g. retriggers) after activating all the other scored_card Jokers normally + if (check_and_score_joker_for_event( + &_joker_card_scored_end_itr, + played[scored_card_index], + JOKER_EVENT_ON_CARD_SCORED_END + )) + { + // If we just scored a retrigger, return early and go back to the + // previous state score the same card again without incrementing + // scored_card_index to score the current card again + if (retrigger) + { + retrigger = false; + play_state = PLAY_SCORING_CARDS; + } + return true; + } + + // increment index to start seeking the next scoring card from the next card + scored_card_index++; + play_state = PLAY_SCORING_CARDS; + } + + return false; +} + +// returns true if the scoring loop has returned early +static inline bool play_scoring_held_cards_update(int played_idx) +{ + if (played_idx == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40)) + { + tte_erase_rect_wrapper(HELD_CARDS_SCORES_RECT); + + // Go through all held cards and see if they activate Jokers + for (; scored_card_index >= 0; scored_card_index--) + { + if (check_and_score_joker_for_event( + &_joker_scored_itr, + hand[scored_card_index], + JOKER_EVENT_ON_CARD_HELD + )) + { + card_object_shake(hand[scored_card_index], SFX_CARD_SELECT); + return true; + } + _joker_scored_itr = list_itr_create(&_owned_jokers_list); + } + + scored_card_index = 0; + _joker_round_end_itr = list_itr_create(&_owned_jokers_list); + play_state = PLAY_SCORING_INDEPENDENT_JOKERS; + } + + return false; +} + +// Score Jokers normally (independent) +// returns true if the scoring loop has returned early +static inline bool play_scoring_independent_jokers_update(int played_idx) +{ + if (played_idx == 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_itr, NULL, JOKER_EVENT_INDEPENDENT)) + { + return true; + } + + scored_card_index = + played_top + 1; // Reset the scored card index to the top of the played stack + + play_state = PLAY_SCORING_HAND_SCORED_END; + } + + return false; +} + +// Trigger hand end effect for all jokers once they are done scoring +static inline bool play_scoring_hand_scored_end_update(int played_idx) +{ + if (played_idx == 0 && (timer % FRAMES(30) == 0) && timer > FRAMES(40)) + { + + tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT); + + bool scored = check_and_score_joker_for_event( + &_joker_round_end_itr, + NULL, + JOKER_EVENT_ON_HAND_SCORED_END + ); + + if (scored) + { + return true; + } + + timer = TM_ZERO; + play_state = PLAY_ENDING; + } + + return false; +} + +// This is the reverse of PLAY_STARTING. The cards get reset back to their neutral position +// sequentially +static inline void play_ending_played_cards_update(int played_idx) +{ + bool card_selected = card_object_is_selected(played[played_top - scored_card_index]); + if (played_idx == played_top && (timer % FRAMES(10) == 0 || !card_selected) && + timer > FRAMES(40)) + { + scored_card_index--; + + if (scored_card_index == 0) + { + timer = TM_ZERO; + play_state = PLAY_ENDED; + } + } + + if (card_object_is_selected(played[played_idx]) && played_top - played_idx >= scored_card_index) + { + played[played_idx]->sprite_object->ty = int2fx(HAND_PLAY_POS.y); + } +} + +static inline void played_cards_update_loop(void) +{ + // 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 played_idx = 0; played_idx <= played_top; played_idx++) { if (played[played_idx] == NULL) @@ -2518,7 +2669,8 @@ static void played_cards_update_loop() if (card_object_get_sprite(played[played_idx]) == NULL) { - card_object_set_sprite(played[played_idx], played_idx + MAX_HAND_SIZE); // Set the sprite for the played card object + // Set the sprite for the played card object + card_object_set_sprite(played[played_idx], played_idx + MAX_HAND_SIZE); } switch (play_state) @@ -2527,7 +2679,7 @@ static void played_cards_update_loop() play_starting_played_cards_update(played_idx); break; - + case PLAY_BEFORE_SCORING: if (play_before_scoring_cards_update()) @@ -2543,7 +2695,7 @@ static void played_cards_update_loop() return; } break; - + case PLAY_SCORING_CARD_JOKERS: if (play_scoring_card_jokers_update()) @@ -2551,7 +2703,7 @@ static void played_cards_update_loop() return; } break; - + case PLAY_SCORING_HELD_CARDS: if (play_scoring_held_cards_update(played_idx)) @@ -2567,7 +2719,7 @@ static void played_cards_update_loop() return; } break; - + case PLAY_SCORING_HAND_SCORED_END: if (play_scoring_hand_scored_end_update(played_idx)) @@ -2585,10 +2737,10 @@ static void played_cards_update_loop() if (play_ended_played_cards_update(played_idx)) { - // we continue here instead of returning for performance - // to instantly go to the next card to discard at played_idx+1, + // we continue here instead of returning for performance + // to instantly go to the next card to discard at played_idx+1, // instead of starting over from index 0 and going up - // to that card again + // to that card again continue; } break; @@ -2599,7 +2751,288 @@ static void played_cards_update_loop() } } -static void game_playing_ui_text_update() +static inline int hand_get_max_size(void) +{ + return hand_size; +} + +/* This needs to stay a power of 2 and small enough + * for the lerping to be done before the next hand is drawn. + */ +static inline void game_playing_process_input_and_state(void) +{ + if (hand_state == HAND_SELECT) + { + game_playing_process_hand_select_input(); + } + else if (play_state == PLAY_ENDING) + { + if (mult > 0) + { + // protect against score overflow + temp_score = u32_protected_mult(chips, mult); + lerped_temp_score = int2fx(temp_score); + lerped_score = int2fx(score); + + display_temp_score(temp_score); + + chips = 0; + mult = 0; + display_mult(); + display_chips(); + } + } + else if (play_state == PLAY_ENDED) + { + /* Using fixed point in case the score is lower than NUM_SCORE_LERP_STEPS and then + * then the division rounds it down to 0 and it's never added to the total. + * The operation is equivalent to + * fxdiv(int2fx(temp_score * get_game_speed()), int2fx(NUM_SCORE_LERP_STEPS)) + */ + lerped_temp_score -= int2fx(temp_score * get_game_speed()) / NUM_SCORE_LERP_STEPS; + lerped_score += int2fx(temp_score * get_game_speed()) / NUM_SCORE_LERP_STEPS; + + if (lerped_temp_score > 0) + { + display_temp_score(fx2uint(lerped_temp_score)); + + // We actually don't need to erase this because the score only increases + display_score(fx2uint(lerped_score)); // Set the score display + } + else + { + score = u32_protected_add(score, temp_score); + temp_score = 0; + lerped_temp_score = 0; + lerped_score = 0; + + tte_erase_rect_wrapper(TEMP_SCORE_RECT); // Just erase the temp score + + display_score(score); + } + } +} + +static inline void game_playing_process_card_draw() +{ + if (hand_state == HAND_DRAW && cards_drawn < hand_size) + { + if (timer % FRAMES(10) == 0) // Draw a card every 10 frames + { + cards_drawn++; + card_draw(); + } + } + else if (hand_state == HAND_DRAW) + { + hand_state = HAND_SELECT; // Change the hand state to select after drawing all the cards + cards_drawn = 0; + timer = TM_ZERO; + } +} + +static inline void game_playing_discarded_cards_loop(void) +{ + // Discarded cards loop (mainly for shuffling) + if (hand_get_size() == 0 && hand_state == HAND_SHUFFLING && discard_top >= -1 && + timer > FRAMES(10)) + { + // Change the background to the round end background. This is how it works in Balatro, so + // I'm doing it this way too. + change_background(BG_ROUND_END); + + // We take each discarded card and put it back into the deck with a short animation + static CardObject* discarded_card_object = NULL; + if (discarded_card_object == NULL) + { + discarded_card_object = card_object_new(discard_pop()); + // discarded_card_object->sprite = sprite_new(ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, + // ATTR1_SIZE_32, + // card_sprite_lut[discarded_card_object->card->suit][discarded_card_object->card->rank], + // 0, 0); + // Set the sprite for the discarded card object + card_object_set_sprite(discarded_card_object, 0); + sprite_object_reset_transform(discarded_card_object->sprite_object); + + discarded_card_object->sprite_object->tx = int2fx(204); + discarded_card_object->sprite_object->ty = int2fx(112); + discarded_card_object->sprite_object->x = int2fx(240); + discarded_card_object->sprite_object->y = int2fx(80); + + card_object_update(discarded_card_object); + } + else + { + card_object_update(discarded_card_object); + + if (discarded_card_object->sprite_object->y >= discarded_card_object->sprite_object->ty) + { + deck_push(discarded_card_object->card); // Put the card back into the deck + card_object_destroy(&discarded_card_object); + + play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + PITCH_STEP_UNDISCARD_SFX); + } + } + + // If there are no more discarded cards, stop shuffling + if (discard_top == -1 && discarded_card_object == NULL) + { + // After HAND_SHUFFLING the round is over + game_playing_handle_round_over(); + } + } +} + +static inline void cards_in_hand_update_loop(void) +{ + // TODO: Break this function up into smaller ones, Gods be good + // Start from the end of the hand and work backwards because that's how Balatro does it + for (int i = hand_top + 1; i >= 0; i--) + { + if (hand[i] != NULL) + { + FIXED hand_x = int2fx(HAND_START_POS.x); + FIXED hand_y = int2fx(HAND_START_POS.y); + + switch (hand_state) + { + case HAND_DRAW: + hand_x = + hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; + break; + case HAND_SELECT: + bool is_focused = (i == selection_x && selection_y == 0); + + if (is_focused && !card_object_is_selected(hand[i])) + { + hand_y -= int2fx(CARD_FOCUSED_UNSEL_Y); + } + else if (!is_focused && card_object_is_selected(hand[i])) + { + hand_y -= int2fx(CARD_UNFOCUSED_SEL_Y); + } + else if (is_focused && card_object_is_selected(hand[i])) + { + hand_y -= int2fx(CARD_FOCUSED_SEL_Y); + } + + if (i != selection_x && hand[i]->sprite_object->y > hand_y) + { + hand[i]->sprite_object->y = hand_y; + hand[i]->sprite_object->vy = 0; + } + + hand_x = + hand_x + (int2fx(i) - int2fx(hand_top) / 2) * + -HAND_SPACING_LUT[hand_top]; // TODO: Change this later to + // reference a 2D LUT of positions + break; + case HAND_SHUFFLING: + /* FALL THROUGH */ + case HAND_DISCARD: // TODO: Add sound + bool break_loop; + card_in_hand_loop_handle_discard_and_shuffling( + i, + &hand_x, + &hand_y, + &break_loop + ); + if (break_loop) + break; + + break; + case HAND_PLAY: + hand_x = + hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; + hand_y += int2fx(24); + + if (card_object_is_selected(hand[i]) && discarded_card == false && + timer % FRAMES(10) == 0) + { + card_object_set_selected(hand[i], false); + played_push(hand[i]); + sprite_destroy(&hand[i]->sprite_object->sprite); + hand[i] = NULL; + sort_cards(); + + play_sfx( + SFX_CARD_DRAW, + MM_BASE_PITCH_RATE + cards_drawn * PITCH_STEP_DISCARD_SFX + ); + + hand_top--; + hand_selections--; + cards_drawn++; + + discarded_card = true; + } + + if (i == 0 && discarded_card == false && timer % FRAMES(10) == 0) + { + hand_state = HAND_PLAYING; + cards_drawn = 0; + hand_selections = 0; + timer = TM_ZERO; + scored_card_index = played_top + 1; + + switch (hand_type) // select the cards that apply to the hand type + { + case NONE: + break; + case HIGH_CARD: + select_highcard_cards_in_played_hand(); + break; + case PAIR: + select_pair_cards_in_played_hand(); + break; + case TWO_PAIR: + select_two_pair_cards_in_played_hand(); + break; + case THREE_OF_A_KIND: + select_three_of_a_kind_cards_in_played_hand(); + break; + case FOUR_OF_A_KIND: + select_four_of_a_kind_cards_in_played_hand(); + break; + case STRAIGHT: + /* FALL THROUGH */ + case FLUSH: + /* 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 + select_all_five_cards_in_played_hand(); + break; + } + } + + break; + // Don't need to do anything here, just wait for the player to select cards + case HAND_PLAYING: + hand_x = + hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; + hand_y += int2fx(24); + break; + } + + hand[i]->sprite_object->tx = hand_x; + hand[i]->sprite_object->ty = hand_y; + card_object_update(hand[i]); + } + } +} + +static inline void game_playing_ui_text_update(void) { static int last_hand_size = 0; static int last_deck_size = 0; @@ -2608,24 +3041,48 @@ static void game_playing_ui_text_update() { if (background == BG_CARD_SELECTING) { - tte_printf("#{P:%d,%d; cx:0x%X000}%d/%d", HAND_SIZE_RECT_SELECT.left, HAND_SIZE_RECT_SELECT.top, TTE_WHITE_PB, hand_get_size(), hand_get_max_size()); // Hand size/max size + // Hand size/max size + tte_printf( + "#{P:%d,%d; cx:0x%X000}%d/%d", + HAND_SIZE_RECT_SELECT.left, + HAND_SIZE_RECT_SELECT.top, + TTE_WHITE_PB, + hand_get_size(), + hand_get_max_size() + ); } else if (background == BG_CARD_PLAYING) { - tte_printf("#{P:%d,%d; cx:0x%X000}%d/%d", HAND_SIZE_RECT_PLAYING.left, HAND_SIZE_RECT_PLAYING.top, TTE_WHITE_PB, hand_get_size(), hand_get_max_size()); // Hand size/max size + // Hand size/max size + tte_printf( + "#{P:%d,%d; cx:0x%X000}%d/%d", + HAND_SIZE_RECT_PLAYING.left, + HAND_SIZE_RECT_PLAYING.top, + TTE_WHITE_PB, + hand_get_size(), + hand_get_max_size() + ); } - tte_printf("#{P:%d,%d; cx:0x%X000}%d/%d", DECK_SIZE_RECT.left, DECK_SIZE_RECT.top, TTE_WHITE_PB, deck_get_size(), deck_get_max_size()); // Deck size/max size + // Deck size/max size + tte_printf( + "#{P:%d,%d; cx:0x%X000}%d/%d", + DECK_SIZE_RECT.left, + DECK_SIZE_RECT.top, + TTE_WHITE_PB, + deck_get_size(), + deck_get_max_size() + ); last_hand_size = hand_get_size(); last_deck_size = deck_get_size(); } } -static void game_playing_process_flaming_score() +static inline void game_playing_process_flaming_score(void) { static u8 flame_score_frame = 0; - + if (score_flames_active) { if (timer % SCORE_FLAMES_ANIM_FREQ == 0) @@ -2634,19 +3091,19 @@ static void game_playing_process_flaming_score() flame_score_frame = (flame_score_frame + 1) % NUM_SCORE_FLAMES_FRAMES; // chips flame (blue) - frame_rect.top += flame_score_frame; + frame_rect.top += flame_score_frame; frame_rect.bottom += flame_score_frame; main_bg_se_copy_rect(frame_rect, SCORE_FLAME_CHIPS_POS); // mult flame (red) - frame_rect.left += SCORE_FLAME_FRAME_WIDTH; + frame_rect.left += SCORE_FLAME_FRAME_WIDTH; frame_rect.right += SCORE_FLAME_FRAME_WIDTH; main_bg_se_copy_rect(frame_rect, SCORE_FLAME_MULT_POS); } } } -static void game_playing_on_update() +static void game_playing_on_update(void) { // Background logic (thissss might be moved to the card'ssss logic later. I'm a sssssnake) if (hand_state == HAND_DRAW || hand_state == HAND_DISCARD || hand_state == HAND_SELECT) @@ -2669,39 +3126,25 @@ static void game_playing_on_update() discarded_card = false; cards_in_hand_update_loop(); - played_cards_update_loop(); - + played_cards_update_loop(); + game_playing_ui_text_update(); // animate score flames if we exceed the score requirement game_playing_process_flaming_score(); } -static int calculate_interest_reward() +static int calculate_interest_reward(void) { - int reward = (money / 5) * INTEREST_PER_5; + int reward = (money / 5) * INTEREST_PER_5; if (reward > MAX_INTEREST) - reward = MAX_INTEREST; + reward = MAX_INTEREST; return reward; } -static void game_round_end_cashout() -{ - money += hands + blind_get_reward(current_blind) + calculate_interest_reward(); // Reward the player - display_money(); - - hands = max_hands; // Reset the hands to the maximum - discards = max_discards; // Reset the discards to the maximum - display_hands(hands); // Set the hands display - display_discards(discards); // Set the discards display - - score = 0; - display_score(score); // Set the score display -} - static void game_round_end_on_exit() { - // Cleanup blind tokens from this round to avoid accumulating + // Cleanup blind tokens from this round to avoid accumulating // allocated blind sprites each round blind_reward = 0; hand_reward = 0; @@ -2713,7 +3156,7 @@ static void game_round_end_on_exit() static void game_round_end_on_update() { - if (state_info[game_state].substate == ROUND_END_EXIT) + if (state_info[game_state].substate == ROUND_END_EXIT) { game_change_state(GAME_STATE_SHOP); return; @@ -2725,11 +3168,12 @@ static void game_round_end_on_update() static void game_round_end_start() { - if (timer == TM_RESET_STATIC_VARS) // Reset static variables to default values upon re-entering the round end state - { + // Reset static variables to default values upon re-entering the round end state + if (timer == TM_RESET_STATIC_VARS) + { change_background(BG_ROUND_END); // Change the background to the round end background state_info[game_state].substate = START_EXPAND_POPUP; // Change the state to the next one - timer = TM_ZERO; // Reset the timer + timer = TM_ZERO; // Reset the timer blind_reward = blind_get_reward(current_blind); hand_reward = hands; interest_reward = calculate_interest_reward(); @@ -2741,7 +3185,7 @@ static void game_round_end_start() static void game_round_end_start_expand_popup() { main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SCREEN_UP); - + if (timer == TM_END_POP_MENU_ANIM) { state_info[game_state].substate = DISPLAY_FINISHED_BLIND; @@ -2760,10 +3204,13 @@ static void game_round_end_extend_black_panel_down(int black_panel_bottom) static void game_round_end_display_finished_blind() { obj_unhide(round_end_blind_token->obj, 0); - + int current_ante = ante; - if (current_blind == BLIND_TYPE_BOSS) current_ante--; // Beating the boss blind increases the ante, so we need to display the previous ante value - + + // Beating the boss blind increases the ante, so we need to display the previous ante value + if (current_blind == BLIND_TYPE_BOSS) + current_ante--; + Rect blind_req_rect = ROUND_END_BLIND_REQ_RECT; u32 blind_req = blind_get_requirement(current_blind, current_ante); @@ -2775,14 +3222,20 @@ static void game_round_end_display_finished_blind() snprintf(blind_req_str_buff, sizeof(blind_req_str_buff), "%lu", blind_req); update_text_rect_to_right_align_str(&blind_req_rect, blind_req_str_buff, OVERFLOW_RIGHT); - - tte_printf("#{P:%d,%d; cx:0x%X000}%s", blind_req_rect.left, blind_req_rect.top, TTE_RED_PB, blind_req_str_buff); - + + tte_printf( + "#{P:%d,%d; cx:0x%X000}%s", + blind_req_rect.left, + blind_req_rect.top, + TTE_RED_PB, + blind_req_str_buff + ); + if (timer == TM_START_ROUND_END_REWARDS_ANIM) { game_round_end_extend_black_panel_down(ROUND_END_BLACK_PANEL_INIT_BOTTOM_SE); } - + if (timer >= TM_END_DISPLAY_FIN_BLIND) { state_info[game_state].substate = DISPLAY_SCORE_MIN; @@ -2797,9 +3250,13 @@ static void game_round_end_display_score_min() const int y_from = 29; const int x_to = 13; const int y_to = 11; - - memcpy16(&se_mem[MAIN_BG_SBB][x_to + timer_offset + 32 * y_to], &se_mem[MAIN_BG_SBB][x_from + timer_offset + 32 * y_from], 1); - + + memcpy16( + &se_mem[MAIN_BG_SBB][x_to + timer_offset + 32 * y_to], + &se_mem[MAIN_BG_SBB][x_from + timer_offset + 32 * y_from], + 1 + ); + if (timer >= TM_END_DISPLAY_SCORE_MIN) { state_info[game_state].substate = UPDATE_BLIND_REWARD; @@ -2809,15 +3266,28 @@ static void game_round_end_display_score_min() static void game_round_end_update_blind_reward() { - if (timer % FRAMES(20) != 0) return; - + if (timer % FRAMES(20) != 0) + return; + // TODO: Add sound effect here - + if (blind_reward > 0) { blind_reward--; - tte_printf("#{P:%d,%d; cx:0x%X000}$%d", BLIND_REWARD_RECT.left , BLIND_REWARD_RECT.top, TTE_YELLOW_PB, blind_reward); - tte_printf("#{P:%d,%d; cx:0x%X000}$%d", ROUND_END_BLIND_REWARD_RECT.left, ROUND_END_BLIND_REWARD_RECT.top, TTE_YELLOW_PB, blind_get_reward(current_blind) - blind_reward); + tte_printf( + "#{P:%d,%d; cx:0x%X000}$%d", + BLIND_REWARD_RECT.left, + BLIND_REWARD_RECT.top, + TTE_YELLOW_PB, + blind_reward + ); + tte_printf( + "#{P:%d,%d; cx:0x%X000}$%d", + ROUND_END_BLIND_REWARD_RECT.left, + ROUND_END_BLIND_REWARD_RECT.top, + TTE_YELLOW_PB, + blind_get_reward(current_blind) - blind_reward + ); } else if (timer > FRAMES(20)) { @@ -2837,8 +3307,9 @@ static void game_round_end_panel_exit() if (timer < 8) { main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SCREEN_UP); - - if (timer == 1) // Copied from shop. Feels slightly too niche of a function for me personally to make one. + + if (timer == 1) // Copied from shop. Feels slightly too niche of a function for me + // personally to make one. { reset_top_left_panel_bottom_row(); } @@ -2847,9 +3318,9 @@ static void game_round_end_panel_exit() int y = 5; memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0001, 1); memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0002, 7); - memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1); + memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1); } - } + } else if (timer > FRAMES(20)) { memset16(&pal_bg_mem[REWARD_PANEL_BORDER_PID], 0x1483, 1); @@ -2858,16 +3329,17 @@ static void game_round_end_panel_exit() } } -static void game_round_end_print_separator_ellipsis() +static inline void game_round_end_print_separator_ellipsis(void) { - int x = (ROUND_END_REWARDS_ELLIPSIS_POS.x + timer - TM_REWARDS_ELLIPSIS_PRINT_START) * TILE_SIZE; + int x = + (ROUND_END_REWARDS_ELLIPSIS_POS.x + timer - TM_REWARDS_ELLIPSIS_PRINT_START) * TILE_SIZE; int y = (ROUND_END_REWARDS_ELLIPSIS_POS.y) * TILE_SIZE; tte_printf("#{P:%d,%d; cx:0x%X000}.", x, y, TTE_WHITE_PB); } // TODO: Allow for more generic rewards and consolidate with game_round_end_print_interest_reward() -static void game_round_end_print_hand_reward(int hand_y_offset) +static inline void game_round_end_print_hand_reward(int hand_y_offset) { int hand_y = ROUND_END_REWARDS_ELLIPSIS_POS.y + hand_y_offset; if (timer == TM_DISPLAY_REWARDS_CONT_WAIT) @@ -2875,18 +3347,24 @@ static void game_round_end_print_hand_reward(int hand_y_offset) game_round_end_extend_black_panel_down(hand_y); tte_printf( - "#{P:%d,%d; cx:0x%X000}%d #{cx:0x%X000}Hands", - ROUND_END_REWARD_TEXT_X, hand_y * TILE_SIZE, - TTE_BLUE_PB, hand_reward, TTE_WHITE_PB + "#{P:%d,%d; cx:0x%X000}%d #{cx:0x%X000}Hands", + ROUND_END_REWARD_TEXT_X, + hand_y * TILE_SIZE, + TTE_BLUE_PB, + hand_reward, + TTE_WHITE_PB ); } - else if (timer > TM_HAND_REWARD_INCR_WAIT && timer % FRAMES(TM_REWARD_INCREMENT_INTERVAL) == 0) // Increment the hand reward text until the hand reward variable is depleted + // Increment the hand reward text until the hand reward variable is depleted + else if (timer > TM_HAND_REWARD_INCR_WAIT && timer % FRAMES(TM_REWARD_INCREMENT_INTERVAL) == 0) { hand_reward--; tte_printf( - "#{P:%d, %d; cx:0x%X000}$%d", - ROUND_END_REWARD_AMOUNT_X, hand_y * TILE_SIZE, - TTE_YELLOW_PB, hands - hand_reward + "#{P:%d, %d; cx:0x%X000}$%d", + ROUND_END_REWARD_AMOUNT_X, + hand_y * TILE_SIZE, + TTE_YELLOW_PB, + hands - hand_reward ); if (hand_reward == 0) { @@ -2895,7 +3373,7 @@ static void game_round_end_print_hand_reward(int hand_y_offset) } } -static void game_round_end_print_interest_reward(int interest_y_offset) +static inline void game_round_end_print_interest_reward(int interest_y_offset) { int interest_y = ROUND_END_REWARDS_ELLIPSIS_POS.y + interest_y_offset; @@ -2904,18 +3382,25 @@ static void game_round_end_print_interest_reward(int interest_y_offset) game_round_end_extend_black_panel_down(interest_y); tte_printf( - "#{P:%d,%d; cx:0x%X000}%d #{cx:0x%X000}Interest", - ROUND_END_REWARD_TEXT_X, interest_y * TILE_SIZE, - TTE_YELLOW_PB, interest_reward, TTE_WHITE_PB + "#{P:%d,%d; cx:0x%X000}%d #{cx:0x%X000}Interest", + ROUND_END_REWARD_TEXT_X, + interest_y * TILE_SIZE, + TTE_YELLOW_PB, + interest_reward, + TTE_WHITE_PB ); } - else if (timer > interest_start_time + TM_REWARD_DISPLAY_INTERVAL && timer % FRAMES(TM_REWARD_INCREMENT_INTERVAL) == 0) // Increment the interest reward text until the interest reward variable is depleted + // Increment the interest reward text until the interest reward variable is depleted + else if (timer > interest_start_time + TM_REWARD_DISPLAY_INTERVAL && + timer % FRAMES(TM_REWARD_INCREMENT_INTERVAL) == 0) { interest_to_count--; tte_printf( - "#{P:%d, %d; cx:0x%X000}$%d", - ROUND_END_REWARD_AMOUNT_X, interest_y * TILE_SIZE, - TTE_YELLOW_PB, interest_reward - interest_to_count + "#{P:%d, %d; cx:0x%X000}$%d", + ROUND_END_REWARD_AMOUNT_X, + interest_y * TILE_SIZE, + TTE_YELLOW_PB, + interest_reward - interest_to_count ); } } @@ -2939,7 +3424,8 @@ static void game_round_end_display_rewards() interest_y_offset = hand_y_offset + 1; } - if (hand_reward <= 0 && interest_to_count <= 0) // Once all rewards are accounted for go to the next state + // Once all rewards are accounted for go to the next state + if (hand_reward <= 0 && interest_to_count <= 0) { timer = TM_ZERO; state_info[game_state].substate = DISPLAY_CASHOUT; @@ -2956,31 +3442,57 @@ static void game_round_end_display_rewards() { game_round_end_print_hand_reward(hand_y_offset); } - else if (interest_start_time != UNDEFINED && timer >= interest_start_time && interest_to_count > 0) + else if (interest_start_time != UNDEFINED && timer >= interest_start_time && + interest_to_count > 0) { game_round_end_print_interest_reward(interest_y_offset); } } +static inline void game_round_end_cashout(void) +{ + // Reward the player + money += hands + blind_get_reward(current_blind) + calculate_interest_reward(); + display_money(); + + hands = max_hands; // Reset the hands to the maximum + discards = max_discards; // Reset the discards to the maximum + display_hands(hands); // Set the hands display + display_discards(discards); // Set the discards display + + score = 0; + display_score(score); // Set the score display +} + static void game_round_end_display_cashout() { - if (timer == FRAMES(40)) // Put the "cash out" button onto the round end panel + if (timer == FRAMES(40)) { + // Put the "cash out" button onto the round end panel main_bg_se_copy_expand_3x3_rect(CASHOUT_DEST_RECT, CASHOUT_SRC_3X3_RECT_POS); - + int cashout_amount = hands + blind_get_reward(current_blind) + calculate_interest_reward(); bool omit_space = cashout_amount >= 10; - tte_printf("#{P:%d, %d; cx:0x%X000}Cash Out:%s$%d", CASHOUT_TEXT_RECT.left, CASHOUT_TEXT_RECT.top, TTE_WHITE_PB, omit_space ? "" : " " , cashout_amount); + tte_printf( + "#{P:%d, %d; cx:0x%X000}Cash Out:%s$%d", + CASHOUT_TEXT_RECT.left, + CASHOUT_TEXT_RECT.top, + TTE_WHITE_PB, + omit_space ? "" : " ", + cashout_amount + ); } - else if (timer > FRAMES(40) && key_hit(SELECT_CARD)) // Wait until the player presses A to cash out + + // Wait until the player presses A to cash out + else if (timer > FRAMES(40) && key_hit(SELECT_CARD)) { game_round_end_cashout(); - + state_info[game_state].substate = DISMISS_ROUND_END_PANEL; // Go to the next state timer = TM_ZERO; - - obj_hide(round_end_blind_token->obj); // Hide the blind token object + + obj_hide(round_end_blind_token->obj); // Hide the blind token object tte_erase_rect_wrapper(BLIND_TOKEN_TEXT_RECT); // Erase the blind token text } } @@ -2990,51 +3502,45 @@ static void game_round_end_dismiss_round_end_panel() Rect round_end_down = ROUND_END_MENU_RECT; round_end_down.top--; main_bg_se_copy_rect_1_tile_vert(round_end_down, SCREEN_DOWN); - + if (timer >= TM_DISMISS_ROUND_END_TM) { - timer = TM_ZERO; + timer = TM_ZERO; state_info[game_state].substate = ROUND_END_EXIT; } } -// Shop -#define REROLL_BASE_COST 5 // Base cost for rerolling the shop items -static int reroll_cost = REROLL_BASE_COST; - -#define NEXT_ROUND_BTN_SEL_X 0 - -#define REROLL_BTN_FRAME_PAL_IDX 7 -#define REROLL_BTN_PAL_IDX 3 - -void print_price_under_sprite_object(SpriteObject *sprite_object, int price) +static void print_price_under_sprite_object(SpriteObject* sprite_object, int price) { int x = fx2int(sprite_object->tx) + TILE_SIZE - (get_digits_even(price) - 1) * TILE_SIZE; - int y = fx2int(sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE; // TODO: Should probably extract the sprite size + // TODO: Should probably extract the sprite size + int y = fx2int(sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE; tte_printf("#{P:%d,%d; cx:0x%X000}$%d", x, y, TTE_YELLOW_PB, price); } -void erase_price_under_sprite_object(SpriteObject *sprite_object) +static void erase_price_under_sprite_object(SpriteObject* sprite_object) { Rect price_rect; price_rect.left = fx2int(sprite_object->tx); - price_rect.top = fx2int(sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE; + price_rect.top = fx2int(sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE; // TODO: Should probably extract the size from the sprite - + price_rect.right = price_rect.left + TILE_SIZE * 3; - price_rect.bottom = price_rect.top + 2*TILE_SIZE; // Taking 2 tiles down so the highlighted case is also covered + // Taking 2 tiles down so the highlighted case is also covered + price_rect.bottom = price_rect.top + 2 * TILE_SIZE; tte_erase_rect_wrapper(price_rect); } -static int game_shop_get_rand_available_joker_id(void) +static inline int game_shop_get_rand_available_joker_id(void) { // Roll for what rarity the joker will be int joker_rarity = joker_get_random_rarity(); - - // Now determine how many jokers are available based on the rarity - int jokers_avail_size = _get_num_shop_jokers_avail(); - if(jokers_avail_size == 0) return UNDEFINED; + // Now determine how many jokers are available based on the rarity + int jokers_avail_size = get_num_shop_jokers_avail(); + + if (jokers_avail_size == 0) + return UNDEFINED; int matching_joker_ids[jokers_avail_size]; int fallback_random_idx = random() % jokers_avail_size; @@ -3045,28 +3551,29 @@ static int game_shop_get_rand_available_joker_id(void) int i = 0; int joker_id = UNDEFINED; - while((joker_id = bitset_itr_next(&itr)) != UNDEFINED) + while ((joker_id = bitset_itr_next(&itr)) != UNDEFINED) { - if(i++ == fallback_random_idx) fallback_random_joker_id = joker_id; - const JokerInfo *info = get_joker_registry_entry(joker_id); + if (i++ == fallback_random_idx) + fallback_random_joker_id = joker_id; + const JokerInfo* info = get_joker_registry_entry(joker_id); if (info->rarity == joker_rarity) { matching_joker_ids[match_count++] = joker_id; } } - int selected_joker_id = (match_count > 0) ? - matching_joker_ids[random() % match_count] : - fallback_random_joker_id; + int selected_joker_id = + (match_count > 0) ? matching_joker_ids[random() % match_count] : fallback_random_joker_id; return selected_joker_id; } -static void game_shop_create_items() +static void game_shop_create_items(void) { tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); - if (_no_avail_jokers()) return; + if (no_avail_jokers()) + return; list_clear(&_shop_jokers_list); _shop_jokers_list = list_create(); @@ -3074,30 +3581,31 @@ static void game_shop_create_items() for (int i = 0; i < MAX_SHOP_JOKERS; i++) { int joker_id = 0; - #ifdef TEST_JOKER_ID0 // Allow defining an ID for a joker to always appear in shop and be tested +#ifdef TEST_JOKER_ID0 // Allow defining an ID for a joker to always appear in shop and be tested if (_get_shop_joker_avail(TEST_JOKER_ID0)) { joker_id = TEST_JOKER_ID0; } else - #endif - #ifdef TEST_JOKER_ID1 - if (_get_shop_joker_avail(TEST_JOKER_ID1)) +#endif +#ifdef TEST_JOKER_ID1 + if (_get_shop_joker_avail(TEST_JOKER_ID1)) { joker_id = TEST_JOKER_ID1; } else - #endif +#endif { joker_id = game_shop_get_rand_available_joker_id(); } // If for some reason only no joker is left, don't make another - if(joker_id == UNDEFINED) break; + if (joker_id == UNDEFINED) + break; - _set_shop_joker_avail(joker_id, false); - - JokerObject *joker_object = joker_object_new(joker_new(joker_id)); + set_shop_joker_avail(joker_id, false); + + JokerObject* joker_object = joker_object_new(joker_new(joker_id)); joker_object->sprite_object->x = int2fx(120 + i * CARD_SPRITE_SIZE); joker_object->sprite_object->y = int2fx(160); @@ -3106,7 +3614,11 @@ static void game_shop_create_items() print_price_under_sprite_object(joker_object->sprite_object, joker_object->joker->value); - sprite_position(joker_object_get_sprite(joker_object), fx2int(joker_object->sprite_object->x), fx2int(joker_object->sprite_object->y)); + sprite_position( + joker_object_get_sprite(joker_object), + fx2int(joker_object->sprite_object->x), + fx2int(joker_object->sprite_object->y) + ); list_push_back(&_shop_jokers_list, joker_object); } @@ -3132,8 +3644,8 @@ static void game_shop_intro() int y_from = 26 + y - timer_offset; int y_to = 0 + y; - Rect from = { 0, y_from, 8, y_from }; - BG_POINT to = { 0, y_to }; + Rect from = {0, y_from, 8, y_from}; + BG_POINT to = {0, y_to}; main_bg_se_copy_rect(from, to); } @@ -3146,57 +3658,23 @@ static void game_shop_intro() } } -static void game_shop_reroll(int *reroll_cost) -{ - money -= *reroll_cost; - display_money(); // Update the money display - - ListItr itr = list_itr_create(&_shop_jokers_list); - JokerObject* joker_object; - - while((joker_object = list_itr_next(&itr))) - { - if (joker_object != NULL) - { - _set_shop_joker_avail(joker_object->joker->id, true); - joker_object_destroy(&joker_object); // Destroy the joker object if it exists - } - } - - list_clear(&_shop_jokers_list); - _shop_jokers_list = list_create(); - - game_shop_create_items(); - - itr = list_itr_create(&_shop_jokers_list); - - while((joker_object = list_itr_next(&itr))) - { - if (joker_object != NULL) - { - joker_object->sprite_object->y = joker_object->sprite_object->ty; // Set the y position to the target position - joker_object_shake(joker_object, UNDEFINED); // Give the joker a little wiggle animation - } - } - - (*reroll_cost)++; - tte_printf("#{P:%d,%d; cx:0x%X000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, TTE_WHITE_PB, *reroll_cost); -} - -static int jokers_sel_row_get_size() +static int jokers_sel_row_get_size(void) { return list_get_len(&_owned_jokers_list); } -static void jokers_sel_row_on_selection_changed(SelectionGrid *selection_grid, - int row_idx, - const Selection *prev_selection, - const Selection *new_selection) +static void jokers_sel_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +) { if (prev_selection->y == row_idx) { - JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_owned_jokers_list, prev_selection->x); - if(joker_object != NULL) + JokerObject* joker_object = + (JokerObject*)list_get_at_idx(&_owned_jokers_list, prev_selection->x); + if (joker_object != NULL) { erase_price_under_sprite_object(joker_object->sprite_object); sprite_object_set_focus(joker_object->sprite_object, false); @@ -3205,27 +3683,31 @@ static void jokers_sel_row_on_selection_changed(SelectionGrid *selection_grid, if (new_selection->y == row_idx) { - JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_owned_jokers_list, new_selection->x); - if(joker_object != NULL) + JokerObject* joker_object = + (JokerObject*)list_get_at_idx(&_owned_jokers_list, new_selection->x); + if (joker_object != NULL) { sprite_object_set_focus(joker_object->sprite_object, true); - print_price_under_sprite_object(joker_object->sprite_object, joker_get_sell_value(joker_object->joker)); + print_price_under_sprite_object( + joker_object->sprite_object, + joker_get_sell_value(joker_object->joker) + ); } } } -void joker_start_discard_animation(JokerObject *joker_object) +static inline void joker_start_discard_animation(JokerObject* joker_object) { joker_object->sprite_object->tx = int2fx(JOKER_DISCARD_TARGET.x); joker_object->sprite_object->ty = int2fx(JOKER_DISCARD_TARGET.y); list_push_back(&_discarded_jokers_list, joker_object); } -void game_sell_joker(int joker_idx) +static inline void game_sell_joker(int joker_idx) { if (joker_idx < 0 || joker_idx >= list_get_len(&_owned_jokers_list)) return; - + JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_owned_jokers_list, joker_idx); money += joker_get_sell_value(joker_object->joker); display_money(); @@ -3247,20 +3729,21 @@ static void jokers_sel_row_on_key_hit(SelectionGrid* selection_grid, Selection* } // Shop input -static int shop_top_row_get_size() +static int shop_top_row_get_size(void) { - return list_get_len(&_shop_jokers_list) + 1; // + 1 to account for next round button + // + 1 to account for next round button + return list_get_len(&_shop_jokers_list) + 1; } -static void add_to_held_jokers(JokerObject *joker_object) +static inline void add_to_held_jokers(JokerObject* joker_object) { joker_object->sprite_object->ty = int2fx(HELD_JOKERS_POS.y); add_joker(joker_object); } -static void game_shop_buy_joker(int shop_joker_idx) +static inline void game_shop_buy_joker(int shop_joker_idx) { - JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx); + JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx); money -= joker_object->joker->value; // Deduct the money spent on the joker display_money(); // Update the money display @@ -3279,51 +3762,64 @@ static void shop_top_row_on_key_hit(SelectionGrid* selection_grid, Selection* se { // Go to next blind selection game state state_info[game_state].substate = GAME_SHOP_EXIT; // Go to the outro sequence state - timer = TM_ZERO; // Reset the timer + timer = TM_ZERO; // Reset the timer reroll_cost = REROLL_BASE_COST; - memcpy16(&pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], &pal_bg_mem[SHOP_PANEL_SHADOW_PID], 1); + memcpy16( + &pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], + &pal_bg_mem[SHOP_PANEL_SHADOW_PID], + 1 + ); - // memcpy16(&pal_bg_mem[16], &pal_bg_mem[6], 1); + // memcpy16(&pal_bg_mem[16], &pal_bg_mem[6], 1); // This changes the color of the button to a dark red. - // However, it shares a palette with the shop icon, so it will change the color of the shop icon as well. - // And I don't care enough to fix it right now. + // However, it shares a palette with the shop icon, so it will change the color of the shop + // icon as well. And I don't care enough to fix it right now. } - else + else { int shop_joker_idx = selection->x - 1; // - 1 to account for next round button - JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx); - if (joker_object == NULL - || list_get_len(&_owned_jokers_list) >= MAX_JOKERS_HELD_SIZE - || money < joker_object->joker->value) + JokerObject* joker_object = + (JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx); + if (joker_object == NULL || list_get_len(&_owned_jokers_list) >= MAX_JOKERS_HELD_SIZE || + money < joker_object->joker->value) { return; } game_shop_buy_joker(shop_joker_idx); - // In Balatro the selection actually stays on the purchased joker it's easier to just move it left + // In Balatro the selection actually stays on the purchased joker it's easier to just move + // it left selection_grid_move_selection_horz(selection_grid, -1); } } -static void shop_top_row_on_selection_changed(SelectionGrid* selection_grid, int row_idx, - const Selection* prev_selection, - const Selection* new_selection) +static void shop_top_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +) { // The selection grid system only guarantees that the new selection is within bounds - if (prev_selection->y == row_idx && prev_selection->x >= 0 && prev_selection->x < shop_top_row_get_size()) + if (prev_selection->y == row_idx && prev_selection->x >= 0 && + prev_selection->x < shop_top_row_get_size()) { if (prev_selection->x == NEXT_ROUND_BTN_SEL_X) { // Remove next round button highlight - memcpy16(&pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], &pal_bg_mem[NEXT_ROUND_BTN_PID], 1); + memcpy16( + &pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], + &pal_bg_mem[NEXT_ROUND_BTN_PID], + 1 + ); } - else + else { - int idx = prev_selection->x - 1; // -1 to account for next round button - JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, idx); - sprite_object_set_focus(joker_object->sprite_object, false); + int idx = prev_selection->x - 1; // -1 to account for next round button + JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, idx); + sprite_object_set_focus(joker_object->sprite_object, false); // -1 to account for next round button } } @@ -3335,11 +3831,11 @@ static void shop_top_row_on_selection_changed(SelectionGrid* selection_grid, int // Highlight next round button memset16(&pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], HIGHLIGHT_COLOR, 1); } - else + else { int idx = new_selection->x - 1; // -1 to account for next round button - JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, idx); - sprite_object_set_focus(joker_object->sprite_object, true); + JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, idx); + sprite_object_set_focus(joker_object->sprite_object, true); } } } @@ -3349,7 +3845,12 @@ static int shop_reroll_row_get_size() return 1; // Only the reroll button } -static void shop_reroll_row_on_selection_changed(SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, const Selection* new_selection) +static void shop_reroll_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +) { if (row_idx == prev_selection->y) { @@ -3362,6 +3863,52 @@ static void shop_reroll_row_on_selection_changed(SelectionGrid* selection_grid, } } +static inline void game_shop_reroll(int* reroll_cost) +{ + money -= *reroll_cost; + display_money(); // Update the money display + + ListItr itr = list_itr_create(&_shop_jokers_list); + JokerObject* joker_object; + + while ((joker_object = list_itr_next(&itr))) + { + if (joker_object != NULL) + { + set_shop_joker_avail(joker_object->joker->id, true); + joker_object_destroy(&joker_object); // Destroy the joker object if it exists + } + } + + list_clear(&_shop_jokers_list); + _shop_jokers_list = list_create(); + + game_shop_create_items(); + + itr = list_itr_create(&_shop_jokers_list); + + while ((joker_object = list_itr_next(&itr))) + { + if (joker_object != NULL) + { + // Set the y position to the target position + joker_object->sprite_object->y = joker_object->sprite_object->ty; + + // Give the joker a little wiggle animation + joker_object_shake(joker_object, UNDEFINED); + } + } + + (*reroll_cost)++; + tte_printf( + "#{P:%d,%d; cx:0x%X000}$%d", + SHOP_REROLL_RECT.left, + SHOP_REROLL_RECT.top, + TTE_WHITE_PB, + *reroll_cost + ); +} + static void shop_reroll_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection) { if (money >= reroll_cost) @@ -3370,58 +3917,28 @@ static void shop_reroll_row_on_key_hit(SelectionGrid* selection_grid, Selection* } } -SelectionGridRow shop_selection_rows[] = -{ - {0, jokers_sel_row_get_size, jokers_sel_row_on_selection_changed, jokers_sel_row_on_key_hit}, - {1, shop_top_row_get_size, shop_top_row_on_selection_changed, shop_top_row_on_key_hit}, - {2, shop_reroll_row_get_size, shop_reroll_row_on_selection_changed, shop_reroll_row_on_key_hit} -}; - -static const Selection SHOP_INIT_SEL = {-1, 1}; - -SelectionGrid shop_selection_grid = {shop_selection_rows, NUM_ELEM_IN_ARR(shop_selection_rows), SHOP_INIT_SEL}; - // Shop menu input and selection static void game_shop_process_user_input() { if (timer == TM_SHOP_PRC_INPUT_START) { - // The selection grid is initialized outside of bounds and moved + // The selection grid is initialized outside of bounds and moved // to trigger the selection change so the initial selection is visible shop_selection_grid.selection = SHOP_INIT_SEL; selection_grid_move_selection_horz(&shop_selection_grid, 1); - tte_printf("#{P:%d,%d; cx:0x%X000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, TTE_WHITE_PB, reroll_cost); + tte_printf( + "#{P:%d,%d; cx:0x%X000}$%d", + SHOP_REROLL_RECT.left, + SHOP_REROLL_RECT.top, + TTE_WHITE_PB, + reroll_cost + ); } // Shop input logic selection_grid_process_input(&shop_selection_grid); } -static void game_shop_lights_anim_frame() -{ - // Shift palette around the border of the shop icon - COLOR shifted_palette[4]; - memcpy16(&shifted_palette[0], &pal_bg_mem[SHOP_LIGHTS_2_PID], 1); - memcpy16(&shifted_palette[1], &pal_bg_mem[SHOP_LIGHTS_3_PID], 1); - memcpy16(&shifted_palette[2], &pal_bg_mem[SHOP_LIGHTS_4_PID], 1); - memcpy16(&shifted_palette[3], &pal_bg_mem[SHOP_LIGHTS_1_PID], 1); - - // Circularly shift the palette - int last = shifted_palette[3]; - - for (int i = 3; i > 0; --i) - { - shifted_palette[i] = shifted_palette[i - 1]; - } - - shifted_palette[0] = last; - - memcpy16(&pal_bg_mem[SHOP_LIGHTS_2_PID], &shifted_palette[0], 1); // Copy the shifted palette to the next 4 slots - memcpy16(&pal_bg_mem[SHOP_LIGHTS_3_PID], &shifted_palette[1], 1); - memcpy16(&pal_bg_mem[SHOP_LIGHTS_4_PID], &shifted_palette[2], 1); - memcpy16(&pal_bg_mem[SHOP_LIGHTS_1_PID], &shifted_palette[3], 1); -} - // Outro sequence (menu and shop icon going out of frame) static void game_shop_outro() { @@ -3438,7 +3955,7 @@ static void game_shop_outro() ListItr itr = list_itr_create(&_shop_jokers_list); JokerObject* joker_object; - while((joker_object = list_itr_next(&itr))) + while ((joker_object = list_itr_next(&itr))) { if (joker_object != NULL) { @@ -3459,10 +3976,36 @@ static void game_shop_outro() if (timer >= MENU_POP_OUT_ANIM_FRAMES) { state_info[game_state].substate = GAME_SHOP_MAX; // Go to the next state - timer = TM_ZERO; // Reset the timer + timer = TM_ZERO; // Reset the timer } } +static inline void game_shop_lights_anim_frame(void) +{ + // Shift palette around the border of the shop icon + COLOR shifted_palette[4]; + memcpy16(&shifted_palette[0], &pal_bg_mem[SHOP_LIGHTS_2_PID], 1); + memcpy16(&shifted_palette[1], &pal_bg_mem[SHOP_LIGHTS_3_PID], 1); + memcpy16(&shifted_palette[2], &pal_bg_mem[SHOP_LIGHTS_4_PID], 1); + memcpy16(&shifted_palette[3], &pal_bg_mem[SHOP_LIGHTS_1_PID], 1); + + // Circularly shift the palette + int last = shifted_palette[3]; + + for (int i = 3; i > 0; --i) + { + shifted_palette[i] = shifted_palette[i - 1]; + } + + shifted_palette[0] = last; + + // Copy the shifted palette to the next 4 slots + memcpy16(&pal_bg_mem[SHOP_LIGHTS_2_PID], &shifted_palette[0], 1); + memcpy16(&pal_bg_mem[SHOP_LIGHTS_3_PID], &shifted_palette[1], 1); + memcpy16(&pal_bg_mem[SHOP_LIGHTS_4_PID], &shifted_palette[2], 1); + memcpy16(&pal_bg_mem[SHOP_LIGHTS_1_PID], &shifted_palette[3], 1); +} + static void game_shop_on_update() { change_background(BG_SHOP); @@ -3471,7 +4014,7 @@ static void game_shop_on_update() { ListItr itr = list_itr_create(&_shop_jokers_list); JokerObject* joker_object; - while((joker_object = list_itr_next(&itr))) + while ((joker_object = list_itr_next(&itr))) { if (joker_object != NULL) { @@ -3501,18 +4044,18 @@ static void game_shop_on_exit() ListItr itr = list_itr_create(&_shop_jokers_list); JokerObject* joker_object; - while((joker_object = list_itr_next(&itr))) + while ((joker_object = list_itr_next(&itr))) { if (joker_object != NULL) { // Make the joker available back to shop - _set_shop_joker_avail(joker_object->joker->id, true); + set_shop_joker_avail(joker_object->joker->id, true); } joker_object_destroy(&joker_object); // Destroy the joker objects } list_clear(&_shop_jokers_list); - + increment_blind(BLIND_STATE_DEFEATED); // TODO: Move to game_round_end()? } @@ -3532,12 +4075,16 @@ static void game_blind_select_start_anim_seq() { change_background(BG_BLIND_SELECT); main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SCREEN_UP); - + for (int i = 0; i < BLIND_TYPE_MAX; i++) { - sprite_position(blind_select_tokens[i], blind_select_tokens[i]->pos.x, blind_select_tokens[i]->pos.y - TILE_SIZE); + sprite_position( + blind_select_tokens[i], + blind_select_tokens[i]->pos.x, + blind_select_tokens[i]->pos.y - TILE_SIZE + ); } - + if (timer == TM_END_ANIM_SEQ) { state_info[game_state].substate = BLIND_SELECT; @@ -3551,7 +4098,7 @@ static void game_blind_select_handle_input() { selection_y = 0; } - + // Blind select input logic if (key_hit(KEY_UP)) { @@ -3572,35 +4119,47 @@ static void game_blind_select_handle_input() else if (current_blind != BLIND_TYPE_BOSS) { increment_blind(BLIND_STATE_SKIPPED); - + background = UNDEFINED; // Force refresh of the background change_background(BG_BLIND_SELECT); - + // TODO: Create a generic vertical move by any number of tiles to avoid for loops? for (int i = 0; i < 12; i++) { main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SCREEN_UP); } - + for (int i = 0; i < BLIND_TYPE_MAX; i++) { - sprite_position(blind_select_tokens[i], blind_select_tokens[i]->pos.x, blind_select_tokens[i]->pos.y - (TILE_SIZE * 12)); + sprite_position( + blind_select_tokens[i], + blind_select_tokens[i]->pos.x, + blind_select_tokens[i]->pos.y - (TILE_SIZE * 12) + ); } - + timer = TM_ZERO; } } - + if (selection_y == 0) { - // 5 is the multiplier palette color and the skip button color + // 5 is the multiplier palette color and the skip button color memset16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], 0xFFFF, 1); - memcpy16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SKIP_BTN_PID], 1); + memcpy16( + &pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], + &pal_bg_mem[BLIND_SKIP_BTN_PID], + 1 + ); } else { - // 15 is the select button color - memcpy16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SELECT_BTN_PID], 1); + // 15 is the select button color + memcpy16( + &pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], + &pal_bg_mem[BLIND_SELECT_BTN_PID], + 1 + ); memset16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], 0xFFFF, 1); } } @@ -3612,10 +4171,14 @@ static void game_blind_select_selected_anim_seq() Rect blinds_rect = POP_MENU_ANIM_RECT; blinds_rect.top -= 1; // Because of the raised blind main_bg_se_move_rect_1_tile_vert(blinds_rect, SCREEN_DOWN); - + for (int i = 0; i < BLIND_TYPE_MAX; i++) { - sprite_position(blind_select_tokens[i], blind_select_tokens[i]->pos.x, blind_select_tokens[i]->pos.y + TILE_SIZE); + sprite_position( + blind_select_tokens[i], + blind_select_tokens[i]->pos.x, + blind_select_tokens[i]->pos.y + TILE_SIZE + ); } } else if (timer >= MENU_POP_OUT_ANIM_FRAMES) @@ -3624,9 +4187,9 @@ static void game_blind_select_selected_anim_seq() { obj_hide(blind_select_tokens[i]->obj); } - + state_info[game_state].substate = DISPLAY_BLIND_PANEL; // Reset the state - timer = TM_ZERO; // Reset the timer + timer = TM_ZERO; // Reset the timer } } @@ -3634,38 +4197,40 @@ static void game_blind_select_display_blind_panel() { if (timer >= TM_DISP_BLIND_PANEL_FINISH) { - state_info[game_state].substate = BLIND_SELECT_MAX; - return; + state_info[game_state].substate = BLIND_SELECT_MAX; + return; } - - if (timer == TM_DISP_BLIND_PANEL_START) // Switches to the selecting background and clears the blind panel area + + // Switches to the selecting background and clears the blind panel area + if (timer == TM_DISP_BLIND_PANEL_START) { change_background(BG_CARD_SELECTING); - + main_bg_se_clear_rect(ROUND_END_MENU_RECT); - + for (int y = 0; y < 5; y++) { int y_from = 28; int y_to = 0 + y; - + Rect from = {0, y_from, 8, y_from + 1}; BG_POINT to = {0, y_to}; - + main_bg_se_copy_rect(from, to); } - + reset_top_left_panel_bottom_row(); } - - for (int y = 0; y < timer; y++) // Shift the blind panel down onto screen + + // Shift the blind panel down onto screen + for (int y = 0; y < timer; y++) { int y_from = 26 + y - timer; int y_to = 0 + y; - + Rect from = {0, y_from, 8, y_from}; BG_POINT to = {0, y_to}; - + main_bg_se_copy_rect(from, to); } } @@ -3676,6 +4241,68 @@ static void game_blind_select_on_exit() background = UNDEFINED; } +static inline void game_start(void) +{ + set_seed(rng_seed); + // set_seed(9); // 9 is a full house + + affine_background_change_background(AFFINE_BG_GAME); + + // Normally I would just cache these and hide/unhide but I didn't feel like dealing with + // defining a layer for it + card_destroy(&main_menu_ace->card); + card_object_destroy(&main_menu_ace); + + hands = max_hands; + discards = max_discards; + + // Fill the deck with all the cards. Later on this can be replaced with a more dynamic system + // that allows for different decks and card types. + for (int suit = 0; suit < NUM_SUITS; suit++) + { + for (int rank = 0; rank < NUM_RANKS; rank++) + { + Card* card = card_new(suit, rank); + deck_push(card); + } + } + + change_background(BG_BLIND_SELECT); + + // Deck size/max size + tte_printf( + "#{P:%d,%d; cx:0x%X000}%d/%d", + DECK_SIZE_RECT.left, + DECK_SIZE_RECT.top, + TTE_WHITE_PB, + deck_get_size(), + deck_get_max_size() + ); + + display_round(round); // Set the round display + display_score(score); // Set the score display + + display_chips(); // Set the chips display + display_mult(); // Set the multiplier display + + display_hands(hands); // Hand + display_discards(discards); // Discard + + display_money(); // Set the money display + + tte_printf( + "#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", + ANTE_TEXT_RECT.left, + ANTE_TEXT_RECT.top, + TTE_YELLOW_PB, + ante, + TTE_WHITE_PB, + MAX_ANTE + ); // Ante + + game_change_state(GAME_STATE_BLIND_SELECT); +} + static void game_main_menu_on_update() { change_background(BG_MAIN_MENU); @@ -3686,7 +4313,8 @@ static void game_main_menu_on_update() // Seed randomization rng_seed++; - if (key_curr_state() != key_prev_state()) // If the keys have changed, make it more pseudo-random + // If the keys have changed, make it more pseudo-random + if (key_curr_state() != key_prev_state()) { rng_seed *= 2; } @@ -3705,9 +4333,9 @@ static void game_main_menu_on_update() selection_x++; } } - + if (selection_x == 0) // Play button - { + { // Select button PID is 5 and the outline is 3 memset16(&pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_PID], HIGHLIGHT_COLOR, 1); @@ -3720,102 +4348,15 @@ static void game_main_menu_on_update() else { // Select button PID is 5 and the outline is 3 - memcpy16(&pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_PID], &pal_bg_mem[MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID], 1); + memcpy16( + &pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_PID], + &pal_bg_mem[MAIN_MENU_PLAY_BUTTON_MAIN_COLOR_PID], + 1 + ); } } -#define EXPIRE_ANIMATION_FRAME_COUNT 3 -static void expired_jokers_update_loop() -{ - if(list_is_empty(&_expired_jokers_list)) { - return; - } - - ListItr itr = list_itr_create(&_expired_jokers_list); - JokerObject* joker_object; - - while((joker_object = list_itr_next(&itr))) - { - joker_object_update(joker_object); - - // let just enough frames pass that we see it rotating and shrinking - if (timer % FRAMES(EXPIRE_ANIMATION_FRAME_COUNT) == 0) - { - // get joker idx - int expired_joker_idx = 0; - ListItr joker_itr = list_itr_create(&_owned_jokers_list); - JokerObject* expired_joker; - while ((expired_joker = list_itr_next(&joker_itr)) && expired_joker != joker_object) - { - expired_joker_idx++; - } - - // Removing expired Jokers here, instead of immediately like ones we - // sell or discard allow us to have a small shrink animation without - // the other owned Jokers rearranging themselves to fill the newly - // freed space, therefore obscuring the animation - remove_owned_joker(expired_joker_idx); - list_itr_remove_current_node(&itr); - joker_object_destroy(&joker_object); - } - } -} - -static void discarded_jokers_update_loop() -{ - if(list_is_empty(&_discarded_jokers_list)) { - return; - } - - ListItr itr = list_itr_create(&_discarded_jokers_list); - JokerObject* joker_object; - - while((joker_object = list_itr_next(&itr))) - { - joker_object_update(joker_object); - if (joker_object->sprite_object->x == joker_object->sprite_object->tx - && joker_object->sprite_object->y == joker_object->sprite_object->ty) - { - list_itr_remove_current_node(&itr); - joker_object_destroy(&joker_object); - } - - } -} - -static void held_jokers_update_loop() -{ - const int spacing_lut[MAX_JOKERS_HELD_SIZE][MAX_JOKERS_HELD_SIZE] = - { - {0, 0, 0, 0, 0}, - {13, -13, 0, 0, 0}, - {26, 0, -26, 0, 0}, - {39, 13, -13, -39, 0}, - {40, 20, 0, -20, -40} - }; - - FIXED hand_x = int2fx(HELD_JOKERS_POS.x); - - ListItr itr = list_itr_create(&_owned_jokers_list); - JokerObject* joker; - int jokers_top = list_get_len(&_owned_jokers_list) - 1; - int i = 0; - while((joker = list_itr_next(&itr))) - { - joker->sprite_object->tx = hand_x - int2fx(spacing_lut[jokers_top][i++]); - - joker_object_update(joker); - } -} - -static void jokers_update_loop() -{ - held_jokers_update_loop(); - discarded_jokers_update_loop(); - expired_jokers_update_loop(); -} - -static void game_over_anim_frame() +static void game_over_anim_frame(void) { main_bg_se_move_rect_1_tile_vert(GAME_OVER_ANIM_RECT, SCREEN_UP); } @@ -3828,10 +4369,16 @@ static void game_lose_on_update() } else if (timer == GAME_OVER_ANIM_FRAMES) { - tte_printf("#{P:%d,%d; cx:0x%X000}GAME OVER", GAME_LOSE_MSG_TEXT_RECT.left, GAME_LOSE_MSG_TEXT_RECT.top, TTE_RED_PB); + tte_printf( + "#{P:%d,%d; cx:0x%X000}GAME OVER", + GAME_LOSE_MSG_TEXT_RECT.left, + GAME_LOSE_MSG_TEXT_RECT.top, + TTE_RED_PB + ); } - if (key_hit(KEY_A)) game_change_state(GAME_STATE_BLIND_SELECT); + if (key_hit(KEY_A)) + game_change_state(GAME_STATE_BLIND_SELECT); } // This function isn't set in stone. This is just a placeholder @@ -3842,7 +4389,7 @@ static void game_over_on_exit() ListItr itr = list_itr_create(&_owned_jokers_list); JokerObject* joker_object; - while((joker_object = list_itr_next(&itr))) + while ((joker_object = list_itr_next(&itr))) { joker_object_destroy(&joker_object); } @@ -3872,6 +4419,7 @@ static void game_over_on_exit() display_hands(hands); display_discards(discards); display_money(); + // Ante tte_printf( "#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", ANTE_TEXT_RECT.left, @@ -3880,7 +4428,7 @@ static void game_over_on_exit() ante, TTE_WHITE_PB, MAX_ANTE - ); // Ante + ); affine_background_load_palette(affine_background_gfxPal); } @@ -3893,17 +4441,14 @@ static void game_win_on_update() } else if (timer == GAME_OVER_ANIM_FRAMES) { - tte_printf("#{P:%d,%d; cx:0x%X000}YOU WIN", GAME_WIN_MSG_TEXT_RECT.left, GAME_WIN_MSG_TEXT_RECT.top, TTE_BLUE_PB); + tte_printf( + "#{P:%d,%d; cx:0x%X000}YOU WIN", + GAME_WIN_MSG_TEXT_RECT.left, + GAME_WIN_MSG_TEXT_RECT.top, + TTE_BLUE_PB + ); } - if (key_hit(KEY_A)) game_change_state(GAME_STATE_BLIND_SELECT); -} - -void game_update() -{ - timer++; - - jokers_update_loop(); - - state_info[game_state].on_update(); + if (key_hit(KEY_A)) + game_change_state(GAME_STATE_BLIND_SELECT); } diff --git a/source/hand_analysis.c b/source/hand_analysis.c index 1ebb3d9..998a736 100644 --- a/source/hand_analysis.c +++ b/source/hand_analysis.c @@ -221,7 +221,8 @@ bool hand_contains_flush(u8* suits) * @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. + * @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) @@ -265,9 +266,15 @@ int find_flush_in_played_cards(CardObject** played, int top, int min_len, bool* 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) +// 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; diff --git a/source/splash_screen.c b/source/splash_screen.c index 2da30d5..8231b12 100644 --- a/source/splash_screen.c +++ b/source/splash_screen.c @@ -16,9 +16,11 @@ void splash_screen_on_init() tte_printf("#{P:72,8; cx:0xF000}DISCLAIMER"); tte_printf( - "#{P:8,24; cx:0xF000}This project is NOT endorsed \n by or affiliated with \n Playstack or LocalThunk.\n\n If " - "you have paid for this, \n you have been scammed \n and should request a refund \n IMMEDIATELY. \n\n The only " - "official place \n to obtain this is from: \n\n 'github.com/\n cellos51/balatro-gba'"); + "#{P:8,24; cx:0xF000}This project is NOT endorsed \n by or affiliated with \n Playstack or " + "LocalThunk.\n\n If you have paid for this, \n you have been scammed \n and should request " + "a refund \n IMMEDIATELY. \n\n The only official place \n to obtain this is from: \n\n " + "'github.com/\n cellos51/balatro-gba'" + ); tte_printf("#{P:8,144; cx:0xF000}(Press any key to skip)"); } @@ -29,10 +31,12 @@ void splash_screen_on_update() if (timer < SPLASH_DURATION_FRAMES) { tte_erase_rect_wrapper(COUNTDOWN_TIMER_RECT); - tte_printf("#{P:%d,%d; cx:0xF000}%d", - COUNTDOWN_TIMER_RECT.left, - COUNTDOWN_TIMER_RECT.top, - 1 + (SPLASH_DURATION_FRAMES - timer) / SPLASH_FPS); + tte_printf( + "#{P:%d,%d; cx:0xF000}%d", + COUNTDOWN_TIMER_RECT.left, + COUNTDOWN_TIMER_RECT.top, + 1 + (SPLASH_DURATION_FRAMES - timer) / SPLASH_FPS + ); if (!key_hit(KEY_ANY)) { diff --git a/source/sprite.c b/source/sprite.c index 22fbe92..1ce64d8 100644 --- a/source/sprite.c +++ b/source/sprite.c @@ -181,8 +181,8 @@ void sprite_object_update(SpriteObject* sprite_object) // set velocity to 0 if it's close enough to the target const FIXED epsilon = float2fx(0.01f); - if (sprite_object->vx < epsilon && sprite_object->vx > -epsilon && sprite_object->vy < epsilon && - sprite_object->vy > -epsilon) + if (sprite_object->vx < epsilon && sprite_object->vx > -epsilon && + sprite_object->vy < epsilon && sprite_object->vy > -epsilon) { sprite_object->vx = 0; sprite_object->vy = 0; @@ -215,7 +215,8 @@ void sprite_object_update(SpriteObject* sprite_object) if (sprite_object->vrotation < epsilon && sprite_object->vrotation > -epsilon) { sprite_object->vrotation = 0; - sprite_object->rotation = sprite_object->trotation; // Set the rotation to the target rotation + // Set the rotation to the target rotation + sprite_object->rotation = sprite_object->trotation; } else { @@ -223,10 +224,13 @@ void sprite_object_update(SpriteObject* sprite_object) sprite_object->rotation += sprite_object->vrotation; } - obj_aff_rotscale(sprite_object->sprite->aff, - sprite_object->scale, - sprite_object->scale, - -sprite_object->vx + sprite_object->rotation); // Apply rotation and scale to the sprite + // Apply rotation and scale to the sprite + obj_aff_rotscale( + sprite_object->sprite->aff, + sprite_object->scale, + sprite_object->scale, + -sprite_object->vx + sprite_object->rotation + ); sprite_position(sprite_object->sprite, fx2int(sprite_object->x), fx2int(sprite_object->y)); }