Refactor/hand (#490)
* move all hand funcs to their new file. now to clean up * WIP * Cleanup + doc hand.h * clang format * Refactor loose variables in hand.c into a proper Hand struct * Fix some more issues * Apply *some* suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * remove outdated comment * fix rebase issue * fix rng_info missing from g_game_vars init --------- Co-authored-by: MathisMartin31 <mathis.martin31@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
MathisMartin31
parent
d7414157a5
commit
2329c4c681
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include <tonc.h>
|
#include <tonc.h>
|
||||||
|
|
||||||
#define MAX_HAND_SIZE 16
|
|
||||||
#define MAX_DECK_SIZE 52
|
#define MAX_DECK_SIZE 52
|
||||||
#define MAX_JOKERS_HELD_SIZE 5 // This doesn't account for negatives right now.
|
#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_SHOP_JOKERS 2 // TODO: Make this dynamic and allow for other items besides jokers
|
||||||
@@ -54,18 +53,6 @@ enum GameState
|
|||||||
GAME_STATE_UNDEFINED
|
GAME_STATE_UNDEFINED
|
||||||
};
|
};
|
||||||
|
|
||||||
enum HandState
|
|
||||||
{
|
|
||||||
HAND_DRAW,
|
|
||||||
HAND_SELECT,
|
|
||||||
// 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
|
|
||||||
};
|
|
||||||
|
|
||||||
enum PlayState
|
enum PlayState
|
||||||
{
|
{
|
||||||
PLAY_STARTING,
|
PLAY_STARTING,
|
||||||
@@ -79,53 +66,6 @@ enum PlayState
|
|||||||
PLAY_ENDED
|
PLAY_ENDED
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hand types
|
|
||||||
enum HandType
|
|
||||||
{
|
|
||||||
NONE,
|
|
||||||
HIGH_CARD,
|
|
||||||
PAIR,
|
|
||||||
TWO_PAIR,
|
|
||||||
THREE_OF_A_KIND,
|
|
||||||
STRAIGHT,
|
|
||||||
FLUSH,
|
|
||||||
FULL_HOUSE,
|
|
||||||
FOUR_OF_A_KIND,
|
|
||||||
STRAIGHT_FLUSH,
|
|
||||||
ROYAL_FLUSH,
|
|
||||||
FIVE_OF_A_KIND,
|
|
||||||
FLUSH_HOUSE,
|
|
||||||
FLUSH_FIVE
|
|
||||||
};
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
// Store all contained hands to optimize "whole hand condition" Jokers
|
|
||||||
typedef struct ContainedHandTypes
|
|
||||||
{
|
|
||||||
union
|
|
||||||
{
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
u16 HIGH_CARD : 1;
|
|
||||||
u16 PAIR : 1;
|
|
||||||
u16 TWO_PAIR : 1;
|
|
||||||
u16 THREE_OF_A_KIND : 1;
|
|
||||||
u16 STRAIGHT : 1;
|
|
||||||
u16 FLUSH : 1;
|
|
||||||
u16 FULL_HOUSE : 1;
|
|
||||||
u16 FOUR_OF_A_KIND : 1;
|
|
||||||
u16 STRAIGHT_FLUSH : 1;
|
|
||||||
u16 ROYAL_FLUSH : 1;
|
|
||||||
u16 FIVE_OF_A_KIND : 1;
|
|
||||||
u16 FLUSH_HOUSE : 1;
|
|
||||||
u16 FLUSH_FIVE : 1;
|
|
||||||
u16 : 3;
|
|
||||||
};
|
|
||||||
u16 value;
|
|
||||||
};
|
|
||||||
} ContainedHandTypes;
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// Game functions
|
// Game functions
|
||||||
void game_init();
|
void game_init();
|
||||||
|
|
||||||
@@ -142,9 +82,6 @@ void game_reset();
|
|||||||
void game_update();
|
void game_update();
|
||||||
void game_change_state(enum GameState new_game_state);
|
void game_change_state(enum GameState new_game_state);
|
||||||
|
|
||||||
CardObject** get_hand_array(void);
|
|
||||||
int get_hand_top(void);
|
|
||||||
int hand_get_size(void);
|
|
||||||
CardObject** get_played_array(void);
|
CardObject** get_played_array(void);
|
||||||
int get_played_top(void);
|
int get_played_top(void);
|
||||||
int get_scored_card_index(void);
|
int get_scored_card_index(void);
|
||||||
@@ -159,9 +96,6 @@ List* get_shop_jokers_list(void);
|
|||||||
Bitset* get_avail_jokers_bitset(void);
|
Bitset* get_avail_jokers_bitset(void);
|
||||||
void set_shop_joker_avail(int joker_id, bool avail);
|
void set_shop_joker_avail(int joker_id, bool avail);
|
||||||
|
|
||||||
ContainedHandTypes* get_contained_hands(void);
|
|
||||||
enum HandType* get_hand_type(void);
|
|
||||||
|
|
||||||
int get_deck_top(void);
|
int get_deck_top(void);
|
||||||
int get_num_discards_remaining(void);
|
int get_num_discards_remaining(void);
|
||||||
int get_num_hands_remaining(void);
|
int get_num_hands_remaining(void);
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
#define MAX_HANDS 4
|
#define MAX_HANDS 4
|
||||||
#define MAX_DISCARDS 4
|
#define MAX_DISCARDS 4
|
||||||
|
|
||||||
|
#define DEFAULT_HAND_SIZE 8
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A central location for all game variables.
|
* @brief A central location for all game variables.
|
||||||
*
|
*
|
||||||
@@ -45,6 +47,7 @@ typedef struct
|
|||||||
s32 round;
|
s32 round;
|
||||||
s32 ante;
|
s32 ante;
|
||||||
s32 money;
|
s32 money;
|
||||||
|
s32 hand_size;
|
||||||
|
|
||||||
// Blind variables
|
// Blind variables
|
||||||
|
|
||||||
|
|||||||
+234
@@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* @file hand.h
|
||||||
|
*
|
||||||
|
* @brief Functions relative to manipulating and analyzing the contents of the Hand,
|
||||||
|
* a.k.a. the Cards we hold and play.
|
||||||
|
*/
|
||||||
|
#ifndef HAND_H
|
||||||
|
#define HAND_H
|
||||||
|
|
||||||
|
#include "card.h"
|
||||||
|
|
||||||
|
#include <tonc.h>
|
||||||
|
|
||||||
|
#define MAX_HAND_SIZE 16
|
||||||
|
|
||||||
|
enum HandState
|
||||||
|
{
|
||||||
|
HAND_DRAW,
|
||||||
|
HAND_SELECT,
|
||||||
|
// 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
|
||||||
|
};
|
||||||
|
|
||||||
|
enum HandType
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
HIGH_CARD,
|
||||||
|
PAIR,
|
||||||
|
TWO_PAIR,
|
||||||
|
THREE_OF_A_KIND,
|
||||||
|
STRAIGHT,
|
||||||
|
FLUSH,
|
||||||
|
FULL_HOUSE,
|
||||||
|
FOUR_OF_A_KIND,
|
||||||
|
STRAIGHT_FLUSH,
|
||||||
|
ROYAL_FLUSH,
|
||||||
|
FIVE_OF_A_KIND,
|
||||||
|
FLUSH_HOUSE,
|
||||||
|
FLUSH_FIVE
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
// Store all contained hands to optimize "whole hand condition" Jokers
|
||||||
|
typedef struct ContainedHandTypes
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u16 HIGH_CARD : 1;
|
||||||
|
u16 PAIR : 1;
|
||||||
|
u16 TWO_PAIR : 1;
|
||||||
|
u16 THREE_OF_A_KIND : 1;
|
||||||
|
u16 STRAIGHT : 1;
|
||||||
|
u16 FLUSH : 1;
|
||||||
|
u16 FULL_HOUSE : 1;
|
||||||
|
u16 FOUR_OF_A_KIND : 1;
|
||||||
|
u16 STRAIGHT_FLUSH : 1;
|
||||||
|
u16 ROYAL_FLUSH : 1;
|
||||||
|
u16 FIVE_OF_A_KIND : 1;
|
||||||
|
u16 FLUSH_HOUSE : 1;
|
||||||
|
u16 FLUSH_FIVE : 1;
|
||||||
|
u16 : 3;
|
||||||
|
};
|
||||||
|
u16 value;
|
||||||
|
};
|
||||||
|
} ContainedHandTypes;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Hand Structure Manipulation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the hand state. Primarily used by the GAME_PLAYING game state.
|
||||||
|
*
|
||||||
|
* @sa HandState
|
||||||
|
*/
|
||||||
|
void set_hand_state(enum HandState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the hand state
|
||||||
|
*
|
||||||
|
* @return enum HandState
|
||||||
|
*
|
||||||
|
* @sa set_hand_state
|
||||||
|
*/
|
||||||
|
enum HandState get_hand_state(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine the HandType and ContainedHandTypes of the currently selected Cards,
|
||||||
|
* then print the Hand's name, chips, and mult on screen.
|
||||||
|
*/
|
||||||
|
void compute_hand_value_info(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current hand type
|
||||||
|
*
|
||||||
|
* @return enum HandType
|
||||||
|
*
|
||||||
|
* @sa compute_hand_value_info
|
||||||
|
*/
|
||||||
|
enum HandType get_hand_type(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the contained hands within the selected hand
|
||||||
|
*
|
||||||
|
* @return ContainedHandTypes*
|
||||||
|
*
|
||||||
|
* @sa compute_hand_value_info
|
||||||
|
*/
|
||||||
|
ContainedHandTypes* get_contained_hands(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the hand array of Cards currently held in hand
|
||||||
|
*
|
||||||
|
* @return CardObject**
|
||||||
|
*/
|
||||||
|
CardObject** get_hand_array(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the position in hand array of the last card obtained
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @sa get_hand_array
|
||||||
|
*/
|
||||||
|
int get_hand_top(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the position in hand array of the last card obtained
|
||||||
|
*
|
||||||
|
* @param new_hand_top
|
||||||
|
*
|
||||||
|
* @sa get_hand_top
|
||||||
|
*/
|
||||||
|
void set_hand_top(int new_hand_top);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current number of Cards in hand.
|
||||||
|
*
|
||||||
|
* @return `hand_top + 1`
|
||||||
|
*
|
||||||
|
* @sa get_hand_top
|
||||||
|
*/
|
||||||
|
int hand_nb_held_cards(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current number of selected Cards.
|
||||||
|
*
|
||||||
|
* @return `card_selections`
|
||||||
|
*/
|
||||||
|
int hand_get_nb_selected_cards(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the current number of selected Cards.
|
||||||
|
*
|
||||||
|
* @param new_selections
|
||||||
|
*
|
||||||
|
* @sa hand_get_nb_selected_cards
|
||||||
|
*/
|
||||||
|
void hand_set_nb_selected_cards(int new_selections);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the card at a given index in Hand as selected
|
||||||
|
*
|
||||||
|
* @param index Index of card to select in hand
|
||||||
|
*/
|
||||||
|
void hand_select_card(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switch to the given sort method. Can sort playing cards in two ways: by rank and suit.
|
||||||
|
* The order of suits is as follows:
|
||||||
|
* ```
|
||||||
|
* SPADES > HEARTS > CLUBS > DIAMONDS
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param to_sort_by_suit
|
||||||
|
*/
|
||||||
|
void hand_change_sort(bool to_sort_by_suit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deselect all cards in hand.
|
||||||
|
*/
|
||||||
|
void hand_deselect_all_cards(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Swaps the order of two cards in hand.
|
||||||
|
*
|
||||||
|
* @param idx_a index of the first card
|
||||||
|
* @param idx_b index of the second card
|
||||||
|
*/
|
||||||
|
void swap_cards_in_hand(int idx_a, int idx_b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the sprites of the Cards held in hand and recreate then in the same
|
||||||
|
* order as the Cards in the hand array. This allows Cards to render properly
|
||||||
|
* when help in hand during the round or a Tarot/Spectral booster pack.
|
||||||
|
*/
|
||||||
|
void reorder_card_sprites_layers(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sort the hand array according to the selected method to do that.
|
||||||
|
*
|
||||||
|
* @sa hand_change_sort
|
||||||
|
*/
|
||||||
|
void sort_cards(void);
|
||||||
|
|
||||||
|
// Hand Contents Analysis
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the largest flush (set of cards with the same suit) in the given array of played cards.
|
||||||
|
* Marks the cards belonging to the best flush in the out_selection array.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* @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);
|
||||||
|
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
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#ifndef HAND_ANALYSIS_H
|
|
||||||
#define HAND_ANALYSIS_H
|
|
||||||
|
|
||||||
#include "card.h"
|
|
||||||
|
|
||||||
#include <tonc.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Outputs the distribution of ranks and suits in the hand
|
|
||||||
* @param ranks_out output - updated such as ranks_out[rank] is the number of cards of rank in the
|
|
||||||
* hand. Must be of size NUM_RANKS.
|
|
||||||
* @param suits_out output - updated such as suits_out[suit] is the number of cards if suit in the
|
|
||||||
* hand Must be of size NUM_SUITS
|
|
||||||
*/
|
|
||||||
void get_hand_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Outputs the distribution of ranks and suits in the played stack
|
|
||||||
* @param ranks_out output - updated such as ranks_out[rank] is the number of cards of rank in the
|
|
||||||
* played stack. Must be of size NUM_RANKS.
|
|
||||||
* @param suits_out output - updated such as suits_out[suit] is the number of cards if suit in the
|
|
||||||
* played stack. Must be of size NUM_SUITS
|
|
||||||
*/
|
|
||||||
void get_played_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS]);
|
|
||||||
|
|
||||||
u8 hand_contains_n_of_a_kind(u8* ranks);
|
|
||||||
bool hand_contains_two_pair(u8* ranks);
|
|
||||||
bool hand_contains_full_house(u8* ranks);
|
|
||||||
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
|
|
||||||
);
|
|
||||||
void select_paired_cards_in_hand(CardObject** played, int top, bool* selection);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "blind_gfx.h"
|
#include "blind_gfx.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "graphic_utils.h"
|
#include "graphic_utils.h"
|
||||||
|
#include "hand.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "stdbool.h"
|
#include "stdbool.h"
|
||||||
|
|||||||
+85
-474
@@ -17,7 +17,7 @@
|
|||||||
#include "game/shop.h"
|
#include "game/shop.h"
|
||||||
#include "game_variables.h"
|
#include "game_variables.h"
|
||||||
#include "graphic_utils.h"
|
#include "graphic_utils.h"
|
||||||
#include "hand_analysis.h"
|
#include "hand.h"
|
||||||
#include "joker.h"
|
#include "joker.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
@@ -86,13 +86,6 @@
|
|||||||
|
|
||||||
#define EXPIRE_ANIMATION_FRAME_COUNT 3
|
#define EXPIRE_ANIMATION_FRAME_COUNT 3
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
u32 chips;
|
|
||||||
u32 mult;
|
|
||||||
char* display_name;
|
|
||||||
} HandValues;
|
|
||||||
|
|
||||||
// Used as a No Operation for game states that have no init and/or exit function.
|
// 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.
|
// efficient. Well, this is the answer.
|
||||||
@@ -111,10 +104,8 @@ static void noop(void)
|
|||||||
static void game_round_on_init(void);
|
static void game_round_on_init(void);
|
||||||
static void game_playing_on_update(void);
|
static void game_playing_on_update(void);
|
||||||
|
|
||||||
static void sort_cards(void);
|
|
||||||
static void display_temp_score(u32 value);
|
static void display_temp_score(u32 value);
|
||||||
static void check_flaming_score(void);
|
static void check_flaming_score(void);
|
||||||
static void set_hand(void);
|
|
||||||
static int deck_get_size(void);
|
static int deck_get_size(void);
|
||||||
static int deck_get_max_size(void);
|
static int deck_get_max_size(void);
|
||||||
static bool check_and_score_joker_for_event(
|
static bool check_and_score_joker_for_event(
|
||||||
@@ -154,12 +145,8 @@ static bool game_playing_hand_row_on_selection_changed(
|
|||||||
static int game_playing_hand_row_get_size(void);
|
static int game_playing_hand_row_get_size(void);
|
||||||
|
|
||||||
static int hand_sel_idx_to_card_idx(int selection_index);
|
static int hand_sel_idx_to_card_idx(int selection_index);
|
||||||
static void hand_select_card(int index);
|
|
||||||
static void hand_toggle_sort(void);
|
|
||||||
static void hand_change_sort(bool to_sort_by_suit);
|
|
||||||
static void hand_deselect_all_cards(void);
|
|
||||||
static bool can_play_hand(void);
|
|
||||||
static bool can_discard_hand(void);
|
static bool can_discard_hand(void);
|
||||||
|
static bool can_play_hand(void);
|
||||||
|
|
||||||
// Consts
|
// Consts
|
||||||
|
|
||||||
@@ -195,9 +182,8 @@ static const BG_POINT SCORE_FLAME_CHIPS_POS = {1, 9};
|
|||||||
static const BG_POINT SCORE_FLAME_MULT_POS = {5, 9};
|
static const BG_POINT SCORE_FLAME_MULT_POS = {5, 9};
|
||||||
|
|
||||||
// Rects for TTE (in pixels)
|
// Rects for TTE (in pixels)
|
||||||
static const Rect HAND_SIZE_RECT_SELECT = {128, 128, 152, 136 };
|
static const Rect HAND_SIZE_RECT_SELECT = {120, 128, 160, 136 };
|
||||||
static const Rect HAND_SIZE_RECT_PLAYING = {128, 152, 152, 160 };
|
static const Rect HAND_SIZE_RECT_PLAYING = {120, 152, 160, 160 };
|
||||||
static const Rect HAND_TYPE_RECT = {8, 64, 64, 72 };
|
|
||||||
// Score displayed in the same place as the hand type
|
// Score displayed in the same place as the hand type
|
||||||
static const Rect TEMP_SCORE_RECT = {8, 64, 64, 72 };
|
static const Rect TEMP_SCORE_RECT = {8, 64, 64, 72 };
|
||||||
static const Rect SCORE_RECT = {24, 48, 64, 56 };
|
static const Rect SCORE_RECT = {24, 48, 64, 56 };
|
||||||
@@ -272,38 +258,17 @@ Button game_playing_buttons[] = {
|
|||||||
static const int HAND_SPACING_LUT[MAX_HAND_SIZE] =
|
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};
|
{28, 28, 28, 28, 27, 21, 18, 15, 13, 12, 10, 9, 9, 8, 8, 7};
|
||||||
|
|
||||||
static const HandValues hand_base_values[] = {
|
|
||||||
{.chips = 0, .mult = 0, .display_name = NULL }, // NONE
|
|
||||||
{.chips = 5, .mult = 1, .display_name = "Hi-Card"}, // 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 = 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 = 60, .mult = 7, .display_name = "4 OAK" }, // FOUR_OF_A_KIND
|
|
||||||
{.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
|
|
||||||
};
|
|
||||||
|
|
||||||
// The current game state, this is used to determine what the game is doing at any given time
|
// 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 GameState game_state = GAME_STATE_UNDEFINED;
|
||||||
static enum HandState hand_state = HAND_DRAW;
|
|
||||||
static enum PlayState play_state = PLAY_STARTING;
|
static enum PlayState play_state = PLAY_STARTING;
|
||||||
|
|
||||||
static enum HandType hand_type = NONE;
|
|
||||||
static ContainedHandTypes _contained_hands = {0};
|
|
||||||
|
|
||||||
// Initialization of the global vars
|
// Initialization of the global vars
|
||||||
// clang-format off
|
// clang-format off
|
||||||
GameVariables g_game_vars = {
|
GameVariables g_game_vars = {
|
||||||
.timer = 0,
|
.timer = 0, .rng_info = {0, 0},
|
||||||
.rng_info = {0, 0},
|
|
||||||
|
|
||||||
.round = 0, .ante = 0, .money = 0,
|
.round = 0, .ante = 0, .money = 0,
|
||||||
|
.hand_size = DEFAULT_HAND_SIZE,
|
||||||
|
|
||||||
.current_blind = BLIND_TYPE_SMALL,
|
.current_blind = BLIND_TYPE_SMALL,
|
||||||
.next_boss_blind = BLIND_TYPE_BIG,
|
.next_boss_blind = BLIND_TYPE_BIG,
|
||||||
@@ -359,9 +324,7 @@ static u32 chips = 0;
|
|||||||
static u32 mult = 0;
|
static u32 mult = 0;
|
||||||
static bool retrigger = false;
|
static bool retrigger = false;
|
||||||
|
|
||||||
static int hand_size = 8; // Default hand size is 8
|
|
||||||
static int cards_drawn = 0;
|
static int cards_drawn = 0;
|
||||||
static int hand_selections = 0;
|
|
||||||
|
|
||||||
// Keeping track of cards scored
|
// Keeping track of cards scored
|
||||||
static int scored_card_index = 0;
|
static int scored_card_index = 0;
|
||||||
@@ -375,8 +338,6 @@ static ListItr _joker_scored_itr;
|
|||||||
static ListItr _joker_card_scored_end_itr;
|
static ListItr _joker_card_scored_end_itr;
|
||||||
static ListItr _joker_round_end_itr;
|
static ListItr _joker_round_end_itr;
|
||||||
|
|
||||||
static bool sort_by_suit = false;
|
|
||||||
|
|
||||||
static List _owned_jokers_list;
|
static List _owned_jokers_list;
|
||||||
static List _discarded_jokers_list;
|
static List _discarded_jokers_list;
|
||||||
static List _expired_jokers_list;
|
static List _expired_jokers_list;
|
||||||
@@ -388,9 +349,6 @@ static List _shop_jokers_list;
|
|||||||
static CardObject* played[MAX_SELECTION_SIZE] = {NULL};
|
static CardObject* played[MAX_SELECTION_SIZE] = {NULL};
|
||||||
static int played_top = -1;
|
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 Card* deck[MAX_DECK_SIZE] = {NULL};
|
||||||
static int deck_top = -1;
|
static int deck_top = -1;
|
||||||
|
|
||||||
@@ -658,21 +616,6 @@ void game_change_state(enum GameState 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)
|
CardObject** get_played_array(void)
|
||||||
{
|
{
|
||||||
return played;
|
return played;
|
||||||
@@ -902,262 +845,6 @@ static inline void display_ante(int value)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 idx_a = 0; idx_a < hand_top; idx_a++)
|
|
||||||
{
|
|
||||||
for (int idx_b = idx_a + 1; idx_b <= hand_top; idx_b++)
|
|
||||||
{
|
|
||||||
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))))
|
|
||||||
{
|
|
||||||
swap_cards_in_hand(idx_a, idx_b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void sort_hand_by_rank(void)
|
|
||||||
{
|
|
||||||
for (int idx_a = 0; idx_a < hand_top; idx_a++)
|
|
||||||
{
|
|
||||||
for (int idx_b = idx_a + 1; idx_b <= hand_top; idx_b++)
|
|
||||||
{
|
|
||||||
if (hand[idx_a] == NULL ||
|
|
||||||
(hand[idx_b] != NULL && hand[idx_a]->card->rank > hand[idx_b]->card->rank))
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
sort_hand_by_suit();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sort_hand_by_rank();
|
|
||||||
}
|
|
||||||
|
|
||||||
reorder_card_sprites_layers();
|
|
||||||
}
|
|
||||||
|
|
||||||
static ContainedHandTypes compute_contained_hand_types(void)
|
|
||||||
{
|
|
||||||
ContainedHandTypes hand_types = {0};
|
|
||||||
|
|
||||||
// Idk if this is how Balatro does it but this is how I'm doing it
|
|
||||||
if (hand_selections == 0 || hand_state == HAND_DISCARD)
|
|
||||||
{
|
|
||||||
return hand_types;
|
|
||||||
}
|
|
||||||
|
|
||||||
hand_types.HIGH_CARD = 1;
|
|
||||||
|
|
||||||
u8 suits[NUM_SUITS];
|
|
||||||
u8 ranks[NUM_RANKS];
|
|
||||||
get_hand_distribution(ranks, suits);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Pair and 2 Pair
|
|
||||||
if (n_of_a_kind >= 2)
|
|
||||||
{
|
|
||||||
hand_types.PAIR = 1;
|
|
||||||
|
|
||||||
if (hand_contains_two_pair(ranks))
|
|
||||||
{
|
|
||||||
hand_types.TWO_PAIR = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3 OAK
|
|
||||||
if (n_of_a_kind >= 3)
|
|
||||||
{
|
|
||||||
hand_types.THREE_OF_A_KIND = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Straight
|
|
||||||
if (hand_contains_straight(ranks))
|
|
||||||
{
|
|
||||||
hand_types.STRAIGHT = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush
|
|
||||||
if (hand_contains_flush(suits))
|
|
||||||
{
|
|
||||||
hand_types.FLUSH = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full House
|
|
||||||
if (n_of_a_kind >= 3 && hand_contains_full_house(ranks))
|
|
||||||
{
|
|
||||||
hand_types.FULL_HOUSE = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4 OAK
|
|
||||||
if (n_of_a_kind >= 4)
|
|
||||||
{
|
|
||||||
hand_types.FOUR_OF_A_KIND = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Straight Flush
|
|
||||||
if (hand_types.STRAIGHT && hand_types.FLUSH)
|
|
||||||
{
|
|
||||||
hand_types.STRAIGHT_FLUSH = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Royal Flush
|
|
||||||
if (hand_types.STRAIGHT_FLUSH)
|
|
||||||
{
|
|
||||||
if (ranks[TEN] && ranks[JACK] && ranks[QUEEN] && ranks[KING] && ranks[ACE])
|
|
||||||
{
|
|
||||||
hand_types.ROYAL_FLUSH = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5 OAK
|
|
||||||
if (n_of_a_kind >= 5)
|
|
||||||
{
|
|
||||||
hand_types.FIVE_OF_A_KIND = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush House and Five
|
|
||||||
if (hand_types.FLUSH)
|
|
||||||
{
|
|
||||||
if (hand_types.FULL_HOUSE)
|
|
||||||
{
|
|
||||||
hand_types.FLUSH_HOUSE = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hand_types.FIVE_OF_A_KIND)
|
|
||||||
{
|
|
||||||
hand_types.FLUSH_FIVE = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hand_types;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContainedHandTypes* get_contained_hands(void)
|
|
||||||
{
|
|
||||||
return &_contained_hands;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HandType compute_hand_type(struct ContainedHandTypes contained_types)
|
|
||||||
{
|
|
||||||
enum HandType ret;
|
|
||||||
|
|
||||||
// test each pit see if it's set to 1, and return the first one
|
|
||||||
for (ret = FLUSH_FIVE; ret > NONE; ret--)
|
|
||||||
{
|
|
||||||
// Shift the bit we want to check to the front and mask it with 1 to keep only that
|
|
||||||
// Since the ContainedHandTypes is ordered the same way as the HandType enum, we
|
|
||||||
// can shift right by ret-1 to have the bit we want at the front
|
|
||||||
if ((contained_types.value >> (ret - 1)) & 0x1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we broke early, ret contains the value of the HandType enum corresponding to
|
|
||||||
// the position of the highest bit set to 1 in contained_types.value, which is the
|
|
||||||
// most powerful poker hand contained in the current Hand
|
|
||||||
// If not, then it contains NONE, which is what we're supposed to return when there
|
|
||||||
// are no Hands contained in what we played
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HandType* get_hand_type(void)
|
|
||||||
{
|
|
||||||
return &hand_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the card is *considered* a face card
|
// Returns true if the card is *considered* a face card
|
||||||
bool card_is_face(Card* card)
|
bool card_is_face(Card* card)
|
||||||
{
|
{
|
||||||
@@ -1375,43 +1062,6 @@ void display_discards(void)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void print_hand_type(const char* hand_type_str)
|
|
||||||
{
|
|
||||||
if (hand_type_str == NULL)
|
|
||||||
return; // NULL-checking paranoia
|
|
||||||
|
|
||||||
Rect hand_type_rect = HAND_TYPE_RECT;
|
|
||||||
update_text_rect_to_center_str(&hand_type_rect, hand_type_str, SCREEN_LEFT);
|
|
||||||
tte_printf(
|
|
||||||
"#{P:%d,%d; cx:0x%X000}%s",
|
|
||||||
hand_type_rect.left,
|
|
||||||
hand_type_rect.top,
|
|
||||||
TTE_WHITE_PB,
|
|
||||||
hand_type_str
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void set_hand(void)
|
|
||||||
{
|
|
||||||
tte_erase_rect_wrapper(HAND_TYPE_RECT);
|
|
||||||
_contained_hands = compute_contained_hand_types();
|
|
||||||
hand_type = compute_hand_type(_contained_hands);
|
|
||||||
|
|
||||||
HandValues hand = hand_base_values[hand_type];
|
|
||||||
|
|
||||||
chips = hand.chips;
|
|
||||||
mult = hand.mult;
|
|
||||||
|
|
||||||
print_hand_type(hand.display_name);
|
|
||||||
display_chips();
|
|
||||||
display_mult();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool can_discard_hand(void)
|
|
||||||
{
|
|
||||||
return (g_game_vars.discards > 0 && hand_state == HAND_SELECT && hand_selections > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int deck_get_size(void)
|
static int deck_get_size(void)
|
||||||
{
|
{
|
||||||
return deck_top + 1;
|
return deck_top + 1;
|
||||||
@@ -1420,7 +1070,7 @@ static int deck_get_size(void)
|
|||||||
static int deck_get_max_size(void)
|
static int deck_get_max_size(void)
|
||||||
{
|
{
|
||||||
// This is the max amount of cards that the player currently has in their possession
|
// 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;
|
return get_hand_top() + played_top + deck_top + discard_top + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void deck_shuffle(void)
|
static inline void deck_shuffle(void)
|
||||||
@@ -1436,9 +1086,9 @@ static inline void deck_shuffle(void)
|
|||||||
|
|
||||||
static void game_round_on_init(void)
|
static void game_round_on_init(void)
|
||||||
{
|
{
|
||||||
hand_state = HAND_DRAW;
|
set_hand_state(HAND_DRAW);
|
||||||
|
hand_set_nb_selected_cards(0);
|
||||||
cards_drawn = 0;
|
cards_drawn = 0;
|
||||||
hand_selections = 0;
|
|
||||||
|
|
||||||
sprite_destroy(&g_game_vars.playing_blind_token);
|
sprite_destroy(&g_game_vars.playing_blind_token);
|
||||||
g_game_vars.playing_blind_token = blind_token_new(
|
g_game_vars.playing_blind_token = blind_token_new(
|
||||||
@@ -1504,6 +1154,14 @@ static void game_round_on_init(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Playing state functions
|
// Playing state functions
|
||||||
|
static bool can_discard_hand(void)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
g_game_vars.discards > 0 && get_hand_state() == HAND_SELECT &&
|
||||||
|
hand_get_nb_selected_cards() > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static void game_playing_discard_on_pressed(void)
|
static void game_playing_discard_on_pressed(void)
|
||||||
{
|
{
|
||||||
if (!can_discard_hand())
|
if (!can_discard_hand())
|
||||||
@@ -1520,10 +1178,10 @@ static void game_playing_execute_discard(void)
|
|||||||
if (!can_discard_hand())
|
if (!can_discard_hand())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
hand_state = HAND_DISCARD;
|
set_hand_state(HAND_DISCARD);
|
||||||
--g_game_vars.discards;
|
--g_game_vars.discards;
|
||||||
display_discards();
|
display_discards();
|
||||||
set_hand();
|
compute_hand_value_info();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void game_playing_sort_by_rank_on_pressed(void)
|
static void game_playing_sort_by_rank_on_pressed(void)
|
||||||
@@ -1536,40 +1194,6 @@ static void game_playing_sort_by_suit_on_pressed(void)
|
|||||||
hand_change_sort(true);
|
hand_change_sort(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hand_deselect_all_cards(void)
|
|
||||||
{
|
|
||||||
bool any_cards_deselected = false;
|
|
||||||
for (int i = 0; i <= get_hand_top(); i++)
|
|
||||||
{
|
|
||||||
if (card_object_is_selected(hand[i]))
|
|
||||||
{
|
|
||||||
card_object_set_selected(hand[i], false);
|
|
||||||
hand_selections--;
|
|
||||||
any_cards_deselected = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (any_cards_deselected)
|
|
||||||
{
|
|
||||||
play_sfx(SFX_CARD_DESELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void hand_toggle_sort(void)
|
|
||||||
{
|
|
||||||
sort_by_suit = !sort_by_suit;
|
|
||||||
sort_cards();
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void hand_change_sort(bool to_sort_by_suit)
|
|
||||||
{
|
|
||||||
if (to_sort_by_suit != sort_by_suit)
|
|
||||||
{
|
|
||||||
sort_by_suit = to_sort_by_suit;
|
|
||||||
sort_cards();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void game_playing_play_hand_on_pressed(void)
|
static void game_playing_play_hand_on_pressed(void)
|
||||||
{
|
{
|
||||||
if (!can_play_hand())
|
if (!can_play_hand())
|
||||||
@@ -1586,14 +1210,14 @@ static void game_playing_execute_play_hand(void)
|
|||||||
if (!can_play_hand())
|
if (!can_play_hand())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
hand_state = HAND_PLAY;
|
set_hand_state(HAND_PLAY);
|
||||||
--g_game_vars.hands;
|
--g_game_vars.hands;
|
||||||
display_hands();
|
display_hands();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int game_playing_hand_row_get_size(void)
|
static int game_playing_hand_row_get_size(void)
|
||||||
{
|
{
|
||||||
return hand_get_size();
|
return hand_nb_held_cards();
|
||||||
}
|
}
|
||||||
|
|
||||||
// card moving logic
|
// card moving logic
|
||||||
@@ -1712,7 +1336,7 @@ static void game_playing_hand_row_on_key_transit(
|
|||||||
else if (key_hit(DESELECT_CARDS))
|
else if (key_hit(DESELECT_CARDS))
|
||||||
{
|
{
|
||||||
hand_deselect_all_cards();
|
hand_deselect_all_cards();
|
||||||
set_hand();
|
compute_hand_value_info();
|
||||||
}
|
}
|
||||||
else if (key_hit(PLAY_HAND_KEY))
|
else if (key_hit(PLAY_HAND_KEY))
|
||||||
{
|
{
|
||||||
@@ -1769,7 +1393,7 @@ static void game_playing_button_row_on_key_hit(SelectionGrid* selection_grid, Se
|
|||||||
|
|
||||||
static bool can_play_hand(void)
|
static bool can_play_hand(void)
|
||||||
{
|
{
|
||||||
if (hand_state != HAND_SELECT || hand_selections == 0)
|
if (get_hand_state() != HAND_SELECT || hand_get_nb_selected_cards() == 0)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1785,27 +1409,7 @@ static inline int hand_sel_idx_to_card_idx(int selection_index)
|
|||||||
// This is because the hand is drawn from right to left.
|
// 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.
|
// 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.
|
// Maybe one day it can be reverted and made consistent so this conversion is not needed.
|
||||||
return hand_get_size() - selection_index - 1;
|
return hand_nb_held_cards() - selection_index - 1;
|
||||||
}
|
|
||||||
|
|
||||||
static void hand_select_card(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= hand_get_size() || hand_state != HAND_SELECT || hand[index] == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (card_object_is_selected(hand[index]))
|
|
||||||
{
|
|
||||||
card_object_set_selected(hand[index], false);
|
|
||||||
hand_selections--;
|
|
||||||
play_sfx(SFX_CARD_DESELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
|
||||||
}
|
|
||||||
else if (hand_selections < MAX_SELECTION_SIZE)
|
|
||||||
{
|
|
||||||
card_object_set_selected(hand[index], true);
|
|
||||||
hand_selections++;
|
|
||||||
play_sfx(SFX_CARD_SELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
|
||||||
}
|
|
||||||
set_hand();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void game_playing_process_hand_select_input(void)
|
static inline void game_playing_process_hand_select_input(void)
|
||||||
@@ -1815,7 +1419,8 @@ static inline void game_playing_process_hand_select_input(void)
|
|||||||
|
|
||||||
static inline void card_draw(void)
|
static inline void card_draw(void)
|
||||||
{
|
{
|
||||||
if (deck_top < 0 || hand_top >= hand_size - 1 || hand_top >= MAX_HAND_SIZE - 1)
|
if (deck_top < 0 || get_hand_top() >= g_game_vars.hand_size - 1 ||
|
||||||
|
get_hand_top() >= MAX_HAND_SIZE - 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CardObject* card_object = card_object_new(deck_pop());
|
CardObject* card_object = card_object_new(deck_pop());
|
||||||
@@ -1827,7 +1432,8 @@ static inline void card_draw(void)
|
|||||||
card_object->sprite_object->y = deck_y;
|
card_object->sprite_object->y = deck_y;
|
||||||
sprite_position(card_object->sprite_object->sprite, fx2int(deck_x), fx2int(deck_y));
|
sprite_position(card_object->sprite_object->sprite, fx2int(deck_x), fx2int(deck_y));
|
||||||
|
|
||||||
hand[++hand_top] = card_object;
|
set_hand_top(get_hand_top() + 1);
|
||||||
|
get_hand_array()[get_hand_top()] = card_object;
|
||||||
|
|
||||||
// Sort the hand after drawing a card
|
// Sort the hand after drawing a card
|
||||||
sort_cards();
|
sort_cards();
|
||||||
@@ -1875,14 +1481,16 @@ static inline void card_in_hand_loop_handle_discard_and_shuffling(
|
|||||||
bool* break_loop
|
bool* break_loop
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (hand_state != HAND_DISCARD && hand_state != HAND_SHUFFLING)
|
if (get_hand_state() != HAND_DISCARD && get_hand_state() != HAND_SHUFFLING)
|
||||||
{
|
{
|
||||||
// Assumes hand_state is one of these
|
// Assumes hand_state is one of these
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CardObject** hand = get_hand_array();
|
||||||
|
|
||||||
*break_loop = false;
|
*break_loop = false;
|
||||||
if (card_object_is_selected(hand[card_idx]) || hand_state == HAND_SHUFFLING)
|
if (card_object_is_selected(hand[card_idx]) || get_hand_state() == HAND_SHUFFLING)
|
||||||
{
|
{
|
||||||
if (!discarded_card)
|
if (!discarded_card)
|
||||||
{
|
{
|
||||||
@@ -1905,7 +1513,7 @@ static inline void card_in_hand_loop_handle_discard_and_shuffling(
|
|||||||
card_object_destroy(&hand[card_idx]);
|
card_object_destroy(&hand[card_idx]);
|
||||||
reorder_card_sprites_layers();
|
reorder_card_sprites_layers();
|
||||||
|
|
||||||
hand_top--;
|
set_hand_top(get_hand_top() - 1);
|
||||||
// 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++;
|
cards_drawn++;
|
||||||
sound_played = false;
|
sound_played = false;
|
||||||
@@ -1919,7 +1527,7 @@ static inline void card_in_hand_loop_handle_discard_and_shuffling(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (hand_state == HAND_DISCARD)
|
if (get_hand_state() == HAND_DISCARD)
|
||||||
{
|
{
|
||||||
// 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);
|
*hand_y -= int2fx(15);
|
||||||
@@ -1928,23 +1536,24 @@ static inline void card_in_hand_loop_handle_discard_and_shuffling(
|
|||||||
{
|
{
|
||||||
*hand_y += int2fx(24);
|
*hand_y += int2fx(24);
|
||||||
}
|
}
|
||||||
*hand_x =
|
*hand_x = *hand_x + (int2fx(card_idx) - int2fx(get_hand_top()) / 2) *
|
||||||
*hand_x + (int2fx(card_idx) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top];
|
-HAND_SPACING_LUT[get_hand_top()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
*hand_x = *hand_x + (int2fx(card_idx) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top];
|
*hand_x = *hand_x + (int2fx(card_idx) - int2fx(get_hand_top()) / 2) *
|
||||||
|
-HAND_SPACING_LUT[get_hand_top()];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card_idx == 0 && discarded_card == false && g_game_vars.timer % FRAMES(10) == 0)
|
if (card_idx == 0 && discarded_card == false && g_game_vars.timer % FRAMES(10) == 0)
|
||||||
{
|
{
|
||||||
// This is never reached in the case of HAND_SHUFFLING. Not sure why but that's how it's
|
// This is never reached in the case of HAND_SHUFFLING. Not sure why but that's how it's
|
||||||
// supposed to be.
|
// supposed to be.
|
||||||
hand_state = HAND_DRAW;
|
set_hand_state(HAND_DRAW);
|
||||||
sound_played = false;
|
sound_played = false;
|
||||||
cards_drawn = 0;
|
cards_drawn = 0;
|
||||||
hand_selections = 0;
|
hand_set_nb_selected_cards(0);
|
||||||
g_game_vars.timer = TM_ZERO;
|
g_game_vars.timer = TM_ZERO;
|
||||||
*break_loop = true;
|
*break_loop = true;
|
||||||
return;
|
return;
|
||||||
@@ -1960,7 +1569,8 @@ static inline void select_flush_and_straight_cards_in_played_hand(void)
|
|||||||
int min_len = get_straight_and_flush_size();
|
int min_len = get_straight_and_flush_size();
|
||||||
|
|
||||||
// if we have a flush in our hand
|
// if we have a flush in our hand
|
||||||
if (hand_type == FLUSH || hand_type == STRAIGHT_FLUSH || hand_type == ROYAL_FLUSH)
|
if (get_hand_type() == FLUSH || get_hand_type() == STRAIGHT_FLUSH ||
|
||||||
|
get_hand_type() == ROYAL_FLUSH)
|
||||||
{
|
{
|
||||||
bool flush_selection[MAX_HAND_SIZE] = {false};
|
bool flush_selection[MAX_HAND_SIZE] = {false};
|
||||||
find_flush_in_played_cards(played, played_top, min_len, flush_selection);
|
find_flush_in_played_cards(played, played_top, min_len, flush_selection);
|
||||||
@@ -1972,7 +1582,8 @@ static inline void select_flush_and_straight_cards_in_played_hand(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we have a straight in our hand
|
// If we have a straight in our hand
|
||||||
if (hand_type == STRAIGHT || hand_type == STRAIGHT_FLUSH || hand_type == ROYAL_FLUSH)
|
if (get_hand_type() == STRAIGHT || get_hand_type() == STRAIGHT_FLUSH ||
|
||||||
|
get_hand_type() == ROYAL_FLUSH)
|
||||||
{
|
{
|
||||||
bool straight_selection[MAX_HAND_SIZE] = {false};
|
bool straight_selection[MAX_HAND_SIZE] = {false};
|
||||||
find_straight_in_played_cards(
|
find_straight_in_played_cards(
|
||||||
@@ -2207,16 +1818,16 @@ static bool play_ended_played_cards_update(int played_idx)
|
|||||||
{
|
{
|
||||||
if (game_round_is_over())
|
if (game_round_is_over())
|
||||||
{
|
{
|
||||||
hand_state = HAND_SHUFFLING;
|
set_hand_state(HAND_SHUFFLING);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
hand_state = HAND_DRAW;
|
set_hand_state(HAND_DRAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
play_state = PLAY_STARTING;
|
play_state = PLAY_STARTING;
|
||||||
cards_drawn = 0;
|
cards_drawn = 0;
|
||||||
hand_selections = 0;
|
hand_set_nb_selected_cards(0);
|
||||||
played_top = -1; // Reset the played stack
|
played_top = -1; // Reset the played stack
|
||||||
scored_card_index = 0;
|
scored_card_index = 0;
|
||||||
_joker_scored_itr = list_itr_create(&_owned_jokers_list);
|
_joker_scored_itr = list_itr_create(&_owned_jokers_list);
|
||||||
@@ -2293,7 +1904,7 @@ static inline bool play_scoring_cards_update(void)
|
|||||||
{
|
{
|
||||||
// reuse these variables for held cards
|
// reuse these variables for held cards
|
||||||
_joker_scored_itr = list_itr_create(&_owned_jokers_list);
|
_joker_scored_itr = list_itr_create(&_owned_jokers_list);
|
||||||
scored_card_index = hand_top;
|
scored_card_index = get_hand_top();
|
||||||
|
|
||||||
play_state = PLAY_SCORING_HELD_CARDS;
|
play_state = PLAY_SCORING_HELD_CARDS;
|
||||||
|
|
||||||
@@ -2393,6 +2004,8 @@ static inline bool play_scoring_held_cards_update(int played_idx)
|
|||||||
{
|
{
|
||||||
tte_erase_rect_wrapper(HELD_CARDS_SCORES_RECT);
|
tte_erase_rect_wrapper(HELD_CARDS_SCORES_RECT);
|
||||||
|
|
||||||
|
CardObject** hand = get_hand_array();
|
||||||
|
|
||||||
// Go through all held cards and see if they activate Jokers
|
// Go through all held cards and see if they activate Jokers
|
||||||
for (; scored_card_index >= 0; scored_card_index--)
|
for (; scored_card_index >= 0; scored_card_index--)
|
||||||
{
|
{
|
||||||
@@ -2594,14 +2207,9 @@ static inline void played_cards_update_loop(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int hand_get_max_size(void)
|
|
||||||
{
|
|
||||||
return hand_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void game_playing_process_input_and_state(void)
|
static inline void game_playing_process_input_and_state(void)
|
||||||
{
|
{
|
||||||
if (hand_state == HAND_SELECT)
|
if (get_hand_state() == HAND_SELECT)
|
||||||
{
|
{
|
||||||
game_playing_process_hand_select_input();
|
game_playing_process_hand_select_input();
|
||||||
}
|
}
|
||||||
@@ -2666,7 +2274,7 @@ static inline void game_playing_process_input_and_state(void)
|
|||||||
|
|
||||||
static inline void game_playing_process_card_draw()
|
static inline void game_playing_process_card_draw()
|
||||||
{
|
{
|
||||||
if (hand_state == HAND_DRAW && cards_drawn < hand_size)
|
if (get_hand_state() == HAND_DRAW && cards_drawn < g_game_vars.hand_size)
|
||||||
{
|
{
|
||||||
if (g_game_vars.timer % FRAMES(10) == 0) // Draw a card every 10 frames
|
if (g_game_vars.timer % FRAMES(10) == 0) // Draw a card every 10 frames
|
||||||
{
|
{
|
||||||
@@ -2674,9 +2282,9 @@ static inline void game_playing_process_card_draw()
|
|||||||
card_draw();
|
card_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (hand_state == HAND_DRAW)
|
else if (get_hand_state() == HAND_DRAW)
|
||||||
{
|
{
|
||||||
hand_state = HAND_SELECT; // Change the hand state to select after drawing all the cards
|
set_hand_state(HAND_SELECT); // Change the hand state to select after drawing all the cards
|
||||||
cards_drawn = 0;
|
cards_drawn = 0;
|
||||||
g_game_vars.timer = TM_ZERO;
|
g_game_vars.timer = TM_ZERO;
|
||||||
}
|
}
|
||||||
@@ -2685,7 +2293,7 @@ static inline void game_playing_process_card_draw()
|
|||||||
static inline void game_playing_discarded_cards_loop(void)
|
static inline void game_playing_discarded_cards_loop(void)
|
||||||
{
|
{
|
||||||
// Discarded cards loop (mainly for shuffling)
|
// Discarded cards loop (mainly for shuffling)
|
||||||
if (hand_get_size() == 0 && hand_state == HAND_SHUFFLING && discard_top >= -1 &&
|
if (hand_nb_held_cards() == 0 && get_hand_state() == HAND_SHUFFLING && discard_top >= -1 &&
|
||||||
g_game_vars.timer > FRAMES(10))
|
g_game_vars.timer > FRAMES(10))
|
||||||
{
|
{
|
||||||
// Change the background to the round end background. This is how it works in Balatro, so
|
// Change the background to the round end background. This is how it works in Balatro, so
|
||||||
@@ -2740,7 +2348,7 @@ static inline void game_playing_discarded_cards_loop(void)
|
|||||||
|
|
||||||
static inline void select_cards_in_played_hand()
|
static inline void select_cards_in_played_hand()
|
||||||
{
|
{
|
||||||
switch (hand_type) // select the cards that apply to the hand type
|
switch (get_hand_type()) // select the cards that apply to the hand type
|
||||||
{
|
{
|
||||||
case NONE:
|
case NONE:
|
||||||
break;
|
break;
|
||||||
@@ -2786,18 +2394,20 @@ static inline void cards_in_hand_update_loop(void)
|
|||||||
|
|
||||||
// TODO: Break this function up into smaller ones, Gods be good
|
// 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
|
// 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--)
|
CardObject** hand = get_hand_array();
|
||||||
|
|
||||||
|
for (int i = get_hand_top(); i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (hand[i] != NULL)
|
if (hand[i] != NULL)
|
||||||
{
|
{
|
||||||
FIXED hand_x = int2fx(HAND_START_POS.x);
|
FIXED hand_x = int2fx(HAND_START_POS.x);
|
||||||
FIXED hand_y = int2fx(HAND_START_POS.y);
|
FIXED hand_y = int2fx(HAND_START_POS.y);
|
||||||
|
|
||||||
switch (hand_state)
|
switch (get_hand_state())
|
||||||
{
|
{
|
||||||
case HAND_DRAW:
|
case HAND_DRAW:
|
||||||
hand_x =
|
hand_x = hand_x + (int2fx(i) - int2fx(get_hand_top()) / 2) *
|
||||||
hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top];
|
-HAND_SPACING_LUT[get_hand_top()];
|
||||||
break;
|
break;
|
||||||
case HAND_SELECT:
|
case HAND_SELECT:
|
||||||
bool is_focused =
|
bool is_focused =
|
||||||
@@ -2830,10 +2440,10 @@ static inline void cards_in_hand_update_loop(void)
|
|||||||
hand[i]->sprite_object->vy = 0;
|
hand[i]->sprite_object->vy = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
hand_x =
|
hand_x = hand_x + (int2fx(i) - int2fx(get_hand_top()) / 2) *
|
||||||
hand_x + (int2fx(i) - int2fx(hand_top) / 2) *
|
-HAND_SPACING_LUT[get_hand_top()]; // TODO: Change this
|
||||||
-HAND_SPACING_LUT[hand_top]; // TODO: Change this later to
|
// later to reference a
|
||||||
// reference a 2D LUT of positions
|
// 2D LUT of positions
|
||||||
break;
|
break;
|
||||||
case HAND_SHUFFLING:
|
case HAND_SHUFFLING:
|
||||||
/* FALL THROUGH */
|
/* FALL THROUGH */
|
||||||
@@ -2850,8 +2460,8 @@ static inline void cards_in_hand_update_loop(void)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case HAND_PLAY:
|
case HAND_PLAY:
|
||||||
hand_x =
|
hand_x = hand_x + (int2fx(i) - int2fx(get_hand_top()) / 2) *
|
||||||
hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top];
|
-HAND_SPACING_LUT[get_hand_top()];
|
||||||
hand_y += int2fx(24);
|
hand_y += int2fx(24);
|
||||||
|
|
||||||
if (card_object_is_selected(hand[i]) && discarded_card == false &&
|
if (card_object_is_selected(hand[i]) && discarded_card == false &&
|
||||||
@@ -2869,8 +2479,8 @@ static inline void cards_in_hand_update_loop(void)
|
|||||||
SFX_DEFAULT_VOLUME
|
SFX_DEFAULT_VOLUME
|
||||||
);
|
);
|
||||||
|
|
||||||
hand_top--;
|
set_hand_top(get_hand_top() - 1);
|
||||||
hand_selections--;
|
hand_set_nb_selected_cards(hand_get_nb_selected_cards() - 1);
|
||||||
cards_drawn++;
|
cards_drawn++;
|
||||||
|
|
||||||
discarded_card = true;
|
discarded_card = true;
|
||||||
@@ -2878,9 +2488,9 @@ static inline void cards_in_hand_update_loop(void)
|
|||||||
|
|
||||||
if (i == 0 && discarded_card == false && g_game_vars.timer % FRAMES(10) == 0)
|
if (i == 0 && discarded_card == false && g_game_vars.timer % FRAMES(10) == 0)
|
||||||
{
|
{
|
||||||
hand_state = HAND_PLAYING;
|
set_hand_state(HAND_PLAYING);
|
||||||
cards_drawn = 0;
|
cards_drawn = 0;
|
||||||
hand_selections = 0;
|
hand_set_nb_selected_cards(0);
|
||||||
g_game_vars.timer = TM_ZERO;
|
g_game_vars.timer = TM_ZERO;
|
||||||
scored_card_index = played_top + 1;
|
scored_card_index = played_top + 1;
|
||||||
|
|
||||||
@@ -2890,8 +2500,8 @@ static inline void cards_in_hand_update_loop(void)
|
|||||||
break;
|
break;
|
||||||
// Don't need to do anything here, just wait for the player to select cards
|
// Don't need to do anything here, just wait for the player to select cards
|
||||||
case HAND_PLAYING:
|
case HAND_PLAYING:
|
||||||
hand_x =
|
hand_x = hand_x + (int2fx(i) - int2fx(get_hand_top()) / 2) *
|
||||||
hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top];
|
-HAND_SPACING_LUT[get_hand_top()];
|
||||||
hand_y += int2fx(24);
|
hand_y += int2fx(24);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2908,30 +2518,30 @@ static inline void game_playing_ui_text_update(void)
|
|||||||
static int last_hand_size = 0;
|
static int last_hand_size = 0;
|
||||||
static int last_deck_size = 0;
|
static int last_deck_size = 0;
|
||||||
|
|
||||||
if (last_hand_size != hand_get_size() || last_deck_size != deck_get_size())
|
if (last_hand_size != hand_nb_held_cards() || last_deck_size != deck_get_size())
|
||||||
{
|
{
|
||||||
if (background_legacy == BG_CARD_SELECTING)
|
if (background_legacy == BG_CARD_SELECTING)
|
||||||
{
|
{
|
||||||
// Hand size/max size
|
// Hand size/max size
|
||||||
tte_printf(
|
tte_printf(
|
||||||
"#{P:%d,%d; cx:0x%X000}%d/%d",
|
"#{P:%d,%d; cx:0x%X000}%2d/%-2ld",
|
||||||
HAND_SIZE_RECT_SELECT.left,
|
HAND_SIZE_RECT_SELECT.left,
|
||||||
HAND_SIZE_RECT_SELECT.top,
|
HAND_SIZE_RECT_SELECT.top,
|
||||||
TTE_WHITE_PB,
|
TTE_WHITE_PB,
|
||||||
hand_get_size(),
|
hand_nb_held_cards(),
|
||||||
hand_get_max_size()
|
g_game_vars.hand_size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (background_legacy == BG_CARD_PLAYING)
|
else if (background_legacy == BG_CARD_PLAYING)
|
||||||
{
|
{
|
||||||
// Hand size/max size
|
// Hand size/max size
|
||||||
tte_printf(
|
tte_printf(
|
||||||
"#{P:%d,%d; cx:0x%X000}%d/%d",
|
"#{P:%d,%d; cx:0x%X000}%2d/%-2ld",
|
||||||
HAND_SIZE_RECT_PLAYING.left,
|
HAND_SIZE_RECT_PLAYING.left,
|
||||||
HAND_SIZE_RECT_PLAYING.top,
|
HAND_SIZE_RECT_PLAYING.top,
|
||||||
TTE_WHITE_PB,
|
TTE_WHITE_PB,
|
||||||
hand_get_size(),
|
hand_nb_held_cards(),
|
||||||
hand_get_max_size()
|
g_game_vars.hand_size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2948,7 +2558,7 @@ static inline void game_playing_ui_text_update(void)
|
|||||||
deck_get_max_size()
|
deck_get_max_size()
|
||||||
);
|
);
|
||||||
|
|
||||||
last_hand_size = hand_get_size();
|
last_hand_size = hand_nb_held_cards();
|
||||||
last_deck_size = deck_get_size();
|
last_deck_size = deck_get_size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2980,11 +2590,12 @@ static inline void game_playing_process_flaming_score(void)
|
|||||||
static void game_playing_on_update(void)
|
static void game_playing_on_update(void)
|
||||||
{
|
{
|
||||||
// Background logic (thissss might be moved to the card'ssss logic later. I'm a sssssnake)
|
// 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)
|
if (get_hand_state() == HAND_DRAW || get_hand_state() == HAND_DISCARD ||
|
||||||
|
get_hand_state() == HAND_SELECT)
|
||||||
{
|
{
|
||||||
change_background(BG_CARD_SELECTING, false);
|
change_background(BG_CARD_SELECTING, false);
|
||||||
}
|
}
|
||||||
else if (hand_state != HAND_SHUFFLING)
|
else if (get_hand_state() != HAND_SHUFFLING)
|
||||||
{
|
{
|
||||||
change_background(BG_CARD_PLAYING, false);
|
change_background(BG_CARD_PLAYING, false);
|
||||||
}
|
}
|
||||||
|
|||||||
+909
@@ -0,0 +1,909 @@
|
|||||||
|
/**
|
||||||
|
* @file hand.c
|
||||||
|
*
|
||||||
|
* @brief Implementation of functions relative to manipulating and analyzing the
|
||||||
|
* contents of the Hand.
|
||||||
|
*/
|
||||||
|
#include "hand.h"
|
||||||
|
|
||||||
|
#include "audio_utils.h"
|
||||||
|
#include "card.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "game_variables.h"
|
||||||
|
#include "graphic_utils.h"
|
||||||
|
#include "soundbank.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <tonc.h>
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u32 chips;
|
||||||
|
u32 mult;
|
||||||
|
char* display_name;
|
||||||
|
} HandValues;
|
||||||
|
|
||||||
|
static const HandValues hand_base_values[] = {
|
||||||
|
{.chips = 0, .mult = 0, .display_name = NULL }, // NONE
|
||||||
|
{.chips = 5, .mult = 1, .display_name = "Hi-Card"}, // 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 = 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 = 60, .mult = 7, .display_name = "4 OAK" }, // FOUR_OF_A_KIND
|
||||||
|
{.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
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
// Rects for TTE (in pixels) left top right bottom
|
||||||
|
static const Rect HAND_TYPE_RECT = {8, 64, 64, 72};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
typedef struct Hand
|
||||||
|
{
|
||||||
|
// Hand stack
|
||||||
|
CardObject* cards[MAX_HAND_SIZE];
|
||||||
|
s32 hand_top; // Position of the last card in hand array, -1 when no card in hand
|
||||||
|
s32 hand_selections; // Number of selected Cards.
|
||||||
|
|
||||||
|
// Hand Type
|
||||||
|
enum HandType hand_type;
|
||||||
|
ContainedHandTypes contained_hands;
|
||||||
|
|
||||||
|
enum HandState state;
|
||||||
|
bool sort_by_suit;
|
||||||
|
} Hand;
|
||||||
|
|
||||||
|
static Hand hand = {
|
||||||
|
.cards = {NULL},
|
||||||
|
.hand_top = -1,
|
||||||
|
.hand_selections = 0,
|
||||||
|
.hand_type = NONE,
|
||||||
|
.contained_hands = {{{0}}},
|
||||||
|
.state = HAND_DRAW,
|
||||||
|
.sort_by_suit = false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
static ContainedHandTypes compute_contained_hand_types(void);
|
||||||
|
static enum HandType compute_hand_type(struct ContainedHandTypes contained_types);
|
||||||
|
|
||||||
|
// Hand Struct Manipulation
|
||||||
|
|
||||||
|
enum HandState get_hand_state(void)
|
||||||
|
{
|
||||||
|
return hand.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_hand_state(enum HandState new_hand_state)
|
||||||
|
{
|
||||||
|
hand.state = new_hand_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardObject** get_hand_array(void)
|
||||||
|
{
|
||||||
|
return hand.cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_hand_top(void)
|
||||||
|
{
|
||||||
|
return hand.hand_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_hand_top(int new_hand_top)
|
||||||
|
{
|
||||||
|
hand.hand_top = new_hand_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hand_nb_held_cards(void)
|
||||||
|
{
|
||||||
|
return hand.hand_top + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hand_get_nb_selected_cards(void)
|
||||||
|
{
|
||||||
|
return hand.hand_selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hand_set_nb_selected_cards(int new_selections)
|
||||||
|
{
|
||||||
|
hand.hand_selections = new_selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HandType get_hand_type(void)
|
||||||
|
{
|
||||||
|
return hand.hand_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainedHandTypes* get_contained_hands(void)
|
||||||
|
{
|
||||||
|
return &hand.contained_hands;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_hand_type(const char* hand_type_str)
|
||||||
|
{
|
||||||
|
if (hand_type_str == NULL)
|
||||||
|
return; // NULL-checking paranoia
|
||||||
|
|
||||||
|
Rect hand_type_rect = HAND_TYPE_RECT;
|
||||||
|
update_text_rect_to_center_str(&hand_type_rect, hand_type_str, SCREEN_LEFT);
|
||||||
|
tte_printf(
|
||||||
|
"#{P:%d,%d; cx:0x%X000}%s",
|
||||||
|
hand_type_rect.left,
|
||||||
|
hand_type_rect.top,
|
||||||
|
TTE_WHITE_PB,
|
||||||
|
hand_type_str
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void compute_hand_value_info(void)
|
||||||
|
{
|
||||||
|
tte_erase_rect_wrapper(HAND_TYPE_RECT);
|
||||||
|
hand.contained_hands = compute_contained_hand_types();
|
||||||
|
hand.hand_type = compute_hand_type(hand.contained_hands);
|
||||||
|
|
||||||
|
HandValues hand_values = hand_base_values[hand.hand_type];
|
||||||
|
|
||||||
|
set_chips(hand_values.chips);
|
||||||
|
set_mult(hand_values.mult);
|
||||||
|
|
||||||
|
print_hand_type(hand_values.display_name);
|
||||||
|
display_chips();
|
||||||
|
display_mult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
void swap_cards_in_hand(int idx_a, int idx_b)
|
||||||
|
{
|
||||||
|
CardObject* temp = hand.cards[idx_a];
|
||||||
|
hand.cards[idx_a] = hand.cards[idx_b];
|
||||||
|
hand.cards[idx_b] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sort_hand_by_suit(void)
|
||||||
|
{
|
||||||
|
for (int idx_a = 0; idx_a < hand.hand_top; idx_a++)
|
||||||
|
{
|
||||||
|
for (int idx_b = idx_a + 1; idx_b <= hand.hand_top; idx_b++)
|
||||||
|
{
|
||||||
|
if (hand.cards[idx_a] == NULL ||
|
||||||
|
(hand.cards[idx_b] != NULL &&
|
||||||
|
(hand.cards[idx_a]->card->suit > hand.cards[idx_b]->card->suit ||
|
||||||
|
(hand.cards[idx_a]->card->suit == hand.cards[idx_b]->card->suit &&
|
||||||
|
hand.cards[idx_a]->card->rank > hand.cards[idx_b]->card->rank))))
|
||||||
|
{
|
||||||
|
swap_cards_in_hand(idx_a, idx_b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sort_hand_by_rank(void)
|
||||||
|
{
|
||||||
|
for (int idx_a = 0; idx_a < hand.hand_top; idx_a++)
|
||||||
|
{
|
||||||
|
for (int idx_b = idx_a + 1; idx_b <= hand.hand_top; idx_b++)
|
||||||
|
{
|
||||||
|
if (hand.cards[idx_a] == NULL ||
|
||||||
|
(hand.cards[idx_b] != NULL &&
|
||||||
|
hand.cards[idx_a]->card->rank > hand.cards[idx_b]->card->rank))
|
||||||
|
{
|
||||||
|
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.hand_top; non_null_card_idx++)
|
||||||
|
{
|
||||||
|
if (hand.cards[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.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
|
||||||
|
for (int j = 0; j <= hand.hand_top - non_null_card_idx; j++)
|
||||||
|
{
|
||||||
|
hand.cards[null_card_idx + j] = hand.cards[non_null_card_idx + j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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.cards[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.cards[i]->sprite_object->sprite));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate the sprites for the remaining non NULL cards, in order
|
||||||
|
for (int i = 0; i <= hand.hand_top; i++)
|
||||||
|
{
|
||||||
|
if (hand.cards[i] != NULL)
|
||||||
|
{
|
||||||
|
// Set the sprite for the card object
|
||||||
|
card_object_set_sprite(hand.cards[i], i);
|
||||||
|
sprite_position(
|
||||||
|
card_object_get_sprite(hand.cards[i]),
|
||||||
|
fx2int(hand.cards[i]->sprite_object->x),
|
||||||
|
fx2int(hand.cards[i]->sprite_object->y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sort_cards(void)
|
||||||
|
{
|
||||||
|
if (hand.sort_by_suit)
|
||||||
|
{
|
||||||
|
sort_hand_by_suit();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sort_hand_by_rank();
|
||||||
|
}
|
||||||
|
|
||||||
|
reorder_card_sprites_layers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hand_change_sort(bool to_sort_by_suit)
|
||||||
|
{
|
||||||
|
if (to_sort_by_suit != hand.sort_by_suit)
|
||||||
|
{
|
||||||
|
hand.sort_by_suit = to_sort_by_suit;
|
||||||
|
sort_cards();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hand_select_card(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= hand_nb_held_cards() || hand.state != HAND_SELECT ||
|
||||||
|
hand.cards[index] == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (card_object_is_selected(hand.cards[index]))
|
||||||
|
{
|
||||||
|
card_object_set_selected(hand.cards[index], false);
|
||||||
|
hand.hand_selections--;
|
||||||
|
play_sfx(SFX_CARD_DESELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
||||||
|
}
|
||||||
|
else if (hand.hand_selections < MAX_SELECTION_SIZE)
|
||||||
|
{
|
||||||
|
card_object_set_selected(hand.cards[index], true);
|
||||||
|
hand.hand_selections++;
|
||||||
|
play_sfx(SFX_CARD_SELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
||||||
|
}
|
||||||
|
compute_hand_value_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hand_deselect_all_cards(void)
|
||||||
|
{
|
||||||
|
bool any_cards_deselected = false;
|
||||||
|
for (int i = 0; i <= hand.hand_top; i++)
|
||||||
|
{
|
||||||
|
if (card_object_is_selected(hand.cards[i]))
|
||||||
|
{
|
||||||
|
card_object_set_selected(hand.cards[i], false);
|
||||||
|
hand.hand_selections--;
|
||||||
|
any_cards_deselected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any_cards_deselected)
|
||||||
|
{
|
||||||
|
play_sfx(SFX_CARD_DESELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hand Analysis
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Outputs the distribution of ranks and suits in the hand
|
||||||
|
* @param ranks_out output - updated such as ranks_out[rank] is the number of cards of rank in the
|
||||||
|
* hand. Must be of size NUM_RANKS.
|
||||||
|
* @param suits_out output - updated such as suits_out[suit] is the number of cards if suit in the
|
||||||
|
* hand Must be of size NUM_SUITS
|
||||||
|
*/
|
||||||
|
static void get_hand_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
ranks_out[i] = 0;
|
||||||
|
for (int i = 0; i < NUM_SUITS; i++)
|
||||||
|
suits_out[i] = 0;
|
||||||
|
|
||||||
|
int top = hand.hand_top;
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
{
|
||||||
|
if (hand.cards[i] && card_object_is_selected(hand.cards[i]))
|
||||||
|
{
|
||||||
|
ranks_out[hand.cards[i]->card->rank]++;
|
||||||
|
suits_out[hand.cards[i]->card->suit]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Outputs the distribution of ranks and suits in the played stack
|
||||||
|
* @param ranks_out output - updated such as ranks_out[rank] is the number of cards of rank in the
|
||||||
|
* played stack. Must be of size NUM_RANKS.
|
||||||
|
* @param suits_out output - updated such as suits_out[suit] is the number of cards if suit in the
|
||||||
|
* played stack. Must be of size NUM_SUITS
|
||||||
|
*/
|
||||||
|
GBAL_UNUSED
|
||||||
|
static void get_played_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
ranks_out[i] = 0;
|
||||||
|
for (int i = 0; i < NUM_SUITS; i++)
|
||||||
|
suits_out[i] = 0;
|
||||||
|
|
||||||
|
CardObject** played = get_played_array();
|
||||||
|
int top = get_played_top();
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
{
|
||||||
|
/* The difference from get_hand_distribution() (not checking if card is selected)
|
||||||
|
* is in line Balatro behavior,
|
||||||
|
* see https://github.com/GBALATRO/balatro-gba/issues/341#issuecomment-3691363488
|
||||||
|
*/
|
||||||
|
if (!played[i])
|
||||||
|
continue;
|
||||||
|
ranks_out[played[i]->card->rank]++;
|
||||||
|
suits_out[played[i]->card->suit]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the highest N of a kind. So a full-house would return 3.
|
||||||
|
static u8 hand_contains_n_of_a_kind(u8* ranks)
|
||||||
|
{
|
||||||
|
u8 highest_n = 0;
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
{
|
||||||
|
if (ranks[i] > highest_n)
|
||||||
|
highest_n = ranks[i];
|
||||||
|
}
|
||||||
|
return highest_n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hand_contains_two_pair(u8* ranks)
|
||||||
|
{
|
||||||
|
bool contains_other_pair = false;
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
{
|
||||||
|
if (ranks[i] >= 2)
|
||||||
|
{
|
||||||
|
if (contains_other_pair)
|
||||||
|
return true;
|
||||||
|
contains_other_pair = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hand_contains_full_house(u8* ranks)
|
||||||
|
{
|
||||||
|
int count_three = 0;
|
||||||
|
int count_pair = 0;
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
{
|
||||||
|
if (ranks[i] >= 3)
|
||||||
|
{
|
||||||
|
count_three++;
|
||||||
|
}
|
||||||
|
else if (ranks[i] >= 2)
|
||||||
|
{
|
||||||
|
count_pair++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Full house if there is:
|
||||||
|
// - at least one three-of-a-kind and at least one other pair,
|
||||||
|
// - OR at least two three-of-a-kinds (second "three" acts as pair).
|
||||||
|
// This accounts for hands with 6 or more cards even though
|
||||||
|
// they are currently not possible and probably never will be.
|
||||||
|
return (count_three >= 2 || (count_three && count_pair));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is mostly from Google Gemini
|
||||||
|
static bool hand_contains_straight(u8* ranks)
|
||||||
|
{
|
||||||
|
if (!is_shortcut_joker_active())
|
||||||
|
{
|
||||||
|
int straight_size = get_straight_and_flush_size();
|
||||||
|
// This is the regular case of detecting straights
|
||||||
|
int run = 0;
|
||||||
|
for (int i = 0; i < NUM_RANKS; ++i)
|
||||||
|
{
|
||||||
|
if (ranks[i])
|
||||||
|
{
|
||||||
|
if (++run >= straight_size)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
run = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for ace low straight
|
||||||
|
if (straight_size >= 2 && ranks[ACE])
|
||||||
|
{
|
||||||
|
// With A as low, the highest rank you can use is FIVE.
|
||||||
|
// -1 for inclusive integer distance and another -1 for the Ace e.g. need=5 -> need 2..5
|
||||||
|
int last_needed = TWO + (straight_size - 2);
|
||||||
|
if (last_needed <= FIVE)
|
||||||
|
{
|
||||||
|
bool ok = true;
|
||||||
|
for (int r = TWO; r <= last_needed; ++r)
|
||||||
|
{
|
||||||
|
if (!ranks[r])
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Shortcut Joker is active, we have to detect straights where any card may "skip" 1 rank
|
||||||
|
// We do this with a dynamic programming algorithm that calculates
|
||||||
|
// the longest possible straight that can end on each rank
|
||||||
|
// and stopping when we find one that is {straight-size} cards long
|
||||||
|
u8 longest_short_cut_at[NUM_RANKS] = {0};
|
||||||
|
|
||||||
|
// A low ace can start a sequence. 'ace_low_len' is 1 if an ace is present,
|
||||||
|
// acting as a potential predecessor for TWO and THREE.
|
||||||
|
int ace_low_len = ranks[ACE] ? 1 : 0;
|
||||||
|
|
||||||
|
// Iterate through all ranks from TWO up to ACE.
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
{
|
||||||
|
// No cards in this rank, no straight can end here, continue
|
||||||
|
if (ranks[i] == 0)
|
||||||
|
{
|
||||||
|
longest_short_cut_at[i] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int prev_len1 = 0;
|
||||||
|
int prev_len2 = 0;
|
||||||
|
|
||||||
|
// This logic handles the special connections for ace-low straights.
|
||||||
|
if (i == TWO)
|
||||||
|
{
|
||||||
|
// A TWO can be preceded by a low ACE (no skip).
|
||||||
|
prev_len1 = ace_low_len;
|
||||||
|
}
|
||||||
|
else if (i == THREE)
|
||||||
|
{
|
||||||
|
// A THREE can be preceded by a TWO (no skip) or a low ACE (skip).
|
||||||
|
prev_len1 = longest_short_cut_at[TWO];
|
||||||
|
prev_len2 = ace_low_len;
|
||||||
|
}
|
||||||
|
else if (i == ACE)
|
||||||
|
{
|
||||||
|
// An ACE (as the highest card) can be preceded by a KING or a QUEEN.
|
||||||
|
prev_len1 = longest_short_cut_at[KING];
|
||||||
|
prev_len2 = longest_short_cut_at[QUEEN];
|
||||||
|
}
|
||||||
|
else // For all other cards (FOUR through KING).
|
||||||
|
{
|
||||||
|
// A card can be preceded by the rank directly below or two ranks below.
|
||||||
|
prev_len1 = longest_short_cut_at[i - 1];
|
||||||
|
prev_len2 = longest_short_cut_at[i - 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The length of the straight ending at rank 'i' is 1 (for the card itself)
|
||||||
|
// plus the length of the longest valid preceding straight.
|
||||||
|
longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
|
||||||
|
|
||||||
|
// If we've formed a sequence of {straight-size} or more cards, we have a straight.
|
||||||
|
if (longest_short_cut_at[i] >= get_straight_and_flush_size())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hand_contains_flush(u8* suits)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NUM_SUITS; i++)
|
||||||
|
{
|
||||||
|
if (suits[i] >= get_straight_and_flush_size())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of cards in the best flush found
|
||||||
|
// or 0 if no flush of min_len is found, and marks them in out_selection.
|
||||||
|
int find_flush_in_played_cards(CardObject** played, int top, int min_len, bool* out_selection)
|
||||||
|
{
|
||||||
|
if (top < 0)
|
||||||
|
return 0;
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
out_selection[i] = false;
|
||||||
|
|
||||||
|
int suit_counts[NUM_SUITS] = {0};
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
{
|
||||||
|
if (played[i] && played[i]->card)
|
||||||
|
{
|
||||||
|
suit_counts[played[i]->card->suit]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int best_suit = -1;
|
||||||
|
int best_count = 0;
|
||||||
|
for (int i = 0; i < NUM_SUITS; i++)
|
||||||
|
{
|
||||||
|
if (suit_counts[i] > best_count)
|
||||||
|
{
|
||||||
|
best_count = suit_counts[i];
|
||||||
|
best_suit = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_count >= min_len)
|
||||||
|
{
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
{
|
||||||
|
if (played[i] && played[i]->card && played[i]->card->suit == best_suit)
|
||||||
|
{
|
||||||
|
out_selection[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best_count;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (top < 0)
|
||||||
|
return 0;
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
out_selection[i] = false;
|
||||||
|
|
||||||
|
// --- Setup for Backtracking DP ---
|
||||||
|
u8 longest_straight_at[NUM_RANKS] = {0};
|
||||||
|
int parent[NUM_RANKS];
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
parent[i] = -1;
|
||||||
|
|
||||||
|
u8 ranks[NUM_RANKS] = {0};
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
{
|
||||||
|
if (played[i] && played[i]->card)
|
||||||
|
{
|
||||||
|
ranks[played[i]->card->rank]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Run DP to find longest straight ---
|
||||||
|
// This is nearly identical to hand_contains_straight() logic
|
||||||
|
// TODO: Consolidate functions to avoid code duplication?
|
||||||
|
// Might cost performance because this does a little more
|
||||||
|
int ace_low_len = ranks[ACE] ? 1 : 0;
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
{
|
||||||
|
if (ranks[i] > 0)
|
||||||
|
{
|
||||||
|
int prev1 = 0, prev2 = 0;
|
||||||
|
int parent1 = -1, parent2 = -1;
|
||||||
|
|
||||||
|
if (shortcut_active)
|
||||||
|
{
|
||||||
|
if (i == TWO)
|
||||||
|
{
|
||||||
|
prev1 = ace_low_len;
|
||||||
|
parent1 = ACE;
|
||||||
|
}
|
||||||
|
else if (i == THREE)
|
||||||
|
{
|
||||||
|
prev1 = longest_straight_at[TWO];
|
||||||
|
parent1 = TWO;
|
||||||
|
prev2 = ace_low_len;
|
||||||
|
parent2 = ACE;
|
||||||
|
}
|
||||||
|
else if (i == ACE)
|
||||||
|
{
|
||||||
|
prev1 = longest_straight_at[KING];
|
||||||
|
parent1 = KING;
|
||||||
|
prev2 = longest_straight_at[QUEEN];
|
||||||
|
parent2 = QUEEN;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prev1 = longest_straight_at[i - 1];
|
||||||
|
parent1 = i - 1;
|
||||||
|
if (i > 1)
|
||||||
|
{
|
||||||
|
prev2 = longest_straight_at[i - 2];
|
||||||
|
parent2 = i - 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (i == TWO)
|
||||||
|
{
|
||||||
|
prev1 = ace_low_len;
|
||||||
|
parent1 = ACE;
|
||||||
|
}
|
||||||
|
else if (i == ACE)
|
||||||
|
{
|
||||||
|
prev1 = longest_straight_at[KING];
|
||||||
|
parent1 = KING;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prev1 = longest_straight_at[i - 1];
|
||||||
|
parent1 = i - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallels longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
|
||||||
|
// in hand_contains_straight()
|
||||||
|
if (prev1 >= prev2)
|
||||||
|
{
|
||||||
|
longest_straight_at[i] = 1 + prev1;
|
||||||
|
parent[i] = parent1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
longest_straight_at[i] = 1 + prev2;
|
||||||
|
parent[i] = parent2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Find best straight and backtrack ---
|
||||||
|
int best_len = 0;
|
||||||
|
int end_rank = -1;
|
||||||
|
for (int i = 0; i < NUM_RANKS; i++)
|
||||||
|
{
|
||||||
|
if (longest_straight_at[i] >= best_len)
|
||||||
|
{
|
||||||
|
best_len = longest_straight_at[i];
|
||||||
|
end_rank = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_len >= min_len)
|
||||||
|
{
|
||||||
|
u8 needed_ranks[NUM_RANKS] = {0};
|
||||||
|
int current_rank = end_rank;
|
||||||
|
while (current_rank != -1 && best_len > 0)
|
||||||
|
{
|
||||||
|
needed_ranks[current_rank]++;
|
||||||
|
current_rank = parent[current_rank];
|
||||||
|
best_len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
{
|
||||||
|
if (played[i] && played[i]->card && needed_ranks[played[i]->card->rank] > 0)
|
||||||
|
{
|
||||||
|
out_selection[i] = true;
|
||||||
|
needed_ranks[played[i]->card->rank]--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int final_card_count = 0;
|
||||||
|
for (int i = 0; i <= top; i++)
|
||||||
|
{
|
||||||
|
if (out_selection[i])
|
||||||
|
final_card_count++;
|
||||||
|
}
|
||||||
|
return final_card_count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is used for the special case in "Four Fingers" where you can add a pair into a straight
|
||||||
|
// (e.g. AA234 should score all 5 cards)
|
||||||
|
void select_paired_cards_in_hand(CardObject** played, int played_top, bool* selection)
|
||||||
|
{
|
||||||
|
// Build a set of ranks that are already selected
|
||||||
|
bool rank_selected[NUM_RANKS] = {0};
|
||||||
|
bool any_selected_rank = false;
|
||||||
|
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
if (selection[i] && played[i] && played[i]->card)
|
||||||
|
{
|
||||||
|
rank_selected[played[i]->card->rank] = true;
|
||||||
|
any_selected_rank = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no ranks were selected initially, nothing to do
|
||||||
|
if (!any_selected_rank)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add any unselected card to the selection if if shares a rank with the selected ranks
|
||||||
|
for (int i = 0; i <= played_top; i++)
|
||||||
|
{
|
||||||
|
if (played[i] && played[i]->card && !selection[i])
|
||||||
|
{
|
||||||
|
if (rank_selected[played[i]->card->rank])
|
||||||
|
{
|
||||||
|
selection[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ContainedHandTypes compute_contained_hand_types(void)
|
||||||
|
{
|
||||||
|
ContainedHandTypes hand_types = {0};
|
||||||
|
|
||||||
|
// Idk if this is how Balatro does it but this is how I'm doing it
|
||||||
|
if (hand.hand_selections == 0 || hand.state == HAND_DISCARD)
|
||||||
|
{
|
||||||
|
return hand_types;
|
||||||
|
}
|
||||||
|
|
||||||
|
hand_types.HIGH_CARD = 1;
|
||||||
|
|
||||||
|
u8 suits[NUM_SUITS];
|
||||||
|
u8 ranks[NUM_RANKS];
|
||||||
|
get_hand_distribution(ranks, suits);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Pair and 2 Pair
|
||||||
|
if (n_of_a_kind >= 2)
|
||||||
|
{
|
||||||
|
hand_types.PAIR = 1;
|
||||||
|
|
||||||
|
if (hand_contains_two_pair(ranks))
|
||||||
|
{
|
||||||
|
hand_types.TWO_PAIR = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 OAK
|
||||||
|
if (n_of_a_kind >= 3)
|
||||||
|
{
|
||||||
|
hand_types.THREE_OF_A_KIND = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Straight
|
||||||
|
if (hand_contains_straight(ranks))
|
||||||
|
{
|
||||||
|
hand_types.STRAIGHT = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush
|
||||||
|
if (hand_contains_flush(suits))
|
||||||
|
{
|
||||||
|
hand_types.FLUSH = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full House
|
||||||
|
if (n_of_a_kind >= 3 && hand_contains_full_house(ranks))
|
||||||
|
{
|
||||||
|
hand_types.FULL_HOUSE = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 OAK
|
||||||
|
if (n_of_a_kind >= 4)
|
||||||
|
{
|
||||||
|
hand_types.FOUR_OF_A_KIND = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Straight Flush
|
||||||
|
if (hand_types.STRAIGHT && hand_types.FLUSH)
|
||||||
|
{
|
||||||
|
hand_types.STRAIGHT_FLUSH = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Royal Flush
|
||||||
|
if (hand_types.STRAIGHT_FLUSH)
|
||||||
|
{
|
||||||
|
if (ranks[TEN] && ranks[JACK] && ranks[QUEEN] && ranks[KING] && ranks[ACE])
|
||||||
|
{
|
||||||
|
hand_types.ROYAL_FLUSH = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5 OAK
|
||||||
|
if (n_of_a_kind >= 5)
|
||||||
|
{
|
||||||
|
hand_types.FIVE_OF_A_KIND = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush House and Five
|
||||||
|
if (hand_types.FLUSH)
|
||||||
|
{
|
||||||
|
if (hand_types.FULL_HOUSE)
|
||||||
|
{
|
||||||
|
hand_types.FLUSH_HOUSE = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hand_types.FIVE_OF_A_KIND)
|
||||||
|
{
|
||||||
|
hand_types.FLUSH_FIVE = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hand_types;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum HandType compute_hand_type(struct ContainedHandTypes contained_types)
|
||||||
|
{
|
||||||
|
enum HandType ret;
|
||||||
|
|
||||||
|
// test each pit see if it's set to 1, and return the first one
|
||||||
|
for (ret = FLUSH_FIVE; ret > NONE; ret--)
|
||||||
|
{
|
||||||
|
// Shift the bit we want to check to the front and mask it with 1 to keep only that
|
||||||
|
// Since the ContainedHandTypes is ordered the same way as the HandType enum, we
|
||||||
|
// can shift right by ret-1 to have the bit we want at the front
|
||||||
|
if ((contained_types.value >> (ret - 1)) & 0x1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we broke early, ret contains the value of the HandType enum corresponding to
|
||||||
|
// the position of the highest bit set to 1 in contained_types.value, which is the
|
||||||
|
// most powerful poker hand contained in the current Hand
|
||||||
|
// If not, then it contains NONE, which is what we're supposed to return when there
|
||||||
|
// are no Hands contained in what we played
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -1,456 +0,0 @@
|
|||||||
#include "hand_analysis.h"
|
|
||||||
|
|
||||||
#include "card.h"
|
|
||||||
#include "game.h"
|
|
||||||
|
|
||||||
void get_hand_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
|
|
||||||
{
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
ranks_out[i] = 0;
|
|
||||||
for (int i = 0; i < NUM_SUITS; i++)
|
|
||||||
suits_out[i] = 0;
|
|
||||||
|
|
||||||
CardObject** cards = get_hand_array();
|
|
||||||
int top = get_hand_top();
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
{
|
|
||||||
if (cards[i] && card_object_is_selected(cards[i]))
|
|
||||||
{
|
|
||||||
ranks_out[cards[i]->card->rank]++;
|
|
||||||
suits_out[cards[i]->card->suit]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void get_played_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
|
|
||||||
{
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
ranks_out[i] = 0;
|
|
||||||
for (int i = 0; i < NUM_SUITS; i++)
|
|
||||||
suits_out[i] = 0;
|
|
||||||
|
|
||||||
CardObject** played = get_played_array();
|
|
||||||
int top = get_played_top();
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
{
|
|
||||||
/* The difference from get_hand_distribution() (not checking if card is selected)
|
|
||||||
* is in line Balatro behavior,
|
|
||||||
* see https://github.com/GBALATRO/balatro-gba/issues/341#issuecomment-3691363488
|
|
||||||
*/
|
|
||||||
if (!played[i])
|
|
||||||
continue;
|
|
||||||
ranks_out[played[i]->card->rank]++;
|
|
||||||
suits_out[played[i]->card->suit]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the highest N of a kind. So a full-house would return 3.
|
|
||||||
u8 hand_contains_n_of_a_kind(u8* ranks)
|
|
||||||
{
|
|
||||||
u8 highest_n = 0;
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
{
|
|
||||||
if (ranks[i] > highest_n)
|
|
||||||
highest_n = ranks[i];
|
|
||||||
}
|
|
||||||
return highest_n;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hand_contains_two_pair(u8* ranks)
|
|
||||||
{
|
|
||||||
bool contains_other_pair = false;
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
{
|
|
||||||
if (ranks[i] >= 2)
|
|
||||||
{
|
|
||||||
if (contains_other_pair)
|
|
||||||
return true;
|
|
||||||
contains_other_pair = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hand_contains_full_house(u8* ranks)
|
|
||||||
{
|
|
||||||
int count_three = 0;
|
|
||||||
int count_pair = 0;
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
{
|
|
||||||
if (ranks[i] >= 3)
|
|
||||||
{
|
|
||||||
count_three++;
|
|
||||||
}
|
|
||||||
else if (ranks[i] >= 2)
|
|
||||||
{
|
|
||||||
count_pair++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Full house if there is:
|
|
||||||
// - at least one three-of-a-kind and at least one other pair,
|
|
||||||
// - OR at least two three-of-a-kinds (second "three" acts as pair).
|
|
||||||
// This accounts for hands with 6 or more cards even though
|
|
||||||
// they are currently not possible and probably never will be.
|
|
||||||
return (count_three >= 2 || (count_three && count_pair));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is mostly from Google Gemini
|
|
||||||
bool hand_contains_straight(u8* ranks)
|
|
||||||
{
|
|
||||||
if (!is_shortcut_joker_active())
|
|
||||||
{
|
|
||||||
int straight_size = get_straight_and_flush_size();
|
|
||||||
// This is the regular case of detecting straights
|
|
||||||
int run = 0;
|
|
||||||
for (int i = 0; i < NUM_RANKS; ++i)
|
|
||||||
{
|
|
||||||
if (ranks[i])
|
|
||||||
{
|
|
||||||
if (++run >= straight_size)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
run = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for ace low straight
|
|
||||||
if (straight_size >= 2 && ranks[ACE])
|
|
||||||
{
|
|
||||||
// With A as low, the highest rank you can use is FIVE.
|
|
||||||
// -1 for inclusive integer distance and another -1 for the Ace e.g. need=5 -> need 2..5
|
|
||||||
int last_needed = TWO + (straight_size - 2);
|
|
||||||
if (last_needed <= FIVE)
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
for (int r = TWO; r <= last_needed; ++r)
|
|
||||||
{
|
|
||||||
if (!ranks[r])
|
|
||||||
{
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ok)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Shortcut Joker is active, we have to detect straights where any card may "skip" 1 rank
|
|
||||||
// We do this with a dynamic programming algorithm that calculates
|
|
||||||
// the longest possible straight that can end on each rank
|
|
||||||
// and stopping when we find one that is {straight-size} cards long
|
|
||||||
u8 longest_short_cut_at[NUM_RANKS] = {0};
|
|
||||||
|
|
||||||
// A low ace can start a sequence. 'ace_low_len' is 1 if an ace is present,
|
|
||||||
// acting as a potential predecessor for TWO and THREE.
|
|
||||||
int ace_low_len = ranks[ACE] ? 1 : 0;
|
|
||||||
|
|
||||||
// Iterate through all ranks from TWO up to ACE.
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
{
|
|
||||||
// No cards in this rank, no straight can end here, continue
|
|
||||||
if (ranks[i] == 0)
|
|
||||||
{
|
|
||||||
longest_short_cut_at[i] = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int prev_len1 = 0;
|
|
||||||
int prev_len2 = 0;
|
|
||||||
|
|
||||||
// This logic handles the special connections for ace-low straights.
|
|
||||||
if (i == TWO)
|
|
||||||
{
|
|
||||||
// A TWO can be preceded by a low ACE (no skip).
|
|
||||||
prev_len1 = ace_low_len;
|
|
||||||
}
|
|
||||||
else if (i == THREE)
|
|
||||||
{
|
|
||||||
// A THREE can be preceded by a TWO (no skip) or a low ACE (skip).
|
|
||||||
prev_len1 = longest_short_cut_at[TWO];
|
|
||||||
prev_len2 = ace_low_len;
|
|
||||||
}
|
|
||||||
else if (i == ACE)
|
|
||||||
{
|
|
||||||
// An ACE (as the highest card) can be preceded by a KING or a QUEEN.
|
|
||||||
prev_len1 = longest_short_cut_at[KING];
|
|
||||||
prev_len2 = longest_short_cut_at[QUEEN];
|
|
||||||
}
|
|
||||||
else // For all other cards (FOUR through KING).
|
|
||||||
{
|
|
||||||
// A card can be preceded by the rank directly below or two ranks below.
|
|
||||||
prev_len1 = longest_short_cut_at[i - 1];
|
|
||||||
prev_len2 = longest_short_cut_at[i - 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
// The length of the straight ending at rank 'i' is 1 (for the card itself)
|
|
||||||
// plus the length of the longest valid preceding straight.
|
|
||||||
longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
|
|
||||||
|
|
||||||
// If we've formed a sequence of {straight-size} or more cards, we have a straight.
|
|
||||||
if (longest_short_cut_at[i] >= get_straight_and_flush_size())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hand_contains_flush(u8* suits)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < NUM_SUITS; i++)
|
|
||||||
{
|
|
||||||
if (suits[i] >= get_straight_and_flush_size())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the number of cards in the best flush found
|
|
||||||
// or 0 if no flush of min_len is found, and marks them in out_selection.
|
|
||||||
/**
|
|
||||||
* Finds the largest flush (set of cards with the same suit) in the given array of played cards.
|
|
||||||
* Marks the cards belonging to the best flush in the out_selection array.
|
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
* @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)
|
|
||||||
{
|
|
||||||
if (top < 0)
|
|
||||||
return 0;
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
out_selection[i] = false;
|
|
||||||
|
|
||||||
int suit_counts[NUM_SUITS] = {0};
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
{
|
|
||||||
if (played[i] && played[i]->card)
|
|
||||||
{
|
|
||||||
suit_counts[played[i]->card->suit]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int best_suit = -1;
|
|
||||||
int best_count = 0;
|
|
||||||
for (int i = 0; i < NUM_SUITS; i++)
|
|
||||||
{
|
|
||||||
if (suit_counts[i] > best_count)
|
|
||||||
{
|
|
||||||
best_count = suit_counts[i];
|
|
||||||
best_suit = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (best_count >= min_len)
|
|
||||||
{
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
{
|
|
||||||
if (played[i] && played[i]->card && played[i]->card->suit == best_suit)
|
|
||||||
{
|
|
||||||
out_selection[i] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best_count;
|
|
||||||
}
|
|
||||||
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
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (top < 0)
|
|
||||||
return 0;
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
out_selection[i] = false;
|
|
||||||
|
|
||||||
// --- Setup for Backtracking DP ---
|
|
||||||
u8 longest_straight_at[NUM_RANKS] = {0};
|
|
||||||
int parent[NUM_RANKS];
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
parent[i] = -1;
|
|
||||||
|
|
||||||
u8 ranks[NUM_RANKS] = {0};
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
{
|
|
||||||
if (played[i] && played[i]->card)
|
|
||||||
{
|
|
||||||
ranks[played[i]->card->rank]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Run DP to find longest straight ---
|
|
||||||
// This is nearly identical to hand_contains_straight() logic
|
|
||||||
// TODO: Consolidate functions to avoid code duplication?
|
|
||||||
// Might cost performance because this does a little more
|
|
||||||
int ace_low_len = ranks[ACE] ? 1 : 0;
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
{
|
|
||||||
if (ranks[i] > 0)
|
|
||||||
{
|
|
||||||
int prev1 = 0, prev2 = 0;
|
|
||||||
int parent1 = -1, parent2 = -1;
|
|
||||||
|
|
||||||
if (shortcut_active)
|
|
||||||
{
|
|
||||||
if (i == TWO)
|
|
||||||
{
|
|
||||||
prev1 = ace_low_len;
|
|
||||||
parent1 = ACE;
|
|
||||||
}
|
|
||||||
else if (i == THREE)
|
|
||||||
{
|
|
||||||
prev1 = longest_straight_at[TWO];
|
|
||||||
parent1 = TWO;
|
|
||||||
prev2 = ace_low_len;
|
|
||||||
parent2 = ACE;
|
|
||||||
}
|
|
||||||
else if (i == ACE)
|
|
||||||
{
|
|
||||||
prev1 = longest_straight_at[KING];
|
|
||||||
parent1 = KING;
|
|
||||||
prev2 = longest_straight_at[QUEEN];
|
|
||||||
parent2 = QUEEN;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
prev1 = longest_straight_at[i - 1];
|
|
||||||
parent1 = i - 1;
|
|
||||||
if (i > 1)
|
|
||||||
{
|
|
||||||
prev2 = longest_straight_at[i - 2];
|
|
||||||
parent2 = i - 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (i == TWO)
|
|
||||||
{
|
|
||||||
prev1 = ace_low_len;
|
|
||||||
parent1 = ACE;
|
|
||||||
}
|
|
||||||
else if (i == ACE)
|
|
||||||
{
|
|
||||||
prev1 = longest_straight_at[KING];
|
|
||||||
parent1 = KING;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
prev1 = longest_straight_at[i - 1];
|
|
||||||
parent1 = i - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parallels longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
|
|
||||||
// in hand_contains_straight()
|
|
||||||
if (prev1 >= prev2)
|
|
||||||
{
|
|
||||||
longest_straight_at[i] = 1 + prev1;
|
|
||||||
parent[i] = parent1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
longest_straight_at[i] = 1 + prev2;
|
|
||||||
parent[i] = parent2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Find best straight and backtrack ---
|
|
||||||
int best_len = 0;
|
|
||||||
int end_rank = -1;
|
|
||||||
for (int i = 0; i < NUM_RANKS; i++)
|
|
||||||
{
|
|
||||||
if (longest_straight_at[i] >= best_len)
|
|
||||||
{
|
|
||||||
best_len = longest_straight_at[i];
|
|
||||||
end_rank = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (best_len >= min_len)
|
|
||||||
{
|
|
||||||
u8 needed_ranks[NUM_RANKS] = {0};
|
|
||||||
int current_rank = end_rank;
|
|
||||||
while (current_rank != -1 && best_len > 0)
|
|
||||||
{
|
|
||||||
needed_ranks[current_rank]++;
|
|
||||||
current_rank = parent[current_rank];
|
|
||||||
best_len--;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
{
|
|
||||||
if (played[i] && played[i]->card && needed_ranks[played[i]->card->rank] > 0)
|
|
||||||
{
|
|
||||||
out_selection[i] = true;
|
|
||||||
needed_ranks[played[i]->card->rank]--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int final_card_count = 0;
|
|
||||||
for (int i = 0; i <= top; i++)
|
|
||||||
{
|
|
||||||
if (out_selection[i])
|
|
||||||
final_card_count++;
|
|
||||||
}
|
|
||||||
return final_card_count;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is used for the special case in "Four Fingers" where you can add a pair into a straight
|
|
||||||
// (e.g. AA234 should score all 5 cards)
|
|
||||||
void select_paired_cards_in_hand(CardObject** played, int played_top, bool* selection)
|
|
||||||
{
|
|
||||||
// Build a set of ranks that are already selected
|
|
||||||
bool rank_selected[NUM_RANKS] = {0};
|
|
||||||
bool any_selected_rank = false;
|
|
||||||
|
|
||||||
for (int i = 0; i <= played_top; i++)
|
|
||||||
{
|
|
||||||
if (selection[i] && played[i] && played[i]->card)
|
|
||||||
{
|
|
||||||
rank_selected[played[i]->card->rank] = true;
|
|
||||||
any_selected_rank = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no ranks were selected initially, nothing to do
|
|
||||||
if (!any_selected_rank)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Add any unselected card to the selection if if shares a rank with the selected ranks
|
|
||||||
for (int i = 0; i <= played_top; i++)
|
|
||||||
{
|
|
||||||
if (played[i] && played[i]->card && !selection[i])
|
|
||||||
{
|
|
||||||
if (rank_selected[played[i]->card->rank])
|
|
||||||
{
|
|
||||||
selection[i] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "game_variables.h"
|
#include "game_variables.h"
|
||||||
#include "hand_analysis.h"
|
#include "hand.h"
|
||||||
#include "joker.h"
|
#include "joker.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "pool.h"
|
#include "pool.h"
|
||||||
@@ -678,8 +678,7 @@ static u32 blackboard_joker_effect(
|
|||||||
|
|
||||||
bool all_cards_are_spades_or_clubs = true;
|
bool all_cards_are_spades_or_clubs = true;
|
||||||
CardObject** hand = get_hand_array();
|
CardObject** hand = get_hand_array();
|
||||||
int hand_size = hand_get_size();
|
for (int i = 0; i < g_game_vars.hand_size; i++)
|
||||||
for (int i = 0; i < hand_size; i++)
|
|
||||||
{
|
{
|
||||||
u8 suit = hand[i]->card->suit;
|
u8 suit = hand[i]->card->suit;
|
||||||
if (suit == HEARTS || suit == DIAMONDS)
|
if (suit == HEARTS || suit == DIAMONDS)
|
||||||
@@ -737,8 +736,7 @@ static u32 raised_fist_joker_effect(
|
|||||||
*p_lowest_value_index = 0;
|
*p_lowest_value_index = 0;
|
||||||
u8 lowest_value = IMPOSSIBLY_HIGH_CARD_VALUE;
|
u8 lowest_value = IMPOSSIBLY_HIGH_CARD_VALUE;
|
||||||
CardObject** hand = get_hand_array();
|
CardObject** hand = get_hand_array();
|
||||||
int hand_size = hand_get_size();
|
for (int i = 0; i < g_game_vars.hand_size; i++)
|
||||||
for (int i = 0; i < hand_size; i++)
|
|
||||||
{
|
{
|
||||||
u8 value = card_get_value(hand[i]->card);
|
u8 value = card_get_value(hand[i]->card);
|
||||||
if (lowest_value > value)
|
if (lowest_value > value)
|
||||||
|
|||||||
Reference in New Issue
Block a user