1use core::fmt;
4
5#[repr(u8)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum Category {
12 Aces,
14 Twos,
16 Threes,
18 Fours,
20 Fives,
22 Sixes,
24 ThreeOfAKind,
26 FourOfAKind,
28 FullHouse,
30 SmallStraight,
32 LargeStraight,
34 Yahtzee,
36 Chance,
38}
39
40impl Category {
41 pub const ALL: [Self; 13] = [
43 Self::Aces,
44 Self::Twos,
45 Self::Threes,
46 Self::Fours,
47 Self::Fives,
48 Self::Sixes,
49 Self::ThreeOfAKind,
50 Self::FourOfAKind,
51 Self::FullHouse,
52 Self::SmallStraight,
53 Self::LargeStraight,
54 Self::Yahtzee,
55 Self::Chance,
56 ];
57
58 #[must_use]
61 pub const fn upper_face(self) -> Option<u8> {
62 let index = self as u8;
63 if index < 6 { Some(index + 1) } else { None }
64 }
65
66 #[must_use]
70 pub const fn upper(face: u8) -> Option<Self> {
71 match face {
72 1 => Some(Self::Aces),
73 2 => Some(Self::Twos),
74 3 => Some(Self::Threes),
75 4 => Some(Self::Fours),
76 5 => Some(Self::Fives),
77 6 => Some(Self::Sixes),
78 _ => None,
79 }
80 }
81
82 #[must_use]
84 pub const fn bit(self) -> Categories {
85 Categories(1 << self as u16)
86 }
87
88 #[must_use]
90 pub const fn name(self) -> &'static str {
91 match self {
92 Self::Aces => "Aces",
93 Self::Twos => "Twos",
94 Self::Threes => "Threes",
95 Self::Fours => "Fours",
96 Self::Fives => "Fives",
97 Self::Sixes => "Sixes",
98 Self::ThreeOfAKind => "Three of a Kind",
99 Self::FourOfAKind => "Four of a Kind",
100 Self::FullHouse => "Full House",
101 Self::SmallStraight => "Small Straight",
102 Self::LargeStraight => "Large Straight",
103 Self::Yahtzee => "Yahtzee",
104 Self::Chance => "Chance",
105 }
106 }
107}
108
109impl fmt::Display for Category {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.pad(self.name())
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
118pub struct Categories(u16);
119
120impl Categories {
121 pub const EMPTY: Self = Self(0);
123
124 pub const ALL: Self = Self(0x1FFF);
126
127 pub const UPPER: Self = Self(0x003F);
129
130 pub const LOWER: Self = Self(0x1FC0);
132
133 #[must_use]
135 pub const fn contains(self, category: Category) -> bool {
136 self.0 & category.bit().0 != 0
137 }
138
139 #[must_use]
141 pub const fn with(self, category: Category) -> Self {
142 Self(self.0 | category.bit().0)
143 }
144
145 #[must_use]
147 pub const fn len(self) -> u32 {
148 self.0.count_ones()
149 }
150
151 #[must_use]
153 pub const fn is_empty(self) -> bool {
154 self.0 == 0
155 }
156
157 #[must_use]
159 pub const fn bits(self) -> u16 {
160 self.0
161 }
162
163 #[must_use]
167 pub const fn from_bits(bits: u16) -> Option<Self> {
168 if bits & !Self::ALL.0 == 0 {
169 Some(Self(bits))
170 } else {
171 None
172 }
173 }
174
175 pub fn iter(self) -> impl Iterator<Item = Category> {
177 Category::ALL.into_iter().filter(move |&c| self.contains(c))
178 }
179}
180
181impl From<Category> for Categories {
182 fn from(category: Category) -> Self {
183 category.bit()
184 }
185}
186
187impl core::ops::BitOr for Categories {
188 type Output = Self;
189
190 fn bitor(self, rhs: Self) -> Self {
191 Self(self.0 | rhs.0)
192 }
193}
194
195impl core::ops::BitAnd for Categories {
196 type Output = Self;
197
198 fn bitand(self, rhs: Self) -> Self {
199 Self(self.0 & rhs.0)
200 }
201}
202
203impl core::ops::Not for Categories {
204 type Output = Self;
205
206 fn not(self) -> Self {
208 Self(!self.0 & Self::ALL.0)
209 }
210}
211
212impl core::ops::Sub for Categories {
213 type Output = Self;
214
215 fn sub(self, rhs: Self) -> Self {
217 Self(self.0 & !rhs.0)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn bits_are_card_order() {
227 for (i, category) in Category::ALL.into_iter().enumerate() {
228 assert_eq!(category as usize, i);
229 assert_eq!(category.bit().bits(), 1 << i);
230 }
231 assert_eq!(Categories::UPPER | Categories::LOWER, Categories::ALL);
232 assert_eq!(Categories::UPPER & Categories::LOWER, Categories::EMPTY);
233 }
234
235 #[test]
236 fn display_honors_width_flags() {
237 assert_eq!(format!("{}", Category::FullHouse), "Full House");
238 assert_eq!(format!("{:16}", Category::Aces), "Aces ");
239 assert_eq!(format!("{:>6}", Category::Twos), " Twos");
240 }
241
242 #[test]
243 fn upper_face_round_trips() {
244 for face in 1..=6 {
245 let category = Category::upper(face).expect("an upper category");
246 assert_eq!(category.upper_face(), Some(face));
247 assert!(Categories::UPPER.contains(category));
248 }
249 assert_eq!(Category::upper(0), None);
250 assert_eq!(Category::upper(7), None);
251 assert_eq!(Category::Yahtzee.upper_face(), None);
252 }
253
254 #[test]
255 fn set_operations() {
256 let set = Category::Aces.bit().with(Category::Chance);
257 assert_eq!(set.len(), 2);
258 assert!(set.contains(Category::Aces));
259 assert!(!set.contains(Category::Twos));
260 assert_eq!(
261 set.iter().collect::<Vec<_>>(),
262 [Category::Aces, Category::Chance]
263 );
264 assert_eq!(!Categories::EMPTY, Categories::ALL);
265 assert_eq!(Categories::ALL - set, !set);
266 assert_eq!(Categories::from_bits(set.bits()), Some(set));
267 assert_eq!(Categories::from_bits(0x2000), None);
268 }
269}