diff options
Diffstat (limited to 'src/board/mod.rs')
-rw-r--r-- | src/board/mod.rs | 431 |
1 files changed, 28 insertions, 403 deletions
diff --git a/src/board/mod.rs b/src/board/mod.rs index 5317cc2..e6a6446 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -1,7 +1,9 @@ use crate::{bitboard::{Bitboard, BitboardFns}, moves::{Move, MoveKind}, attacks::Attacks, square::Square, board::io::IO}; -use self::{ttable::{TranspositionTable, TTABLE_SIZE}, zobrist::{ZobristSeed, Zobrist}}; +use self::{ttable::{TranspositionTable, TTABLE_SIZE}, zobrist::{ZobristSeed, Zobrist}, piece::Piece, color::Color}; pub mod io; +pub mod color; +pub mod piece; mod zobrist; mod engine; mod ttable; @@ -32,45 +34,6 @@ pub struct Board { attacks: Attacks, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] -#[repr(usize)] -pub enum PieceType { - #[default] - Pawn, - Knight, - Bishop, - Rook, - Queen, - King, - PawnBlack, - KnightBlack, - BishopBlack, - RookBlack, - QueenBlack, - KingBlack, -} - -impl PieceType { - pub fn without_color(&self) -> Self { - let index = *self as usize; - Self::from(index % 6) - } - // Return the price of the peice - pub fn static_eval(&self) -> f32 { - match self.without_color() { - PieceType::Pawn => 1.0, - PieceType::Bishop => 3.3, - PieceType::Knight => 3.2, - PieceType::Rook => 5.0, - PieceType::Queen => 9.0, - PieceType::King => 0., - _ => panic!("Piece should be without color"), - } - } -} - - - impl Board { pub fn new() -> Self { let default_fen = String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); @@ -101,303 +64,18 @@ impl Board { self.pieces_by_color(color).iter().fold(0, |acc, bitboard| acc | bitboard) } - - - 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[PieceType::KingBlack as usize], - Color::Black => self.piece_sets[PieceType::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 = 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"), - }; - - // Generalized attack/move pattern for all pieces - for target in (move_targets & targets).serialize() { - moves.push(Move { source, target, kind }); - - // 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)}) - } - } - } - } - - // 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 }); - } - } - } - } - - 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, - }) - } - }, - _ => {}, - } - } - } - } - 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[PieceType::Pawn as usize], - Color::White => self.piece_sets[PieceType::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 PieceType::from(piece_type) { - 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, - }; - 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; - } - } - PieceType::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[PieceType::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.; - } - }, - _ => {}, - } - } - } - } - } - PieceType::Knight => { - for source in piece.serialize() { - mobility += (self.attacks.knight[source as usize] & (empty | opponent_occupancy)).pop_count() as f32; - } - } - PieceType::Bishop => { - for source in piece.serialize() { - mobility += (self.attacks.bishop(self.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32; - } - } - PieceType::Rook => { - for source in piece.serialize() { - mobility += (self.attacks.rook(self.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32; - } - } - PieceType::Queen => { - // Do not account queen in mobility - } - incorrect_type => panic!("Incorrect piece type: {:?}", incorrect_type), - } - } - - mobility - } - - fn piece_by_square(&self, square: Square) -> Option<PieceType> { + fn piece_by_square(&self, square: Square) -> Option<Piece> { let square_bb = square.to_bitboard(); self.piece_sets .iter() .enumerate() .find(|(_, bitboard)| *bitboard & square_bb > 0) - .and_then(|(pt, _)| Some(PieceType::from(pt))) + .and_then(|(pt, _)| Some(Piece::from(pt))) } /// *Blindlessly* apply a move without any validation /// Legality test should still be performed - pub fn make_move(&mut self, mov: Move) -> Option<PieceType> { + pub fn make_move(&mut self, mov: Move) -> Option<Piece> { let move_source_bb = mov.source.to_bitboard(); let move_target_bb = mov.target.to_bitboard(); @@ -452,7 +130,7 @@ impl Board { self.zobrist_toggle_piece(source_piece, mov.target); } } - PieceType::from(source_piece) + Piece::from(source_piece) }, None => { self.print(); @@ -502,13 +180,13 @@ impl Board { // Withdraw castling rights when moving rooks or king let source_color = Color::from_piece(source_piece); match source_piece.without_color() { - PieceType::King => { + Piece::King => { self.castling_rights[source_color as usize][CastlingSide::King as usize] = false; self.castling_rights[source_color as usize][CastlingSide::Queen as usize] = false; self.zobrist_toggle_castling_right(source_color, CastlingSide::King); self.zobrist_toggle_castling_right(source_color, CastlingSide::Queen); }, - PieceType::Rook => { + Piece::Rook => { match mov.source.file() { 0 => { self.castling_rights[source_color as usize][CastlingSide::Queen as usize] = false; @@ -534,7 +212,7 @@ impl Board { pub fn unmake_move( &mut self, mov: Move, - captured_piece: Option<PieceType>, + captured_piece: Option<Piece>, previous_ep_target: Option<Square>, previous_castling_rights: [[bool; 2]; 2], previous_hash: u64, @@ -552,8 +230,8 @@ impl Board { self.occupancy ^= move_target_bb; let source_id = match Color::from_piece(promotion_piece) { - Color::White => PieceType::Pawn, - Color::Black => PieceType::PawnBlack, + Color::White => Piece::Pawn, + Color::Black => Piece::PawnBlack, } as usize; self.piece_sets[source_id] |= move_source_bb; @@ -626,33 +304,33 @@ impl Board { fn is_square_attacked(&self, square: Square, attacker_color: Color) -> bool { for (piece_type, piece) in self.pieces_by_color(attacker_color).iter().enumerate() { - match PieceType::from(piece_type) { - PieceType::Queen => { + match Piece::from(piece_type) { + Piece::Queen => { if self.attacks.queen(self.occupancy, square) & piece > 0 { return true } } - PieceType::Knight => { + Piece::Knight => { if self.attacks.knight[square as usize] & piece > 0 { return true } } - PieceType::Bishop => { + Piece::Bishop => { if self.attacks.bishop(self.occupancy, square) & piece > 0 { return true } } - PieceType::Rook => { + Piece::Rook => { if self.attacks.rook(self.occupancy, square) & piece > 0 { return true } } - PieceType::Pawn => { + Piece::Pawn => { if self.attacks.pawn[attacker_color.flip() as usize][square as usize] & piece > 0 { return true } } - PieceType::King => { + Piece::King => { if self.attacks.king[square as usize] & piece > 0 { return true } @@ -665,8 +343,8 @@ impl Board { fn is_king_in_check(&self, color: Color) -> bool { let king_bb = 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], }; let square = king_bb.bitscan(); self.is_square_attacked(square, color.flip()) @@ -674,33 +352,10 @@ impl Board { } -#[derive(Debug, Clone, Copy, PartialEq, num_enum::FromPrimitive)] -#[repr(u8)] -pub enum Color { - #[default] - White, - Black, -} -impl Color { - pub fn flip(&self) -> Self { - match self { - Self::White => Self::Black, - Self::Black => Self::White, - } - } - pub fn from_piece(piece: PieceType) -> Self { - if (piece as u8) < 6 { - Self::White - } else { - Self::Black - } - } -} - #[cfg(test)] mod tests { use super::*; - use crate::{bitboard::BitboardFns, square::Square, board::zobrist::Zobrist}; + use crate::{square::Square, board::zobrist::Zobrist}; #[test] fn square_enum() { @@ -710,36 +365,6 @@ mod tests { } #[test] - fn new_from_default_fen() { - let board = Board::new(); - - board.print(); - board.empty().print("Empty squares"); - - assert_eq!(board.piece_sets[PieceType::Pawn as usize].pop_count(), 8); - assert_eq!(board.piece_sets[PieceType::Knight as usize].pop_count(), 2); - assert_eq!(board.piece_sets[PieceType::Bishop as usize].pop_count(), 2); - assert_eq!(board.piece_sets[PieceType::Rook as usize].pop_count(), 2); - assert_eq!(board.piece_sets[PieceType::Queen as usize].pop_count(), 1); - assert_eq!(board.piece_sets[PieceType::King as usize].pop_count(), 1); - - assert_eq!(board.piece_sets[PieceType::PawnBlack as usize].pop_count(), 8); - assert_eq!(board.piece_sets[PieceType::KnightBlack as usize].pop_count(), 2); - assert_eq!(board.piece_sets[PieceType::BishopBlack as usize].pop_count(), 2); - assert_eq!(board.piece_sets[PieceType::RookBlack as usize].pop_count(), 2); - assert_eq!(board.piece_sets[PieceType::QueenBlack as usize].pop_count(), 1); - assert_eq!(board.piece_sets[PieceType::KingBlack as usize].pop_count(), 1); - - assert_eq!(board.piece_sets[PieceType::King as usize].bitscan(), Square::E1); - assert_eq!(board.piece_sets[PieceType::QueenBlack as usize].bitscan(), Square::D8); - - assert_eq!(board.occupancy.pop_count(), 32); - assert_eq!(board.empty().pop_count(), 32); - assert_eq!(board.color_occupancy(Color::White).pop_count(), 16); - assert_eq!(board.color_occupancy(Color::Black).pop_count(), 16); - } - - #[test] fn generate_pseudolegal_moves_starting_position() { let mut board = Board::new(); let moves = board.generate_pseudolegal_moves(); @@ -785,23 +410,23 @@ mod tests { board.compute_hash(); assert_eq!(hash, board.hash, "Hash should be correctly updated after move"); - assert!(board.piece_sets[PieceType::PawnBlack as usize] & Square::F7.to_bitboard() == 0); - assert!(board.piece_sets[PieceType::PawnBlack as usize] & Square::F5.to_bitboard() > 0); + assert!(board.piece_sets[Piece::PawnBlack as usize] & Square::F7.to_bitboard() == 0); + assert!(board.piece_sets[Piece::PawnBlack as usize] & Square::F5.to_bitboard() > 0); assert!(board.ply == 1); let white_move = Move { source: Square::D2, target: Square::A5, kind: MoveKind::Capture }; println!("\n{:?}", white_move); match board.make_move(white_move) { - Some(captured) => assert!(captured == PieceType::PawnBlack), + Some(captured) => assert!(captured == Piece::PawnBlack), None => panic!("A piece should be captured"), }; board.print(); - assert!(board.piece_sets[PieceType::PawnBlack as usize] & Square::A5.to_bitboard() == 0, "Target piece should be captured"); - assert!(board.piece_sets[PieceType::Queen as usize] & Square::D2.to_bitboard() == 0); - assert!(board.piece_sets[PieceType::Queen as usize] & Square::A5.to_bitboard() > 0); + assert!(board.piece_sets[Piece::PawnBlack as usize] & Square::A5.to_bitboard() == 0, "Target piece should be captured"); + assert!(board.piece_sets[Piece::Queen as usize] & Square::D2.to_bitboard() == 0); + assert!(board.piece_sets[Piece::Queen as usize] & Square::A5.to_bitboard() > 0); assert_ne!(board.occupancy, initial_board.occupancy, "Occupancy should change after make_move"); assert!(board.ply == 2); } |