#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
pub mod contract;
pub mod deal;
pub mod solver;
pub use contract::{Bid, Contract, Level, Penalty};
pub use deal::{Card, Deal, Hand, Holding, Rank, Seat, SeatFlags};
pub use solver::Solver;
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 Strain {
Clubs,
Diamonds,
Hearts,
Spades,
Notrump,
}
impl Strain {
#[must_use]
#[inline]
pub const fn is_minor(self) -> bool {
matches!(self, Self::Clubs | Self::Diamonds)
}
#[must_use]
#[inline]
pub const fn is_major(self) -> bool {
matches!(self, Self::Hearts | Self::Spades)
}
#[must_use]
#[inline]
pub const fn is_suit(self) -> bool {
!matches!(self, Self::Notrump)
}
#[must_use]
#[inline]
pub const fn is_notrump(self) -> bool {
matches!(self, Self::Notrump)
}
#[must_use]
#[inline]
pub const fn suit(self) -> Option<Suit> {
match self {
Self::Clubs => Some(Suit::Clubs),
Self::Diamonds => Some(Suit::Diamonds),
Self::Hearts => Some(Suit::Hearts),
Self::Spades => Some(Suit::Spades),
Self::Notrump => None,
}
}
#[must_use]
#[inline]
pub const fn letter(self) -> char {
match self {
Self::Clubs => 'C',
Self::Diamonds => 'D',
Self::Hearts => 'H',
Self::Spades => 'S',
Self::Notrump => 'N',
}
}
}
impl fmt::Display for Strain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Clubs => f.write_char('♣'),
Self::Diamonds => f.write_char('♦'),
Self::Hearts => f.write_char('♥'),
Self::Spades => f.write_char('♠'),
Self::Notrump => f.write_str("NT"),
}
}
}
impl Strain {
pub const ASC: [Self; 5] = [
Self::Clubs,
Self::Diamonds,
Self::Hearts,
Self::Spades,
Self::Notrump,
];
pub const DESC: [Self; 5] = [
Self::Notrump,
Self::Spades,
Self::Hearts,
Self::Diamonds,
Self::Clubs,
];
}
#[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 => '♠',
})
}
}
impl From<Suit> for Strain {
fn from(suit: Suit) -> Self {
match suit {
Suit::Clubs => Self::Clubs,
Suit::Diamonds => Self::Diamonds,
Suit::Hearts => Self::Hearts,
Suit::Spades => Self::Spades,
}
}
}
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
#[error("Notrump is not a suit")]
pub struct SuitFromNotrumpError;
impl TryFrom<Strain> for Suit {
type Error = SuitFromNotrumpError;
fn try_from(strain: Strain) -> Result<Self, Self::Error> {
match strain {
Strain::Clubs => Ok(Self::Clubs),
Strain::Diamonds => Ok(Self::Diamonds),
Strain::Hearts => Ok(Self::Hearts),
Strain::Spades => Ok(Self::Spades),
Strain::Notrump => Err(SuitFromNotrumpError),
}
}
}
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),
}
}
}
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
#[error("Invalid strain: expected one of C, D, H, S, N, NT, ♣, ♦, ♥, ♠, ♧, ♢, ♡, ♤")]
pub struct ParseStrainError;
impl FromStr for Strain {
type Err = ParseStrainError;
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),
"N" | "NT" => Ok(Self::Notrump),
_ => Err(ParseStrainError),
}
}
}