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_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()); } 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 } pub 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 } pub 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 } } #[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 = board.negamax_search(-INFINITY, INFINITY, 4); println!("{}", eval); } }