aboutsummaryrefslogtreecommitdiff
path: root/src/board/engine.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/board/engine.rs')
-rw-r--r--src/board/engine.rs310
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();
}
}
}