Skip to main content

timecat/chess/
move_generator.rs

1use super::*;
2
3trait PieceMoves {
4    fn is(piece: PieceType) -> bool;
5    fn into_piece() -> PieceType;
6    fn pseudo_legals(src: Square, color: Color, occupied: BitBoard, mask: BitBoard) -> BitBoard;
7    fn legals<T>(move_list: &mut MoveList, position: &ChessPosition, mask: BitBoard)
8    where
9        T: CheckMoves,
10    {
11        let occupied = position.occupied();
12        let color = position.turn();
13        let ksq = position.get_king_square(color);
14
15        let pieces = position.get_colored_piece_mask(Self::into_piece(), color);
16        let pinned = position.pinned();
17        let checkers = position.get_checkers();
18
19        let check_mask = if T::IN_CHECK {
20            unsafe { checkers.to_square_unchecked() }.between(ksq) ^ checkers
21        } else {
22            BitBoard::ALL
23        };
24
25        for src in pieces & !pinned {
26            let square_and_bitboard_array =
27                Self::pseudo_legals(src, color, occupied, mask) & check_mask;
28            if !square_and_bitboard_array.is_empty() {
29                unsafe {
30                    move_list.push_unchecked(SquareAndBitBoard::new(
31                        src,
32                        square_and_bitboard_array,
33                        false,
34                    ));
35                }
36            }
37        }
38
39        if !T::IN_CHECK {
40            for src in pieces & pinned {
41                let square_and_bitboard_array =
42                    Self::pseudo_legals(src, color, occupied, mask) & src.line(ksq);
43                if !square_and_bitboard_array.is_empty() {
44                    unsafe {
45                        move_list.push_unchecked(SquareAndBitBoard::new(
46                            src,
47                            square_and_bitboard_array,
48                            false,
49                        ));
50                    }
51                }
52            }
53        }
54    }
55}
56
57struct PawnMoves;
58struct BishopMoves;
59struct KnightMoves;
60struct RookMoves;
61struct QueenMoves;
62struct KingMoves;
63
64trait CheckMoves {
65    const IN_CHECK: bool;
66}
67
68struct InCheckMoves;
69struct NotInCheckMoves;
70
71impl CheckMoves for InCheckMoves {
72    const IN_CHECK: bool = true;
73}
74
75impl CheckMoves for NotInCheckMoves {
76    const IN_CHECK: bool = false;
77}
78
79impl PawnMoves {
80    fn legal_ep_move(position: &ChessPosition, source: Square, dest: Square) -> bool {
81        let Some(ep_square) = position.ep_square() else {
82            return false;
83        };
84
85        let occupied = position.occupied()
86            ^ ep_square.wrapping_backward(position.turn()).to_bitboard()
87            ^ source.to_bitboard()
88            ^ dest.to_bitboard();
89
90        let ksq = unsafe {
91            (position.get_colored_piece_mask(King, position.turn())).to_square_unchecked()
92        };
93
94        let rooks = (position.get_piece_mask(Rook) ^ position.get_piece_mask(Queen))
95            & position.opponent_occupied();
96
97        if !(ksq.get_rook_rays_bb() & rooks).is_empty()
98            && !(ksq.get_rook_moves(occupied) & rooks).is_empty()
99        {
100            return false;
101        }
102
103        let bishops = (position.get_piece_mask(Bishop) ^ position.get_piece_mask(Queen))
104            & position.opponent_occupied();
105
106        if !(ksq.get_bishop_rays_bb() & bishops).is_empty()
107            && !(ksq.get_bishop_moves(occupied) & bishops).is_empty()
108        {
109            return false;
110        }
111
112        true
113    }
114}
115
116impl PieceMoves for PawnMoves {
117    fn is(piece: PieceType) -> bool {
118        piece == Pawn
119    }
120
121    fn into_piece() -> PieceType {
122        Pawn
123    }
124
125    #[inline]
126    fn pseudo_legals(src: Square, color: Color, occupied: BitBoard, mask: BitBoard) -> BitBoard {
127        src.get_pawn_moves(color, occupied) & mask
128    }
129
130    #[inline]
131    fn legals<T>(move_list: &mut MoveList, position: &ChessPosition, mask: BitBoard)
132    where
133        T: CheckMoves,
134    {
135        let occupied = position.occupied();
136        let color = position.turn();
137        let ksq = position.get_king_square(color);
138
139        let pieces = position.get_colored_piece_mask(Self::into_piece(), color);
140        let pinned = position.pinned();
141        let checkers = position.get_checkers();
142
143        let check_mask = if T::IN_CHECK {
144            unsafe { checkers.to_square_unchecked() }.between(ksq) ^ checkers
145        } else {
146            BitBoard::ALL
147        };
148
149        for src in pieces & !pinned {
150            let square_and_bitboard_array =
151                Self::pseudo_legals(src, color, occupied, mask) & check_mask;
152            if !square_and_bitboard_array.is_empty() {
153                unsafe {
154                    move_list.push_unchecked(SquareAndBitBoard::new(
155                        src,
156                        square_and_bitboard_array,
157                        src.get_rank() == color.to_seventh_rank(),
158                    ));
159                }
160            }
161        }
162
163        if !T::IN_CHECK {
164            for src in pieces & pinned {
165                let square_and_bitboard_array =
166                    Self::pseudo_legals(src, color, occupied, mask) & ksq.line(src);
167                if !square_and_bitboard_array.is_empty() {
168                    unsafe {
169                        move_list.push_unchecked(SquareAndBitBoard::new(
170                            src,
171                            square_and_bitboard_array,
172                            src.get_rank() == color.to_seventh_rank(),
173                        ));
174                    }
175                }
176            }
177        }
178
179        if let Some(dest) = position.ep_square() {
180            let dest_rank = dest.get_rank();
181            let rank_bb = if dest_rank.to_int() > 3 {
182                dest_rank.wrapping_down().to_bitboard()
183            } else {
184                dest_rank.wrapping_up().to_bitboard()
185            };
186            let files_bb = dest.get_file().get_adjacent_files_bb();
187            for src in rank_bb & files_bb & pieces {
188                if Self::legal_ep_move(position, src, dest) {
189                    unsafe {
190                        move_list.push_unchecked(SquareAndBitBoard::new(
191                            src,
192                            dest.to_bitboard(),
193                            false,
194                        ));
195                    }
196                }
197            }
198        }
199    }
200}
201
202impl PieceMoves for BishopMoves {
203    fn is(piece: PieceType) -> bool {
204        piece == Bishop
205    }
206
207    fn into_piece() -> PieceType {
208        Bishop
209    }
210
211    #[inline]
212    fn pseudo_legals(src: Square, _: Color, occupied: BitBoard, mask: BitBoard) -> BitBoard {
213        src.get_bishop_moves(occupied) & mask
214    }
215}
216
217impl PieceMoves for KnightMoves {
218    fn is(piece: PieceType) -> bool {
219        piece == Knight
220    }
221
222    fn into_piece() -> PieceType {
223        Knight
224    }
225
226    #[inline]
227    fn pseudo_legals(src: Square, _: Color, _: BitBoard, mask: BitBoard) -> BitBoard {
228        src.get_knight_moves() & mask
229    }
230
231    #[inline]
232    fn legals<T>(move_list: &mut MoveList, position: &ChessPosition, mask: BitBoard)
233    where
234        T: CheckMoves,
235    {
236        let occupied = position.occupied();
237        let color = position.turn();
238        let ksq = position.get_king_square(color);
239
240        let pieces = position.get_colored_piece_mask(Self::into_piece(), color);
241        let pinned = position.pinned();
242        let checkers = position.get_checkers();
243
244        if T::IN_CHECK {
245            let check_mask = unsafe { checkers.to_square_unchecked() }.between(ksq) ^ checkers;
246
247            for src in pieces & !pinned {
248                let square_and_bitboard_array =
249                    Self::pseudo_legals(src, color, occupied, mask & check_mask);
250                if !square_and_bitboard_array.is_empty() {
251                    unsafe {
252                        move_list.push_unchecked(SquareAndBitBoard::new(
253                            src,
254                            square_and_bitboard_array,
255                            false,
256                        ));
257                    }
258                }
259            }
260        } else {
261            for src in pieces & !pinned {
262                let square_and_bitboard_array = Self::pseudo_legals(src, color, occupied, mask);
263                if !square_and_bitboard_array.is_empty() {
264                    unsafe {
265                        move_list.push_unchecked(SquareAndBitBoard::new(
266                            src,
267                            square_and_bitboard_array,
268                            false,
269                        ));
270                    }
271                }
272            }
273        };
274    }
275}
276
277impl PieceMoves for RookMoves {
278    fn is(piece: PieceType) -> bool {
279        piece == Rook
280    }
281
282    fn into_piece() -> PieceType {
283        Rook
284    }
285
286    #[inline]
287    fn pseudo_legals(src: Square, _: Color, occupied: BitBoard, mask: BitBoard) -> BitBoard {
288        src.get_rook_moves(occupied) & mask
289    }
290}
291
292impl PieceMoves for QueenMoves {
293    fn is(piece: PieceType) -> bool {
294        piece == Queen
295    }
296
297    fn into_piece() -> PieceType {
298        Queen
299    }
300
301    #[inline]
302    fn pseudo_legals(src: Square, _: Color, occupied: BitBoard, mask: BitBoard) -> BitBoard {
303        src.get_queen_moves(occupied) & mask
304    }
305}
306
307impl KingMoves {
308    #[inline]
309    fn legal_king_move(position: &ChessPosition, dest: Square) -> bool {
310        let occupied = position.occupied() ^ position.get_colored_piece_mask(King, position.turn())
311            | dest.to_bitboard();
312
313        let rooks = (position.get_piece_mask(Rook) ^ position.get_piece_mask(Queen))
314            & position.opponent_occupied();
315
316        let mut attackers = dest.get_rook_moves(occupied) & rooks;
317
318        let bishops = (position.get_piece_mask(Bishop) ^ position.get_piece_mask(Queen))
319            & position.opponent_occupied();
320
321        attackers |= dest.get_bishop_moves(occupied) & bishops;
322
323        let knight_rays = dest.get_knight_moves();
324
325        // Using ^ because knight square_and_bitboard_array bitboard do not collide with rook and bishop square_and_bitboard_array bitboard
326        attackers ^= knight_rays & position.get_colored_piece_mask(Knight, !position.turn());
327        attackers |=
328            dest.get_king_moves() & position.get_colored_piece_mask(King, !position.turn());
329        attackers |= dest.get_pawn_attacks(
330            position.turn(),
331            position.get_colored_piece_mask(Pawn, !position.turn()),
332        );
333
334        attackers.is_empty()
335    }
336}
337
338impl PieceMoves for KingMoves {
339    fn is(piece: PieceType) -> bool {
340        piece == King
341    }
342
343    fn into_piece() -> PieceType {
344        King
345    }
346
347    #[inline]
348    fn pseudo_legals(src: Square, _: Color, _: BitBoard, mask: BitBoard) -> BitBoard {
349        src.get_king_moves() & mask
350    }
351
352    #[inline]
353    fn legals<T>(move_list: &mut MoveList, position: &ChessPosition, mask: BitBoard)
354    where
355        T: CheckMoves,
356    {
357        let occupied = position.occupied();
358        let color = position.turn();
359        let ksq = position.get_king_square(color);
360
361        let mut square_and_bitboard_array = Self::pseudo_legals(ksq, color, occupied, mask);
362
363        let copy = square_and_bitboard_array;
364        for dest in copy {
365            if !Self::legal_king_move(position, dest) {
366                square_and_bitboard_array ^= dest.to_bitboard();
367            }
368        }
369
370        // If we are not in check, we may be able to castle.
371        // We can do so iff:
372        //  * the `ChessPosition` structure says we can.
373        //  * the squares between my king and my rook are empty.
374        //  * no enemy pieces are attacking the squares between the king, and the kings
375        //    destination square.
376        //  ** This is determined by going to the left or right, and calling
377        //     'legal_king_move' for that square.
378        if !T::IN_CHECK {
379            if position.my_castle_rights().has_kingside()
380                && (occupied & position.my_castle_rights().kingside_squares(color)).is_empty()
381            {
382                let middle = ksq.wrapping_right();
383                let right = middle.wrapping_right();
384                if Self::legal_king_move(position, middle) && Self::legal_king_move(position, right)
385                {
386                    square_and_bitboard_array ^= right.to_bitboard();
387                }
388            }
389
390            if position.my_castle_rights().has_queenside()
391                && (occupied & position.my_castle_rights().queenside_squares(color)).is_empty()
392            {
393                let middle = ksq.wrapping_left();
394                let left = middle.wrapping_left();
395                if Self::legal_king_move(position, middle) && Self::legal_king_move(position, left)
396                {
397                    square_and_bitboard_array ^= left.to_bitboard();
398                }
399            }
400        }
401        if !square_and_bitboard_array.is_empty() {
402            unsafe {
403                move_list.push_unchecked(SquareAndBitBoard::new(
404                    ksq,
405                    square_and_bitboard_array,
406                    false,
407                ));
408            }
409        }
410    }
411}
412
413#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
414#[derive(Clone, PartialEq, Eq, Debug)]
415struct SquareAndBitBoard {
416    square: Square,
417    bitboard: BitBoard,
418    promotion: bool,
419}
420
421impl SquareAndBitBoard {
422    fn new(square: Square, bb: BitBoard, promotion: bool) -> Self {
423        Self {
424            square,
425            bitboard: bb,
426            promotion,
427        }
428    }
429}
430
431type MoveList = ArrayVec<SquareAndBitBoard, 18>;
432
433#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
434#[derive(Clone, Debug)]
435pub struct MoveGenerator {
436    square_and_bitboard_array: MoveList,
437    promotion_index: usize,
438    from_bitboard_iterator_mask: BitBoard,
439    to_bitboard_iterator_mask: BitBoard,
440}
441
442impl MoveGenerator {
443    #[inline]
444    fn enumerate_moves(position: &ChessPosition) -> MoveList {
445        let checkers = position.get_checkers();
446        let mask = !position.self_occupied();
447        let mut move_list = ArrayVec::new();
448
449        if checkers.is_empty() {
450            PawnMoves::legals::<NotInCheckMoves>(&mut move_list, position, mask);
451            KnightMoves::legals::<NotInCheckMoves>(&mut move_list, position, mask);
452            BishopMoves::legals::<NotInCheckMoves>(&mut move_list, position, mask);
453            RookMoves::legals::<NotInCheckMoves>(&mut move_list, position, mask);
454            QueenMoves::legals::<NotInCheckMoves>(&mut move_list, position, mask);
455            KingMoves::legals::<NotInCheckMoves>(&mut move_list, position, mask);
456        } else if checkers.popcnt() == 1 {
457            PawnMoves::legals::<InCheckMoves>(&mut move_list, position, mask);
458            KnightMoves::legals::<InCheckMoves>(&mut move_list, position, mask);
459            BishopMoves::legals::<InCheckMoves>(&mut move_list, position, mask);
460            RookMoves::legals::<InCheckMoves>(&mut move_list, position, mask);
461            QueenMoves::legals::<InCheckMoves>(&mut move_list, position, mask);
462            KingMoves::legals::<InCheckMoves>(&mut move_list, position, mask);
463        } else {
464            KingMoves::legals::<InCheckMoves>(&mut move_list, position, mask);
465        }
466
467        move_list
468    }
469
470    #[inline]
471    pub fn get_single_legal_move(
472        position: &ChessPosition,
473        from_bitboard: BitBoard,
474        to_bitboard: BitBoard,
475    ) -> Option<Move> {
476        let checkers = position.get_checkers();
477        let mask = !position.self_occupied() & to_bitboard;
478        let mut move_list = ArrayVec::new();
479
480        let legal_functions = if checkers.is_empty() {
481            [
482                PawnMoves::legals::<NotInCheckMoves>,
483                KnightMoves::legals::<NotInCheckMoves>,
484                BishopMoves::legals::<NotInCheckMoves>,
485                RookMoves::legals::<NotInCheckMoves>,
486                QueenMoves::legals::<NotInCheckMoves>,
487                KingMoves::legals::<NotInCheckMoves>,
488            ]
489        } else if checkers.popcnt() == 1 {
490            [
491                PawnMoves::legals::<InCheckMoves>,
492                KnightMoves::legals::<InCheckMoves>,
493                BishopMoves::legals::<InCheckMoves>,
494                RookMoves::legals::<InCheckMoves>,
495                QueenMoves::legals::<InCheckMoves>,
496                KingMoves::legals::<InCheckMoves>,
497            ]
498        } else {
499            KingMoves::legals::<InCheckMoves>(&mut move_list, position, mask);
500            return move_list.into_iter().find_map(|square_and_bitboard| {
501                (from_bitboard.contains(square_and_bitboard.square)
502                    && !(square_and_bitboard.bitboard & to_bitboard).is_empty())
503                .then(|| unsafe {
504                    Move::new_unchecked(
505                        square_and_bitboard.square,
506                        square_and_bitboard.bitboard.to_square_unchecked(),
507                        square_and_bitboard.promotion.then_some(Queen),
508                    )
509                })
510            });
511        };
512
513        let mut start_index = 0;
514
515        for function in legal_functions {
516            function(&mut move_list, position, mask);
517            for square_and_bitboard in &move_list[start_index..] {
518                if from_bitboard.contains(square_and_bitboard.square)
519                    && !(square_and_bitboard.bitboard & to_bitboard).is_empty()
520                {
521                    return Some(unsafe {
522                        Move::new_unchecked(
523                            square_and_bitboard.square,
524                            square_and_bitboard.bitboard.to_square_unchecked(),
525                            square_and_bitboard.promotion.then_some(Queen),
526                        )
527                    });
528                }
529            }
530            start_index = move_list.len();
531        }
532
533        None
534    }
535
536    #[inline]
537    pub fn has_legal_moves(
538        position: &ChessPosition,
539        from_bitboard: BitBoard,
540        to_bitboard: BitBoard,
541    ) -> bool {
542        Self::get_single_legal_move(position, from_bitboard, to_bitboard).is_some()
543    }
544
545    #[inline]
546    pub fn new_legal(position: &ChessPosition) -> Self {
547        Self {
548            square_and_bitboard_array: Self::enumerate_moves(position),
549            promotion_index: 0,
550            from_bitboard_iterator_mask: BitBoard::ALL,
551            to_bitboard_iterator_mask: BitBoard::ALL,
552        }
553    }
554
555    /// The iterator portion of this struct relies on the invariant that
556    /// the bitboards at the beginning of the square_and_bitboard_array[] array are the only
557    /// ones used.  As a result, we must partition the list such that the
558    /// assumption is true.
559    pub fn reorganize_square_and_bitboard_array(&mut self) {
560        // first, find the first non-used square_and_bitboard_array index, and store that in i
561        let mut i = 0;
562        while i < self.square_and_bitboard_array.len()
563            && !(get_item_unchecked!(self.square_and_bitboard_array, i).bitboard
564                & self.to_bitboard_iterator_mask)
565                .is_empty()
566        {
567            i += 1;
568        }
569
570        // next, find each element past i where the square_and_bitboard_array are used, and store
571        // that in i.  Then, increment i to point to a new unused slot.
572        for j in (i + 1)..self.square_and_bitboard_array.len() {
573            if !(get_item_unchecked!(self.square_and_bitboard_array, j).bitboard
574                & self.to_bitboard_iterator_mask)
575                .is_empty()
576            {
577                // unsafe { self.square_and_bitboard_array.swap_unchecked(i, j) };
578                self.square_and_bitboard_array.swap(i, j);
579                i += 1;
580            }
581        }
582    }
583
584    pub fn remove_mask(&mut self, mask: BitBoard) {
585        self.square_and_bitboard_array
586            .iter_mut()
587            .for_each(|square_and_bitboard| square_and_bitboard.bitboard &= !mask);
588        self.reorganize_square_and_bitboard_array();
589    }
590
591    pub fn remove_move(&mut self, move_: Move) -> bool {
592        let mut square_removed = false;
593        for square_and_bitboard in self.square_and_bitboard_array.iter_mut() {
594            if square_and_bitboard.square == move_.get_source() {
595                square_and_bitboard.bitboard &= !move_.get_dest().to_bitboard();
596                square_removed = true;
597            }
598        }
599        self.reorganize_square_and_bitboard_array();
600        square_removed
601    }
602
603    pub fn get_from_bitboard_iterator_mask(&self) -> BitBoard {
604        self.from_bitboard_iterator_mask
605    }
606
607    pub fn set_from_bitboard_iterator_mask(&mut self, mask: BitBoard) {
608        self.from_bitboard_iterator_mask = mask;
609        self.reorganize_square_and_bitboard_array();
610    }
611
612    pub fn get_to_bitboard_iterator_mask(&self) -> BitBoard {
613        self.to_bitboard_iterator_mask
614    }
615
616    pub fn set_to_bitboard_iterator_mask(&mut self, mask: BitBoard) {
617        self.to_bitboard_iterator_mask = mask;
618        self.reorganize_square_and_bitboard_array();
619    }
620
621    pub fn set_iterator_masks(&mut self, from_bitboard: BitBoard, to_bitboard: BitBoard) {
622        self.from_bitboard_iterator_mask = from_bitboard;
623        self.to_bitboard_iterator_mask = to_bitboard;
624        self.reorganize_square_and_bitboard_array();
625    }
626
627    pub fn perft_test(position: &ChessPosition, depth: usize) -> usize {
628        let iterable = position.generate_legal_moves();
629
630        let mut result: usize = 0;
631        if depth == 1 {
632            iterable.len()
633        } else {
634            for m in iterable {
635                let board_result = position.make_move_new(m);
636                result += Self::perft_test(&board_result, depth - 1);
637            }
638            result
639        }
640    }
641
642    pub fn perft_test_piecewise(position: &ChessPosition, depth: usize) -> usize {
643        let mut iterable = position.generate_legal_moves();
644
645        let targets = position.opponent_occupied();
646        let mut result: usize = 0;
647
648        for &piece_mask in position.get_all_piece_masks() {
649            iterable.set_from_bitboard_iterator_mask(piece_mask);
650            iterable.set_to_bitboard_iterator_mask(targets);
651            if depth == 1 {
652                result += iterable.len();
653                iterable.set_to_bitboard_iterator_mask(!targets);
654                result += iterable.len();
655            } else {
656                for x in &iterable {
657                    result += Self::perft_test(&position.make_move_new(x), depth - 1);
658                }
659                iterable.set_to_bitboard_iterator_mask(!targets);
660                for x in &iterable {
661                    result += Self::perft_test(&position.make_move_new(x), depth - 1);
662                }
663            }
664        }
665        result
666    }
667
668    #[inline]
669    pub fn iter(&self) -> impl Iterator<Item = Move> {
670        let iterator = self
671            .square_and_bitboard_array
672            .iter()
673            .take_while(|square_and_bitboard| {
674                !(square_and_bitboard.bitboard & self.to_bitboard_iterator_mask).is_empty()
675            })
676            .filter(|square_and_bitboard| {
677                self.from_bitboard_iterator_mask
678                    .contains(square_and_bitboard.square)
679            })
680            .flat_map(move |square_and_bitboard| {
681                let promotion_pieces: &[Option<PieceType>] = if square_and_bitboard.promotion {
682                    const { &[Some(Queen), Some(Knight), Some(Rook), Some(Bishop)] }
683                } else {
684                    const { &[None] }
685                };
686                promotion_pieces.iter().flat_map(move |&promotion| {
687                    (square_and_bitboard.bitboard & self.to_bitboard_iterator_mask).map(
688                        move |dest| unsafe {
689                            Move::new_unchecked(square_and_bitboard.square, dest, promotion)
690                        },
691                    )
692                })
693            });
694
695        MoveGeneratorReferencedIterator {
696            move_generator: self,
697            iterator,
698        }
699    }
700
701    pub fn len(&self) -> usize {
702        let mut result = 0;
703        for square_and_bitboard in &self.square_and_bitboard_array {
704            let bitboard_and_to_bitboard_iterator_mask =
705                square_and_bitboard.bitboard & self.to_bitboard_iterator_mask;
706            if !self
707                .from_bitboard_iterator_mask
708                .contains(square_and_bitboard.square)
709                || bitboard_and_to_bitboard_iterator_mask.is_empty()
710            {
711                break;
712            }
713            if square_and_bitboard.promotion {
714                result += (bitboard_and_to_bitboard_iterator_mask.popcnt() as usize)
715                    * NUM_PROMOTION_PIECES;
716            } else {
717                result += bitboard_and_to_bitboard_iterator_mask.popcnt() as usize;
718            }
719        }
720        result
721    }
722
723    #[inline]
724    pub fn is_empty(&self) -> bool {
725        self.len() == 0
726    }
727
728    #[inline]
729    pub fn contains(&self, move_: &Move) -> bool {
730        self.square_and_bitboard_array
731            .iter()
732            .any(|square_and_bitboard| {
733                square_and_bitboard.square == move_.get_source()
734                    && square_and_bitboard.bitboard.contains(move_.get_dest())
735                    && if square_and_bitboard.promotion {
736                        // TODO: How about Binary Search here?
737                        const { [Some(Knight), Some(Bishop), Some(Rook), Some(Queen)] }
738                            .contains(&move_.get_promotion())
739                    } else {
740                        move_.get_promotion().is_none()
741                    }
742            })
743    }
744
745    #[inline]
746    pub fn is_legal(position: &ChessPosition, move_: &Move) -> bool {
747        // TODO: Scope of improvement
748        let Some(piece_type) = position.get_piece_type_at(move_.get_source()) else {
749            return false;
750        };
751        let possibly_legal = match piece_type {
752            Pawn => {
753                if move_.get_source().get_file() != move_.get_dest().get_file()
754                    && position.get_piece_type_at(move_.get_dest()).is_none()
755                {
756                    // en-passant
757                    PawnMoves::legal_ep_move(position, move_.get_source(), move_.get_dest())
758                } else {
759                    true
760                }
761            }
762            King => {
763                let bb = move_.get_source().between(move_.get_dest());
764                if bb.popcnt() == 1 {
765                    // castles
766                    if !KingMoves::legal_king_move(position, unsafe { bb.to_square_unchecked() }) {
767                        false
768                    } else {
769                        KingMoves::legal_king_move(position, move_.get_dest())
770                    }
771                } else {
772                    KingMoves::legal_king_move(position, move_.get_dest())
773                }
774            }
775            _ => true,
776        };
777        if !possibly_legal {
778            return false;
779        }
780        position.generate_legal_moves().contains(move_)
781    }
782}
783
784pub struct MoveGeneratorReferencedIterator<'a, T: Iterator<Item = Move>> {
785    move_generator: &'a MoveGenerator,
786    iterator: T,
787}
788
789impl<T: Iterator<Item = Move>> ExactSizeIterator for MoveGeneratorReferencedIterator<'_, T> {
790    #[inline]
791    fn len(&self) -> usize {
792        self.move_generator.len()
793    }
794}
795
796impl<T: Iterator<Item = Move>> Iterator for MoveGeneratorReferencedIterator<'_, T> {
797    type Item = Move;
798
799    fn size_hint(&self) -> (usize, Option<usize>) {
800        let len = self.len();
801        (len, Some(len))
802    }
803
804    fn next(&mut self) -> Option<Move> {
805        self.iterator.next()
806    }
807}
808
809impl<'a> IntoIterator for &'a MoveGenerator {
810    type Item = Move;
811    // TODO: Avoid Heap Allocation.
812    type IntoIter = Box<dyn Iterator<Item = Move> + 'a>;
813
814    fn into_iter(self) -> Self::IntoIter {
815        Box::new(self.iter())
816    }
817}
818
819pub struct MoveGeneratorIterator {
820    move_generator: MoveGenerator,
821    index: usize,
822    last_index: usize,
823}
824
825impl Iterator for MoveGeneratorIterator {
826    type Item = Move;
827
828    fn size_hint(&self) -> (usize, Option<usize>) {
829        let len = self.move_generator.len();
830        (len, Some(len))
831    }
832
833    fn next(&mut self) -> Option<Move> {
834        // TODO: Check Logic
835        let square_and_bitboard_array_len = self.move_generator.square_and_bitboard_array.len();
836        if self.index >= square_and_bitboard_array_len {
837            return None;
838        }
839        if self.index != self.last_index {
840            while !self.move_generator.from_bitboard_iterator_mask.contains(
841                get_item_unchecked_mut!(self.move_generator.square_and_bitboard_array, self.index)
842                    .square,
843            ) {
844                self.index += 1;
845                if self.index >= square_and_bitboard_array_len {
846                    return None;
847                }
848            }
849            self.last_index = self.index;
850        }
851        let square_and_bitboard =
852            get_item_unchecked_mut!(self.move_generator.square_and_bitboard_array, self.index);
853
854        if !self
855            .move_generator
856            .from_bitboard_iterator_mask
857            .contains(square_and_bitboard.square)
858            || (square_and_bitboard.bitboard & self.move_generator.to_bitboard_iterator_mask)
859                .is_empty()
860        {
861            // are we done?
862            None
863        } else if square_and_bitboard.promotion {
864            let dest = unsafe {
865                (square_and_bitboard.bitboard & self.move_generator.to_bitboard_iterator_mask)
866                    .to_square_unchecked()
867            };
868
869            // deal with potential promotions for this pawn
870            let result = unsafe {
871                Move::new_unchecked(
872                    square_and_bitboard.square,
873                    dest,
874                    Some(*get_item_unchecked!(
875                        @internal
876                        PROMOTION_PIECES,
877                        self.move_generator.promotion_index
878                    )),
879                )
880            };
881            self.move_generator.promotion_index += 1;
882            if self.move_generator.promotion_index >= NUM_PROMOTION_PIECES {
883                square_and_bitboard.bitboard ^= dest.to_bitboard();
884                self.move_generator.promotion_index = 0;
885                if (square_and_bitboard.bitboard & self.move_generator.to_bitboard_iterator_mask)
886                    .is_empty()
887                {
888                    self.index += 1;
889                }
890            }
891            Some(result)
892        } else {
893            // not a promotion move, so its a 'normal' move as far as this function is concerned
894            let dest = unsafe {
895                (square_and_bitboard.bitboard & self.move_generator.to_bitboard_iterator_mask)
896                    .to_square_unchecked()
897            };
898
899            square_and_bitboard.bitboard ^= dest.to_bitboard();
900            if (square_and_bitboard.bitboard & self.move_generator.to_bitboard_iterator_mask)
901                .is_empty()
902            {
903                self.index += 1;
904            }
905            Some(unsafe { Move::new_unchecked(square_and_bitboard.square, dest, None) })
906        }
907    }
908}
909
910impl ExactSizeIterator for MoveGeneratorIterator {
911    #[inline]
912    fn len(&self) -> usize {
913        self.move_generator.len()
914    }
915}
916
917impl IntoIterator for MoveGenerator {
918    type Item = Move;
919    type IntoIter = MoveGeneratorIterator;
920
921    fn into_iter(self) -> Self::IntoIter {
922        MoveGeneratorIterator {
923            move_generator: self,
924            index: 0,
925            last_index: usize::MAX,
926        }
927    }
928}