use crate::{bitboard::pop_count, board::*}; impl Board { 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) } 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; #[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 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); } }