Skip to main content

chess/
board.rs

1use core::{fmt, ops};
2
3use crate::{
4    Move, Side, Square, finite_for,
5    position::{Error, Result, Special},
6    square::{File, Rank},
7};
8
9mod bitboard;
10mod moves;
11mod piece;
12mod scharnagl;
13
14pub use bitboard::Bitboard;
15pub use piece::*;
16pub use scharnagl::{Scharnagl, scharnagl_by_id};
17
18pub use Player::*;
19pub use Role::*;
20
21// This has 1 + 2 players + 6 roles, so should be 9 * 8 = 72 bytes
22//
23// The `occupied` field is redundant, at which point it would be 64 bytes or 512 bits.
24// Maybe this fits in AVX-512 registers?
25//
26// Invariant: players disjoint, roles disjoint, both union to same (=occupied)
27#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
28/// Validated location of pieces on the board.
29///
30/// A board is structurally valid when:
31/// - the Black and White squares are disjoint;
32/// - the six role bitboards are pairwise disjoint;
33/// - the union of the player bitboards equals the union of the role bitboards; and
34/// - `occupied` is that shared union.
35///
36/// This does not imply that the board is a legal chess position.
37pub struct Board {
38    occupied: Bitboard,
39    players: Players<Bitboard>,
40    roles: Roles<Bitboard>,
41}
42
43/// Unvalidated location of pieces on the board, without the redundant occupied cache.
44#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
45pub struct Parts {
46    pub players: Players<Bitboard>,
47    pub roles: Roles<Bitboard>,
48}
49
50impl fmt::Debug for Board {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        use fmt::Write as _;
53
54        for rank in Rank::ALL.into_iter().rev() {
55            for file in File::ALL {
56                let square = Square::new(file, rank);
57                f.write_char(self.get(square).map_or('.', Piece::char))?;
58                f.write_char(if file < File::H { ' ' } else { '\n' })?;
59            }
60        }
61
62        Ok(())
63    }
64}
65
66/// Board Construction API.
67impl Board {
68    pub const EMPTY: Board = Board {
69        players: Players { black: Bitboard::EMPTY, white: Bitboard::EMPTY },
70        roles: Roles {
71            pawn: Bitboard::EMPTY,
72            knight: Bitboard::EMPTY,
73            bishop: Bitboard::EMPTY,
74            rook: Bitboard::EMPTY,
75            queen: Bitboard::EMPTY,
76            king: Bitboard::EMPTY,
77        },
78        occupied: Bitboard::EMPTY,
79    };
80
81    #[inline]
82    pub const fn freestyle(i: Scharnagl) -> Board {
83        i.board()
84    }
85
86    #[inline]
87    pub const fn new(parts: Parts) -> Result<Board> {
88        let players = parts.players.black.union(parts.players.white);
89        if !parts.players.black.is_disjoint(parts.players.white) {
90            return Err(Error::InconsistentBoard);
91        }
92
93        let mut roles = Bitboard::EMPTY;
94        finite_for!(role in Role {
95            let squares = parts.roles.get(role);
96            if !roles.is_disjoint(squares) {
97                return Err(Error::InconsistentBoard);
98            }
99            roles.append(squares);
100        });
101
102        if !players.eq(roles) {
103            return Err(Error::InconsistentBoard);
104        }
105
106        Ok(Board { occupied: players, players: parts.players, roles: parts.roles })
107    }
108
109    #[inline]
110    pub const fn parts(self) -> Parts {
111        Parts { players: self.players, roles: self.roles }
112    }
113
114    #[inline]
115    pub const fn standard() -> Board {
116        Board {
117            occupied: Bitboard(0xffff_0000_0000_ffff),
118            players: Players { black: Bitboard(0xffff_0000_0000_0000), white: Bitboard(0xffff) },
119            roles: Roles {
120                pawn: Bitboard(0x00ff_0000_0000_ff00),
121                knight: Bitboard(0x4200_0000_0000_0042),
122                bishop: Bitboard(0x2400_0000_0000_0024),
123                rook: Bitboard(0x8100_0000_0000_0081),
124                queen: Bitboard(0x0800_0000_0000_0008),
125                king: Bitboard(0x1000_0000_0000_0010),
126            },
127        }
128    }
129}
130
131/// Board Chess API.
132impl Board {
133    #[inline]
134    pub const fn bishops(self) -> Bitboard {
135        self.roles.bishop
136    }
137
138    #[inline]
139    pub const fn bishops_and_queens(self) -> Bitboard {
140        self.bishops().union(self.queens())
141    }
142
143    #[inline]
144    pub const fn black(self) -> Bitboard {
145        self.players.black
146    }
147
148    #[inline]
149    pub const fn king_of(self, player: Player) -> Option<Square> {
150        self.roles.king.intersection(self.player(player)).first()
151    }
152
153    #[inline]
154    pub const fn kings(self) -> Bitboard {
155        self.roles.king
156    }
157
158    #[inline]
159    pub const fn knights(self) -> Bitboard {
160        self.roles.knight
161    }
162
163    #[inline]
164    pub const fn occupied(self) -> Bitboard {
165        self.occupied
166    }
167
168    #[inline]
169    pub const fn pawns(self) -> Bitboard {
170        self.roles.pawn
171    }
172
173    #[inline]
174    pub(super) const fn play_unchecked(&mut self, player: Player, play: Move) {
175        let Some(special) = play.specials() else {
176            self.remove(play.from);
177            self.remove(play.to);
178            self.insert(play.to, play.role.of(player));
179            return;
180        };
181
182        match special {
183            Special::EnPassant => {
184                let captured = Square::new(play.to.file(), play.from.rank());
185                self.remove(play.from);
186                self.remove(captured);
187                self.insert(play.to, player.pawn());
188            }
189            Special::Castle(file) => {
190                let side = Side::of_rook(play.from, file);
191                let rook_from = player.castle_rook_from(file);
192                let rook_to = player.castle_rook_to(side);
193                self.remove(play.from);
194                self.remove(rook_from);
195                self.insert(play.to, player.king());
196                self.insert(rook_to, player.rook());
197            }
198            Special::Promote(role) => {
199                self.remove(play.from);
200                self.remove(play.to);
201                self.insert(play.to, role.of(player));
202            }
203        }
204    }
205
206    #[inline]
207    pub const fn player(self, player: Player) -> Bitboard {
208        self.players.get(player)
209    }
210
211    #[inline]
212    pub const fn player_at(self, square: Square) -> Option<Player> {
213        match self.get(square) {
214            Some(piece) => Some(piece.player),
215            None => None,
216        }
217    }
218
219    #[inline]
220    pub const fn queens(self) -> Bitboard {
221        self.roles.queen
222    }
223
224    #[inline]
225    pub const fn role(self, role: Role) -> Bitboard {
226        self.roles.get(role)
227    }
228
229    #[inline]
230    pub const fn role_at(self, square: Square) -> Option<Role> {
231        match self.get(square) {
232            Some(piece) => Some(piece.role),
233            None => None,
234        }
235    }
236
237    #[inline]
238    pub const fn rooks(self) -> Bitboard {
239        self.roles.rook
240    }
241
242    #[inline]
243    pub const fn rooks_and_queens(self) -> Bitboard {
244        self.rooks().union(self.queens())
245    }
246
247    /// Bishops, rooks and queens.
248    #[inline]
249    pub const fn sliders(self) -> Bitboard {
250        let Roles { bishop, rook, queen, .. } = self.roles;
251        bishop.symmetric_difference(rook).symmetric_difference(queen)
252    }
253
254    /// Pawns, knights and kings.
255    #[inline]
256    pub const fn steppers(self) -> Bitboard {
257        let Roles { pawn, knight, king, .. } = self.roles;
258        pawn.symmetric_difference(knight).symmetric_difference(king)
259    }
260
261    #[inline]
262    pub const fn unique_king_of(self, player: Player) -> Option<Square> {
263        let kings = self.roles.king.intersection(self.player(player));
264        if kings.more_than_one() { None } else { kings.first() }
265    }
266
267    #[inline]
268    pub const fn white(self) -> Bitboard {
269        self.players.white
270    }
271}
272
273/// Board Map API.
274impl Board {
275    #[inline]
276    pub const fn clear(&mut self) {
277        *self = Self::EMPTY;
278    }
279
280    #[inline]
281    pub const fn get(self, square: Square) -> Option<Piece> {
282        if !self.occupied.contains(square) {
283            return None;
284        }
285
286        let player = if self.players.black.contains(square) {
287            Black
288        } else if self.players.white.contains(square) {
289            White
290        } else {
291            return None;
292        };
293
294        let role = if self.roles.pawn.contains(square) {
295            Pawn
296        } else if self.roles.knight.contains(square) {
297            Knight
298        } else if self.roles.bishop.contains(square) {
299            Bishop
300        } else if self.roles.rook.contains(square) {
301            Rook
302        } else if self.roles.queen.contains(square) {
303            Queen
304        } else if self.roles.king.contains(square) {
305            King
306        } else {
307            return None;
308        };
309
310        Some(role.of(player))
311    }
312
313    #[inline]
314    pub const fn insert(&mut self, square: Square, piece: Piece) -> Option<Piece> {
315        let previous = self.remove(square);
316        self.occupied.insert(square);
317        self.players.get_mut(piece.player).insert(square);
318        self.roles.get_mut(piece.role).insert(square);
319        previous
320    }
321
322    #[inline]
323    pub const fn remove(&mut self, square: Square) -> Option<Piece> {
324        let Some(piece) = self.get(square) else {
325            return None;
326        };
327        self.occupied.remove(square);
328        self.players.get_mut(piece.player).remove(square);
329        self.roles.get_mut(piece.role).remove(square);
330        Some(piece)
331    }
332}
333
334impl Default for Board {
335    fn default() -> Self {
336        Self::EMPTY
337    }
338}
339
340impl From<Board> for Parts {
341    #[inline]
342    fn from(board: Board) -> Self {
343        board.parts()
344    }
345}
346
347impl TryFrom<Parts> for Board {
348    type Error = Error;
349
350    #[inline]
351    fn try_from(parts: Parts) -> Result<Self> {
352        Board::new(parts)
353    }
354}
355
356impl Parts {
357    #[inline]
358    pub const fn bitor_assign(&mut self, other: Self) {
359        self.players.black.append(other.players.black);
360        self.players.white.append(other.players.white);
361
362        finite_for!(role in Role {
363            self.roles.get_mut(role).append(other.roles.get(role));
364        });
365    }
366}
367
368impl ops::BitOrAssign for Parts {
369    #[inline]
370    fn bitor_assign(&mut self, other: Self) {
371        Parts::bitor_assign(self, other);
372    }
373}