Skip to main content

axfive_matrix_dicebot/
roll.rs

1use crate::dice;
2use rand::prelude::*;
3use std::fmt;
4use std::ops::{Deref, DerefMut};
5
6pub trait Roll {
7    type Output;
8
9    fn roll(&self) -> Self::Output;
10}
11
12pub trait Rolled {
13    fn rolled_value(&self) -> i32;
14}
15
16#[derive(Debug, PartialEq, Eq, Clone)]
17pub struct DiceRoll(Vec<u32>);
18
19impl DiceRoll {
20    pub fn rolls(&self) -> &[u32] {
21        &self.0
22    }
23
24    pub fn total(&self) -> u32 {
25        self.0.iter().sum()
26    }
27}
28
29impl Rolled for DiceRoll {
30    fn rolled_value(&self) -> i32 {
31        self.total() as i32
32    }
33}
34
35impl fmt::Display for DiceRoll {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        write!(f, "{}", self.rolled_value())?;
38        let rolls = self.rolls();
39        let mut iter = rolls.iter();
40        if let Some(first) = iter.next() {
41            write!(f, " ({}", first)?;
42            for roll in iter {
43                write!(f, " + {}", roll)?;
44            }
45            write!(f, ")")?;
46        }
47        Ok(())
48    }
49}
50
51impl Roll for dice::Dice {
52    type Output = DiceRoll;
53
54    fn roll(&self) -> DiceRoll {
55        let mut rng = rand::thread_rng();
56        let rolls = (0..self.count)
57            .map(|_| rng.gen_range(1, self.sides + 1))
58            .collect();
59        DiceRoll(rolls)
60    }
61}
62
63#[derive(Debug, PartialEq, Eq, Clone)]
64pub enum ElementRoll {
65    Dice(DiceRoll),
66    Bonus(u32),
67}
68
69impl Rolled for ElementRoll {
70    fn rolled_value(&self) -> i32 {
71        match self {
72            ElementRoll::Dice(d) => d.rolled_value(),
73            ElementRoll::Bonus(b) => *b as i32,
74        }
75    }
76}
77
78impl Roll for dice::Element {
79    type Output = ElementRoll;
80
81    fn roll(&self) -> ElementRoll {
82        match self {
83            dice::Element::Dice(d) => ElementRoll::Dice(d.roll()),
84            dice::Element::Bonus(b) => ElementRoll::Bonus(*b),
85        }
86    }
87}
88
89impl fmt::Display for ElementRoll {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            ElementRoll::Dice(d) => write!(f, "{}", d),
93            ElementRoll::Bonus(b) => write!(f, "{}", b),
94        }
95    }
96}
97
98#[derive(Debug, PartialEq, Eq, Clone)]
99pub enum SignedElementRoll {
100    Positive(ElementRoll),
101    Negative(ElementRoll),
102}
103
104impl Rolled for SignedElementRoll {
105    fn rolled_value(&self) -> i32 {
106        match self {
107            SignedElementRoll::Positive(e) => e.rolled_value(),
108            SignedElementRoll::Negative(e) => -e.rolled_value(),
109        }
110    }
111}
112
113impl Roll for dice::SignedElement {
114    type Output = SignedElementRoll;
115
116    fn roll(&self) -> SignedElementRoll {
117        match self {
118            dice::SignedElement::Positive(e) => SignedElementRoll::Positive(e.roll()),
119            dice::SignedElement::Negative(e) => SignedElementRoll::Negative(e.roll()),
120        }
121    }
122}
123
124impl fmt::Display for SignedElementRoll {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            SignedElementRoll::Positive(e) => write!(f, "{}", e),
128            SignedElementRoll::Negative(e) => write!(f, "-{}", e),
129        }
130    }
131}
132
133#[derive(Debug, PartialEq, Eq, Clone)]
134pub struct ElementExpressionRoll(Vec<SignedElementRoll>);
135
136impl Deref for ElementExpressionRoll {
137    type Target = Vec<SignedElementRoll>;
138
139    fn deref(&self) -> &Self::Target {
140        &self.0
141    }
142}
143
144impl DerefMut for ElementExpressionRoll {
145    fn deref_mut(&mut self) -> &mut Self::Target {
146        &mut self.0
147    }
148}
149
150impl Rolled for ElementExpressionRoll {
151    fn rolled_value(&self) -> i32 {
152        self.iter().map(Rolled::rolled_value).sum()
153    }
154}
155
156impl Roll for dice::ElementExpression {
157    type Output = ElementExpressionRoll;
158
159    fn roll(&self) -> ElementExpressionRoll {
160        ElementExpressionRoll(self.iter().map(Roll::roll).collect())
161    }
162}
163
164impl fmt::Display for ElementExpressionRoll {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        if self.len() > 1 {
167            write!(f, "{}", self.rolled_value())?;
168            let mut iter = self.0.iter();
169            if let Some(first) = iter.next() {
170                write!(f, " ({}", first)?;
171                for roll in iter {
172                    match roll {
173                        SignedElementRoll::Positive(e) => write!(f, " + {}", e)?,
174                        SignedElementRoll::Negative(e) => write!(f, " - {}", e)?,
175                    }
176                }
177                write!(f, ")")?;
178            }
179            Ok(())
180        } else if self.len() > 0 {
181            // For a single item, just show the inner item to avoid redundancy
182            let first = self.0.iter().next().unwrap();
183            write!(f, "{}", first)
184        } else {
185            write!(f, "0")
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    #[test]
194    fn dice_roll_display_test() {
195        assert_eq!(DiceRoll(vec![1, 3, 4]).to_string(), "8 (1 + 3 + 4)");
196        assert_eq!(DiceRoll(vec![]).to_string(), "0");
197        assert_eq!(
198            DiceRoll(vec![4, 7, 2, 10]).to_string(),
199            "23 (4 + 7 + 2 + 10)"
200        );
201    }
202
203    #[test]
204    fn element_roll_display_test() {
205        assert_eq!(
206            ElementRoll::Dice(DiceRoll(vec![1, 3, 4])).to_string(),
207            "8 (1 + 3 + 4)"
208        );
209        assert_eq!(ElementRoll::Bonus(7).to_string(), "7");
210    }
211
212    #[test]
213    fn signed_element_roll_display_test() {
214        assert_eq!(
215            SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 3, 4]))).to_string(),
216            "8 (1 + 3 + 4)"
217        );
218        assert_eq!(
219            SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 3, 4]))).to_string(),
220            "-8 (1 + 3 + 4)"
221        );
222        assert_eq!(
223            SignedElementRoll::Positive(ElementRoll::Bonus(7)).to_string(),
224            "7"
225        );
226        assert_eq!(
227            SignedElementRoll::Negative(ElementRoll::Bonus(7)).to_string(),
228            "-7"
229        );
230    }
231
232    #[test]
233    fn element_expression_roll_display_test() {
234        assert_eq!(
235            ElementExpressionRoll(vec![SignedElementRoll::Positive(ElementRoll::Dice(
236                DiceRoll(vec![1, 3, 4])
237            )),])
238            .to_string(),
239            "8 (1 + 3 + 4)"
240        );
241        assert_eq!(
242            ElementExpressionRoll(vec![SignedElementRoll::Negative(ElementRoll::Dice(
243                DiceRoll(vec![1, 3, 4])
244            )),])
245            .to_string(),
246            "-8 (1 + 3 + 4)"
247        );
248        assert_eq!(
249            ElementExpressionRoll(vec![SignedElementRoll::Positive(ElementRoll::Bonus(7)),])
250                .to_string(),
251            "7"
252        );
253        assert_eq!(
254            ElementExpressionRoll(vec![SignedElementRoll::Negative(ElementRoll::Bonus(7)),])
255                .to_string(),
256            "-7"
257        );
258        assert_eq!(
259            ElementExpressionRoll(vec![
260                SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 3, 4]))),
261                SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 2]))),
262                SignedElementRoll::Positive(ElementRoll::Bonus(4)),
263                SignedElementRoll::Negative(ElementRoll::Bonus(7)),
264            ])
265            .to_string(),
266            "2 (8 (1 + 3 + 4) - 3 (1 + 2) + 4 - 7)"
267        );
268        assert_eq!(
269            ElementExpressionRoll(vec![
270                SignedElementRoll::Negative(ElementRoll::Dice(DiceRoll(vec![1, 3, 4]))),
271                SignedElementRoll::Positive(ElementRoll::Dice(DiceRoll(vec![1, 2]))),
272                SignedElementRoll::Negative(ElementRoll::Bonus(4)),
273                SignedElementRoll::Positive(ElementRoll::Bonus(7)),
274            ])
275            .to_string(),
276            "-2 (-8 (1 + 3 + 4) + 3 (1 + 2) - 4 + 7)"
277        );
278    }
279}