diff --git a/include/button.h b/include/button.h new file mode 100644 index 0000000..b8ed615 --- /dev/null +++ b/include/button.h @@ -0,0 +1,76 @@ +/** + * @file button.h + * + * @brief A button structure containing the common button functionalities + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include + +#define BTN_HIGHLIGHT_COLOR 0xFFFF + +#define BUTTON_SFX_VOLUME 154 // 60% of MM_FULL_VOLUME + +/** + * @brief The function to be called when the button is pressed + */ +typedef void (*ButtonOnPressedFunc)(void); + +/** + * @brief Returns true if the button should be activated when pressed. + */ +typedef bool (*ButtonCanBePressedFunc)(void); + +/** + * @brief A button representation + */ +typedef struct +{ + // Using u8 for the palette indices to make sure they don't overflow pal_bg_mem + + /** + * @brief Palette index of the button border whose color is updated depending on highlight. + */ + u8 border_pal_idx; + + /** + * @brief Palette index of the color of the button itself, used for the border when not + * highlighted. + */ + u8 button_pal_idx; + + /** + * @brief Called when the button is pressed if @ref can_be_pressed, don't call this directly, + * call @ref button_press() instead. + * Should not be set to NULL. + */ + ButtonOnPressedFunc on_pressed; + + /** + * @brief Returns true if the button should be activated when pressed. + * Can be NULL if button can always be pressed. + */ + ButtonCanBePressedFunc can_be_pressed; +} Button; + +/** + * @brief Set the button highlight on or off. + * @param button The button to modify. No-op on NULL. + * @param highlight true to highlight, false to unhighlight. + */ +void button_set_highlight(Button* button, bool highlight); + +/** + * @brief Execute a button press, by calling the button's @ref on_pressed function. + * This checks the button's @ref can_be_pressed function and will do nothing if + * returns false. + * + * Button presses should go through this function rather than directly call @ref on_pressed. + * + * @param button The button being pressed. No-op on NULL. + */ +void button_press(Button* button); + +#endif // BUTTON_H \ No newline at end of file diff --git a/include/selection_grid.h b/include/selection_grid.h index 1bfbd00..274d043 100644 --- a/include/selection_grid.h +++ b/include/selection_grid.h @@ -37,9 +37,10 @@ typedef struct SelectionGrid SelectionGrid; * Contains the previous cursor position * @param new_selection Pointer to the Selection state after the change occurred. * Contains the new cursor position + * @return false if the selection change needs to be aborted, true if can proceed. * */ -typedef void (*RowOnSelectionChangedFunc)( +typedef bool (*RowOnSelectionChangedFunc)( SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, @@ -71,6 +72,18 @@ typedef int (*RowGetSizeFunc)(); */ typedef void (*RowOnKeyTransitFunc)(SelectionGrid* selection_grid, Selection* selection); +/** + * @brief A set of attributes to the selection grid row affecting selection grid behavior. + */ +typedef struct +{ + + /** + * @brief Whether to wrap selection when it passes the end of the row. + */ + bool wrap; +} SelGridRowAttributes; + /** * @brief A single row in the selection grid, defined by its callback functions. * @@ -82,6 +95,7 @@ struct SelectionGridRow RowGetSizeFunc get_size; RowOnSelectionChangedFunc on_selection_changed; RowOnKeyTransitFunc on_key_transit; + SelGridRowAttributes attributes; }; /** diff --git a/source/button.c b/source/button.c new file mode 100644 index 0000000..f94cfee --- /dev/null +++ b/source/button.c @@ -0,0 +1,29 @@ +#include "button.h" + +#include "audio_utils.h" +#include "soundbank.h" + +#include + +void button_set_highlight(Button* button, bool highlight) +{ + if (button == NULL) + return; + + u16 set_color = highlight ? BTN_HIGHLIGHT_COLOR : pal_bg_mem[button->button_pal_idx]; + + memset16(&pal_bg_mem[button->border_pal_idx], set_color, 1); +} + +void button_press(Button* button) +{ + if (button == NULL || button->on_pressed == NULL || + (button->can_be_pressed != NULL && !button->can_be_pressed())) + { + return; + } + + play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME); + + button->on_pressed(); +} diff --git a/source/game.c b/source/game.c index acd15b5..2b1f913 100644 --- a/source/game.c +++ b/source/game.c @@ -9,6 +9,7 @@ #include "background_shop_gfx.h" #include "bitset.h" #include "blind.h" +#include "button.h" #include "card.h" #include "graphic_utils.h" #include "hand_analysis.h" @@ -45,7 +46,6 @@ #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 @@ -55,8 +55,6 @@ #define PITCH_STEP_DRAW_SFX 24 #define PITCH_STEP_UNDISCARD_SFX 2 * PITCH_STEP_DRAW_SFX -#define BUTTON_SFX_VOLUME 154 // 60% of MM_FULL_VOLUME - #define STARTING_ROUND 0 #define STARTING_ANTE 1 #define STARTING_MONEY 4 @@ -129,12 +127,9 @@ #define NEXT_ROUND_BTN_SEL_X 0 -#define GAME_PLAYING_HAND_SEL_Y 0 -#define GAME_PLAYING_BUTTONS_SEL_Y 1 -#define GAME_PLAYING_PLAY_BTN_SEL_X 0 -#define GAME_PLAYING_DISCARD_BTN_SEL_X 1 -#define GAME_PLAYING_NUM_SEL_ROWS 2 -#define GAME_PLAYING_NUM_BOTTOM_BTNS 2 +#define GAME_PLAYING_HAND_SEL_Y 1 +#define GAME_PLAYING_BUTTONS_SEL_Y 2 +#define GAME_PLAYING_NUM_BOTTOM_BTNS 2 #define REROLL_BTN_FRAME_PAL_IDX 7 #define REROLL_BTN_PAL_IDX 3 @@ -242,8 +237,6 @@ 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_can_discard(void); static int deck_get_size(void); static int deck_get_max_size(void); static void increment_blind(enum BlindState increment_reason); @@ -255,15 +248,41 @@ static bool check_and_score_joker_for_event( ); static int calculate_interest_reward(void); static void game_over_anim_frame(void); + +static void game_playing_discard_on_pressed(void); +static void game_playing_play_hand_on_pressed(void); +static int game_playing_button_row_get_size(void); +static bool game_playing_button_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +); +static void game_playing_button_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection); + +static void game_playing_hand_row_on_key_transit( + SelectionGrid* selection_grid, + Selection* selection +); + +static bool game_playing_hand_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +); + +static int game_playing_hand_row_get_size(void); + static void shop_reroll_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection); -static void shop_reroll_row_on_selection_changed( +static bool 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( +static bool shop_top_row_on_selection_changed( SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, @@ -272,7 +291,7 @@ static void shop_top_row_on_selection_changed( static void shop_top_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection); static int shop_top_row_get_size(void); static void jokers_sel_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection); -static void jokers_sel_row_on_selection_changed( +static bool jokers_sel_row_on_selection_changed( SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, @@ -280,11 +299,20 @@ static void jokers_sel_row_on_selection_changed( ); 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); + +static int hand_sel_idx_to_card_idx(int selection_index); +static void hand_select_card(int index); +static void hand_change_sort(void); +static void hand_deselect_all_cards(void); +static bool can_play_hand(void); +static bool can_discard_hand(void); + // Consts // clang-format off @@ -404,10 +432,50 @@ static StateInfo state_info[] = { #undef DEF_STATE_INFO }; +// clang-format off +SelectionGridRow game_playing_selection_rows[] = { + { + 0, + jokers_sel_row_get_size, + jokers_sel_row_on_selection_changed, + jokers_sel_row_on_key_transit, + {.wrap = false} + }, + { + 1, + game_playing_hand_row_get_size, + game_playing_hand_row_on_selection_changed, + game_playing_hand_row_on_key_transit, + {.wrap = true} + }, + { + 2, + game_playing_button_row_get_size, + game_playing_button_row_on_selection_changed, + game_playing_button_row_on_key_hit, + {.wrap = false} + } +}; +// clang-format on + +static const Selection GAME_PLAYING_INIT_SEL = {0, 1}; + +SelectionGrid game_playing_selection_grid = { + game_playing_selection_rows, + NUM_ELEM_IN_ARR(game_playing_selection_rows), + GAME_PLAYING_INIT_SEL +}; + +// Array of buttons by horizontal selection index (x) +Button game_playing_buttons[] = { + {PLAY_HAND_BTN_BORDER_PID, PLAY_HAND_BTN_PID, game_playing_play_hand_on_pressed, can_play_hand }, + {DISCARD_BTN_BORDER_PID, DISCARD_BTN_PID, game_playing_discard_on_pressed, can_discard_hand}, +}; + SelectionGridRow shop_selection_rows[] = { - {0, jokers_sel_row_get_size, jokers_sel_row_on_selection_changed, jokers_sel_row_on_key_transit }, - {1, shop_top_row_get_size, shop_top_row_on_selection_changed, shop_top_row_on_key_transit }, - {2, shop_reroll_row_get_size, shop_reroll_row_on_selection_changed, shop_reroll_row_on_key_transit} + {0, jokers_sel_row_get_size, jokers_sel_row_on_selection_changed, jokers_sel_row_on_key_transit, {.wrap = false}}, + {1, shop_top_row_get_size, shop_top_row_on_selection_changed, shop_top_row_on_key_transit, {.wrap = false}}, + {2, shop_reroll_row_get_size, shop_reroll_row_on_selection_changed, shop_reroll_row_on_key_transit, {.wrap = false}} }; static const Selection SHOP_INIT_SEL = {-1, 1}; @@ -1051,6 +1119,17 @@ void display_mult(void) check_flaming_score(); } +static inline 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 + ); +} + // idx_a and idx_b are assumed to be valid indexes within the hand array // no checks will be performed here for performance's sake static inline void swap_cards_in_hand(int idx_a, int idx_b) @@ -1256,9 +1335,10 @@ static void change_background(enum BackgroundId id) 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); - memcpy16(&pal_bg_mem[DISCARD_BTN_BORDER_PID], &pal_bg_mem[DISCARD_BTN_PID], 1); + for (int i = 0; i < NUM_ELEM_IN_ARR(game_playing_buttons); i++) + { + button_set_highlight(&game_playing_buttons[i], false); + } } } else if (id == BG_CARD_PLAYING) @@ -1725,37 +1805,9 @@ static void set_hand(void) display_mult(); } -static void hand_set_focus(int index) +static bool can_discard_hand(void) { - 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) - { - selection_x = hand_top; - } - else if (index > hand_top) - { - selection_x = 0; - } - else - { - selection_x = index; - } - - play_sfx( - SFX_CARD_FOCUS, - MM_BASE_PITCH_RATE + rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE, - SFX_DEFAULT_VOLUME - ); -} - -static bool hand_can_discard(void) -{ - if (hand_state != HAND_SELECT || hand_selections == 0) - return false; - return true; + return (discards > 0 && hand_state == HAND_SELECT && hand_selections > 0); } static int deck_get_size(void) @@ -1856,6 +1908,12 @@ static void game_round_on_init() ); // Blind reward deck_shuffle(); // Shuffle the deck at the start of the round + + /* Note that since cards_in_hand_update_loop() handles card highlight there's no need + * to call a selection changed callback to highlight the initial card, this wouldn't work + * otherwise or for the buttons. + */ + game_playing_selection_grid.selection = GAME_PLAYING_INIT_SEL; } static void game_main_menu_on_init() @@ -1902,22 +1960,207 @@ static inline void set_seed(int seed) srand(rng_seed); } -static inline void hand_toggle_card_selection(void) +// Playing state functions +static void game_playing_discard_on_pressed(void) { - if (hand_state != HAND_SELECT || hand[selection_x] == NULL) + if (!can_discard_hand()) return; - if (card_object_is_selected(hand[selection_x])) + hand_state = HAND_DISCARD; + 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 + ); + + // Move back to hand selection + selection_grid_move_selection_vert(&game_playing_selection_grid, -1); +} + +static void game_playing_play_hand_on_pressed(void) +{ + if (!can_play_hand()) + return; + + hand_state = HAND_PLAY; + display_hands(--hands); + + // Move back to hand selection + selection_grid_move_selection_vert(&game_playing_selection_grid, -1); +} + +static int game_playing_hand_row_get_size(void) +{ + return hand_get_size(); +} + +// card moving logic + +// true if and only if we are currently moving a card around +static bool moving_card = false; + +// This will prevent us from moving cards around if we selected one +// by moving too fast after pressing the A button +static bool card_moved_too_fast = false; +static bool card_selected_instead_of_moved = false; + +// After pressing A, if we press Left/Right too fast, we should select the card +// and change focus to the next one, instead of swapping them +// This should fix inputs sometimes not registering when quickly selecting cards +static const int card_swap_time_threshold = 6; +static uint selection_hit_timer = TM_ZERO; + +static bool game_playing_hand_row_on_selection_changed( + SelectionGrid* selection_grid, + int row_idx, + const Selection* prev_selection, + const Selection* new_selection +) +{ + int prev_card_idx = UNDEFINED; + int next_card_idx = UNDEFINED; + + // Do not use FRAMES(x) here as we are counting real frames ignoring game speed + card_moved_too_fast = (timer - selection_hit_timer) < card_swap_time_threshold; + + if (prev_selection->y == GAME_PLAYING_HAND_SEL_Y) { - card_object_set_selected(hand[selection_x], false); - hand_selections--; - play_sfx(SFX_CARD_DESELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME); + prev_card_idx = hand_sel_idx_to_card_idx(prev_selection->x); } - else if (hand_selections < MAX_SELECTION_SIZE) + + if (new_selection->y == GAME_PLAYING_HAND_SEL_Y) { - card_object_set_selected(hand[selection_x], true); - hand_selections++; - play_sfx(SFX_CARD_SELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME); + next_card_idx = hand_sel_idx_to_card_idx(new_selection->x); + } + + bool on_the_same_row = new_selection->y == prev_selection->y; // == GAME_PLAYING_HAND_SEL_Y + + if (on_the_same_row && key_is_down(SELECT_CARD) && !card_moved_too_fast && + !card_selected_instead_of_moved) + { + bool moved_by_one_tile = abs(new_selection->x - prev_selection->x) == 1; + + // Avoid swapping when selection wraps + if (!moved_by_one_tile) + { + // Abort the selection if swapping so it doesn't wrap + return false; + } + else + { + swap_cards_in_hand(prev_card_idx, next_card_idx); + moving_card = true; + reorder_card_sprites_layers(); + + /* Not calling sprite_object_set_focus() because focus is handled by + * cards_in_hand_update_loop() based on the selection grid value... + */ + play_sfx( + SFX_CARD_FOCUS, + MM_BASE_PITCH_RATE + rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE, + SFX_DEFAULT_VOLUME + ); + } + } + else + { + // select current card if we tried moving it too fast + if (key_released(SELECT_CARD) || (card_moved_too_fast && !moving_card)) + { + hand_select_card(prev_card_idx); + card_selected_instead_of_moved = true; + } + if (next_card_idx != UNDEFINED) + { + /* Not calling sprite_object_set_focus() because focus is handled by + * cards_in_hand_update_loop() based on the selection grid value... + */ + play_sfx( + SFX_CARD_FOCUS, + MM_BASE_PITCH_RATE + rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE, + SFX_DEFAULT_VOLUME + ); + } + } + + return true; +} + +static void game_playing_hand_row_on_key_transit( + SelectionGrid* selection_grid, + Selection* selection +) +{ + if (key_hit(SELECT_CARD)) + { + selection_hit_timer = timer; + } + else if (key_released(SELECT_CARD)) + { + if (!moving_card && !card_selected_instead_of_moved) + { + hand_select_card(hand_sel_idx_to_card_idx(selection->x)); + } + moving_card = false; + card_moved_too_fast = false; + card_selected_instead_of_moved = false; + selection_hit_timer = TM_ZERO; + } + else if (key_hit(DESELECT_CARDS)) + { + hand_deselect_all_cards(); + set_hand(); + } + else if (key_hit(SORT_HAND)) + { + hand_change_sort(); + } +} + +static int game_playing_button_row_get_size(void) +{ + return NUM_ELEM_IN_ARR(game_playing_buttons); +} + +static inline void game_playing_button_set_highlight(int btn_idx, bool highlight) +{ + button_set_highlight(&game_playing_buttons[btn_idx], highlight); +} + +static bool game_playing_button_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 + // but not the previous one... + // As of writing (PR #348), this check is not strictly needed for this row but it is + // left in, in case that ever changes. It can be reconsidered and removed. + if (prev_selection->y == row_idx && prev_selection->x >= 0 && + prev_selection->x < game_playing_button_row_get_size()) + { + game_playing_button_set_highlight(prev_selection->x, false); + } + + if (new_selection->y == row_idx) + { + game_playing_button_set_highlight(new_selection->x, true); + } + + return true; +} + +static void game_playing_button_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection) +{ + if (key_hit(SELECT_CARD)) + { + button_press(&game_playing_buttons[selection->x]); } } @@ -1940,220 +2183,56 @@ static void hand_deselect_all_cards(void) } } -static inline void hand_change_sort(void) +static void hand_change_sort(void) { sort_by_suit = !sort_by_suit; sort_cards(); } -static inline bool hand_can_play(void) +static bool can_play_hand(void) { if (hand_state != HAND_SELECT || hand_selections == 0) return false; return true; } -static inline void select_current_card(void) +/** + * @brief Converts a selection index from the selection grid into a card index within the hand array + * @param selection_index The selection index from the selection grid. + * @return The index within the hand stack array. + * Note that the result is not valid if hand size is 0. + */ +static inline int hand_sel_idx_to_card_idx(int selection_index) { - hand_toggle_card_selection(); - set_hand(); + // This is because the hand is drawn from right to left. + // There is no particular reason for why that was done, it's just how it was done. + // Maybe one day it can be reverted and made consistent so this conversion is not needed. + return hand_get_size() - selection_index - 1; } -// card moving logic - -// true if and only if we are currently moving a card around -static bool moving_card = false; - -// This will prevent us from moving cards around if we selected one -// by moving too fast after pressing the A button -static bool card_moved_too_fast = false; -static bool card_selected_instead_of_moved = false; - -// After pressing A, if we press Left/Right too fast, we should select the card -// and change focus to the next one, instead of swapping them -// This should fix inputs sometimes not registering when quickly selecting cards -static const int card_swap_time_threshold = 6; -static uint selection_hit_timer = TM_ZERO; - -static inline void game_playing_apply_card_movement_input(enum ScreenHorzDir move_dir) +static void hand_select_card(int index) { - // The reason why this adds +1 (-SCREEN_LEFT) is because the hand is drawn from right to left. - // There is no particular reason for this, it's just how I did it. - int next_card = selection_x - move_dir; + if (index < 0 || index >= hand_get_size() || hand_state != HAND_SELECT || hand[index] == NULL) + return; - // Do not use FRAMES(x) here as we are counting real frames ignoring game speed - card_moved_too_fast = (timer - selection_hit_timer) < card_swap_time_threshold; - - // swap cards around if A is held down when pressing D-pad keys - if (key_is_down(SELECT_CARD) && !card_moved_too_fast && !card_selected_instead_of_moved) + if (card_object_is_selected(hand[index])) { - bool selection_not_at_border = - (move_dir == SCREEN_LEFT) ? selection_x < hand_top : selection_x > 0; - - if (selection_not_at_border) - { - swap_cards_in_hand(selection_x, next_card); - moving_card = true; - reorder_card_sprites_layers(); - hand_set_focus(next_card); - } + card_object_set_selected(hand[index], false); + hand_selections--; + play_sfx(SFX_CARD_DESELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME); } - else + else if (hand_selections < MAX_SELECTION_SIZE) { - // select current card if we tried moving it too fast - if (key_released(SELECT_CARD) || (card_moved_too_fast && !moving_card)) - { - select_current_card(); - card_selected_instead_of_moved = true; - } - hand_set_focus(next_card); + card_object_set_selected(hand[index], true); + hand_selections++; + play_sfx(SFX_CARD_SELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME); } -} - -static inline void game_playing_highlight_play_btn(void) -{ - memset16(&pal_bg_mem[PLAY_HAND_BTN_BORDER_PID], HIGHLIGHT_COLOR, 1); - memcpy16(&pal_bg_mem[DISCARD_BTN_BORDER_PID], &pal_bg_mem[DISCARD_BTN_PID], 1); -} - -static inline void game_playing_highlight_discard_btn(void) -{ - memcpy16(&pal_bg_mem[PLAY_HAND_BTN_BORDER_PID], &pal_bg_mem[PLAY_HAND_BTN_PID], 1); - memset16(&pal_bg_mem[DISCARD_BTN_BORDER_PID], HIGHLIGHT_COLOR, 1); -} - -static inline void game_playing_unhighlight_buttons(void) -{ - memcpy16(&pal_bg_mem[PLAY_HAND_BTN_BORDER_PID], &pal_bg_mem[PLAY_HAND_BTN_PID], 1); - memcpy16(&pal_bg_mem[DISCARD_BTN_BORDER_PID], &pal_bg_mem[DISCARD_BTN_PID], 1); -} - -static inline void game_playing_execute_hand_discard(void) -{ - play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME); - - hand_state = HAND_DISCARD; - selection_x = 0; - 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 - ); -} - -static inline void game_playing_execute_hand_play(void) -{ - play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME); - - hand_state = HAND_PLAY; - selection_x = 0; - selection_y = 0; - display_hands(--hands); } static inline void game_playing_process_hand_select_input(void) { - // true = play button highlighted, false = discard button highlighted - static bool discard_button_highlighted = false; - - if (key_hit(KEY_LEFT)) - { - if (selection_y == GAME_PLAYING_HAND_SEL_Y) - { - game_playing_apply_card_movement_input(SCREEN_LEFT); - } - else if (selection_y == GAME_PLAYING_BUTTONS_SEL_Y) - { - discard_button_highlighted = false; - } - } - else if (key_hit(KEY_RIGHT)) - { - if (selection_y == GAME_PLAYING_HAND_SEL_Y) - { - game_playing_apply_card_movement_input(SCREEN_RIGHT); - } - else if (selection_y == GAME_PLAYING_BUTTONS_SEL_Y) - { - discard_button_highlighted = true; - } - } - else if (key_hit(KEY_UP) && selection_y == GAME_PLAYING_BUTTONS_SEL_Y) - { - selection_y = GAME_PLAYING_HAND_SEL_Y; - } - else if (key_hit(KEY_DOWN) && selection_y == GAME_PLAYING_HAND_SEL_Y) - { - selection_y = GAME_PLAYING_BUTTONS_SEL_Y; - - if (selection_x > hand_top / 2) - { - discard_button_highlighted = false; // Play button - } - else - { - discard_button_highlighted = true; // Discard button - } - } - else if (selection_y == GAME_PLAYING_BUTTONS_SEL_Y) - { - if (discard_button_highlighted == false) // Play button logic - { - game_playing_highlight_play_btn(); - if (key_hit(SELECT_CARD) && hands > 0 && hand_can_play()) - { - game_playing_execute_hand_play(); - } - } - else - { - game_playing_highlight_discard_btn(); - if (key_hit(SELECT_CARD) && discards > 0 && hand_can_discard()) - { - game_playing_execute_hand_discard(); - } - } - } - else if (selection_y == GAME_PLAYING_HAND_SEL_Y) - { - game_playing_unhighlight_buttons(); - - // Register timer when we hit A to pick a card - // If we try to move the picked card before card_swap_time_threshold frames, - // We will select it instead of moving it - if (key_hit(SELECT_CARD)) - { - selection_hit_timer = timer; - } - // select card if we were not moving it around - else if (key_released(SELECT_CARD)) - { - if (!moving_card && !card_selected_instead_of_moved) - { - select_current_card(); - } - moving_card = false; - card_moved_too_fast = false; - card_selected_instead_of_moved = false; - selection_hit_timer = TM_ZERO; - } - - if (key_hit(DESELECT_CARDS)) - { - hand_deselect_all_cards(); - set_hand(); - } - } - - if (key_hit(SORT_HAND)) - { - hand_change_sort(); - } + selection_grid_process_input(&game_playing_selection_grid); } static inline void card_draw(void) @@ -2181,17 +2260,6 @@ static inline void card_draw(void) ); } -static inline 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 - ); -} - static inline void game_playing_handle_round_over(void) { enum GameState next_state = GAME_STATE_ROUND_END; @@ -3087,8 +3155,52 @@ static inline void game_playing_discarded_cards_loop(void) } } +static inline void select_cards_in_played_hand() +{ + 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; + 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; + } +} + static inline void cards_in_hand_update_loop(void) { + int selected_card_idx = hand_sel_idx_to_card_idx(game_playing_selection_grid.selection.x); + // 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--) @@ -3105,7 +3217,9 @@ static inline void cards_in_hand_update_loop(void) 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); + bool is_focused = + (i == selected_card_idx && + game_playing_selection_grid.selection.y == GAME_PLAYING_HAND_SEL_Y); if (is_focused && !card_object_is_selected(hand[i])) { @@ -3120,7 +3234,7 @@ static inline void cards_in_hand_update_loop(void) hand_y -= int2fx(CARD_FOCUSED_SEL_Y); } - if (i != selection_x && hand[i]->sprite_object->y > hand_y) + if (i != selected_card_idx && hand[i]->sprite_object->y > hand_y) { hand[i]->sprite_object->y = hand_y; hand[i]->sprite_object->vy = 0; @@ -3180,45 +3294,7 @@ static inline void cards_in_hand_update_loop(void) 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; - } + select_cards_in_played_hand(); } break; @@ -3892,7 +3968,7 @@ static int jokers_sel_row_get_size(void) return list_get_len(&_owned_jokers_list); } -static void jokers_sel_row_on_selection_changed( +static bool jokers_sel_row_on_selection_changed( SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, @@ -3946,6 +4022,8 @@ static void jokers_sel_row_on_selection_changed( (unsigned int)new_selection->x ); } + + return true; } static inline void joker_start_discard_animation(JokerObject* joker_object) @@ -4069,7 +4147,7 @@ static void shop_top_row_on_key_transit(SelectionGrid* selection_grid, Selection } } -static void shop_top_row_on_selection_changed( +static bool shop_top_row_on_selection_changed( SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, @@ -4077,6 +4155,8 @@ static void shop_top_row_on_selection_changed( ) { // The selection grid system only guarantees that the new selection is within bounds + // but not the previous one... + // This allows using INIT_SEL = {-1, 1} and move to set the initial selection in a hacky way... if (prev_selection->y == row_idx && prev_selection->x >= 0 && prev_selection->x < shop_top_row_get_size()) { @@ -4094,7 +4174,6 @@ static void shop_top_row_on_selection_changed( 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 } } @@ -4103,7 +4182,7 @@ static void shop_top_row_on_selection_changed( if (new_selection->x == NEXT_ROUND_BTN_SEL_X) { // Highlight next round button - memset16(&pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], HIGHLIGHT_COLOR, 1); + memset16(&pal_bg_mem[NEXT_ROUND_BTN_SELECTED_BORDER_PID], BTN_HIGHLIGHT_COLOR, 1); } else { @@ -4112,6 +4191,8 @@ static void shop_top_row_on_selection_changed( sprite_object_set_focus(joker_object->sprite_object, true); } } + + return true; } static int shop_reroll_row_get_size() @@ -4119,7 +4200,7 @@ static int shop_reroll_row_get_size() return 1; // Only the reroll button } -static void shop_reroll_row_on_selection_changed( +static bool shop_reroll_row_on_selection_changed( SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, @@ -4133,8 +4214,10 @@ static void shop_reroll_row_on_selection_changed( } else if (row_idx == new_selection->y) { - memset16(&pal_bg_mem[REROLL_BTN_SELECTED_BORDER_PID], HIGHLIGHT_COLOR, 1); + memset16(&pal_bg_mem[REROLL_BTN_SELECTED_BORDER_PID], BTN_HIGHLIGHT_COLOR, 1); } + + return true; } static inline void game_shop_reroll(int* reroll_cost) @@ -4203,6 +4286,7 @@ static void game_shop_process_user_input() { if (timer == TM_SHOP_PRC_INPUT_START) { + // TODO: Move to on_init? // 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; @@ -4343,6 +4427,8 @@ static void game_shop_on_exit() static void game_blind_select_on_init() { change_background(BG_BLIND_SELECT); + selection_x = 0; + selection_y = 0; play_sfx(SFX_POP, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME); } @@ -4533,7 +4619,6 @@ static void game_blind_select_handle_input() if (selection_y == 0) { - // 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], @@ -4543,7 +4628,6 @@ static void game_blind_select_handle_input() } else { - // 15 is the select button color memcpy16( &pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SELECT_BTN_PID], @@ -4725,7 +4809,7 @@ static void game_main_menu_on_update() if (selection_x == MAIN_MENU_PLAY_BTN_IDX) { - memset16(&pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_PID], HIGHLIGHT_COLOR, 1); + memset16(&pal_bg_mem[MAIN_MENU_PLAY_BUTTON_OUTLINE_PID], BTN_HIGHLIGHT_COLOR, 1); if (key_hit(SELECT_CARD)) { diff --git a/source/selection_grid.c b/source/selection_grid.c index d21f63b..7a6a3b9 100644 --- a/source/selection_grid.c +++ b/source/selection_grid.c @@ -23,22 +23,35 @@ static void selection_grid_process_directional_input(SelectionGrid* selection_gr void selection_grid_move_selection_horz(SelectionGrid* selection_grid, int direction_tribool) { - if (selection_grid == NULL) + if (selection_grid == NULL || selection_grid->selection.y < 0 || + selection_grid->selection.y >= selection_grid->num_rows) + { return; + } Selection new_selection = selection_grid->selection; new_selection.x += direction_tribool; - if (selection_grid->selection.y >= 0 && - selection_grid->selection.y < selection_grid->num_rows && new_selection.x >= 0 && - new_selection.x < selection_grid->rows[new_selection.y].get_size()) + int row_size = selection_grid->rows[new_selection.y].get_size(); + bool wrap_enabled = selection_grid->rows[new_selection.y].attributes.wrap; + + if (wrap_enabled) { - selection_grid->rows[new_selection.y].on_selection_changed( + new_selection.x = wrap(new_selection.x, 0, row_size); + } + + if (wrap_enabled || (new_selection.x >= 0 && new_selection.x < row_size)) + { + bool proceed_selection = selection_grid->rows[new_selection.y].on_selection_changed( selection_grid, new_selection.y, &selection_grid->selection, &new_selection ); - selection_grid->selection = new_selection; + + if (proceed_selection) + { + selection_grid->selection = new_selection; + } } } @@ -66,14 +79,29 @@ void selection_grid_move_selection_vert(SelectionGrid* selection_grid, int direc // The operations are equivalent to fixed point if all the numbers were converted new_selection.x = fx2int(selection.x * ((int2fx(new_row_size) / old_row_size))); + bool proceed_selection = true; + if (selection.y >= 0 && selection.y < selection_grid->num_rows) { - selection_grid->rows[selection.y] - .on_selection_changed(selection_grid, selection.y, &selection, &new_selection); + proceed_selection = + selection_grid->rows[selection.y] + .on_selection_changed(selection_grid, selection.y, &selection, &new_selection); + } + + if (proceed_selection) + { + proceed_selection = selection_grid->rows[new_selection.y].on_selection_changed( + selection_grid, + new_selection.y, + &selection, + &new_selection + ); + } + + if (proceed_selection) + { + selection_grid->selection = new_selection; } - selection_grid->rows[new_selection.y] - .on_selection_changed(selection_grid, new_selection.y, &selection, &new_selection); - selection_grid->selection = new_selection; } }