#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![allow(clippy::cast_possible_truncation)]
#[cfg(feature = "rand")]
pub mod deck;
pub mod game;
pub mod hand;
pub mod meld;
pub mod player;
pub mod round;
pub mod rules;
pub use game::{FinalScore, Game};
pub use hand::{Card, Hand, Holding, Rank};
pub use meld::{Meld, MeldKind, Melds, best_melds, deadwood, pip_sum};
pub use player::Player;
pub use round::{Phase, Round, RoundResult};
pub use rules::{Rules, Shutout};
use core::fmt::{self, Write as _};
use core::str::FromStr;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
impl Suit {
pub const ASC: [Self; 4] = [Self::Clubs, Self::Diamonds, Self::Hearts, Self::Spades];
pub const DESC: [Self; 4] = [Self::Spades, Self::Hearts, Self::Diamonds, Self::Clubs];
#[must_use]
#[inline]
pub const fn letter(self) -> char {
match self {
Self::Clubs => 'C',
Self::Diamonds => 'D',
Self::Hearts => 'H',
Self::Spades => 'S',
}
}
}
impl fmt::Display for Suit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_char(match self {
Self::Clubs => '♣',
Self::Diamonds => '♦',
Self::Hearts => '♥',
Self::Spades => '♠',
})
}
}
const EMOJI_SELECTORS: [char; 2] = ['\u{FE0F}', '\u{FE0E}'];
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
#[error("Invalid suit: expected one of C, D, H, S, ♣, ♦, ♥, ♠, ♧, ♢, ♡, ♤")]
pub struct ParseSuitError;
impl FromStr for Suit {
type Err = ParseSuitError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s
.to_ascii_uppercase()
.as_str()
.trim_end_matches(EMOJI_SELECTORS)
{
"C" | "♣" | "♧" => Ok(Self::Clubs),
"D" | "♦" | "♢" => Ok(Self::Diamonds),
"H" | "♥" | "♡" => Ok(Self::Hearts),
"S" | "♠" | "♤" => Ok(Self::Spades),
_ => Err(ParseSuitError),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn suit_order_matches_arrays() {
assert!(Suit::Clubs < Suit::Diamonds);
assert!(Suit::Diamonds < Suit::Hearts);
assert!(Suit::Hearts < Suit::Spades);
let mut sorted = Suit::DESC;
sorted.sort();
assert_eq!(sorted, Suit::ASC);
}
#[test]
fn suit_parsing() {
for suit in Suit::ASC {
assert_eq!(suit.to_string().parse(), Ok(suit));
assert_eq!(suit.letter().to_string().parse(), Ok(suit));
assert_eq!(
suit.letter().to_ascii_lowercase().to_string().parse(),
Ok(suit)
);
}
assert_eq!("SPADES".parse::<Suit>(), Err(ParseSuitError));
assert_eq!("".parse::<Suit>(), Err(ParseSuitError));
assert_eq!("♠♠".parse::<Suit>(), Err(ParseSuitError));
assert_eq!("♠\u{FE0F}".parse(), Ok(Suit::Spades));
assert_eq!("♤\u{FE0E}".parse(), Ok(Suit::Spades));
}
}