pub trait StoryRng {
fn from_seed(seed: i32) -> Self;
fn next_int(&mut self) -> i32;
}
#[derive(Clone)]
pub struct FastRng {
state: u32,
}
impl StoryRng for FastRng {
#[expect(clippy::cast_sign_loss)]
fn from_seed(seed: i32) -> Self {
let s = seed as u32;
Self {
state: if s == 0 { 1 } else { s },
}
}
#[expect(clippy::cast_possible_wrap)]
fn next_int(&mut self) -> i32 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
self.state = x;
(x & 0x7FFF_FFFF) as i32
}
}
const MBIG: i32 = i32::MAX; const MSEED: i32 = 161_803_398;
const SEED_ARRAY_LEN: usize = 56;
#[derive(Clone)]
pub struct DotNetRng {
seed_array: [i32; SEED_ARRAY_LEN],
inext: i32,
inextp: i32,
}
impl StoryRng for DotNetRng {
fn from_seed(seed: i32) -> Self {
let mut seed_array = [0i32; SEED_ARRAY_LEN];
let subtraction = if seed == i32::MIN {
i32::MAX
} else {
seed.abs()
};
let mut mj = MSEED.wrapping_sub(subtraction);
seed_array[55] = mj;
let mut mk: i32 = 1;
for i in 1..55 {
let ii = (21_usize.wrapping_mul(i)) % 55;
seed_array[ii] = mk;
mk = mj.wrapping_sub(mk);
if mk < 0 {
mk = mk.wrapping_add(MBIG);
}
mj = seed_array[ii];
}
for _k in 1..5 {
for i in 1..56 {
let idx = 1 + (i + 30) % 55;
seed_array[i] = seed_array[i].wrapping_sub(seed_array[idx]);
if seed_array[i] < 0 {
seed_array[i] = seed_array[i].wrapping_add(MBIG);
}
}
}
Self {
seed_array,
inext: 0,
inextp: 21,
}
}
#[expect(clippy::cast_sign_loss)]
fn next_int(&mut self) -> i32 {
let mut inext = self.inext + 1;
if inext >= 56 {
inext = 1;
}
let mut inextp = self.inextp + 1;
if inextp >= 56 {
inextp = 1;
}
let mut num =
self.seed_array[inext as usize].wrapping_sub(self.seed_array[inextp as usize]);
if num == MBIG {
num -= 1;
}
if num < 0 {
num = num.wrapping_add(MBIG);
}
self.seed_array[inext as usize] = num;
self.inext = inext;
self.inextp = inextp;
num
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dotnet_rng_seed_0_sequence() {
let mut rng = DotNetRng::from_seed(0);
assert_eq!(rng.next_int(), 1_559_595_546);
assert_eq!(rng.next_int(), 1_755_192_844);
assert_eq!(rng.next_int(), 1_649_316_166);
assert_eq!(rng.next_int(), 1_198_642_031);
assert_eq!(rng.next_int(), 442_452_829);
}
#[test]
fn dotnet_rng_negative_seed() {
let mut rng = DotNetRng::from_seed(-1);
let v = rng.next_int();
assert!(
v >= 0,
"negative seed should still produce non-negative values"
);
}
#[test]
fn dotnet_rng_all_non_negative() {
let mut rng = DotNetRng::from_seed(42);
for _ in 0..1000 {
assert!(rng.next_int() >= 0);
}
}
#[test]
fn fast_rng_all_non_negative() {
let mut rng = FastRng::from_seed(42);
for _ in 0..1000 {
assert!(rng.next_int() >= 0);
}
}
#[test]
fn fast_rng_seed_zero_not_stuck() {
let mut rng = FastRng::from_seed(0);
let first = rng.next_int();
let second = rng.next_int();
assert_ne!(first, 0);
assert_ne!(first, second);
}
}