use crate::{bitboard::pop_count, board::*}; #[derive(Debug, Default, PartialEq)] pub struct PerftResult { leaf_nodes: u64, captures: u64, en_passants: u64, castles: u64, checks: u64, } impl Board { pub fn perft(&mut self, depth: u8, print: bool) -> PerftResult { let mut result = PerftResult::default(); if depth == 0 { result.leaf_nodes = 1; return result; } let color = self.color(); 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()); } 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 => { result.captures += 1; } MoveKind::EnPassant => { result.en_passants += 1; result.captures += 1; } MoveKind::Castle => { result.castles += 1; } _ => {} } if self.is_king_in_check(color.flip()) { result.checks += 1; } } if print { println!("{:?}", mov); self.print(); } let subtree_result = self.perft(depth - 1, print); result.leaf_nodes += subtree_result.leaf_nodes; result.captures += subtree_result.captures; result.checks += subtree_result.checks; result.castles += subtree_result.castles; result.en_passants += subtree_result.en_passants; } self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before); } if print { println!("Found {} leaf nodes in this subtree (depth {})", result.leaf_nodes, depth); } result } /// Compute material advantage relative to the current player pub fn material_advantage(&self) -> f32 { let mut eval = 0f32; for (piece_index, bitboard) in self.pieces.iter().enumerate() { let piece_type = PieceType::from(piece_index); let sign = if Color::from_piece(piece_type) == self.color() { 1. } else { -1. }; let multiplier = match piece_type.without_color() { PieceType::Pawn => 1., PieceType::Bishop => 3., PieceType::Knight => 3., PieceType::Rook => 4.5, PieceType::Queen => 9., PieceType::King => 0., _ => panic!("Piece should be without color"), }; eval += sign * multiplier * pop_count(*bitboard) as f32; } eval } pub fn negamax_search(&mut self, mut alpha: f32, beta: f32, depth_left: u8) -> (f32, Vec) { let mut principal_variation = Vec::new(); let color = self.color(); if depth_left == 0 { return (self.material_advantage(), principal_variation); } 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 (mut evaluation, mut subtree_pv) = self.negamax_search(-beta, -alpha, depth_left - 1); evaluation *= -1.; self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before); if evaluation >= beta { return (beta, principal_variation); // Fail-hard beta-cutoff } if evaluation > alpha { alpha = evaluation; principal_variation = Vec::with_capacity(depth_left as usize); principal_variation.push(mov); principal_variation.append(&mut subtree_pv); } } else { self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before); } } (alpha, principal_variation) } } #[cfg(test)] mod tests { use std::f32::INFINITY; use crate::board::{Board, engine::PerftResult}; #[test] fn perft() { let mut board = Board::new(); assert_eq!(board.perft(0, false), PerftResult { leaf_nodes: 1, captures: 0, en_passants: 0, castles: 0 , checks: 0 }); assert_eq!(board.perft(1, false), PerftResult { leaf_nodes: 20, captures: 0, en_passants: 0, castles: 0 , checks: 0 }); assert_eq!(board.perft(2, false), PerftResult { leaf_nodes: 400, captures: 0, en_passants: 0, castles: 0 , checks: 0 }); assert_eq!(board.perft(3, false), PerftResult { leaf_nodes: 8902, captures: 34, en_passants: 0, castles: 0 , checks: 12 }); assert_eq!(board.perft(4, false), PerftResult { leaf_nodes: 197281, captures: 1576, en_passants: 0, castles: 0 , checks: 469 }); // 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), PerftResult { leaf_nodes: 1, captures: 0, en_passants: 0, castles: 0 , checks: 0 }); assert_eq!(board.perft(1, false), PerftResult { leaf_nodes: 48, captures: 8, en_passants: 0, castles: 2 , checks: 0 }); assert_eq!(board.perft(2, false), PerftResult { leaf_nodes: 2039, captures: 351, en_passants: 1, castles: 91 , checks: 3 }); assert_eq!(board.perft(3, false), PerftResult { leaf_nodes: 97862, captures: 17102, en_passants: 45, castles: 3162, checks: 993 }); } #[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, pv) = board.negamax_search(-INFINITY, INFINITY, 4); assert_eq!(eval, 0.0); assert_eq!(pv.len(), 4); } #[test] fn material_advantage() { let board = Board::new(); assert_eq!(board.material_advantage(), 0.0, "Material advantage should be 0 at starting position"); } }