diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/board/engine.rs | 6 | ||||
-rw-r--r-- | src/board/mod.rs | 321 |
2 files changed, 155 insertions, 172 deletions
diff --git a/src/board/engine.rs b/src/board/engine.rs index 5168350..d58e529 100644 --- a/src/board/engine.rs +++ b/src/board/engine.rs @@ -99,7 +99,7 @@ impl Board { } let color = self.color(); - let moves = self.generate_pseudolegal_moves(color); + let moves = self.generate_pseudolegal_moves(); if print { println!("Running perft for depth {}. Color to move is {:?}\n{} moves available", depth, color, moves.len()); @@ -374,7 +374,7 @@ impl Board { return (self.quiscence(alpha, beta), principal_variation); } - let mut moves = self.generate_pseudolegal_moves(color); + let mut moves = self.generate_pseudolegal_moves(); moves = self.order_moves(moves, parent_killers.to_vec()); let mut should_pv_search = true; @@ -469,7 +469,7 @@ impl Board { pub fn quiscence(&mut self, mut alpha: f32, beta: f32) -> f32 { let color = self.color(); - let mut moves = self.generate_pseudolegal_moves(color); + let mut moves = self.generate_pseudolegal_moves(); moves = self.order_moves(moves, Vec::new()); if !self.is_king_in_check(color) { diff --git a/src/board/mod.rs b/src/board/mod.rs index 1533ea2..fcc476a 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -163,7 +163,7 @@ impl Board { Err(e) => return Err(e), }; - let moves = self.generate_pseudolegal_moves(self.color()); + let moves = self.generate_pseudolegal_moves(); let mov = match moves.iter().find(|m| m.source == source && m.target == target) { Some(m) => *m, @@ -254,187 +254,169 @@ impl Board { println!(" a b c d e f g h"); } - pub fn generate_pseudolegal_moves(&self, color: Color) -> Vec<Move> { - let mut moves = Vec::with_capacity(256); - let capture_targets = self.color_occupancy(color.flip()) ^ match color { - // Exclude opponent king because we can't capture it - Color::White => self.pieces[PieceType::KingBlack as usize], - Color::Black => self.pieces[PieceType::King as usize], - }; - let empty = self.empty(); - let player_pieces = self.pieces_by_color(color); - - for (piece_id, piece) in player_pieces.iter().enumerate() { - match PieceType::from(piece_id) { - PieceType::Pawn => { - for source in piece.serialize() { - let ep_bitboard = match self.ep_target { - Some(square) => { - let rank = square.rank(); - if (rank == 2 && color == Color::Black) || (rank == 5 && color == Color::White) { - square.to_bitboard() - } else { - 0 - } - } - None => 0, - }; - for target in (self.attacks.pawn[color as usize][source as usize] & capture_targets).serialize() { - moves.push(Move { source, target, kind: MoveKind::Capture }); - if target.rank() == 7 { - for promo_type in [PieceType::Bishop, PieceType::Knight, PieceType::Rook, PieceType::Queen] { - moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)}) - } - } else if target.rank() == 0 { - for promo_type in [PieceType::BishopBlack, PieceType::KnightBlack, PieceType::RookBlack, PieceType::QueenBlack] { - moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)}) - } - } - }; - for target in (self.attacks.pawn[color as usize][source as usize] & ep_bitboard).serialize() { - moves.push(Move { source, target, kind: MoveKind::EnPassant }); - } - for target in (self.attacks.pawn_pushes[color as usize][source as usize] & empty).serialize() { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - if target.rank() == 7 { - for promo_type in [PieceType::Bishop, PieceType::Knight, PieceType::Rook, PieceType::Queen] { - moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)}) - } - } else if target.rank() == 0 { - for promo_type in [PieceType::BishopBlack, PieceType::KnightBlack, PieceType::RookBlack, PieceType::QueenBlack] { - moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)}) - } - } - }; - } - - // 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 (*piece & 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 }); - }; - } + fn ep_bitboard(&self) -> Bitboard { + let color = self.color(); + match self.ep_target { + Some(square) => { + let rank = square.rank(); + if (rank == 2 && color == Color::Black) || (rank == 5 && color == Color::White) { + square.to_bitboard() + } else { + 0 } - PieceType::King => { - for source in piece.serialize() { - for target in (self.attacks.king[source as usize] & empty).serialize() { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - for target in (self.attacks.king[source as usize] & capture_targets).serialize() { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; + } + None => 0, + } + } - // Castling - let king_home_position = match color { - Color::White => Square::E1, - Color::Black => Square::E8, - }; - if *piece == king_home_position.to_bitboard() { - for rook_square in (player_pieces[PieceType::Rook as usize]).serialize() - .iter() - .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(), + pub fn generate_pseudolegal_moves(&self) -> Vec<Move> { + let mut result = Vec::new(); + result.append(&mut self.generate_moves_core(true)); + result.append(&mut self.generate_moves_core(false)); + result + } - ].iter().all(|square| empty & square.to_bitboard() > 0); + pub fn generate_moves_core(&self, tactical_only: bool) -> Vec<Move> { + let color = self.color(); + let player_pieces = self.pieces_by_color(color); + let mut moves = Vec::with_capacity(256); + let empty = self.empty(); - 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())); + let targets = if tactical_only { + self.color_occupancy(color.flip()) ^ match color { + // Exclude opponent king because we can't capture it + Color::White => self.pieces[PieceType::KingBlack as usize], + Color::Black => self.pieces[PieceType::King as usize], + } + } else { + empty + }; - 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(), + let kind = if tactical_only { + MoveKind::Capture + } else { + MoveKind::Quiet + }; - ].iter().all(|square| empty & square.to_bitboard() > 0); + for (piece_id, piece) in player_pieces.iter().enumerate() { + for source in piece.serialize() { + let piece_type = PieceType::from(piece_id); + let move_targets = match piece_type { + PieceType::Bishop => self.attacks.bishop(self.occupancy, source), + PieceType::Rook => self.attacks.rook(self.occupancy, source), + PieceType::Queen => self.attacks.queen(self.occupancy, source), + PieceType::Knight => self.attacks.knight[source as usize], + PieceType::King => self.attacks.king[source as usize], + PieceType::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"), + }; - 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())); + // Generalized attack/move pattern for all pieces + for target in (move_targets & targets).serialize() { + moves.push(Move { source, target, kind }); - 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, - }) - } - }, - _ => {}, - } - } + // Promotions + if piece_type == PieceType::Pawn { + if target.rank() == 7 { + for promo_type in [PieceType::Bishop, PieceType::Knight, PieceType::Rook, PieceType::Queen] { + moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)}) + } + } else if target.rank() == 0 { + for promo_type in [PieceType::BishopBlack, PieceType::KnightBlack, PieceType::RookBlack, PieceType::QueenBlack] { + moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)}) + } } } } - PieceType::Knight => { - for source in piece.serialize() { - for target in (self.attacks.knight[source as usize] & empty).serialize() { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - for target in (self.attacks.knight[source as usize] & capture_targets).serialize() { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; - } - } - PieceType::Bishop => { - for source in piece.serialize() { - for target in (self.attacks.bishop(self.occupancy, source) & empty).serialize() { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - for target in (self.attacks.bishop(self.occupancy, source) & capture_targets).serialize() { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; - } - } - PieceType::Rook => { - for source in piece.serialize() { - for target in (self.attacks.rook(self.occupancy, source) & empty).serialize() { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - } - for source in piece.serialize() { - for target in (self.attacks.rook(self.occupancy, source) & capture_targets).serialize() { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; + + // En Passant + if piece_type == PieceType::Pawn && tactical_only { + for target in (move_targets & self.ep_bitboard()).serialize() { + moves.push(Move { source, target, kind: MoveKind::EnPassant }); } } - PieceType::Queen => { - for source in piece.serialize() { - for target in (self.attacks.queen(self.occupancy, source) & empty).serialize() { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - for target in (self.attacks.queen(self.occupancy, source) & capture_targets).serialize() { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; + } + } + + 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[PieceType::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[PieceType::King as usize].bitscan() == king_home_position { + for rook_square in player_pieces[PieceType::Rook as usize] + .serialize() + .iter() + .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, + }) + } + }, + _ => {}, } } - _ => todo!("Incorrect piece type") } } - moves + return moves } /// Count pseudo-legal moves without actually generating them @@ -910,9 +892,10 @@ mod tests { #[test] fn generate_pseudolegal_moves_starting_position() { - let board = Board::new(); - let moves = board.generate_pseudolegal_moves(Color::White); - let black_moves = board.generate_pseudolegal_moves(Color::Black); + let mut board = Board::new(); + let moves = board.generate_pseudolegal_moves(); + board.ply += 1; + let black_moves = board.generate_pseudolegal_moves(); assert_eq!(moves.len(), 20); assert_eq!(black_moves.len(), 20); @@ -1015,7 +998,7 @@ mod tests { 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 }); - let moves = board.generate_pseudolegal_moves(board.color()); + 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); |