aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreug-vs <eugene@eug-vs.xyz>2023-02-21 11:57:43 +0300
committereug-vs <eugene@eug-vs.xyz>2023-02-21 11:57:43 +0300
commit27c08580d7ae61ea86a3c0bfbb62c5930ec010ac (patch)
tree45f3dd3203ec7801e2e8277b3ec7a17d27f7172c
parent46e9fcdcce5730827355db9449c41dc0a0e42b1f (diff)
downloadchessnost-27c08580d7ae61ea86a3c0bfbb62c5930ec010ac.tar.gz
refactor: generalize move generation
-rw-r--r--src/board/engine.rs6
-rw-r--r--src/board/mod.rs321
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);