dice_roll/
dice_roll.rs

1/*
2Copyright 2021 Robin Marchart
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17use crate::dice_types::*;
18use rand::{distributions::Uniform, Rng};
19use std::convert::TryInto;
20
21#[cfg(feature = "logging")]
22use log::debug;
23
24#[derive(Debug, PartialEq, Eq)]
25pub enum EvaluationErrors {
26    DivideByZero,
27    Timeout,
28    Overflow,
29}
30
31impl std::fmt::Display for EvaluationErrors{
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f,"{}",match *self {
34            Self::DivideByZero=>"division by zero occurred",
35            Self::Timeout=>"timeout reached",
36            Self::Overflow=>"overflow occurred"
37        })
38    }
39}
40
41impl std::error::Error for EvaluationErrors{
42    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
43        None
44    }
45
46    fn cause(&self) -> Option<&dyn std::error::Error> {
47        self.source()
48    }
49}
50
51pub trait DiceEvaluate {
52    fn evaluate<T: FnMut() -> bool, R: Rng>(
53        &self,
54        timeout_f: &mut T,
55        rng: &mut R,
56    ) -> Result<(Vec<i64>, Vec<i64>), EvaluationErrors>;
57}
58
59impl DiceEvaluate for Dice {
60    fn evaluate<T: FnMut() -> bool, R: Rng>(
61        &self,
62        timeout_f: &mut T,
63        rng: &mut R,
64    ) -> Result<(Vec<i64>, Vec<i64>), EvaluationErrors> {
65        if timeout_f() {
66            return Err(EvaluationErrors::Timeout);
67        }
68        let mut rolls: Vec<i64> = Vec::with_capacity(self.throws.try_into().unwrap());
69        let mut roll_counter: u8 = 0;
70        match self.dice {
71            DiceType::Number(faces) => {
72                let dist = Uniform::new_inclusive(1, faces as i64);
73                for _ in 0..self.throws {
74                    roll_counter = roll_counter.wrapping_add(1);
75                    if roll_counter == 0 && timeout_f() {
76                        return Err(EvaluationErrors::Timeout);
77                    }
78                    rolls.push(rng.sample::<i64, _>(dist));
79                }
80            }
81            DiceType::Fudge => {
82                let dist: Uniform<i64> = Uniform::new_inclusive(-1, 1);
83                for _ in 0..self.throws {
84                    roll_counter = roll_counter.wrapping_add(1);
85                    if roll_counter == 0 && timeout_f() {
86                        return Err(EvaluationErrors::Timeout);
87                    }
88                    rolls.push(rng.sample(dist));
89                }
90            }
91            DiceType::Multiply(base_faces) => {
92                let dist = Uniform::new_inclusive(1, base_faces as i64);
93                for _ in 0..self.throws {
94                    roll_counter = roll_counter.wrapping_add(1);
95                    if roll_counter == 0 && timeout_f() {
96                        return Err(EvaluationErrors::Timeout);
97                    }
98                    rolls.push(
99                        rng.sample(dist)
100                            .checked_mul(rng.sample(dist))
101                            .ok_or(EvaluationErrors::Overflow)?,
102                    );
103                }
104            }
105        }
106
107        #[cfg(feature = "logging")]
108        {
109            debug!("Dice roll result for {} is {:?}", &self, &rolls);
110        }
111
112        let rolls_copy = rolls.clone();
113        Ok((rolls, rolls_copy))
114    }
115}
116
117impl DiceEvaluate for FilteredDice {
118    fn evaluate<T: FnMut() -> bool, R: Rng>(
119        &self,
120        timeout_f: &mut T,
121        rng: &mut R,
122    ) -> Result<(Vec<i64>, Vec<i64>), EvaluationErrors> {
123        let result = match self {
124            FilteredDice::Simple(dice) => dice.evaluate(timeout_f, rng),
125            FilteredDice::Filtered(dice, filter, target) => {
126                dice.evaluate(timeout_f, rng).map(|original| {
127                    (
128                        original
129                            .0
130                            .into_iter()
131                            .filter(match filter {
132                                Filter::Bigger => {
133                                    Box::new(|i: &i64| i > &(target.to_owned() as i64))
134                                        as Box<dyn Fn(&i64) -> bool>
135                                }
136                                Filter::BiggerEq => {
137                                    Box::new(|i: &i64| i > &(target.to_owned() as i64))
138                                        as Box<dyn Fn(&i64) -> bool>
139                                }
140                                Filter::Smaller => {
141                                    Box::new(|i: &i64| i < &(target.to_owned() as i64))
142                                        as Box<dyn Fn(&i64) -> bool>
143                                }
144                                Filter::SmallerEq => {
145                                    Box::new(|i: &i64| i <= &(target.to_owned() as i64))
146                                        as Box<dyn Fn(&i64) -> bool>
147                                }
148                                Filter::NotEq => {
149                                    Box::new(|i: &i64| i != &(target.to_owned() as i64))
150                                        as Box<dyn Fn(&i64) -> bool>
151                                }
152                            })
153                            .collect(),
154                        original.1,
155                    )
156                })
157            }
158        };
159        #[cfg(feature = "logging")]
160        {
161            debug!("rolled {:?} for filtered dice {}", &result, &self)
162        }
163        result
164    }
165}
166
167impl DiceEvaluate for SelectedDice {
168    fn evaluate<T: FnMut() -> bool, R: Rng>(
169        &self,
170        timeout_f: &mut T,
171        rng: &mut R,
172    ) -> Result<(Vec<i64>, Vec<i64>), EvaluationErrors> {
173        let result = match self {
174            SelectedDice::Unchanged(dice) => dice.evaluate(timeout_f, rng),
175            SelectedDice::Selected(dice, selector, max_size) => {
176                dice.evaluate(timeout_f, rng)
177                    .map(|original: (Vec<i64>, Vec<i64>)| {
178                        if original.0.len() > max_size.to_owned() as usize {
179                            let range = match selector {
180                                Selector::Higher => {
181                                    (original.0.len() - max_size.to_owned() as usize)
182                                        ..original.0.len()
183                                }
184                                Selector::Lower => (0..(max_size.to_owned() as usize)),
185                            };
186                            let mut source = original;
187                            source.0.sort_unstable();
188                            (source.0[range].to_vec(), source.1)
189                        } else {
190                            original
191                        }
192                    })
193            }
194        };
195        #[cfg(feature = "logging")]
196        {
197            debug!("rolled {:?} for selected dice {}", &result, &self)
198        }
199        result
200    }
201}
202
203pub trait TermEvaluate {
204    fn evaluate<T: FnMut() -> bool, R: Rng>(
205        &self,
206        timeout_f: &mut T,
207        rng: &mut R,
208    ) -> Result<(i64, Vec<i64>), EvaluationErrors>;
209}
210
211impl TermEvaluate for Term {
212    fn evaluate<T: FnMut() -> bool, R: Rng>(
213        &self,
214        timeout_f: &mut T,
215        rng: &mut R,
216    ) -> Result<(i64, Vec<i64>), EvaluationErrors> {
217        let result = match self {
218            Term::Constant(i) => Ok((i.to_owned(), Vec::new())),
219            Term::DiceThrow(dice) => dice.evaluate(timeout_f, rng).map(|roll_results| {
220                (
221                    roll_results.0.into_iter().reduce(|a, b| a + b).unwrap_or(0),
222                    roll_results.1,
223                )
224            }),
225            Term::SubTerm(term) => term.evaluate(timeout_f, rng),
226            Term::Calculation(left, op, right) => {
227                let left_r = left.evaluate(timeout_f, rng)?;
228                let right_r = right.evaluate(timeout_f, rng)?;
229                let result = match op {
230                    Operation::Add => left_r
231                        .0
232                        .checked_add(right_r.0)
233                        .ok_or(EvaluationErrors::Overflow),
234                    Operation::Sub => left_r
235                        .0
236                        .checked_sub(right_r.0)
237                        .ok_or(EvaluationErrors::Overflow),
238                    Operation::Mul => left_r
239                        .0
240                        .checked_mul(right_r.0)
241                        .ok_or(EvaluationErrors::Overflow),
242                    Operation::Div => left_r
243                        .0
244                        .checked_div(right_r.0)
245                        .ok_or(EvaluationErrors::DivideByZero),
246                }?;
247                Ok((result, [left_r.1, right_r.1].concat()))
248            }
249        };
250        #[cfg(feature = "logging")]
251        {
252            debug!("got {:?} for term {}", &result, &self)
253        }
254        result
255    }
256}
257
258impl TermEvaluate for Box<Term> {
259    fn evaluate<T: FnMut() -> bool, R: Rng>(
260        &self,
261        timeout_f: &mut T,
262        rng: &mut R,
263    ) -> Result<(i64, Vec<i64>), EvaluationErrors> {
264        self.as_ref().evaluate(timeout_f, rng)
265    }
266}
267
268pub trait ExpressionEvaluate {
269    fn evaluate<T: FnMut() -> bool, R: Rng>(
270        &self,
271        timeout_t: &mut T,
272        rng: &mut R,
273    ) -> Result<Vec<(i64, Vec<i64>)>, EvaluationErrors>;
274}
275
276impl ExpressionEvaluate for Expression {
277    fn evaluate<T: FnMut() -> bool, R: Rng>(
278        &self,
279        timeout_f: &mut T,
280        rng: &mut R,
281    ) -> Result<Vec<(i64, Vec<i64>)>, EvaluationErrors> {
282        match self {
283            Expression::Simple(term) => term.evaluate(timeout_f, rng).map(|res| vec![res]),
284            Expression::List(count, term) => {
285                let size: usize = (*count).try_into().expect("failed to convert u32 to usize");
286                let mut result_collector: Vec<(i64, Vec<i64>)> = Vec::with_capacity(size);
287                for _ in 0..size {
288                    result_collector.push(term.evaluate(timeout_f, rng)?);
289                }
290                Ok(result_collector)
291            }
292        }
293    }
294}