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}