Skip to main content

chess/board/
piece.rs

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