ndice/
lib.rs

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                // match minimum number on dice based on dice type
50                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}