roll_rs/
roll.rs

1use crate::filtermodifier::FilterModifier;
2use rand_core::RngCore;
3use std::num::NonZeroU64;
4
5#[derive(Debug, Clone)]
6pub struct Roll {
7    pub vals: Vec<u64>,
8    pub total: i64,
9    pub sides: NonZeroU64,
10}
11
12pub fn roll_die(
13    times: u64,
14    sides: NonZeroU64,
15    fm: FilterModifier<u64>,
16    mut rng: impl RngCore,
17) -> Roll {
18    let mut rolls = Vec::new();
19    let range = sides.get();
20    for _ in 0..times {
21        let roll = (rng.next_u64() % range) + 1;
22        rolls.push(roll);
23    }
24
25    rolls.sort_unstable();
26
27    match fm {
28        FilterModifier::KeepLowest(i) => {
29            rolls.truncate(i as usize);
30        }
31        FilterModifier::KeepHighest(i) => {
32            rolls.reverse();
33            rolls.truncate(i as usize);
34        }
35        FilterModifier::DropLowest(i) => {
36            rolls.reverse();
37            rolls.truncate(rolls.len() - i.min(rolls.len() as u64) as usize)
38        }
39        FilterModifier::DropHighest(i) => {
40            rolls.truncate(rolls.len() - i.min(rolls.len() as u64) as usize)
41        }
42        FilterModifier::None => {}
43    }
44
45    // Shuffle order of results again
46    if !rolls.is_empty() {
47        let range = rolls.len() as u64;
48        for _ in 0..rolls.len() + 1 {
49            let a = rng.next_u64() % range + 1;
50            let b = rng.next_u64() % range + 1;
51            rolls.swap(a as usize - 1, b as usize - 1);
52        }
53    }
54
55    Roll {
56        total: rolls.iter().sum::<u64>() as i64,
57        vals: rolls,
58        sides,
59    }
60}
61
62const DIR: &[&str] = &[
63    "North",
64    "North East",
65    "East",
66    "South East",
67    "South",
68    "South West",
69    "West",
70    "North West",
71    "Stay",
72];
73
74pub fn roll_direction(rng: impl RngCore) -> String {
75    let value = roll_die(
76        1,
77        NonZeroU64::new(DIR.len() as u64).unwrap(),
78        FilterModifier::None,
79        rng,
80    );
81    DIR[value.total as usize - 1].to_string()
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use rand_core::{Error, RngCore};
88
89    struct DeterministicRng {
90        value: i64,
91    }
92
93    impl DeterministicRng {
94        pub fn new() -> Self {
95            Self { value: -1 }
96        }
97    }
98
99    impl RngCore for DeterministicRng {
100        fn next_u32(&mut self) -> u32 {
101            self.value += 1;
102            self.value as u32
103        }
104
105        fn next_u64(&mut self) -> u64 {
106            self.value += 1;
107            self.value as u64
108        }
109
110        fn fill_bytes(&mut self, _: &mut [u8]) {
111            unimplemented!()
112        }
113
114        fn try_fill_bytes(&mut self, _: &mut [u8]) -> Result<(), Error> {
115            unimplemented!()
116        }
117    }
118
119    #[test]
120    fn test_kl() {
121        let roll = roll_die(
122            6,
123            NonZeroU64::new(6).unwrap(),
124            FilterModifier::KeepLowest(3),
125            DeterministicRng::new(),
126        );
127
128        assert_eq!(roll.vals.len(), 3);
129        assert_eq!(roll.total, 6);
130    }
131
132    #[test]
133    fn test_dl_overflow() {
134        let roll = roll_die(
135            100,
136            NonZeroU64::new(6).unwrap(),
137            FilterModifier::DropLowest(300),
138            DeterministicRng::new(),
139        );
140        assert_eq!(roll.vals.len(), 0);
141        assert_eq!(roll.total, 0);
142    }
143}