Skip to main content

chess/board/
moves.rs

1use crate::{
2    Player::{self, *},
3    Square,
4    position::EnPassant,
5    square::Rank::{Six, Three},
6};
7
8use super::{Bitboard, Board};
9
10/// Board Move API.
11impl Board {
12    /// Pieces of `attacker` on this board that attack `square`.
13    ///
14    /// `occupied` is the occupancy view used for line-of-sight and for
15    /// filtering attackers. This supports king-safety checks after the other
16    /// player moves: removed pieces are ignored, and slider rays use the
17    /// post-move blockers.
18    ///
19    /// This is not a full hypothetical-board query for moves by `attacker`,
20    /// because piece roles and players still come from `self`.
21    pub const fn attacks_on(
22        self,
23        square: Square,
24        attacker: Player,
25        occupied: Bitboard,
26    ) -> Bitboard {
27        // Work backwards from the target square to find possible source squares.
28        // Sliders, knights and kings are symmetric enough for this.
29        let straight = square.rook_sight(occupied).intersection(self.rooks_and_queens());
30        let diagonal = square.bishop_sight(occupied).intersection(self.bishops_and_queens());
31        let knights = square.knight_moves().intersection(self.knights());
32        let kings = square.king_moves().intersection(self.kings());
33
34        // Pawns are directional, so find pawn source squares with the opposite
35        // player's pawn attacks, then filter down to `attacker` below.
36        let pawns = square.pawn_attack_moves(attacker.other()).intersection(self.pawns());
37
38        let attacks = straight.union(diagonal).union(knights).union(kings).union(pawns);
39
40        // The role filters above include both players' pieces. Intersecting
41        // with `occupied` removes pieces that are gone in this occupancy view.
42        self.player(attacker).intersection(occupied).intersection(attacks)
43    }
44
45    #[inline]
46    pub const fn attacked_en_passant(
47        self,
48        en_passant: EnPassant,
49        turn: Player,
50    ) -> Option<EnPassant> {
51        let to = en_passant.square();
52        let pawns = self.pawns().intersection(self.player(turn));
53        if pawns.intersection(to.pawn_attack_moves(turn.other())).is_empty() {
54            None
55        } else {
56            Some(en_passant)
57        }
58    }
59
60    #[inline]
61    pub const fn effective_en_passant(
62        self,
63        en_passant: Option<EnPassant>,
64        turn: Player,
65    ) -> Option<EnPassant> {
66        let Some(en_passant) = en_passant else {
67            return None;
68        };
69        let to = en_passant.square();
70        let expected = match to.rank() {
71            Three => Black,
72            Six => White,
73            _ => return None,
74        };
75        if !turn.eq(expected) {
76            return None;
77        }
78
79        // If say d6 is the en passant square, check that d5 actually contains a pawn.
80        let Some(captured) = to.checked_add(turn.other().pawn_push()) else {
81            return None;
82        };
83        let Some(piece) = self.get(captured) else {
84            return None;
85        };
86        if !piece.eq(turn.other().pawn()) {
87            return None;
88        }
89
90        self.attacked_en_passant(en_passant, turn)
91    }
92
93    pub fn king_shields(self, player: Player) -> Bitboard {
94        match self.king_of(player) {
95            Some(king) => {
96                let attacker = self.player(player.other());
97                let straight =
98                    king.rook_sight(Bitboard::EMPTY).intersection(self.rooks_and_queens());
99                let diagonal =
100                    king.bishop_sight(Bitboard::EMPTY).intersection(self.bishops_and_queens());
101                let snipers = straight.union(diagonal).intersection(attacker);
102
103                let mut shields = Bitboard::EMPTY;
104                let mut snipers = snipers;
105                while let Some(sniper) = snipers.pop_first() {
106                    let blockers = king.between(sniper).intersection(self.occupied());
107                    if !blockers.more_than_one() {
108                        shields.append(blockers.intersection(self.player(player)));
109                    }
110                }
111
112                shields
113            }
114            None => Bitboard::EMPTY,
115        }
116    }
117}