Swap Cards in Hand (#280)

* card sorting

* Added refactor from @MeirGavish

---------

Co-authored-by: MathisMartin31 <mathis.martin31@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Geralt
2025-12-18 15:14:28 +02:00
committed by GitHub
co-authored by MathisMartin31 Copilot
parent 9a2d128e87
commit faa75a4eea
+260 -112
View File
@@ -125,6 +125,13 @@
#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 REROLL_BTN_FRAME_PAL_IDX 7
#define REROLL_BTN_PAL_IDX 3
@@ -230,7 +237,7 @@ 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 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);
@@ -1044,20 +1051,27 @@ void display_mult(void)
check_flaming_score();
}
// 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)
{
CardObject* temp = hand[idx_a];
hand[idx_a] = hand[idx_b];
hand[idx_b] = temp;
}
static inline void sort_hand_by_suit(void)
{
for (int a = 0; a < hand_top; a++)
for (int idx_a = 0; idx_a < hand_top; idx_a++)
{
for (int b = a + 1; b <= hand_top; b++)
for (int idx_b = idx_a + 1; idx_b <= hand_top; idx_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[idx_a] == NULL ||
(hand[idx_b] != NULL && (hand[idx_a]->card->suit > hand[idx_b]->card->suit ||
(hand[idx_a]->card->suit == hand[idx_b]->card->suit &&
hand[idx_a]->card->rank > hand[idx_b]->card->rank))))
{
CardObject* temp = hand[a];
hand[a] = hand[b];
hand[b] = temp;
swap_cards_in_hand(idx_a, idx_b);
}
}
}
@@ -1065,20 +1079,88 @@ static inline void sort_hand_by_suit(void)
static inline void sort_hand_by_rank(void)
{
for (int a = 0; a < hand_top; a++)
for (int idx_a = 0; idx_a < hand_top; idx_a++)
{
for (int b = a + 1; b <= hand_top; b++)
for (int idx_b = idx_a + 1; idx_b <= hand_top; idx_b++)
{
if (hand[a] == NULL || (hand[b] != NULL && hand[a]->card->rank > hand[b]->card->rank))
if (hand[idx_a] == NULL ||
(hand[idx_b] != NULL && hand[idx_a]->card->rank > hand[idx_b]->card->rank))
{
CardObject* temp = hand[a];
hand[a] = hand[b];
hand[b] = temp;
swap_cards_in_hand(idx_a, idx_b);
}
}
}
}
static inline bool shift_null_card_to_end(int null_card_idx)
{
// Start by searching any non NULL cards after the NULL one
// don't start at null_card_idx+1 to avoid potential illegal array access
int non_null_card_idx = null_card_idx;
for (; non_null_card_idx <= hand_top; non_null_card_idx++)
{
if (hand[non_null_card_idx] != NULL)
{
break;
}
}
// return false if there are no non-NULL cards left/there are no more sprites to destroy
if (non_null_card_idx > hand_top)
{
return false;
}
// If there is one, shift it and all the cards that follow forward
// This way we close the gap and ensure the next card is not NULL
// Iterating up to `hand_top - non_null_card_idx + 1` should end up out of bounds
// but for some reason it doesn't pose any issue, and taking out the +1 breaks
// the code, so I'll be elaving it here until someone figures it out ^^'
for (int j = 0; j <= hand_top - non_null_card_idx + 1; j++)
{
hand[null_card_idx + j] = hand[non_null_card_idx + j];
}
return true;
}
static void reorder_card_sprites_layers(void)
{
// Update the sprites in the hand by destroying them and creating new ones in the correct order
// (This feels like a diabolical solution but like literally how else would you do this)
for (int i = 0; i <= hand_top; i++)
{
// a NULL card will only happen if we rearrange the sprites without having sorted them
// before. Any NULL CardObject will be sent to the end by shifting all elements forward
if (hand[i] == NULL)
{
if (!shift_null_card_to_end(i))
{
break;
}
}
// card_object_get_sprite() will not work here since we need the address
sprite_destroy(&(hand[i]->sprite_object->sprite));
}
// Recreate the sprites for the remaining non NULL cards, in order
for (int i = 0; i <= hand_top; i++)
{
if (hand[i] != NULL)
{
// Set the sprite for the card object
card_object_set_sprite(hand[i], i);
sprite_position(
card_object_get_sprite(hand[i]),
fx2int(hand[i]->sprite_object->x),
fx2int(hand[i]->sprite_object->y)
);
}
}
}
static void sort_cards(void)
{
if (sort_by_suit)
@@ -1090,31 +1172,7 @@ static void sort_cards(void)
sort_hand_by_rank();
}
// Update the sprites in the hand by destroying them and creating new ones in the correct order
// (This is feels like a diabolical solution but like literally how else would you do this)
for (int i = 0; i <= hand_top; i++)
{
if (hand[i] != NULL)
{
// card_object_get_sprite() will not work here since we need the address
sprite_destroy(&(hand[i]->sprite_object->sprite));
}
}
for (int i = 0; i <= hand_top; i++)
{
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);
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)
);
}
}
reorder_card_sprites_layers();
}
/* Copies the appropriate item into the top left panel (blind/shop icon)
@@ -1693,7 +1751,7 @@ static void hand_set_focus(int index)
);
}
static bool hand_discard(void)
static bool hand_can_discard(void)
{
if (hand_state != HAND_SELECT || hand_selections == 0)
return false;
@@ -1888,83 +1946,90 @@ static inline void hand_change_sort(void)
sort_cards();
}
static inline bool hand_play(void)
static inline bool hand_can_play(void)
{
if (hand_state != HAND_SELECT || hand_selections == 0)
return false;
return true;
}
static inline void game_playing_process_hand_select_input(void)
static inline void select_current_card(void)
{
static bool discard_button_highlighted =
false; // true = play button highlighted, false = discard button highlighted
hand_toggle_card_selection();
set_hand();
}
if (key_hit(KEY_LEFT))
{
if (selection_y == 0)
{
// 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
{
discard_button_highlighted = false; // Play button
}
}
else if (key_hit(KEY_RIGHT))
{
if (selection_y == 0)
{
hand_set_focus(selection_x - 1);
}
else
{
discard_button_highlighted = true; // Discard button
}
}
else if (key_hit(KEY_UP) && selection_y != 0)
{
selection_y = 0;
}
else if (key_hit(KEY_DOWN) && selection_y != 1)
{
selection_y = 1;
// card moving logic
if (selection_x > hand_top / 2)
// 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)
{
// 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;
// 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)
{
discard_button_highlighted = false; // Play button
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);
}
}
else
{
discard_button_highlighted = true; // Discard button
}
}
else if (selection_y == 1) // On row of play/discard buttons
{
if (discard_button_highlighted == false) // Play button logic
// 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);
}
}
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);
}
if (key_hit(SELECT_CARD) && hands > 0 && hand_play())
{
play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME);
hand_state = HAND_PLAY;
selection_x = 0;
selection_y = 0;
display_hands(--hands);
}
}
else // Discard button logic
{
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);
}
if (key_hit(SELECT_CARD) && discards > 0 && hand_discard())
{
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;
@@ -1979,20 +2044,103 @@ static inline void game_playing_process_hand_select_input(void)
TTE_RED_PB,
discards
);
}
}
}
else if (selection_y == 0) // On row of cards
{
// 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);
}
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))
{
hand_toggle_card_selection();
set_hand();
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))
@@ -2105,7 +2253,7 @@ static inline void card_in_hand_loop_handle_discard_and_shuffling(
{
discard_push(hand[card_idx]->card);
card_object_destroy(&hand[card_idx]);
sort_cards();
reorder_card_sprites_layers();
hand_top--;
// This technically isn't drawing cards, I'm just reusing the variable
@@ -3013,7 +3161,7 @@ static inline void cards_in_hand_update_loop(void)
played_push(hand[i]);
sprite_destroy(&hand[i]->sprite_object->sprite);
hand[i] = NULL;
sort_cards();
reorder_card_sprites_layers();
play_sfx(
SFX_CARD_DRAW,