Skip to main content

dds_bridge/
seat.rs

1//! Table positions and bitsets over them.
2//!
3//! [`Seat`] names one of the four positions at the table ([`North`], [`East`],
4//! [`South`], [`West`]) in dealing order.  It is the indexing key for the deal
5//! containers in [`crate::deal`], but it is also useful on its own — e.g. as a
6//! dealer tag, as the declarer of a [`Contract`](crate::Contract), or as the
7//! leader of a trick.  Parses case-insensitively from full names (`"North"`)
8//! or single letters (`"N"`).
9//!
10//! [`SeatFlags`] is a [`bitflags`] bitset over seats with named constants for
11//! the empty and full sets ([`SeatFlags::EMPTY`], [`SeatFlags::ALL`]) and the
12//! two partnerships ([`SeatFlags::NS`], [`SeatFlags::EW`]).  A single `Seat`
13//! converts into a singleton `SeatFlags` via [`From<Seat>`].
14//!
15//! When the `serde` feature is enabled, `Seat` derives `Serialize`/
16//! `Deserialize` using the standard enum-variant encoding, and `SeatFlags`
17//! derives the `bitflags` crate's serde encoding.
18//!
19//! [`North`]: Seat::North
20//! [`East`]: Seat::East
21//! [`South`]: Seat::South
22//! [`West`]: Seat::West
23
24use core::fmt::{self, Write as _};
25use core::str::FromStr;
26use thiserror::Error;
27
28/// Position at the table
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31pub enum Seat {
32    /// Dealer of Board 1, partner of [`Seat::South`]
33    North,
34    /// Dealer of Board 2, partner of [`Seat::West`]
35    East,
36    /// Dealer of Board 3, partner of [`Seat::North`]
37    South,
38    /// Dealer of Board 4, partner of [`Seat::East`]
39    West,
40}
41
42impl Seat {
43    /// Seats in the order of dealing
44    pub const ALL: [Self; 4] = [Self::North, Self::East, Self::South, Self::West];
45
46    /// The partner of the seat
47    #[must_use]
48    pub const fn partner(self) -> Self {
49        match self {
50            Self::North => Self::South,
51            Self::East => Self::West,
52            Self::South => Self::North,
53            Self::West => Self::East,
54        }
55    }
56
57    /// The opponent on the left of the seat
58    #[must_use]
59    pub const fn lho(self) -> Self {
60        match self {
61            Self::North => Self::East,
62            Self::East => Self::South,
63            Self::South => Self::West,
64            Self::West => Self::North,
65        }
66    }
67
68    /// The opponent on the right of the seat
69    #[must_use]
70    pub const fn rho(self) -> Self {
71        match self {
72            Self::North => Self::West,
73            Self::East => Self::North,
74            Self::South => Self::East,
75            Self::West => Self::South,
76        }
77    }
78
79    /// Display character for this seat
80    #[must_use]
81    #[inline]
82    pub const fn letter(self) -> char {
83        match self {
84            Self::North => 'N',
85            Self::East => 'E',
86            Self::South => 'S',
87            Self::West => 'W',
88        }
89    }
90}
91
92const _: () = assert!(Seat::ALL[0] as u8 == 0);
93const _: () = assert!(Seat::ALL[1] as u8 == 1);
94const _: () = assert!(Seat::ALL[2] as u8 == 2);
95const _: () = assert!(Seat::ALL[3] as u8 == 3);
96
97impl fmt::Display for Seat {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        f.write_char(self.letter())
100    }
101}
102
103/// Error returned when parsing a [`Seat`] fails
104#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
105#[error("Invalid seat: expected one of N, E, S, W (or full names)")]
106pub struct ParseSeatError;
107
108impl FromStr for Seat {
109    type Err = ParseSeatError;
110
111    fn from_str(s: &str) -> Result<Self, Self::Err> {
112        match s.to_ascii_uppercase().as_str() {
113            "N" | "NORTH" => Ok(Self::North),
114            "E" | "EAST" => Ok(Self::East),
115            "S" | "SOUTH" => Ok(Self::South),
116            "W" | "WEST" => Ok(Self::West),
117            _ => Err(ParseSeatError),
118        }
119    }
120}
121
122bitflags::bitflags! {
123    /// A set of seats
124    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
125    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126    pub struct SeatFlags: u8 {
127        /// The set containing [`Seat::North`]
128        const NORTH = 0b0001;
129        /// The set containing [`Seat::East`]
130        const EAST = 0b0010;
131        /// The set containing [`Seat::South`]
132        const SOUTH = 0b0100;
133        /// The set containing [`Seat::West`]
134        const WEST = 0b1000;
135    }
136}
137
138impl SeatFlags {
139    /// The empty set
140    pub const EMPTY: Self = Self::empty();
141
142    /// The set containing all seats
143    pub const ALL: Self = Self::all();
144
145    /// The set containing [`Seat::North`] and [`Seat::South`]
146    pub const NS: Self = Self::NORTH.union(Self::SOUTH);
147
148    /// The set containing [`Seat::East`] and [`Seat::West`]
149    pub const EW: Self = Self::EAST.union(Self::WEST);
150}
151
152impl From<Seat> for SeatFlags {
153    fn from(seat: Seat) -> Self {
154        match seat {
155            Seat::North => Self::NORTH,
156            Seat::East => Self::EAST,
157            Seat::South => Self::SOUTH,
158            Seat::West => Self::WEST,
159        }
160    }
161}