1use core::fmt;
4use core::str::FromStr;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct Dice {
14 counts: [u8; 6],
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23pub struct Keep {
24 counts: [u8; 6],
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
29#[non_exhaustive]
30pub enum ParseDiceError {
31 #[error("invalid die face (expected digits 1-6)")]
33 InvalidFace,
34 #[error("wrong number of dice")]
36 WrongCount,
37}
38
39const fn parse_counts(s: &str) -> Result<([u8; 6], u8), ParseDiceError> {
40 let mut counts = [0u8; 6];
41 let mut total = 0u8;
42 let bytes = s.as_bytes();
43 let mut i = 0;
44 while i < bytes.len() {
45 match bytes[i] {
46 b'1'..=b'6' => {
47 counts[(bytes[i] - b'1') as usize] += 1;
48 total += 1;
49 if total > 5 {
50 return Err(ParseDiceError::WrongCount);
51 }
52 }
53 b' ' | b'\t' | b'\n' | b'\r' => {}
54 _ => return Err(ParseDiceError::InvalidFace),
55 }
56 i += 1;
57 }
58 Ok((counts, total))
59}
60
61fn fmt_counts(counts: &[u8; 6], f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 for (i, &n) in counts.iter().enumerate() {
63 for _ in 0..n {
64 write!(f, "{}", i + 1)?;
65 }
66 }
67 Ok(())
68}
69
70impl Dice {
71 #[must_use]
75 pub const fn from_faces(faces: [u8; 5]) -> Option<Self> {
76 let mut counts = [0u8; 6];
77 let mut i = 0;
78 while i < 5 {
79 if faces[i] < 1 || faces[i] > 6 {
80 return None;
81 }
82 counts[(faces[i] - 1) as usize] += 1;
83 i += 1;
84 }
85 Some(Self { counts })
86 }
87
88 #[must_use]
92 pub const fn from_counts(counts: [u8; 6]) -> Option<Self> {
93 let mut sum = 0u16;
96 let mut i = 0;
97 while i < 6 {
98 sum += counts[i] as u16;
99 i += 1;
100 }
101 if sum == 5 {
102 Some(Self { counts })
103 } else {
104 None
105 }
106 }
107
108 #[must_use]
114 pub const fn count(self, face: u8) -> u8 {
115 self.counts[(face - 1) as usize]
116 }
117
118 #[must_use]
120 pub const fn counts(self) -> [u8; 6] {
121 self.counts
122 }
123
124 #[must_use]
126 pub const fn sum(self) -> u8 {
127 let mut sum = 0u8;
128 let mut f = 0;
129 while f < 6 {
130 sum += self.counts[f] * (f as u8 + 1);
131 f += 1;
132 }
133 sum
134 }
135
136 #[must_use]
138 pub const fn yahtzee_face(self) -> Option<u8> {
139 let mut f = 0;
140 while f < 6 {
141 if self.counts[f] == 5 {
142 return Some(f as u8 + 1);
143 }
144 f += 1;
145 }
146 None
147 }
148
149 pub fn faces(self) -> impl Iterator<Item = u8> {
151 (1..=6u8).flat_map(move |f| core::iter::repeat_n(f, usize::from(self.count(f))))
152 }
153
154 pub fn keeps(self) -> impl Iterator<Item = Keep> {
158 Keeps {
159 limits: self.counts,
160 next: Some([0; 6]),
161 }
162 }
163
164 #[must_use]
166 pub const fn contains(self, keep: Keep) -> bool {
167 let mut f = 0;
168 while f < 6 {
169 if keep.counts[f] > self.counts[f] {
170 return false;
171 }
172 f += 1;
173 }
174 true
175 }
176}
177
178impl Keep {
179 pub const EMPTY: Self = Self { counts: [0; 6] };
181
182 #[must_use]
186 pub const fn from_counts(counts: [u8; 6]) -> Option<Self> {
187 let mut sum = 0u16;
189 let mut i = 0;
190 while i < 6 {
191 sum += counts[i] as u16;
192 i += 1;
193 }
194 if sum <= 5 {
195 Some(Self { counts })
196 } else {
197 None
198 }
199 }
200
201 #[must_use]
207 pub const fn count(self, face: u8) -> u8 {
208 self.counts[(face - 1) as usize]
209 }
210
211 #[must_use]
213 pub const fn counts(self) -> [u8; 6] {
214 self.counts
215 }
216
217 #[must_use]
219 pub const fn len(self) -> u8 {
220 let mut sum = 0u8;
221 let mut i = 0;
222 while i < 6 {
223 sum += self.counts[i];
224 i += 1;
225 }
226 sum
227 }
228
229 #[must_use]
231 pub const fn is_empty(self) -> bool {
232 self.len() == 0
233 }
234}
235
236impl From<Dice> for Keep {
237 fn from(dice: Dice) -> Self {
239 Self {
240 counts: dice.counts,
241 }
242 }
243}
244
245impl TryFrom<&[u8]> for Keep {
246 type Error = ParseDiceError;
247
248 fn try_from(faces: &[u8]) -> Result<Self, ParseDiceError> {
250 if faces.len() > 5 {
251 return Err(ParseDiceError::WrongCount);
252 }
253 let mut counts = [0u8; 6];
254 for &face in faces {
255 if !(1..=6).contains(&face) {
256 return Err(ParseDiceError::InvalidFace);
257 }
258 counts[usize::from(face - 1)] += 1;
259 }
260 Ok(Self { counts })
261 }
262}
263
264impl FromStr for Dice {
265 type Err = ParseDiceError;
266
267 fn from_str(s: &str) -> Result<Self, ParseDiceError> {
269 let (counts, total) = parse_counts(s)?;
270 if total == 5 {
271 Ok(Self { counts })
272 } else {
273 Err(ParseDiceError::WrongCount)
274 }
275 }
276}
277
278impl FromStr for Keep {
279 type Err = ParseDiceError;
280
281 fn from_str(s: &str) -> Result<Self, ParseDiceError> {
284 let (counts, _) = parse_counts(s)?;
285 Ok(Self { counts })
286 }
287}
288
289impl fmt::Display for Dice {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 fmt_counts(&self.counts, f)
293 }
294}
295
296impl fmt::Display for Keep {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 fmt_counts(&self.counts, f)
300 }
301}
302
303struct Keeps {
305 limits: [u8; 6],
306 next: Option<[u8; 6]>,
307}
308
309impl Iterator for Keeps {
310 type Item = Keep;
311
312 fn next(&mut self) -> Option<Keep> {
313 let current = self.next?;
314 let mut counts = current;
315 self.next = 'carry: {
316 for f in 0..6 {
317 if counts[f] < self.limits[f] {
318 counts[f] += 1;
319 break 'carry Some(counts);
320 }
321 counts[f] = 0;
322 }
323 None
324 };
325 Some(Keep { counts: current })
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn constructors_enforce_invariants() {
335 assert_eq!(Dice::from_faces([1, 3, 4, 4, 6]), "13446".parse().ok());
336 assert_eq!(Dice::from_faces([0, 3, 4, 4, 6]), None);
337 assert_eq!(Dice::from_faces([7, 3, 4, 4, 6]), None);
338 assert_eq!(Dice::from_counts([5, 0, 0, 0, 0, 0]), "11111".parse().ok());
339 assert_eq!(Dice::from_counts([4, 0, 0, 0, 0, 0]), None);
340 assert_eq!(Keep::from_counts([6, 0, 0, 0, 0, 0]), None);
341 assert_eq!(Dice::from_counts([200, 61, 0, 0, 0, 0]), None);
343 assert_eq!(Keep::from_counts([255, 6, 0, 0, 0, 0]), None);
344 assert_eq!("1344".parse::<Dice>(), Err(ParseDiceError::WrongCount));
345 assert_eq!("134467".parse::<Dice>(), Err(ParseDiceError::InvalidFace));
346 assert_eq!("".parse::<Keep>(), Ok(Keep::EMPTY));
347 assert_eq!("1 3 4\t46".parse::<Dice>(), "13446".parse());
348 }
349
350 #[test]
351 fn accessors() {
352 let dice: Dice = "13446".parse().expect("a valid roll");
353 assert_eq!(dice.count(4), 2);
354 assert_eq!(dice.sum(), 18);
355 assert_eq!(dice.yahtzee_face(), None);
356 assert_eq!(dice.faces().collect::<Vec<_>>(), [1, 3, 4, 4, 6]);
357 assert_eq!(dice.to_string(), "13446");
358
359 let yahtzee: Dice = "22222".parse().expect("a valid roll");
360 assert_eq!(yahtzee.yahtzee_face(), Some(2));
361 }
362
363 #[test]
364 fn keep_containment() {
365 let dice: Dice = "13446".parse().expect("a valid roll");
366 assert!(dice.contains("44".parse().expect("a valid keep")));
367 assert!(dice.contains(Keep::EMPTY));
368 assert!(dice.contains(Keep::from(dice)));
369 assert!(!dice.contains("444".parse().expect("a valid keep")));
370 assert!(!dice.contains("2".parse().expect("a valid keep")));
371 }
372
373 #[test]
374 fn keeps_enumerates_every_sub_multiset() {
375 let distinct: Dice = "12345".parse().expect("a valid roll");
376 assert_eq!(distinct.keeps().count(), 32);
377
378 let yahtzee: Dice = "66666".parse().expect("a valid roll");
379 assert_eq!(yahtzee.keeps().count(), 6);
380
381 let dice: Dice = "13446".parse().expect("a valid roll");
382 let keeps: Vec<Keep> = dice.keeps().collect();
383 assert_eq!(keeps.len(), 24); assert!(keeps.iter().all(|&k| dice.contains(k)));
385 for (i, a) in keeps.iter().enumerate() {
387 assert!(keeps[i + 1..].iter().all(|b| a != b));
388 }
389 }
390}