RingRacers/src/m_random.c
2025-02-13 15:32:26 -06:00

409 lines
10 KiB
C

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour.
// Copyright (C) 2025 by Kart Krew.
// Copyright (C) 2020 by Sonic Team Junior.
// Copyright (C) 2016 by Kay "Kaito" Sinclaire.
// Copyright (C) 2000 by DooM Legacy Team.
// Copyright (C) 1996 by id Software, Inc.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file m_random.c
/// \brief RNG for client effects and PRNG for game actions
#include "doomdef.h"
#include "doomtype.h"
#include "m_cond.h" // gamedata->totalplaytime
#include "m_random.h"
#include "m_fixed.h"
// ---------------------------
// RNG functions (not synched)
// ---------------------------
ATTRINLINE static UINT32 FUNCINLINE __external_prng__(void)
{
UINT32 rnd = rand();
#if RAND_MAX < 65535
// Compensate for especially bad randomness.
UINT32 rndv = (rand() & 1) << 15;
rnd ^= rndv;
#endif
// Shuffle like we do for our own PRNG, since RAND_MAX
// tends to be [0, INT32_MAX] instead of [0, UINT32_MAX].
rnd ^= rnd >> 13;
rnd ^= rnd >> 11;
rnd ^= rnd << 21;
return (rnd * 36548569);
}
ATTRINLINE static UINT32 FUNCINLINE __external_prng_bound__(UINT32 bound)
{
// Do rejection sampling to remove the modulo bias.
UINT32 threshold = -bound % bound;
for (;;)
{
UINT32 r = __external_prng__();
if (r >= threshold)
{
return r % bound;
}
}
}
/** Provides a random 32-bit number. Distribution is uniform.
* As with all M_Random functions, not synched in netgames.
*
* \return A random 32-bit number.
*/
UINT32 M_Random(void)
{
return __external_prng__();
}
/** Provides a random fixed point number. Distribution is uniform.
* As with all M_Random functions, not synched in netgames.
*
* \return A random fixed point number from [0,1].
*/
fixed_t M_RandomFixed(void)
{
return (fixed_t)(__external_prng_bound__(FRACUNIT));
}
/** Provides a random byte. Distribution is uniform.
* As with all M_Random functions, not synched in netgames.
*
* \return A random integer from [0, 255].
*/
UINT8 M_RandomByte(void)
{
return (UINT8)(__external_prng_bound__(UINT8_MAX));
}
/** Provides a random integer for picking random elements from an array.
* Distribution is uniform.
* As with all M_Random functions, not synched in netgames.
*
* \param a Number of items in array.
* \return A random integer from [0,a).
*/
UINT32 M_RandomKey(UINT32 a)
{
return __external_prng_bound__(a);
}
/** Provides a random integer in a given range.
* Distribution is uniform.
* As with all M_Random functions, not synched in netgames.
*
* \param a Lower bound.
* \param b Upper bound.
* \return A random integer from [a,b].
*/
INT32 M_RandomRange(INT32 a, INT32 b)
{
return (INT32)(__external_prng_bound__((b - a) + 1)) + a;
}
// ------------------------
// PRNG functions (synched)
// ------------------------
#define DEFAULT_SEED (0xBADE4404)
typedef struct
{
UINT32 seed[PRNUMCLASS]; // Holds each block's current seed.
UINT32 init[PRNUMCLASS]; // Holds the INITIAL seed value. Used for demos, possibly other debugging
} rng_t;
static rng_t rng; // The entire PRNG state
/** Provides a random 32 bit integer.
* This is a variant of an xorshift PRNG; state fits in a 32 bit integer structure.
*
* \return A random, uniformly distributed number from [0,UINT32_MAX].
*/
ATTRINLINE static UINT32 FUNCINLINE __internal_prng__(pr_class_t pr_class)
{
rng.seed[pr_class] ^= rng.seed[pr_class] >> 13;
rng.seed[pr_class] ^= rng.seed[pr_class] >> 11;
rng.seed[pr_class] ^= rng.seed[pr_class] << 21;
return (rng.seed[pr_class] * 36548569);
}
/** Provides a random number within a specified range.
*
* \return A random, uniformly distributed integer from [0,bound).
*/
ATTRINLINE static UINT32 FUNCINLINE __internal_prng_bound__(pr_class_t pr_class, UINT32 bound)
{
// Do rejection sampling to remove the modulo bias.
UINT32 threshold = -bound % bound;
for (;;)
{
UINT32 r = __internal_prng__(pr_class);
if (r >= threshold)
{
return r % bound;
}
}
}
/** Provides a random fixed point number. Distribution is uniform.
* Literally a wrapper for the internal PRNG function.
*
* \return A random fixed point number from [0,UINT32_MAX].
*/
#ifndef DEBUGRANDOM
UINT32 P_Random(pr_class_t pr_class)
{
#else
UINT32 P_RandomD(const char *rfile, INT32 rline, pr_class_t pr_class)
{
CONS_Printf("P_Random(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
return __internal_prng__(pr_class);
}
/** Provides a random fixed point number. Distribution is uniform.
*
* \return A random fixed point number from [0,1].
*/
#ifndef DEBUGRANDOM
fixed_t P_RandomFixed(pr_class_t pr_class)
{
#else
fixed_t P_RandomFixedD(const char *rfile, INT32 rline, pr_class_t pr_class)
{
CONS_Printf("P_RandomFixed(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
return (fixed_t)(__internal_prng_bound__(pr_class, FRACUNIT));
}
/** Provides a random byte. Distribution is uniform.
* If you're curious, (&0xFF00) >> 8 gives the same result
* as a fixed point multiplication by 256.
*
* \return Random integer from [0,255].
* \sa __internal_prng__
*/
#ifndef DEBUGRANDOM
UINT8 P_RandomByte(pr_class_t pr_class)
{
#else
UINT8 P_RandomByteD(const char *rfile, INT32 rline, pr_class_t pr_class)
{
CONS_Printf("P_RandomByte(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
return (UINT8)(__internal_prng_bound__(pr_class, UINT8_MAX));
}
/** Provides a random integer for picking random elements from an array.
* Distribution is uniform.
* NOTE: Maximum range is 65536.
*
* \param a Number of items in array.
* \return A random integer from [0,a).
* \sa __internal_prng__
*/
#ifndef DEBUGRANDOM
UINT32 P_RandomKey(pr_class_t pr_class, UINT32 a)
{
#else
UINT32 P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 a)
{
CONS_Printf("P_RandomKey(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
return __internal_prng_bound__(pr_class, a);
}
/** Provides a random integer in a given range.
* Distribution is uniform.
*
* \param a Lower bound.
* \param b Upper bound.
* \return A random integer from [a,b].
* \sa __internal_prng__
*/
#ifndef DEBUGRANDOM
INT32 P_RandomRange(pr_class_t pr_class, INT32 a, INT32 b)
{
#else
INT32 P_RandomRangeD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 a, INT32 b)
{
CONS_Printf("P_RandomRange(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
return (INT32)(__internal_prng_bound__(pr_class, (b - a) + 1)) + a;
}
// ----------------------
// PRNG seeds & debugging
// ----------------------
/** Peeks to see what the next result from the PRNG will be.
* Used for debugging.
*
* \return A 'random' number from [0,UINT32_MAX]
* \sa __internal_prng__
*/
UINT32 P_RandomPeek(pr_class_t pr_class)
{
UINT32 r = rng.seed[pr_class];
UINT32 ret = __internal_prng__(pr_class);
rng.seed[pr_class] = r;
return ret;
}
/** Gets the current random seed. Used by netgame savegames.
*
* \return Current random seed.
* \sa P_SetRandSeed
*/
#ifndef DEBUGRANDOM
UINT32 P_GetRandSeed(pr_class_t pr_class)
{
#else
UINT32 P_GetRandSeedD(const char *rfile, INT32 rline, pr_class_t pr_class)
{
CONS_Printf("P_GetRandSeed(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
return rng.seed[pr_class];
}
/** Gets the initial random seed. Used by demos.
*
* \return Initial random seed.
* \sa P_SetRandSeed
*/
#ifndef DEBUGRANDOM
UINT32 P_GetInitSeed(pr_class_t pr_class)
{
#else
UINT32 P_GetInitSeedD(const char *rfile, INT32 rline, pr_class_t pr_class)
{
CONS_Printf("P_GetInitSeed(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
return rng.init[pr_class];
}
/** Sets the random seed.
* Used at the beginning of a game.
*
* \param pr_class RNG class to adjust.
* \param seed New random seed.
* \sa P_GetRandSeed
*/
#ifndef DEBUGRANDOM
void P_SetRandSeed(pr_class_t pr_class, UINT32 seed)
{
#else
void P_SetRandSeedD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 seed)
{
CONS_Printf("P_SetRandSeed(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
// xorshift requires a nonzero seed
// this should never happen, but just in case it DOES, we check
if (!seed) seed = DEFAULT_SEED;
rng.seed[pr_class] = rng.init[pr_class] = seed;
}
/** Sets both the initial seed and the current seed.
* Used for netgame sync.
*
* \param pr_class RNG class to adjust.
* \param init Sent initial seed.
* \param seed Sent current seed.
* \sa P_SetRandSeed
*/
#ifndef DEBUGRANDOM
void P_SetRandSeedNet(pr_class_t pr_class, UINT32 init, UINT32 seed)
{
#else
void P_SetRandSeedNetD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 init, UINT32 seed)
{
CONS_Printf("P_SetRandSeedNet(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
if (!init) init = DEFAULT_SEED;
rng.init[pr_class] = init;
if (!seed) seed = DEFAULT_SEED;
rng.seed[pr_class] = seed;
}
/** Change PR_INTERPHUDRANDOM state.
* Used for interp-safe HUD randomisation.
*
* \sa P_SetRandSeed
*/
#ifndef DEBUGRANDOM
void P_ResetInterpHudRandSeed(boolean newframe)
{
#else
void P_ResetInterpHudRandSeedD(const char *rfile, INT32 rline, boolean newframe)
{
CONS_Printf("P_ResetInterpHudRandSeed(%c) at: %sp %d\n", (newframe ? 'T' : 'F'), rfile, rline);
#endif
if (newframe == true)
{
// Advance the initialisation to the current seed.
rng.init[PR_INTERPHUDRANDOM] = rng.seed[PR_INTERPHUDRANDOM];
}
else
{
// Rewind the seed to the last initialisation.
rng.seed[PR_INTERPHUDRANDOM] = rng.init[PR_INTERPHUDRANDOM];
}
// xorshift requires a nonzero seed
// this should never happen, but just in case it DOES, we check
if (!rng.seed[PR_INTERPHUDRANDOM])
rng.seed[PR_INTERPHUDRANDOM] = rng.init[PR_INTERPHUDRANDOM] = DEFAULT_SEED;
}
/** Initializes random seeds for all classes.
* Used at the beginning of a game.
*
* \param rindex New random index.
* \sa P_SetRandSeed
*/
void P_ClearRandom(UINT32 seed)
{
size_t i;
if (!seed) seed = DEFAULT_SEED;
for (i = 0; i < PRNUMSYNCED; i++)
{
P_SetRandSeed(i, seed);
// Different XOR from __internal_prng__
// so that it's not as predictable.
seed ^= seed >> 13;
seed ^= seed << 25;
seed ^= seed >> 11;
}
}
/** Gets a randomized seed for setting the random seed.
*
* \sa P_GetRandSeed
*/
UINT32 M_RandomizedSeed(void)
{
return ((gamedata->totalplaytime & 0xFFFF) << 16) | M_RandomFixed();
}