use std::fmt;
use std::str::FromStr;
use nom::IResult;
use crate::error::DiceFormatError;
use crate::parse::parse_roll;
#[derive(Debug, PartialEq)]
pub struct DiceRoll {
pub count: usize,
pub sides: u8,
pub bonus: i32,
}
impl DiceRoll {
pub fn new(count: usize, sides: u8, bonus: i32) -> Self {
DiceRoll {
count,
sides,
bonus,
}
}
pub fn roll(&self) -> i32 {
self.roll_no_bonus() + self.bonus
}
pub fn roll_no_bonus(&self) -> i32 {
let max = self.sides as usize * self.count;
rand::random_range(self.count..=max) as i32
}
}
impl fmt::Display for DiceRoll {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.bonus != 0 {
write!(f, "{}d{}+{}", self.count, self.sides, self.bonus)
} else {
write!(f, "{}d{}", self.count, self.sides)
}
}
}
impl FromStr for DiceRoll {
type Err = DiceFormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match parse_roll(s) {
IResult::Ok((_, (count, sides, bonus))) => Ok(DiceRoll::new(count, sides, bonus)),
_ => Err(DiceFormatError::new(s)),
}
}
}
#[test]
fn test_dice_roll() {
assert_eq!(
DiceRoll {
count: 1,
sides: 8,
bonus: 3
},
DiceRoll::new(1, 8, 3)
);
let dice = DiceRoll::new(1, 6, 0);
for _ in 0..1000 {
let r = dice.roll();
assert!(r >= 1);
assert!(r <= 6);
}
let dice = DiceRoll::new(2, 6, 0);
for _ in 0..1000 {
let r = dice.roll();
assert!(r >= 1);
assert!(r <= 12);
}
let dice = DiceRoll::new(1, 6, 6);
for _ in 0..1000 {
let r = dice.roll();
assert!(r >= 6);
assert!(r <= 12);
}
}
#[test]
fn test_dice_roll_from_str() {
assert_eq!(
Ok(DiceRoll {
count: 1,
sides: 8,
bonus: 0
}),
DiceRoll::from_str("1d8")
);
assert_eq!(
Ok(DiceRoll {
count: 3,
sides: 12,
bonus: 0
}),
DiceRoll::from_str("3d12")
);
assert_eq!(
Ok(DiceRoll {
count: 1,
sides: 8,
bonus: 3
}),
DiceRoll::from_str("1d8+3")
);
assert_eq!(
Ok(DiceRoll {
count: 1,
sides: 8,
bonus: 3
}),
DiceRoll::from_str("1d8 + 3")
);
for s in ["", "1", "d", "1d", "d3"] {
assert_eq!(Err(DiceFormatError::new(s)), DiceRoll::from_str(s));
}
}