#![allow(missing_docs)]
#![allow(clippy::missing_docs_in_private_items)]
dataset_enum! {
pub enum Suit {
Hearts => "Hearts",
Clubs => "Clubs",
Spades => "Spades",
Diamonds => "Diamonds",
}
}
#[cfg(test)]
mod test_suit {
use crate::Suit;
#[test]
fn standard_deck_contains_four_suits() {
assert_eq!(Suit::Hearts as u8, 0);
assert_eq!(Suit::Clubs as u8, 1);
assert_eq!(Suit::Spades as u8, 2);
assert_eq!(Suit::Diamonds as u8, 3);
}
#[cfg(feature = "strum")]
#[test]
fn suit_count_matches_standard_deck() {
use strum::EnumCount;
assert_eq!(<Suit as EnumCount>::COUNT, 4);
}
}
dataset_enum! {
pub enum Rank {
Ace = 1 => "Ace",
Two = 2 => "Two",
Three = 3 => "Three",
Four = 4 => "Four",
Five = 5 => "Five",
Six = 6 => "Six",
Seven = 7 => "Seven",
Eight = 8 => "Eight",
Nine = 9 => "Nine",
Ten = 10 => "Ten",
Jack = 11 => "Jack",
Queen = 12 => "Queen",
King = 13 => "King",
}
}
impl Rank {
#[must_use]
pub const fn ordinal(self) -> u8 {
self as u8
}
}
#[cfg(test)]
mod test_rank {
use crate::Rank;
#[test]
fn ordinal_reflects_rank_positions() {
assert_eq!(Rank::Ace.ordinal(), 1);
assert_eq!(Rank::Ten.ordinal(), 10);
assert_eq!(Rank::King.ordinal(), 13);
}
#[test]
fn face_cards_are_higher_than_number_cards() {
assert!(Rank::Queen.ordinal() > Rank::Ten.ordinal());
assert!(Rank::Jack.ordinal() > Rank::Nine.ordinal());
}
#[cfg(feature = "strum")]
#[test]
fn ranks_cover_standard_deck_span() {
use strum::EnumCount;
assert_eq!(<Rank as EnumCount>::COUNT, 13);
}
}
numeric_enum! {
#[repr(u8)]
pub enum DiceFace {
One = 1 => "One",
Two = 2 => "Two",
Three = 3 => "Three",
Four = 4 => "Four",
Five = 5 => "Five",
Six = 6 => "Six",
}
}
impl DiceFace {
#[must_use]
pub const fn ordinal(self) -> u8 {
self as u8
}
}
#[cfg(test)]
mod test_dice_face {
use crate::DiceFace;
#[test]
fn pip_values_match_die_faces() {
assert_eq!(DiceFace::One.ordinal(), 1);
assert_eq!(DiceFace::Three.ordinal(), 3);
assert_eq!(DiceFace::Six.ordinal(), 6);
}
#[cfg(feature = "strum")]
#[test]
fn dice_face_count_matches_standard_die() {
use strum::EnumCount;
assert_eq!(<DiceFace as EnumCount>::COUNT, 6);
}
}
dataset_enum! {
#[repr(u8)]
pub enum ChessPiece {
Pawn = 1 => "Pawn",
Knight = 2 => "Knight",
Bishop = 3 => "Bishop",
Rook = 4 => "Rook",
Queen = 5 => "Queen",
King = 6 => "King",
}
}
impl ChessPiece {
#[must_use]
pub const fn ordinal(self) -> u8 {
self as u8
}
}
#[cfg(test)]
mod test_chess_piece {
use crate::ChessPiece;
#[test]
fn ordinals_reflect_piece_hierarchy() {
assert!(ChessPiece::Queen.ordinal() > ChessPiece::Rook.ordinal());
assert!(ChessPiece::King.ordinal() > ChessPiece::Queen.ordinal());
assert_eq!(ChessPiece::Pawn.ordinal(), 1);
}
#[cfg(feature = "strum")]
#[test]
fn chess_piece_count_matches_standard_set() {
use strum::EnumCount;
assert_eq!(<ChessPiece as EnumCount>::COUNT, 6);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Card {
pub suit: Suit,
pub rank: Rank,
}
#[must_use]
pub fn standard_deck() -> [Card; 52] {
const SUITS: [Suit; 4] = [Suit::Hearts, Suit::Clubs, Suit::Spades, Suit::Diamonds];
const RANKS: [Rank; 13] = [
Rank::Ace,
Rank::Two,
Rank::Three,
Rank::Four,
Rank::Five,
Rank::Six,
Rank::Seven,
Rank::Eight,
Rank::Nine,
Rank::Ten,
Rank::Jack,
Rank::Queen,
Rank::King,
];
std::array::from_fn(|i| Card {
suit: SUITS[i / 13],
rank: RANKS[i % 13],
})
}
#[cfg(feature = "rand")]
pub fn shuffled_deck<R: rand::Rng + ?Sized>(rng: &mut R) -> [Card; 52] {
use rand::seq::SliceRandom;
let mut deck = standard_deck();
deck.shuffle(rng);
deck
}
#[cfg(feature = "rand")]
impl rand::distr::Distribution<Card> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Card {
Card {
suit: rng.random(),
rank: rng.random(),
}
}
}
dataset_enum! {
pub enum CoinSide { Heads => "Heads", Tails }
}
impl CoinSide {
#[must_use]
pub const fn opposite(self) -> Self {
match self {
Self::Heads => Self::Tails,
Self::Tails => Self::Heads,
}
}
}
dataset_enum! {
pub enum RockPaperScissors { Rock => "Rock", Paper => "Paper", Scissors }
}
dataset_enum! {
pub enum RoundOutcome { Win => "Win", Loss => "Loss", Draw }
}
impl RockPaperScissors {
#[must_use]
pub const fn outcome_against(self, opponent: Self) -> RoundOutcome {
match (self, opponent) {
(Self::Rock, Self::Rock)
| (Self::Paper, Self::Paper)
| (Self::Scissors, Self::Scissors) => RoundOutcome::Draw,
(Self::Rock, Self::Scissors)
| (Self::Paper, Self::Rock)
| (Self::Scissors, Self::Paper) => RoundOutcome::Win,
_ => RoundOutcome::Loss,
}
}
}
impl ChessPiece {
#[must_use]
pub const fn material_value(self) -> Option<u8> {
match self {
Self::Pawn => Some(1),
Self::Knight | Self::Bishop => Some(3),
Self::Rook => Some(5),
Self::Queen => Some(9),
Self::King => None,
}
}
}
impl Rank {
#[must_use]
pub const fn is_face_card(self) -> bool {
matches!(self, Self::Jack | Self::Queen | Self::King)
}
}
checked_u8_enum!(Rank, ordinal);
impl RoundOutcome {
#[must_use]
pub const fn reversed(self) -> Self {
match self {
Self::Win => Self::Loss,
Self::Loss => Self::Win,
Self::Draw => Self::Draw,
}
}
}