pub trait BattleRng {
fn next_u8(&mut self) -> u8;
fn range(&mut self, bound: u32) -> u32 {
if bound == 0 {
return 0;
}
if bound <= 256 {
return (self.next_u8() as u32) % bound;
}
let mut acc: u32 = 0;
let mut produced: u64 = 1;
while produced < bound as u64 {
acc = acc.wrapping_shl(8) | self.next_u8() as u32;
produced = produced.saturating_mul(256);
}
acc % bound
}
fn chance(&mut self, num: u32, den: u32) -> bool {
if den == 0 {
return false;
}
self.range(den) < num
}
}
#[derive(Debug, Clone)]
pub struct ScriptedRng {
bytes: Vec<u8>,
pos: usize,
}
impl ScriptedRng {
pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
Self {
bytes: bytes.into(),
pos: 0,
}
}
pub fn consumed(&self) -> usize {
self.pos
}
}
impl BattleRng for ScriptedRng {
fn next_u8(&mut self) -> u8 {
let b = if self.bytes.is_empty() {
0
} else if self.pos < self.bytes.len() {
self.bytes[self.pos]
} else {
*self.bytes.last().unwrap()
};
self.pos += 1;
b
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scripted_yields_bytes_in_order_then_repeats_last() {
let mut rng = ScriptedRng::new(vec![1, 2, 3]);
assert_eq!(rng.next_u8(), 1);
assert_eq!(rng.next_u8(), 2);
assert_eq!(rng.next_u8(), 3);
assert_eq!(rng.next_u8(), 3);
assert_eq!(rng.consumed(), 4);
}
#[test]
fn empty_script_yields_zero() {
let mut rng = ScriptedRng::new(Vec::new());
assert_eq!(rng.next_u8(), 0);
assert_eq!(rng.next_u8(), 0);
}
#[test]
fn range_zero_bound_is_zero() {
let mut rng = ScriptedRng::new(vec![200]);
assert_eq!(rng.range(0), 0);
}
#[test]
fn range_small_bound_is_modulo_of_byte() {
let mut rng = ScriptedRng::new(vec![10]);
assert_eq!(rng.range(4), 2);
}
#[test]
fn range_wide_bound_assembles_bytes() {
let mut rng = ScriptedRng::new(vec![0x00, 0x01]);
assert_eq!(rng.range(1000), 1);
}
#[test]
fn chance_uses_range() {
let mut rng = ScriptedRng::new(vec![0]);
assert!(rng.chance(1, 2));
let mut rng = ScriptedRng::new(vec![1]);
assert!(!rng.chance(1, 2));
}
#[test]
fn chance_zero_den_is_false() {
let mut rng = ScriptedRng::new(vec![0]);
assert!(!rng.chance(1, 0));
}
}