1use crate::{Bid, Penalty};
17use core::borrow::Borrow;
18use core::fmt::{self, Write as _};
19use core::ops::Deref;
20use core::str::FromStr;
21use thiserror::Error;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[cfg_attr(
29 feature = "serde",
30 derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
31)]
32pub enum Call {
33 Pass,
35 Double,
37 Redouble,
39 Bid(Bid),
41}
42
43impl From<Bid> for Call {
44 fn from(bid: Bid) -> Self {
45 Self::Bid(bid)
46 }
47}
48
49impl fmt::Display for Call {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 Self::Pass => f.write_char('P'),
53 Self::Double => f.write_char('X'),
54 Self::Redouble => f.write_str("XX"),
55 Self::Bid(bid) => bid.fmt(f),
56 }
57 }
58}
59
60#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
62#[error("Invalid call: expected pass, double, redouble, or a bid like '1NT' or '3♠'")]
63pub struct ParseCallError;
64
65impl FromStr for Call {
66 type Err = ParseCallError;
67
68 fn from_str(s: &str) -> Result<Self, Self::Err> {
69 match s.to_ascii_uppercase().as_str() {
70 "-" | "P" | "PASS" => Ok(Self::Pass),
71 "X" | "DBL" | "DOUBLE" => Ok(Self::Double),
72 "XX" | "RDBL" | "REDOUBLE" => Ok(Self::Redouble),
73 _ => s.parse::<Bid>().map(Self::Bid).map_err(|_| ParseCallError),
74 }
75 }
76}
77
78bitflags::bitflags! {
79 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
81 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82 pub struct RelativeVulnerability: u8 {
83 const WE = 1;
85 const THEY = 2;
87 }
88}
89
90impl RelativeVulnerability {
91 pub const NONE: Self = Self::empty();
93 pub const ALL: Self = Self::all();
95}
96
97impl fmt::Display for RelativeVulnerability {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match *self {
100 Self::NONE => f.write_str("none"),
101 Self::WE => f.write_str("we"),
102 Self::THEY => f.write_str("they"),
103 Self::ALL => f.write_str("both"),
104 _ => unreachable!("RelativeVulnerability has only 4 valid bit combinations"),
105 }
106 }
107}
108
109#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
111#[error("Invalid relative vulnerability: expected one of none, we, they, both, all")]
112pub struct ParseRelativeVulnerabilityError;
113
114impl FromStr for RelativeVulnerability {
115 type Err = ParseRelativeVulnerabilityError;
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 match s.to_ascii_lowercase().as_str() {
119 "none" => Ok(Self::NONE),
120 "we" => Ok(Self::WE),
121 "they" => Ok(Self::THEY),
122 "both" | "all" => Ok(Self::ALL),
123 _ => Err(ParseRelativeVulnerabilityError),
124 }
125 }
126}
127
128bitflags::bitflags! {
129 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138 pub struct AbsoluteVulnerability: u8 {
139 const NS = 1;
141 const EW = 2;
143 }
144}
145
146impl AbsoluteVulnerability {
147 pub const NONE: Self = Self::empty();
149 pub const ALL: Self = Self::all();
151}
152
153impl fmt::Display for AbsoluteVulnerability {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 match *self {
156 Self::NONE => f.write_str("none"),
157 Self::NS => f.write_str("ns"),
158 Self::EW => f.write_str("ew"),
159 Self::ALL => f.write_str("both"),
160 _ => unreachable!("AbsoluteVulnerability has only 4 valid bit combinations"),
161 }
162 }
163}
164
165#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
167#[error("Invalid vulnerability: expected one of none, ns, ew, both, all")]
168pub struct ParseAbsoluteVulnerabilityError;
169
170impl FromStr for AbsoluteVulnerability {
171 type Err = ParseAbsoluteVulnerabilityError;
172
173 fn from_str(s: &str) -> Result<Self, Self::Err> {
174 match s.to_ascii_lowercase().as_str() {
175 "none" => Ok(Self::NONE),
176 "ns" => Ok(Self::NS),
177 "ew" => Ok(Self::EW),
178 "both" | "all" => Ok(Self::ALL),
179 _ => Err(ParseAbsoluteVulnerabilityError),
180 }
181 }
182}
183
184#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192#[non_exhaustive]
193pub enum IllegalCall {
194 #[error("Law 27: insufficient bid")]
196 InsufficientBid {
197 this: Bid,
199 last: Option<Bid>,
201 },
202
203 #[error("Law 36: inadmissible doubles and redoubles")]
205 InadmissibleDouble(Penalty),
206
207 #[error("Law 39: call after the final pass")]
209 AfterFinalPass,
210}
211
212#[derive(Debug, Clone, Default, PartialEq, Eq)]
214#[cfg_attr(
215 feature = "serde",
216 derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
217)]
218pub struct Auction(Vec<Call>);
219
220pub fn display_calls(calls: &[Call]) -> impl fmt::Display + '_ {
226 CallsAsAuction(calls)
227}
228
229struct CallsAsAuction<'a>(&'a [Call]);
230
231impl fmt::Display for CallsAsAuction<'_> {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 let mut iter = self.0.iter();
234 if let Some(first) = iter.next() {
235 write_call_in_auction(first, f)?;
236 for call in iter {
237 f.write_char(' ')?;
238 write_call_in_auction(call, f)?;
239 }
240 }
241 Ok(())
242 }
243}
244
245fn write_call_in_auction(call: &Call, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 match call {
249 Call::Pass => f.write_char('-'),
250 other => fmt::Display::fmt(other, f),
251 }
252}
253
254impl fmt::Display for Auction {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 display_calls(&self.0).fmt(f)
257 }
258}
259
260#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
262pub enum ParseAuctionError {
263 #[error(transparent)]
265 Call(#[from] ParseCallError),
266 #[error(transparent)]
268 Illegal(#[from] IllegalCall),
269}
270
271impl FromStr for Auction {
272 type Err = ParseAuctionError;
273
274 fn from_str(s: &str) -> Result<Self, Self::Err> {
275 let mut auction = Self::new();
276 for token in s.split_ascii_whitespace() {
277 auction.try_push(token.parse()?)?;
278 }
279 Ok(auction)
280 }
281}
282
283impl Deref for Auction {
285 type Target = [Call];
286
287 fn deref(&self) -> &[Call] {
288 &self.0
289 }
290}
291
292impl AsRef<[Call]> for Auction {
293 fn as_ref(&self) -> &[Call] {
294 self
295 }
296}
297
298impl Borrow<[Call]> for Auction {
299 fn borrow(&self) -> &[Call] {
300 self
301 }
302}
303
304impl From<Auction> for Vec<Call> {
305 fn from(auction: Auction) -> Self {
306 auction.0
307 }
308}
309
310impl IntoIterator for Auction {
311 type Item = Call;
312 type IntoIter = std::vec::IntoIter<Call>;
313
314 fn into_iter(self) -> Self::IntoIter {
315 self.0.into_iter()
316 }
317}
318
319impl<'a> IntoIterator for &'a Auction {
320 type Item = &'a Call;
321 type IntoIter = core::slice::Iter<'a, Call>;
322
323 fn into_iter(self) -> Self::IntoIter {
324 self.0.iter()
325 }
326}
327
328impl Auction {
329 #[must_use]
331 pub const fn new() -> Self {
332 Self(Vec::new())
333 }
334
335 #[must_use]
338 pub fn has_ended(&self) -> bool {
339 self.len() >= 4 && self[self.len() - 3..] == [Call::Pass; 3]
340 }
341
342 fn can_double(&self) -> Result<(), IllegalCall> {
344 let admissible = self
345 .iter()
346 .rev()
347 .copied()
348 .enumerate()
349 .find(|&(_, call)| call != Call::Pass)
350 .is_some_and(|(index, call)| index & 1 == 0 && matches!(call, Call::Bid(_)));
351
352 if !admissible {
353 return Err(IllegalCall::InadmissibleDouble(Penalty::Doubled));
354 }
355 Ok(())
356 }
357
358 fn can_redouble(&self) -> Result<(), IllegalCall> {
360 let admissible = self
361 .iter()
362 .rev()
363 .copied()
364 .enumerate()
365 .find(|&(_, call)| call != Call::Pass)
366 .is_some_and(|(index, call)| index & 1 == 0 && call == Call::Double);
367
368 if !admissible {
369 return Err(IllegalCall::InadmissibleDouble(Penalty::Redoubled));
370 }
371 Ok(())
372 }
373
374 fn can_bid(&self, bid: Bid) -> Result<(), IllegalCall> {
376 let last = self.iter().rev().find_map(|&call| match call {
377 Call::Bid(bid) => Some(bid),
378 _ => None,
379 });
380
381 if last >= Some(bid) {
382 return Err(IllegalCall::InsufficientBid { this: bid, last });
383 }
384 Ok(())
385 }
386
387 pub fn can_push(&self, call: Call) -> Result<(), IllegalCall> {
399 if self.has_ended() {
400 return Err(IllegalCall::AfterFinalPass);
401 }
402
403 match call {
404 Call::Pass => Ok(()),
405 Call::Double => self.can_double(),
406 Call::Redouble => self.can_redouble(),
407 Call::Bid(bid) => self.can_bid(bid),
408 }
409 }
410
411 pub fn push(&mut self, call: Call) {
417 self.try_push(call).unwrap();
418 }
419
420 pub fn try_push(&mut self, call: Call) -> Result<(), IllegalCall> {
429 self.can_push(call)?;
430 self.0.push(call);
431 Ok(())
432 }
433
434 pub fn try_extend(&mut self, iter: impl IntoIterator<Item = Call>) -> Result<(), IllegalCall> {
442 let iter = iter.into_iter();
443
444 if let Some(size) = iter.size_hint().1 {
445 self.0.reserve(size);
446 }
447
448 for call in iter {
449 self.try_push(call)?;
450 }
451 Ok(())
452 }
453
454 pub fn pop(&mut self) -> Option<Call> {
456 self.0.pop()
457 }
458
459 pub fn truncate(&mut self, len: usize) {
463 self.0.truncate(len);
464 }
465
466 #[must_use]
501 pub fn declarer(&self) -> Option<usize> {
502 let (parity, strain) =
503 self.iter()
504 .copied()
505 .enumerate()
506 .rev()
507 .find_map(|(index, call)| match call {
508 Call::Bid(bid) => Some((index & 1, bid.strain)),
509 _ => None,
510 })?;
511
512 self.iter()
513 .skip(parity)
514 .step_by(2)
515 .position(|call| match call {
516 Call::Bid(bid) => bid.strain == strain,
517 _ => false,
518 })
519 .map(|position| position << 1 | parity)
520 }
521}
522
523#[cfg(test)]
524mod tests;