1use rand::{thread_rng, Rng};
2use regex::Regex;
3
4#[derive(Debug)]
5pub struct Die {
6 sides: u64,
7}
8
9impl Die {
10 pub fn new(sides: u64) -> Die {
11 Die { sides }
12 }
13
14 fn generate_dice(count: u64, sides: u64) -> Vec<Die> {
15 let mut result: Vec<Die> = vec![];
16 for _ in 0..count {
18 result.push(Die::new(sides))
19 }
20
21 result
22 }
23
24 pub fn roll(&self) -> u64 {
25 let mut rng = thread_rng();
26 rng.gen_range(1, self.sides + 1)
27 }
28}
29
30impl PartialEq for Die {
31 fn eq(&self, other: &Die) -> bool {
32 self.sides == other.sides
33 }
34}
35
36pub struct RollResult {
37 modifier: i64,
38 rolls: Vec<u64>,
39}
40
41impl RollResult {
42 pub fn total(&self) -> i64 {
43 self.modifier + self.rolls.iter().sum::<u64>() as i64
44 }
45
46 pub fn rolls(&self) -> &Vec<u64> {
47 &self.rolls
48 }
49}
50
51pub struct Roller {
52 dice: Vec<Die>,
53 modifier: i64,
54}
55
56impl Roller {
57 pub fn new(dice: Vec<Die>, modifier: i64) -> Roller {
58 Roller { dice, modifier }
59 }
60
61 pub fn parse(definition: &str) -> Roller {
62 let regex = Regex::new(r"(?P<num>\d+)?d(?P<sides>\d+)(?P<modifier>[+-]\d+)?").unwrap();
63 let capture = regex.captures(definition).unwrap();
64 let num: u64 = match capture.name("num") {
65 Some(value) => value.as_str().parse().unwrap(),
66 None => 1,
67 };
68 let sides: u64 = match capture.name("sides") {
69 Some(value) => value.as_str().parse().unwrap(),
70 None => 6,
71 };
72 let modifier: i64 = match capture.name("modifier") {
73 Some(value) => value.as_str().parse().unwrap(),
74 None => 0,
75 };
76
77 let dice = Die::generate_dice(num, sides);
78
79 Roller::new(dice, modifier)
80 }
81
82 pub fn roll(&self) -> RollResult {
83 let rolls = self.dice.iter().map(Die::roll).collect();
84
85 RollResult {
86 modifier: self.modifier,
87 rolls,
88 }
89 }
90}
91
92#[cfg(test)]
93mod test_die {
94 use super::*;
95
96 #[test]
97 fn die_new_accepts_sides() {
98 let sides = 6;
99 let die = Die::new(sides);
100
101 assert_eq!(sides, die.sides);
102 }
103
104 #[test]
105 fn die_roll_returns_random() {
106 let sides = 6;
107 let die = Die::new(sides);
108
109 let result = die.roll();
110 assert!(result <= die.sides);
111 }
112}
113
114#[cfg(test)]
115mod test_roller {
116 use super::*;
117
118 #[test]
119 fn roller_new_accepts_input() {
120 let dice = vec![Die::new(6), Die::new(4)];
121 let modifier = 6;
122 let roller = Roller::new(dice, modifier);
123
124 assert_eq!(2, roller.dice.len());
125 assert_eq!(6, roller.dice[0].sides);
126 assert_eq!(4, roller.dice[1].sides);
127 assert_eq!(modifier, roller.modifier);
128 }
129
130 #[test]
131 fn roller_roll_returns_result() {
132 let roller = Roller::new(vec![Die::new(6), Die::new(4)], 4);
133 let result = roller.roll();
134
135 assert_eq!(result.rolls.len(), roller.dice.len());
136 assert_eq!(result.modifier, roller.modifier);
137 }
138
139 #[test]
140 fn roller_parse_accepts_d_n() {
141 let definition = "d4";
142 let roller = Roller::parse(definition);
143
144 assert_eq!(1, roller.dice.len());
145 assert_eq!(4, roller.dice[0].sides);
146 assert_eq!(0, roller.modifier)
147 }
148
149 #[test]
150 fn roller_parse_accepts_n_d_m() {
151 let definition = "2d8";
152 let roller = Roller::parse(definition);
153
154 assert_eq!(2, roller.dice.len());
155 assert_eq!(8, roller.dice[0].sides);
156 assert_eq!(8, roller.dice[1].sides);
157 assert_eq!(0, roller.modifier);
158 }
159
160 #[test]
161 fn roller_parse_accepts_n_d_m_plus_x() {
162 let definition = "3d4+5";
163 let roller = Roller::parse(definition);
164
165 assert_eq!(3, roller.dice.len());
166 assert_eq!(4, roller.dice[0].sides);
167 assert_eq!(5, roller.modifier);
168 }
169
170 #[test]
171 fn roller_parse_accepts_n_d_m_minux_x() {
172 let definition = "1d20-5";
173 let roller = Roller::parse(definition);
174
175 assert_eq!(1, roller.dice.len());
176 assert_eq!(20, roller.dice[0].sides);
177 assert_eq!(-5, roller.modifier);
178 }
179
180 }
184
185#[cfg(test)]
186mod test_result {
187 use super::*;
188
189 #[test]
190 fn result_total_sums_rolls() {
191 let result = RollResult {
192 modifier: 0,
193 rolls: vec![1, 2, 3, 4],
194 };
195
196 assert_eq!(10, result.total())
197 }
198
199 #[test]
200 fn result_total_adds_modifier() {
201 let result = RollResult {
202 modifier: 2,
203 rolls: vec![1, 2],
204 };
205
206 assert_eq!(5, result.total())
207 }
208
209 #[test]
210 fn result_total_subtracts_modifier() {
211 let result = RollResult {
212 modifier: -4,
213 rolls: vec![6, 7],
214 };
215
216 assert_eq!(9, result.total())
217 }
218}