namespace Microsoft.Xna.Framework;
///
public static partial class MathHelper
{
///
/// Random Number Generator based on Permutation-Congruential-Generator (PCG), which is a fancy wording to
/// describe a family of RNG which are simple, fast, statistically excellent, and hardly predictable.
///
/// More interestingly, PCG allows to generate multiple sequences with the same seed, which is very handy
/// in game development to have a unique seed per game session while using different streams for each
/// RNG which requires an isolated context (e.g. generating a procedural level, but we don't want the loot
/// generation to affect subsequent level generations).
///
/// https://www.pcg-random.org/
///
/// This code is derived from the minimal C implementation.
/// https://github.com/imneme/pcg-c-basic
///
public class Random
{
// State
private ulong _state;
private readonly ulong _inc;
///
/// State of the random number generator.
///
public ulong State
{
get => _state;
set
{
_state = value;
PCG32(); // Advance state to ensure we don't return the same number twice.
}
}
///
/// Initialize a random number generator.
///
public Random() : this((ulong)System.Environment.TickCount) { }
///
/// Initialize a random number generator. Specified in two parts, state initializer (a.k.a. seed) and a sequence selection constant (a.k.a. stream id).
///
/// State initializer (a.k.a. seed).
/// Sequence selection constant (a.k.a. stream id). Defaults to 0.
public Random(ulong state, ulong streamID = 0)
{
_state = 0ul;
_inc = (streamID << 1) | 1ul;
PCG32();
_state += state;
PCG32();
}
///
/// Generate a uniformly distributed number.
///
/// A uniformly distributed 32bit unsigned integer.
private uint PCG32()
{
ulong oldState = _state;
// Advance internal state
_state = unchecked(_state * 6364136223846793005ul + _inc);
// Calculate output function (XSH RR), uses old state for max ILP
uint xorshifted = (uint)(((oldState >> 18) ^ oldState) >> 27);
int rot = (int)(oldState >> 59);
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
///
/// Generate a uniformly distributed number, r, where 0 <= and r < .
///
/// Exclusive upper bound of the number to generate.
/// A uniformly distributed 32bit unsigned integer strictly less than .
private uint PCG32(uint bound)
{
if (bound <= 0) { bound = 1; }
// To avoid bias, we need to make the range of the RNG a multiple of
// bound, which we do by dropping output less than a threshold.
uint threshold = ((uint)-bound) % bound;
// Uniformity guarantees that this loop will terminate. In practice, it
// should usually terminate quickly; on average (assuming all bounds are
// equally likely), 82.25% of the time, we can expect it to require just
// one iteration. In the worst case, someone passes a bound of 2^31 + 1
// (i.e., 2147483649), which invalidates almost 50% of the range. In
// practice, bounds are typically small and only a tiny amount of the range
// is eliminated.
while (true)
{
uint r = PCG32();
if (r >= threshold)
return r % bound;
}
}
///
/// Generate a random positive number.
///
/// A random positive number.
public int Next()
{
return Next(int.MaxValue);
}
///
/// Generate a random number with an exclusive upper bound.
///
/// Exclusive upper bound.
/// A random number with an exclusive upper bound.
public int Next(int maxValue)
{
if (maxValue < 0)
maxValue = 1;
return (int)PCG32((uint)maxValue);
}
///
/// Generate a random number ranging from to .
///
/// Lower bound.
/// Upper bound.
/// A random number ranging from to .
public int Next(int minValue, int maxValue)
{
if (maxValue < minValue)
maxValue = minValue;
return (int)(minValue + PCG32((uint)((long)maxValue - minValue)));
}
///
/// Generate a random float ranging from 0.0f to 1.0f.
///
/// A random float ranging from 0.0f to 1.0f.
public float NextFloat()
{
// This is quite hackish because we want to avoid BitConverter, but who cares?
int bound = int.MaxValue / 2 - 1;
return Next(bound) * 1.0f / bound;
}
///
/// Generate a random float ranging from to .
///
/// Lower bound. MUST be greater than zero.
/// Upper bound. MUST be greater than zero.
/// A random float ranging from to .
public float NextFloat(float minValue, float maxValue)
{
if (minValue < 0)
minValue = 0;
if (maxValue < minValue)
maxValue = minValue;
return minValue + (maxValue - minValue) * NextFloat();
}
///
/// Generate a random bool.
///
/// A random bool.
public bool NextBool()
{
return NextFloat() <= 0.5f;
}
}
}