diff options
Diffstat (limited to 'src/board/engine.rs')
-rw-r--r-- | src/board/engine.rs | 310 |
1 files changed, 297 insertions, 13 deletions
diff --git a/src/board/engine.rs b/src/board/engine.rs index 5a2b2bf..2dc93eb 100644 --- a/src/board/engine.rs +++ b/src/board/engine.rs @@ -155,19 +155,303 @@ impl Board { result } + 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 + } + } + None => 0, + } + } + + 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 + } + + 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 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() + .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, + }) + } + }, + _ => {}, + } + } + } + } + return moves + } + + /// Count pseudo-legal moves without actually generating them + /// Also exclude all moves that put a piece under attack of a pawn - so called safe mobility + pub fn mobility(&self, color: Color) -> f32 { + let mut mobility = 0.; + let opponent_occupancy = self.color_occupancy(color.flip()); + let player_pieces = self.pieces_by_color(color); + + let opponent_pawns = match color { + Color::Black => self.piece_sets[Piece::Pawn as usize], + Color::White => self.piece_sets[Piece::PawnBlack as usize], + }; + + let pawn_attacked_squares = opponent_pawns.serialize().iter().fold(0u64, |acc, square| { + acc | self.attacks.pawn[color.flip() as usize][*square as usize] + }); + + // Exclude squares controlled by enemy pawns from mobility + let empty = self.empty() & !pawn_attacked_squares; + + for (piece_type, piece) in player_pieces.iter().enumerate() { + match Piece::from(piece_type) { + Piece::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, + }; + mobility += (self.attacks.pawn[color as usize][source as usize] & (opponent_occupancy | ep_bitboard)).pop_count() as f32; + mobility += (self.attacks.pawn_pushes[color as usize][source as usize] & empty).pop_count() as f32; + } + 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() { + mobility += (self.attacks.pawn_double_pushes[color as usize][source as usize] & empty).pop_count() as f32; + } + } + Piece::King => { + for source in piece.serialize() { + mobility += (self.attacks.king[source as usize] & (empty | opponent_occupancy)).pop_count() as f32; + + // 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[Piece::Rook as usize] + .serialize() + .iter() + .filter(|rook_square| rook_square.rank() == king_home_position.rank()) + { + match rook_square.file() { + 0 => { + let castle_line = [ + king_home_position.west_one(), + king_home_position.west_one().west_one(), + ]; + + let all_empty = castle_line.iter().all(|square| empty & square.to_bitboard() > 0); + let any_checks = castle_line.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] { + mobility += 1.; + } + }, + 7 => { + let castle_line = [ + king_home_position.east_one(), + king_home_position.east_one().east_one(), + ]; + + let all_empty = castle_line.iter().all(|square| empty & square.to_bitboard() > 0); + let any_checks = castle_line.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] { + mobility += 1.; + } + }, + _ => {}, + } + } + } + } + } + Piece::Knight => { + for source in piece.serialize() { + mobility += (self.attacks.knight[source as usize] & (empty | opponent_occupancy)).pop_count() as f32; + } + } + Piece::Bishop => { + for source in piece.serialize() { + mobility += (self.attacks.bishop(self.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32; + } + } + Piece::Rook => { + for source in piece.serialize() { + mobility += (self.attacks.rook(self.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32; + } + } + Piece::Queen => { + // Do not account queen in mobility + } + incorrect_type => panic!("Incorrect piece type: {:?}", incorrect_type), + } + } + + mobility + } + + /// Count player pieces' material, giving bonus for pieces standing well pub fn material(&self, color: Color) -> f32 { let mut material = 0f32; for (piece_index, bitboard) in self.pieces_by_color(color).iter().enumerate() { - let piece_type = PieceType::from(piece_index); + let piece_type = Piece::from(piece_index); let bonus_table = match piece_type { - PieceType::Pawn => PAWN_BONUS, - PieceType::Knight => KNIGHT_BONUS, - PieceType::Bishop => BISHOP_BONUS, - PieceType::Rook => ROOK_BONUS, - PieceType::King => KING_BONUS, - PieceType::Queen => QUEEN_BONUS, + Piece::Pawn => PAWN_BONUS, + Piece::Knight => KNIGHT_BONUS, + Piece::Bishop => BISHOP_BONUS, + Piece::Rook => ROOK_BONUS, + Piece::King => KING_BONUS, + Piece::Queen => QUEEN_BONUS, _ => panic!("Unreachable") }; @@ -189,8 +473,8 @@ impl Board { let mut result = 0.0; let pawns = match color { - Color::White => self.piece_sets[PieceType::Pawn as usize], - Color::Black => self.piece_sets[PieceType::PawnBlack as usize], + Color::White => self.piece_sets[Piece::Pawn as usize], + Color::Black => self.piece_sets[Piece::PawnBlack as usize], }; for file in 0..8 { @@ -225,18 +509,18 @@ impl Board { let mut result = 0.0; let king_square = match color { - Color::White => self.piece_sets[PieceType::King as usize], - Color::Black => self.piece_sets[PieceType::KingBlack as usize], + Color::White => self.piece_sets[Piece::King as usize], + Color::Black => self.piece_sets[Piece::KingBlack as usize], }.bitscan(); for (piece_type, bitboard) in self.pieces_by_color(color.flip()).iter().enumerate() { - if piece_type != PieceType::King as usize && piece_type != PieceType::Pawn as usize { + if piece_type != Piece::King as usize && piece_type != Piece::Pawn as usize { for square in bitboard.serialize() { let distance = (king_square.rank() as f32 - square.rank() as f32).abs() + (king_square.file() as f32 - square.file() as f32).abs(); - result += distance / PieceType::from(piece_type).static_eval(); + result += distance / Piece::from(piece_type).static_eval(); } } } |