diff options
| author | eug-vs <eugene@eug-vs.xyz> | 2023-02-21 14:19:34 +0300 | 
|---|---|---|
| committer | eug-vs <eugene@eug-vs.xyz> | 2023-02-21 14:48:06 +0300 | 
| commit | 69f3c48fb99d96f3fbc4ab49f5fb6d1d8e90e270 (patch) | |
| tree | 57c53013f2742c3d05762c7fdd066f66bd631e09 /src/board/mod.rs | |
| parent | 5e9543dcc6a012aef73d342080bfec46690b5446 (diff) | |
| download | chessnost-69f3c48fb99d96f3fbc4ab49f5fb6d1d8e90e270.tar.gz | |
refactor: split Board module into submodules
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);      }  |