battler/rng/
prng.rs

1use std::any::Any;
2
3use rand::Rng;
4
5/// A pseudo-random number generator, created with the intention of using a random number generator
6/// that can be deterministically "replayed" for battle simulations.
7pub trait PseudoRandomNumberGenerator {
8    /// Returns the initial seed the generator was created with.
9    ///
10    /// The initial seed can be used to replay the random number generation sequence.
11    fn initial_seed(&self) -> u64;
12
13    /// Returns the next integer in the sequence.
14    fn next(&mut self) -> u64;
15
16    /// Mutable cast to [`Any`]` for testing.
17    fn as_any_mut(&mut self) -> &mut dyn Any;
18}
19
20pub struct RealPseudoRandomNumberGenerator {
21    initial_seed: u64,
22    seed: u64,
23}
24
25impl RealPseudoRandomNumberGenerator {
26    /// Creates a new random number generator.
27    ///
28    /// If two random number generators are created with the same seed, their output should be
29    /// exactly the same.
30    pub fn new(seed: Option<u64>) -> Self {
31        let seed = seed.unwrap_or_else(|| Self::generate_seed());
32        Self {
33            initial_seed: seed,
34            seed,
35        }
36    }
37
38    fn generate_seed() -> u64 {
39        let mut rng = rand::thread_rng();
40        rng.gen()
41    }
42
43    /// Linear Congruential Generator (LCRNG).
44    fn next_seed(seed: u64) -> u64 {
45        // Constants in the generation V and VI games.
46        const A: u64 = 0x5D588B656C078965;
47        const C: u64 = 0x0000000000269EC3;
48        seed.wrapping_mul(A).overflowing_add(C).0
49    }
50}
51
52impl PseudoRandomNumberGenerator for RealPseudoRandomNumberGenerator {
53    fn initial_seed(&self) -> u64 {
54        self.initial_seed
55    }
56
57    fn next(&mut self) -> u64 {
58        self.seed = Self::next_seed(self.seed);
59        // Use the upper 32 bits. The lower ones are predictable in some situations.
60        self.seed >> 32
61    }
62
63    fn as_any_mut(&mut self) -> &mut dyn Any {
64        self
65    }
66}
67
68#[cfg(test)]
69mod prng_tests {
70    use crate::rng::{
71        PseudoRandomNumberGenerator,
72        RealPseudoRandomNumberGenerator,
73    };
74
75    #[test]
76    fn stores_initial_seed() {
77        assert_eq!(
78            RealPseudoRandomNumberGenerator::new(Some(12345)).initial_seed(),
79            12345
80        );
81        assert_eq!(
82            RealPseudoRandomNumberGenerator::new(Some(6789100000)).initial_seed(),
83            6789100000
84        );
85    }
86}