[Feature] Implement seeded runs by adding a Seed selection menu (#482)

* initial commit

* wip

* WIP

* WIP

* clang format

* Choose Seed navigation mostly done

* seed input navigation done

* seed input done

* cleanup and clang format

* Implement base 10 to 36 conversions

* Enable seeded runs

* clang format

* show default deck sprite, name and description

* Rename Deck enum to DeckType

* Implement arbitrary 9-patch expand system

* Make 9-patch example smaller in the docs

* Improve NinePatchRect docs

* fix Deck back side sprite palette

* clang format

* Add last deck description

* clang format

* Fix 'Z' in seed input keyboard

* Fix rebase issue

* fix deck sprite not updating

* Documents static funcs in run_setup.c

* more documentation

* clang format

* Move Play button to the left in its row

* Tests WIP

* removing tests for random.h

* use the new StateMachine struct

* First wave of fixes

* Fix the 9-patch again

* Changed the base 36 logic so it's in big endian

* properly set end of base 36 string to '\0'

* typos

* fix cursor position after rolling a seed + fix random seed generation

* Make keyboard more intuitive to use

* static const var

* Address more comment from @ricfehr3

* clang format

* clang format

* clang format

* clang format

* clang format

* clang format

* clang format

* fix deck palette

* create the `DEF_SEED_KEYBOARD_BUTTON_OBJECT` macro

* Replace `expand_3x3` by a call to `expand_9-patch`

* clang format

* clang format

* clang format

* fix rebase iussue in state_machine.h

---------

Co-authored-by: MathisMartin31 <mathis.martin31@gmail.com>
This commit is contained in:
Geralt
2026-06-02 00:06:37 -07:00
committed by GitHub
co-authored by MathisMartin31
parent 21d8d912ab
commit 1df8402900
32 changed files with 2161 additions and 123 deletions
+4
View File
@@ -1,6 +1,7 @@
#ifndef CARD_H
#define CARD_H
#include "deck_types.h"
#include "sprite.h"
#include <maxmod.h>
@@ -58,6 +59,8 @@ typedef struct CardObject
bool selected;
} CardObject;
void card_init(void);
// Card sprites accessibility functions
void set_cards_high_contrast(bool enable);
void set_cards_more_readable(bool enable);
@@ -74,6 +77,7 @@ CardObject* card_object_new(Card* card);
void card_object_destroy(CardObject** card_object);
void card_object_update(CardObject* card_object); // Update the card object position and scale
void card_object_set_sprite(CardObject* card_object, int layer);
void card_object_set_sprite_face_down(CardObject* card_object, enum DeckType deck, int layer);
void card_object_shake(CardObject* card_object, mm_word sound_id);
void card_object_set_selected(CardObject* card_object, bool selected);
+45
View File
@@ -0,0 +1,45 @@
/**
* @file deck_types.h
*
* @brief Functions related to the different types of Decks.
*/
#ifndef DECK_TYPES_H
#define DECK_TYPES_H
#include "graphic_utils.h"
#define DECK_SPRITES_PB 1
#define DECK_NAME_LENGTH 14 // 13 chars + '\0'
#define DECK_DESC_WIDTH DECK_NAME_LENGTH
#define DECK_DESC_HEIGHT 5
enum DeckType
{
DECK_TYPE_RED,
DECK_TYPE_BLUE,
DECK_TYPE_YELLOW,
DECK_TYPE_GREEN,
DECK_TYPE_BLACK,
DECK_TYPE_PAINTED,
DECK_TYPE_MAX
};
/**
* @brief Prints the Deck name string to the screen at the given position.
*
* @param deck Decktype we want the name of.
* @param pos screen position in pixels to print the name at.
*/
void print_deck_name(enum DeckType deck, BG_POINT pos);
/**
* @brief Prints the Deck description string to the screen at the given position.
*
* @param deck DeckType we want the description of.
* @param pos screen position in pixels of the top-left corner of the rectangle
* we'll print the Deck description into
*/
void print_deck_description(enum DeckType deck, BG_POINT pos);
#endif // DECK_TYPES_H
+1
View File
@@ -3,6 +3,7 @@
DEF_STATE_INFO(GAME_STATE_SPLASH_SCREEN, splash_screen_on_init, splash_screen_on_update, splash_screen_on_exit )
DEF_STATE_INFO(GAME_STATE_MAIN_MENU, game_main_menu_on_init, game_main_menu_on_update, game_main_menu_on_exit )
DEF_STATE_INFO(GAME_STATE_OPTIONS_MENU, game_options_menu_on_init, game_options_menu_on_update, game_options_menu_on_exit)
DEF_STATE_INFO(GAME_STATE_RUN_SETUP , game_run_setup_on_init, game_run_setup_on_update, game_run_setup_on_exit )
DEF_STATE_INFO(GAME_STATE_GAME_START, noop, game_start, noop )
DEF_STATE_INFO(GAME_STATE_PLAYING, game_round_on_init, game_playing_on_update, noop )
DEF_STATE_INFO(GAME_STATE_ROUND_END, game_round_end_on_init, game_round_end_on_update, game_round_end_on_exit )
+2
View File
@@ -24,6 +24,8 @@
#define SORT_HAND KEY_R
#define PAUSE_GAME KEY_START // Not implemented
#define SELL_KEY KEY_L
#define TAB_LEFT KEY_L
#define TAB_RIGHT KEY_R
// Matching the position of the on-screen buttons
#define PLAY_HAND_KEY KEY_L
+1
View File
@@ -19,6 +19,7 @@ enum BackgroundId
BG_ROUND_END,
BG_SHOP,
BG_BLIND_SELECT,
BG_RUN_SETUP,
BG_OPTIONS_MENU,
BG_MAIN_MENU
};
+29
View File
@@ -0,0 +1,29 @@
/**
* @file run_setup.h
*
* @brief Setup Run menu state functions.
*/
#ifndef GAME_RUN_SETUP_H
#define GAME_RUN_SETUP_H
/**
* @brief Change to the Run Setup menu background
*/
void game_run_setup_change_background(void);
/**
* @brief Run Setup menu state initialization
*/
void game_run_setup_on_init(void);
/**
* @brief Run Setup menu state update
*/
void game_run_setup_on_update(void);
/**
* @brief Run Setup menu cleanup
*/
void game_run_setup_on_exit(void);
#endif // GAME_RUN_SETUP_H
+1
View File
@@ -47,6 +47,7 @@ typedef struct
s32 ante;
s32 money;
s32 hand_size;
s32 deck;
// Blind variables
+61 -1
View File
@@ -63,6 +63,9 @@
/** @def TTE_BIT_ON_CLR_IDX */
#define TTE_BIT_ON_CLR_IDX TTE_BIT_UNPACK_OFFSET + 1
/** @def TTE_BLACK_PB */
#define TTE_BLACK_PB 0 // 0x0
/** @def TTE_YELLOW_PB */
#define TTE_YELLOW_PB 12 // 0xC
@@ -149,6 +152,37 @@ enum OverflowDir
// When making this, missed that it already exists in tonc_math.h
typedef RECT Rect;
// clang-format off
/**
* @brief Structure to represent arbitrary-sized 9-patches.
*
* While expanding a 3x3 rect is simple enough and has its purpose, this is an attempt
* to generalize the idea to a struct representing a 9-patch like this, such that areas
* 1, 2, 3 and 4 have no null dimensions and don't overlap:
* ```
* margins.left margins.right │ Such as:
* ╭───┴───╮ ╭─┴─╮ │
* ╔═══════╤═════╤═══╗ ╮ │ 1) margins.left + margins.right <= width (patch_rect)
* ║ 1 │ 6 │ 2 ║ ├─ margins.top │
* ╟───────┼─────┼───╢ ╯ │ 2) margins.top + margins.bottom <= height(patch_rect)
* ║ 5 │ 0 │ 7 ║ │
* ║ │ │ ║ │ 3) margins values are inclusive, in number of tiles
* ╟───────┼─────┼───╢ ╮ │
* ║ │ │ ║ │ │
* ║ 4 │ 8 │ 3 ║ ├─ margins.bottom │
* ║ │ │ ║ │ │
* ╚═══════╧═════╧═══╝ ╯ │
* ╰────────┬────────╯ │
* patch_rect │
* ```
*/
// clang-format on
typedef struct NinePatchRect
{
Rect patch_rect;
Rect margins;
} NinePatchRect;
/**
* @brief Get the width of a rectangle
*
@@ -218,7 +252,33 @@ void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction
void main_bg_se_copy_rect(Rect se_rect, BG_POINT dest_pos);
/**
* @brief Copies a 3x3 rect and expands it to fit a passed rect.
* @brief Copies a tile in the main background at se_tile_src to fill se_rect_dest.
*
* @param se_rect_dest dimensions are in number of tiles.
* @param se_tile_src x and y are the coordinates in number of tiles.
*/
void main_bg_se_copy_expand_tile(Rect se_rect_dest, BG_POINT se_tile_src);
/**
* @brief Copies a 9-patch and expands it to fit a passed rect.
*
* Performs the following operation:
*
* 1. The corners are copied
* 2. The sides are stretched
* 3. The center is stretched to fill `se_rect_dest` minus the patch's margins.
*
* @param se_rect_dest Destination for the 9-patch copy. Needs to be at least large enough to fit
* the corners and sides of the patch, (`margins.left + margins.right` by
* `margins.top + margins.bottom`) in which case only the corners are copied.
* @param src_9_ptch Pointer to the properties of the 9-patch we want to stretch.
*
* @sa NinePatchRect
*/
void main_bg_se_copy_expand_9_patch(Rect se_dest_rect, const NinePatchRect* src_9_ptch);
/**
* @brief Copies a 3x3 rect and expands it to fit a passed rect. Special case of the 9-patch.
*
* Performs the following operation:
*
+1 -1
View File
@@ -18,7 +18,7 @@
#define JOKER_STARTING_LAYER 26
// Tile ID for the starting index in the tile memory
#define JOKER_TID (JOKER_STARTING_LAYER * JOKER_SPRITE_OFFSET)
#define JOKER_BASE_PB 3 // The starting palette index for the jokers
#define JOKER_BASE_PB 4 // The starting palette index for the jokers, after the boss blind tokens
#define JOKER_LAST_PB (NUM_PALETTES - 1)
// Currently allocating the rest of the palettes for the jokers.
// This number needs to be decreased once we need to allocated palettes for other sprites
+1 -6
View File
@@ -12,11 +12,6 @@
#include <tonc.h>
/**
* @brief Hex value of "ZZZZZZ" in base 36
*/
#define MAX_SEED 0x81BF0FFF
/**
* @brief Information to track and restore RNG state
*/
@@ -41,7 +36,7 @@ void rng_update(void);
/**
* @brief Set the rng seed to the chosen value, and reset the step counter to 0.
* The seed will be capped at `MAX_SEED` for compatibility with the Seed Input screen
* The seed will be capped at `MAX_BASE36` for compatibility with the Seed Input screen
* used to choose a seed for seeded runs.
*
* @param seed the new RNG seed
+7
View File
@@ -31,6 +31,13 @@
#include <tonc.h>
/**
* @brief Checks whether the SaveGame section is present and valid.
*
* @returns true if it is, false if not.
*/
bool is_game_data_valid(void);
/**
* @brief Save current run data to SRAM.
*/
+2 -3
View File
@@ -106,15 +106,14 @@ void state_machine_change_state(StateMachine* state_machine, int new_state);
void noop(void);
// clang-format off
#define STATE_INFO_UPDATE_FN_ONLY(fn) {.on_init = noop, .on_update = fn, .on_exit = noop}
#define STATE_INFO_UPDATE_FN_ONLY(fn) {.on_init = noop, .on_update = fn, .on_exit = noop}
#define STATE_INFO_INIT_UPDATE_FN(init_fn, update_fn) {.on_init = init_fn, .on_update = update_fn, .on_exit = noop}
#define STATE_MACHINE_DEFINE(infos, num) \
{ \
.state_infos = &infos[0], \
.num_infos = num, \
.registered = false, \
};
// clang-format on
#endif // STATE_MACHINE_H
+52 -3
View File
@@ -17,6 +17,12 @@
#define UNDEFINED -1
/**
* @def MAX_BASE36
* @brief Hex value of "ZZZZZZ" in base 36
*/
#define MAX_BASE36 0x81BF0FFF
/**
* @def SIGN
* @brief Get the sign (signum) of an integer
@@ -33,9 +39,10 @@
*/
#define NUM_ELEM_IN_ARR(arr) (sizeof(arr) / sizeof((arr)[0]))
#define INT_MAX_DIGITS 11 // strlen(str(INT_MAX)) = strlen("-2147483647")
#define UINT_MAX_DIGITS 10 // strlen(str(UINT32_MAX)) = strlen("4294967295")
#define UINT8_MAX_DIGITS 3 // strlen(str(UINT8_MAX)) = strlen("255")
#define INT_MAX_DIGITS 11 // strlen(str(INT_MAX)) = strlen("-2147483647")
#define UINT_MAX_DIGITS 10 // strlen(str(UINT32_MAX)) = strlen("4294967295")
#define UINT8_MAX_DIGITS 3 // strlen(str(UINT8_MAX)) = strlen("255")
#define BASE36_MAX_DIGITS 6 // strlen("ZZZZZZ")
#define ONE_K 1000
#define ONE_M 1000000
@@ -150,4 +157,46 @@ static inline int u32_get_digits(uint32_t n)
return 10;
}
/**
* @brief Convert a base-36 string representation to a 32-bit unsigned integer.
* Since we are dealing with base-36 instead of decimal, the 32-bit decimal
* value of a base-36 string representation `b36` is equal to:
*
* \f( b36[0] * 36^0 + b36[1] * 36^1 + b36[2] * 36^2 ... \f)
*
* @param b36_str input char[] to convert to decimal, must be of size `BASE36_MAX_DIGITS+1`
*
* @returns the 32-bit unsigned value of `b36_str`
*/
uint32_t base36_to_u32(const char b36_str[]);
/**
* @brief Convert a 32-bit unsigned integer to its base-36 string representation.
* This will perform 6 divisions, so it will be significantly more expensive
* than its `base36_to_u32` counterpart.
*
* We will iterate over all digits from `BASE36_MAX_DIGITS-1` to 0 and determine
* their values in base-36, to then construct the string representation `b36_str`
* in base-36 or the integer `n`
*
* Initially set to `n`, the variable `acc` will contain any given stage `i`:
* ```
* b32[i] * 36^i + b32[i-1] * 36^(i-1) + ... + b32[0]
* ```
*
* And we can thus extract the two following values:
* ```
* b32[i] = acc / 36^i
* acc = acc mod 36^i = b32[i-1] * 36^(i-1) + ... + b32[0]
* ```
*
* So that acc can now be used for the following step, until `i` hits 0
*
* @param n integer value to convert to a base-36 representation
* @param b36_str output char[], representation of `n` in base-36
*
* @sa base36_to_u32
*/
void u32_to_base36(uint32_t n, char b36_str[]);
#endif // UTIL_H