diff --git a/agb/src/hash_map.rs b/agb/src/hash_map.rs index 90094785..e6e98532 100644 --- a/agb/src/hash_map.rs +++ b/agb/src/hash_map.rs @@ -831,7 +831,7 @@ mod test { use core::cell::RefCell; use super::*; - use crate::{rng, Gba}; + use crate::{rng::RandomNumberGenerator, Gba}; #[test_case] fn can_store_and_retrieve_8_elements(_gba: &mut Gba) { @@ -956,13 +956,14 @@ mod test { #[test_case] fn extreme_case(_gba: &mut Gba) { let mut map = HashMap::new(); + let mut rng = RandomNumberGenerator::new(); let mut answers: [Option; 128] = [None; 128]; for _ in 0..5_000 { - let command = rng::next().rem_euclid(2); - let key = rng::next().rem_euclid(answers.len() as i32); - let value = rng::next(); + let command = rng.next().rem_euclid(2); + let key = rng.next().rem_euclid(answers.len() as i32); + let value = rng.next(); match command { 0 => { diff --git a/agb/src/rng.rs b/agb/src/rng.rs index 411f8976..bbbc2ae3 100644 --- a/agb/src/rng.rs +++ b/agb/src/rng.rs @@ -4,21 +4,34 @@ use bare_metal::Mutex; use crate::interrupt::free; +/// A fast pseudo-random number generator. Note that the output of the +/// random number generator for a given seed is guaranteed stable +/// between minor releases, however could change in a major release. pub struct RandomNumberGenerator { state: [u32; 4], } impl RandomNumberGenerator { + /// Create a new random number generator with a fixed seed + /// + /// Note that this seed is guaranteed to be the same between minor releases. pub const fn new() -> Self { - Self { - state: [1014776995, 476057059, 3301633994, 706340607], - } + Self::new_with_seed([1014776995, 476057059, 3301633994, 706340607]) } + /// Produces a random number generator with the given initial state / seed. + /// None of the values can be 0. pub const fn new_with_seed(seed: [u32; 4]) -> Self { + // this can't be in a loop because const + assert!(seed[0] != 0, "seed must not be 0"); + assert!(seed[1] != 0, "seed must not be 0"); + assert!(seed[2] != 0, "seed must not be 0"); + assert!(seed[3] != 0, "seed must not be 0"); + Self { state: seed } } + /// Returns the next value for the random number generator pub fn next(&mut self) -> i32 { let result = (self.state[0].wrapping_add(self.state[3])) .rotate_left(7) @@ -40,6 +53,50 @@ impl RandomNumberGenerator { static GLOBAL_RNG: Mutex> = Mutex::new(RefCell::new(RandomNumberGenerator::new())); +/// Using a global random number generator, provides the next random number pub fn next() -> i32 { free(|cs| GLOBAL_RNG.borrow(*cs).borrow_mut().next()) } + +#[cfg(test)] +mod test { + use super::*; + use crate::Gba; + + #[test_case] + fn should_be_reasonably_distributed(_gba: &mut Gba) { + let mut values: [u32; 16] = Default::default(); + + let mut rng = RandomNumberGenerator::new(); + for _ in 0..500 { + values[(rng.next().rem_euclid(16)) as usize] += 1; + } + + for (i, &value) in values.iter().enumerate() { + assert!( + value >= 500 / 10 / 3, + "{} came up less than expected {}", + i, + value + ); + } + } + + #[test_case] + fn global_rng_should_be_reasonably_distributed(_gba: &mut Gba) { + let mut values: [u32; 16] = Default::default(); + + for _ in 0..500 { + values[super::next().rem_euclid(16) as usize] += 1; + } + + for (i, &value) in values.iter().enumerate() { + assert!( + value >= 500 / 10 / 3, + "{} came up less than expected {}", + i, + value + ); + } + } +}