1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Random number generation without state for games.
//!
//! One way of thinking about a pseudorandom number generator (RNG) is as a list of numbers defined by an initial seed.
//! Every time we generate a random number we increment the index into this list. 
//! Suppose you are building a rougelike game where users can share seeds. A design goal might be to keep the map generation consistent between updates.
//! With a traditional RNG you would have to be very careful about adding additional RNG calls as doing so would affect all RNG calls made after it.
//! 
//! This library solves this problem by removing the mutable state from the RNG.
//! 
//! ---
//! 
//! For a concrete example, suppose we are filling a level with enemies.
//! 
//! ```
//! let mut rng = ExampleRng::from_seed(seed);
//! let mut enemies = vec![];
//! for id in 0..100 {
//!   let x = rng.next();
//!   let y = rng.next();
//!   enemies.push(Enemy::new(id, x, y));
//! }
//! ``` 
//! 
//! Now suppose in an update we want to add variety, so give enemies a choice of random weapons.
//! We might do that as follows:
//! 
//! ```
//! let mut rng = ExampleRng::from_seed(seed);
//! let mut enemies = vec![];
//! for id in 0..100 {
//!   let x = rng.next();
//!   let y = rng.next();
//!   let weapon_type = rng.next();
//!   enemies.push(Enemy::new(id, x, y, weapon_type));
//! }
//! ``` 
//! 
//! However we have just changed the positions of all enemies past the first!
//! 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.
//! 
//! ---
//!
//! Another approach might be to be to embrace the "list of random numbers" view and transform the stateful RNG into an indexing function.
//! 
//! ```
//! fn random(seed : SeedValue, i : usize) -> RandomValue
//! { ... }
//! ```
//! 
//! But this would require the user to explicitly keep track of the index somewhere.
//! `FroggyRand` uses a two stage approach, first it generates a hash value from its input argument.
//! Then it combines that with its seed to generate and index.
//! 
//! Here is how we would use `FroggyRand` with the example above:
//! 
//! ```
//! let froggy_rand = FroggyRand::new(seed);
//! let mut enemies = vec![];
//! for id in 0..100 {
//!   // We want the x position to be based on two things:
//!   //   The hash of the string "enemy_x" to make sure its different to the y value
//!   //   The enemy id
//!   let x = froggy_rand.gen(("enemy_x", id));
//!   let y = froggy_rand.gen(("enemy_y", id));
//!   let weapon_type = froggy_rand.gen(("weapon_type", id));
//!   enemies.push(Enemy::new(id, x, y, weapon_type));
//! }
//! ``` 
//! 
//! Now we can add as many new parameters as we want without them effecting each other. 
//! 
//! For a more detailed explanation see
//! [this talk.](https://www.youtube.com/watch?v=e4b--cyXEsM)

#![no_std]

use core::hash::{Hash, Hasher};
use core::num::Wrapping;

#[derive(Debug, Copy, Clone)]
pub struct FroggyRand {
    seed : u64,
}

fn split_mix_64(index : u64) -> u64 {
    let mut z = Wrapping(index) + Wrapping(0x9E3779B97F4A7C15);
    z = (z ^ (z >> 30)) * Wrapping(0xBF58476D1CE4E5B9);
    z = (z ^ (z >> 27)) * Wrapping(0x94D049BB133111EB);
    (z ^ (z >> 31)).0
}

#[inline]
fn hash<T : Hash>(x : T) -> u64 {
    let mut hasher = deterministic_hash::DeterministicHasher::new(hashers::jenkins::Lookup3Hasher::default());
    x.hash(&mut hasher);
    hasher.finish()
}

impl FroggyRand {
    pub fn new(seed : u64) -> Self {
        Self {seed}
    }

    pub fn from_hash<T : Hash>(x : T) -> Self {
        Self::new(hash(x))
    }

    /// Should be uniform over all u64 values
    pub fn gen<T : Hash>(&self, x : T) -> u64 {
        let hash = hash(x);
        let index = (Wrapping(self.seed) + Wrapping(hash)).0;
        split_mix_64(index)
    }

    /// Should be uniform in [0, 1]
    pub fn gen_unit<T : Hash>(&self, x : T) -> f64 {
        // Should be enough precision for a game
        (self.gen(x) % 1_000_000) as f64 / 1_000_000.0
    }

    /// Should be uniform in [min, max]
    pub fn gen_range<T : Hash>(&self, x : T, min : f64, max : f64) -> f64 {
        min + self.gen_unit(x) * (max - min)
    }

    /// Should give a uniform random element of the slice choices. 
    pub fn choose<'a, T : Hash, X>(&self, x : T, choices : &'a [X]) -> &'a X {
        // usize can be aliased to u32 or u64 in wasm based on the compilation
        // for safety we restrict to u32 range.
        let index = self.gen(x) as u64 % u32::MAX as u64;
        let i = index as usize % choices.len();
        &choices[i]
    }

    /// I dont know what a statistic is
    /// Approx normal dist https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution
    pub fn gen_froggy<T : Hash>(&self, x : T, min : f64, max : f64, n : u32) -> f64 {
        let mut sum = 0.;
        let gen_min = min / n as f64;
        let gen_max = max / n as f64;

        for i in 0..n {
            sum += self.gen_range((&x, i), gen_min, gen_max);
        }

        sum
    }

    pub fn gen_usize_range<T : Hash>(&self, x : T, min : usize, max : usize) -> usize {
        let range = 1 + max - min;
        min + ((self.gen(x) as usize) % range)
    }

    pub fn shuffle<T : Hash, X>(&self, x : T, xs : &mut [X]) {
        // Fisher-yates
        // See https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
        for i in 0..=xs.len()-2 {
            let j = self.gen_usize_range((&x, i), i, xs.len() - 1);
            xs.swap(i, j);
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::*;

    #[test]
    fn different_hashes() {
        let froggy_rand = FroggyRand::new(100);
        let val0 = froggy_rand.gen(("test", 0));
        let val1 = froggy_rand.gen(("test", 1));
        let val2 = froggy_rand.gen(("test_other", 0));
        let val3 = froggy_rand.gen(("test_other", 1));

        assert_ne!(val0, val1);
        assert_ne!(val0, val2);
        assert_ne!(val0, val3);

        assert_ne!(val1, val0);
        assert_ne!(val1, val2);
        assert_ne!(val1, val3);

        assert_ne!(val2, val0);
        assert_ne!(val2, val1);
        assert_ne!(val2, val3);

        assert_ne!(val3, val0);
        assert_ne!(val3, val1);
        assert_ne!(val3, val2);
    }
}