candidate/movegen/
piece_type.rs

1use crate::bitboard::{BitBoard, EMPTY};
2use crate::board::Board;
3use crate::color::Color;
4use crate::movegen::{MoveList, SquareAndBitBoard};
5use crate::piece::Piece;
6use crate::square::Square;
7
8use crate::magic::{
9    between, get_adjacent_files, get_bishop_moves, get_bishop_rays, get_king_moves,
10    get_knight_moves, get_pawn_attacks, get_pawn_moves, get_rank, get_rook_moves, get_rook_rays,
11    line,
12};
13
14pub trait PieceType {
15    fn is(piece: Piece) -> bool;
16    fn into_piece() -> Piece;
17    fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard;
18    #[inline(always)]
19    fn legals<T>(
20        movelist: &mut MoveList,
21        board: &Board,
22        mask: BitBoard,
23        from_square: Option<Square>,
24    ) where
25        T: CheckType,
26    {
27        let combined = board.combined();
28        let color = board.side_to_move();
29        let my_pieces = board.color_combined(color);
30        let ksq = board.king_square(color);
31
32        let from_mask = match from_square {
33            Some(from) => BitBoard::from_square(from),
34            None => *my_pieces,
35        };
36
37        let pieces = board.pieces(Self::into_piece()) & from_mask;
38        let pinned = board.pinned();
39        let checkers = board.checkers();
40
41        let check_mask = if T::IN_CHECK {
42            between(checkers.to_square(), ksq) ^ checkers
43        } else {
44            !EMPTY
45        };
46
47        for src in pieces & !pinned {
48            let moves = Self::pseudo_legals(src, color, *combined, mask) & check_mask;
49            if moves != EMPTY {
50                unsafe {
51                    movelist.push_unchecked(SquareAndBitBoard::new(src, moves, false));
52                }
53            }
54        }
55
56        if !T::IN_CHECK {
57            for src in pieces & pinned {
58                let moves = Self::pseudo_legals(src, color, *combined, mask) & line(src, ksq);
59                if moves != EMPTY {
60                    unsafe {
61                        movelist.push_unchecked(SquareAndBitBoard::new(src, moves, false));
62                    }
63                }
64            }
65        }
66    }
67}
68
69pub struct PawnType;
70pub struct BishopType;
71pub struct KnightType;
72pub struct RookType;
73pub struct QueenType;
74pub struct KingType;
75
76pub trait CheckType {
77    const IN_CHECK: bool;
78}
79
80pub struct InCheckType;
81pub struct NotInCheckType;
82
83impl CheckType for InCheckType {
84    const IN_CHECK: bool = true;
85}
86
87impl CheckType for NotInCheckType {
88    const IN_CHECK: bool = false;
89}
90
91impl PawnType {
92    /// Is a particular en-passant capture legal?
93    pub fn legal_ep_move(board: &Board, source: Square, dest: Square) -> bool {
94        let combined = board.combined()
95            ^ BitBoard::from_square(board.en_passant().unwrap())
96            ^ BitBoard::from_square(source)
97            ^ BitBoard::from_square(dest);
98
99        let ksq =
100            (board.pieces(Piece::King) & board.color_combined(board.side_to_move())).to_square();
101
102        let rooks = (board.pieces(Piece::Rook) | board.pieces(Piece::Queen))
103            & board.color_combined(!board.side_to_move());
104
105        if (get_rook_rays(ksq) & rooks) != EMPTY {
106            if (get_rook_moves(ksq, combined) & rooks) != EMPTY {
107                return false;
108            }
109        }
110
111        let bishops = (board.pieces(Piece::Bishop) | board.pieces(Piece::Queen))
112            & board.color_combined(!board.side_to_move());
113
114        if (get_bishop_rays(ksq) & bishops) != EMPTY {
115            if (get_bishop_moves(ksq, combined) & bishops) != EMPTY {
116                return false;
117            }
118        }
119
120        return true;
121    }
122}
123
124impl PieceType for PawnType {
125    fn is(piece: Piece) -> bool {
126        piece == Piece::Pawn
127    }
128
129    fn into_piece() -> Piece {
130        Piece::Pawn
131    }
132
133    #[inline(always)]
134    fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard {
135        get_pawn_moves(src, color, combined) & mask
136    }
137
138    #[inline(always)]
139    fn legals<T>(
140        movelist: &mut MoveList,
141        board: &Board,
142        mask: BitBoard,
143        from_square: Option<Square>,
144    ) where
145        T: CheckType,
146    {
147        let combined = board.combined();
148        let color = board.side_to_move();
149        let my_pieces = board.color_combined(color);
150        let ksq = board.king_square(color);
151
152        let from_mask = match from_square {
153            Some(from) => BitBoard::from_square(from),
154            None => *my_pieces,
155        };
156
157        let pieces = board.pieces(Self::into_piece()) & from_mask;
158        let pinned = board.pinned();
159        let checkers = board.checkers();
160
161        let check_mask = if T::IN_CHECK {
162            between(checkers.to_square(), ksq) ^ checkers
163        } else {
164            !EMPTY
165        };
166
167        for src in pieces & !pinned {
168            let moves = Self::pseudo_legals(src, color, *combined, mask) & check_mask;
169            if moves != EMPTY {
170                unsafe {
171                    movelist.push_unchecked(SquareAndBitBoard::new(
172                        src,
173                        moves,
174                        src.get_rank() == color.to_seventh_rank(),
175                    ));
176                }
177            }
178        }
179
180        if !T::IN_CHECK {
181            for src in pieces & pinned {
182                let moves = Self::pseudo_legals(src, color, *combined, mask) & line(ksq, src);
183                if moves != EMPTY {
184                    unsafe {
185                        movelist.push_unchecked(SquareAndBitBoard::new(
186                            src,
187                            moves,
188                            src.get_rank() == color.to_seventh_rank(),
189                        ));
190                    }
191                }
192            }
193        }
194
195        if board.en_passant().is_some() {
196            let ep_sq = board.en_passant().unwrap();
197            let rank = get_rank(ep_sq.get_rank());
198            let files = get_adjacent_files(ep_sq.get_file());
199            for src in rank & files & pieces {
200                let dest = ep_sq.uforward(color);
201                if PawnType::legal_ep_move(board, src, dest) {
202                    unsafe {
203                        movelist.push_unchecked(SquareAndBitBoard::new(
204                            src,
205                            BitBoard::from_square(dest),
206                            false,
207                        ));
208                    }
209                }
210            }
211        }
212    }
213}
214
215impl PieceType for BishopType {
216    fn is(piece: Piece) -> bool {
217        piece == Piece::Bishop
218    }
219
220    fn into_piece() -> Piece {
221        Piece::Bishop
222    }
223
224    #[inline(always)]
225    fn pseudo_legals(src: Square, _color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard {
226        get_bishop_moves(src, combined) & mask
227    }
228}
229
230impl PieceType for KnightType {
231    fn is(piece: Piece) -> bool {
232        piece == Piece::Knight
233    }
234
235    fn into_piece() -> Piece {
236        Piece::Knight
237    }
238
239    #[inline(always)]
240    fn pseudo_legals(src: Square, _color: Color, _combined: BitBoard, mask: BitBoard) -> BitBoard {
241        get_knight_moves(src) & mask
242    }
243
244    #[inline(always)]
245    fn legals<T>(
246        movelist: &mut MoveList,
247        board: &Board,
248        mask: BitBoard,
249        from_square: Option<Square>,
250    ) where
251        T: CheckType,
252    {
253        let combined = board.combined();
254        let color = board.side_to_move();
255        let my_pieces = board.color_combined(color);
256        let ksq = board.king_square(color);
257
258        let from_mask = match from_square {
259            Some(from) => BitBoard::from_square(from),
260            None => *my_pieces,
261        };
262
263        let pieces = board.pieces(Self::into_piece()) & from_mask;
264        let pinned = board.pinned();
265        let checkers = board.checkers();
266
267        if T::IN_CHECK {
268            let check_mask = between(checkers.to_square(), ksq) ^ checkers;
269
270            for src in pieces & !pinned {
271                let moves = Self::pseudo_legals(src, color, *combined, mask & check_mask);
272                if moves != EMPTY {
273                    unsafe {
274                        movelist.push_unchecked(SquareAndBitBoard::new(src, moves, false));
275                    }
276                }
277            }
278        } else {
279            for src in pieces & !pinned {
280                let moves = Self::pseudo_legals(src, color, *combined, mask);
281                if moves != EMPTY {
282                    unsafe {
283                        movelist.push_unchecked(SquareAndBitBoard::new(src, moves, false));
284                    }
285                }
286            }
287        };
288    }
289}
290
291impl PieceType for RookType {
292    fn is(piece: Piece) -> bool {
293        piece == Piece::Rook
294    }
295
296    fn into_piece() -> Piece {
297        Piece::Rook
298    }
299
300    #[inline(always)]
301    fn pseudo_legals(src: Square, _color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard {
302        get_rook_moves(src, combined) & mask
303    }
304}
305
306impl PieceType for QueenType {
307    fn is(piece: Piece) -> bool {
308        piece == Piece::Queen
309    }
310
311    fn into_piece() -> Piece {
312        Piece::Queen
313    }
314
315    #[inline(always)]
316    fn pseudo_legals(src: Square, _color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard {
317        (get_rook_moves(src, combined) ^ get_bishop_moves(src, combined)) & mask
318    }
319}
320
321impl KingType {
322    /// Is a particular king move legal?
323    #[inline(always)]
324    pub fn legal_king_move(board: &Board, dest: Square) -> bool {
325        let combined = board.combined()
326            ^ (board.pieces(Piece::King) & board.color_combined(board.side_to_move()))
327            | BitBoard::from_square(dest);
328
329        let mut attackers = EMPTY;
330
331        let rooks = (board.pieces(Piece::Rook) | board.pieces(Piece::Queen))
332            & board.color_combined(!board.side_to_move());
333
334        attackers |= get_rook_moves(dest, combined) & rooks;
335
336        let bishops = (board.pieces(Piece::Bishop) | board.pieces(Piece::Queen))
337            & board.color_combined(!board.side_to_move());
338
339        attackers |= get_bishop_moves(dest, combined) & bishops;
340
341        let knight_rays = get_knight_moves(dest);
342        attackers |=
343            knight_rays & board.pieces(Piece::Knight) & board.color_combined(!board.side_to_move());
344
345        let king_rays = get_king_moves(dest);
346        attackers |=
347            king_rays & board.pieces(Piece::King) & board.color_combined(!board.side_to_move());
348
349        attackers |= get_pawn_attacks(
350            dest,
351            board.side_to_move(),
352            board.pieces(Piece::Pawn) & board.color_combined(!board.side_to_move()),
353        );
354
355        return attackers == EMPTY;
356    }
357}
358
359impl PieceType for KingType {
360    fn is(piece: Piece) -> bool {
361        piece == Piece::King
362    }
363
364    fn into_piece() -> Piece {
365        Piece::King
366    }
367
368    #[inline(always)]
369    fn pseudo_legals(src: Square, _color: Color, _combined: BitBoard, mask: BitBoard) -> BitBoard {
370        get_king_moves(src) & mask
371    }
372
373    #[inline(always)]
374    fn legals<T>(
375        movelist: &mut MoveList,
376        board: &Board,
377        mask: BitBoard,
378        from_square: Option<Square>, // ignored
379    ) where
380        T: CheckType,
381    {
382        let combined = board.combined();
383        let color = board.side_to_move();
384        let ksq = board.king_square(color);
385
386        let mut moves = Self::pseudo_legals(ksq, color, *combined, mask);
387
388        let copy = moves;
389        for dest in copy {
390            if !KingType::legal_king_move(board, dest) {
391                moves ^= BitBoard::from_square(dest);
392            }
393        }
394
395        // If we are not in check, we may be able to castle.
396        // We can do so iff:
397        //  * the `Board` structure says we can.
398        //  * the squares between my king and my rook are empty.
399        //  * no enemy pieces are attacking the squares between the king, and the kings
400        //    destination square.
401        //  ** This is determined by going to the left or right, and calling
402        //     'legal_king_move' for that square.
403        if !T::IN_CHECK {
404            if board.my_castle_rights().has_kingside()
405                && (combined & board.my_castle_rights().kingside_squares(color)) == EMPTY
406            {
407                let middle = ksq.uright();
408                let right = middle.uright();
409                if KingType::legal_king_move(board, middle)
410                    && KingType::legal_king_move(board, right)
411                {
412                    moves ^= BitBoard::from_square(right);
413                }
414            }
415
416            if board.my_castle_rights().has_queenside()
417                && (combined & board.my_castle_rights().queenside_squares(color)) == EMPTY
418            {
419                let middle = ksq.uleft();
420                let left = middle.uleft();
421                if KingType::legal_king_move(board, middle)
422                    && KingType::legal_king_move(board, left)
423                {
424                    moves ^= BitBoard::from_square(left);
425                }
426            }
427        }
428        if moves != EMPTY {
429            unsafe {
430                movelist.push_unchecked(SquareAndBitBoard::new(ksq, moves, false));
431            }
432        }
433    }
434}