use core::cell::RefCell;
use bare_metal::Mutex;
use crate::interrupt::free;
pub struct RandomNumberGenerator {
state: [u32; 4],
}
impl RandomNumberGenerator {
#[must_use]
pub const fn new() -> Self {
Self::new_with_seed([1014776995, 476057059, 3301633994, 706340607])
}
#[must_use]
pub const fn new_with_seed(seed: [u32; 4]) -> Self {
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 }
}
pub fn gen(&mut self) -> i32 {
let result = (self.state[0].wrapping_add(self.state[3]))
.rotate_left(7)
.wrapping_mul(9);
let t = self.state[1].wrapping_shr(9);
self.state[2] ^= self.state[0];
self.state[3] ^= self.state[1];
self.state[1] ^= self.state[2];
self.state[0] ^= self.state[3];
self.state[2] ^= t;
self.state[3] = self.state[3].rotate_left(11);
result as i32
}
}
impl Default for RandomNumberGenerator {
fn default() -> Self {
Self::new()
}
}
static GLOBAL_RNG: Mutex<RefCell<RandomNumberGenerator>> =
Mutex::new(RefCell::new(RandomNumberGenerator::new()));
#[must_use]
pub fn gen() -> i32 {
free(|cs| GLOBAL_RNG.borrow(cs).borrow_mut().gen())
}
#[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.gen().rem_euclid(16)) as usize] += 1;
}
for (i, &value) in values.iter().enumerate() {
assert!(
value >= 500 / 10 / 3,
"{i} came up less than expected {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::gen().rem_euclid(16) as usize] += 1;
}
for (i, &value) in values.iter().enumerate() {
assert!(
value >= 500 / 10 / 3,
"{i} came up less than expected {value}"
);
}
}
}