froggy_rand/lib.rs
1//! Random number generation without state for games.
2//!
3//! One way of thinking about a stateful pseudorandom number generator (RNGs) is as a list of numbers.
4//! Each initial seed yields a different list.
5//! Every time we generate a random number we are incrementing an index into this list.
6//!
7//! Suppose you are building a rougelike game where users can share seeds.
8//! A design goal might be to keep the map generation consistent between updates.
9//! With a stateful RNG you would have to be very careful about adding additional RNG calls.
10//! This is because additional RNG calls will increment the index into the RNG's list, and will affect all RNG calls made after it.
11//!
12//! This library solves this problem by removing the mutable state from the RNG.
13//!
14//! ---
15//!
16//! For a concrete example, suppose we are filling a level with enemies at random positions.
17//!
18//! ```
19//! let mut rng = ExampleRng::from_seed(seed);
20//! let mut enemies = vec![];
21//! for id in 0..100 {
22//! let x = rng.next();
23//! let y = rng.next();
24//! enemies.push(Enemy::new(id, x, y));
25//! }
26//! ```
27//!
28//! Now in an update we want to add variety, so we give enemies a choice of random weapons.
29//! We might do that as follows:
30//!
31//! ```
32//! let mut rng = ExampleRng::from_seed(seed);
33//! let mut enemies = vec![];
34//! for id in 0..100 {
35//! let x = rng.next();
36//! let y = rng.next();
37//! let weapon_type = rng.next();
38//! enemies.push(Enemy::new(id, x, y, weapon_type));
39//! }
40//! ```
41//!
42//! We load up the game with a known seed however the positions of all the enemies change!
43//! What has happened is the additional rng calls have shifted all of the subsequent position generations.
44//! One fix would be to initialize a new random number generator for the weapon type based on a seed generated from the initial, but this gets messy if we need a generator per property.
45//!
46//! ---
47//!
48//! Another approach might be to be to embrace the "list of random numbers" view and transform the stateful RNG into an indexing function.
49//!
50//! ```
51//! fn random(seed : SeedValue, i : usize) -> RandomValue
52//! { ... }
53//! ```
54//!
55//! This would require the user to explicitly keep track of the index somewhere.
56//! `FroggyRand` makes one more jump after this.
57//! First it generates a hash value from its input argument, then it combines that with its seed to generate an index into an RNG list.
58//!
59//! Here is how we would use `FroggyRand` with the example above:
60//!
61//! ```
62//! let froggy_rand = FroggyRand::new(seed);
63//! let mut enemies = vec![];
64//! for id in 0..100 {
65//! // We want the x position to be based on two things:
66//! // The enemy id
67//! // The hash of the string "enemy_x" to make sure its different to the y value
68//! let x = froggy_rand.gen(("enemy_x", id));
69//! let y = froggy_rand.gen(("enemy_y", id));
70//! let weapon_type = froggy_rand.gen(("weapon_type", id));
71//! enemies.push(Enemy::new(id, x, y, weapon_type));
72//! }
73//! ```
74//!
75//! Now we can add as many new parameters as we want without them effecting each other.
76//!
77//! For a more detailed explanation see
78//! [this talk.](https://www.youtube.com/watch?v=e4b--cyXEsM)
79
80#![no_std]
81
82use core::hash::{Hash, Hasher};
83use core::num::Wrapping;
84
85mod hasher;
86
87#[derive(Debug, Copy, Clone)]
88pub struct FroggyRand {
89 pub seed : u64,
90}
91
92fn split_mix_64(index : u64) -> u64 {
93 let mut z = Wrapping(index) + Wrapping(0x9E3779B97F4A7C15);
94 z = (z ^ (z >> 30)) * Wrapping(0xBF58476D1CE4E5B9);
95 z = (z ^ (z >> 27)) * Wrapping(0x94D049BB133111EB);
96 (z ^ (z >> 31)).0
97}
98
99#[inline]
100fn hash<T : Hash>(x : T) -> u64 {
101 let mut hasher = hasher::Lookup3Hasher::default();
102 x.hash(&mut hasher);
103 hasher.finish()
104}
105
106impl FroggyRand {
107 #[inline]
108 pub fn new(seed : u64) -> Self {
109 Self {seed}
110 }
111
112 #[inline]
113 pub fn from_hash<T : Hash>(x : T) -> Self {
114 Self::new(hash(x))
115 }
116
117 #[inline]
118 pub fn subrand<T : Hash>(&self, x : T) -> Self {
119 Self::from_hash((x, self.seed))
120 }
121
122 /// Should be uniform over all u64 values
123 #[inline]
124 pub fn gen<T : Hash>(&self, x : T) -> u64 {
125 let hash = hash(x);
126 let index = self.seed.wrapping_add(hash);
127 split_mix_64(index)
128 }
129
130 /// Should be uniform in [0, 1]
131 #[inline]
132 pub fn gen_unit<T : Hash>(&self, x : T) -> f32 {
133 // Should be enough precision for a game
134 (self.gen(x) % 1_000_000) as f32 / 1_000_000.0
135 }
136
137 /// Should be uniform in [min, max]
138 #[inline]
139 pub fn gen_range<T : Hash>(&self, x : T, min : f32, max : f32) -> f32 {
140 min + self.gen_unit(x) * (max - min)
141 }
142
143 /// Should give a uniform random element of the slice choices.
144 #[inline]
145 pub fn choose<'a, T : Hash, X>(&self, x : T, choices : &'a [X]) -> &'a X {
146 // usize can be aliased to u32 or u64 in wasm based on the compilation
147 // for safety we restrict to u32 range.
148 let index = self.gen(x) as u64 % u32::MAX as u64;
149 let i = index as usize % choices.len();
150 &choices[i]
151 }
152
153 /// I dont know what a statistic is
154 /// Approx normal dist https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution
155 #[inline]
156 pub fn gen_froggy<T : Hash>(&self, x : T, min : f32, max : f32, n : u32) -> f32 {
157 let mut sum = 0.;
158 let gen_min = min / n as f32;
159 let gen_max = max / n as f32;
160
161 for i in 0..n {
162 sum += self.gen_range((&x, i), gen_min, gen_max);
163 }
164
165 sum
166 }
167
168 #[inline]
169 pub fn gen_usize_range<T : Hash>(&self, x : T, min : usize, max : usize) -> usize {
170 let range = 1 + max - min;
171 min + ((self.gen(x) as usize) % range)
172 }
173
174 #[inline]
175 pub fn shuffle<T : Hash, X>(&self, x : T, xs : &mut [X]) {
176 // Fisher-yates
177 // See https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
178 for i in 0..=xs.len()-2 {
179 let j = self.gen_usize_range((&x, i), i, xs.len() - 1);
180 xs.swap(i, j);
181 }
182 }
183
184 /// Should be uniform in [0, 255]
185 #[inline]
186 pub fn gen_byte<T : Hash>(&self, x : T) -> u8 {
187 (self.gen(x) % 255) as u8
188 }
189
190 /// More performant gen() if the only control parameter you need is a single int.
191 #[inline]
192 pub fn gen_perf(&self, seed: i32) -> u64 {
193 let index = (Wrapping(self.seed) + Wrapping(seed as u64)).0;
194 split_mix_64(index)
195 }
196
197 /// More performant gen_unit() if the only control parameter you need is a single int.
198 #[inline]
199 pub fn gen_unit_perf(&self, seed: i32) -> f32 {
200 (self.gen_perf(seed) % 1_000_000) as f32 / 1_000_000.0
201 }
202}
203
204
205#[cfg(test)]
206mod tests {
207 use crate::*;
208
209 #[test]
210 fn same_hashes() {
211 let froggy_rand = FroggyRand::new(100);
212 let val0 = froggy_rand.gen(("test", 0));
213 let val1 = froggy_rand.gen(("test", 0));
214 assert_eq!(val0, val1);
215 }
216
217 #[test]
218 fn different_hashes() {
219 let froggy_rand = FroggyRand::new(100);
220 let val0 = froggy_rand.gen(("test", 0));
221 let val1 = froggy_rand.gen(("test", 1));
222 let val2 = froggy_rand.gen(("test_other", 0));
223 let val3 = froggy_rand.gen(("test_other", 1));
224
225 assert_ne!(val0, val1);
226 assert_ne!(val0, val2);
227 assert_ne!(val0, val3);
228
229 assert_ne!(val1, val0);
230 assert_ne!(val1, val2);
231 assert_ne!(val1, val3);
232
233 assert_ne!(val2, val0);
234 assert_ne!(val2, val1);
235 assert_ne!(val2, val3);
236
237 assert_ne!(val3, val0);
238 assert_ne!(val3, val1);
239 assert_ne!(val3, val2);
240 }
241}