1use crate::bitboard::{BitBoard, EMPTY};
2use crate::board::Board;
3use crate::chess_move::ChessMove;
4use crate::magic::between;
5use crate::movegen::piece_type::*;
6use crate::piece::{NUM_PROMOTION_PIECES, PROMOTION_PIECES, Piece};
7use crate::square::Square;
8use arrayvec::ArrayVec;
9use nodrop::NoDrop;
10use std::iter::ExactSizeIterator;
11
12#[derive(Copy, Clone, PartialEq, PartialOrd)]
13pub struct SquareAndBitBoard {
14 square: Square,
15 bitboard: BitBoard,
16 promotion: bool,
17}
18
19impl SquareAndBitBoard {
20 pub fn new(sq: Square, bb: BitBoard, promotion: bool) -> SquareAndBitBoard {
21 SquareAndBitBoard { square: sq, bitboard: bb, promotion }
22 }
23}
24
25pub type MoveList = NoDrop<ArrayVec<SquareAndBitBoard, 18>>;
26
27pub struct MoveGen {
82 moves: MoveList,
83 promotion_index: usize,
84 iterator_mask: BitBoard,
85 index: usize,
86}
87
88impl MoveGen {
89 #[inline(always)]
90 fn enumerate_moves(board: &Board) -> MoveList {
91 let checkers = *board.checkers();
92 let mask = !board.color_combined(board.side_to_move());
93 let mut movelist = NoDrop::new(ArrayVec::<SquareAndBitBoard, 18>::new());
94
95 if checkers == EMPTY {
96 PawnType::legals::<NotInCheckType>(&mut movelist, board, mask);
97 KnightType::legals::<NotInCheckType>(&mut movelist, board, mask);
98 BishopType::legals::<NotInCheckType>(&mut movelist, board, mask);
99 RookType::legals::<NotInCheckType>(&mut movelist, board, mask);
100 QueenType::legals::<NotInCheckType>(&mut movelist, board, mask);
101 KingType::legals::<NotInCheckType>(&mut movelist, board, mask);
102 } else if checkers.popcnt() == 1 {
103 PawnType::legals::<InCheckType>(&mut movelist, board, mask);
104 KnightType::legals::<InCheckType>(&mut movelist, board, mask);
105 BishopType::legals::<InCheckType>(&mut movelist, board, mask);
106 RookType::legals::<InCheckType>(&mut movelist, board, mask);
107 QueenType::legals::<InCheckType>(&mut movelist, board, mask);
108 KingType::legals::<InCheckType>(&mut movelist, board, mask);
109 } else {
110 KingType::legals::<InCheckType>(&mut movelist, board, mask);
111 }
112
113 movelist
114 }
115
116 #[inline(always)]
118 pub fn new_legal(board: &Board) -> MoveGen {
119 MoveGen { moves: MoveGen::enumerate_moves(board), promotion_index: 0, iterator_mask: !EMPTY, index: 0 }
120 }
121
122 pub fn remove_mask(&mut self, mask: BitBoard) {
124 for x in 0..self.moves.len() {
125 self.moves[x].bitboard &= !mask;
126 }
127 }
128
129 pub fn remove_move(&mut self, chess_move: ChessMove) -> bool {
131 for x in 0..self.moves.len() {
132 if self.moves[x].square == chess_move.get_source() {
133 self.moves[x].bitboard &= !BitBoard::from_square(chess_move.get_dest());
134 return true;
135 }
136 }
137 false
138 }
139
140 pub fn set_iterator_mask(&mut self, mask: BitBoard) {
144 self.iterator_mask = mask;
145 self.index = 0;
146
147 let mut i = 0;
154 while i < self.moves.len() && self.moves[i].bitboard & self.iterator_mask != EMPTY {
155 i += 1;
156 }
157
158 for j in (i + 1)..self.moves.len() {
161 if self.moves[j].bitboard & self.iterator_mask != EMPTY {
162 let backup = self.moves[i];
163 self.moves[i] = self.moves[j];
164 self.moves[j] = backup;
165 i += 1;
166 }
167 }
168 }
169
170 pub fn legal_quick(board: &Board, chess_move: ChessMove) -> bool {
178 let piece = board.piece_on(chess_move.get_source()).unwrap();
179 match piece {
180 Piece::Rook => true,
181 Piece::Bishop => true,
182 Piece::Knight => true,
183 Piece::Queen => true,
184 Piece::Pawn => {
185 if chess_move.get_source().get_file() != chess_move.get_dest().get_file() && board.piece_on(chess_move.get_dest()).is_none() {
186 PawnType::legal_ep_move(board, chess_move.get_source(), chess_move.get_dest())
188 } else {
189 true
190 }
191 }
192 Piece::King => {
193 let bb = between(chess_move.get_source(), chess_move.get_dest());
194 if bb.popcnt() == 1 {
195 if !KingType::legal_king_move(board, bb.to_square()) {
197 false
198 } else {
199 KingType::legal_king_move(board, chess_move.get_dest())
200 }
201 } else {
202 KingType::legal_king_move(board, chess_move.get_dest())
203 }
204 }
205 }
206 }
207
208 pub fn movegen_perft_test(board: &Board, depth: usize) -> usize {
210 let iterable = MoveGen::new_legal(board);
211
212 let mut result: usize = 0;
213 if depth == 1 {
214 iterable.len()
215 } else {
216 for m in iterable {
217 let bresult = board.make_move_new(m);
218 result += MoveGen::movegen_perft_test(&bresult, depth - 1);
219 }
220 result
221 }
222 }
223
224 #[cfg(test)]
225 pub fn movegen_perft_test_piecewise(board: &Board, depth: usize) -> usize {
227 let mut iterable = MoveGen::new_legal(board);
228
229 let targets = board.color_combined(!board.side_to_move());
230 let mut result: usize = 0;
231
232 if depth == 1 {
233 iterable.set_iterator_mask(*targets);
234 result += iterable.len();
235 iterable.set_iterator_mask(!targets);
236 result += iterable.len();
237 result
238 } else {
239 iterable.set_iterator_mask(*targets);
240 for x in &mut iterable {
241 let mut bresult = std::mem::MaybeUninit::<Board>::uninit();
242 unsafe {
243 board.make_move(x, &mut *bresult.as_mut_ptr());
244 result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1);
245 }
246 }
247 iterable.set_iterator_mask(!EMPTY);
248 for x in &mut iterable {
249 let mut bresult = std::mem::MaybeUninit::<Board>::uninit();
250 unsafe {
251 board.make_move(x, &mut *bresult.as_mut_ptr());
252 result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1);
253 }
254 }
255 result
256 }
257 }
258}
259
260impl ExactSizeIterator for MoveGen {
261 fn len(&self) -> usize {
263 let mut result = 0;
264 for i in 0..self.moves.len() {
265 if self.moves[i].bitboard & self.iterator_mask == EMPTY {
266 break;
267 }
268 if self.moves[i].promotion {
269 result += ((self.moves[i].bitboard & self.iterator_mask).popcnt() as usize) * NUM_PROMOTION_PIECES;
270 } else {
271 result += (self.moves[i].bitboard & self.iterator_mask).popcnt() as usize;
272 }
273 }
274 result
275 }
276}
277
278impl Iterator for MoveGen {
279 type Item = ChessMove;
280
281 fn size_hint(&self) -> (usize, Option<usize>) {
283 let len = self.len();
284 (len, Some(len))
285 }
286
287 fn next(&mut self) -> Option<ChessMove> {
289 if self.index >= self.moves.len() || self.moves[self.index].bitboard & self.iterator_mask == EMPTY {
290 None
292 } else if self.moves[self.index].promotion {
293 let moves = &mut self.moves[self.index];
294
295 let dest = (moves.bitboard & self.iterator_mask).to_square();
296
297 let result = ChessMove::new(moves.square, dest, Some(PROMOTION_PIECES[self.promotion_index]));
299 self.promotion_index += 1;
300 if self.promotion_index >= NUM_PROMOTION_PIECES {
301 moves.bitboard ^= BitBoard::from_square(dest);
302 self.promotion_index = 0;
303 if moves.bitboard & self.iterator_mask == EMPTY {
304 self.index += 1;
305 }
306 }
307 Some(result)
308 } else {
309 let moves = &mut self.moves[self.index];
311 let dest = (moves.bitboard & self.iterator_mask).to_square();
312
313 moves.bitboard ^= BitBoard::from_square(dest);
314 if moves.bitboard & self.iterator_mask == EMPTY {
315 self.index += 1;
316 }
317 Some(ChessMove::new(moves.square, dest, None))
318 }
319 }
320}
321
322#[cfg(test)]
323use crate::board_builder::BoardBuilder;
324#[cfg(test)]
325use std::collections::HashSet;
326#[cfg(test)]
327use std::convert::TryInto;
328#[cfg(test)]
329use std::str::FromStr;
330
331#[cfg(test)]
332fn movegen_perft_test(fen: String, depth: usize, result: usize) {
333 let board: Board = BoardBuilder::from_str(&fen).unwrap().try_into().unwrap();
334
335 assert_eq!(MoveGen::movegen_perft_test(&board, depth), result);
336 assert_eq!(MoveGen::movegen_perft_test_piecewise(&board, depth), result);
337}
338
339#[test]
340fn movegen_perft_kiwipete() {
341 movegen_perft_test("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1".to_owned(), 5, 193690690);
342}
343
344#[test]
345fn movegen_perft_1() {
346 movegen_perft_test("8/5bk1/8/2Pp4/8/1K6/8/8 w - d6 0 1".to_owned(), 6, 824064);
347 }
349
350#[test]
351fn movegen_perft_2() {
352 movegen_perft_test("8/8/1k6/8/2pP4/8/5BK1/8 b - d3 0 1".to_owned(), 6, 824064);
353 }
355
356#[test]
357fn movegen_perft_3() {
358 movegen_perft_test("8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1".to_owned(), 6, 1440467);
359}
360
361#[test]
362fn movegen_perft_4() {
363 movegen_perft_test("8/5k2/8/2Pp4/2B5/1K6/8/8 w - d6 0 1".to_owned(), 6, 1440467);
364}
365
366#[test]
367fn movegen_perft_5() {
368 movegen_perft_test("5k2/8/8/8/8/8/8/4K2R w K - 0 1".to_owned(), 6, 661072);
369}
370
371#[test]
372fn movegen_perft_6() {
373 movegen_perft_test("4k2r/8/8/8/8/8/8/5K2 b k - 0 1".to_owned(), 6, 661072);
374}
375
376#[test]
377fn movegen_perft_7() {
378 movegen_perft_test("3k4/8/8/8/8/8/8/R3K3 w Q - 0 1".to_owned(), 6, 803711);
379}
380
381#[test]
382fn movegen_perft_8() {
383 movegen_perft_test("r3k3/8/8/8/8/8/8/3K4 b q - 0 1".to_owned(), 6, 803711);
384}
385
386#[test]
387fn movegen_perft_9() {
388 movegen_perft_test("r3k2r/1b4bq/8/8/8/8/7B/R3K2R w KQkq - 0 1".to_owned(), 4, 1274206);
389}
390
391#[test]
392fn movegen_perft_10() {
393 movegen_perft_test("r3k2r/7b/8/8/8/8/1B4BQ/R3K2R b KQkq - 0 1".to_owned(), 4, 1274206);
394}
395
396#[test]
397fn movegen_perft_11() {
398 movegen_perft_test("r3k2r/8/3Q4/8/8/5q2/8/R3K2R b KQkq - 0 1".to_owned(), 4, 1720476);
399}
400
401#[test]
402fn movegen_perft_12() {
403 movegen_perft_test("r3k2r/8/5Q2/8/8/3q4/8/R3K2R w KQkq - 0 1".to_owned(), 4, 1720476);
404}
405
406#[test]
407fn movegen_perft_13() {
408 movegen_perft_test("2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1".to_owned(), 6, 3821001);
409}
410
411#[test]
412fn movegen_perft_14() {
413 movegen_perft_test("3K4/8/8/8/8/8/4p3/2k2R2 b - - 0 1".to_owned(), 6, 3821001);
414}
415
416#[test]
417fn movegen_perft_15() {
418 movegen_perft_test("8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1".to_owned(), 5, 1004658);
419}
420
421#[test]
422fn movegen_perft_16() {
423 movegen_perft_test("5K2/8/1Q6/2N5/8/1p2k3/8/8 w - - 0 1".to_owned(), 5, 1004658);
424}
425
426#[test]
427fn movegen_perft_17() {
428 movegen_perft_test("4k3/1P6/8/8/8/8/K7/8 w - - 0 1".to_owned(), 6, 217342);
429}
430
431#[test]
432fn movegen_perft_18() {
433 movegen_perft_test("8/k7/8/8/8/8/1p6/4K3 b - - 0 1".to_owned(), 6, 217342);
434}
435
436#[test]
437fn movegen_perft_19() {
438 movegen_perft_test("8/P1k5/K7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 92683);
439}
440
441#[test]
442fn movegen_perft_20() {
443 movegen_perft_test("8/8/8/8/8/k7/p1K5/8 b - - 0 1".to_owned(), 6, 92683);
444}
445
446#[test]
447fn movegen_perft_21() {
448 movegen_perft_test("K1k5/8/P7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 2217);
449}
450
451#[test]
452fn movegen_perft_22() {
453 movegen_perft_test("8/8/8/8/8/p7/8/k1K5 b - - 0 1".to_owned(), 6, 2217);
454}
455
456#[test]
457fn movegen_perft_23() {
458 movegen_perft_test("8/k1P5/8/1K6/8/8/8/8 w - - 0 1".to_owned(), 7, 567584);
459}
460
461#[test]
462fn movegen_perft_24() {
463 movegen_perft_test("8/8/8/8/1k6/8/K1p5/8 b - - 0 1".to_owned(), 7, 567584);
464}
465
466#[test]
467fn movegen_perft_25() {
468 movegen_perft_test("8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1".to_owned(), 4, 23527);
469}
470
471#[test]
472fn movegen_perft_26() {
473 movegen_perft_test("8/5k2/8/5N2/5Q2/2K5/8/8 w - - 0 1".to_owned(), 4, 23527);
474}
475
476#[test]
477fn movegen_issue_15() {
478 let board = BoardBuilder::from_str("rnbqkbnr/ppp2pp1/4p3/3N4/3PpPp1/8/PPP3PP/R1B1KBNR b KQkq f3 0 1").unwrap().try_into().unwrap();
479 let _ = MoveGen::new_legal(&board);
480}
481
482#[cfg(test)]
483fn move_of(m: &str) -> ChessMove {
484 let promo = if m.len() > 4 {
485 Some(match m.as_bytes()[4] {
486 b'q' => Piece::Queen,
487 b'r' => Piece::Rook,
488 b'b' => Piece::Bishop,
489 b'n' => Piece::Knight,
490 _ => panic!("unrecognized uci move: {}", m),
491 })
492 } else {
493 None
494 };
495 ChessMove::new(Square::from_str(&m[..2]).unwrap(), Square::from_str(&m[2..4]).unwrap(), promo)
496}
497
498#[test]
499fn test_masked_move_gen() {
500 let board = Board::from_str("r1bqkb1r/pp3ppp/5n2/2ppn1N1/4pP2/1BN1P3/PPPP2PP/R1BQ1RK1 w kq - 0 9").unwrap();
501
502 let mut capture_moves = MoveGen::new_legal(&board);
503 let targets = *board.color_combined(!board.side_to_move());
504 capture_moves.set_iterator_mask(targets);
505
506 let expected = vec![move_of("f4e5"), move_of("b3d5"), move_of("g5e4"), move_of("g5f7"), move_of("g5h7"), move_of("c3e4"), move_of("c3d5")];
507
508 assert_eq!(capture_moves.collect::<HashSet<_>>(), expected.into_iter().collect());
509}