use crate::{moves::{Move, MoveKind}, board::{color::Color, piece::Piece, CastlingSide}, bitboard::BitboardFns, square::Square}; use super::Board; impl Board { pub fn generate_pseudolegal_moves(&self) -> Vec { let mut result = Vec::new(); result.append(&mut self.generate_moves_core(true)); result.append(&mut self.generate_moves_core(false)); result } fn generate_moves_core(&self, tactical_only: bool) -> Vec { let color = self.color(); let player_pieces = self.pieces_by_color(color); let mut moves = Vec::with_capacity(256); let empty = self.empty(); let targets = if tactical_only { self.color_occupancy(color.flip()) ^ match color { // Exclude opponent king because we can't capture it Color::White => self.piece_sets[Piece::KingBlack as usize], Color::Black => self.piece_sets[Piece::King as usize], } } else { empty }; let kind = if tactical_only { MoveKind::Capture } else { MoveKind::Quiet }; for (piece_id, piece) in player_pieces.iter().enumerate() { for source in piece.serialize() { let piece_type = Piece::from(piece_id); let move_targets = match piece_type { Piece::Bishop => self.attacks.bishop(self.occupancy, source), Piece::Rook => self.attacks.rook(self.occupancy, source), Piece::Queen => self.attacks.queen(self.occupancy, source), Piece::Knight => self.attacks.knight[source as usize], Piece::King => self.attacks.king[source as usize], Piece::Pawn => if tactical_only { self.attacks.pawn[color as usize][source as usize] } else { self.attacks.pawn_pushes[color as usize][source as usize] }, _ => panic!("Incorrect piece type"), }; // Generalized attack/move pattern for all pieces for target in (move_targets & targets).serialize() { moves.push(Move { source, target, kind }); // Promotions if piece_type == Piece::Pawn { if target.rank() == 7 { for promo_type in [Piece::Bishop, Piece::Knight, Piece::Rook, Piece::Queen] { moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)}) } } else if target.rank() == 0 { for promo_type in [Piece::BishopBlack, Piece::KnightBlack, Piece::RookBlack, Piece::QueenBlack] { moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)}) } } } } // En Passant if piece_type == Piece::Pawn && tactical_only { for target in (move_targets & self.ep_bitboard()).serialize() { moves.push(Move { source, target, kind: MoveKind::EnPassant }); } } } } if !tactical_only { // Double Pushes // Make sure no blocking piece is standing in front of the pawns // that are potential double-push sources let able_to_double_push_mask = match color { Color::White => empty >> 8, Color::Black => empty << 8, }; for source in (player_pieces[Piece::Pawn as usize] & able_to_double_push_mask).serialize() { for target in (self.attacks.pawn_double_pushes[color as usize][source as usize] & empty).serialize() { moves.push(Move { source, target, kind: MoveKind::DoublePush }); }; } // Castling let king_home_position = match color { Color::White => Square::E1, Color::Black => Square::E8, }; if player_pieces[Piece::King as usize].bitscan() == king_home_position { for rook_square in player_pieces[Piece::Rook as usize] .serialize() .filter(|rook_square| rook_square.rank() == king_home_position.rank()) { match rook_square.file() { 0 => { let all_empty = [ king_home_position.west_one(), king_home_position.west_one().west_one(), king_home_position.west_one().west_one().west_one(), ].iter().all(|square| empty & square.to_bitboard() > 0); let any_checks = [ king_home_position, king_home_position.west_one(), king_home_position.west_one().west_one(), ].iter().any(|square| self.is_square_attacked(*square, color.flip())); if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::Queen as usize] { moves.push(Move { source: king_home_position, target: king_home_position.west_one().west_one(), kind: MoveKind::Castle, }) } }, 7 => { let all_empty = [ king_home_position.east_one(), king_home_position.east_one().east_one(), ].iter().all(|square| empty & square.to_bitboard() > 0); let any_checks = [ king_home_position, king_home_position.east_one(), king_home_position.east_one().east_one(), ].iter().any(|square| self.is_square_attacked(*square, color.flip())); if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::King as usize] { moves.push(Move { source: king_home_position, target: king_home_position.east_one().east_one(), kind: MoveKind::Castle, }) } }, _ => {}, } } } } moves } } #[cfg(test)] mod tests { use crate::board::{Board, io::IO}; use super::*; #[test] fn generate_pseudolegal_moves_starting_position() { let mut board = Board::new(); let moves = board.generate_pseudolegal_moves(); let black_moves = board.generate_pseudolegal_moves(); assert_eq!(moves.len(), 20); assert_eq!(black_moves.len(), 20); for mov in moves { mov.print(); } } #[test] fn moved_king_castle() { let fen = String::from("4k2r/ppp1n3/8/4R1Pp/5P2/q1P5/P1P1BP2/1K1R4 b - - 2 22"); let mut board = Board::from_FEN(fen); // Shuffle kings around, returning to the same position board.make_move(Move { source: Square::E8, target: Square::F8, kind: MoveKind::Quiet }); board.make_move(Move { source: Square::B1, target: Square::A1, kind: MoveKind::Quiet }); board.make_move(Move { source: Square::F8, target: Square::E8, kind: MoveKind::Quiet }); board.make_move(Move { source: Square::A1, target: Square::B1, kind: MoveKind::Quiet }); board.print(); let moves = board.generate_pseudolegal_moves(); let castle = moves.iter().find(|m| **m == Move { source: Square::E8, target: Square::G8, kind: MoveKind::Castle }); println!("{:?}", board.castling_rights); assert!(castle.is_none(), "Castle should not be allowed after king has moved"); } }