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 | |
parent | 299c6d6dee96a50f9366955192f922d449d11f20 (diff) | |
download | chessnost-canary.tar.gz |
chore: autoformat codecanary
Use #[rustfmt:skip] to preserve aligned blocks
-rw-r--r-- | src/anomaly-searcher.rs | 17 | ||||
-rw-r--r-- | src/attacks.rs | 236 | ||||
-rw-r--r-- | src/bitboard.rs | 18 | ||||
-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 | ||||
-rw-r--r-- | src/lib.rs | 6 | ||||
-rw-r--r-- | src/moves.rs | 41 | ||||
-rw-r--r-- | src/square.rs | 41 |
12 files changed, 781 insertions, 408 deletions
diff --git a/src/anomaly-searcher.rs b/src/anomaly-searcher.rs index 6b5cc15..fa5ec37 100644 --- a/src/anomaly-searcher.rs +++ b/src/anomaly-searcher.rs @@ -1,7 +1,6 @@ use std::env; use std::fs::File; use std::io::{self, BufRead, Write}; -use std::path::Path; use std::process::{Command, Stdio}; fn read_game_log(log_file: &str) -> Option<Vec<String>> { @@ -29,8 +28,16 @@ fn main() { .spawn() .expect("Failed to start the engine process"); - let mut engine_stdin = engine_process.stdin.take().expect("Failed to open engine STDIN"); - let mut engine_stdout = io::BufReader::new(engine_process.stdout.take().expect("Failed to open engine STDOUT")); + let mut engine_stdin = engine_process + .stdin + .take() + .expect("Failed to open engine STDIN"); + let mut engine_stdout = io::BufReader::new( + engine_process + .stdout + .take() + .expect("Failed to open engine STDOUT"), + ); for line in lines.iter().filter(|l| !l.contains("info string")) { if line.starts_with("<<") { @@ -42,7 +49,9 @@ fn main() { let mut response = String::new(); while response.is_empty() || response.contains("info string") { response = String::new(); - engine_stdout.read_line(&mut response).expect("Failed to read from engine STDOUT"); + engine_stdout + .read_line(&mut response) + .expect("Failed to read from engine STDOUT"); } // Print the engine's response diff --git a/src/attacks.rs b/src/attacks.rs index f666bae..bec2b62 100644 --- a/src/attacks.rs +++ b/src/attacks.rs @@ -1,15 +1,15 @@ -use crate::{bitboard::Bitboard, square::Square, board::color::Color}; +use crate::{bitboard::Bitboard, board::color::Color, square::Square}; static NOT_A_FILE: Bitboard = 0xFEFEFEFEFEFEFEFE; static NOT_B_FILE: Bitboard = 0xFDFDFDFDFDFDFDFD; static NOT_G_FILE: Bitboard = 0xBFBFBFBFBFBFBFBF; static NOT_H_FILE: Bitboard = 0x7F7F7F7F7F7F7F7F; -static B_FILE: Bitboard = 0x0202020202020202; -static H_FILE: Bitboard = 0x8080808080808080; +static B_FILE: Bitboard = 0x0202020202020202; +static H_FILE: Bitboard = 0x8080808080808080; static DIAG_C2_H7: Bitboard = 0x0080402010080400; static DIAG_A1_H8: Bitboard = 0x8040201008040201; -static RANK_2: Bitboard = 0x000000000000FF00; -static RANK_7: Bitboard = 0x00FF000000000000; +static RANK_2: Bitboard = 0x000000000000FF00; +static RANK_7: Bitboard = 0x00FF000000000000; /// An array where N-th item is an attack bitboard /// of a piece on N-th square @@ -80,8 +80,10 @@ impl Attacks { let mut attacks = [[0; 64]; 2]; for index in 0..64 { let square = 1u64 << index; - attacks[Color::White as usize][index] = ((square & NOT_A_FILE) << 7) | ((square & NOT_H_FILE) << 9); - attacks[Color::Black as usize][index] = ((square & NOT_A_FILE) >> 9) | ((square & NOT_H_FILE) >> 7); + attacks[Color::White as usize][index] = + ((square & NOT_A_FILE) << 7) | ((square & NOT_H_FILE) << 9); + attacks[Color::Black as usize][index] = + ((square & NOT_A_FILE) >> 9) | ((square & NOT_H_FILE) >> 7); } attacks } @@ -92,10 +94,10 @@ impl Attacks { for index in 0..64 { let square = 1u64 << index; - pushes [Color::White as usize][index] = (square << 8); + pushes[Color::White as usize][index] = (square << 8); double_pushes[Color::White as usize][index] = ((square & RANK_2) << 16); - pushes [Color::Black as usize][index] = (square >> 8); + pushes[Color::Black as usize][index] = (square >> 8); double_pushes[Color::Black as usize][index] = ((square & RANK_7) >> 16); } (pushes, double_pushes) @@ -106,15 +108,14 @@ impl Attacks { for (index, bitboard) in attacks.iter_mut().enumerate() { let square_bb = Square::from(index as u8).to_bitboard(); - *bitboard = - ((square_bb & NOT_A_FILE & NOT_B_FILE) << 6) | - ((square_bb & NOT_G_FILE & NOT_H_FILE) << 10) | - ((square_bb & NOT_A_FILE) << 15) | - ((square_bb & NOT_H_FILE) << 17) | - ((square_bb & NOT_G_FILE & NOT_H_FILE) >> 6) | - ((square_bb & NOT_A_FILE & NOT_B_FILE) >> 10) | - ((square_bb & NOT_H_FILE) >> 15) | - ((square_bb & NOT_A_FILE) >> 17); + *bitboard = ((square_bb & NOT_A_FILE & NOT_B_FILE) << 6) + | ((square_bb & NOT_G_FILE & NOT_H_FILE) << 10) + | ((square_bb & NOT_A_FILE) << 15) + | ((square_bb & NOT_H_FILE) << 17) + | ((square_bb & NOT_G_FILE & NOT_H_FILE) >> 6) + | ((square_bb & NOT_A_FILE & NOT_B_FILE) >> 10) + | ((square_bb & NOT_H_FILE) >> 15) + | ((square_bb & NOT_A_FILE) >> 17); } attacks } @@ -124,20 +125,18 @@ impl Attacks { for (index, bitboard) in attacks.iter_mut().enumerate() { let square_bb = Square::from(index as u8).to_bitboard(); - *bitboard = - ((square_bb & NOT_A_FILE) >> 1) | - ((square_bb & NOT_A_FILE) << 7) | - ((square_bb & NOT_A_FILE) >> 9) | - ((square_bb & NOT_H_FILE) << 1) | - ((square_bb & NOT_H_FILE) >> 7) | - ((square_bb & NOT_H_FILE) << 9) | - (square_bb << 8) | - (square_bb >> 8); + *bitboard = ((square_bb & NOT_A_FILE) >> 1) + | ((square_bb & NOT_A_FILE) << 7) + | ((square_bb & NOT_A_FILE) >> 9) + | ((square_bb & NOT_H_FILE) << 1) + | ((square_bb & NOT_H_FILE) >> 7) + | ((square_bb & NOT_H_FILE) << 9) + | (square_bb << 8) + | (square_bb >> 8); } attacks } - // Compute rook-like attacks on the first rank based on // occupancy and rook file. fn precompute_first_rank_attacks() -> FirstRankAttacks { @@ -164,7 +163,7 @@ impl Attacks { } } - for index in left_bound..right_bound+1 { + for index in left_bound..right_bound + 1 { if index != file { attacks[occupancy as usize][file as usize] |= 1 << index; } @@ -260,11 +259,17 @@ impl Attacks { /// /// Given a square and occupancy masked for rank, diagonal or anti-diagonal (note: not a file!) /// return an attack bitboard that considers blocking pieces - fn kindergarten_attacks_base(&self, occupancy: Bitboard, mask: Bitboard, square: Square) -> Bitboard { + fn kindergarten_attacks_base( + &self, + occupancy: Bitboard, + mask: Bitboard, + square: Square, + ) -> Bitboard { let file = square.file(); let masked_occupancy = occupancy & mask; let occupancy_rank = ((masked_occupancy as u128 * B_FILE as u128) >> 58 & 0b111111) << 1; - let rank_attacks = self.first_rank_attacks[occupancy_rank as usize][file as usize] as Bitboard; + let rank_attacks = + self.first_rank_attacks[occupancy_rank as usize][file as usize] as Bitboard; let mut filled_up_attacks = 0; for rank in 0..8 { @@ -274,38 +279,43 @@ impl Attacks { } /// https://www.chessprogramming.org/Kindergarten_Bitboards - fn kindergarten_attacks_file(&self, occupancy: Bitboard, mask: Bitboard, square: Square) -> Bitboard { + fn kindergarten_attacks_file( + &self, + occupancy: Bitboard, + mask: Bitboard, + square: Square, + ) -> Bitboard { let file = square.file(); let rank = square.rank(); let masked_occupancy = (occupancy & mask) >> file; // Shift occupancy to A file - let occupancy_rank = ((masked_occupancy as u128 * DIAG_C2_H7 as u128) >> 58 & 0b111111) << 1; + let occupancy_rank = + ((masked_occupancy as u128 * DIAG_C2_H7 as u128) >> 58 & 0b111111) << 1; // Use reversed rank as index, since occupancy is reversed - let rank_attacks = self.first_rank_attacks[occupancy_rank as usize][7 - rank as usize] as Bitboard; + let rank_attacks = + self.first_rank_attacks[occupancy_rank as usize][7 - rank as usize] as Bitboard; ((rank_attacks as u128 * DIAG_A1_H8 as u128) as Bitboard & H_FILE) >> (7 - file) } pub fn bishop(&self, occupancy: Bitboard, square: Square) -> Bitboard { - let diagonal_mask = - self.ray_attacks[Direction::NoEa as usize][square as usize] | - self.ray_attacks[Direction::SoWe as usize][square as usize]; - let anti_diagonal_mask = - self.ray_attacks[Direction::NoWe as usize][square as usize] | - self.ray_attacks[Direction::SoEa as usize][square as usize]; - - self.kindergarten_attacks_base(occupancy, diagonal_mask, square) | self.kindergarten_attacks_base(occupancy, anti_diagonal_mask, square) + let diagonal_mask = self.ray_attacks[Direction::NoEa as usize][square as usize] + | self.ray_attacks[Direction::SoWe as usize][square as usize]; + let anti_diagonal_mask = self.ray_attacks[Direction::NoWe as usize][square as usize] + | self.ray_attacks[Direction::SoEa as usize][square as usize]; + + self.kindergarten_attacks_base(occupancy, diagonal_mask, square) + | self.kindergarten_attacks_base(occupancy, anti_diagonal_mask, square) } pub fn rook(&self, occupancy: Bitboard, square: Square) -> Bitboard { - let vertical = - self.ray_attacks[Direction::Nort as usize][square as usize] | - self.ray_attacks[Direction::Sout as usize][square as usize]; - let horizontal = - self.ray_attacks[Direction::West as usize][square as usize] | - self.ray_attacks[Direction::East as usize][square as usize]; - - self.kindergarten_attacks_file(occupancy, vertical, square) | self.kindergarten_attacks_base(occupancy, horizontal, square) + let vertical = self.ray_attacks[Direction::Nort as usize][square as usize] + | self.ray_attacks[Direction::Sout as usize][square as usize]; + let horizontal = self.ray_attacks[Direction::West as usize][square as usize] + | self.ray_attacks[Direction::East as usize][square as usize]; + + self.kindergarten_attacks_file(occupancy, vertical, square) + | self.kindergarten_attacks_base(occupancy, horizontal, square) } pub fn queen(&self, occupancy: Bitboard, square: Square) -> Bitboard { @@ -319,11 +329,10 @@ mod tests { use super::*; - static DEFAULT_OCCUPANCY: Bitboard = - 1 << Square::B7 as usize | - 1 << Square::B1 as usize | - 1 << Square::C2 as usize | - 1 << Square::F3 as usize; + static DEFAULT_OCCUPANCY: Bitboard = 1 << Square::B7 as usize + | 1 << Square::B1 as usize + | 1 << Square::C2 as usize + | 1 << Square::F3 as usize; #[test] fn pawn_attacks() { @@ -333,27 +342,60 @@ mod tests { white_attacks.print("Pawn e4"); - assert_eq!(white_attacks, Square::D5.to_bitboard() | Square::F5.to_bitboard()); - - assert_eq!(attacks[Color::White as usize][Square::H4 as usize], Square::G5.to_bitboard()); - assert_eq!(attacks[Color::White as usize][Square::A4 as usize], Square::B5.to_bitboard()); - - assert_eq!(attacks[Color::White as usize][Square::E8 as usize].pop_count(), 0); - assert_eq!(attacks[Color::Black as usize][Square::E1 as usize].pop_count(), 0); + assert_eq!( + white_attacks, + Square::D5.to_bitboard() | Square::F5.to_bitboard() + ); + + assert_eq!( + attacks[Color::White as usize][Square::H4 as usize], + Square::G5.to_bitboard() + ); + assert_eq!( + attacks[Color::White as usize][Square::A4 as usize], + Square::B5.to_bitboard() + ); + + assert_eq!( + attacks[Color::White as usize][Square::E8 as usize].pop_count(), + 0 + ); + assert_eq!( + attacks[Color::Black as usize][Square::E1 as usize].pop_count(), + 0 + ); } #[test] fn pawn_pushes() { let (pushes, double_pushes) = Attacks::precompute_pawn_pushes(); - assert_eq!(pushes[Color::White as usize][Square::E4 as usize], Square::E5.to_bitboard()); - assert_eq!(pushes[Color::White as usize][Square::A2 as usize], Square::A3.to_bitboard()); - assert_eq!(pushes[Color::Black as usize][Square::E4 as usize], Square::E3.to_bitboard()); - assert_eq!(pushes[Color::Black as usize][Square::H6 as usize], Square::H5.to_bitboard()); + assert_eq!( + pushes[Color::White as usize][Square::E4 as usize], + Square::E5.to_bitboard() + ); + assert_eq!( + pushes[Color::White as usize][Square::A2 as usize], + Square::A3.to_bitboard() + ); + assert_eq!( + pushes[Color::Black as usize][Square::E4 as usize], + Square::E3.to_bitboard() + ); + assert_eq!( + pushes[Color::Black as usize][Square::H6 as usize], + Square::H5.to_bitboard() + ); assert_eq!(double_pushes[Color::White as usize][Square::E4 as usize], 0); - assert_eq!(double_pushes[Color::White as usize][Square::A2 as usize], Square::A4.to_bitboard()); + assert_eq!( + double_pushes[Color::White as usize][Square::A2 as usize], + Square::A4.to_bitboard() + ); assert_eq!(double_pushes[Color::Black as usize][Square::E4 as usize], 0); - assert_eq!(double_pushes[Color::Black as usize][Square::H7 as usize], Square::H5.to_bitboard()); + assert_eq!( + double_pushes[Color::Black as usize][Square::H7 as usize], + Square::H5.to_bitboard() + ); } #[test] @@ -403,12 +445,30 @@ mod tests { fn first_rank_attacks() { let attacks = Attacks::precompute_first_rank_attacks(); // HGFEDCBA HGFEDCBA - assert_eq!(attacks[0b00010000][4], 0b11101111, "If rook is the only piece on a rank, it should be able to attack all rank"); - assert_eq!(attacks[0b00000000][4], 0b11101111, "Even with 0 occupancy rook should be able to attack all rank"); - assert_eq!(attacks[0b00000000][0], 0b11111110, "Even with 0 occupancy rook should be able to attack all rank"); - assert_eq!(attacks[0b00010001][4], 0b11101111, "If only other piece is on A rank, rook should be able to attack all rank"); - assert_eq!(attacks[0b10010000][4], 0b11101111, "If only other piece is on H rank, rook should be able to attack all rank"); - assert_eq!(attacks[0b10010001][4], 0b11101111, "If only other pieces are on A and H ranks, rook should be able to attack all rank"); + assert_eq!( + attacks[0b00010000][4], 0b11101111, + "If rook is the only piece on a rank, it should be able to attack all rank" + ); + assert_eq!( + attacks[0b00000000][4], 0b11101111, + "Even with 0 occupancy rook should be able to attack all rank" + ); + assert_eq!( + attacks[0b00000000][0], 0b11111110, + "Even with 0 occupancy rook should be able to attack all rank" + ); + assert_eq!( + attacks[0b00010001][4], 0b11101111, + "If only other piece is on A rank, rook should be able to attack all rank" + ); + assert_eq!( + attacks[0b10010000][4], 0b11101111, + "If only other piece is on H rank, rook should be able to attack all rank" + ); + assert_eq!( + attacks[0b10010001][4], 0b11101111, + "If only other pieces are on A and H ranks, rook should be able to attack all rank" + ); assert_eq!(attacks[0b00010100][4], 0b11101100); assert_eq!(attacks[0b01010100][4], 0b01101100); assert_eq!(attacks[0b01010010][4], 0b01101110); @@ -420,15 +480,14 @@ mod tests { fn ray_attacks() { let attacks = Attacks::precompute_ray_attacks(); let square = Square::E4 as usize; - let bitboard = - attacks[0][square] | - attacks[1][square] | - attacks[2][square] | - attacks[3][square] | - attacks[4][square] | - attacks[5][square] | - attacks[6][square] | - attacks[7][square]; + let bitboard = attacks[0][square] + | attacks[1][square] + | attacks[2][square] + | attacks[3][square] + | attacks[4][square] + | attacks[5][square] + | attacks[6][square] + | attacks[7][square]; bitboard.print("Rays from e4"); } @@ -454,12 +513,11 @@ mod tests { fn rook_attacks() { let attacks = Attacks::new(); let square = Square::E4; - let occupancy = - Square::B7.to_bitboard() | - Square::B1.to_bitboard() | - Square::C2.to_bitboard() | - Square::E3.to_bitboard() | - Square::F3.to_bitboard(); + let occupancy = Square::B7.to_bitboard() + | Square::B1.to_bitboard() + | Square::C2.to_bitboard() + | Square::E3.to_bitboard() + | Square::F3.to_bitboard(); let bb = attacks.rook(occupancy, square); occupancy.print("Occupancy"); diff --git a/src/bitboard.rs b/src/bitboard.rs index 48a60d8..ab029bc 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -43,18 +43,12 @@ pub trait BitboardFns { fn nort_fill(self) -> Self; /// Return bitboard with a sout fill fn sout_fill(self) -> Self; - } const DE_BRUJIN_SEQUENCE: [u8; 64] = [ - 0, 1, 48, 2, 57, 49, 28, 3, - 61, 58, 50, 42, 38, 29, 17, 4, - 62, 55, 59, 36, 53, 51, 43, 22, - 45, 39, 33, 30, 24, 18, 12, 5, - 63, 47, 56, 27, 60, 41, 37, 16, - 54, 35, 52, 21, 44, 32, 23, 11, - 46, 26, 40, 15, 34, 20, 31, 10, - 25, 14, 19, 9, 13, 8, 7, 6 + 0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4, 62, 55, 59, 36, 53, 51, 43, 22, 45, + 39, 33, 30, 24, 18, 12, 5, 63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11, 46, + 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6, ]; static NOT_A_FILE: Bitboard = 0xFEFEFEFEFEFEFEFE; @@ -85,15 +79,13 @@ impl BitboardFns for Bitboard { count } - fn ls1b(self) -> Self { if self == 0 { - return 0 + return 0; } self & !(self - 1) } - fn bitscan(self) -> Square { // TODO: generate private De Brujin routine debug_assert!(self != 0, "Can not bitscan empty bitboard"); @@ -146,7 +138,7 @@ impl BitboardFns for Bitboard { } } -pub struct SquareIterator (Bitboard); +pub struct SquareIterator(Bitboard); impl Iterator for SquareIterator { type Item = Square; 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 } @@ -1,7 +1,7 @@ -pub mod square; +pub mod attacks; pub mod bitboard; pub mod board; pub mod clock; -pub mod attacks; -pub mod moves; pub mod grossmeister; +pub mod moves; +pub mod square; diff --git a/src/moves.rs b/src/moves.rs index b338afe..5dc769c 100644 --- a/src/moves.rs +++ b/src/moves.rs @@ -1,6 +1,6 @@ -use std::{str::Chars, fmt::Display}; +use std::{fmt::Display, str::Chars}; -use crate::{square::Square, bitboard::BitboardFns, board::piece::Piece}; +use crate::{bitboard::BitboardFns, board::piece::Piece, square::Square}; #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum MoveKind { @@ -27,7 +27,10 @@ impl Move { /// Tactical move is a move that changes material score pub fn is_tactical(&self) -> bool { - matches!(self.kind, MoveKind::Capture | MoveKind::EnPassant | MoveKind::Promotion(_)) + matches!( + self.kind, + MoveKind::Capture | MoveKind::EnPassant | MoveKind::Promotion(_) + ) } pub fn from_notation(mut notation: Chars) -> Self { @@ -40,27 +43,33 @@ impl Move { 'q' => Piece::Queen, 'b' => Piece::Bishop, 'n' => Piece::Knight, - _ => panic!("Illegal promotion piece: {:?}", promotion) + _ => panic!("Illegal promotion piece: {:?}", promotion), }; - return Move { source, target, kind: MoveKind::Promotion(piece) } + return Move { + source, + target, + kind: MoveKind::Promotion(piece), + }; + } + Move { + source, + target, + kind: MoveKind::Quiet, } - Move { source, target, kind: MoveKind::Quiet } } } impl Display for Move { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let promotion_postfix = match self.kind { - MoveKind::Promotion(piece) => { - match piece.without_color() { - Piece::Rook => "r", - Piece::Queen => "q", - Piece::Bishop => "b", - Piece::Knight => "n", - _ => panic!("Illegal promotion piece: {:?}", piece) - } - } - _ => "" + MoveKind::Promotion(piece) => match piece.without_color() { + Piece::Rook => "r", + Piece::Queen => "q", + Piece::Bishop => "b", + Piece::Knight => "n", + _ => panic!("Illegal promotion piece: {:?}", piece), + }, + _ => "", }; write!(f, "{}{}{}", self.source, self.target, promotion_postfix) } diff --git a/src/square.rs b/src/square.rs index c7f185e..f94f065 100644 --- a/src/square.rs +++ b/src/square.rs @@ -1,4 +1,4 @@ -use std::{str::Chars, fmt::Display}; +use std::{fmt::Display, str::Chars}; use crate::bitboard::Bitboard; @@ -6,6 +6,7 @@ use crate::bitboard::Bitboard; #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] #[repr(u8)] +#[rustfmt::skip] pub enum Square { #[default] A1, B1, C1, D1, E1, F1, G1, H1, @@ -54,29 +55,25 @@ impl Square { pub fn from_notation(chars: &mut Chars) -> Result<Self, String> { let file = match chars.next() { - Some(ch) => { - match ch { - 'a' => 0, - 'b' => 1, - 'c' => 2, - 'd' => 3, - 'e' => 4, - 'f' => 5, - 'g' => 6, - 'h' => 7, - _ => return Err(String::from("Incorrect file!")) - } + Some(ch) => match ch { + 'a' => 0, + 'b' => 1, + 'c' => 2, + 'd' => 3, + 'e' => 4, + 'f' => 5, + 'g' => 6, + 'h' => 7, + _ => return Err(String::from("Incorrect file!")), }, - None => return Err(String::from("Missing file")) + None => return Err(String::from("Missing file")), }; let rank = match chars.next() { - Some(ch) => { - match ch.to_digit(10) { - Some(digit) => digit - 1, - None => return Err(String::from("Incorrect rank")) - } - } - None => return Err(String::from("Missing rank")) + Some(ch) => match ch.to_digit(10) { + Some(digit) => digit - 1, + None => return Err(String::from("Incorrect rank")), + }, + None => return Err(String::from("Missing rank")), }; Ok(Self::from_coords(rank as u8, file)) @@ -97,5 +94,3 @@ impl Display for Square { write!(f, "{}", notation) } } - - |