#[cfg(feature = "parsing")]
use crate::prelude::{parse_dice_string, DiceParseError, DiceType};
use rand::{Rng, RngCore, SeedableRng};
use rand_xorshift::XorShiftRng;
#[cfg(feature = "serde")]
use serde_crate::{Deserialize, Serialize};
#[cfg(target_arch = "wasm32")]
fn get_seed() -> u64 {
let mut buf = [0u8; 8];
if crate::js_seed::getrandom_inner(&mut buf).is_ok() {
u64::from_be_bytes(buf)
} else {
js_sys::Date::now() as u64
}
}
#[cfg(not(target_arch = "wasm32"))]
fn get_seed() -> u64 {
let mut buf = [0u8; 8];
if getrandom::getrandom(&mut buf).is_ok() {
u64::from_be_bytes(buf)
} else {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as u64
}
}
#[derive(Clone)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub struct RandomNumberGenerator {
rng: XorShiftRng,
}
impl RandomNumberGenerator {
#[allow(clippy::new_without_default)] pub fn new() -> RandomNumberGenerator {
let rng: XorShiftRng = SeedableRng::seed_from_u64(get_seed());
RandomNumberGenerator { rng }
}
pub fn seeded(seed: u64) -> RandomNumberGenerator {
let rng: XorShiftRng = SeedableRng::seed_from_u64(seed);
RandomNumberGenerator { rng }
}
pub fn rand<T>(&mut self) -> T
where
rand::distributions::Standard: rand::distributions::Distribution<T>,
{
self.rng.gen::<T>()
}
pub fn range<T>(&mut self, min: T, max: T) -> T
where
T: rand::distributions::uniform::SampleUniform + PartialOrd,
{
self.rng.gen_range(min..max)
}
pub fn roll_dice(&mut self, n: i32, die_type: i32) -> i32 {
(0..n).map(|_| self.range(1, die_type + 1)).sum()
}
pub fn next_u64(&mut self) -> u64 {
self.rng.next_u64()
}
#[cfg(feature = "parsing")]
pub fn roll(&mut self, dice: DiceType) -> i32 {
self.roll_dice(dice.n_dice, dice.die_type) + dice.bonus
}
#[cfg(feature = "parsing")]
pub fn roll_str<S: ToString>(&mut self, dice: S) -> Result<i32, DiceParseError> {
match parse_dice_string(&dice.to_string()) {
Ok(dt) => Ok(self.roll(dt)),
Err(e) => Err(e),
}
}
pub fn random_slice_index<T>(&mut self, slice: &[T]) -> Option<usize> {
if slice.is_empty() {
None
} else {
let sz = slice.len();
if sz == 1 {
Some(0)
} else {
Some(self.roll_dice(1, sz as i32) as usize - 1)
}
}
}
pub fn random_slice_entry<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> {
if slice.is_empty() {
None
} else {
let sz = slice.len();
if sz == 1 {
Some(&slice[0])
} else {
Some(&slice[self.roll_dice(1, sz as i32) as usize - 1])
}
}
}
pub fn get_rng(&mut self) -> &mut XorShiftRng {
&mut self.rng
}
}
#[cfg(test)]
mod tests {
use crate::prelude::RandomNumberGenerator;
#[test]
fn roll_str_1d6() {
let mut rng = RandomNumberGenerator::new();
assert!(rng.roll_str("1d6").is_ok());
}
#[test]
fn roll_str_3d6plus1() {
let mut rng = RandomNumberGenerator::new();
assert!(rng.roll_str("3d6+1").is_ok());
}
#[test]
fn roll_str_3d20minus1() {
let mut rng = RandomNumberGenerator::new();
assert!(rng.roll_str("3d20-1").is_ok());
}
#[test]
fn roll_str_error() {
let mut rng = RandomNumberGenerator::new();
assert!(rng.roll_str("blah").is_err());
}
#[test]
fn test_roll_range() {
let mut rng = RandomNumberGenerator::new();
for _ in 0..100 {
let n = rng.roll_dice(1, 20);
assert!(n > 0 && n < 21);
}
}
#[test]
fn random_slice_index_empty() {
let mut rng = RandomNumberGenerator::new();
let test_data: Vec<i32> = Vec::new();
assert!(rng.random_slice_index(&test_data).is_none());
}
#[test]
fn random_slice_index_valid() {
let mut rng = RandomNumberGenerator::new();
let test_data: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for _ in 0..100 {
match rng.random_slice_index(&test_data) {
None => assert!(1 == 2),
Some(idx) => assert!(idx < test_data.len()),
}
}
}
#[test]
fn random_slice_entry_empty() {
let mut rng = RandomNumberGenerator::new();
let test_data: Vec<i32> = Vec::new();
assert!(rng.random_slice_entry(&test_data).is_none());
}
#[test]
fn random_slice_entry_valid() {
let mut rng = RandomNumberGenerator::new();
let test_data: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for _ in 0..100 {
match rng.random_slice_entry(&test_data) {
None => assert!(1 == 2),
Some(e) => assert!(*e > 0 && *e < 11),
}
}
}
#[cfg(feature = "serde")]
#[test]
fn serialize_rng() {
use serde_crate::{Deserialize, Serialize};
let mut rng = RandomNumberGenerator::seeded(1000);
let serialized = serde_json::to_string(&rng).unwrap();
let n = rng.range(0, 100);
let mut deserialized: RandomNumberGenerator = serde_json::from_str(&serialized).unwrap();
let n2 = deserialized.range(0, 100);
assert!(n == n2);
}
}