dice_roller/
dice.rs

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        // vec![Die::new(sides)]
17        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    // fn roller_parse_accepts_n_d_m_plus_x_d_y()
181    // fn roller_parse_accepts_n_d_m_plus_x_d_y_plus_z()
182    // fn roller_parse_accepts_arbitrary_whitespace()
183}
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}