Skip to main content

dds_bridge/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use core::fmt::{self, Write as _};
5use thiserror::Error;
6
7/// Bidding and scoring
8pub mod contract;
9
10/// Deals and hands
11pub mod deal;
12
13/// Solver functions for double dummy problems
14pub mod solver;
15
16pub use contract::{Bid, Contract, Level, Penalty};
17pub use deal::{Card, Deal, Hand, Holding, Rank, Seat, SeatFlags};
18pub use solver::Solver;
19
20/// Denomination, a suit or notrump
21///
22/// We choose this representation over `Option<Suit>` because we are not sure if
23/// the latter can be optimized to a single byte.
24///
25/// The order of the suits deviates from [`dds`][dds], but this order provides
26/// natural ordering by deriving [`PartialOrd`] and [`Ord`].
27///
28/// [dds]: https://github.com/dds-bridge/dds
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
30#[repr(u8)]
31pub enum Strain {
32    /// ♣
33    Clubs,
34    /// ♦
35    Diamonds,
36    /// ♥
37    Hearts,
38    /// ♠
39    Spades,
40    /// NT, the strain not proposing a trump suit
41    Notrump,
42}
43
44impl Strain {
45    /// Whether this strain is a minor suit (clubs or diamonds)
46    #[must_use]
47    #[inline]
48    pub const fn is_minor(self) -> bool {
49        matches!(self, Self::Clubs | Self::Diamonds)
50    }
51
52    /// Whether this strain is a major suit (hearts or spades)
53    #[must_use]
54    #[inline]
55    pub const fn is_major(self) -> bool {
56        matches!(self, Self::Hearts | Self::Spades)
57    }
58
59    /// Whether this strain is a suit
60    #[must_use]
61    #[inline]
62    pub const fn is_suit(self) -> bool {
63        !matches!(self, Self::Notrump)
64    }
65
66    /// Whether this strain is notrump
67    #[must_use]
68    #[inline]
69    pub const fn is_notrump(self) -> bool {
70        matches!(self, Self::Notrump)
71    }
72
73    /// Convert to a [`Suit`], returning `None` for notrump
74    #[must_use]
75    #[inline]
76    pub const fn suit(self) -> Option<Suit> {
77        match self {
78            Self::Clubs => Some(Suit::Clubs),
79            Self::Diamonds => Some(Suit::Diamonds),
80            Self::Hearts => Some(Suit::Hearts),
81            Self::Spades => Some(Suit::Spades),
82            Self::Notrump => None,
83        }
84    }
85
86    /// Uppercase letter
87    #[must_use]
88    #[inline]
89    pub const fn letter(self) -> char {
90        match self {
91            Self::Clubs => 'C',
92            Self::Diamonds => 'D',
93            Self::Hearts => 'H',
94            Self::Spades => 'S',
95            Self::Notrump => 'N',
96        }
97    }
98}
99
100impl fmt::Display for Strain {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            Self::Clubs => f.write_char('♣'),
104            Self::Diamonds => f.write_char('♦'),
105            Self::Hearts => f.write_char('♥'),
106            Self::Spades => f.write_char('♠'),
107            Self::Notrump => f.write_str("NT"),
108        }
109    }
110}
111
112impl Strain {
113    /// Strains in the ascending order, the order in this crate
114    pub const ASC: [Self; 5] = [
115        Self::Clubs,
116        Self::Diamonds,
117        Self::Hearts,
118        Self::Spades,
119        Self::Notrump,
120    ];
121
122    /// Strains in the descending order
123    pub const DESC: [Self; 5] = [
124        Self::Notrump,
125        Self::Spades,
126        Self::Hearts,
127        Self::Diamonds,
128        Self::Clubs,
129    ];
130}
131
132/// A suit of playing cards
133///
134/// Suits are convertible to [`Strain`]s since suits form a subset of strains.
135#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
136#[repr(u8)]
137pub enum Suit {
138    /// ♣, convertible to [`Strain::Clubs`]
139    Clubs,
140    /// ♦, convertible to [`Strain::Diamonds`]
141    Diamonds,
142    /// ♥, convertible to [`Strain::Hearts`]
143    Hearts,
144    /// ♠, convertible to [`Strain::Spades`]
145    Spades,
146}
147
148impl Suit {
149    /// Suits in the ascending order, the order in this crate
150    pub const ASC: [Self; 4] = [Self::Clubs, Self::Diamonds, Self::Hearts, Self::Spades];
151
152    /// Suits in the descending order, the order in [`dds_bridge_sys`]
153    pub const DESC: [Self; 4] = [Self::Spades, Self::Hearts, Self::Diamonds, Self::Clubs];
154
155    /// Uppercase letter
156    #[must_use]
157    #[inline]
158    pub const fn letter(self) -> char {
159        match self {
160            Self::Clubs => 'C',
161            Self::Diamonds => 'D',
162            Self::Hearts => 'H',
163            Self::Spades => 'S',
164        }
165    }
166}
167
168impl fmt::Display for Suit {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        f.write_char(match self {
171            Self::Clubs => '♣',
172            Self::Diamonds => '♦',
173            Self::Hearts => '♥',
174            Self::Spades => '♠',
175        })
176    }
177}
178
179impl From<Suit> for Strain {
180    fn from(suit: Suit) -> Self {
181        match suit {
182            Suit::Clubs => Self::Clubs,
183            Suit::Diamonds => Self::Diamonds,
184            Suit::Hearts => Self::Hearts,
185            Suit::Spades => Self::Spades,
186        }
187    }
188}
189
190/// Error raised when converting [`Strain::Notrump`] to a suit
191#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
192#[error("Notrump is not a suit")]
193pub struct SuitFromNotrumpError;
194
195impl TryFrom<Strain> for Suit {
196    type Error = SuitFromNotrumpError;
197
198    fn try_from(strain: Strain) -> Result<Self, Self::Error> {
199        match strain {
200            Strain::Clubs => Ok(Self::Clubs),
201            Strain::Diamonds => Ok(Self::Diamonds),
202            Strain::Hearts => Ok(Self::Hearts),
203            Strain::Spades => Ok(Self::Spades),
204            Strain::Notrump => Err(SuitFromNotrumpError),
205        }
206    }
207}