use core::fmt;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Category {
Aces,
Twos,
Threes,
Fours,
Fives,
Sixes,
ThreeOfAKind,
FourOfAKind,
FullHouse,
SmallStraight,
LargeStraight,
Yahtzee,
Chance,
}
impl Category {
pub const ALL: [Self; 13] = [
Self::Aces,
Self::Twos,
Self::Threes,
Self::Fours,
Self::Fives,
Self::Sixes,
Self::ThreeOfAKind,
Self::FourOfAKind,
Self::FullHouse,
Self::SmallStraight,
Self::LargeStraight,
Self::Yahtzee,
Self::Chance,
];
#[must_use]
pub const fn upper_face(self) -> Option<u8> {
let index = self as u8;
if index < 6 { Some(index + 1) } else { None }
}
#[must_use]
pub const fn upper(face: u8) -> Option<Self> {
match face {
1 => Some(Self::Aces),
2 => Some(Self::Twos),
3 => Some(Self::Threes),
4 => Some(Self::Fours),
5 => Some(Self::Fives),
6 => Some(Self::Sixes),
_ => None,
}
}
#[must_use]
pub const fn bit(self) -> Categories {
Categories(1 << self as u16)
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Aces => "Aces",
Self::Twos => "Twos",
Self::Threes => "Threes",
Self::Fours => "Fours",
Self::Fives => "Fives",
Self::Sixes => "Sixes",
Self::ThreeOfAKind => "Three of a Kind",
Self::FourOfAKind => "Four of a Kind",
Self::FullHouse => "Full House",
Self::SmallStraight => "Small Straight",
Self::LargeStraight => "Large Straight",
Self::Yahtzee => "Yahtzee",
Self::Chance => "Chance",
}
}
}
impl fmt::Display for Category {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Categories(u16);
impl Categories {
pub const EMPTY: Self = Self(0);
pub const ALL: Self = Self(0x1FFF);
pub const UPPER: Self = Self(0x003F);
pub const LOWER: Self = Self(0x1FC0);
#[must_use]
pub const fn contains(self, category: Category) -> bool {
self.0 & category.bit().0 != 0
}
#[must_use]
pub const fn with(self, category: Category) -> Self {
Self(self.0 | category.bit().0)
}
#[must_use]
pub const fn len(self) -> u32 {
self.0.count_ones()
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn bits(self) -> u16 {
self.0
}
#[must_use]
pub const fn from_bits(bits: u16) -> Option<Self> {
if bits & !Self::ALL.0 == 0 {
Some(Self(bits))
} else {
None
}
}
pub fn iter(self) -> impl Iterator<Item = Category> {
Category::ALL.into_iter().filter(move |&c| self.contains(c))
}
}
impl From<Category> for Categories {
fn from(category: Category) -> Self {
category.bit()
}
}
impl core::ops::BitOr for Categories {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl core::ops::BitAnd for Categories {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
impl core::ops::Not for Categories {
type Output = Self;
fn not(self) -> Self {
Self(!self.0 & Self::ALL.0)
}
}
impl core::ops::Sub for Categories {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0 & !rhs.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bits_are_card_order() {
for (i, category) in Category::ALL.into_iter().enumerate() {
assert_eq!(category as usize, i);
assert_eq!(category.bit().bits(), 1 << i);
}
assert_eq!(Categories::UPPER | Categories::LOWER, Categories::ALL);
assert_eq!(Categories::UPPER & Categories::LOWER, Categories::EMPTY);
}
#[test]
fn display_honors_width_flags() {
assert_eq!(format!("{}", Category::FullHouse), "Full House");
assert_eq!(format!("{:16}", Category::Aces), "Aces ");
assert_eq!(format!("{:>6}", Category::Twos), " Twos");
}
#[test]
fn upper_face_round_trips() {
for face in 1..=6 {
let category = Category::upper(face).expect("an upper category");
assert_eq!(category.upper_face(), Some(face));
assert!(Categories::UPPER.contains(category));
}
assert_eq!(Category::upper(0), None);
assert_eq!(Category::upper(7), None);
assert_eq!(Category::Yahtzee.upper_face(), None);
}
#[test]
fn set_operations() {
let set = Category::Aces.bit().with(Category::Chance);
assert_eq!(set.len(), 2);
assert!(set.contains(Category::Aces));
assert!(!set.contains(Category::Twos));
assert_eq!(
set.iter().collect::<Vec<_>>(),
[Category::Aces, Category::Chance]
);
assert_eq!(!Categories::EMPTY, Categories::ALL);
assert_eq!(Categories::ALL - set, !set);
assert_eq!(Categories::from_bits(set.bits()), Some(set));
assert_eq!(Categories::from_bits(0x2000), None);
}
}