1use regex::Regex;
2use std::num::ParseIntError;
3
4pub mod structs;
5
6use structs::{Dice, DiceModifier, DiceResult, RollingHand, RollingHandResult};
7
8const DICE_REGEX: &str = r#"(?P<numberOfDice>[^[dwe\s+-]]+\d?)(?P<explodingDice>[e])?(?P<diceIndicator>[dw])(?P<numberOfSides>\d+)(?P<mod>[+-]\d+)?"#;
9
10fn str_to_i64(input: &str) -> Result<i64, ParseIntError> {
11 input.parse()
12}
13
14fn parse_dice_from_str(input: &str) -> Option<regex::Captures<'_>> {
15 let rx = Regex::new(DICE_REGEX).unwrap();
16
17 rx.captures(input)
18}
19
20pub fn parse_dices(args: Vec<String>) -> Result<RollingHand, ParseIntError> {
21 let mut hand = RollingHand::default();
22
23 for arg in args {
24 let res = parse_dice_from_str(arg.as_str());
25
26 match res {
27 Some(matches) => {
28 let mut i: i64 = 0;
29 let number_of_dice = matches.name("numberOfDice").unwrap().as_str();
30 let number_of_sides = matches.name("numberOfSides").unwrap().as_str();
31 let exploding_dice = match matches.name("explodingDice") {
32 Some(_ed) => true,
33 None => false,
34 };
35 let modifier = match matches.name("mod") {
36 Some(ed) => ed.as_str(),
37 None => "",
38 };
39 let modifier_value: i64 =
40 match modifier.split(&['+', '-']).collect::<Vec<&str>>().last() {
41 Some(v) => match str_to_i64(v) {
42 Ok(v) => v,
43 Err(_e) => 0,
44 },
45 _ => 0,
46 };
47 let dices = str_to_i64(number_of_dice)?;
48 let sides = str_to_i64(number_of_sides)?;
49 let min: i64 = match sides {
51 10 => 0,
52 _ => 1,
53 };
54 while i < dices {
55 i = i + 1;
56 hand.dices.push(Dice {
57 min,
58 sides,
59 modifier: DiceModifier {
60 is_plus: modifier.contains("+"),
61 value: modifier_value,
62 },
63 exploding_dice,
64 })
65 }
66 }
67 _ => {}
68 }
69 }
70
71 Ok(hand)
72}
73
74pub fn roll_dices(in_hand: RollingHand) -> RollingHandResult {
75 let hand = in_hand;
76 let mut result = RollingHandResult::default();
77
78 for dice in hand.dices {
79 let dice_result = dice.roll();
80 let mut modi = "-";
81 let mut exploding = "";
82 if dice.modifier.is_plus {
83 modi = "+";
84 };
85
86 if dice.exploding_dice {
87 exploding = "e";
88 }
89
90 result.rolls.push(DiceResult {
91 sides: dice.sides,
92 result: dice_result,
93 modifier: format!("{}{}", modi, dice.modifier.value),
94 exploding_dice: String::from(exploding),
95 });
96 result.sum += dice_result;
97 if dice.modifier.is_plus {
98 result.sum += dice.modifier.value
99 } else {
100 result.sum -= dice.modifier.value
101 }
102 }
103
104 result
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_parse_dice() {
113 struct TestCase<'a> {
114 name: &'a str,
115 input: &'a str,
116 expected: RollingHand,
117 }
118
119 let tests = [
120 TestCase {
121 name: "dice: 1d4",
122 input: "1d4",
123 expected: RollingHand {
124 dices: vec![Dice {
125 sides: 4,
126 min: 1,
127 ..Default::default()
128 }],
129 ..Default::default()
130 },
131 },
132 TestCase {
133 name: "dice: 3d12",
134 input: "3d12",
135 expected: RollingHand {
136 dices: vec![
137 Dice {
138 sides: 12,
139 ..Default::default()
140 },
141 Dice {
142 sides: 12,
143 ..Default::default()
144 },
145 Dice {
146 sides: 12,
147 ..Default::default()
148 },
149 ],
150 ..Default::default()
151 },
152 },
153 TestCase {
154 name: "dice: 1ed6",
155 input: "1ed6",
156 expected: RollingHand {
157 dices: vec![Dice {
158 sides: 6,
159 exploding_dice: true,
160 ..Default::default()
161 }],
162 ..Default::default()
163 },
164 },
165 TestCase {
166 name: "dice: 1w6",
167 input: "1w6",
168 expected: RollingHand {
169 dices: vec![Dice {
170 sides: 6,
171 ..Default::default()
172 }],
173 ..Default::default()
174 },
175 },
176 TestCase {
177 name: "dice: 1d10",
178 input: "1d10",
179 expected: RollingHand {
180 dices: vec![Dice {
181 sides: 10,
182 min: 0,
183 ..Default::default()
184 }],
185 ..Default::default()
186 },
187 },
188 ];
189
190 for test in tests {
191 dbg!(test.name);
192 let dice = parse_dices(vec![String::from(test.input)]).unwrap();
193 assert_eq!(test.expected, dice)
194 }
195 }
196}