use rand::Rng; use std::fmt; use std::fmt::Formatter; #[derive(PartialEq, Debug)] pub struct DieRoll { sides: u32, face: u32, } impl DieRoll { fn new(sides: u32, face: u32) -> Self { DieRoll { sides, face } } } pub struct RollResult { rolls: Vec, } impl RollResult { pub fn new() -> Self { RollResult { rolls: Vec::new() } } fn add_roll(&mut self, roll: DieRoll) { self.rolls.push(roll) } pub fn total(&self) -> u32 { self.rolls.iter().map(|die_roll| die_roll.face).sum() } pub fn increase_sides_below_max(&mut self, max_value: u32, increase_by: u32) { for roll in &mut self.rolls { if roll.face < max_value { roll.face += increase_by; } } } } impl fmt::Display for RollResult { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( f, "{} ({})", self.total(), self.rolls .iter() .map(|dr| dr.face.to_string()) .collect::>() .join(", ") ) } } /// Roll a single die with the specified number of sides. pub fn roll_die(sides: u32) -> u32 { let mut rng = rand::thread_rng(); rng.gen_range(1..=sides) } /// Parse and roll dice from a dice formula (e.g. "4d6+2" or "d12*3"). pub fn roll_formula(formula: &str) -> Option { let delimiters = ['d', '+', '-', '*', '/']; // Only d implemented right now. let parts: Vec<&str> = formula.split(|c| delimiters.contains(&c)).collect(); if parts.len() < 2 { return None; } let num_dice = if parts[0].is_empty() { 1 } else { match parts[0].parse::() { Ok(num) => num, Err(_) => return None, } }; let sides = match parts[1].parse::() { Ok(num) => num, Err(_) => return None, }; let mut roll_result = RollResult::new(); for _ in 0..num_dice { let face = roll_die(sides); roll_result.add_roll(DieRoll::new(sides, face)); } Some(roll_result) } #[cfg(test)] mod tests { use super::*; #[test] fn test_roll_result_total() { let mut roll_result = RollResult::new(); roll_result.add_roll(DieRoll::new(6, 4)); roll_result.add_roll(DieRoll::new(6, 2)); roll_result.add_roll(DieRoll::new(6, 6)); assert_eq!(roll_result.total(), 12); } #[test] fn test_increase_sides_below_max() { let mut roll_result = RollResult::new(); roll_result.add_roll(DieRoll::new(6, 4)); roll_result.add_roll(DieRoll::new(6, 2)); roll_result.add_roll(DieRoll::new(6, 6)); roll_result.increase_sides_below_max(6, 1); let expected_rolls = vec![ DieRoll::new(6, 5), // Increased from 4. DieRoll::new(6, 3), // Increased from 2. DieRoll::new(6, 6), // Remains the same, as it's 6. ]; assert_eq!(roll_result.rolls, expected_rolls); } }