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
use anyhow::Result;
use rand::{prelude::Rng, RngCore};
use regex::Regex;
use crate::error::{Error, RollError};
pub struct DiceRoll {
pub rolls: u32,
pub sides: u32,
}
impl DiceRoll {
pub const MINIMUM_ROLLS: u32 = 1;
pub const MINIMUM_SIDES: u32 = 2;
pub fn new(rolls: u32, sides: u32) -> Result<Self> {
if rolls < Self::MINIMUM_ROLLS {
return Err(Error::InvalidRolls(rolls).into());
}
if sides < Self::MINIMUM_SIDES {
return Err(Error::InvalidSides(sides).into());
}
Ok(Self { rolls, sides })
}
pub fn from_string(string: &str) -> Result<Self> {
let (rolls, sides) = Self::parse_rolls_and_sides(string)?;
Self::new(rolls, sides)
}
pub fn parse_rolls_and_sides(string: &str) -> Result<(u32, u32)> {
lazy_static! {
static ref PATTERN: Regex = Regex::new(r"^(\d+)d(\d+)$").unwrap();
}
let captures = PATTERN
.captures(string)
.ok_or_else(|| Error::InvalidExpression(string.to_string()))?;
let rolls = captures
.get(1)
.ok_or_else(|| Error::FailedToParseRolls(string.to_string()))?
.as_str()
.parse::<u32>()?;
let sides = captures
.get(2)
.ok_or_else(|| Error::FailedToParseSides(string.to_string()))?
.as_str()
.parse::<u32>()?;
Ok((rolls, sides))
}
pub fn roll(&self, rng: &mut impl RngCore) -> Result<u32> {
let mut result: u32 = 0;
for _ in 0..self.rolls {
let roll = rng.gen_range(1, self.sides + 1);
result = result
.checked_add(roll)
.ok_or(RollError::IntegerOverFlow(result, roll))?;
}
Ok(result)
}
}
#[cfg(test)]
mod dice_unit_tests {
use super::DiceRoll;
#[test]
fn dice_from_string() {
let _d = DiceRoll::from_string(&"1d6".to_string());
assert!(true);
}
}