using System; /// /// Deterministic PRNG for game-critical randomness (deck shuffling, sync keys, coin flips). /// Uses Xoshiro256** with SplitMix64 seeding for a 256-bit internal state. /// Both clients seed with the same value to produce identical sequences. /// public static class GameRandom { private static ulong s0, s1, s2, s3; private static bool initialized = false; /// /// Seed the PRNG with a 64-bit value. Uses SplitMix64 to expand /// the seed into 256 bits of Xoshiro256** state. /// public static void Seed(long seed) { ulong s = (ulong)seed; s0 = SplitMix64(ref s); s1 = SplitMix64(ref s); s2 = SplitMix64(ref s); s3 = SplitMix64(ref s); // Ensure state is not all-zero (degenerate case) if (s0 == 0 && s1 == 0 && s2 == 0 && s3 == 0) s0 = 1; initialized = true; } /// /// Returns a random int in [min, exclusiveMax) with no modulo bias. /// Uses rejection sampling. /// public static int Range(int min, int exclusiveMax) { if (!initialized) throw new InvalidOperationException("GameRandom has not been seeded. Call GameRandom.Seed() first."); if (exclusiveMax <= min) return min; uint range = (uint)(exclusiveMax - min); if (range == 1) return min; // Rejection sampling to eliminate modulo bias // Threshold: values below this are in the biased zone uint threshold = (uint)((0x100000000UL - range) % range); uint raw; do { raw = NextUInt32(); } while (raw < threshold); return min + (int)(raw % range); } /// /// Returns a random float in [min, max). /// public static float Range(float min, float max) { if (!initialized) throw new InvalidOperationException("GameRandom has not been seeded. Call GameRandom.Seed() first."); // Use 24 bits of randomness for float precision (IEEE 754 single has 23-bit mantissa) float t = (NextUInt32() >> 8) * (1.0f / (1 << 24)); return min + (max - min) * t; } /// /// Returns true with the given probability [0..1]. /// public static bool Probability(float p) { if (p >= 1f) return true; if (p <= 0f) return false; return Range(0f, 1f) <= p; } // --- Internal: Xoshiro256** --- private static ulong NextState() { // xoshiro256** result calculation ulong result = RotateLeft(s1 * 5, 7) * 9; ulong t = s1 << 17; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; s3 = RotateLeft(s3, 45); return result; } private static uint NextUInt32() { return (uint)(NextState() >> 32); } private static ulong RotateLeft(ulong x, int k) { return (x << k) | (x >> (64 - k)); } // --- Internal: SplitMix64 (for seed expansion) --- private static ulong SplitMix64(ref ulong state) { ulong z = (state += 0x9E3779B97F4A7C15UL); z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL; z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL; return z ^ (z >> 31); } }