1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use core::fmt::{self, Write as _};
5use thiserror::Error;
6
7pub mod contract;
9
10pub mod deal;
12
13pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
30#[repr(u8)]
31pub enum Strain {
32 Clubs,
34 Diamonds,
36 Hearts,
38 Spades,
40 Notrump,
42}
43
44impl Strain {
45 #[must_use]
47 #[inline]
48 pub const fn is_minor(self) -> bool {
49 matches!(self, Self::Clubs | Self::Diamonds)
50 }
51
52 #[must_use]
54 #[inline]
55 pub const fn is_major(self) -> bool {
56 matches!(self, Self::Hearts | Self::Spades)
57 }
58
59 #[must_use]
61 #[inline]
62 pub const fn is_suit(self) -> bool {
63 !matches!(self, Self::Notrump)
64 }
65
66 #[must_use]
68 #[inline]
69 pub const fn is_notrump(self) -> bool {
70 matches!(self, Self::Notrump)
71 }
72
73 #[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 #[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 pub const ASC: [Self; 5] = [
115 Self::Clubs,
116 Self::Diamonds,
117 Self::Hearts,
118 Self::Spades,
119 Self::Notrump,
120 ];
121
122 pub const DESC: [Self; 5] = [
124 Self::Notrump,
125 Self::Spades,
126 Self::Hearts,
127 Self::Diamonds,
128 Self::Clubs,
129 ];
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
136#[repr(u8)]
137pub enum Suit {
138 Clubs,
140 Diamonds,
142 Hearts,
144 Spades,
146}
147
148impl Suit {
149 pub const ASC: [Self; 4] = [Self::Clubs, Self::Diamonds, Self::Hearts, Self::Spades];
151
152 pub const DESC: [Self; 4] = [Self::Spades, Self::Hearts, Self::Diamonds, Self::Clubs];
154
155 #[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#[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}