caith/rollresult/
singlerollresult.rs

1use crate::{
2    error::Result, parser::TotalModifier, rollresult::DiceResult, rollresult::RollHistory,
3    rollresult::Value,
4};
5
6/// Carry the result of one roll and an history of the steps taken.
7///
8/// Usually created through [`super::RollResult::new_single()`] function.
9#[derive(Debug, Clone)]
10pub struct SingleRollResult {
11    /// Result of the roll. In the case of option `t` and/or `f` used, it's the number of `success -
12    /// failure`
13    total: i64,
14    /// History of the steps taken that lead to this result.
15    history: Vec<RollHistory>,
16    /// Internal usage field to avoid computing a total if it's already done.
17    dirty: bool,
18    constant: Option<f64>,
19}
20
21impl SingleRollResult {
22    /// Create an empty `SingleRollResult`
23    pub(crate) fn new() -> Self {
24        Self {
25            total: 0,
26            history: Vec::new(),
27            dirty: true,
28            constant: None,
29        }
30    }
31
32    /// Create a `SingleRollResult` with already a total. Used to carry constant value.
33    pub(crate) fn with_total(total: i64) -> Self {
34        Self {
35            total,
36            history: vec![RollHistory::Value(Value::Int(total))],
37            dirty: false,
38            constant: None,
39        }
40    }
41
42    /// Create a `SingleRollResult` with already a total. Used to carry float constant value.
43    pub(crate) fn with_float(f: f64) -> Self {
44        Self {
45            total: f as i64,
46            history: vec![RollHistory::Value(Value::Float(f))],
47            dirty: false,
48            constant: Some(f),
49        }
50    }
51
52    #[cfg(feature = "ova")]
53    /// Create a `SingleRollResult` with a history and a total.
54    pub(crate) fn with_total_and_hist(total: u64, history: Vec<DiceResult>) -> Self {
55        Self {
56            total: total as i64,
57            history: vec![RollHistory::Roll(history)],
58            dirty: false,
59            constant: None,
60        }
61    }
62
63    /// Get the history of the result
64    pub fn get_history(&self) -> &Vec<RollHistory> {
65        &self.history
66    }
67
68    /// Add a step in the history
69    pub(crate) fn add_history(&mut self, mut history: Vec<DiceResult>, is_fudge: bool) {
70        self.dirty = true;
71        history.sort_unstable_by(|a, b| b.cmp(a));
72        self.history.push(if is_fudge {
73            RollHistory::Fudge(history.iter().map(|r| r.res).collect())
74        } else {
75            RollHistory::Roll(history)
76        });
77    }
78
79    pub(crate) fn add_rerolled_history(&mut self, mut history: Vec<Vec<DiceResult>>) {
80        self.dirty = true;
81        history.sort_unstable_by(|a, b| b.cmp(a));
82        self.history.push(RollHistory::ReRolls(history));
83    }
84
85    pub(crate) fn add_parenthesis(&mut self) {
86        self.history.insert(0, RollHistory::OpenParenthesis);
87        self.history.push(RollHistory::CloseParenthesis);
88    }
89
90    /// Compute the total value according to some modifier
91    pub(crate) fn compute_total(&mut self, modifier: TotalModifier) -> Result<i64> {
92        if self.dirty {
93            self.dirty = false;
94            let mut flat = self.history.iter().fold(Vec::new(), |mut acc, h| {
95                match h {
96                    RollHistory::Roll(r) => {
97                        let mut c = r.iter().map(|u| u.res as i64).collect();
98                        acc.append(&mut c);
99                    }
100                    RollHistory::Fudge(r) => {
101                        let mut c = r.iter().map(|u| *u as i64).collect();
102                        acc.append(&mut c);
103                    }
104                    RollHistory::Value(v) => acc.push(v.get_value()),
105                    _ => (),
106                };
107                acc
108            });
109            flat.sort_unstable();
110            let flat = flat;
111            match modifier {
112                TotalModifier::KeepHi(n)
113                | TotalModifier::KeepLo(n)
114                | TotalModifier::DropHi(n)
115                | TotalModifier::DropLo(n) => {
116                    if n > flat.len() {
117                        return Err("Not enough dice to keep or drop".into());
118                    }
119                }
120                TotalModifier::None(_)
121                | TotalModifier::TargetFailureDouble(_, _, _)
122                | TotalModifier::TargetEnum(_)
123                | TotalModifier::Fudge => (),
124            }
125
126            let slice = match modifier {
127                TotalModifier::KeepHi(n) => &flat[flat.len() - n..],
128                TotalModifier::KeepLo(n) => &flat[..n],
129                TotalModifier::DropHi(n) => &flat[..flat.len() - n],
130                TotalModifier::DropLo(n) => &flat[n..],
131                TotalModifier::None(_)
132                | TotalModifier::TargetFailureDouble(_, _, _)
133                | TotalModifier::TargetEnum(_)
134                | TotalModifier::Fudge => flat.as_slice(),
135            };
136
137            self.total = match modifier {
138                TotalModifier::TargetFailureDouble(t, f, d) => slice.iter().fold(0, |acc, &x| {
139                    let x = x as u64;
140                    if d > 0 && x >= d {
141                        acc + 2
142                    } else if t > 0 && x >= t {
143                        acc + 1
144                    } else if f > 0 && x <= f {
145                        acc - 1
146                    } else {
147                        acc
148                    }
149                }),
150                TotalModifier::TargetEnum(v) => slice.iter().fold(0, |acc, &x| {
151                    if v.contains(&(x as u64)) {
152                        acc + 1
153                    } else {
154                        acc
155                    }
156                }),
157                TotalModifier::Fudge => slice.iter().fold(0, |acc, &x| {
158                    if x <= 2 {
159                        acc - 1
160                    } else if x <= 4 {
161                        acc
162                    } else {
163                        acc + 1
164                    }
165                }),
166                _ => slice.iter().sum::<i64>(),
167            };
168        }
169
170        Ok(self.total)
171    }
172
173    /// Get the result value
174    pub fn get_total(&self) -> i64 {
175        self.total
176    }
177
178    /// Says if the used value for math operation is 0
179    ///
180    /// If there's a constant stored, we'll use it and if not, `total` is used instead
181    pub fn is_zero(&self) -> bool {
182        if let Some(c) = self.constant {
183            c == 0.0
184        } else {
185            self.total == 0
186        }
187    }
188
189    /// Turn the vector of `RollHistory` to a `String`
190    pub fn to_string_history(&self) -> String {
191        self.history.iter().fold(String::new(), |mut s, v| {
192            s.push_str(v.to_string().as_str());
193            s
194        })
195    }
196
197    /// Turn the `RollResult` to a readable String, with or without markdown formatting.
198    pub fn to_string(&self, md: bool) -> String {
199        if self.history.is_empty() {
200            if md {
201                format!("`{}`", self.total)
202            } else {
203                format!("{}", self.total)
204            }
205        } else {
206            let s = self.to_string_history();
207            format!(
208                "{1}{0}{1} = {2}{3}{2}",
209                s,
210                if md { "`" } else { "" },
211                if md { "**" } else { "" },
212                self.get_total()
213            )
214        }
215    }
216}
217
218fn merge_history(left: &mut SingleRollResult, right: &mut SingleRollResult, op: &'static str) {
219    if !right.history.is_empty() {
220        left.history.push(RollHistory::Separator(op));
221        left.history.append(&mut right.history);
222    }
223}
224
225impl std::ops::Add for SingleRollResult {
226    type Output = Self;
227
228    fn add(mut self, mut rhs: Self) -> Self::Output {
229        merge_history(&mut self, &mut rhs, " + ");
230        let total = match (self.constant, rhs.constant) {
231            (None, None) => self.total + rhs.total,
232            (None, Some(constant)) => (self.total as f64 + constant).trunc() as i64,
233            (Some(constant), None) => (constant + rhs.total as f64).trunc() as i64,
234            (Some(lconstant), Some(rconstant)) => (lconstant + rconstant).trunc() as i64,
235        };
236        SingleRollResult {
237            total,
238            history: self.history,
239            dirty: false,
240            constant: None,
241        }
242    }
243}
244
245impl std::ops::Sub for SingleRollResult {
246    type Output = Self;
247
248    fn sub(mut self, mut rhs: Self) -> Self::Output {
249        merge_history(&mut self, &mut rhs, " - ");
250        let total = match (self.constant, rhs.constant) {
251            (None, None) => self.total - rhs.total,
252            (None, Some(constant)) => (self.total as f64 - constant).trunc() as i64,
253            (Some(constant), None) => (constant - rhs.total as f64).trunc() as i64,
254            (Some(lconstant), Some(rconstant)) => (lconstant - rconstant).trunc() as i64,
255        };
256        SingleRollResult {
257            total,
258            history: self.history,
259            dirty: false,
260            constant: None,
261        }
262    }
263}
264
265impl std::ops::Mul for SingleRollResult {
266    type Output = Self;
267
268    fn mul(mut self, mut rhs: Self) -> Self::Output {
269        merge_history(&mut self, &mut rhs, " * ");
270        let total = match (self.constant, rhs.constant) {
271            (None, None) => self.total * rhs.total,
272            (None, Some(constant)) => (self.total as f64 * constant).trunc() as i64,
273            (Some(constant), None) => (constant * rhs.total as f64).trunc() as i64,
274            (Some(lconstant), Some(rconstant)) => (lconstant * rconstant).trunc() as i64,
275        };
276        SingleRollResult {
277            total,
278            history: self.history,
279            dirty: false,
280            constant: None,
281        }
282    }
283}
284
285impl std::ops::Div for SingleRollResult {
286    type Output = Self;
287
288    fn div(mut self, mut rhs: Self) -> Self::Output {
289        merge_history(&mut self, &mut rhs, " / ");
290        let total = match (self.constant, rhs.constant) {
291            (None, None) => self.total / rhs.total,
292            (None, Some(constant)) => (self.total as f64 / constant).trunc() as i64,
293            (Some(constant), None) => (constant / rhs.total as f64).trunc() as i64,
294            (Some(lconstant), Some(rconstant)) => (lconstant / rconstant).trunc() as i64,
295        };
296        SingleRollResult {
297            total,
298            history: self.history,
299            dirty: false,
300            constant: None,
301        }
302    }
303}