aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreug-vs <eugene@eug-vs.xyz>2024-01-25 11:24:36 +0100
committereug-vs <eugene@eug-vs.xyz>2024-01-25 11:24:36 +0100
commit746e3bf17463a377b6c54b291ebef9a736d6ceb7 (patch)
treea4e965669871084b98d3ce89ac95fa9d50131699
parent299c6d6dee96a50f9366955192f922d449d11f20 (diff)
downloadchessnost-canary.tar.gz
chore: autoformat codecanary
Use #[rustfmt:skip] to preserve aligned blocks
-rw-r--r--src/anomaly-searcher.rs17
-rw-r--r--src/attacks.rs236
-rw-r--r--src/bitboard.rs18
-rw-r--r--src/grossmeister/UCI.rs118
-rw-r--r--src/grossmeister/evaluation.rs276
-rw-r--r--src/grossmeister/mod.rs10
-rw-r--r--src/grossmeister/move_selector.rs66
-rw-r--r--src/grossmeister/search.rs309
-rw-r--r--src/grossmeister/ttable.rs51
-rw-r--r--src/lib.rs6
-rw-r--r--src/moves.rs41
-rw-r--r--src/square.rs41
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
}
diff --git a/src/lib.rs b/src/lib.rs
index 7f94687..837150e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)
}
}
-
-