tyche/dice/modifier.rs
1//! Dice modifiers and their related types.
2
3use alloc::{
4 borrow::ToOwned,
5 format,
6 string::{String, ToString},
7 vec::Vec,
8};
9use core::fmt;
10
11use super::{roller::Roller, Error, Rolled};
12
13/// Routines that can be applied to [`Dice`](super::Dice) to automatically manipulate resulting [`Rolled`] dice sets
14/// from them as part of their rolling process.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16#[non_exhaustive]
17pub enum Modifier {
18 /// Rerolls (drops original and adds a newly-rolled die) dice that meet a condition.
19 ///
20 /// # Examples
21 ///
22 /// ## Reroll recursively (`rr`)
23 /// ```
24 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
25 ///
26 /// // Build the 4d6rr1 dice set and create a roller that has predetermined values for the dice rolls
27 /// let dice = Dice::builder().count(4).sides(6).reroll(Condition::Eq(1), true).build();
28 /// let premade_rolls = [3, 6, 1, 2, 1, 4];
29 /// let mut rng = IterRoller::new(premade_rolls);
30 ///
31 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
32 /// // Only the first four predetermined values will be used for the dice rolls since there are only four dice to roll.
33 /// let mut rolled = rng.roll(&dice, false)?;
34 ///
35 /// // Explicitly create and apply an rr1 modifier. This is usually not necessary since the dice has its own instance of
36 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
37 /// // for demonstration purposes.
38 /// let rr1_mod = Modifier::Reroll {
39 /// cond: Condition::Eq(1),
40 /// recurse: true,
41 /// };
42 /// rr1_mod.apply(&mut rolled, &mut rng)?;
43 ///
44 /// // Upon being applied, the modifier will drop the 1 roll, roll a new 1 from the predetermined RNG, drop that too,
45 /// // then roll a new 4 from the RNG.
46 /// // Final expected rolled dice set, after rr1 modifier: 4d6rr1[3, 6, 1 (d), 2, 1 (d), 4]
47 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
48 /// expected.rolls[2].drop(rr1_mod);
49 /// expected.rolls[4].add(rr1_mod);
50 /// expected.rolls[4].drop(rr1_mod);
51 /// expected.rolls[5].add(rr1_mod);
52 /// assert_eq!(rolled, expected);
53 /// # Ok::<(), tyche::dice::Error>(())
54 /// ```
55 ///
56 /// ## Reroll once (`r`)
57 /// ```
58 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
59 ///
60 /// // Build the 4d6r1 dice set and create a roller that has predetermined values for the dice rolls
61 /// let dice = Dice::builder().count(4).sides(6).reroll(Condition::Eq(1), false).build();
62 /// let premade_rolls = [3, 6, 1, 2, 1];
63 /// let mut rng = IterRoller::new(premade_rolls);
64 ///
65 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
66 /// // Only the first four predetermined values will be used for the dice rolls since there are only four dice to roll.
67 /// let mut rolled = rng.roll(&dice, false)?;
68 ///
69 /// // Explicitly create and apply an r1 modifier. This is usually not necessary since the dice has its own instance of
70 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
71 /// // for demonstration purposes.
72 /// let r1_mod = Modifier::Reroll {
73 /// cond: Condition::Eq(1),
74 /// recurse: false,
75 /// };
76 /// r1_mod.apply(&mut rolled, &mut rng)?;
77 ///
78 /// // Upon being applied, the modifier will drop the 1 roll and roll a new 1 from the predetermined RNG.
79 /// // Final expected rolled dice set, after r1 modifier: 4d6r1[3, 6, 1 (d), 2, 1]
80 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
81 /// expected.rolls[2].drop(r1_mod);
82 /// expected.rolls[4].add(r1_mod);
83 /// assert_eq!(rolled, expected);
84 /// # Ok::<(), tyche::dice::Error>(())
85 /// ```
86 Reroll {
87 /// Condition that rolls must pass in order to be rerolled
88 cond: Condition,
89
90 /// Whether the reroll should be done repeatedly until the rerolled die no longer meets the condition
91 recurse: bool,
92 },
93
94 /// Explodes (keeps original and adds an additional newly-rolled die) dice that meet a condition.
95 ///
96 /// # Examples
97 ///
98 /// ## Explode recursively (`x`)
99 /// ```
100 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
101 ///
102 /// // Build the 4d6x dice set and create a roller that has predetermined values for the dice rolls
103 /// let dice = Dice::builder().count(4).sides(6).explode(None, true).build();
104 /// let premade_rolls = [3, 6, 1, 2, 6, 4];
105 /// let mut rng = IterRoller::new(premade_rolls);
106 ///
107 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
108 /// // Only the first four predetermined values will be used for the dice rolls since there are only four dice to roll.
109 /// let mut rolled = rng.roll(&dice, false)?;
110 ///
111 /// // Explicitly create and apply an x modifier. This is usually not necessary since the dice has its own instance of
112 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
113 /// // for demonstration purposes.
114 /// let x_mod = Modifier::Explode {
115 /// cond: None,
116 /// recurse: true,
117 /// };
118 /// x_mod.apply(&mut rolled, &mut rng)?;
119 ///
120 /// // Upon being applied, the modifier will see that a 6 (the max die value) was rolled, roll a new additional 6, see
121 /// // that is also the max die value, then roll a new 4 as well.
122 /// // Final expected rolled dice set, after x modifier: 4d6x[3, 6, 1, 2, 6, 4]
123 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
124 /// expected.rolls[4].add(x_mod);
125 /// expected.rolls[5].add(x_mod);
126 /// assert_eq!(rolled, expected);
127 /// # Ok::<(), tyche::dice::Error>(())
128 /// ```
129 ///
130 /// ## Explode once (`xo`)
131 /// ```
132 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
133 ///
134 /// // Build the 4d6xo dice set and create a roller that has predetermined values for the dice rolls
135 /// let dice = Dice::builder().count(4).sides(6).explode(None, false).build();
136 /// let premade_rolls = [3, 6, 1, 2, 6];
137 /// let mut rng = IterRoller::new(premade_rolls);
138 ///
139 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
140 /// // Only the first four predetermined values will be used for the dice rolls since there are only four dice to roll.
141 /// let mut rolled = rng.roll(&dice, false)?;
142 ///
143 /// // Explicitly create and apply an xo modifier. This is usually not necessary since the dice has its own instance of
144 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
145 /// // for demonstration purposes.
146 /// let xo_mod = Modifier::Explode {
147 /// cond: None,
148 /// recurse: false,
149 /// };
150 /// xo_mod.apply(&mut rolled, &mut rng)?;
151 ///
152 /// // Upon being applied, the modifier will see that a 6 (the max die value) was rolled and roll a new additional 6.
153 /// // Final expected rolled dice set, after xo modifier: 4d6xo[3, 6, 1, 2, 6]
154 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
155 /// expected.rolls[4].add(xo_mod);
156 /// assert_eq!(rolled, expected);
157 /// # Ok::<(), tyche::dice::Error>(())
158 /// ```
159 Explode {
160 /// Condition that rolls must pass in order to explode.
161 /// If `None`, the roll values must be equal to the number of sides of the dice being rolled.
162 cond: Option<Condition>,
163
164 /// Whether the explosion should be done repeatedly for any additional rolls that also meet the condition
165 recurse: bool,
166 },
167
168 /// Keeps only the highest x dice, dropping the rest.
169 ///
170 /// # Examples
171 ///
172 /// ## Keep highest die (`kh`)
173 /// ```
174 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
175 ///
176 /// // Build the 4d6kh dice set and create a roller that has predetermined values for the dice rolls
177 /// let dice = Dice::builder().count(4).sides(6).keep_high(1).build();
178 /// let premade_rolls = [3, 6, 1, 2];
179 /// let mut rng = IterRoller::new(premade_rolls);
180 ///
181 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
182 /// let mut rolled = rng.roll(&dice, false)?;
183 ///
184 /// // Explicitly create and apply a kh modifier. This is usually not necessary since the dice has its own instance of
185 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
186 /// // for demonstration purposes.
187 /// let kh_mod = Modifier::KeepHigh(1);
188 /// kh_mod.apply(&mut rolled, &mut rng)?;
189 ///
190 /// // Upon being applied, the modifier will drop all rolls except the highest one, so 3, 1, and 2 will be dropped.
191 /// // Final expected rolled dice set, after kh modifier: 4d6kh[3 (d), 6, 1 (d), 2 (d)]
192 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
193 /// expected.rolls[0].drop(kh_mod);
194 /// expected.rolls[2].drop(kh_mod);
195 /// expected.rolls[3].drop(kh_mod);
196 /// assert_eq!(rolled, expected);
197 /// # Ok::<(), tyche::dice::Error>(())
198 /// ```
199 ///
200 /// ## Keep highest 2 dice (`kh2`)
201 /// ```
202 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
203 ///
204 /// // Build the 4d6kh2 dice set and create a roller that has predetermined values for the dice rolls
205 /// let dice = Dice::builder().count(4).sides(6).keep_high(2).build();
206 /// let premade_rolls = [3, 6, 1, 2];
207 /// let mut rng = IterRoller::new(premade_rolls);
208 ///
209 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
210 /// let mut rolled = rng.roll(&dice, false)?;
211 ///
212 /// // Explicitly create and apply a kh2 modifier. This is usually not necessary since the dice has its own instance of
213 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
214 /// // for demonstration purposes.
215 /// let kh2_mod = Modifier::KeepHigh(2);
216 /// kh2_mod.apply(&mut rolled, &mut rng)?;
217 ///
218 /// // Upon being applied, the modifier will drop all rolls except the two highest, so 1 and 2 will be dropped.
219 /// // Final expected rolled dice set, after kh2 modifier: 4d6kh2[3, 6, 1 (d), 2 (d)]
220 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
221 /// expected.rolls[2].drop(kh2_mod);
222 /// expected.rolls[3].drop(kh2_mod);
223 /// assert_eq!(rolled, expected);
224 /// # Ok::<(), tyche::dice::Error>(())
225 /// ```
226 KeepHigh(u8),
227
228 /// Keeps only the lowest x dice, dropping the rest.
229 ///
230 /// # Examples
231 ///
232 /// ## Keep lowest die (`kl`)
233 /// ```
234 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
235 ///
236 /// // Build the 4d6kl dice set and create a roller that has predetermined values for the dice rolls
237 /// let dice = Dice::builder().count(4).sides(6).keep_low(1).build();
238 /// let premade_rolls = [3, 6, 1, 2];
239 /// let mut rng = IterRoller::new(premade_rolls);
240 ///
241 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
242 /// let mut rolled = rng.roll(&dice, false)?;
243 ///
244 /// // Explicitly create and apply a kl modifier. This is usually not necessary since the dice has its own instance of
245 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
246 /// // for demonstration purposes.
247 /// let kl_mod = Modifier::KeepLow(1);
248 /// kl_mod.apply(&mut rolled, &mut rng)?;
249 ///
250 /// // Upon being applied, the modifier will drop all rolls except the lowest one, so 3, 6, and 2 will be dropped.
251 /// // Final expected rolled dice set, after kl modifier: 4d6kl[3 (d), 6 (d), 1, 2 (d)]
252 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
253 /// expected.rolls[0].drop(kl_mod);
254 /// expected.rolls[1].drop(kl_mod);
255 /// expected.rolls[3].drop(kl_mod);
256 /// assert_eq!(rolled, expected);
257 /// # Ok::<(), tyche::dice::Error>(())
258 /// ```
259 ///
260 /// ## Keep lowest 2 dice (`kl2`)
261 /// ```
262 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
263 ///
264 /// // Build the 4d6kl2 dice set and create a roller that has predetermined values for the dice rolls
265 /// let dice = Dice::builder().count(4).sides(6).keep_low(2).build();
266 /// let premade_rolls = [3, 6, 1, 2];
267 /// let mut rng = IterRoller::new(premade_rolls);
268 ///
269 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
270 /// let mut rolled = rng.roll(&dice, false)?;
271 ///
272 /// // Explicitly create and apply a kl2 modifier. This is usually not necessary since the dice has its own instance of
273 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
274 /// // for demonstration purposes.
275 /// let kl2_mod = Modifier::KeepLow(2);
276 /// kl2_mod.apply(&mut rolled, &mut rng)?;
277 ///
278 /// // Upon being applied, the modifier will drop all rolls except the two lowest, so 3 and 6 will be dropped.
279 /// // Final expected rolled dice set, after kl2 modifier: 4d6kl2[3 (d), 6 (d), 1, 2]
280 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
281 /// expected.rolls[0].drop(kl2_mod);
282 /// expected.rolls[1].drop(kl2_mod);
283 /// assert_eq!(rolled, expected);
284 /// # Ok::<(), tyche::dice::Error>(())
285 /// ```
286 KeepLow(u8),
287
288 /// Replaces values of rolls lower than a minimum with the minimum.
289 ///
290 /// # Examples
291 /// ```
292 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
293 ///
294 /// // Build the 4d6min3 dice set and create a roller that has predetermined values for the dice rolls
295 /// let dice = Dice::builder().count(4).sides(6).min(3).build();
296 /// let premade_rolls = [3, 6, 1, 2];
297 /// let mut rng = IterRoller::new(premade_rolls);
298 ///
299 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
300 /// let mut rolled = rng.roll(&dice, false)?;
301 ///
302 /// // Explicitly create and apply a min3 modifier. This is usually not necessary since the dice has its own instance of
303 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
304 /// // for demonstration purposes.
305 /// let min3_mod = Modifier::Min(3);
306 /// min3_mod.apply(&mut rolled, &mut rng)?;
307 ///
308 /// // Upon being applied, the modifier will replace the values of all rolls less than 3 with 3, so 1 and 2 will
309 /// // both become 3.
310 /// // Final expected rolled dice set, after min3 modifier: 4d6min3[3, 6, 3 (m), 3 (m)]
311 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
312 /// expected.rolls[2].change(min3_mod, 3);
313 /// expected.rolls[3].change(min3_mod, 3);
314 /// assert_eq!(rolled, expected);
315 /// # Ok::<(), tyche::dice::Error>(())
316 /// ```
317 Min(u8),
318
319 /// Replaces values of rolls higher than a maximum with the maximum.
320 ///
321 /// # Examples
322 /// ```
323 /// use tyche::dice::{modifier::{Condition, Modifier}, roller::{Iter as IterRoller, Roller}, Dice, Rolled};
324 ///
325 /// // Build the 4d6max3 dice set and create a roller that has predetermined values for the dice rolls
326 /// let dice = Dice::builder().count(4).sides(6).max(3).build();
327 /// let premade_rolls = [3, 6, 1, 2];
328 /// let mut rng = IterRoller::new(premade_rolls);
329 ///
330 /// // Roll the dice, but don't have it automatically apply its modifiers (passing `false` as the second `roll()` param).
331 /// let mut rolled = rng.roll(&dice, false)?;
332 ///
333 /// // Explicitly create and apply a max3 modifier. This is usually not necessary since the dice has its own instance of
334 /// // it from the builder and will automatically apply it when passing `true` to `roll()`, but we do it this way here
335 /// // for demonstration purposes.
336 /// let max3_mod = Modifier::Max(3);
337 /// max3_mod.apply(&mut rolled, &mut rng)?;
338 ///
339 /// // Upon being applied, the modifier will replace the values of all rolls greater than 3 with 3, so 6 will become 3.
340 /// // Final expected rolled dice set, after max3 modifier: 4d6max3[3, 3 (m), 1, 2]
341 /// let mut expected = Rolled::from_dice_and_rolls(&dice, premade_rolls);
342 /// expected.rolls[1].change(max3_mod, 3);
343 /// assert_eq!(rolled, expected);
344 /// # Ok::<(), tyche::dice::Error>(())
345 /// ```
346 Max(u8),
347 //
348 // /// Count the number of dice that meet or don't meet (second parameter `true` for meets, `false` for does not meet)
349 // /// the given condition.
350 // CountCond(Condition, bool),
351
352 // /// Count the number of dice that are even (`true`) or odd (`false`)
353 // CountParity(bool),
354
355 // /// Subtract the number of dice that meet the given condition
356 // SubCond(Condition),
357
358 // /// Subtract the values of dice that meet the given condition
359 // SubCondVal(Condition),
360
361 // /// Subtract a value from the total
362 // Margin(u8),
363}
364
365impl Modifier {
366 /// Applies the modifier to a set of rolls, using a given roller if additional die rolls are needed.
367 ///
368 /// # Errors
369 /// If applying the modifier would result in infinite additional die rolls, an error variant is returned.
370 pub fn apply(self, rolled: &mut Rolled, rng: &mut impl Roller) -> Result<(), Error> {
371 match self {
372 Self::Reroll { cond, recurse } => self.apply_reroll(rolled, rng, cond, recurse)?,
373 Self::Explode { cond, recurse } => self.apply_explode(rolled, rng, cond, recurse)?,
374 Self::KeepHigh(count) => self.apply_keep_high(rolled, count),
375 Self::KeepLow(count) => self.apply_keep_low(rolled, count),
376 Self::Min(min) => self.apply_min(rolled, min),
377 Self::Max(max) => self.apply_max(rolled, max),
378 }
379
380 Ok(())
381 }
382
383 /// Applies the [`Self::Reroll`] variant to a set of rolled dice.
384 fn apply_reroll(
385 self,
386 rolled: &mut Rolled,
387 rng: &mut impl Roller,
388 cond: Condition,
389 recurse: bool,
390 ) -> Result<(), Error> {
391 // Prevent recursively rerolling dice that would result in infinite rerolls
392 if recurse {
393 match cond {
394 Condition::Eq(other) if other == 1 && rolled.dice.sides == 1 => {
395 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
396 }
397 Condition::Gt(0) | Condition::Gte(..=1) => {
398 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
399 }
400 Condition::Lt(other) if other > rolled.dice.sides => {
401 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
402 }
403 Condition::Lte(other) if other >= rolled.dice.sides => {
404 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
405 }
406 _ => {}
407 }
408 }
409
410 loop {
411 // Determine which rolls qualify for reroll
412 let mut to_reroll = rolled
413 .rolls
414 .iter_mut()
415 .filter(|roll| roll.is_kept())
416 .filter(|roll| cond.check(roll.val))
417 .collect::<Vec<_>>();
418
419 if to_reroll.is_empty() {
420 break;
421 }
422
423 // Roll additional dice and drop the originals
424 let mut rerolls = Vec::with_capacity(to_reroll.len());
425 for roll in &mut to_reroll {
426 let mut reroll = rng.roll_die(rolled.dice.sides);
427 reroll.add(self);
428 rerolls.push(reroll);
429 roll.drop(self);
430 }
431
432 // Add the rerolls to the rolls
433 rolled.rolls.append(&mut rerolls);
434
435 if !recurse {
436 break;
437 }
438 }
439
440 Ok(())
441 }
442
443 /// Applies the [`Self::Explode`] variant to a set of rolled dice.
444 fn apply_explode(
445 self,
446 rolled: &mut Rolled,
447 rng: &mut impl Roller,
448 cond: Option<Condition>,
449 recurse: bool,
450 ) -> Result<(), Error> {
451 // Prevent recursively exploding dice that would result in infinite explosions
452 if recurse {
453 match cond {
454 Some(Condition::Eq(other)) if other == 1 && rolled.dice.sides == 1 => {
455 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
456 }
457 Some(Condition::Gt(0) | Condition::Gte(..=1)) => {
458 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
459 }
460 Some(Condition::Lt(other)) if other > rolled.dice.sides => {
461 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
462 }
463 Some(Condition::Lte(other)) if other >= rolled.dice.sides => {
464 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
465 }
466 None if rolled.dice.sides == 1 => {
467 return Err(Error::InfiniteRolls((*rolled.dice).clone()));
468 }
469 _ => {}
470 }
471 }
472
473 // Determine how many initial rolls qualify for explosion
474 let mut to_explode = rolled
475 .rolls
476 .iter()
477 .filter(|roll| roll.is_kept())
478 .filter(|roll| match cond {
479 Some(cond) => cond.check(roll.val),
480 None => roll.val == rolled.dice.sides,
481 })
482 .count();
483
484 loop {
485 // Roll additional dice
486 let mut explosions = Vec::with_capacity(to_explode);
487 for _ in 0..to_explode {
488 let mut roll = rng.roll_die(rolled.dice.sides);
489 roll.add(self);
490 explosions.push(roll);
491 }
492
493 // Determine how many additional rolls qualify for explosion
494 to_explode = if recurse {
495 explosions
496 .iter()
497 .filter(|roll| match cond {
498 Some(cond) => cond.check(roll.val),
499 None => roll.val == rolled.dice.sides,
500 })
501 .count()
502 } else {
503 0
504 };
505
506 // Add the explosions to the rolls and finish if there are no further rolls to explode
507 rolled.rolls.append(&mut explosions);
508 if to_explode == 0 {
509 break;
510 }
511 }
512
513 Ok(())
514 }
515
516 /// Applies the [`Self::KeepHigh`] variant to a set of rolled dice.
517 fn apply_keep_high(self, rolled: &mut Rolled, count: u8) {
518 let mut refs = rolled
519 .rolls
520 .iter_mut()
521 .filter(|roll| roll.is_kept())
522 .collect::<Vec<_>>();
523 refs.sort();
524 refs.reverse();
525 refs.iter_mut().skip(count as usize).for_each(|roll| roll.drop(self));
526 }
527
528 /// Applies the [`Self::KeepLow`] variant to a set of rolled dice.
529 fn apply_keep_low(self, rolled: &mut Rolled, count: u8) {
530 let mut refs = rolled
531 .rolls
532 .iter_mut()
533 .filter(|roll| roll.is_kept())
534 .collect::<Vec<_>>();
535 refs.sort();
536 refs.iter_mut().skip(count as usize).for_each(|roll| roll.drop(self));
537 }
538
539 /// Applies the [`Self::Min`] variant to a set of rolled dice.
540 fn apply_min(self, rolled: &mut Rolled, min: u8) {
541 rolled
542 .rolls
543 .iter_mut()
544 .filter(|roll| roll.is_kept() && roll.val < min)
545 .for_each(|roll| roll.change(self, min));
546 }
547
548 /// Applies the [`Self::Max`] variant to a set of rolled dice.
549 fn apply_max(self, rolled: &mut Rolled, max: u8) {
550 rolled
551 .rolls
552 .iter_mut()
553 .filter(|roll| roll.is_kept() && roll.val > max)
554 .for_each(|roll| roll.change(self, max));
555 }
556}
557
558impl fmt::Display for Modifier {
559 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
560 write!(
561 f,
562 "{}{}",
563 match self {
564 Self::Reroll { recurse, .. } => format!("r{}", recurse.then_some("r").unwrap_or("")),
565 Self::Explode { recurse, .. } => format!("x{}", recurse.then_some("").unwrap_or("o")),
566 Self::KeepHigh(count) => format!("kh{}", if *count > 1 { count.to_string() } else { String::new() }),
567 Self::KeepLow(count) => format!("kl{}", if *count > 1 { count.to_string() } else { String::new() }),
568 Self::Min(min) => format!("min{min}"),
569 Self::Max(max) => format!("max{max}"),
570 },
571 match self {
572 Self::Reroll { cond, .. } | Self::Explode { cond: Some(cond), .. } => cond.to_string(),
573 Self::Explode { cond: None, .. }
574 | Self::KeepHigh(..)
575 | Self::KeepLow(..)
576 | Self::Max(..)
577 | Self::Min(..) => String::new(),
578 }
579 )
580 }
581}
582
583/// Test that die values can be checked against
584#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
585#[allow(clippy::exhaustive_enums)]
586pub enum Condition {
587 /// Checks whether values are equal to its own value. Symbol: `=`
588 Eq(u8),
589
590 /// Checks whether values are greater than its own value. Symbol: `>`
591 Gt(u8),
592
593 /// Checks whether values are greater than or equal to its own value. Symbol: `>=`
594 Gte(u8),
595
596 /// Checks whether values are less than its own value. Symbol: `<`
597 Lt(u8),
598
599 /// Checks whether values are less than or equal to its own value. Symbol: `<=`
600 Lte(u8),
601}
602
603impl Condition {
604 /// Creates a condition from its corresponding symbol and a given value.
605 ///
606 /// # Errors
607 /// If the symbol doesn't match to a known condition variant, an error variant will be returned.
608 pub fn from_symbol_and_val(symbol: &str, val: u8) -> Result<Self, Error> {
609 Ok(match symbol {
610 "=" => Self::Eq(val),
611 ">" => Self::Gt(val),
612 ">=" => Self::Gte(val),
613 "<" => Self::Lt(val),
614 "<=" => Self::Lte(val),
615 _ => return Err(Error::UnknownCondition(symbol.to_owned())),
616 })
617 }
618
619 /// Checks a value against the condition.
620 #[must_use]
621 pub const fn check(&self, val: u8) -> bool {
622 match self {
623 Self::Eq(expected) => val == *expected,
624 Self::Gt(expected) => val > *expected,
625 Self::Gte(expected) => val >= *expected,
626 Self::Lt(expected) => val < *expected,
627 Self::Lte(expected) => val <= *expected,
628 }
629 }
630
631 /// Gets the symbol that represents the condition.
632 #[must_use]
633 pub const fn symbol(&self) -> &'static str {
634 match self {
635 Self::Eq(..) => "=",
636 Self::Gt(..) => ">",
637 Self::Gte(..) => ">=",
638 Self::Lt(..) => "<",
639 Self::Lte(..) => "<=",
640 }
641 }
642}
643
644impl fmt::Display for Condition {
645 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646 write!(
647 f,
648 "{}{}",
649 self.symbol(),
650 match self {
651 Self::Eq(expected)
652 | Self::Gt(expected)
653 | Self::Gte(expected)
654 | Self::Lt(expected)
655 | Self::Lte(expected) => expected,
656 }
657 )
658 }
659}