diff options
author | eug-vs <eugene@eug-vs.xyz> | 2023-01-24 21:40:06 +0300 |
---|---|---|
committer | eug-vs <eugene@eug-vs.xyz> | 2023-01-24 21:40:06 +0300 |
commit | e27b950db851c91231abf3f3a3afebae18af47af (patch) | |
tree | 74bffdf56b2a64163204374f5309ff36384d7549 /src/board.rs | |
parent | 4a62a723f052ed0506cf9342c009cc315a8379a3 (diff) | |
download | chessnost-e27b950db851c91231abf3f3a3afebae18af47af.tar.gz |
refactor: separate engine into submodule
Diffstat (limited to 'src/board.rs')
-rw-r--r-- | src/board.rs | 839 |
1 files changed, 0 insertions, 839 deletions
diff --git a/src/board.rs b/src/board.rs deleted file mode 100644 index 9ff81b1..0000000 --- a/src/board.rs +++ /dev/null @@ -1,839 +0,0 @@ -use crate::{bitboard::{Bitboard, serialize_bitboard, bitscan, pop_count}, moves::{Move, MoveKind}, attacks::Attacks, square::Square}; - -pub enum CastlingSide { - King, - Queen, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Board { - pub pieces: [Bitboard; 12], - - pub occupancy: Bitboard, - pub ply: u16, - - /// En passsant target square - pub ep_target: Option<Square>, - - /// Castling rights indexed by Color and CastlingSide - /// ``` - /// let can_castle = castling_rights[Color::White as usize][CastlingSide::Queen as usize]; - /// ``` - pub castling_rights: [[bool; 2]; 2], - - 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, -} - -const PIECE_CHARS: [&str; 12] = [ - "♟︎", "♞", "♝", "♜", "♛", "♚", - "♙", "♘", "♗", "♖", "♕", "♔", -]; - - -#[allow(unused)] -impl Board { - #[allow(non_snake_case)] - pub fn from_FEN(fen: String) -> Self { - let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - let mut rank = 7; - let mut file = 0i32; - - for character in fen.chars() { - let index = rank * 8 + file; - let position = 1 << index.clamp(0, 63); - - if character.is_numeric() { - let digit = match character.to_digit(10) { - None => todo!("What to do here?"), - Some(digit) => digit, - }; - if digit > 0 && digit <= 8 { - file += digit as i32; - } - } else { - match character { - 'P' => pieces[PieceType::Pawn as usize] |= position, - 'N' => pieces[PieceType::Knight as usize] |= position, - 'B' => pieces[PieceType::Bishop as usize] |= position, - 'R' => pieces[PieceType::Rook as usize] |= position, - 'Q' => pieces[PieceType::Queen as usize] |= position, - 'K' => pieces[PieceType::King as usize] |= position, - 'p' => pieces[PieceType::PawnBlack as usize] |= position, - 'n' => pieces[PieceType::KnightBlack as usize] |= position, - 'b' => pieces[PieceType::BishopBlack as usize] |= position, - 'r' => pieces[PieceType::RookBlack as usize] |= position, - 'q' => pieces[PieceType::QueenBlack as usize] |= position, - 'k' => pieces[PieceType::KingBlack as usize] |= position, - '/' => { - rank -= 1; - file = -1; // So it becomes 0 - }, - ' ' => { break }, // TODO: break for now, parse everything else later - '-' => {}, // TODO - 'w' => {}, // TODO - _ => todo!("Unexpected character!"), - } - file += 1; - } - } - - let mut board = Self { - pieces, - occupancy: 0, - ply: 0, - attacks: Attacks::new(), - castling_rights: [[true; 2]; 2], // TODO: actualy parse from FEN - ep_target: None, - }; - board.update_occupancy(); - board - } - - pub fn new() -> Self { - let default_fen = String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - Self::from_FEN(default_fen) - } - - /// Color to move at this ply - fn color(&self) -> Color { - Color::from(self.ply as u8 % 2) - } - - fn update_occupancy(&mut self) { - self.occupancy = 0; - // TODO: reduce - for piece in self.pieces { - self.occupancy |= piece; - } - } - - fn empty(&self) -> Bitboard { - !self.occupancy - } - - fn pieces_by_color(&self, color: Color) -> &[Bitboard] { - match color { - Color::White => &self.pieces[0..6], - Color::Black => &self.pieces[6..12], - } - } - - fn color_occupancy(&self, color: Color) -> Bitboard { - let mut occupancy = 0; - for piece in self.pieces_by_color(color) { - occupancy |= piece; - } - occupancy - } - - pub fn color_to_move(&self) -> Color { - Color::from((self.ply % 2) as u8) - } - - pub fn print(&self) { - println!(); - for rank in (0..8).rev() { - print!("{}|", rank + 1); - for file in 0..8 { - let index = rank * 8 + file; - let position: Bitboard = 1 << index; - let mut found = false; - for (piece_type, piece_bitboard) in self.pieces.iter().enumerate() { - if (piece_bitboard & position) > 0 { - found = true; - print!("{} ", PIECE_CHARS[piece_type]); - } - } - if !found { - print!(". "); - } - } - println!(); - } - 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(1024); - let opponent_occupancy = self.color_occupancy(color.flip()); - let empty = self.empty(); - let available_targets = opponent_occupancy | empty; - let player_pieces = self.pieces_by_color(color); - - for (piece_type, piece) in player_pieces.iter().enumerate() { - match PieceType::from(piece_type) { - PieceType::Pawn => { - for source in serialize_bitboard(*piece) { - let ep_bitboard = match self.ep_target { - Some(square) => square.to_bitboard(), - None => 0, - }; - for target in serialize_bitboard(self.attacks.pawn[color as usize][source as usize] & opponent_occupancy) { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; - for target in serialize_bitboard(self.attacks.pawn[color as usize][source as usize] & ep_bitboard) { - moves.push(Move { source, target, kind: MoveKind::EnPassant }); - } - for target in serialize_bitboard(self.attacks.pawn_pushes[color as usize][source as usize] & empty) { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - } - - // 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 serialize_bitboard(*piece & able_to_double_push_mask) { - for target in serialize_bitboard(self.attacks.pawn_double_pushes[color as usize][source as usize] & empty) { - moves.push(Move { source, target, kind: MoveKind::DoublePush }); - }; - } - } - PieceType::King => { - for source in serialize_bitboard(*piece) { - for target in serialize_bitboard(self.attacks.king[source as usize] & empty) { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - for target in serialize_bitboard(self.attacks.king[source as usize] & opponent_occupancy) { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; - - // 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 serialize_bitboard(player_pieces[PieceType::Rook as usize]) - .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] { - moves.push(Move { - source: king_home_position, - target: king_home_position.west_one().west_one(), - kind: MoveKind::Castle, - }) - } - }, - 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] { - moves.push(Move { - source: king_home_position, - target: king_home_position.east_one().east_one(), - kind: MoveKind::Castle, - }) - } - }, - _ => {}, - } - } - } - } - } - PieceType::Knight => { - for source in serialize_bitboard(*piece) { - for target in serialize_bitboard(self.attacks.knight[source as usize] & empty) { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - for target in serialize_bitboard(self.attacks.knight[source as usize] & opponent_occupancy) { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; - } - } - PieceType::Bishop => { - for source in serialize_bitboard(*piece) { - for target in serialize_bitboard(self.attacks.bishop(self.occupancy, source) & empty) { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - for target in serialize_bitboard(self.attacks.bishop(self.occupancy, source) & opponent_occupancy) { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; - } - } - PieceType::Rook => { - for source in serialize_bitboard(*piece) { - for target in serialize_bitboard(self.attacks.rook(self.occupancy, source) & empty) { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - } - for source in serialize_bitboard(*piece) { - for target in serialize_bitboard(self.attacks.rook(self.occupancy, source) & opponent_occupancy) { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; - } - } - PieceType::Queen => { - for source in serialize_bitboard(*piece) { - for target in serialize_bitboard(self.attacks.queen(self.occupancy, source) & empty) { - moves.push(Move { source, target, kind: MoveKind::Quiet }); - }; - for target in serialize_bitboard(self.attacks.queen(self.occupancy, source) & opponent_occupancy) { - moves.push(Move { source, target, kind: MoveKind::Capture }); - }; - } - } - _ => todo!("Incorrect piece type") - } - } - moves - } - - /// *Blindlessly* apply a move without any validation - /// Move should be validated beforehand - pub fn make_move(&mut self, mov: Move) -> Option<PieceType> { - let move_source_bb = mov.source.to_bitboard(); - let move_target_bb = mov.target.to_bitboard(); - - // Remove existing piece (if any) from target square - let mut captured_piece = match self.pieces - .iter() - .enumerate() - .find(|(piece_type, bitboard)| *bitboard & mov.target.to_bitboard() > 0) - { - Some((target_piece, _)) => { - self.pieces[target_piece] ^= move_target_bb; - Some(PieceType::from(target_piece)) - }, - None => None, - }; - - // En Passant captures diffirently - if mov.kind == MoveKind::EnPassant { - debug_assert!(captured_piece.is_none(), "No capture should be found at this point"); - let captured_bb = Square::from_coords(mov.source.rank(), mov.target.file()).to_bitboard(); - captured_piece = match self.pieces - .iter() - .enumerate() - .find(|(piece_type, bitboard)| *bitboard & captured_bb > 0) - { - Some((pawn_type, _)) => { - self.pieces[pawn_type] ^= captured_bb; - Some(PieceType::from(pawn_type)) - } - None => panic!("Pawn captured by En Passant was not found"), - } - } - - // Move a piece from source square to target - let source_piece = match self.pieces - .iter() - .enumerate() - .find(|(piece_type, bitboard)| *bitboard & mov.source.to_bitboard() > 0) - { - Some((source_piece, _)) => { - self.pieces[source_piece] ^= move_source_bb; - self.occupancy ^= move_source_bb; - - self.pieces[source_piece] |= move_target_bb; - self.occupancy |= move_target_bb; - PieceType::from(source_piece) - }, - None => panic!("Move is malformed: source piece not found"), - }; - - // When castling, also move a rook - if mov.kind == MoveKind::Castle { - debug_assert!(mov.source.file() == 4, "Castle can only be done from E file"); - let (rook_source_file, rook_target_file) = match mov.target.file() { - 2 => (0, 3), - 6 => (7, 5), - _ => panic!("Malformed castle, target square invalid: {:?}", mov), - }; - let rook_source_bb = Square::from_coords(mov.target.rank(), rook_source_file).to_bitboard(); - let rook_target_bb = Square::from_coords(mov.target.rank(), rook_target_file).to_bitboard(); - - match self.pieces - .iter() - .enumerate() - .find(|(rook_type, bitboard)| *bitboard & rook_source_bb > 0) - { - Some((rook_type, _)) => { - self.pieces[rook_type] ^= rook_source_bb; - self.occupancy ^= rook_source_bb; - - self.pieces[rook_type] |= rook_target_bb; - self.occupancy |= rook_target_bb; - }, - None => panic!("Rook was not found when castling"), - } - } - - // Double push should set En Passant target square - self.ep_target = if mov.kind == MoveKind::DoublePush { - match mov.source.rank() { - 1 => Some(mov.source.nort_one()), - 6 => Some(mov.source.sout_one()), - rank => panic!("Double-push was used from invalid rank({}) when trying to make {:?}", rank, mov), - } - } else { None }; - - // Withdraw castling rights when moving rooks or king - match source_piece { - PieceType::King => { - self.castling_rights[Color::from_piece(source_piece) as usize][CastlingSide::King as usize] = false; - self.castling_rights[Color::from_piece(source_piece) as usize][CastlingSide::Queen as usize] = false; - }, - PieceType::Rook => { - match mov.source.file() { - 0 => self.castling_rights[Color::from_piece(source_piece) as usize][CastlingSide::Queen as usize] = false, - 7 => self.castling_rights[Color::from_piece(source_piece) as usize][CastlingSide::King as usize] = false, - _ => {}, - } - }, - _ => {}, - } - - self.ply += 1; - - captured_piece - } - - /// Completely reverse make_move as if it never happened - pub fn unmake_move(&mut self, mov: Move, captured_piece: Option<PieceType>, previous_ep_target: Option<Square>, previous_castling_rights: [[bool; 2]; 2]) { - let move_source_bb = mov.source.to_bitboard(); - let move_target_bb = mov.target.to_bitboard(); - - // Move a piece from target square back to source square - match self.pieces - .iter() - .enumerate() - .find(|(piece_type, bitboard)| *bitboard & mov.target.to_bitboard() > 0) - { - Some((source_piece, _)) => { - self.pieces[source_piece] ^= move_target_bb; - self.occupancy ^= move_target_bb; - - self.pieces[source_piece] |= move_source_bb; - self.occupancy |= move_source_bb; - }, - None => panic!("Trying to unmake move which was not made: no piece was found on target square"), - }; - - // If unmaking castle, also return rook to its place - if mov.kind == MoveKind::Castle { - let (rook_source_file, rook_target_file) = match mov.target.file() { - 2 => (0, 3), - 6 => (7, 5), - _ => panic!("Malformed castle, target square invalid: {:?}", mov), - }; - let rook_source_bb = Square::from_coords(mov.target.rank(), rook_source_file).to_bitboard(); - let rook_target_bb = Square::from_coords(mov.target.rank(), rook_target_file).to_bitboard(); - - match self.pieces - .iter() - .enumerate() - .find(|(rook_type, bitboard)| *bitboard & rook_target_bb > 0) - { - Some((rook_type, _)) => { - self.pieces[rook_type] |= rook_source_bb; - self.occupancy |= rook_source_bb; - - self.pieces[rook_type] ^= rook_target_bb; - self.occupancy ^= rook_target_bb; - }, - None => panic!("Rook was not found when castling"), - } - } - - // Return captured piece to target square - match captured_piece { - Some(target_piece) => { - match mov.kind { - // Return pawn captured by En Passant pawn if needed - MoveKind::EnPassant => { - let original_dead_pawn_bb = Square::from_coords(mov.source.rank(), mov.target.file()).to_bitboard(); - self.pieces[target_piece as usize] |= original_dead_pawn_bb; - self.occupancy |= original_dead_pawn_bb; - }, - _ => { - self.pieces[target_piece as usize] |= move_target_bb; - self.occupancy |= move_target_bb; - }, - } - }, - None => {} - } - - - self.ep_target = previous_ep_target; - self.castling_rights = previous_castling_rights; - self.ply -= 1; - } - - pub fn perft(&mut self, depth: u8, print: bool) -> (u64, u64, u64, u64, u64) { - if depth == 0 { - return (1, 0, 0, 0, 0) // This a leaf, exactly one node - } - let color = self.color_to_move(); - - let moves = self.generate_pseudolegal_moves(color); - - if print { - println!("Running perft for depth {}. Color to move is {:?}\n{} moves available", depth, color, moves.len()); - println!("{} moves available", moves.len()); - } - - let mut total = 0; - let mut captures = 0; - let mut checks = 0; - let mut castles = 0; - let mut en_passants = 0; - - for mov in moves { - let ep_target_before = self.ep_target.clone(); - let castling_rights_before = self.castling_rights.clone(); - let captured_piece = self.make_move(mov); - // King can not be in check after our own move - if !self.is_king_in_check(color) { - if depth == 1 { - match mov.kind { - MoveKind::Capture => { - captures += 1; - } - MoveKind::EnPassant => { - en_passants += 1; - captures += 1; - } - MoveKind::Castle => { - castles += 1; - } - _ => {} - } - if self.is_king_in_check(color.flip()) { - checks += 1; - } - } - - if print { - println!("{:?}", mov); - self.print(); - } - let (children_total, children_tactical, children_checks, children_castles, children_ep) = self.perft(depth - 1, print); - total += children_total; - captures += children_tactical; - checks += children_checks; - castles += children_castles; - en_passants += children_ep; - - } - self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before); - } - - if print { - println!("Found {} nodes in this subtree (depth {})", total, depth); - } - - (total, captures, checks, castles, en_passants) - } - - fn is_square_attacked(&self, square: Square, attacker_color: Color) -> bool { - let square_bb = square.to_bitboard(); - for (piece_type, piece) in self.pieces_by_color(attacker_color).iter().enumerate() { - match PieceType::from(piece_type) { - PieceType::Pawn => { - if (self.attacks.pawn[attacker_color.flip() as usize][square as usize] & piece > 0) { - return true - } - } - PieceType::Knight => { - if (self.attacks.knight[square as usize] & piece > 0) { - return true - } - } - PieceType::Bishop => { - if (self.attacks.bishop(self.occupancy, square) & piece > 0) { - return true - } - } - PieceType::Rook => { - if (self.attacks.rook(self.occupancy, square) & piece > 0) { - return true - } - } - PieceType::Queen => { - if (self.attacks.queen(self.occupancy, square) & piece > 0) { - return true - } - } - PieceType::King => {} - _ => panic!("Unexpected piece type! Pieces by color should be considered white") - } - } - false - } - - fn is_king_in_check(&self, color: Color) -> bool { - let king_bb = match color { - Color::White => self.pieces[PieceType::King as usize], - Color::Black => self.pieces[PieceType::KingBlack as usize], - }; - let square = bitscan(king_bb); - self.is_square_attacked(square, color.flip()) - } - - fn evaluate(&self) -> f32 { - let mut eval = 0f32; - let pieces = self.pieces_by_color(self.color()); - eval += pop_count(pieces[PieceType::Pawn as usize]) as f32; - eval += pop_count(pieces[PieceType::Bishop as usize]) as f32 * 3.; - eval += pop_count(pieces[PieceType::Knight as usize]) as f32 * 3.; - eval += pop_count(pieces[PieceType::Rook as usize]) as f32 * 4.5; - eval += pop_count(pieces[PieceType::Queen as usize]) as f32 * 9.; - eval - } - - fn negamax_search(&mut self, mut alpha: f32, beta: f32, depth_left: u8) -> f32 { - let color = Color::from(self.ply as u8 % 2); - - self.print(); - - if depth_left == 0 { - return self.evaluate(); - } - let moves = self.generate_pseudolegal_moves(color); - - for mov in moves { - let ep_target_before = self.ep_target.clone(); - let castling_rights_before = self.castling_rights.clone(); - let captured_piece = self.make_move(mov); - - if !self.is_king_in_check(color) { - let evaluation = -self.negamax_search(-beta, -alpha, depth_left - 1); - self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before); - - if (evaluation >= beta) { - return beta; // Fail-hard beta-cutoff - } - if (evaluation > alpha) { - alpha = evaluation - } - } else { - self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before); - } - } - alpha - } -} - - -#[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 std::f32::INFINITY; - - use super::*; - use crate::{bitboard::{pop_count, bitscan, print}, square::Square}; - - #[test] - fn square_enum() { - assert_eq!(Square::A1 as u8, 0); - assert_eq!(Square::F1 as u8, 5); - assert_eq!(Square::H8 as u8, 63); - } - - #[test] - fn new_from_default_fen() { - let board = Board::new(); - - board.print(); - print(board.empty(), "Empty squares"); - - assert_eq!(pop_count(board.pieces[PieceType::Pawn as usize]), 8); - assert_eq!(pop_count(board.pieces[PieceType::Knight as usize]), 2); - assert_eq!(pop_count(board.pieces[PieceType::Bishop as usize]), 2); - assert_eq!(pop_count(board.pieces[PieceType::Rook as usize]), 2); - assert_eq!(pop_count(board.pieces[PieceType::Queen as usize]), 1); - assert_eq!(pop_count(board.pieces[PieceType::King as usize]), 1); - - assert_eq!(pop_count(board.pieces[PieceType::PawnBlack as usize]), 8); - assert_eq!(pop_count(board.pieces[PieceType::KnightBlack as usize]), 2); - assert_eq!(pop_count(board.pieces[PieceType::BishopBlack as usize]), 2); - assert_eq!(pop_count(board.pieces[PieceType::RookBlack as usize]), 2); - assert_eq!(pop_count(board.pieces[PieceType::QueenBlack as usize]), 1); - assert_eq!(pop_count(board.pieces[PieceType::KingBlack as usize]), 1); - - assert_eq!(bitscan(board.pieces[PieceType::King as usize]), Square::E1); - assert_eq!(bitscan(board.pieces[PieceType::QueenBlack as usize]), Square::D8); - - assert_eq!(pop_count(board.occupancy), 32); - assert_eq!(pop_count(board.empty()), 32); - assert_eq!(pop_count(board.color_occupancy(Color::White)), 16); - assert_eq!(pop_count(board.color_occupancy(Color::Black)), 16); - } - - #[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); - - assert_eq!(moves.len(), 20); - assert_eq!(black_moves.len(), 20); - - for mov in moves { - mov.print(); - } - } - - #[test] - fn make_move() { - let fen = String::from("q1b2k2/5p1p/4p1pb/pPPp4/3N4/3nPB2/P2QKnR1/1R6 w - - 0 25"); - let mut board = Board::from_FEN(fen); - let initial_board = board.clone(); - board.print(); - - let black_move = Move { source: Square::F7, target: Square::F5, kind: MoveKind::Quiet }; - println!("\n{:?}", black_move); - - match board.make_move(black_move) { - Some(..) => panic!("No piece should be captured"), - None => {}, - }; - - board.print(); - - assert!(board.pieces[PieceType::PawnBlack as usize] & Square::F7.to_bitboard() == 0); - assert!(board.pieces[PieceType::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), - None => panic!("A piece should be captured"), - }; - - board.print(); - - assert!(board.pieces[PieceType::PawnBlack as usize] & Square::A5.to_bitboard() == 0, "Target piece should be captured"); - assert!(board.pieces[PieceType::Queen as usize] & Square::D2.to_bitboard() == 0); - assert!(board.pieces[PieceType::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); - } - - #[test] - fn unmake_move() { - let fen = String::from("q1b2k2/5p1p/4p1pb/pPPp4/3N4/3nPB2/P2QKnR1/1R6 w - - 0 25"); - let mut board = Board::from_FEN(fen); - let initial_board = board.clone(); - - let mov = Move { source: Square::D2, target: Square::A5, kind: MoveKind::Capture }; - - board.print(); - - let captured_piece = board.make_move(mov); - board.print(); - - board.unmake_move(mov, captured_piece, None, board.castling_rights); - board.print(); - - assert_eq!(board, initial_board, "Board state after unmake_move should be the same as before make_move"); - } - - #[test] - fn perft() { - let mut board = Board::new(); - - assert_eq!(board.perft(0, false), (1, 0, 0, 0, 0)); - assert_eq!(board.perft(1, false), (20, 0, 0, 0, 0)); - assert_eq!(board.perft(2, false), (400, 0, 0, 0, 0)); - assert_eq!(board.perft(3, false), (8902, 34, 12, 0, 0)); - assert_eq!(board.perft(4, false), (197281, 1576, 469, 0, 0)); - // assert_eq!(board.perft(5, false), (4865609, 82719, 27351, 0, 258)); - // assert_eq!(board.perft(6, false), (119060324, 2812008, 809099, 0, 5248)); - } - - #[test] - fn position_perft() { - let fen = String::from("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - "); - let mut board = Board::from_FEN(fen); - assert_eq!(board.perft(0, false), (1, 0, 0, 0, 0)); - assert_eq!(board.perft(1, false), (48, 8, 0, 2, 0)); - assert_eq!(board.perft(2, false), (2039, 351, 3, 91, 1)); - assert_eq!(board.perft(3, false), (97862, 17102, 993, 3162, 45)); - } - - #[test] - fn is_square_attacked() { - let board = Board::new(); - - assert_eq!(board.is_square_attacked(Square::E2, Color::White), true); - assert_eq!(board.is_square_attacked(Square::E2, Color::Black), false); - assert_eq!(board.is_square_attacked(Square::E4, Color::White), false); - assert_eq!(board.is_square_attacked(Square::B6, Color::Black), true); - } - - #[test] - fn negamax_search() { - let fen = String::from("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - "); - let mut board = Board::from_FEN(fen); - - let eval = board.negamax_search(-INFINITY, INFINITY, 4); - println!("{}", eval); - } -} |