chess_move_gen/generation/
mod.rs

1mod attacks;
2mod castle;
3mod lookup;
4mod pawn;
5mod pinned;
6pub mod slider;
7mod util;
8
9use self::attacks::*;
10use self::castle::*;
11use self::lookup::*;
12use self::pawn::*;
13use self::pinned::*;
14use self::slider::consts::squares_between;
15use self::slider::*;
16use std::cmp::Ordering;
17
18use self::attacks::king_danger_squares;
19use crate::bb::{BB, EMPTY};
20use crate::mv_list::MoveAdder;
21use crate::piece::*;
22use crate::position::Position;
23
24/// Contains data about checkers, pinned pieces, and pinners
25///
26/// If you need to verify if a position is in check before generating legal moves then using
27/// movegen_preprocessing() followed by legal_moves_with_preprocessing()
28/// will save some time. eg:
29///
30///
31/// let movegen_data = movegen_preprocessing(position);
32/// if movegen_data.in_check() {
33///    // ...do something
34/// }
35/// legal_moves_with_preprocessing(list, data);
36///
37pub struct MoveGenPreprocessing((BB, BB, BB));
38
39impl MoveGenPreprocessing {
40    #[allow(dead_code)]
41    pub fn in_check(&self) -> bool {
42        self.0.0 != EMPTY
43    }
44}
45
46/// Adds all legal moves to the provided MoveAdder. Returns true if mover is in check
47pub fn legal_moves<L: MoveAdder>(position: &Position, list: &mut L) -> bool {
48    legal_moves_with_preprocessing(position, list, movegen_preprocessing(position))
49}
50
51/// Generates data about checkers, pinned pieces and pinners to be used by legal_moves_with_preprocessing()
52pub fn movegen_preprocessing(position: &Position) -> MoveGenPreprocessing {
53    let stm = position.state().stm;
54    let kings = position.bb_pc(KING.pc(stm));
55
56    MoveGenPreprocessing(checkers_and_pinned(kings, stm.flip(), position))
57}
58
59/// Adds all legal moves to the provided MoveAdder. Returns true if moving side is in check
60pub fn legal_moves_with_preprocessing<L: MoveAdder>(
61    position: &Position,
62    list: &mut L,
63    preprocessed_data: MoveGenPreprocessing,
64) -> bool {
65    let stm = position.state().stm;
66    let kings = position.bb_pc(KING.pc(stm));
67
68    // We always need legal king moves
69    let attacked_squares = king_danger_squares(kings, stm.flip(), position);
70
71    let king_sq = kings.bitscan();
72
73    let (checkers, pinned, pinners) = preprocessed_data.0;
74    let king_attacks_count = checkers.pop_count();
75
76    // capture_mask and push_mask represent squares our pieces are allowed to move to or capture,
77    // respectively. The difference between the two is only important for pawn EP captures
78    // Since push_mask is used to block a pin, we ignore push_mask when calculating king moves
79    let enemy = position.bb_side(stm.flip());
80    let empty_squares = position.bb_empty();
81
82    let mut capture_mask = enemy;
83    let king_capture_mask = enemy & !attacked_squares;
84    let mut push_mask = empty_squares;
85    let king_push_mask = empty_squares & !attacked_squares;
86
87    match king_attacks_count.cmp(&1) {
88        Ordering::Greater => {
89            // multiple attackers... only solutions are king moves
90            king_moves(position, king_capture_mask, king_push_mask, list);
91            return true;
92        }
93        Ordering::Less => (),
94        Ordering::Equal => {
95            // if ony one attacker, we can try attacking the attacker with
96            // our other pieces.
97            capture_mask = checkers;
98            let checker_sq = checkers.bitscan();
99            let checker = position.at(checker_sq);
100            debug_assert!(checker.is_some());
101            if checker.is_slider() {
102                // If the piece giving check is a slider, we can additionally attempt
103                // to block the sliding piece;
104                push_mask = squares_between(king_sq, checker_sq);
105            } else {
106                // If we are in check by a jumping piece (aka a knight) then
107                // there are no valid non-captures to avoid check
108                push_mask = EMPTY;
109            }
110        }
111    }
112
113    // generate moves for pinned and unpinned sliders
114    slider_moves(position, capture_mask, push_mask, pinned, king_sq, list);
115
116    // generate moves for non-pinned knights (pinned knights can't move)
117    knight_moves(position, capture_mask, push_mask, !pinned, list);
118
119    // generate moves for unpinned pawns
120    pawn_moves(position, capture_mask, push_mask, !pinned, list);
121
122    // generate moves for pinned pawns
123    // pinned pawn captures can only include pinners
124    pawn_pin_ray_moves(
125        position,
126        capture_mask & pinners,
127        push_mask,
128        king_sq,
129        pinned,
130        stm,
131        list,
132    );
133
134    if king_attacks_count == 0 {
135        // Not in check so can generate castles
136        // impossible for castles to be affected by pins
137        // so we don't need to consider pins here
138        castles(position, attacked_squares, list);
139    }
140
141    king_moves(position, king_capture_mask, king_push_mask, list);
142
143    king_attacks_count > 0
144}
145
146/// Adds 'loud' legal moves to the move list. Returns true if moving side is in check
147///
148/// Loud moves are defined as:
149/// * Captures
150/// * Check evasions
151#[allow(dead_code)]
152pub fn loud_legal_moves<L: MoveAdder>(position: &Position, list: &mut L) -> bool {
153    loud_legal_moves_with_preprocessing(position, list, movegen_preprocessing(position))
154}
155
156#[allow(dead_code)]
157pub fn loud_legal_moves_with_preprocessing<L: MoveAdder>(
158    position: &Position,
159    list: &mut L,
160    preprocessed_data: MoveGenPreprocessing,
161) -> bool {
162    let stm = position.state().stm;
163    let kings = position.bb_pc(KING.pc(stm));
164
165    // We always need legal king movess
166    let attacked_squares = king_danger_squares(kings, stm.flip(), position);
167
168    let king_sq = kings.bitscan();
169
170    let (checkers, pinned, pinners) = preprocessed_data.0;
171    let king_attacks_count = checkers.pop_count();
172
173    // capture_mask and push_mask represent squares our pieces are allowed to move to or capture,
174    // respectively. The difference between the two is only important for pawn EP captures
175    // Since push_mask is used to block a pin, we ignore push_mask when calculating king moves
176    let enemy = position.bb_side(stm.flip());
177    let empty_squares = position.bb_empty();
178
179    let mut capture_mask = enemy;
180    let king_capture_mask = enemy & !attacked_squares;
181    let king_push_mask = empty_squares & !attacked_squares;
182
183    match king_attacks_count.cmp(&1) {
184        Ordering::Greater => {
185            // multiple attackers... only solutions are king moves
186            king_moves(position, king_capture_mask, king_push_mask, list);
187            return true;
188        }
189        Ordering::Equal => {
190            // if ony one attacker, we can try attacking the attacker with
191            // our other pieces.
192            capture_mask = checkers;
193            let checker_sq = checkers.bitscan();
194            let checker = position.at(checker_sq);
195            debug_assert!(checker.is_some());
196            let push_mask = if checker.is_slider() {
197                // If the piece giving check is a slider, we can additionally attempt
198                // to block the sliding piece;
199                squares_between(king_sq, checker_sq)
200            } else {
201                // If we are in check by a jumping piece (aka a knight) then
202                // there are no valid non-captures to avoid check
203                EMPTY
204            };
205            // When in check evaluate moves in this order
206            // 1. king moves
207            // 2. knight moves
208            // 3. sliding piece moves
209            // 4. pawn moves
210            // No need for pawn pin ray moves since cannot move a pinned piece if in check
211            king_moves(position, king_capture_mask, king_push_mask, list);
212            knight_moves(position, capture_mask, push_mask, !pinned, list);
213            slider_moves(position, capture_mask, push_mask, pinned, king_sq, list);
214            pawn_moves(position, capture_mask, push_mask, !pinned, list);
215        }
216        Ordering::Less => {
217            let push_mask = EMPTY;
218
219            // When not in check evaluate moves in this order
220            // 1. pawn captures
221            // 2. knight captures
222            // 3. sliding piece captures
223            // 4. pawn pin-ray captures
224            pawn_captures(position, capture_mask, push_mask, !pinned, list);
225            pawn_pin_ray_captures(position, capture_mask & pinners, king_sq, pinned, stm, list);
226            knight_captures(position, capture_mask, !pinned, list);
227            slider_captures(position, capture_mask, pinned, king_sq, list);
228            king_captures(position, king_capture_mask, list);
229        }
230    }
231
232    king_attacks_count > 0
233}
234
235#[cfg(test)]
236mod test {
237    use super::*;
238    use crate::mv_list::MoveVec;
239    use crate::position::STARTING_POSITION_FEN;
240
241    macro_rules! test_legal_moves {
242        ($name:ident, $moves:expr, $fen:expr) => {
243            #[test]
244            fn $name() {
245                let mut list = MoveVec::new();
246                let position = &Position::from_fen($fen).unwrap();
247                legal_moves::<MoveVec>(position, &mut list);
248                if list.len() != $moves {
249                    println!("Found {} moves, expected {}", list.len(), $moves);
250                    println!("Moves: {}", list);
251                    println!("{}", position);
252                }
253                assert_eq!(list.len(), $moves);
254            }
255        };
256    }
257
258    macro_rules! test_loud_legal_moves {
259        ($name:ident, $moves:expr, $fen:expr) => {
260            #[test]
261            fn $name() {
262                let mut list = MoveVec::new();
263                let position = &Position::from_fen($fen).unwrap();
264                loud_legal_moves::<MoveVec>(position, &mut list);
265                if list.len() != $moves {
266                    println!("Found {} moves, expected {}", list.len(), $moves);
267                    println!("Moves: {}", list);
268                    println!("{}", position);
269                }
270                assert_eq!(list.len(), $moves);
271            }
272        };
273    }
274
275    test_legal_moves!(legal_moves_starting_position, 20, STARTING_POSITION_FEN);
276    test_legal_moves!(legal_moves_1, 8, "r6r/1b2k1bq/8/8/7B/8/8/R3K2R b QK - 3 2");
277    test_legal_moves!(legal_moves_2, 8, "8/8/8/2k5/2pP4/8/B7/4K3 b - d3 5 3");
278    test_legal_moves!(
279        legal_moves_3,
280        19,
281        "r1bqkbnr/pppppppp/n7/8/8/P7/1PPPPPPP/RNBQKBNR w QqKk - 2 2"
282    );
283    test_legal_moves!(
284        legal_moves_4,
285        5,
286        "r3k2r/p1pp1pb1/bn2Qnp1/2qPN3/1p2P3/2N5/PPPBBPPP/R3K2R b QqKk - 3 2"
287    );
288    test_legal_moves!(
289        legal_moves_5,
290        44,
291        "2kr3r/p1ppqpb1/bn2Qnp1/3PN3/1p2P3/2N5/PPPBBPPP/R3K2R b QK - 3 2"
292    );
293    test_legal_moves!(
294        legal_moves_6,
295        39,
296        "rnb2k1r/pp1Pbppp/2p5/q7/2B5/8/PPPQNnPP/RNB1K2R w QK - 3 9"
297    );
298
299    test_legal_moves!(legal_moves_7, 8, "5k2/8/8/q7/8/2Q5/8/4K3 w - -");
300
301    test_legal_moves!(legal_moves_8, 9, "2r5/3pk3/8/2P5/8/2K5/8/8 w - - 5 4");
302
303    test_legal_moves!(legal_moves_9, 3, "5k2/5pb1/5Q1B/8/8/8/8/4K3 b - - 1 1");
304
305    test_legal_moves!(legal_moves_10, 3, "4k3/8/5r2/8/8/8/4PPpP/5K2 w - - 0 1");
306
307    test_legal_moves!(
308        legal_moves_11,
309        5,
310        "4k3/3pq3/4Q3/1B2N3/1p2P3/2N5/PPPB1PP1/R3K2R b KQ - 0 1"
311    );
312
313    // en-passant capture along pin ray
314    test_legal_moves!(legal_moves_12, 7, "8/8/8/6k1/4Pp2/8/8/K1B5 b - e3 0 1");
315
316    // en-passant discovered check
317    test_legal_moves!(legal_moves_13, 7, "8/8/8/8/R3Ppk1/8/8/K7 b - e3 0 1");
318
319    test_legal_moves!(legal_moves_14, 4, "4k3/3NqQ2/8/8/8/8/4p3/4K3 b - - 0 1");
320
321    test_legal_moves!(legal_moves_15, 8, "8/8/5k2/8/4Pp2/8/8/4KR2 b - e3 0 1");
322
323    test_loud_legal_moves!(loud_legal_moves_1, 1, "8/8/8/6k1/4Pp2/8/8/K1B5 b - e3 0 1");
324
325    test_loud_legal_moves!(
326        loud_legal_moves_2,
327        3,
328        "rnbqkbnr/pppppppp/8/2N5/8/8/PPP1PPPP/R1BQKBNR w KQkq - 0 1"
329    );
330
331    test_loud_legal_moves!(loud_legal_moves_3, 0, "8/8/8/8/R3Ppk1/8/8/K7 b - e3 0 1");
332}