1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
//! Abstractions for rolling [`DieRoll`]s using various means.
use std::{borrow::Cow, iter::Peekable};
#[cfg(feature = "fastrand")]
use fastrand::Rng;
use super::{Dice, DieRoll, Error, Rolled};
/// Rolls dice - what else is there to say?
pub trait Roller {
/// Rolls a single die.
#[must_use]
fn roll_die(&mut self, sides: u8) -> DieRoll;
/// Rolls a set of dice and optionally applies all of its modifiers to the rolls.
///
/// # Errors
/// If any errors are encountered while applying the dice's modifiers, an error variant is returned.
fn roll<'d, 'r>(&mut self, dice: &'d Dice, apply_mods: bool) -> Result<Rolled<'r>, Error>
where
'd: 'r,
Self: Sized,
{
// Roll the dice!
let mut rolls = Vec::with_capacity(dice.count as usize);
for _ in 0..dice.count {
rolls.push(self.roll_die(dice.sides));
}
let mut rolled = Rolled {
rolls,
dice: Cow::Borrowed(dice),
};
// Apply all of the dice's modifiers
if apply_mods {
for modifier in &dice.modifiers {
modifier.apply(&mut rolled, self)?;
}
}
Ok(rolled)
}
}
/// Generates rolls with random values using [fastrand]. Requires the `fastrand` feature (enabled by default).
///
/// # Examples
///
/// ## Default fastrand roller
/// ```
/// use tyche::dice::{roller::{FastRand as FastRandRoller, Roller}, Dice};
///
/// let mut roller = FastRandRoller::default();
/// let dice = Dice::new(4, 6);
/// let _ = roller.roll(&dice, true)?;
/// let _ = roller.roll(&dice, true)?;
/// # Ok::<(), tyche::dice::Error>(())
/// ```
///
/// ## Custom fastrand roller (seeded)
/// ```
/// use tyche::dice::{roller::{FastRand as FastRandRoller, Roller}, Dice};
/// use fastrand::Rng;
///
/// let mut roller = FastRandRoller::new(Rng::with_seed(0x750c38d574400));
/// let dice = Dice::new(4, 6);
/// let _ = roller.roll(&dice, true)?;
/// let _ = roller.roll(&dice, true)?;
/// # Ok::<(), tyche::dice::Error>(())
/// ```
#[cfg(feature = "fastrand")]
#[derive(Debug, Default, Clone)]
pub struct FastRand(Rng);
#[cfg(feature = "fastrand")]
impl FastRand {
/// Creates a new fastrand roller that uses the given RNG instance to generate rolls.
#[must_use]
#[inline]
pub const fn new(rng: Rng) -> Self {
Self(rng)
}
/// Creates a new fastrand roller that uses a pre-seeded RNG instance to generate rolls.
#[must_use]
#[inline]
pub fn with_seed(seed: u64) -> Self {
Self(Rng::with_seed(seed))
}
}
#[cfg(feature = "fastrand")]
impl Roller for FastRand {
/// Rolls a single die using the [`fastrand::Rng`] the roller was created with.
#[inline]
fn roll_die(&mut self, sides: u8) -> DieRoll {
DieRoll::new(self.0.u8(1..=sides))
}
}
/// Generates rolls that always have a specific value.
///
/// # Examples
/// ```
/// use tyche::dice::{roller::{Roller, Val as ValRoller}, Dice};
///
/// let mut roller = ValRoller(42);
///
/// let dice = Dice::new(4, 6);
/// let rolled = roller.roll(&dice, true)?;
/// assert!(rolled.rolls.iter().all(|roll| roll.val == 42));
///
/// let dice = Dice::new(2, 20);
/// let rolled = roller.roll(&dice, true)?;
/// assert!(rolled.rolls.iter().all(|roll| roll.val == 42));
/// # Ok::<(), tyche::dice::Error>(())
/// ```
#[derive(Debug, Default, Clone)]
#[allow(clippy::exhaustive_structs)]
pub struct Val(pub u8);
impl Roller for Val {
/// Rolls a single die, always with one specific value.
#[inline]
fn roll_die(&mut self, _sides: u8) -> DieRoll {
DieRoll::new(self.0)
}
}
/// Generates rolls that always have their max value.
///
/// # Examples
/// ```
/// use tyche::dice::{roller::{Max as MaxRoller, Roller}, Dice};
///
/// let mut roller = MaxRoller;
///
/// let dice = Dice::new(4, 6);
/// let rolled = roller.roll(&dice, true)?;
/// assert!(rolled.rolls.iter().all(|roll| roll.val == 6));
///
/// let dice = Dice::new(2, 20);
/// let rolled = roller.roll(&dice, true)?;
/// assert!(rolled.rolls.iter().all(|roll| roll.val == 20));
/// # Ok::<(), tyche::dice::Error>(())
/// ```
#[derive(Debug, Default, Clone)]
#[allow(clippy::exhaustive_structs)]
pub struct Max;
impl Roller for Max {
/// Rolls a single die, always with the max value (same as the number of sides).
#[inline]
fn roll_die(&mut self, sides: u8) -> DieRoll {
DieRoll::new(sides)
}
}
/// Generates rolls from an iterator of values. Mainly useful for testing purposes.
///
/// # Examples
/// ```
/// use tyche::dice::{roller::{Iter as IterRoller, Roller}, Dice, DieRoll};
///
/// let mut roller = IterRoller::new(vec![1, 2, 3, 4, 10]);
/// let dice = Dice::new(5, 6);
/// assert_eq!(
/// roller.roll(&dice, true)?.rolls,
/// vec![DieRoll::new(1), DieRoll::new(2), DieRoll::new(3), DieRoll::new(4), DieRoll::new(10)]
/// );
/// # Ok::<(), tyche::dice::Error>(())
/// ```
#[derive(Debug, Clone)]
pub struct Iter<I: Iterator<Item = u8>>(Peekable<I>);
impl<I: Iterator<Item = u8>> Iter<I> {
/// Checks whether the iterator still has values available.
#[inline]
pub fn can_roll(&mut self) -> bool {
self.0.peek().is_some()
}
/// Creates a new roller that uses the given iterator to provide roll values.
#[must_use]
#[inline]
pub fn new(iter: impl IntoIterator<IntoIter = I>) -> Self {
Self(iter.into_iter().peekable())
}
}
impl<I: Iterator<Item = u8>> Roller for Iter<I> {
/// Rolls a die with the value from the next iteration.
///
/// # Panics
/// If the iterator has finished, this will panic.
#[inline]
#[allow(clippy::expect_used)]
fn roll_die(&mut self, _sides: u8) -> DieRoll {
DieRoll::new(self.0.next().expect("iterator is finished"))
}
}