Refactor/RNG system (#476)

* Extract rng manipulation to its own file

* clang format

* Get CPU cycles since game start to use for RNG

* change a few things

* clang format

* Implement suggestions from @ricfehr3

* clang format

* Fix rebase issue

* clang format

* Create RngInfo struct to hold seed and step

* clang format

* Make a note about rng_restore system not having been tested properly yet

* Apply suggestions from @ricfehr3

Co-authored-by: Rickey <ric@rf3.xyz>

* clang format

---------

Co-authored-by: MathisMartin31 <mathis.martin31@gmail.com>
Co-authored-by: Rickey <ric@rf3.xyz>
This commit is contained in:
Geralt
2026-05-21 19:47:48 -07:00
committed by GitHub
co-authored by Rickey MathisMartin31
parent e3f680d6d0
commit c2844a5d32
14 changed files with 162 additions and 57 deletions
-2
View File
@@ -175,8 +175,6 @@ void display_mult(void);
void display_money(void);
void set_retrigger(bool new_retrigger);
u32 get_rand(void);
// joker specific functions
bool is_shortcut_joker_active(void);
int get_straight_and_flush_size(void);
+2 -2
View File
@@ -7,6 +7,7 @@
#define GAME_VARIABLES_H
#include "blind.h"
#include "random.h"
#include <tonc.h>
@@ -37,8 +38,7 @@ typedef struct
// Internal variables
s32 timer; // This might already exist in libtonc but idk so i'm just making my own
u32 rng_seed;
u32 rng_step; // Position in the rng sequence.
RngInfo rng_info;
// Variables visible by the player
+76
View File
@@ -0,0 +1,76 @@
/**
* @file random.h
*
* @brief Common functions to handle RNG manipulation. Using this interface has three goals:
* 1. Make RNG repeatable so that people can share seeds and saves, and end up with the same run
* 2. Prevent the players from engaging in save-scumming since the pseudo-RNG is deterministic
* 3. Accomodate for base-36 seeds, so that we can freely chose one with alpha-numeric characters in
* the seed screen input
*/
#ifndef RANDOM_H
#define RANDOM_H
#include <tonc.h>
/**
* @brief Hex value of "ZZZZZZ" in base 36
*/
#define MAX_SEED 0x81BF0FFF
/**
* @brief Information to track and restore RNG state
*/
typedef struct
{
/** Initial seed */
u32 seed;
/** Position in the rng sequence. */
u32 step;
} RngInfo;
/**
* @brief Starts counting CPU cycles, this will be used by rng_shuffle_seed to
* generate a more random seed. To be called once on game start.
*/
void rng_init(void);
/**
* @brief Update the CPU cycles counter and accumulate it into a bigger u32 `timer_acc` variable.
*/
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
* used to choose a seed for seeded runs.
*
* @param seed the new RNG seed
*/
void rng_set_seed(u32 seed);
/**
* @brief Uses the CPU cycles counter to randomize the RNG seed as much as possible.
* This will be called by the main menu and the game over screens so that
* the next run's seed isn't the same as the last's.
* rng_start_sampling needs to have been called, and will stop the profiling.
*/
void rng_shuffle_seed(void);
/**
* @brief Get the next "randomly" generated number in the sequence from the current seed.
*
* @return u32
*/
u32 rng_get_u32(void);
/**
* @brief Restore RNG info struct in the GameVariables. Sets the `seed` and seeks the
* position `step` in the rng sequence.
*
* @param info RngInfo struct applied
*
* @note Consistency of the RNG after reloading the RngInfo struct has yet to be tested properly
*/
void rng_restore(RngInfo info);
#endif // RANDOM_H
+2 -1
View File
@@ -4,6 +4,7 @@
#include "game.h"
#include "graphic_utils.h"
#include "list.h"
#include "random.h"
#include "stdbool.h"
#include "util.h"
@@ -139,7 +140,7 @@ enum BlindType roll_blind_type(bool showdown)
}
// roll a random blind among the unbeaten ones
int random_blind_idx = get_rand() % list_get_len(p_unbeaten_blinds);
int random_blind_idx = rng_get_u32() % list_get_len(p_unbeaten_blinds);
Blind* random_blind = list_get_at_idx(p_unbeaten_blinds, random_blind_idx);
return random_blind->type;
+9 -20
View File
@@ -21,6 +21,7 @@
#include "joker.h"
#include "layout.h"
#include "list.h"
#include "random.h"
#include "save.h"
#include "selection_grid.h"
#include "soundbank.h"
@@ -299,7 +300,8 @@ static ContainedHandTypes _contained_hands = {0};
// Initialization of the global vars
// clang-format off
GameVariables g_game_vars = {
.timer = 0, .rng_seed = 0, .rng_step = 0,
.timer = 0,
.rng_info = {0, 0},
.round = 0, .ante = 0, .money = 0,
@@ -629,6 +631,8 @@ static inline void jokers_update_loop(void)
void game_update()
{
rng_update();
g_game_vars.timer++;
jokers_update_loop();
@@ -654,12 +658,6 @@ void game_change_state(enum GameState new_game_state)
}
}
u32 get_rand()
{
g_game_vars.rng_step++;
return rand();
}
CardObject** get_hand_array(void)
{
return hand;
@@ -1429,7 +1427,7 @@ static inline void deck_shuffle(void)
{
for (int i = deck_top; i > 0; i--)
{
int j = get_rand() % (i + 1);
int j = rng_get_u32() % (i + 1);
Card* temp = deck[i];
deck[i] = deck[j];
deck[j] = temp;
@@ -1505,14 +1503,6 @@ static void game_round_on_init(void)
game_playing_selection_grid.selection = GAME_PLAYING_INIT_SEL;
}
// General functions
static inline void set_seed(int seed)
{
srand(seed);
g_game_vars.rng_seed = seed;
g_game_vars.rng_step = 0;
}
// Playing state functions
static void game_playing_discard_on_pressed(void)
{
@@ -1670,7 +1660,7 @@ static bool game_playing_hand_row_on_selection_changed(
*/
play_sfx(
SFX_CARD_FOCUS,
MM_BASE_PITCH_RATE + get_rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
MM_BASE_PITCH_RATE + rng_get_u32() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
SFX_DEFAULT_VOLUME
);
}
@@ -1690,7 +1680,7 @@ static bool game_playing_hand_row_on_selection_changed(
*/
play_sfx(
SFX_CARD_FOCUS,
MM_BASE_PITCH_RATE + get_rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
MM_BASE_PITCH_RATE + rng_get_u32() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
SFX_DEFAULT_VOLUME
);
}
@@ -3020,8 +3010,7 @@ static void game_playing_on_update(void)
void game_start(void)
{
// set_seed(9); // 9 is a full house
set_seed(g_game_vars.rng_seed);
rng_shuffle_seed();
affine_background_change_background(AFFINE_BG_GAME);
+2
View File
@@ -7,6 +7,7 @@
#include "game/main_menu.h"
#include "graphic_utils.h"
#include "layout.h"
#include "random.h"
#include "soundbank.h"
#include "timer.h"
#include "util.h"
@@ -128,4 +129,5 @@ void game_over_on_exit(void)
{
condition = END_CONDITION_NONE;
game_reset();
rng_shuffle_seed();
}
-8
View File
@@ -127,14 +127,6 @@ void game_main_menu_on_update(void)
main_menu_ace->sprite_object->trotation = lu_sin((g_game_vars.timer << 8) / 2) / 3;
card_object_update(main_menu_ace);
// Seed randomization
g_game_vars.rng_seed++;
// If the keys have changed, make it more pseudo-random
if (key_curr_state() != key_prev_state())
{
g_game_vars.rng_seed *= 2;
}
selection_grid_process_input(&main_menu_selection_grid);
}
+4 -3
View File
@@ -17,6 +17,7 @@
#include "joker.h"
#include "layout.h"
#include "list.h"
#include "random.h"
#include "save.h"
#include "soundbank.h"
#include "timer.h"
@@ -196,7 +197,7 @@ static inline int game_shop_get_rand_available_joker_id(void)
return UNDEFINED;
int matching_joker_ids[jokers_avail_size];
int fallback_random_idx = get_rand() % jokers_avail_size;
int fallback_random_idx = rng_get_u32() % jokers_avail_size;
int fallback_random_joker_id = UNDEFINED;
int match_count = 0;
@@ -215,8 +216,8 @@ static inline int game_shop_get_rand_available_joker_id(void)
}
}
int selected_joker_id =
(match_count > 0) ? matching_joker_ids[get_rand() % match_count] : fallback_random_joker_id;
int selected_joker_id = (match_count > 0) ? matching_joker_ids[rng_get_u32() % match_count]
: fallback_random_joker_id;
return selected_joker_id;
}
+2 -1
View File
@@ -5,6 +5,7 @@
#include "graphic_utils.h"
#include "joker_gfx.h"
#include "pool.h"
#include "random.h"
#include "soundbank.h"
#include "util.h"
@@ -368,7 +369,7 @@ Sprite* joker_object_get_sprite(JokerObject* joker_object)
int joker_get_random_rarity()
{
int joker_rarity = 0;
int rarity_roll = get_rand() % 100;
int rarity_roll = rng_get_u32() % 100;
if (rarity_roll < COMMON_JOKER_CHANCE)
{
joker_rarity = COMMON_JOKER;
+4 -3
View File
@@ -4,6 +4,7 @@
#include "joker.h"
#include "list.h"
#include "pool.h"
#include "random.h"
#include "util.h"
#include <stdlib.h>
@@ -563,7 +564,7 @@ static u32 misprint_joker_effect(
*joker_effect = &shared_joker_effect;
(*joker_effect)->mult = get_rand() % (MISPRINT_MAX_MULT + 1);
(*joker_effect)->mult = rng_get_u32() % (MISPRINT_MAX_MULT + 1);
return JOKER_EFFECT_FLAG_MULT;
}
@@ -776,7 +777,7 @@ static u32 reserved_parking_joker_effect(
u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE;
if ((get_rand() % 2 == 0) && card_is_face(scored_card))
if ((rng_get_u32() % 2 == 0) && card_is_face(scored_card))
{
*joker_effect = &shared_joker_effect;
@@ -798,7 +799,7 @@ static u32 business_card_joker_effect(
u32 effect_flags_ret = JOKER_EFFECT_FLAG_NONE;
if ((get_rand() % 2 == 0) && card_is_face(scored_card))
if ((rng_get_u32() % 2 == 0) && card_is_face(scored_card))
{
*joker_effect = &shared_joker_effect;
+3
View File
@@ -6,6 +6,7 @@
#include "gbalatro_sys8.h"
#include "graphic_utils.h"
#include "joker.h"
#include "random.h"
#include "save.h"
#include "sprite.h"
@@ -24,6 +25,8 @@
void init()
{
rng_init();
irq_init(NULL);
irq_add(II_VBLANK, mmVBlank);
irq_add(II_HBLANK, affine_background_hblank);
+50
View File
@@ -0,0 +1,50 @@
#include "random.h"
#include "game_variables.h"
#include <stdlib.h>
#include <tonc.h>
// Accumulate timer 1 into a bigger variable so we can generate more diverse seeds
static u32 timer_acc = 0;
// Timers usage docs: https://gbadev.net/tonc/timers.html
void rng_init(void)
{
REG_TM1D = 0;
REG_TM1CNT = TM_FREQ_1 | TM_ENABLE; // using timer with x1 prescale
}
void rng_update(void)
{
timer_acc += (u32)REG_TM1D;
}
void rng_set_seed(u32 seed)
{
g_game_vars.rng_info.seed = seed % (MAX_SEED + 1);
g_game_vars.rng_info.step = 0;
srand(g_game_vars.rng_info.seed);
}
void rng_shuffle_seed(void)
{
rng_set_seed(timer_acc);
}
u32 rng_get_u32(void)
{
g_game_vars.rng_info.step++;
return rand();
}
void rng_restore(RngInfo info)
{
g_game_vars.rng_info = info;
srand(g_game_vars.rng_info.seed);
for (u32 i = 0; i < g_game_vars.rng_info.step; i++)
{
(void)rng_get_u32();
}
}
+6 -16
View File
@@ -105,8 +105,8 @@ typedef struct JokerObjectSaveData
* 2 | 'L' | ' ' | 'D' | 'A' | - | -
* 3 | 'T' | 'A' | ' ' | '-' | - | -
* 4 | T[0] | T[1] | T[2] | T[3] | GLOB TIMER | The global timer used for animations thoughout the game
* 5 | SED[0] | SED[1] | SED[2] | SED[3] | RNG SEED | The seed used for RNG, either randomly shuffled or chosen by the player at game start
* 6 | STP[0] | STP[1] | STP[2] | STP[3] | RNG STEP | The current position in the RNG sequence for the given seed, since the start of the run
* 5 | RNG[0] | RNG[1] | RNG[2] | RNG[3] | RNG INFO | RNG Info struct, containing the seed used for RNG, either randomly shuffled or chosen by the player
* 6 | RNG[4] | RNG[5] | RNG[6] | RNG[7] | - | at game start, and the current position in the RNG sequence for the given seed, since the start of the run
* 7 | RND[0] | RND[1] | RND[2] | RND[3] | ROUND | What Round we are about to start
* 8 | ANT[0] | ANT[1] | ANT[2] | ANT[3] | ANTE | What Ante we are on
* 9 | MNY[0] | MNY[1] | MNY[2] | MNY[3] | MONEY | How much money we currently have left
@@ -127,8 +127,7 @@ typedef struct SaveGame
{
char tag_internal[SAVE_LABEL_SIZE];
s32 timer;
u32 rng_seed;
u32 rng_step;
RngInfo rng_info;
int round;
int ante;
int money;
@@ -168,8 +167,7 @@ static const SaveOptions SaveOptions_default = {
static const SaveGame SaveGame_default = {
.tag_internal = "-INTERNAL DATA -",
.timer = 0,
.rng_seed = 0,
.rng_step = 0,
.rng_info = {0, 0},
.round = 0,
.ante = 0,
.money = 0,
@@ -328,8 +326,7 @@ void save_game(void)
// Fixed data
game.timer = g_game_vars.timer;
game.rng_seed = g_game_vars.rng_seed;
game.rng_step = g_game_vars.rng_step;
game.rng_info = g_game_vars.rng_info;
game.round = g_game_vars.round;
game.ante = g_game_vars.ante;
game.money = g_game_vars.money;
@@ -370,17 +367,10 @@ void load_game(void)
read_sram(GAME_ADDRESS, (u8*)&game, sizeof(game));
g_game_vars.timer = game.timer;
g_game_vars.rng_seed = game.rng_seed;
g_game_vars.rng_step = game.rng_step;
rng_restore(game.rng_info);
g_game_vars.round = game.round;
g_game_vars.ante = game.ante;
g_game_vars.money = game.money;
// TODO: load Jokers from stored minimal data
// return to where we were in the random sequence so that the run stays reproducible
for (u32 i = 0; i < g_game_vars.rng_step; i++)
{
(void)rand();
}
}
+2 -1
View File
@@ -5,6 +5,7 @@
#include "game_variables.h"
#include "graphic_utils.h"
#include "pool.h"
#include "random.h"
#include "soundbank.h"
#include "util.h"
@@ -330,7 +331,7 @@ void sprite_object_set_focus(SpriteObject* sprite_object, bool focus)
play_sfx(
SFX_CARD_FOCUS,
MM_BASE_PITCH_RATE + get_rand() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
MM_BASE_PITCH_RATE + rng_get_u32() % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
SFX_DEFAULT_VOLUME
);
sprite_object->ty = sprite_object->ty + int2fx((focus ? -1 : 1) * SPRITE_FOCUS_RAISE_PX);