1use crate::{
2 error::Result, parser::TotalModifier, rollresult::DiceResult, rollresult::RollHistory,
3 rollresult::Value,
4};
5
6#[derive(Debug, Clone)]
10pub struct SingleRollResult {
11 total: i64,
14 history: Vec<RollHistory>,
16 dirty: bool,
18 constant: Option<f64>,
19}
20
21impl SingleRollResult {
22 pub(crate) fn new() -> Self {
24 Self {
25 total: 0,
26 history: Vec::new(),
27 dirty: true,
28 constant: None,
29 }
30 }
31
32 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 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 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 pub fn get_history(&self) -> &Vec<RollHistory> {
65 &self.history
66 }
67
68 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 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 pub fn get_total(&self) -> i64 {
175 self.total
176 }
177
178 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 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 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}