1#![no_std]
19
20#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
21
22#[cfg(feature = "std")]
23extern crate std;
24
25use core::{fmt, ops};
26use core::num::NonZeroU16;
27
28mod random;
29
30#[derive(PartialEq, Eq, Debug)]
31pub enum ParseError {
33 MissingD,
35 MissingFaces,
37 MissingModifierValue,
39 InvalidNum,
41 InvalidFaces,
43 InvalidExtra,
45}
46
47impl ParseError {
48 pub fn desc(&self) -> &'static str {
50 match self {
51 ParseError::MissingD => "'d' is missing",
52 ParseError::MissingFaces => "Number of dice's faces is missing",
53 ParseError::MissingModifierValue => "Modifier for roll is missing",
54 ParseError::InvalidNum => "Number of dices is invalid. Should be positive integer",
55 ParseError::InvalidFaces => "Number of faces is invalid. Should be positive integer",
56 ParseError::InvalidExtra => "Number of extra is invalid. Should be positive integer",
57 }
58 }
59}
60
61impl fmt::Display for ParseError {
62 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
63 write!(fmt, "{}", self.desc())
64 }
65}
66
67#[cfg(feature = "std")]
68impl std::error::Error for ParseError {
69}
70
71#[derive(Eq, PartialEq, Debug, Clone, Copy)]
72pub enum Modifier {
74 Plus(u16),
76 Minus(u16),
78}
79
80impl ops::Add<u16> for Modifier {
81 type Output = Modifier;
82
83 fn add(self, other: u16) -> Self::Output {
84 match self {
85 Modifier::Plus(modifier) => Modifier::Plus(modifier.saturating_add(other)),
86 Modifier::Minus(modifier) => match other >= modifier {
87 #[allow(clippy::suspicious_arithmetic_impl)]
88 true => Modifier::Plus(other - modifier),
89 #[allow(clippy::suspicious_arithmetic_impl)]
90 false => Modifier::Minus(modifier - other),
91 },
92 }
93 }
94}
95
96impl ops::AddAssign<u16> for Modifier {
97 fn add_assign(&mut self, other: u16) {
98 *self = *self + other;
99 }
100}
101
102impl ops::Sub<u16> for Modifier {
103 type Output = Modifier;
104
105 fn sub(self, other: u16) -> Self::Output {
106 match self {
107 Modifier::Minus(modifier) => Modifier::Minus(modifier.saturating_sub(other)),
108 Modifier::Plus(modifier) => match other > modifier {
109 true => Modifier::Minus(other - modifier),
110 false => Modifier::Plus(modifier - other),
111 },
112 }
113 }
114}
115
116impl ops::SubAssign<u16> for Modifier {
117 fn sub_assign(&mut self, other: u16) {
118 *self = *self - other;
119 }
120}
121
122impl Modifier {
123 fn modify(self, value: u16) -> u16 {
124 match self {
125 Modifier::Plus(modifier) => value.saturating_add(modifier),
126 Modifier::Minus(modifier) => value.saturating_sub(modifier),
127 }
128 }
129
130 pub fn is_neg(self) -> bool {
132 match self {
133 Modifier::Plus(_) => false,
134 Modifier::Minus(_) => true,
135 }
136 }
137}
138
139impl fmt::Display for Modifier {
140 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
141 match self {
142 Modifier::Plus(0) => Ok(()),
143 Modifier::Plus(value) => write!(fmt, "+{}", value),
144 Modifier::Minus(0) => Ok(()),
145 Modifier::Minus(value) => write!(fmt, "-{}", value),
146 }
147 }
148}
149
150#[derive(Debug)]
151pub struct Roll {
153 pub num: u16,
155 pub faces: NonZeroU16,
157 pub extra: Modifier,
159}
160
161impl Roll {
162 #[inline]
163 pub const fn new(num: u16, faces: NonZeroU16, extra: Modifier) -> Self {
164 Self {
165 num,
166 faces,
167 extra,
168 }
169 }
170
171 pub fn from_str(text: &str) -> Result<Self, ParseError> {
173 const D: &[char] = &['d', 'D'];
174 const PLUS: char = '+';
175 const MINUS: char = '-';
176
177 let text = text.trim();
178
179 let dice_idx = match text.find(D) {
180 Some(idx) => idx,
181 None => return Err(ParseError::MissingD),
182 };
183
184 if dice_idx == text.len() - 1 {
185 return Err(ParseError::MissingFaces);
186 }
187
188 let num = match dice_idx {
189 0 => 1,
190 dice_idx => match text[..dice_idx].trim().parse() {
191 Ok(0) => return Err(ParseError::InvalidNum),
192 Ok(num) => num,
193 Err(_) => return Err(ParseError::InvalidNum),
194 }
195 };
196
197 let extra = text.find(PLUS)
198 .map(|extra| (extra, false))
199 .or_else(|| text.find(MINUS).map(|extra| (extra, true)));
200
201 let (extra, dice_end) = match extra {
202 Some((idx, is_extra_neg)) => match text.len() - 1 == idx {
203 true => return Err(ParseError::MissingModifierValue),
204 false => match text[idx+1..].trim().parse() {
205 Ok(extra) => match is_extra_neg {
206 true => (Modifier::Minus(extra), idx),
207 false => (Modifier::Plus(extra), idx),
208 }
209 Err(_) => return Err(ParseError::InvalidExtra),
210 },
211 },
212 None => (Modifier::Plus(0), text.len()),
213 };
214
215 let faces = match text[dice_idx+1..dice_end].trim().parse() {
216 Ok(0) => return Err(ParseError::InvalidFaces),
217 Ok(faces) => unsafe {
218 NonZeroU16::new_unchecked(faces)
219 },
220 Err(_) => return Err(ParseError::InvalidFaces),
221 };
222
223 Ok(Self::new(num, faces, extra))
224 }
225
226 pub fn min(&self) -> u16 {
228 self.extra.modify(self.num)
229 }
230
231 pub fn max(&self) -> u16 {
233 let res = self.num.saturating_mul(self.faces.get());
234 self.extra.modify(res)
235 }
236
237 pub fn roll_with(&self, fun: fn() -> u64) -> u16 {
248 #[inline(always)]
249 fn mul_high_u64(a: u64, b: u64) -> u64 {
250 (((a as u128) * (b as u128)) >> 64) as u64
251 }
252
253 let faces = self.faces.get() as u64;
254 let mut result: u16 = 0;
255
256 for _ in 0..self.num {
257 let mut random = fun();
258 let mut hi = mul_high_u64(random, faces);
259 let mut lo = random.wrapping_mul(faces);
260
261 if lo < faces {
262 while lo < (faces.wrapping_neg() % faces) {
263 random = fun();
264 hi = mul_high_u64(random, faces);
265 lo = random.wrapping_mul(faces);
266 }
267 }
268
269 debug_assert_ne!(hi, faces);
272 result = result.saturating_add(hi as u16 + 1);
273 }
274
275 self.extra.modify(result)
276 }
277
278 #[inline(always)]
279 pub fn roll(&self) -> u16 {
283 const RANDOM: fn() -> u64 = random::random;
284 self.roll_with(RANDOM)
285 }
286}
287
288impl fmt::Display for Roll {
289 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
290 write!(fmt, "{}d{}{}", self.num, self.faces, self.extra)
291 }
292}