bracket_random/
random.rs

1#[cfg(feature = "parsing")]
2use crate::prelude::{parse_dice_string, DiceParseError, DiceType};
3use rand::{Rng, RngCore, SeedableRng};
4use rand_xorshift::XorShiftRng;
5
6#[cfg(feature = "serde")]
7use serde_crate::{Deserialize, Serialize};
8
9#[cfg(target_arch = "wasm32")]
10fn get_seed() -> u64 {
11    let mut buf = [0u8; 8];
12    if crate::js_seed::getrandom_inner(&mut buf).is_ok() {
13        u64::from_be_bytes(buf)
14    } else {
15        js_sys::Date::now() as u64
16    }
17}
18
19#[cfg(not(target_arch = "wasm32"))]
20fn get_seed() -> u64 {
21    let mut buf = [0u8; 8];
22    if getrandom::getrandom(&mut buf).is_ok() {
23        u64::from_be_bytes(buf)
24    } else {
25        use std::time::{SystemTime, UNIX_EPOCH};
26        SystemTime::now()
27            .duration_since(UNIX_EPOCH)
28            .unwrap()
29            .as_secs() as u64
30    }
31}
32
33#[derive(Clone)]
34#[cfg_attr(
35    feature = "serde",
36    derive(Serialize, Deserialize),
37    serde(crate = "serde_crate")
38)]
39pub struct RandomNumberGenerator {
40    rng: XorShiftRng,
41}
42
43impl RandomNumberGenerator {
44    /// Creates a new RNG from a randomly generated seed
45    #[allow(clippy::new_without_default)] // XorShiftRng doesn't have a Default, so we don't either
46    pub fn new() -> RandomNumberGenerator {
47        let rng: XorShiftRng = SeedableRng::seed_from_u64(get_seed());
48        RandomNumberGenerator { rng }
49    }
50
51    /// Creates a new RNG from a specific seed
52    pub fn seeded(seed: u64) -> RandomNumberGenerator {
53        let rng: XorShiftRng = SeedableRng::seed_from_u64(seed);
54        RandomNumberGenerator { rng }
55    }
56
57    /// Returns a random value of whatever type you specify
58    pub fn rand<T>(&mut self) -> T
59    where
60        rand::distributions::Standard: rand::distributions::Distribution<T>,
61    {
62        self.rng.gen::<T>()
63    }
64
65    /// Returns a random value in the specified range, of type specified at the call site.
66    /// This is INCLUSIVE of the first parameter, and EXCLUSIVE of the second.
67    /// So range(1,6) will give you numbers from 1 to 5.
68    pub fn range<T>(&mut self, min: T, max: T) -> T
69    where
70        T: rand::distributions::uniform::SampleUniform + PartialOrd,
71    {
72        self.rng.gen_range(min..max)
73    }
74
75    /// Rolls dice, using the classic 3d6 type of format: n is the number of dice, die_type is the size of the dice.
76    pub fn roll_dice(&mut self, n: i32, die_type: i32) -> i32 {
77        (0..n).map(|_| self.range(1, die_type + 1)).sum()
78    }
79
80    /// Returns the RNG's next unsigned-64 type
81    pub fn next_u64(&mut self) -> u64 {
82        self.rng.next_u64()
83    }
84
85    /// Rolls dice based on a DiceType struct, including application of the bonus
86    #[cfg(feature = "parsing")]
87    pub fn roll(&mut self, dice: DiceType) -> i32 {
88        self.roll_dice(dice.n_dice, dice.die_type) + dice.bonus
89    }
90
91    /// Rolls dice based on passing in a string, such as roll_str("1d12")
92    #[cfg(feature = "parsing")]
93    pub fn roll_str<S: ToString>(&mut self, dice: S) -> Result<i32, DiceParseError> {
94        match parse_dice_string(&dice.to_string()) {
95            Ok(dt) => Ok(self.roll(dt)),
96            Err(e) => Err(e),
97        }
98    }
99
100    /// Returns a random index into a slice
101    pub fn random_slice_index<T>(&mut self, slice: &[T]) -> Option<usize> {
102        if slice.is_empty() {
103            None
104        } else {
105            let sz = slice.len();
106            if sz == 1 {
107                Some(0)
108            } else {
109                Some(self.roll_dice(1, sz as i32) as usize - 1)
110            }
111        }
112    }
113
114    /// Returns a random entry in a slice (or none if empty)
115    pub fn random_slice_entry<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> {
116        if slice.is_empty() {
117            None
118        } else {
119            let sz = slice.len();
120            if sz == 1 {
121                Some(&slice[0])
122            } else {
123                Some(&slice[self.roll_dice(1, sz as i32) as usize - 1])
124            }
125        }
126    }
127
128    /// Get underlying RNG implementation for use in traits / algorithms exposed by
129    /// other crates (eg. `rand` itself)
130    pub fn get_rng(&mut self) -> &mut XorShiftRng {
131        &mut self.rng
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use crate::prelude::RandomNumberGenerator;
138
139    #[test]
140    fn roll_str_1d6() {
141        let mut rng = RandomNumberGenerator::new();
142        assert!(rng.roll_str("1d6").is_ok());
143    }
144
145    #[test]
146    fn roll_str_3d6plus1() {
147        let mut rng = RandomNumberGenerator::new();
148        assert!(rng.roll_str("3d6+1").is_ok());
149    }
150
151    #[test]
152    fn roll_str_3d20minus1() {
153        let mut rng = RandomNumberGenerator::new();
154        assert!(rng.roll_str("3d20-1").is_ok());
155    }
156
157    #[test]
158    fn roll_str_error() {
159        let mut rng = RandomNumberGenerator::new();
160        assert!(rng.roll_str("blah").is_err());
161    }
162
163    #[test]
164    fn test_roll_range() {
165        let mut rng = RandomNumberGenerator::new();
166        for _ in 0..100 {
167            let n = rng.roll_dice(1, 20);
168            assert!(n > 0 && n < 21);
169        }
170    }
171
172    #[test]
173    fn random_slice_index_empty() {
174        let mut rng = RandomNumberGenerator::new();
175        let test_data: Vec<i32> = Vec::new();
176        assert!(rng.random_slice_index(&test_data).is_none());
177    }
178
179    #[test]
180    fn random_slice_index_valid() {
181        let mut rng = RandomNumberGenerator::new();
182        let test_data: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
183        for _ in 0..100 {
184            match rng.random_slice_index(&test_data) {
185                None => assert!(1 == 2),
186                Some(idx) => assert!(idx < test_data.len()),
187            }
188        }
189    }
190
191    #[test]
192    fn random_slice_entry_empty() {
193        let mut rng = RandomNumberGenerator::new();
194        let test_data: Vec<i32> = Vec::new();
195        assert!(rng.random_slice_entry(&test_data).is_none());
196    }
197
198    #[test]
199    fn random_slice_entry_valid() {
200        let mut rng = RandomNumberGenerator::new();
201        let test_data: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
202        for _ in 0..100 {
203            match rng.random_slice_entry(&test_data) {
204                None => assert!(1 == 2),
205                Some(e) => assert!(*e > 0 && *e < 11),
206            }
207        }
208    }
209
210    #[cfg(feature = "serde")]
211    #[test]
212    fn serialize_rng() {
213        use serde_crate::{Deserialize, Serialize};
214        let mut rng = RandomNumberGenerator::seeded(1000);
215        let serialized = serde_json::to_string(&rng).unwrap();
216        let n = rng.range(0, 100);
217        let mut deserialized: RandomNumberGenerator = serde_json::from_str(&serialized).unwrap();
218        let n2 = deserialized.range(0, 100);
219        assert!(n == n2);
220    }
221}