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}