Skip to main content

chess/board/
piece.rs

1use core::str::FromStr;
2
3use crate::{
4    Side, Square,
5    square::{Direction, File, Rank},
6};
7
8use super::Bitboard;
9
10use Player::*;
11use Rank::*;
12use Role::*;
13
14/// A chess piece, for instance a white pawn or a black queen.
15#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
16pub struct Piece {
17    pub player: Player,
18    pub role: Role,
19}
20
21crate::finite_set!(
22    /// Black and white
23    #[derive(DeserializeFromStr, SerializeDisplay)]
24    Player,
25    Players,
26    PlayerTable,
27    {
28        Black = 0 as black,
29        White = 1 as white,
30    }
31);
32
33crate::finite_set!(
34    /// A chess piece role, such as pawn, knight, bishop, etc.
35    Role,
36    Roles,
37    RoleTable,
38    {
39        Pawn = 1 as pawn,
40        Knight = 2 as knight,
41        Bishop = 3 as bishop,
42        Rook = 5 as rook,
43        Queen = 9 as queen,
44        King = 4 as king,
45    }
46);
47
48impl Piece {
49    #[inline]
50    pub const fn from_char(c: char) -> Option<Self> {
51        let player = if c.is_ascii_lowercase() { Black } else { White };
52        match Role::from_char(c) {
53            Some(role) => Some(Self { player, role }),
54            None => None,
55        }
56    }
57
58    #[inline]
59    #[track_caller]
60    pub(crate) const fn panicky_from_char(c: char) -> Self {
61        match Self::from_char(c) {
62            Some(piece) => piece,
63            None => panic!("invalid piece character"),
64        }
65    }
66
67    #[inline]
68    pub const fn char(self) -> char {
69        match self.player {
70            Black => self.role.black(),
71            White => self.role.white(),
72        }
73    }
74
75    // Eq::eq is not const
76    #[inline]
77    pub const fn eq(self, other: Piece) -> bool {
78        self.player.eq(other.player) && self.role.eq(other.role)
79    }
80}
81
82impl Player {
83    #[inline]
84    pub const fn is_black(self) -> bool {
85        matches!(self, Black)
86    }
87
88    #[inline]
89    pub const fn is_white(self) -> bool {
90        matches!(self, White)
91    }
92
93    #[inline]
94    pub const fn other(self) -> Player {
95        match self {
96            Black => White,
97            White => Black,
98        }
99    }
100
101    #[inline]
102    pub const fn backrank(self) -> Rank {
103        match self {
104            Black => Eight,
105            White => One,
106        }
107    }
108
109    #[inline]
110    pub const fn pawn_start_rank(self) -> Rank {
111        match self {
112            Black => Seven,
113            White => Two,
114        }
115    }
116
117    #[inline]
118    pub const fn pawn_push(self) -> Direction {
119        use Direction::*;
120
121        match self {
122            Black => South,
123            White => North,
124        }
125    }
126
127    #[inline]
128    pub const fn pawn_double_push(self) -> Direction {
129        use Direction::*;
130
131        match self {
132            Black => SouthSouth,
133            White => NorthNorth,
134        }
135    }
136
137    #[inline]
138    pub const fn pawn_capture_directions(self) -> [Direction; 2] {
139        use Direction::*;
140
141        match self {
142            Black => [SouthWest, SouthEast],
143            White => [NorthWest, NorthEast],
144        }
145    }
146
147    #[inline]
148    pub const fn pawn_double_push_rank(self) -> Rank {
149        match self {
150            Black => Six,
151            White => Three,
152        }
153    }
154
155    #[inline]
156    pub const fn promotion_rank(self) -> Rank {
157        match self {
158            Black => One,
159            White => Eight,
160        }
161    }
162
163    /// `White.pawn()`. `Role::of` is the inverse spelling for `Pawn.of(White)`.
164    #[inline]
165    pub const fn pawn(self) -> Piece {
166        Pawn.of(self)
167    }
168
169    #[inline]
170    pub const fn knight(self) -> Piece {
171        Knight.of(self)
172    }
173
174    #[inline]
175    pub const fn bishop(self) -> Piece {
176        Bishop.of(self)
177    }
178
179    #[inline]
180    pub const fn rook(self) -> Piece {
181        Rook.of(self)
182    }
183
184    #[inline]
185    pub const fn queen(self) -> Piece {
186        Queen.of(self)
187    }
188
189    #[inline]
190    pub const fn king(self) -> Piece {
191        King.of(self)
192    }
193
194    /// The square the king moves to when castling on this side.
195    #[inline]
196    pub const fn castle_king_to(self, side: Side) -> Square {
197        Square::new(side.king_to_file(), self.backrank())
198    }
199
200    /// The square the rook moves from when castling from this file.
201    #[inline]
202    pub const fn castle_rook_from(self, file: File) -> Square {
203        Square::new(file, self.backrank())
204    }
205
206    /// The square the rook moves to when castling on this side.
207    #[inline]
208    pub const fn castle_rook_to(self, side: Side) -> Square {
209        Square::new(side.rook_to_file(), self.backrank())
210    }
211
212    /// Squares that must be empty when castling with this king and rook.
213    #[inline]
214    pub const fn castle_empty_path(self, king_from: Square, rook_file: File) -> Bitboard {
215        let side = Side::of_rook(king_from, rook_file);
216        let king_to = self.castle_king_to(side);
217        let rook_from = self.castle_rook_from(rook_file);
218        let rook_to = self.castle_rook_to(side);
219
220        let king_path = king_from.between(king_to).with(king_to);
221        let rook_path = rook_from.between(rook_to).with(rook_to);
222
223        // interval between king and rook, excluding endpoints
224        king_path
225            .union(rook_path)
226            .difference(Bitboard::from_square(king_from))
227            .difference(Bitboard::from_square(rook_from))
228    }
229
230    /// Squares the king occupies or crosses when castling on this side.
231    #[inline]
232    pub const fn castle_king_path(self, king_from: Square, side: Side) -> Bitboard {
233        let king_to = self.castle_king_to(side);
234        king_from.between(king_to).with(king_from).with(king_to)
235    }
236}
237
238impl FromStr for Player {
239    type Err = &'static str;
240
241    fn from_str(value: &str) -> Result<Self, Self::Err> {
242        let value = value.trim();
243        if value.eq_ignore_ascii_case("black") {
244            Ok(Black)
245        } else if value.eq_ignore_ascii_case("white") {
246            Ok(White)
247        } else {
248            Err("player must be white or black")
249        }
250    }
251}
252
253impl<T> Players<T> {
254    #[inline]
255    pub fn swap(self) -> Players<T> {
256        Players { black: self.white, white: self.black }
257    }
258}
259
260impl Role {
261    #[inline]
262    pub const fn from_char(c: char) -> Option<Self> {
263        Some(match c {
264            'p' | 'P' => Pawn,
265            'n' | 'N' => Knight,
266            'b' | 'B' => Bishop,
267            'r' | 'R' => Rook,
268            'q' | 'Q' => Queen,
269            'k' | 'K' => King,
270            _ => return None,
271        })
272    }
273
274    #[track_caller]
275    #[inline]
276    pub(crate) const fn panicky_from_char(c: char) -> Self {
277        match Self::from_char(c) {
278            Some(role) => role,
279            None => panic!("invalid role"),
280        }
281    }
282
283    /// `Bishop.of(White)`
284    #[inline]
285    pub const fn of(self, player: Player) -> Piece {
286        Piece { player, role: self }
287    }
288
289    #[inline]
290    pub const fn lower(self) -> char {
291        match self {
292            Pawn => 'p',
293            Knight => 'n',
294            Bishop => 'b',
295            Rook => 'r',
296            Queen => 'q',
297            King => 'k',
298        }
299    }
300
301    #[inline]
302    pub const fn upper(self) -> char {
303        match self {
304            Pawn => 'P',
305            Knight => 'N',
306            Bishop => 'B',
307            Rook => 'R',
308            Queen => 'Q',
309            King => 'K',
310        }
311    }
312
313    #[inline]
314    pub const fn figurine(self) -> char {
315        match self {
316            Pawn => '♙',
317            Knight => '♘',
318            Bishop => '♗',
319            Rook => '♖',
320            Queen => '♕',
321            King => '♔',
322        }
323    }
324
325    #[inline]
326    pub const fn black(self) -> char {
327        self.lower()
328    }
329
330    #[inline]
331    pub const fn white(self) -> char {
332        self.upper()
333    }
334}