1pub trait StoryRng {
14 fn from_seed(seed: i32) -> Self;
16 fn next_int(&mut self) -> i32;
18}
19
20#[derive(Clone)]
24pub struct FastRng {
25 state: u32,
26}
27
28impl StoryRng for FastRng {
29 #[expect(clippy::cast_sign_loss)]
30 fn from_seed(seed: i32) -> Self {
31 let s = seed as u32;
33 Self {
34 state: if s == 0 { 1 } else { s },
35 }
36 }
37
38 #[expect(clippy::cast_possible_wrap)]
39 fn next_int(&mut self) -> i32 {
40 let mut x = self.state;
41 x ^= x << 13;
42 x ^= x >> 17;
43 x ^= x << 5;
44 self.state = x;
45 (x & 0x7FFF_FFFF) as i32
47 }
48}
49
50const MBIG: i32 = i32::MAX; const MSEED: i32 = 161_803_398;
54const SEED_ARRAY_LEN: usize = 56; #[derive(Clone)]
61pub struct DotNetRng {
62 seed_array: [i32; SEED_ARRAY_LEN],
63 inext: i32,
64 inextp: i32,
65}
66
67impl StoryRng for DotNetRng {
68 fn from_seed(seed: i32) -> Self {
69 let mut seed_array = [0i32; SEED_ARRAY_LEN];
70
71 let subtraction = if seed == i32::MIN {
73 i32::MAX
74 } else {
75 seed.abs()
76 };
77 let mut mj = MSEED.wrapping_sub(subtraction);
78 seed_array[55] = mj;
79 let mut mk: i32 = 1;
80
81 for i in 1..55 {
82 let ii = (21_usize.wrapping_mul(i)) % 55;
84 seed_array[ii] = mk;
85 mk = mj.wrapping_sub(mk);
86 if mk < 0 {
87 mk = mk.wrapping_add(MBIG);
88 }
89 mj = seed_array[ii];
90 }
91
92 for _k in 1..5 {
93 for i in 1..56 {
94 let idx = 1 + (i + 30) % 55;
95 seed_array[i] = seed_array[i].wrapping_sub(seed_array[idx]);
96 if seed_array[i] < 0 {
97 seed_array[i] = seed_array[i].wrapping_add(MBIG);
98 }
99 }
100 }
101
102 Self {
103 seed_array,
104 inext: 0,
105 inextp: 21,
106 }
107 }
108
109 #[expect(clippy::cast_sign_loss)]
110 fn next_int(&mut self) -> i32 {
111 let mut inext = self.inext + 1;
112 if inext >= 56 {
113 inext = 1;
114 }
115 let mut inextp = self.inextp + 1;
116 if inextp >= 56 {
117 inextp = 1;
118 }
119
120 let mut num =
121 self.seed_array[inext as usize].wrapping_sub(self.seed_array[inextp as usize]);
122 if num == MBIG {
123 num -= 1;
124 }
125 if num < 0 {
126 num = num.wrapping_add(MBIG);
127 }
128
129 self.seed_array[inext as usize] = num;
130 self.inext = inext;
131 self.inextp = inextp;
132
133 num
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
145 fn dotnet_rng_seed_0_sequence() {
146 let mut rng = DotNetRng::from_seed(0);
147 assert_eq!(rng.next_int(), 1_559_595_546);
148 assert_eq!(rng.next_int(), 1_755_192_844);
149 assert_eq!(rng.next_int(), 1_649_316_166);
150 assert_eq!(rng.next_int(), 1_198_642_031);
151 assert_eq!(rng.next_int(), 442_452_829);
152 }
153
154 #[test]
156 fn dotnet_rng_negative_seed() {
157 let mut rng = DotNetRng::from_seed(-1);
158 let v = rng.next_int();
159 assert!(
160 v >= 0,
161 "negative seed should still produce non-negative values"
162 );
163 }
164
165 #[test]
167 fn dotnet_rng_all_non_negative() {
168 let mut rng = DotNetRng::from_seed(42);
169 for _ in 0..1000 {
170 assert!(rng.next_int() >= 0);
171 }
172 }
173
174 #[test]
176 fn fast_rng_all_non_negative() {
177 let mut rng = FastRng::from_seed(42);
178 for _ in 0..1000 {
179 assert!(rng.next_int() >= 0);
180 }
181 }
182
183 #[test]
185 fn fast_rng_seed_zero_not_stuck() {
186 let mut rng = FastRng::from_seed(0);
187 let first = rng.next_int();
188 let second = rng.next_int();
189 assert_ne!(first, 0);
190 assert_ne!(first, second);
191 }
192}