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 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}