diff options
author | eug-vs <eugene@eug-vs.xyz> | 2024-01-25 11:24:36 +0100 |
---|---|---|
committer | eug-vs <eugene@eug-vs.xyz> | 2024-01-25 11:24:36 +0100 |
commit | 746e3bf17463a377b6c54b291ebef9a736d6ceb7 (patch) | |
tree | a4e965669871084b98d3ce89ac95fa9d50131699 /src/grossmeister | |
parent | 299c6d6dee96a50f9366955192f922d449d11f20 (diff) | |
download | chessnost-canary.tar.gz |
chore: autoformat codecanary
Use #[rustfmt:skip] to preserve aligned blocks
Diffstat (limited to 'src/grossmeister')
-rw-r--r-- | src/grossmeister/UCI.rs | 118 | ||||
-rw-r--r-- | src/grossmeister/evaluation.rs | 276 | ||||
-rw-r--r-- | src/grossmeister/mod.rs | 10 | ||||
-rw-r--r-- | src/grossmeister/move_selector.rs | 66 | ||||
-rw-r--r-- | src/grossmeister/search.rs | 309 | ||||
-rw-r--r-- | src/grossmeister/ttable.rs | 51 |
6 files changed, 570 insertions, 260 deletions
diff --git a/src/grossmeister/UCI.rs b/src/grossmeister/UCI.rs index 0c3c158..e345943 100644 --- a/src/grossmeister/UCI.rs +++ b/src/grossmeister/UCI.rs @@ -1,6 +1,18 @@ -use std::{io::stdin, thread::{JoinHandle, spawn, sleep}, str::Split, time::Duration, sync::{Arc, atomic::{AtomicBool, Ordering}}}; +use std::{ + io::stdin, + str::Split, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread::{sleep, spawn, JoinHandle}, + time::Duration, +}; -use crate::{board::{Board, io::IO, color::Color}, moves::{Move, MoveKind}}; +use crate::{ + board::{color::Color, io::IO, Board}, + moves::{Move, MoveKind}, +}; use super::Grossmeister; @@ -15,12 +27,16 @@ impl Grossmeister { let mut cmd = String::new(); stdin().read_line(&mut cmd).unwrap(); if !self.parse_command(cmd, &mut search_handle) { - break + break; } } } - pub fn parse_command(&mut self, cmd: String, search_handle: &mut Option<JoinHandle<Self>>) -> bool { + pub fn parse_command( + &mut self, + cmd: String, + search_handle: &mut Option<JoinHandle<Self>>, + ) -> bool { let mut tokens = cmd.trim().split(' '); if let Some(token) = tokens.next() { match token { @@ -44,16 +60,17 @@ impl Grossmeister { "setoption" => { todo!() } - "ucinewgame" => { - *self = Self::default() - } + "ucinewgame" => *self = Self::default(), "position" => { - match tokens.next() { + match tokens.next() { Some("startpos") => { self.board = Board::new(); } Some("fen") => { - let fen = (0..6).filter_map(|_| tokens.next()).collect::<Vec<_>>().join(" "); + let fen = (0..6) + .filter_map(|_| tokens.next()) + .collect::<Vec<_>>() + .join(" "); self.board = Board::from_FEN(fen); } _ => panic!("Expected position"), @@ -62,27 +79,34 @@ impl Grossmeister { for token in tokens.by_ref() { let input_move = Move::from_notation(token.chars()); let moves = self.board.generate_pseudolegal_moves(); - if let Some(mov) = moves - .iter() - .find(|m| { - let promo_matches = match input_move.kind { - MoveKind::Promotion(piece) => match m.kind { - MoveKind::Promotion(another_piece) => piece.without_color() == another_piece.without_color(), - _ => false - }, - _ => true, - }; - promo_matches && m.source == input_move.source && m.target == input_move.target - }) { - self.board.make_move(*mov); - let repeated = self.board.positions.iter().filter(|&&p| p == self.board.hash).count(); - if self.board.hash == 13465997660506371065 { - self.board.print(); - dbg!(mov, repeated, self.board.hash, self.board.ply); + if let Some(mov) = moves.iter().find(|m| { + let promo_matches = match input_move.kind { + MoveKind::Promotion(piece) => match m.kind { + MoveKind::Promotion(another_piece) => { + piece.without_color() == another_piece.without_color() } - } else { - panic!("Illegal move: {}", input_move); - } + _ => false, + }, + _ => true, + }; + promo_matches + && m.source == input_move.source + && m.target == input_move.target + }) { + self.board.make_move(*mov); + let repeated = self + .board + .positions + .iter() + .filter(|&&p| p == self.board.hash) + .count(); + if self.board.hash == 13465997660506371065 { + self.board.print(); + dbg!(mov, repeated, self.board.hash, self.board.ply); + } + } else { + panic!("Illegal move: {}", input_move); + } } } } @@ -90,7 +114,9 @@ impl Grossmeister { // Before we go, let's join to the latest search if let Some(hand) = search_handle.take() { match hand.join() { - Ok(better_self) => self.transposition_table = better_self.transposition_table, + Ok(better_self) => { + self.transposition_table = better_self.transposition_table + } Err(err) => println!("info string error {:?}", err), } } @@ -98,20 +124,25 @@ impl Grossmeister { *search_handle = Some(self.parse_go(tokens, false)); } "stop" => { - self.should_halt.store(true, std::sync::atomic::Ordering::SeqCst); + self.should_halt + .store(true, std::sync::atomic::Ordering::SeqCst); } "ponderhit" => { // Since we were pondering without any scheduled halting previously, // in case of ponderhit we need to schedule a new halting based on clock // TODO: isn't the color flipped here? Might lead to time management bugs let color = self.board.color(); - let duration = (self.board.clock.time[color as usize] + self.board.clock.increment[color as usize]) / 20; + let duration = (self.board.clock.time[color as usize] + + self.board.clock.increment[color as usize]) + / 20; let halt_scheduled = self.schedule_halt(duration); // Join to the current pondering search if let Some(hand) = search_handle.take() { match hand.join() { - Ok(better_self) => self.transposition_table = better_self.transposition_table, + Ok(better_self) => { + self.transposition_table = better_self.transposition_table + } Err(err) => println!("info string error {:?}", err), } halt_scheduled.store(false, Ordering::SeqCst); // Cancel scheduled halting @@ -122,7 +153,7 @@ impl Grossmeister { "quit" => return false, // Non-UCI debug commands "print" => self.board.print(), - _ => {}, + _ => {} } } true @@ -134,7 +165,7 @@ impl Grossmeister { "searchmoves" => todo!(), "ponder" => { println!("info will start in pondering mode"); - return self.parse_go(tokens, true) + return self.parse_go(tokens, true); } "wtime" => { if let Some(time) = tokens.next() { @@ -151,13 +182,15 @@ impl Grossmeister { "winc" => { if let Some(time) = tokens.next() { let time: u64 = time.parse().unwrap(); - self.board.clock.increment[Color::White as usize] = Duration::from_millis(time); + self.board.clock.increment[Color::White as usize] = + Duration::from_millis(time); } } "binc" => { if let Some(time) = tokens.next() { let time: u64 = time.parse().unwrap(); - self.board.clock.increment[Color::Black as usize] = Duration::from_millis(time); + self.board.clock.increment[Color::Black as usize] = + Duration::from_millis(time); } } "movestogo" => {} @@ -166,7 +199,7 @@ impl Grossmeister { let depth: u8 = depth.parse().unwrap(); return self.create_search_thread(depth, Duration::MAX); } - }, + } "nodes" => todo!(), "mate" => todo!(), "movetime" => { @@ -174,12 +207,12 @@ impl Grossmeister { let time: u64 = time.parse().unwrap(); let duration = Duration::from_millis(time); - return self.create_search_thread(u8::MAX, duration) + return self.create_search_thread(u8::MAX, duration); } - }, + } "infinite" => { return self.create_search_thread(u8::MAX, Duration::MAX); - }, + } _ => {} } return self.parse_go(tokens, ponder); @@ -190,7 +223,8 @@ impl Grossmeister { let duration = if ponder { Duration::MAX } else { - (self.board.clock.time[color as usize] + self.board.clock.increment[color as usize]) / 20 + (self.board.clock.time[color as usize] + self.board.clock.increment[color as usize]) + / 20 }; self.create_search_thread(u8::MAX, duration) diff --git a/src/grossmeister/evaluation.rs b/src/grossmeister/evaluation.rs index 1b57f5a..8deb929 100644 --- a/src/grossmeister/evaluation.rs +++ b/src/grossmeister/evaluation.rs @@ -1,12 +1,17 @@ use std::f32::INFINITY; -use crate::{board::{piece::Piece, color::Color, CastlingSide}, bitboard::{Bitboard, BitboardFns}, square::Square}; +use crate::{ + bitboard::{Bitboard, BitboardFns}, + board::{color::Color, piece::Piece, CastlingSide}, + square::Square, +}; use super::Grossmeister; /// Score in pawns (not centipawns) pub type Score = f32; +#[rustfmt::skip] const PAWN_BONUS: [Score; 64] = [ // A B C D E F G H 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, @@ -19,6 +24,7 @@ const PAWN_BONUS: [Score; 64] = [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 ]; +#[rustfmt::skip] const PAWN_BONUS_PASSER_ENDGAME: [Score; 64] = [ // A B C D E F G H 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, @@ -31,6 +37,7 @@ const PAWN_BONUS_PASSER_ENDGAME: [Score; 64] = [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, ]; +#[rustfmt::skip] const KNIGHT_BONUS: [Score; 64] = [ // A B C D E F G H -0.50, -0.40, -0.30, -0.30, -0.30, -0.30, -0.40, -0.50, @@ -43,6 +50,7 @@ const KNIGHT_BONUS: [Score; 64] = [ -0.50, -0.40, -0.30, -0.30, -0.30, -0.30, -0.40, -0.50, ]; +#[rustfmt::skip] const BISHOP_BONUS: [Score; 64] = [ // A B C D E F G H -0.20, -0.10, -0.10, -0.10, -0.10, -0.10, -0.10, -0.20, @@ -55,6 +63,7 @@ const BISHOP_BONUS: [Score; 64] = [ -0.20, -0.10, -0.10, -0.10, -0.10, -0.10, -0.10, -0.20, ]; +#[rustfmt::skip] const ROOK_BONUS: [Score; 64] = [ // A B C D E F G H 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, @@ -67,6 +76,7 @@ const ROOK_BONUS: [Score; 64] = [ 0.00, 0.00, 0.00, 0.05, 0.05, 0.00, 0.00, 0.00 ]; +#[rustfmt::skip] const QUEEN_BONUS: [Score; 64] = [ // A B C D E F G H -0.20, -0.10, -0.10, -0.05, -0.05, -0.10, -0.10, -0.20, @@ -79,6 +89,7 @@ const QUEEN_BONUS: [Score; 64] = [ -0.20, -0.10, -0.10, -0.05, -0.05, -0.10, -0.10, -0.20 ]; +#[rustfmt::skip] const KING_BONUS: [Score; 64] = [ // A B C D E F G H -0.30, -0.40, -0.40, -0.50, -0.50, -0.40, -0.40, -0.30, @@ -91,6 +102,7 @@ const KING_BONUS: [Score; 64] = [ 0.20, 0.10, 0.30, -0.20, 0.00, 0.10, 0.30, 0.20 ]; +#[rustfmt::skip] const KING_BONUS_ENGAME: [Score; 64] = [ // A B C D E F G H -0.50, -0.40, -0.30, -0.20, -0.20, -0.30, -0.40, -0.50, @@ -149,39 +161,50 @@ impl Grossmeister { /// Compute total bonus for well-positioned pieces /// according to Piece-Square Tables pub fn pst_bonus(&self, color: Color, is_endgame: bool) -> f32 { - self.board.pieces_by_color(color).iter().enumerate().fold(0., |total, (piece_index, bitboard)| { - let pst = match Piece::from(piece_index).without_color() { - Piece::Pawn => if is_endgame && (bitboard & self.passer_mask(color) > 0) { - PAWN_BONUS_PASSER_ENDGAME - } else { - PAWN_BONUS - } - Piece::Knight => KNIGHT_BONUS, - Piece::Bishop => BISHOP_BONUS, - Piece::Rook => ROOK_BONUS, - Piece::Queen => QUEEN_BONUS, - Piece::King => if is_endgame { - KING_BONUS_ENGAME - } else { - KING_BONUS - } - _ => panic!("Unreachable") - }; - total + bitboard.serialize().fold(0., |acc, square| { - let pst_index = match color { - Color::White => square.mirror(), - Color::Black => square, - } as usize; - acc + pst[pst_index] - }) - }) + self.board.pieces_by_color(color).iter().enumerate().fold( + 0., + |total, (piece_index, bitboard)| { + let pst = match Piece::from(piece_index).without_color() { + Piece::Pawn => { + if is_endgame && (bitboard & self.passer_mask(color) > 0) { + PAWN_BONUS_PASSER_ENDGAME + } else { + PAWN_BONUS + } + } + Piece::Knight => KNIGHT_BONUS, + Piece::Bishop => BISHOP_BONUS, + Piece::Rook => ROOK_BONUS, + Piece::Queen => QUEEN_BONUS, + Piece::King => { + if is_endgame { + KING_BONUS_ENGAME + } else { + KING_BONUS + } + } + _ => panic!("Unreachable"), + }; + total + + bitboard.serialize().fold(0., |acc, square| { + let pst_index = match color { + Color::White => square.mirror(), + Color::Black => square, + } as usize; + acc + pst[pst_index] + }) + }, + ) } /// Count raw material of the given color pub fn material(&self, color: Color) -> f32 { - self.board.pieces_by_color(color).iter().enumerate().fold(0., |acc, (piece_index, bitboard)| { - acc + Piece::from(piece_index).static_eval() * bitboard.pop_count() as f32 - }) + self.board.pieces_by_color(color).iter().enumerate().fold( + 0., + |acc, (piece_index, bitboard)| { + acc + Piece::from(piece_index).static_eval() * bitboard.pop_count() as f32 + }, + ) } // Returns a value in [0, 240] representing how @@ -193,24 +216,27 @@ impl Grossmeister { let rook_phase = 20; let queen_phase = 40; - let total_phase = knight_phase*4 + bishop_phase*4 + rook_phase*4 + queen_phase*2; - // If you change it, make sure to update denominator in interpolation + let total_phase = knight_phase * 4 + bishop_phase * 4 + rook_phase * 4 + queen_phase * 2; + // If you change it, make sure to update denominator in interpolation debug_assert_eq!(total_phase, 240); - self.board.piece_sets.iter().enumerate().fold(0, |acc, (piece_index, &bitboard)| { - acc + match Piece::from(piece_index).without_color() { - Piece::King => 0, - Piece::Pawn => 0, - Piece::Knight => knight_phase, - Piece::Bishop => bishop_phase, - Piece::Rook => rook_phase, - Piece::Queen => queen_phase, - _ => panic!("Unreachable") - } * bitboard.pop_count() - }) + self.board + .piece_sets + .iter() + .enumerate() + .fold(0, |acc, (piece_index, &bitboard)| { + acc + match Piece::from(piece_index).without_color() { + Piece::King => 0, + Piece::Pawn => 0, + Piece::Knight => knight_phase, + Piece::Bishop => bishop_phase, + Piece::Rook => rook_phase, + Piece::Queen => queen_phase, + _ => panic!("Unreachable"), + } * bitboard.pop_count() + }) } - /// Evaluate a position relative to the current player pub fn evaluate(&self) -> Score { let color = self.board.color(); @@ -220,7 +246,7 @@ impl Grossmeister { let loseable = self.board.is_theoretically_winnable(opponent_color); if !winnable && !loseable { - return 0.0 + return 0.0; } let material_advantage = self.material(color) - self.material(opponent_color); @@ -229,22 +255,21 @@ impl Grossmeister { let pawn_islands_advantage = self.pawn_islands(opponent_color) - self.pawn_islands(color); // Middlegame eval, assumming all pieces are on the Board - let middlegame_eval = - mobility_advantage * 0.05 + - pawn_shield_advantage * 0.20 + - pawn_islands_advantage * 0.10 + - (self.pst_bonus(color, false) - self.pst_bonus(opponent_color, false)); + let middlegame_eval = mobility_advantage * 0.05 + + pawn_shield_advantage * 0.20 + + pawn_islands_advantage * 0.10 + + (self.pst_bonus(color, false) - self.pst_bonus(opponent_color, false)); // Engame eval, assuming no pieces are left on the Board (only kings and pawns) - let endgame_eval = - mobility_advantage * 0.03 + - pawn_shield_advantage * 0.05 + - pawn_islands_advantage * 0.15 + - (self.pst_bonus(color, true) - self.pst_bonus(opponent_color, true)); + let endgame_eval = mobility_advantage * 0.03 + + pawn_shield_advantage * 0.05 + + pawn_islands_advantage * 0.15 + + (self.pst_bonus(color, true) - self.pst_bonus(opponent_color, true)); // Interpolate evalutaion based on amount of pieces to remove discontinuity let phase = self.phase(); - let tapered_eval = (middlegame_eval * phase as f32 + endgame_eval * (240 - phase) as f32) / 240.; + let tapered_eval = + (middlegame_eval * phase as f32 + endgame_eval * (240 - phase) as f32) / 240.; let eval = (material_advantage + tapered_eval) .min(if winnable { INFINITY } else { 0.0 }) // Can not score > 0 if not winnable @@ -279,7 +304,9 @@ impl Grossmeister { let ep_bitboard = match self.board.ep_target { Some(square) => { let rank = square.rank(); - if (rank == 2 && color == Color::Black) || (rank == 5 && color == Color::White) { + if (rank == 2 && color == Color::Black) + || (rank == 5 && color == Color::White) + { square.to_bitboard() } else { 0 @@ -287,20 +314,30 @@ impl Grossmeister { } None => 0, }; - mobility += (self.board.attacks.pawn[color as usize][source as usize] & (opponent_occupancy | ep_bitboard)).pop_count() as f32; - mobility += (self.board.attacks.pawn_pushes[color as usize][source as usize] & empty).pop_count() as f32; + mobility += (self.board.attacks.pawn[color as usize][source as usize] + & (opponent_occupancy | ep_bitboard)) + .pop_count() as f32; + mobility += (self.board.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.board.attacks.pawn_double_pushes[color as usize][source as usize] & empty).pop_count() as f32; + mobility += (self.board.attacks.pawn_double_pushes[color as usize] + [source as usize] + & empty) + .pop_count() as f32; } } Piece::King => { for source in piece.serialize() { - mobility += (self.board.attacks.king[source as usize] & (empty | opponent_occupancy)).pop_count() as f32; + mobility += (self.board.attacks.king[source as usize] + & (empty | opponent_occupancy)) + .pop_count() as f32; // Castling let king_home_position = match color { @@ -308,56 +345,79 @@ impl Grossmeister { Color::Black => Square::E8, }; if *piece == king_home_position.to_bitboard() { - for rook_square in player_pieces[Piece::Rook as usize] - .serialize() - .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.board.is_square_attacked(*square, color.flip())); - - if all_empty && !any_checks && self.board.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.board.is_square_attacked(*square, color.flip())); - - if all_empty && !any_checks && self.board.castling_rights[color as usize][CastlingSide::King as usize] { - mobility += 1.; - } - }, - _ => {}, + for rook_square in + player_pieces[Piece::Rook as usize].serialize().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.board.is_square_attacked(*square, color.flip()) + }); + + if all_empty + && !any_checks + && self.board.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.board.is_square_attacked(*square, color.flip()) + }); + + if all_empty + && !any_checks + && self.board.castling_rights[color as usize] + [CastlingSide::King as usize] + { + mobility += 1.; + } } + _ => {} } + } } } } Piece::Knight => { for source in piece.serialize() { - mobility += (self.board.attacks.knight[source as usize] & (empty | opponent_occupancy)).pop_count() as f32; + mobility += (self.board.attacks.knight[source as usize] + & (empty | opponent_occupancy)) + .pop_count() as f32; } } Piece::Bishop => { for source in piece.serialize() { - mobility += (self.board.attacks.bishop(self.board.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32; + mobility += (self.board.attacks.bishop(self.board.occupancy, source) + & (empty | opponent_occupancy)) + .pop_count() as f32; } } Piece::Rook => { for source in piece.serialize() { - mobility += (self.board.attacks.rook(self.board.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32; + mobility += (self.board.attacks.rook(self.board.occupancy, source) + & (empty | opponent_occupancy)) + .pop_count() as f32; } } Piece::Queen => { @@ -372,7 +432,10 @@ impl Grossmeister { #[cfg(test)] mod tests { - use crate::{board::{Board, io::IO}, moves::{Move, MoveKind}}; + use crate::{ + board::{io::IO, Board}, + moves::{Move, MoveKind}, + }; use super::*; @@ -407,7 +470,6 @@ mod tests { let board = Board::new(); let gm = Grossmeister::new(board); assert_eq!(gm.material(Color::Black), gm.material(Color::White)); - } #[test] @@ -421,7 +483,11 @@ mod tests { fn king_tropism() { let board = Board::new(); let mut gm = Grossmeister::new(board); - gm.board.make_move(Move { source: Square::D1, target: Square::F5, kind: MoveKind::Quiet }); + gm.board.make_move(Move { + source: Square::D1, + target: Square::F5, + kind: MoveKind::Quiet, + }); let score = gm.evaluate(); gm.board.print(); println!("Score {}", score); @@ -482,7 +548,8 @@ mod tests { #[test] fn discourage_edge_knights() { let score1 = { - let fen = String::from("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3"); + let fen = + String::from("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3"); let board = Board::from_FEN(fen); let gm = Grossmeister::new(board); let score = gm.evaluate(); @@ -492,7 +559,8 @@ mod tests { }; let score2 = { - let fen = String::from("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/7N/PPPP1PPP/RNBQKB1R w KQkq - 2 3"); + let fen = + String::from("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/7N/PPPP1PPP/RNBQKB1R w KQkq - 2 3"); let board = Board::from_FEN(fen); let gm = Grossmeister::new(board); let score = gm.evaluate(); @@ -507,7 +575,8 @@ mod tests { #[test] fn mirrored_evaluation() { let score1 = { - let fen = String::from("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1"); + let fen = + String::from("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1"); let board = Board::from_FEN(fen); let gm = Grossmeister::new(board); let score = gm.evaluate(); @@ -517,7 +586,8 @@ mod tests { }; let score2 = { - let fen = String::from("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1 "); + let fen = + String::from("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1 "); let board = Board::from_FEN(fen); let gm = Grossmeister::new(board); let score = gm.evaluate(); diff --git a/src/grossmeister/mod.rs b/src/grossmeister/mod.rs index 1f1a141..ea73bff 100644 --- a/src/grossmeister/mod.rs +++ b/src/grossmeister/mod.rs @@ -1,15 +1,15 @@ use std::sync::{atomic::AtomicBool, Arc}; -use smallvec::{SmallVec, smallvec}; +use smallvec::{smallvec, SmallVec}; +use self::{move_selector::MoveSelector, ttable::MasterTable}; use crate::board::Board; -use self::{ttable::MasterTable, move_selector::MoveSelector}; -mod ttable; -mod evaluation; -mod search; mod UCI; +mod evaluation; mod move_selector; +mod search; +mod ttable; /// Grossmeister is a powerful entity that plays the game of Chess. /// This structure represents a player, it stores his knowledge diff --git a/src/grossmeister/move_selector.rs b/src/grossmeister/move_selector.rs index 08e5cf1..debaba5 100644 --- a/src/grossmeister/move_selector.rs +++ b/src/grossmeister/move_selector.rs @@ -1,8 +1,8 @@ use smallvec::SmallVec; -use crate::{moves::Move, board::{Board, move_generation::MoveList}}; +use crate::{board::move_generation::MoveList, moves::Move}; -use super::{Grossmeister, ttable::NodeType}; +use super::{ttable::NodeType, Grossmeister}; pub type ScoredMove = (Move, f32); pub type ScoredMoveList = SmallVec<[ScoredMove; 128]>; @@ -28,7 +28,7 @@ impl Iterator for ScoredMoveIter { } self.index += 1; - return Some(self.moves[self.index - 1]) + return Some(self.moves[self.index - 1]); } None } @@ -65,7 +65,7 @@ impl MoveGenStage { MoveGenStage::WinningOrEqualTactical => MoveGenStage::Killer, MoveGenStage::Killer => MoveGenStage::Quiet, MoveGenStage::Quiet => MoveGenStage::LosingTactical, - MoveGenStage::LosingTactical => todo!() + MoveGenStage::LosingTactical => todo!(), } } } @@ -96,17 +96,21 @@ impl Grossmeister { /// Register killer for ply-before pub fn register_killer(&mut self, killer: Move) { if self.board.ply > 1 { - let parent_killers = &mut self.move_selectors[(self.board.ply - 2) as usize].killer_moves; + let parent_killers = + &mut self.move_selectors[(self.board.ply - 2) as usize].killer_moves; match parent_killers.iter().find(|m| **m == killer) { None => { parent_killers.push(killer); - debug_assert!(!parent_killers.spilled(), "Killer move list should remain on the stack"); + debug_assert!( + !parent_killers.spilled(), + "Killer move list should remain on the stack" + ); // We want to have max 3 killers, so if we exceed the limit remove the oldest killer if parent_killers.len() > 3 { parent_killers.remove(0); } } - Some(..) => {}, + Some(..) => {} } } } @@ -117,22 +121,17 @@ impl Grossmeister { if m.is_tactical() { let [source_eval, target_eval] = [m.source, m.target] .map(|sq| self.board.piece_by_square(sq)) - .map(|p| { - match p { - Some(p) => p.static_eval(), - None => 0., - } + .map(|p| match p { + Some(p) => p.static_eval(), + None => 0., }); - return 2. * target_eval - source_eval + return 2. * target_eval - source_eval; } 0.0 } pub fn score_moves(&self, movelist: MoveList) -> ScoredMoveList { - movelist - .iter() - .map(|&m| (m, self.eval_move(m))) - .collect() + movelist.iter().map(|&m| (m, self.eval_move(m))).collect() } pub fn next_tactical(&mut self) -> Option<Move> { @@ -146,10 +145,7 @@ impl Grossmeister { } fn init_stage(&mut self, moves: ScoredMoveList) { - self.move_selector().stage_moves = ScoredMoveIter { - moves, - index: 0, - } + self.move_selector().stage_moves = ScoredMoveIter { moves, index: 0 } } /// TODO: next stage @@ -168,7 +164,7 @@ impl Grossmeister { if let Some(transposition) = self.transposition() { if transposition.node_type != NodeType::All { if let Some(mov) = transposition.mov { - return Some(mov) + return Some(mov); } } } @@ -181,8 +177,10 @@ impl Grossmeister { self.move_selector().tactical_moves = self.score_moves(moves); // But we only care about current stage now - let new_stage = - self.move_selector().tactical_moves.iter() + let new_stage = self + .move_selector() + .tactical_moves + .iter() .filter(|(_, score)| *score >= 0.0) .copied() .collect(); @@ -197,9 +195,17 @@ impl Grossmeister { } MoveGenStage::Killer => { if self.move_selector().stage_moves.moves.is_empty() { - let new_stage = self.move_selector().killer_moves.clone() + let new_stage = self + .move_selector() + .killer_moves + .clone() .iter() - .filter(|&m| self.move_selector().tactical_moves.iter().any(|(m2, _)| m2 == m)) // Test if killer is in the movelist + .filter(|&m| { + self.move_selector() + .tactical_moves + .iter() + .any(|(m2, _)| m2 == m) + }) // Test if killer is in the movelist .map(|&m| (m, 0.0)) .collect(); self.init_stage(new_stage); @@ -223,8 +229,10 @@ impl Grossmeister { } MoveGenStage::LosingTactical => { if self.move_selector().stage_moves.moves.is_empty() { - let new_stage = - self.move_selector().tactical_moves.iter() + let new_stage = self + .move_selector() + .tactical_moves + .iter() .filter(|(_, score)| *score < 0.0) .copied() .collect(); @@ -232,7 +240,7 @@ impl Grossmeister { } match self.move_selector().stage_moves.next() { Some((mov, _)) => return Some(mov), - None => return None + None => return None, } } } diff --git a/src/grossmeister/search.rs b/src/grossmeister/search.rs index 372fecc..e8cb696 100644 --- a/src/grossmeister/search.rs +++ b/src/grossmeister/search.rs @@ -1,8 +1,15 @@ use std::f32::INFINITY; -use crate::{moves::{Move, MoveKind}, board::io::IO}; +use crate::{ + board::io::IO, + moves::{Move, MoveKind}, +}; -use super::{Grossmeister, ttable::{NodeType, TranspositionTableItem}, evaluation::Score}; +use super::{ + evaluation::Score, + ttable::{NodeType, TranspositionTableItem}, + Grossmeister, +}; const SCORE_MATE: Score = 20_000.0; @@ -19,38 +26,46 @@ impl Grossmeister { // Mate distance pruning // If a non-zero value (mating score) is returned, branch could be safely pruned pub fn MDP(alpha: &mut Score, beta: &mut Score, root_distance: u8) -> Score { - { // Update bound in case we are winning + { + // Update bound in case we are winning let mating_score = SCORE_MATE - root_distance as Score; if mating_score < *beta { *beta = mating_score; if *alpha >= mating_score { - return mating_score + return mating_score; } } } - { // Update bound in case we are losing + { + // Update bound in case we are losing let mating_score = -SCORE_MATE + root_distance as Score; if mating_score > *alpha { *alpha = mating_score; if *beta <= mating_score { - return mating_score + return mating_score; } } } 0.0 } - pub fn negamax_search(&mut self, mut alpha: Score, mut beta: Score, depth_left: u8, root_distance: u8) -> Score { + pub fn negamax_search( + &mut self, + mut alpha: Score, + mut beta: Score, + depth_left: u8, + root_distance: u8, + ) -> Score { let color = self.board.color(); if self.board.threefold_repetition() { - return 0.0 + return 0.0; } // Mate distance pruning let mating_score = Grossmeister::MDP(&mut alpha, &mut beta, root_distance); if mating_score != 0.0 { - return mating_score + return mating_score; } if let Some(transposition) = self.transposition() { @@ -84,7 +99,7 @@ impl Grossmeister { }; if depth_left == 0 { - return self.quiscence(alpha, beta, root_distance) + return self.quiscence(alpha, beta, root_distance); } let mut full_window_search = true; @@ -106,7 +121,12 @@ impl Grossmeister { } else { // After we have PV-node (that raised alpha) all other nodes will be searched // with zero-window first to confirm this assumption - let score = -self.negamax_search(-(alpha + 0.01), -alpha, depth_left - 1, root_distance + 1); + let score = -self.negamax_search( + -(alpha + 0.01), + -alpha, + depth_left - 1, + root_distance + 1, + ); // In case some of the other nodes raises alpha, then it's true PV node now, // let's research with full window to find its exact value if score > alpha { @@ -115,7 +135,13 @@ impl Grossmeister { score } }; - self.board.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before, hash_before); + self.board.unmake_move( + mov, + captured_piece, + ep_target_before, + castling_rights_before, + hash_before, + ); if score >= beta { transposition.mov = Some(mov); @@ -127,7 +153,7 @@ impl Grossmeister { self.register_killer(mov); } - return beta + return beta; } if score > alpha { alpha = score; @@ -137,7 +163,13 @@ impl Grossmeister { transposition.node_type = NodeType::PV; } } else { - self.board.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before, hash_before); + self.board.unmake_move( + mov, + captured_piece, + ep_target_before, + castling_rights_before, + hash_before, + ); } // Could not finish in time, return what we have so far @@ -148,11 +180,11 @@ impl Grossmeister { if !legal_move_found { if self.board.is_king_in_check(color) { - return -SCORE_MATE + root_distance as Score + return -SCORE_MATE + root_distance as Score; } else { transposition.score = 0.0; self.store_transposition(transposition); - return 0.0 + return 0.0; } } @@ -164,13 +196,13 @@ impl Grossmeister { let color = self.board.color(); if self.board.threefold_repetition() { - return 0.0 + return 0.0; } // Mate distance pruning let mating_score = Grossmeister::MDP(&mut alpha, &mut beta, root_distance); if mating_score != 0.0 { - return mating_score + return mating_score; } if let Some(transposition) = self.transposition() { @@ -219,7 +251,11 @@ impl Grossmeister { let mut legal_move_found = false; self.cleanup_selector(); - while let Some(mov) = if tactical_only { self.next_tactical() } else { self.next_move() } { + while let Some(mov) = if tactical_only { + self.next_tactical() + } else { + self.next_move() + } { let ep_target_before = self.board.ep_target; let castling_rights_before = self.board.castling_rights; let hash_before = self.board.hash; @@ -228,7 +264,13 @@ impl Grossmeister { if !self.board.is_king_in_check(color) { legal_move_found = true; let evaluation = -self.quiscence(-beta, -alpha, root_distance + 1); - self.board.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before, hash_before); + self.board.unmake_move( + mov, + captured_piece, + ep_target_before, + castling_rights_before, + hash_before, + ); if evaluation >= beta { transposition.mov = Some(mov); @@ -249,12 +291,18 @@ impl Grossmeister { transposition.node_type = NodeType::All; } } else { - self.board.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before, hash_before); + self.board.unmake_move( + mov, + captured_piece, + ep_target_before, + castling_rights_before, + hash_before, + ); } } if !legal_move_found && self.board.is_king_in_check(color) { - return -SCORE_MATE + root_distance as Score + return -SCORE_MATE + root_distance as Score; } self.store_transposition(transposition); @@ -282,8 +330,13 @@ impl Grossmeister { let mut subtree_pv = self.collect_pv(depth - 1); pv.append(&mut subtree_pv); - self.board.unmake_move(mov, captured, ep_target_before, castling_rights_before, hash_before); - + self.board.unmake_move( + mov, + captured, + ep_target_before, + castling_rights_before, + hash_before, + ); } } else { println!("No PV node found for hash {}", self.board.hash); @@ -304,11 +357,12 @@ impl Grossmeister { let mut upperbound = INFINITY; while lowerbound < upperbound { - let beta = score + if score == lowerbound { - window_size - } else { - 0.0 - }; + let beta = score + + if score == lowerbound { + window_size + } else { + 0.0 + }; score = self.negamax_search(beta - window_size, beta, depth, 0); @@ -340,12 +394,14 @@ impl Grossmeister { // Print UCI info print!("info depth {} score ", depth); - if score.abs() >= SCORE_MATE - 128f32 { // TODO: VALUE_WIN - MAX_PLY - let mate_distance = (SCORE_MATE - score.abs()) * if score > 0.0 { - 1. - } else { - -1. // -N means we are mated in N plies - }; + if score.abs() >= SCORE_MATE - 128f32 { + // TODO: VALUE_WIN - MAX_PLY + let mate_distance = (SCORE_MATE - score.abs()) + * if score > 0.0 { + 1. + } else { + -1. // -N means we are mated in N plies + }; print!("mate {:.0} ", (mate_distance / 2.0).ceil()); } else { print!("cp {:.0} ", score * 100.0) @@ -430,35 +486,92 @@ impl Grossmeister { result.checks += subtree_result.checks; result.castles += subtree_result.castles; result.en_passants += subtree_result.en_passants; - } - self.board.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before, hash_before); + self.board.unmake_move( + mov, + captured_piece, + ep_target_before, + castling_rights_before, + hash_before, + ); } if print { - println!("Found {} leaf nodes in this subtree (depth {})", result.leaf_nodes, depth); + println!( + "Found {} leaf nodes in this subtree (depth {})", + result.leaf_nodes, depth + ); } result } } - #[cfg(test)] mod tests { - use crate::{board::{Board, io::IO}, square::Square, moves::{Move, MoveKind}, grossmeister::{Grossmeister, search::PerftResult}}; use super::SCORE_MATE; + use crate::{ + board::{io::IO, Board}, + grossmeister::{search::PerftResult, Grossmeister}, + moves::{Move, MoveKind}, + square::Square, + }; #[test] fn perft() { let board = Board::new(); let mut gm = Grossmeister::new(board); - assert_eq!(gm.perft(0, false), PerftResult { leaf_nodes: 1, captures: 0, en_passants: 0, castles: 0 , checks: 0 }); - assert_eq!(gm.perft(1, false), PerftResult { leaf_nodes: 20, captures: 0, en_passants: 0, castles: 0 , checks: 0 }); - assert_eq!(gm.perft(2, false), PerftResult { leaf_nodes: 400, captures: 0, en_passants: 0, castles: 0 , checks: 0 }); - assert_eq!(gm.perft(3, false), PerftResult { leaf_nodes: 8902, captures: 34, en_passants: 0, castles: 0 , checks: 12 }); - assert_eq!(gm.perft(4, false), PerftResult { leaf_nodes: 197281, captures: 1576, en_passants: 0, castles: 0 , checks: 469 }); + assert_eq!( + gm.perft(0, false), + PerftResult { + leaf_nodes: 1, + captures: 0, + en_passants: 0, + castles: 0, + checks: 0 + } + ); + assert_eq!( + gm.perft(1, false), + PerftResult { + leaf_nodes: 20, + captures: 0, + en_passants: 0, + castles: 0, + checks: 0 + } + ); + assert_eq!( + gm.perft(2, false), + PerftResult { + leaf_nodes: 400, + captures: 0, + en_passants: 0, + castles: 0, + checks: 0 + } + ); + assert_eq!( + gm.perft(3, false), + PerftResult { + leaf_nodes: 8902, + captures: 34, + en_passants: 0, + castles: 0, + checks: 12 + } + ); + assert_eq!( + gm.perft(4, false), + PerftResult { + leaf_nodes: 197281, + captures: 1576, + en_passants: 0, + castles: 0, + checks: 469 + } + ); // assert_eq!(board.perft(5, false), PerftResult { leaf_nodes: 4865609, captures: 82719, en_passants: 258, castles: 0, checks: 27351 }); } @@ -468,10 +581,46 @@ mod tests { let board = Board::from_FEN(fen); let mut gm = Grossmeister::new(board); - assert_eq!(gm.perft(0, false), PerftResult { leaf_nodes: 1, captures: 0, en_passants: 0, castles: 0 , checks: 0 }); - assert_eq!(gm.perft(1, false), PerftResult { leaf_nodes: 48, captures: 8, en_passants: 0, castles: 2 , checks: 0 }); - assert_eq!(gm.perft(2, false), PerftResult { leaf_nodes: 2039, captures: 351, en_passants: 1, castles: 91 , checks: 3 }); - assert_eq!(gm.perft(3, false), PerftResult { leaf_nodes: 97862, captures: 17102, en_passants: 45, castles: 3162, checks: 993 }); + assert_eq!( + gm.perft(0, false), + PerftResult { + leaf_nodes: 1, + captures: 0, + en_passants: 0, + castles: 0, + checks: 0 + } + ); + assert_eq!( + gm.perft(1, false), + PerftResult { + leaf_nodes: 48, + captures: 8, + en_passants: 0, + castles: 2, + checks: 0 + } + ); + assert_eq!( + gm.perft(2, false), + PerftResult { + leaf_nodes: 2039, + captures: 351, + en_passants: 1, + castles: 91, + checks: 3 + } + ); + assert_eq!( + gm.perft(3, false), + PerftResult { + leaf_nodes: 97862, + captures: 17102, + en_passants: 45, + castles: 3162, + checks: 993 + } + ); // assert_eq!(board.perft(4, false), PerftResult { leaf_nodes: 4085603, captures: 757163, en_passants: 1929, castles: 128013, checks: 25523 }); } @@ -481,8 +630,26 @@ mod tests { let board = Board::from_FEN(fen); let mut gm = Grossmeister::new(board); - assert_eq!(gm.perft(1, false), PerftResult { leaf_nodes: 14, captures: 1, en_passants: 0, castles: 0 , checks: 2 }); - assert_eq!(gm.perft(2, false), PerftResult { leaf_nodes: 191, captures: 14, en_passants: 0, castles: 0 , checks: 10 }); + assert_eq!( + gm.perft(1, false), + PerftResult { + leaf_nodes: 14, + captures: 1, + en_passants: 0, + castles: 0, + checks: 2 + } + ); + assert_eq!( + gm.perft(2, false), + PerftResult { + leaf_nodes: 191, + captures: 14, + en_passants: 0, + castles: 0, + checks: 10 + } + ); // assert_eq!(board.perft(3, false), PerftResult { leaf_nodes: 2812, captures: 209, en_passants: 2, castles: 0 , checks: 267 }); } @@ -494,11 +661,26 @@ mod tests { let (score, pv) = gm.iterative_deepening(8); assert_eq!(score, SCORE_MATE - 3.0); - assert_eq!(pv, vec![ - Move { source: Square::F3, target: Square::C6, kind: MoveKind::Capture }, - Move { source: Square::B7, target: Square::C6, kind: MoveKind::Capture }, - Move { source: Square::D3, target: Square::A6, kind: MoveKind::Quiet }, - ]); + assert_eq!( + pv, + vec![ + Move { + source: Square::F3, + target: Square::C6, + kind: MoveKind::Capture + }, + Move { + source: Square::B7, + target: Square::C6, + kind: MoveKind::Capture + }, + Move { + source: Square::D3, + target: Square::A6, + kind: MoveKind::Quiet + }, + ] + ); } #[test] @@ -510,21 +692,30 @@ mod tests { let (_, pv) = gm.iterative_deepening(6); assert_eq!( pv[0], - Move { source: Square::F5, target: Square::H4, kind: MoveKind::Quiet }, + Move { + source: Square::F5, + target: Square::H4, + kind: MoveKind::Quiet + }, "You should save this poor knight from danger!" ); } #[test] fn weird_bishop_sac() { - let fen = String::from("r1b1k1nr/p4pp1/1pp1p3/4n2p/1b1qP3/1B1P3N/PPPBQPPP/RN2K2R w KQkq - 7 10"); + let fen = + String::from("r1b1k1nr/p4pp1/1pp1p3/4n2p/1b1qP3/1B1P3N/PPPBQPPP/RN2K2R w KQkq - 7 10"); let board = Board::from_FEN(fen); let mut gm = Grossmeister::new(board); let (_, pv) = gm.iterative_deepening(5); assert_eq!( pv[0], - Move { source: Square::C2, target: Square::C3, kind: MoveKind::Quiet }, + Move { + source: Square::C2, + target: Square::C3, + kind: MoveKind::Quiet + }, "You should fork this bastard!" ); } diff --git a/src/grossmeister/ttable.rs b/src/grossmeister/ttable.rs index b40bac1..162e4f5 100644 --- a/src/grossmeister/ttable.rs +++ b/src/grossmeister/ttable.rs @@ -33,7 +33,7 @@ struct TranspositionTable { impl Default for TranspositionTable { fn default() -> Self { Self { - table: vec![None; TTABLE_SIZE as usize] + table: vec![None; TTABLE_SIZE as usize], } } } @@ -46,13 +46,9 @@ impl TranspositionTable { /// This operation is safe from collisions since it compares the *full* hash /// TODO: only compare the other half of the hash fn get(&self, hash: u64) -> Option<&TranspositionTableItem> { - self.table[(hash % TTABLE_SIZE) as usize].as_ref().and_then(|item| { - if item.hash == hash { - Some(item) - } else { - None - } - }) + self.table[(hash % TTABLE_SIZE) as usize] + .as_ref() + .and_then(|item| if item.hash == hash { Some(item) } else { None }) } fn len(&self) -> usize { @@ -72,33 +68,43 @@ pub struct MasterTable { impl Grossmeister { pub fn transposition(&self) -> Option<&TranspositionTableItem> { - self.transposition_table.depth_preferred.get(self.board.hash) + self.transposition_table + .depth_preferred + .get(self.board.hash) .or(self.transposition_table.always_replace.get(self.board.hash)) } pub fn store_transposition(&mut self, transposition: TranspositionTableItem) { - self.transposition_table.always_replace.set(self.board.hash, transposition); - - if match self.transposition_table.depth_preferred.get(self.board.hash) { + self.transposition_table + .always_replace + .set(self.board.hash, transposition); + + if match self + .transposition_table + .depth_preferred + .get(self.board.hash) + { Some(existing_transposition) => transposition.depth >= existing_transposition.depth, - None => true + None => true, } { - self.transposition_table.depth_preferred.set(self.board.hash, transposition) + self.transposition_table + .depth_preferred + .set(self.board.hash, transposition) } // Store PV/Cut nodes in PV table // Note: Cut nodes are probably only relevant in close-to-mate situations if match transposition.node_type { NodeType::PV => true, // Always replace PV nodes - NodeType::Cut => { - match self.transposition_table.pv.get(self.board.hash) { - Some(existing_transposition) => existing_transposition.node_type != NodeType::PV, - None => true, - } - } + NodeType::Cut => match self.transposition_table.pv.get(self.board.hash) { + Some(existing_transposition) => existing_transposition.node_type != NodeType::PV, + None => true, + }, _ => false, } { - self.transposition_table.pv.set(self.board.hash, transposition) + self.transposition_table + .pv + .set(self.board.hash, transposition) } } @@ -108,7 +114,8 @@ impl Grossmeister { } pub fn table_full(&self) -> u64 { - let total_entries = self.transposition_table.always_replace.len() + self.transposition_table.depth_preferred.len(); + let total_entries = self.transposition_table.always_replace.len() + + self.transposition_table.depth_preferred.len(); let total_size = TTABLE_SIZE * 2; (1000.0 * (total_entries as f64 / total_size as f64)) as u64 } |