use std::ops::Add; use rand::rngs::ThreadRng; use rand::Rng; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct RiskDie(u8); pub const NUM_DIE_LEVELS: usize = 6; pub const RISK_DIE_VALUES: [u8; NUM_DIE_LEVELS] = [4, 6, 8, 10, 12, 20]; pub const DELTA_4: RiskDie = RiskDie(0); pub const DELTA_6: RiskDie = RiskDie(1); pub const DELTA_8: RiskDie = RiskDie(2); pub const DELTA_10: RiskDie = RiskDie(3); pub const DELTA_12: RiskDie = RiskDie(4); pub const DELTA_20: RiskDie = RiskDie(5); #[derive(Debug, PartialEq, Eq)] pub enum Error { InvalidLevel, InvalidValue, } impl RiskDie { pub fn from_level(level: u8) -> Result { if (level as usize) < RISK_DIE_VALUES.len() { Ok(Self(level)) } else { Err(Error::InvalidLevel) } } pub fn from_value(value: u8) -> Result { for (i, val) in RISK_DIE_VALUES.iter().enumerate() { if *val == value { return Ok(Self(i as u8)); } } Err(Error::InvalidValue) } pub fn level(&self) -> u8 { self.0 } pub fn value(&self) -> u8 { RISK_DIE_VALUES[self.0 as usize] } /// Roll the die. If the die shows 3 or less, it returns the next lower die. /// If it is more, it returns itself. If it was already the lowest die value /// and it would be stepped down it returns `None`. pub fn roll(self) -> Option { let mut rng = rand::thread_rng(); self.roll_with_rng(&mut rng) } pub fn roll_with_rng(self, rng: &mut ThreadRng) -> Option { let rolled = rng.gen_range(1..=self.value()); if rolled > 3 { Some(self) } else { self.level_down() } } pub fn can_be_lowered(&self) -> bool { self.0 > 0 } pub fn level_down(mut self) -> Option { if self.can_be_lowered() { self.0 -= 1; Some(self) } else { /* Resource depleted */ None } } pub fn can_be_upped(&self) -> bool { self.0 < NUM_DIE_LEVELS as u8 - 1 } pub fn level_up(mut self) -> Self { if self.can_be_upped() { self.0 += 1; } self } } #[rustfmt::skip] const DELTA_ADDITION_TABLE: [[u8; NUM_DIE_LEVELS]; NUM_DIE_LEVELS] = [ /* ------------------- with d4 d6 d8 d10 d12 d20 */ /* delta 4 combinations */ [1, 2, 3, 4, 4, 5], /* delta 6 combinations */ [2, 2, 3, 4, 5, 5], /* delta 8 combinations */ [3, 3, 4, 4, 5, 5], /* delta 10 combinations */ [4, 4, 4, 5, 5, 5], /* delta 12 combinations */ [4, 5, 5, 5, 5, 5], /* delta 20 combinations */ [5, 5, 5, 5, 5, 5], ]; impl Add for RiskDie { type Output = Self; fn add(self, other: Self) -> Self::Output { RiskDie(DELTA_ADDITION_TABLE[self.0 as usize][other.0 as usize]) } } #[cfg(test)] mod tests { use super::*; #[test] fn create_die_from_level() { let die = RiskDie::from_level(0).expect("Unable to create die for correct level"); assert_eq!(die.value(), 4); let die = RiskDie::from_level(1).expect("Unable to create die for correct level"); assert_eq!(die.value(), 6); let die = RiskDie::from_level(2).expect("Unable to create die for correct level"); assert_eq!(die.value(), 8); let die = RiskDie::from_level(3).expect("Unable to create die for correct level"); assert_eq!(die.value(), 10); let die = RiskDie::from_level(4).expect("Unable to create die for correct level"); assert_eq!(die.value(), 12); let die = RiskDie::from_level(5).expect("Unable to create die for correct level"); assert_eq!(die.value(), 20); } #[test] fn create_die_from_invalid_level() { assert_eq!( RiskDie::from_level(6).expect_err("Should not be able to create die from level"), Error::InvalidLevel ); } #[test] fn create_from_value() { let die = RiskDie::from_value(4).expect("Unable to create die from correct value"); assert_eq!(die.level(), 0); let die = RiskDie::from_value(6).expect("Unable to create die from correct value"); assert_eq!(die.level(), 1); let die = RiskDie::from_value(8).expect("Unable to create die from correct value"); assert_eq!(die.level(), 2); let die = RiskDie::from_value(10).expect("Unable to create die from correct value"); assert_eq!(die.level(), 3); let die = RiskDie::from_value(12).expect("Unable to create die from correct value"); assert_eq!(die.level(), 4); let die = RiskDie::from_value(20).expect("Unable to create die from correct value"); assert_eq!(die.level(), 5); } #[test] fn level_down() { assert_eq!(DELTA_4.level_down(), None); assert_eq!(DELTA_6.level_down(), Some(RiskDie(0))); assert_eq!(DELTA_8.level_down(), Some(RiskDie(1))); assert_eq!(DELTA_10.level_down(), Some(RiskDie(2))); assert_eq!(DELTA_12.level_down(), Some(RiskDie(3))); assert_eq!(DELTA_20.level_down(), Some(RiskDie(4))); } #[test] fn level_up() { assert_eq!(DELTA_4.level_up(), RiskDie(1)); assert_eq!(DELTA_6.level_up(), RiskDie(2)); assert_eq!(DELTA_8.level_up(), RiskDie(3)); assert_eq!(DELTA_10.level_up(), RiskDie(4)); assert_eq!(DELTA_12.level_up(), RiskDie(5)); assert_eq!(DELTA_20.level_up(), RiskDie(5)); } #[test] fn add_risk_dice() { assert_eq!(DELTA_4 + DELTA_4, DELTA_6); assert_eq!(DELTA_4 + DELTA_6, DELTA_8); assert_eq!(DELTA_4 + DELTA_8, DELTA_10); assert_eq!(DELTA_4 + DELTA_10, DELTA_12); assert_eq!(DELTA_4 + DELTA_12, DELTA_12); assert_eq!(DELTA_4 + DELTA_20, DELTA_20); assert_eq!(DELTA_6 + DELTA_4, DELTA_8); assert_eq!(DELTA_6 + DELTA_6, DELTA_8); assert_eq!(DELTA_6 + DELTA_8, DELTA_10); assert_eq!(DELTA_6 + DELTA_10, DELTA_12); assert_eq!(DELTA_6 + DELTA_12, DELTA_20); assert_eq!(DELTA_6 + DELTA_20, DELTA_20); assert_eq!(DELTA_8 + DELTA_4, DELTA_10); assert_eq!(DELTA_8 + DELTA_6, DELTA_10); assert_eq!(DELTA_8 + DELTA_8, DELTA_12); assert_eq!(DELTA_8 + DELTA_10, DELTA_12); assert_eq!(DELTA_8 + DELTA_12, DELTA_20); assert_eq!(DELTA_8 + DELTA_20, DELTA_20); assert_eq!(DELTA_10 + DELTA_4, DELTA_12); assert_eq!(DELTA_10 + DELTA_6, DELTA_12); assert_eq!(DELTA_10 + DELTA_8, DELTA_12); assert_eq!(DELTA_10 + DELTA_10, DELTA_20); assert_eq!(DELTA_10 + DELTA_12, DELTA_20); assert_eq!(DELTA_10 + DELTA_20, DELTA_20); assert_eq!(DELTA_12 + DELTA_4, DELTA_12); assert_eq!(DELTA_12 + DELTA_6, DELTA_20); assert_eq!(DELTA_12 + DELTA_8, DELTA_20); assert_eq!(DELTA_12 + DELTA_10, DELTA_20); assert_eq!(DELTA_12 + DELTA_12, DELTA_20); assert_eq!(DELTA_12 + DELTA_20, DELTA_20); assert_eq!(DELTA_20 + DELTA_4, DELTA_20); assert_eq!(DELTA_20 + DELTA_6, DELTA_20); assert_eq!(DELTA_20 + DELTA_8, DELTA_20); assert_eq!(DELTA_20 + DELTA_10, DELTA_20); assert_eq!(DELTA_20 + DELTA_12, DELTA_20); assert_eq!(DELTA_20 + DELTA_20, DELTA_20); } }