From e2b594ebc75546fc5ff343bcf54b6ebb34e1b5a8 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 2 Mar 2025 22:25:40 -0800 Subject: [PATCH 01/94] Fix typo --- src/rank.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rank.rs b/src/rank.rs index 7df67fe0..599fd2fa 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -55,7 +55,7 @@ impl Rank { Rank::from_index(self.to_index().wrapping_sub(1)) } - /// Go one file up. If impossible, wrap around. + /// Go one rank up. If impossible, wrap around. #[inline] pub fn up(&self) -> Rank { Rank::from_index(self.to_index() + 1) From cee771383dd0b6f268eb426b18ac6b2206657a14 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 2 Mar 2025 22:26:34 -0800 Subject: [PATCH 02/94] remove unnessesary conversion --- src/square.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/square.rs b/src/square.rs index 1e02b803..1229e54d 100644 --- a/src/square.rs +++ b/src/square.rs @@ -65,7 +65,7 @@ impl Square { /// ``` #[inline] pub fn make_square(rank: Rank, file: File) -> Square { - Square((rank.to_index() as u8) << 3 ^ (file.to_index() as u8)) + Square((rank as u8) << 3 ^ (file as u8)) } /// Return the rank given this square. From 7ea564714226c929105ebdd266e90915ebc3f778 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 2 Mar 2025 22:29:00 -0800 Subject: [PATCH 03/94] Removed duplicate code (functions are inlined anyway) --- src/square.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/square.rs b/src/square.rs index 1229e54d..e81a0831 100644 --- a/src/square.rs +++ b/src/square.rs @@ -112,7 +112,7 @@ impl Square { if self.get_rank() == Rank::Eighth { None } else { - Some(Square::make_square(self.get_rank().up(), self.get_file())) + Some(self.uup()) } } @@ -132,7 +132,7 @@ impl Square { if self.get_rank() == Rank::First { None } else { - Some(Square::make_square(self.get_rank().down(), self.get_file())) + Some(self.udown()) } } @@ -152,7 +152,7 @@ impl Square { if self.get_file() == File::A { None } else { - Some(Square::make_square(self.get_rank(), self.get_file().left())) + Some(self.uleft()) } } @@ -172,10 +172,7 @@ impl Square { if self.get_file() == File::H { None } else { - Some(Square::make_square( - self.get_rank(), - self.get_file().right(), - )) + Some(self.uright()) } } From 8293deb49ea22451d91236c2632e6bc64792ab02 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 2 Mar 2025 23:44:51 -0800 Subject: [PATCH 04/94] Build error for the first example in README.md (Issue #50) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 042ce9ad..9ec70513 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Here we iterate over all moves with incremental move generation. The iterator b // lets iterate over targets. let targets = board.color_combined(!board.side_to_move()); - iterable.set_iterator_mask(targets); + iterable.set_iterator_mask(*targets); // count the number of targets let mut count = 0; From 7d284687bc20cedaad8a5ab191b1cd9b57face0c Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 2 Mar 2025 23:57:23 -0800 Subject: [PATCH 05/94] Added en_passant_target which produces the target of the En Passant move, if it exists --- src/board.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/board.rs b/src/board.rs index b92d4e35..3d297cd4 100644 --- a/src/board.rs +++ b/src/board.rs @@ -807,6 +807,40 @@ impl Board { self.en_passant } + /// Give me the en_passant target, if it exists. + /// + /// ``` + /// use chess::{Board, ChessMove, Square}; + /// + /// let move1 = ChessMove::new(Square::D2, + /// Square::D4, + /// None); + /// + /// let move2 = ChessMove::new(Square::H7, + /// Square::H5, + /// None); + /// + /// let move3 = ChessMove::new(Square::D4, + /// Square::D5, + /// None); + /// + /// let move4 = ChessMove::new(Square::E7, + /// Square::E5, + /// None); + /// + /// let board = Board::default().make_move_new(move1) + /// .make_move_new(move2) + /// .make_move_new(move3) + /// .make_move_new(move4); + /// + /// assert_eq!(board.en_passant_target(), Some(Square::E6)); + /// ``` + #[inline] + pub fn en_passant_target(self) -> Option { + let color = !self.side_to_move(); + self.en_passant().map(|square| square.ubackward(color)) + } + /// Set the en_passant square. Note: This must only be called when self.en_passant is already /// None. fn set_ep(&mut self, sq: Square) { From 3ffaa92b729c3834ed54a10cd361be94c293e748 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 3 Mar 2025 00:24:56 -0800 Subject: [PATCH 06/94] Errors made more verbose --- src/error.rs | 37 +++++++++++++++++++++++++++++++------ src/file.rs | 13 ++++++++++--- src/rank.rs | 13 ++++++++++--- src/square.rs | 21 +++++++++++++++------ 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/error.rs b/src/error.rs index 185b83a7..a22a22f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,8 +14,10 @@ pub enum Error { InvalidBoard, /// An attempt was made to create a square from an invalid string - #[fail(display = "The string specified does not contain a valid algebraic notation square")] - InvalidSquare, + #[fail(display = "The string specified does not contain a valid algebraic notation square. {}", info)] + InvalidSquare{ + info: InvalidInfo, + }, /// An attempt was made to create a move from an invalid SAN string #[fail(display = "The string specified does not contain a valid SAN notation move")] @@ -26,10 +28,33 @@ pub enum Error { InvalidUciMove, /// An attempt was made to convert a string not equal to "1"-"8" to a rank - #[fail(display = "The string specified does not contain a valid rank")] - InvalidRank, + #[fail(display = "The string specified does not contain a valid rank. {}", info)] + InvalidRank{ + info: InvalidInfo + }, /// An attempt was made to convert a string not equal to "a"-"h" to a file - #[fail(display = "The string specified does not contain a valid file")] - InvalidFile, + #[fail(display = "The string specified does not contain a valid file. {}", info)] + InvalidFile { + info: InvalidInfo + }, } + +#[derive(Clone, Debug, Fail)] +pub enum InvalidInfo { + #[fail(display = "Input is too short (expected {} but recieved {})", expected, recieved)] + InputStringTooShort { + expected: usize, + recieved: usize, + }, + + #[fail(display = "File char ({}) was not matched", recieved)] + FileCharNotMatched { + recieved: char + }, + + #[fail(display = "Rank char ({}) was not matched", recieved)] + RankCharNotMatched { + recieved: char, + }, +} \ No newline at end of file diff --git a/src/file.rs b/src/file.rs index e058084b..cdf0748a 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,4 +1,4 @@ -use crate::error::Error; +use crate::error::{Error, InvalidInfo}; use std::str::FromStr; /// Describe a file (column) on a chess board @@ -72,7 +72,12 @@ impl FromStr for File { fn from_str(s: &str) -> Result { if s.len() < 1 { - return Err(Error::InvalidFile); + return Err(Error::InvalidFile { + info: InvalidInfo::InputStringTooShort { + expected: 1, + recieved: s.len(), + }, + }); } match s.chars().next().unwrap() { 'a' => Ok(File::A), @@ -83,7 +88,9 @@ impl FromStr for File { 'f' => Ok(File::F), 'g' => Ok(File::G), 'h' => Ok(File::H), - _ => Err(Error::InvalidFile), + e => Err(Error::InvalidFile { + info: InvalidInfo::FileCharNotMatched { recieved: e }, + }), } } } diff --git a/src/rank.rs b/src/rank.rs index 599fd2fa..dad81a72 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -1,4 +1,4 @@ -use crate::error::Error; +use crate::error::{Error, InvalidInfo}; use std::str::FromStr; /// Describe a rank (row) on a chess board @@ -73,7 +73,12 @@ impl FromStr for Rank { fn from_str(s: &str) -> Result { if s.len() < 1 { - return Err(Error::InvalidRank); + return Err(Error::InvalidRank { + info: InvalidInfo::InputStringTooShort { + expected: 1, + recieved: s.len(), + }, + }); } match s.chars().next().unwrap() { '1' => Ok(Rank::First), @@ -84,7 +89,9 @@ impl FromStr for Rank { '6' => Ok(Rank::Sixth), '7' => Ok(Rank::Seventh), '8' => Ok(Rank::Eighth), - _ => Err(Error::InvalidRank), + e => Err(Error::InvalidRank { + info: InvalidInfo::RankCharNotMatched { recieved: e }, + }), } } } diff --git a/src/square.rs b/src/square.rs index e81a0831..8255451e 100644 --- a/src/square.rs +++ b/src/square.rs @@ -1,5 +1,5 @@ use crate::color::Color; -use crate::error::Error; +use crate::error::{Error, InvalidInfo}; use crate::file::File; use crate::rank::Rank; use std::fmt; @@ -975,19 +975,28 @@ impl FromStr for Square { fn from_str(s: &str) -> Result { if s.len() < 2 { - return Err(Error::InvalidSquare); + return Err(Error::InvalidSquare { + info: InvalidInfo::InputStringTooShort { + expected: 2, + recieved: s.len(), + }, + }); } let ch: Vec = s.chars().collect(); match ch[0] { 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {} - _ => { - return Err(Error::InvalidSquare); + e => { + return Err(Error::InvalidSquare { + info: InvalidInfo::FileCharNotMatched { recieved: e }, + }); } } match ch[1] { '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {} - _ => { - return Err(Error::InvalidSquare); + e => { + return Err(Error::InvalidSquare { + info: InvalidInfo::RankCharNotMatched { recieved: e }, + }); } } Ok(Square::make_square( From 6e30c875162ca9a816adb7f3a8f8d83ca635fadc Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 3 Mar 2025 00:30:20 -0800 Subject: [PATCH 07/94] Silly :{ --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ef97ea8..c3b6cf10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "chess" -version = "3.2.0" +version = "4.0.0" edition = "2018" -authors = ["Jordan Bray "] +authors = ["Jordan Bray ", "Orkking2"] description = "This is a fast chess move generator. It has a very good set of documentation, so you should take advantage of that. It (now) generates all lookup tables with a build.rs file, which means that very little pseudo-legal move generation requires branching. There are some convenience functions that are exposed to, for example, find all the squares between two squares. This uses a copy-on-make style structure, and the Board structure is as slimmed down as possible to reduce the cost of copying the board. There are places to improve perft-test performance further, but I instead opt to be more feature-complete to make it useful in real applications. For example, I generate both a hash of the board and a pawn-hash of the board for use in evaluation lookup tables (using Zobrist hashing). There are two ways to generate moves, one is faster, the other has more features that will be useful if making a chess engine. See the documentation for more details." build = "src/build.rs" -homepage = "https://github.com/jordanbray/chess" -repository = "https://github.com/jordanbray/chess" +homepage = "https://github.com/Orkking2/chess" +repository = "https://github.com/Orkking2/chess" readme = "README.md" keywords = ["chess", "move", "generator"] license = "MIT" From f481efb223102aca48580b5ad599b1dee13cc5f6 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 3 Mar 2025 00:30:57 -0800 Subject: [PATCH 08/94] Added en_passant_target to produce the square which can be targetted in En Passant --- src/board.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index 3d297cd4..d90ff751 100644 --- a/src/board.rs +++ b/src/board.rs @@ -807,7 +807,7 @@ impl Board { self.en_passant } - /// Give me the en_passant target, if it exists. + /// Give me the en_passant target square, if it exists. /// /// ``` /// use chess::{Board, ChessMove, Square}; From 3af91970e97b164df514895decc7020eb4f4a763 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 3 Mar 2025 00:39:53 -0800 Subject: [PATCH 09/94] removed unused std::mem import --- src/board.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index d90ff751..9f599d5a 100644 --- a/src/board.rs +++ b/src/board.rs @@ -17,7 +17,6 @@ use crate::zobrist::Zobrist; use std::convert::{TryFrom, TryInto}; use std::fmt; use std::hash::{Hash, Hasher}; -use std::mem; use std::str::FromStr; /// A representation of a chess board. That's why you're here, right? From 53c8292775e94ba68370b3ae01a57302495b34d2 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 3 Mar 2025 00:43:20 -0800 Subject: [PATCH 10/94] Inline is ignored on function prototypes --- src/movegen/piece_type.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index d4e45626..c09e045b 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -14,7 +14,6 @@ use crate::magic::{ pub trait PieceType { fn is(piece: Piece) -> bool; fn into_piece() -> Piece; - #[inline(always)] fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard; #[inline(always)] fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) From 66f34653d6af78ba05bf27c67f4923445485c409 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 3 Mar 2025 20:19:33 -0800 Subject: [PATCH 11/94] Turned magic numbers into const usize for Rook and Bishop indexing (this is what is generated anyway) --- src/gen_tables/rays.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/gen_tables/rays.rs b/src/gen_tables/rays.rs index 67085d65..5d449221 100644 --- a/src/gen_tables/rays.rs +++ b/src/gen_tables/rays.rs @@ -9,11 +9,14 @@ use std::io::Write; // This will be generated here, and then put into the magic_gen.rs as a const array. static mut RAYS: [[BitBoard; 64]; 2] = [[EMPTY; 64]; 2]; +const ROOK: usize = 0; +const BISHOP: usize = 1; + // For each square, generate the RAYS for the bishop. pub fn gen_bishop_rays() { for src in ALL_SQUARES.iter() { unsafe { - RAYS[1][src.to_index()] = ALL_SQUARES + RAYS[BISHOP][src.to_index()] = ALL_SQUARES .iter() .filter(|dest| { let src_rank = src.get_rank().to_index() as i8; @@ -32,7 +35,7 @@ pub fn gen_bishop_rays() { pub fn gen_rook_rays() { for src in ALL_SQUARES.iter() { unsafe { - RAYS[0][src.to_index()] = ALL_SQUARES + RAYS[ROOK][src.to_index()] = ALL_SQUARES .iter() .filter(|dest| { let src_rank = src.get_rank().to_index(); @@ -48,13 +51,13 @@ pub fn gen_rook_rays() { } pub fn get_rays(sq: Square, piece: Piece) -> BitBoard { - unsafe { RAYS[if piece == Piece::Rook { 0 } else { 1 }][sq.to_index()] } + unsafe { RAYS[if piece == Piece::Rook { ROOK } else { BISHOP }][sq.to_index()] } } // Write the RAYS array to the specified file. pub fn write_rays(f: &mut File) { - write!(f, "const ROOK: usize = {};\n", 0).unwrap(); - write!(f, "const BISHOP: usize = {};\n", 1).unwrap(); + write!(f, "const ROOK: usize = {};\n", ROOK).unwrap(); + write!(f, "const BISHOP: usize = {};\n", BISHOP).unwrap(); write!(f, "const RAYS: [[BitBoard; 64]; 2] = [[\n").unwrap(); for i in 0..2 { for j in 0..64 { From 025b017485be79ff3405a51c23eeac2717868d0a Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:06:43 -0800 Subject: [PATCH 12/94] rename --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a9da30c5..fcc0a1e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://jordanbray.github.io/chess/")] +#![doc(html_root_url = "https://orkking2.github.io/chess/")] //! # Rust Chess Library //! This is a chess move generation library for rust. It is designed to be fast, so that it can be //! used in a chess engine or UI without performance issues. From 86c748da869b04e24c80714c445167b360fba21e Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:07:00 -0800 Subject: [PATCH 13/94] compiler suggestion --- src/chess_move.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chess_move.rs b/src/chess_move.rs index 1538aec2..380c9502 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -24,9 +24,9 @@ impl ChessMove { #[inline] pub fn new(source: Square, dest: Square, promotion: Option) -> ChessMove { ChessMove { - source: source, - dest: dest, - promotion: promotion, + source, + dest, + promotion, } } From 927a64ca7af82be107c73848f5ff618247554a4d Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:22:32 -0800 Subject: [PATCH 14/94] Make white explicitly zero and black explicitly one --- src/color.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/color.rs b/src/color.rs index 6da8f596..0985b44e 100644 --- a/src/color.rs +++ b/src/color.rs @@ -4,8 +4,8 @@ use std::ops::Not; /// Represent a color. #[derive(PartialOrd, PartialEq, Eq, Copy, Clone, Debug, Hash)] pub enum Color { - White, - Black, + White = 0, + Black = 1, } /// How many colors are there? From baa173c8e0f966da4b8c0b29c3d6055984475536 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:23:29 -0800 Subject: [PATCH 15/94] make comments on CASTLES_PER_SQUARE more verbose --- src/castle_rights.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/castle_rights.rs b/src/castle_rights.rs index efe6808e..68f15ac8 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -38,7 +38,7 @@ const CASTLES_PER_SQUARE: [[u8; 64]; 2] = [ 0, 0, 0, 0, 0, 0, 0, 0, // 6 0, 0, 0, 0, 0, 0, 0, 0, // 7 0, 0, 0, 0, 0, 0, 0, 0, // 8 - ], + ], // white [ 0, 0, 0, 0, 0, 0, 0, 0, // 1 0, 0, 0, 0, 0, 0, 0, 0, // 2 @@ -47,8 +47,8 @@ const CASTLES_PER_SQUARE: [[u8; 64]; 2] = [ 0, 0, 0, 0, 0, 0, 0, 0, // 5 0, 0, 0, 0, 0, 0, 0, 0, // 6 0, 0, 0, 0, 0, 0, 0, 0, // 7 - 2, 0, 0, 0, 3, 0, 0, 1, - ], + 2, 0, 0, 0, 3, 0, 0, 1, // 8 + ], // black ]; impl CastleRights { From b0f535138a73830a852318d2502b69eb2266d434 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:25:21 -0800 Subject: [PATCH 16/94] Add more comments for Caslte rights logic --- src/castle_rights.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 68f15ac8..8b7dfd2d 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -54,11 +54,13 @@ const CASTLES_PER_SQUARE: [[u8; 64]; 2] = [ impl CastleRights { /// Can I castle kingside? pub fn has_kingside(&self) -> bool { + // Self::Both == 3 == 0b11 & 0b01 == 0b01 👍 self.to_index() & 1 == 1 } /// Can I castle queenside? pub fn has_queenside(&self) -> bool { + // Self::Both == 3 == 0b11 & 0b10 == 0b10 👍 self.to_index() & 2 == 2 } From ce652a25d2ee749317134810fb990b468ca454b1 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:27:32 -0800 Subject: [PATCH 17/94] slight change in comments --- src/castle_rights.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 8b7dfd2d..022e0bfd 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -54,13 +54,13 @@ const CASTLES_PER_SQUARE: [[u8; 64]; 2] = [ impl CastleRights { /// Can I castle kingside? pub fn has_kingside(&self) -> bool { - // Self::Both == 3 == 0b11 & 0b01 == 0b01 👍 + // Self::Both == 3 -> 0b11 & 0b01 == 0b01 👍 self.to_index() & 1 == 1 } /// Can I castle queenside? pub fn has_queenside(&self) -> bool { - // Self::Both == 3 == 0b11 & 0b10 == 0b10 👍 + // Self::Both == 3 -> 0b11 & 0b10 == 0b10 👍 self.to_index() & 2 == 2 } From 90a5fdaf2d5bac900bdf6cfd53900f6b5ca0c844 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:35:45 -0800 Subject: [PATCH 18/94] Add From for bool --- src/color.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/color.rs b/src/color.rs index 0985b44e..10796d87 100644 --- a/src/color.rs +++ b/src/color.rs @@ -82,3 +82,14 @@ impl Not for Color { } } } + +impl From for bool { + /// While in the backend, `Color::White == 0` and `Color::Black == 1`, + /// it is more intuitive for `Color::White` to evaluate `true`, as it goes first + fn from(value: Color) -> Self { + match value { + Color::White => true, + Color::Black => false, + } + } +} From be989ee4c6f0879953e9c4440d95d92b0ee12816 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:36:42 -0800 Subject: [PATCH 19/94] inline fn from: Color -> bool --- src/color.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/color.rs b/src/color.rs index 10796d87..35165737 100644 --- a/src/color.rs +++ b/src/color.rs @@ -86,6 +86,7 @@ impl Not for Color { impl From for bool { /// While in the backend, `Color::White == 0` and `Color::Black == 1`, /// it is more intuitive for `Color::White` to evaluate `true`, as it goes first + #[inline] fn from(value: Color) -> Self { match value { Color::White => true, From a01b08ba4e360bf1d96a118804475f8f1472b673 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 7 Mar 2025 15:45:45 -0800 Subject: [PATCH 20/94] Horrible suggestive comment --- src/castle_rights.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 022e0bfd..1d4a1d7d 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -28,6 +28,22 @@ pub const ALL_CASTLE_RIGHTS: [CastleRights; NUM_CASTLE_RIGHTS] = [ CastleRights::Both, ]; +//? Could this be turned into a logic function? Or does this simply increase complexity... +/* +fn square_to_castle_rights(color: Color, square: Square) -> CastleRights { + let rank = if color.into() { Rank::1 } else { Rank::8 }; + if square.get_rank() != rank.to_index() { + CastleRights::NoRights + } else { + match square.get_file() { + File::A => CastleRights::QueenSide, + File::E => CastleRights::Both, + File::H => CastleRights::KingSide, + _ => CastleRights::None, + } + } +} +*/ const CASTLES_PER_SQUARE: [[u8; 64]; 2] = [ [ 2, 0, 0, 0, 3, 0, 0, 1, // 1 @@ -64,6 +80,7 @@ impl CastleRights { self.to_index() & 2 == 2 } + /// What rights does this square enable? pub fn square_to_castle_rights(color: Color, sq: Square) -> CastleRights { CastleRights::from_index(unsafe { *CASTLES_PER_SQUARE From a2754c0a74c6f12ee1efb7ed3578699adfcaf24f Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 8 Mar 2025 17:11:42 -0800 Subject: [PATCH 21/94] CastleRights variant clarity --- src/castle_rights.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 1d4a1d7d..1d17c229 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -11,10 +11,10 @@ use crate::magic::{KINGSIDE_CASTLE_SQUARES, QUEENSIDE_CASTLE_SQUARES}; #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug, Hash)] pub enum CastleRights { - NoRights = 0, - KingSide = 1, - QueenSide = 2, - Both = 3, + NoRights = 0b00, + Both = 0b11, + KingSide = 0b01, + QueenSide = 0b10, } /// How many different types of `CastleRights` are there? From 912a2b61e63e08e2b0b353ced85abc3bfbf4060f Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 8 Mar 2025 17:48:13 -0800 Subject: [PATCH 22/94] refactor unmoved_rooks --- src/castle_rights.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 1d17c229..5b74a1d8 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -127,13 +127,14 @@ impl CastleRights { /// Which rooks can we "guarantee" we haven't moved yet? pub fn unmoved_rooks(&self, color: Color) -> BitBoard { + let my_backrank = color.to_my_backrank(); match *self { CastleRights::NoRights => EMPTY, - CastleRights::KingSide => BitBoard::set(color.to_my_backrank(), File::H), - CastleRights::QueenSide => BitBoard::set(color.to_my_backrank(), File::A), + CastleRights::KingSide => BitBoard::set(my_backrank, File::H), + CastleRights::QueenSide => BitBoard::set(my_backrank, File::A), CastleRights::Both => { - BitBoard::set(color.to_my_backrank(), File::A) - ^ BitBoard::set(color.to_my_backrank(), File::H) + BitBoard::set(my_backrank, File::A) + ^ BitBoard::set(my_backrank, File::H) } } } From b4ea574ade1033755287f01c0f6a0a5342c38b19 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 8 Mar 2025 17:53:37 -0800 Subject: [PATCH 23/94] comment --- src/castle_rights.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 5b74a1d8..826a134e 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -134,6 +134,7 @@ impl CastleRights { CastleRights::QueenSide => BitBoard::set(my_backrank, File::A), CastleRights::Both => { BitBoard::set(my_backrank, File::A) + //? Why is this a carrot (^) and not a pipe (|) ^ BitBoard::set(my_backrank, File::H) } } From 04d7d51954ea1c7ff3c8deb1a05bb9b0bbdc4126 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 8 Mar 2025 17:56:31 -0800 Subject: [PATCH 24/94] comment --- src/chess_move.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/chess_move.rs b/src/chess_move.rs index 380c9502..e18c26b2 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -381,6 +381,8 @@ impl fmt::Display for ChessMove { } } +//? Why does this exist? +//? What does it even mean for a move to be "less" than another? impl Ord for ChessMove { fn cmp(&self, other: &ChessMove) -> Ordering { if self.source != other.source { From 049186ff82b512a974a37977a5a80e3e1263b788 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 8 Mar 2025 18:01:58 -0800 Subject: [PATCH 25/94] Changed From to From<&Color> --- src/color.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/color.rs b/src/color.rs index 35165737..12e7ab94 100644 --- a/src/color.rs +++ b/src/color.rs @@ -83,11 +83,11 @@ impl Not for Color { } } -impl From for bool { +impl From<&Color> for bool { /// While in the backend, `Color::White == 0` and `Color::Black == 1`, /// it is more intuitive for `Color::White` to evaluate `true`, as it goes first #[inline] - fn from(value: Color) -> Self { + fn from(value: &Color) -> Self { match value { Color::White => true, Color::Black => false, From 5336474c6fcc051f5ccd31f4a55583b95a5b5d07 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 8 Mar 2025 19:54:17 -0800 Subject: [PATCH 26/94] Shorthand struct initialization --- src/movegen/movegen.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index fa983d1c..2deb7e38 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -18,11 +18,11 @@ pub struct SquareAndBitBoard { } impl SquareAndBitBoard { - pub fn new(sq: Square, bb: BitBoard, promotion: bool) -> SquareAndBitBoard { + pub fn new(square: Square, bitboard: BitBoard, promotion: bool) -> SquareAndBitBoard { SquareAndBitBoard { - square: sq, - bitboard: bb, - promotion: promotion, + square, + bitboard, + promotion, } } } From 0f7d2de47efd568aea1fc4e30391c93b086a5108 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 8 Mar 2025 23:49:05 -0800 Subject: [PATCH 27/94] Make deprication notes clearer --- src/board.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/board.rs b/src/board.rs index 9f599d5a..1d45fa82 100644 --- a/src/board.rs +++ b/src/board.rs @@ -92,7 +92,10 @@ impl Board { /// # Ok(()) /// # } /// ``` - #[deprecated(since = "3.1.0", note = "please use `Board::from_str(fen)?` instead")] + #[deprecated( + since = "3.1.0", + note = "Internally this is a wrapper for `Board::from_str`, please use this function directly instead" + )] #[inline] pub fn from_fen(fen: String) -> Option { Board::from_str(&fen).ok() @@ -100,7 +103,7 @@ impl Board { #[deprecated( since = "3.0.0", - note = "please use the MoveGen structure instead. It is faster and more idiomatic." + note = "Internally this wraps `MoveGen::new_legal`, please use this structure instead" )] #[inline] pub fn enumerate_moves(&self, moves: &mut [ChessMove; 256]) -> usize { @@ -807,7 +810,7 @@ impl Board { } /// Give me the en_passant target square, if it exists. - /// + /// /// ``` /// use chess::{Board, ChessMove, Square}; /// From 85f18fcf8c91593360fc488c4fd40d2796d85df3 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 8 Mar 2025 23:58:42 -0800 Subject: [PATCH 28/94] Why is BoardStatus Ordered this doesnt make sense, status stalemate and checkmate are both endings to the game and so should be ordered the same --- src/board.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index 1d45fa82..53a5c199 100644 --- a/src/board.rs +++ b/src/board.rs @@ -34,7 +34,7 @@ pub struct Board { } /// What is the status of this game? -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] pub enum BoardStatus { Ongoing, Stalemate, From f9ac1e97bc93aa602a477284478c3e50b5b497d9 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 17:04:58 -0700 Subject: [PATCH 29/94] Comment --- src/movegen/movegen.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index 2deb7e38..2e49cdec 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -31,9 +31,7 @@ pub type MoveList = NoDrop>; /// An incremental move generator /// -/// This structure enumerates moves slightly slower than board.enumerate_moves(...), -/// but has some extra features, such as: -/// +/// It has the following features: /// * Being an iterator /// * Not requiring you to create a buffer /// * Only iterating moves that match a certain pattern From 3535f289cb1de6017e4a9a1f5a6b199c48796194 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 17:10:18 -0700 Subject: [PATCH 30/94] Comment --- src/board.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/board.rs b/src/board.rs index 53a5c199..2866f363 100644 --- a/src/board.rs +++ b/src/board.rs @@ -156,6 +156,7 @@ impl Board { /// assert_eq!(board.status(), BoardStatus::Checkmate); /// ``` #[inline] + // Todo Optimize -- This function should not generate every legal move, it should generate only a single one. pub fn status(&self) -> BoardStatus { let moves = MoveGen::new_legal(&self).len(); match moves { From 33feabb8487ca4f0b8c97bffa707a3bed47c043d Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 17:27:47 -0700 Subject: [PATCH 31/94] Todos --- src/board.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index 2866f363..f023c104 100644 --- a/src/board.rs +++ b/src/board.rs @@ -299,6 +299,7 @@ impl Board { } } + // Todo Rewrite -- This doctest uses deprecated functions. /// Remove castle rights for a particular side. /// /// ``` @@ -335,7 +336,8 @@ impl Board { self.side_to_move } - /// Grab my `CastleRights`. + // Todo Rewrite -- This doctest uses deprecated functions. + /// Literally `board.castle_rights(board.side_to_move())` /// /// ``` /// use chess::{Board, Color, CastleRights}; @@ -385,6 +387,7 @@ impl Board { self.remove_castle_rights(color, remove); } + // Todo Rewrite -- This doctest uses deprecated functions. /// My opponents `CastleRights`. /// /// ``` From e642642dcd2fa68ad17b26bc1540325c52c27a2d Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 20:10:23 -0700 Subject: [PATCH 32/94] en_passant and en_passant_target should not consume self --- src/board.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/board.rs b/src/board.rs index f023c104..22048ee3 100644 --- a/src/board.rs +++ b/src/board.rs @@ -809,7 +809,7 @@ impl Board { /// assert_eq!(board.en_passant(), Some(Square::E5)); /// ``` #[inline] - pub fn en_passant(self) -> Option { + pub fn en_passant(&self) -> Option { self.en_passant } @@ -842,7 +842,7 @@ impl Board { /// assert_eq!(board.en_passant_target(), Some(Square::E6)); /// ``` #[inline] - pub fn en_passant_target(self) -> Option { + pub fn en_passant_target(&self) -> Option { let color = !self.side_to_move(); self.en_passant().map(|square| square.ubackward(color)) } From 5720184d12259f7f57b9378851e2a17a4a6ffd7e Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 20:48:07 -0700 Subject: [PATCH 33/94] Remove repetitive code --- src/board.rs | 111 +-------------------------------------------------- 1 file changed, 1 insertion(+), 110 deletions(-) diff --git a/src/board.rs b/src/board.rs index 22048ee3..a258e4df 100644 --- a/src/board.rs +++ b/src/board.rs @@ -902,116 +902,7 @@ impl Board { #[inline] pub fn make_move_new(&self, m: ChessMove) -> Board { let mut result = *self; - result.remove_ep(); - result.checkers = EMPTY; - result.pinned = EMPTY; - let source = m.get_source(); - let dest = m.get_dest(); - - let source_bb = BitBoard::from_square(source); - let dest_bb = BitBoard::from_square(dest); - let move_bb = source_bb ^ dest_bb; - let moved = self.piece_on(source).unwrap(); - - result.xor(moved, source_bb, self.side_to_move); - result.xor(moved, dest_bb, self.side_to_move); - if let Some(captured) = self.piece_on(dest) { - result.xor(captured, dest_bb, !self.side_to_move); - } - - #[allow(deprecated)] - result.remove_their_castle_rights(CastleRights::square_to_castle_rights( - !self.side_to_move, - dest, - )); - - #[allow(deprecated)] - result.remove_my_castle_rights(CastleRights::square_to_castle_rights( - self.side_to_move, - source, - )); - - let opp_king = result.pieces(Piece::King) & result.color_combined(!result.side_to_move); - - let castles = moved == Piece::King && (move_bb & get_castle_moves()) == move_bb; - - let ksq = opp_king.to_square(); - - const CASTLE_ROOK_START: [File; 8] = [ - File::A, - File::A, - File::A, - File::A, - File::H, - File::H, - File::H, - File::H, - ]; - const CASTLE_ROOK_END: [File; 8] = [ - File::D, - File::D, - File::D, - File::D, - File::F, - File::F, - File::F, - File::F, - ]; - - if moved == Piece::Knight { - result.checkers ^= get_knight_moves(ksq) & dest_bb; - } else if moved == Piece::Pawn { - if let Some(Piece::Knight) = m.get_promotion() { - result.xor(Piece::Pawn, dest_bb, self.side_to_move); - result.xor(Piece::Knight, dest_bb, self.side_to_move); - result.checkers ^= get_knight_moves(ksq) & dest_bb; - } else if let Some(promotion) = m.get_promotion() { - result.xor(Piece::Pawn, dest_bb, self.side_to_move); - result.xor(promotion, dest_bb, self.side_to_move); - } else if (source_bb & get_pawn_source_double_moves()) != EMPTY - && (dest_bb & get_pawn_dest_double_moves()) != EMPTY - { - result.set_ep(dest); - result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); - } else if Some(dest.ubackward(self.side_to_move)) == self.en_passant { - result.xor( - Piece::Pawn, - BitBoard::from_square(dest.ubackward(self.side_to_move)), - !self.side_to_move, - ); - result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); - } else { - result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); - } - } else if castles { - let my_backrank = self.side_to_move.to_my_backrank(); - let index = dest.get_file().to_index(); - let start = BitBoard::set(my_backrank, unsafe { - *CASTLE_ROOK_START.get_unchecked(index) - }); - let end = BitBoard::set(my_backrank, unsafe { - *CASTLE_ROOK_END.get_unchecked(index) - }); - result.xor(Piece::Rook, start, self.side_to_move); - result.xor(Piece::Rook, end, self.side_to_move); - } - // now, lets see if we're in check or pinned - let attackers = result.color_combined(result.side_to_move) - & ((get_bishop_rays(ksq) - & (result.pieces(Piece::Bishop) | result.pieces(Piece::Queen))) - | (get_rook_rays(ksq) - & (result.pieces(Piece::Rook) | result.pieces(Piece::Queen)))); - - for sq in attackers { - let between = between(sq, ksq) & result.combined(); - if between == EMPTY { - result.checkers ^= BitBoard::from_square(sq); - } else if between.popcnt() == 1 { - result.pinned ^= between; - } - } - - result.side_to_move = !result.side_to_move; + self.make_move(m, &mut result); result } From be0db0c585e86415ac70b6ee1b25bd8f133ec9f5 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 20:59:15 -0700 Subject: [PATCH 34/94] Refactor piece_on to piece_on and piece_on_unchecked --- src/board.rs | 57 +++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/board.rs b/src/board.rs index a258e4df..c43c41ad 100644 --- a/src/board.rs +++ b/src/board.rs @@ -733,32 +733,39 @@ impl Board { if self.combined() & opp == EMPTY { None } else { - //naiive algorithm - /* - for p in ALL_PIECES { - if self.pieces(*p) & opp { - return p; - } - } */ - if (self.pieces(Piece::Pawn) ^ self.pieces(Piece::Knight) ^ self.pieces(Piece::Bishop)) - & opp - != EMPTY - { - if self.pieces(Piece::Pawn) & opp != EMPTY { - Some(Piece::Pawn) - } else if self.pieces(Piece::Knight) & opp != EMPTY { - Some(Piece::Knight) - } else { - Some(Piece::Bishop) - } + Some(unsafe { self.piece_on_unchecked(square) }) + } + } + + /// Get the piece on a particular `Square`, defaults to Piece::King after a full search of if/else tree. + #[inline] + pub unsafe fn piece_on_unchecked(&self, square: Square) -> Piece { + let opp = BitBoard::from_square(square); + //naiive algorithm + /* + for p in ALL_PIECES { + if self.pieces(*p) & opp { + return p; + } + } */ + if (self.pieces(Piece::Pawn) ^ self.pieces(Piece::Knight) ^ self.pieces(Piece::Bishop)) + & opp + != EMPTY + { + if self.pieces(Piece::Pawn) & opp != EMPTY { + Piece::Pawn + } else if self.pieces(Piece::Knight) & opp != EMPTY { + Piece::Knight } else { - if self.pieces(Piece::Rook) & opp != EMPTY { - Some(Piece::Rook) - } else if self.pieces(Piece::Queen) & opp != EMPTY { - Some(Piece::Queen) - } else { - Some(Piece::King) - } + Piece::Bishop + } + } else { + if self.pieces(Piece::Rook) & opp != EMPTY { + Piece::Rook + } else if self.pieces(Piece::Queen) & opp != EMPTY { + Piece::Queen + } else { + Piece::King } } } From bca631f02efdc90df0a1e77f300aba4eadba7f5d Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 21:02:03 -0700 Subject: [PATCH 35/94] doctest for piece_on_unchecked --- src/board.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/board.rs b/src/board.rs index c43c41ad..f67610e6 100644 --- a/src/board.rs +++ b/src/board.rs @@ -738,6 +738,16 @@ impl Board { } /// Get the piece on a particular `Square`, defaults to Piece::King after a full search of if/else tree. + /// + /// ``` + /// use chess::{Board, Piece, Square}; + /// + /// let board = Board::default(); + /// + /// assert_eq!(unsafe { board.piece_on_unchecked(Square::A1) }, Piece::Rook); + /// // Would be None if you called Board::piece_on(...), but will default to Piece::King instead. + /// assert_eq!(unsafe { board.piece_on_unchecked(Square::D4) }, Piece::King); + /// ``` #[inline] pub unsafe fn piece_on_unchecked(&self, square: Square) -> Piece { let opp = BitBoard::from_square(square); From a9da3b6311777f023f0312607bcc286bfc6db1d2 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 21:04:26 -0700 Subject: [PATCH 36/94] Doctest for Board::color_on --- src/board.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/board.rs b/src/board.rs index f67610e6..b411574f 100644 --- a/src/board.rs +++ b/src/board.rs @@ -781,6 +781,15 @@ impl Board { } /// What color piece is on a particular square? + /// + /// ``` + /// use chess::{Board, Square, Color}; + /// + /// let board = Board::default(); + /// + /// assert_eq!(board.color_on(Square::A1), Some(Color::White)); + /// assert_eq!(board.color_on(Square::A3), None); + /// ``` #[inline] pub fn color_on(&self, square: Square) -> Option { if (self.color_combined(Color::White) & BitBoard::from_square(square)) != EMPTY { From 289aaea5333c6bbe6a051c417f07d15045aa0977 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 21:10:52 -0700 Subject: [PATCH 37/94] Remove repetitive code --- src/board.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index b411574f..f19f2b72 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1068,7 +1068,7 @@ impl Board { self.pinned = EMPTY; self.checkers = EMPTY; - let ksq = (self.pieces(Piece::King) & self.color_combined(self.side_to_move)).to_square(); + let ksq = self.king_square(self.side_to_move); let pinners = self.color_combined(!self.side_to_move) & ((get_bishop_rays(ksq) & (self.pieces(Piece::Bishop) | self.pieces(Piece::Queen))) From 6257f099b6b1f0c33e0c5c971ccc1fd376c3b9ad Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 21:13:33 -0700 Subject: [PATCH 38/94] Large functions should not be inlined --- src/board.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index f19f2b72..9bc25519 100644 --- a/src/board.rs +++ b/src/board.rs @@ -948,7 +948,6 @@ impl Board { /// board.make_move(m, &mut result); /// assert_eq!(result.side_to_move(), Color::Black); /// ``` - #[inline] pub fn make_move(&self, m: ChessMove, result: &mut Board) { *result = *self; result.remove_ep(); From d8a6e913ec7955b6837aac0d5e8a27ff3b817a29 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 21:31:37 -0700 Subject: [PATCH 39/94] comment --- src/movegen/piece_type.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index c09e045b..a30e8c34 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -12,6 +12,7 @@ use crate::magic::{ }; pub trait PieceType { + //? What is the purpose of this function fn is(piece: Piece) -> bool; fn into_piece() -> Piece; fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard; @@ -126,7 +127,7 @@ impl PieceType for PawnType { get_pawn_moves(src, color, combined) & mask } - #[inline(always)] + #[inline] fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) where T: CheckType, From eb305b5fa0bc2c8ed755a4cc5f6e451c4e3f434b Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 21:31:48 -0700 Subject: [PATCH 40/94] renamed variable for increased clarity --- src/movegen/movegen.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index 2e49cdec..495f2a35 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -92,25 +92,25 @@ impl MoveGen { #[inline(always)] fn enumerate_moves(board: &Board) -> MoveList { let checkers = *board.checkers(); - let mask = !board.color_combined(board.side_to_move()); + let unoccupied_by_me = !board.color_combined(board.side_to_move()); let mut movelist = NoDrop::new(ArrayVec::::new()); if checkers == EMPTY { - PawnType::legals::(&mut movelist, &board, mask); - KnightType::legals::(&mut movelist, &board, mask); - BishopType::legals::(&mut movelist, &board, mask); - RookType::legals::(&mut movelist, &board, mask); - QueenType::legals::(&mut movelist, &board, mask); - KingType::legals::(&mut movelist, &board, mask); + PawnType::legals::(&mut movelist, &board, unoccupied_by_me); + KnightType::legals::(&mut movelist, &board, unoccupied_by_me); + BishopType::legals::(&mut movelist, &board, unoccupied_by_me); + RookType::legals::(&mut movelist, &board, unoccupied_by_me); + QueenType::legals::(&mut movelist, &board, unoccupied_by_me); + KingType::legals::(&mut movelist, &board, unoccupied_by_me); } else if checkers.popcnt() == 1 { - PawnType::legals::(&mut movelist, &board, mask); - KnightType::legals::(&mut movelist, &board, mask); - BishopType::legals::(&mut movelist, &board, mask); - RookType::legals::(&mut movelist, &board, mask); - QueenType::legals::(&mut movelist, &board, mask); - KingType::legals::(&mut movelist, &board, mask); + PawnType::legals::(&mut movelist, &board, unoccupied_by_me); + KnightType::legals::(&mut movelist, &board, unoccupied_by_me); + BishopType::legals::(&mut movelist, &board, unoccupied_by_me); + RookType::legals::(&mut movelist, &board, unoccupied_by_me); + QueenType::legals::(&mut movelist, &board, unoccupied_by_me); + KingType::legals::(&mut movelist, &board, unoccupied_by_me); } else { - KingType::legals::(&mut movelist, &board, mask); + KingType::legals::(&mut movelist, &board, unoccupied_by_me); } movelist From 9500b6418c6f8b2619badf74859850a10619f31d Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 9 Mar 2025 21:32:14 -0700 Subject: [PATCH 41/94] Larget functions should not be inline(always) --- src/movegen/piece_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index a30e8c34..dd9f1396 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -224,7 +224,7 @@ impl PieceType for KnightType { get_knight_moves(src) & mask } - #[inline(always)] + #[inline] fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) where T: CheckType, From 619168a3f06882c531350a1155086cfbe9eecee2 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 10 Mar 2025 10:47:08 -0700 Subject: [PATCH 42/94] comment --- src/movegen/piece_type.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index dd9f1396..33b55fc6 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -12,11 +12,15 @@ use crate::magic::{ }; pub trait PieceType { - //? What is the purpose of this function - fn is(piece: Piece) -> bool; fn into_piece() -> Piece; - fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard; + //? What is the purpose of this function + #[allow(dead_code)] #[inline(always)] + fn is(piece: Piece) -> bool { + Self::into_piece() == piece + } + fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard; + #[inline] fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) where T: CheckType, @@ -114,10 +118,7 @@ impl PawnType { } impl PieceType for PawnType { - fn is(piece: Piece) -> bool { - piece == Piece::Pawn - } - + #[inline(always)] fn into_piece() -> Piece { Piece::Pawn } @@ -196,10 +197,12 @@ impl PieceType for PawnType { } impl PieceType for BishopType { + #[inline(always)] fn is(piece: Piece) -> bool { piece == Piece::Bishop } + #[inline(always)] fn into_piece() -> Piece { Piece::Bishop } @@ -211,10 +214,12 @@ impl PieceType for BishopType { } impl PieceType for KnightType { + #[inline(always)] fn is(piece: Piece) -> bool { piece == Piece::Knight } + #[inline(always)] fn into_piece() -> Piece { Piece::Knight } @@ -267,6 +272,7 @@ impl PieceType for RookType { piece == Piece::Rook } + #[inline(always)] fn into_piece() -> Piece { Piece::Rook } @@ -282,6 +288,7 @@ impl PieceType for QueenType { piece == Piece::Queen } + #[inline(always)] fn into_piece() -> Piece { Piece::Queen } @@ -294,7 +301,7 @@ impl PieceType for QueenType { impl KingType { /// Is a particular king move legal? - #[inline(always)] + #[inline] pub fn legal_king_move(board: &Board, dest: Square) -> bool { let combined = board.combined() ^ (board.pieces(Piece::King) & board.color_combined(board.side_to_move())) @@ -335,6 +342,7 @@ impl PieceType for KingType { piece == Piece::King } + #[inline(always)] fn into_piece() -> Piece { Piece::King } @@ -344,7 +352,7 @@ impl PieceType for KingType { get_king_moves(src) & mask } - #[inline(always)] + #[inline] fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) where T: CheckType, From 09ccca18cfb115497f95f2f8aba03423ee92e501 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 10 Mar 2025 10:48:39 -0700 Subject: [PATCH 43/94] Refactor repetitive code --- src/movegen/piece_type.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index 33b55fc6..b9a7e1d9 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -92,8 +92,7 @@ impl PawnType { ^ BitBoard::from_square(source) ^ BitBoard::from_square(dest); - let ksq = - (board.pieces(Piece::King) & board.color_combined(board.side_to_move())).to_square(); + let ksq = board.king_square(board.side_to_move()); let rooks = (board.pieces(Piece::Rook) | board.pieces(Piece::Queen)) & board.color_combined(!board.side_to_move()); From 5e544607995f7fa1367077822a44278983502543 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 10 Mar 2025 19:08:43 -0700 Subject: [PATCH 44/94] Add nostd support --- Cargo.toml | 12 +++++++---- src/bitboard.rs | 9 ++++----- src/board.rs | 1 + src/board_builder.rs | 6 +++--- src/castle_rights.rs | 37 ++++++++++++++++++++++++++++++++++ src/error.rs | 46 ++++++++++++++++--------------------------- src/file.rs | 13 +++--------- src/lib.rs | 6 ++++++ src/piece.rs | 42 ++++++++++++++++++++++++++++++++------- src/rank.rs | 13 +++--------- src/square.rs | 47 +++++++++++++++++++++----------------------- 11 files changed, 139 insertions(+), 93 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c3b6cf10..2c80b4ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,12 @@ repository = "https://github.com/Orkking2/chess" readme = "README.md" keywords = ["chess", "move", "generator"] license = "MIT" -documentation = "https://jordanbray.github.io/chess/chess/index.html" +documentation = "https://orkking2.github.io/chess/chess/index.html" [dependencies] -arrayvec = "0.7.2" -nodrop = "0.1.14" -failure = "0.1.6" +arrayvec = { version = "0.7.2", default_features = false } +nodrop = { version = "0.1.14", default_features = false } +failure = { version = "0.1.6", optional = true } [profile.release] opt-level = 3 @@ -40,3 +40,7 @@ opt-level = 3 [build-dependencies] rand = { version = "0.7.2", default_features = false, features = ["small_rng"] } failure = "0.1.6" + +[features] +default = ["std"] +std = ["dep:failure"] \ No newline at end of file diff --git a/src/bitboard.rs b/src/bitboard.rs index aa2a474d..7e548f8f 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -254,18 +254,17 @@ impl Not for &BitBoard { impl fmt::Display for BitBoard { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut s: String = "".to_owned(); for x in 0..64 { if self.0 & (1u64 << x) == (1u64 << x) { - s.push_str("X "); + write!(f, "X ")?; } else { - s.push_str(". "); + write!(f, ". ")?; } if x % 8 == 7 { - s.push_str("\n"); + write!(f, "\n")?; } } - write!(f, "{}", s) + Ok(()) } } diff --git a/src/board.rs b/src/board.rs index 9bc25519..0bd1524e 100644 --- a/src/board.rs +++ b/src/board.rs @@ -97,6 +97,7 @@ impl Board { note = "Internally this is a wrapper for `Board::from_str`, please use this function directly instead" )] #[inline] + #[cfg(feature="std")] pub fn from_fen(fen: String) -> Option { Board::from_str(&fen).ok() } diff --git a/src/board_builder.rs b/src/board_builder.rs index da2f6795..19808c6a 100644 --- a/src/board_builder.rs +++ b/src/board_builder.rs @@ -288,7 +288,7 @@ impl fmt::Display for BoardBuilder { } if let Some((piece, color)) = self.pieces[square] { - write!(f, "{}", piece.to_string(color))?; + write!(f, "{}", piece.with_color(color))?; } else { count += 1; } @@ -315,12 +315,12 @@ impl fmt::Display for BoardBuilder { write!( f, "{}", - self.castle_rights[Color::White.to_index()].to_string(Color::White) + self.castle_rights[Color::White.to_index()].with_color(Color::White) )?; write!( f, "{}", - self.castle_rights[Color::Black.to_index()].to_string(Color::Black) + self.castle_rights[Color::Black.to_index()].with_color(Color::Black) )?; if self.castle_rights[0] == CastleRights::NoRights && self.castle_rights[1] == CastleRights::NoRights diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 826a134e..f245770b 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::hint::unreachable_unchecked; use crate::bitboard::{BitBoard, EMPTY}; @@ -114,6 +115,16 @@ impl CastleRights { *self as usize } + /// Convert this into a `&'static str` (for displaying) + fn to_str(&self) -> &'static str { + match *self { + CastleRights::NoRights => "", + CastleRights::KingSide => "k", + CastleRights::QueenSide => "q", + CastleRights::Both => "kq", + } + } + /// Convert `usize` to `CastleRights`. pub fn from_index(i: usize) -> CastleRights { match i & 3 { @@ -150,6 +161,7 @@ impl CastleRights { /// assert_eq!(CastleRights::KingSide.to_string(Color::White), "K"); /// assert_eq!(CastleRights::QueenSide.to_string(Color::Black), "q"); /// ``` + #[cfg(feature="std")] pub fn to_string(&self, color: Color) -> String { let result = match *self { CastleRights::NoRights => "", @@ -173,4 +185,29 @@ impl CastleRights { _ => CastleRights::NoRights, } } + + /// Combine this `CastleRights` with a `Color` (to display) + pub fn with_color(&self, color: Color) -> CastleRightsWithColor { + CastleRightsWithColor { castle_rights: *self, color } + } +} + +pub struct CastleRightsWithColor { + castle_rights: CastleRights, + color: Color, } + +impl fmt::Display for CastleRightsWithColor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = self.castle_rights.to_str(); + + if self.color == Color::White { + for c in s.chars() { + write!(f, "{}", c.to_uppercase())? + } + Ok(()) + } else { + write!(f, "{}", s) + } + } +} \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index a22a22f0..41a2aae3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,7 @@ use failure::Fail; /// Sometimes, bad stuff happens. #[derive(Clone, Debug, Fail)] +#[cfg(feature="std")] pub enum Error { /// The FEN string is invalid #[fail(display = "Invalid FEN string: {}", fen)] @@ -14,10 +15,8 @@ pub enum Error { InvalidBoard, /// An attempt was made to create a square from an invalid string - #[fail(display = "The string specified does not contain a valid algebraic notation square. {}", info)] - InvalidSquare{ - info: InvalidInfo, - }, + #[fail(display = "The string specified does not contain a valid algebraic notation square.")] + InvalidSquare, /// An attempt was made to create a move from an invalid SAN string #[fail(display = "The string specified does not contain a valid SAN notation move")] @@ -28,33 +27,22 @@ pub enum Error { InvalidUciMove, /// An attempt was made to convert a string not equal to "1"-"8" to a rank - #[fail(display = "The string specified does not contain a valid rank. {}", info)] - InvalidRank{ - info: InvalidInfo - }, + #[fail(display = "The string specified does not contain a valid rank.")] + InvalidRank, /// An attempt was made to convert a string not equal to "a"-"h" to a file - #[fail(display = "The string specified does not contain a valid file. {}", info)] - InvalidFile { - info: InvalidInfo - }, + #[fail(display = "The string specified does not contain a valid file.")] + InvalidFile, } -#[derive(Clone, Debug, Fail)] -pub enum InvalidInfo { - #[fail(display = "Input is too short (expected {} but recieved {})", expected, recieved)] - InputStringTooShort { - expected: usize, - recieved: usize, - }, - - #[fail(display = "File char ({}) was not matched", recieved)] - FileCharNotMatched { - recieved: char - }, - - #[fail(display = "Rank char ({}) was not matched", recieved)] - RankCharNotMatched { - recieved: char, - }, +#[derive(Clone, Debug)] +#[cfg(not(feature="std"))] +pub enum Error { + InvalidFen, + InvalidBoard, + InvalidSquare, + InvalidSanMove, + InvalidUciMove, + InvalidRank, + InvalidFile, } \ No newline at end of file diff --git a/src/file.rs b/src/file.rs index cdf0748a..e058084b 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,4 +1,4 @@ -use crate::error::{Error, InvalidInfo}; +use crate::error::Error; use std::str::FromStr; /// Describe a file (column) on a chess board @@ -72,12 +72,7 @@ impl FromStr for File { fn from_str(s: &str) -> Result { if s.len() < 1 { - return Err(Error::InvalidFile { - info: InvalidInfo::InputStringTooShort { - expected: 1, - recieved: s.len(), - }, - }); + return Err(Error::InvalidFile); } match s.chars().next().unwrap() { 'a' => Ok(File::A), @@ -88,9 +83,7 @@ impl FromStr for File { 'f' => Ok(File::F), 'g' => Ok(File::G), 'h' => Ok(File::H), - e => Err(Error::InvalidFile { - info: InvalidInfo::FileCharNotMatched { recieved: e }, - }), + _ => Err(Error::InvalidFile), } } } diff --git a/src/lib.rs b/src/lib.rs index fcc0a1e7..443355e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![doc(html_root_url = "https://orkking2.github.io/chess/")] +#![cfg_attr(not(feature="std"), no_std)] //! # Rust Chess Library //! This is a chess move generation library for rust. It is designed to be fast, so that it can be //! used in a chess engine or UI without performance issues. @@ -18,6 +19,9 @@ //! ``` //! +#[cfg(not(feature="std"))] +extern crate core as std; + mod board; pub use crate::board::*; @@ -66,7 +70,9 @@ pub use crate::movegen::MoveGen; mod zobrist; +#[cfg(feature="std")] mod game; +#[cfg(feature="std")] pub use crate::game::{Action, Game, GameResult}; mod board_builder; diff --git a/src/piece.rs b/src/piece.rs index c199e9a0..8a40767a 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -38,6 +38,25 @@ impl Piece { *self as usize } + /// Convert the `Piece` to its FEN `char` + pub fn to_char(&self) -> char { + match *self { + Piece::Pawn => 'p', + Piece::Knight => 'n', + Piece::Bishop => 'b', + Piece::Rook => 'r', + Piece::Queen => 'q', + Piece::King => 'k', + } + } + + pub fn with_color(&self, color: Color) -> PieceWithColor { + PieceWithColor { + piece: *self, + color, + } + } + /// Convert a piece with a color to a string. White pieces are uppercase, black pieces are /// lowercase. /// @@ -48,6 +67,7 @@ impl Piece { /// assert_eq!(Piece::Knight.to_string(Color::Black), "n"); /// ``` #[inline] + #[cfg(feature = "std")] pub fn to_string(&self, color: Color) -> String { let piece = format!("{}", self); if color == Color::White { @@ -60,16 +80,24 @@ impl Piece { impl fmt::Display for Piece { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_char()) + } +} + +pub struct PieceWithColor { + piece: Piece, + color: Color, +} + +impl fmt::Display for PieceWithColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", - match *self { - Piece::Pawn => "p", - Piece::Knight => "n", - Piece::Bishop => "b", - Piece::Rook => "r", - Piece::Queen => "q", - Piece::King => "k", + if (&self.color).into() { + self.piece.to_char().to_ascii_uppercase() + } else { + self.piece.to_char() } ) } diff --git a/src/rank.rs b/src/rank.rs index dad81a72..599fd2fa 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -1,4 +1,4 @@ -use crate::error::{Error, InvalidInfo}; +use crate::error::Error; use std::str::FromStr; /// Describe a rank (row) on a chess board @@ -73,12 +73,7 @@ impl FromStr for Rank { fn from_str(s: &str) -> Result { if s.len() < 1 { - return Err(Error::InvalidRank { - info: InvalidInfo::InputStringTooShort { - expected: 1, - recieved: s.len(), - }, - }); + return Err(Error::InvalidRank); } match s.chars().next().unwrap() { '1' => Ok(Rank::First), @@ -89,9 +84,7 @@ impl FromStr for Rank { '6' => Ok(Rank::Sixth), '7' => Ok(Rank::Seventh), '8' => Ok(Rank::Eighth), - e => Err(Error::InvalidRank { - info: InvalidInfo::RankCharNotMatched { recieved: e }, - }), + _ => Err(Error::InvalidRank), } } } diff --git a/src/square.rs b/src/square.rs index 8255451e..d14b5864 100644 --- a/src/square.rs +++ b/src/square.rs @@ -1,5 +1,5 @@ use crate::color::Color; -use crate::error::{Error, InvalidInfo}; +use crate::error::Error; use crate::file::File; use crate::rank::Rank; use std::fmt; @@ -378,6 +378,7 @@ impl Square { since = "3.1.0", note = "please use `Square::from_str(square)?` instead" )] + #[cfg(feature = "std")] pub fn from_string(s: String) -> Option { Square::from_str(&s).ok() } @@ -975,34 +976,30 @@ impl FromStr for Square { fn from_str(s: &str) -> Result { if s.len() < 2 { - return Err(Error::InvalidSquare { - info: InvalidInfo::InputStringTooShort { - expected: 2, - recieved: s.len(), - }, - }); + return Err(Error::InvalidSquare); } - let ch: Vec = s.chars().collect(); - match ch[0] { - 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {} - e => { - return Err(Error::InvalidSquare { - info: InvalidInfo::FileCharNotMatched { recieved: e }, - }); + + let mut i = s.chars(); + if let (Some(c1), Some(c2)) = (i.next(), i.next()) { + match c1 { + 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {} + _ => { + return Err(Error::InvalidSquare); + } } - } - match ch[1] { - '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {} - e => { - return Err(Error::InvalidSquare { - info: InvalidInfo::RankCharNotMatched { recieved: e }, - }); + match c2 { + '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {} + _ => { + return Err(Error::InvalidSquare); + } } + Ok(Square::make_square( + Rank::from_index((c1 as usize) - ('1' as usize)), + File::from_index((c2 as usize) - ('a' as usize)), + )) + } else { + Err(Error::InvalidSquare) } - Ok(Square::make_square( - Rank::from_index((ch[1] as usize) - ('1' as usize)), - File::from_index((ch[0] as usize) - ('a' as usize)), - )) } } From 6cbd82308ae8fc8e651af32d23252751d2d50a1e Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Mon, 10 Mar 2025 19:16:10 -0700 Subject: [PATCH 45/94] Remove Fail dependency (unmaintained crate) --- Cargo.toml | 4 +--- src/error.rs | 33 +++++++++++++++++++-------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c80b4ea..af4ce9d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ documentation = "https://orkking2.github.io/chess/chess/index.html" [dependencies] arrayvec = { version = "0.7.2", default_features = false } nodrop = { version = "0.1.14", default_features = false } -failure = { version = "0.1.6", optional = true } [profile.release] opt-level = 3 @@ -39,8 +38,7 @@ opt-level = 3 [build-dependencies] rand = { version = "0.7.2", default_features = false, features = ["small_rng"] } -failure = "0.1.6" [features] default = ["std"] -std = ["dep:failure"] \ No newline at end of file +std = [] \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 41a2aae3..48ef2261 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,42 +1,47 @@ -use failure::Fail; +use std::fmt; /// Sometimes, bad stuff happens. -#[derive(Clone, Debug, Fail)] -#[cfg(feature="std")] +#[derive(Clone, Debug)] +#[cfg(feature = "std")] pub enum Error { /// The FEN string is invalid - #[fail(display = "Invalid FEN string: {}", fen)] InvalidFen { fen: String }, /// The board created from BoardBuilder was found to be invalid - #[fail( - display = "The board specified did not pass sanity checks. Are you sure the kings exist and the side to move cannot capture the opposing king?" - )] InvalidBoard, /// An attempt was made to create a square from an invalid string - #[fail(display = "The string specified does not contain a valid algebraic notation square.")] InvalidSquare, /// An attempt was made to create a move from an invalid SAN string - #[fail(display = "The string specified does not contain a valid SAN notation move")] InvalidSanMove, /// An atempt was made to create a move from an invalid UCI string - #[fail(display = "The string specified does not contain a valid UCI notation move")] InvalidUciMove, /// An attempt was made to convert a string not equal to "1"-"8" to a rank - #[fail(display = "The string specified does not contain a valid rank.")] InvalidRank, /// An attempt was made to convert a string not equal to "a"-"h" to a file - #[fail(display = "The string specified does not contain a valid file.")] InvalidFile, } +impl fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidFen{ fen: s } => write!(f, "Invalid FEN string: {}", s), + Self::InvalidBoard => write!(f, "The board specified did not pass sanity checks. Are you sure the kings exist and the side to move cannot capture the opposing king?"), + Self::InvalidSquare => write!(f, "The string specified does not contain a valid algebraic notation square."), + Self::InvalidSanMove => write!(f, "The string specified does not contain a valid SAN notation move"), + Self::InvalidUciMove => write!(f, "The string specified does not contain a valid UCI notation move"), + Self::InvalidRank => write!(f, "The string specified does not contain a valid rank."), + Self::InvalidFile => write!(f, "The string specified does not contain a valid file.") + } + } +} + #[derive(Clone, Debug)] -#[cfg(not(feature="std"))] +#[cfg(not(feature = "std"))] pub enum Error { InvalidFen, InvalidBoard, @@ -45,4 +50,4 @@ pub enum Error { InvalidUciMove, InvalidRank, InvalidFile, -} \ No newline at end of file +} From 18e4f9018e9c90b78d474b7281d6cf130520fd11 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 12 Mar 2025 10:32:29 -0700 Subject: [PATCH 46/94] Updating to modern rust --- Cargo.toml | 12 +- src/bitboard.rs | 38 ++++- src/board.rs | 224 ++++++++++++++++++++------ src/board_builder.rs | 114 +++++++------ src/cache_table.rs | 10 +- src/castle_rights.rs | 20 +-- src/chess_move.rs | 155 ++++++++++++++---- src/color.rs | 15 +- src/error.rs | 51 +++--- src/file.rs | 25 +-- src/game.rs | 100 ++++++------ src/gen_tables/between.rs | 22 +-- src/gen_tables/generate_all_tables.rs | 6 +- src/gen_tables/king.rs | 36 ++--- src/gen_tables/knights.rs | 16 +- src/gen_tables/lines.rs | 22 +-- src/gen_tables/magic.rs | 34 ++-- src/gen_tables/magic_helpers.rs | 10 +- src/gen_tables/mod.rs | 2 - src/gen_tables/pawns.rs | 36 ++--- src/gen_tables/ranks_files.rs | 35 ++-- src/gen_tables/rays.rs | 34 ++-- src/gen_tables/zobrist.rs | 30 ++-- src/lib.rs | 4 +- src/magic.rs | 30 ++-- src/movegen/mod.rs | 1 - src/movegen/movegen.rs | 31 ++-- src/movegen/piece_type.rs | 19 ++- src/piece.rs | 19 +-- src/rank.rs | 24 +-- src/square.rs | 46 +++--- src/zobrist.rs | 14 +- 32 files changed, 727 insertions(+), 508 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af4ce9d3..0daa0c6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "chess" version = "4.0.0" -edition = "2018" +edition = "2021" authors = ["Jordan Bray ", "Orkking2"] description = "This is a fast chess move generator. It has a very good set of documentation, so you should take advantage of that. It (now) generates all lookup tables with a build.rs file, which means that very little pseudo-legal move generation requires branching. There are some convenience functions that are exposed to, for example, find all the squares between two squares. This uses a copy-on-make style structure, and the Board structure is as slimmed down as possible to reduce the cost of copying the board. There are places to improve perft-test performance further, but I instead opt to be more feature-complete to make it useful in real applications. For example, I generate both a hash of the board and a pawn-hash of the board for use in evaluation lookup tables (using Zobrist hashing). There are two ways to generate moves, one is faster, the other has more features that will be useful if making a chess engine. See the documentation for more details." build = "src/build.rs" @@ -12,14 +12,16 @@ readme = "README.md" keywords = ["chess", "move", "generator"] license = "MIT" documentation = "https://orkking2.github.io/chess/chess/index.html" +rust-version = "1.56.0" [dependencies] -arrayvec = { version = "0.7.2", default_features = false } -nodrop = { version = "0.1.14", default_features = false } +arrayvec = { version = "0.7.2", default-features = false } +nodrop = { version = "0.1.14", default-features = false } [profile.release] opt-level = 3 debug = false +lto = true [profile.dev] opt-level = 3 @@ -37,8 +39,8 @@ opt-level = 3 opt-level = 3 [build-dependencies] -rand = { version = "0.7.2", default_features = false, features = ["small_rng"] } +rand = { version = "0.7.2", default-features = false, features = ["small_rng"] } [features] default = ["std"] -std = [] \ No newline at end of file +std = [] diff --git a/src/bitboard.rs b/src/bitboard.rs index 7e548f8f..337e457e 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -271,51 +271,73 @@ impl fmt::Display for BitBoard { impl BitBoard { /// Construct a new bitboard from a u64 #[inline] - pub fn new(b: u64) -> BitBoard { + pub const fn new(b: u64) -> BitBoard { BitBoard(b) } /// Construct a new `BitBoard` with a particular `Square` set #[inline] - pub fn set(rank: Rank, file: File) -> BitBoard { + pub const fn set(rank: Rank, file: File) -> BitBoard { BitBoard::from_square(Square::make_square(rank, file)) } /// Construct a new `BitBoard` with a particular `Square` set #[inline] - pub fn from_square(sq: Square) -> BitBoard { + pub const fn from_square(sq: Square) -> BitBoard { BitBoard(1u64 << sq.to_int()) } /// Convert an `Option` to an `Option` #[inline] + #[deprecated( + since = "4.0.0", + note = "Unnecessary shorthand for `square_option.map(BitBoard::from_square)`.", + )] pub fn from_maybe_square(sq: Option) -> Option { - sq.map(|s| BitBoard::from_square(s)) + sq.map(BitBoard::from_square) } /// Convert a `BitBoard` to a `Square`. This grabs the least-significant `Square` #[inline] - pub fn to_square(&self) -> Square { + pub const fn to_square(&self) -> Square { Square::new(self.0.trailing_zeros() as u8) } /// Count the number of `Squares` set in this `BitBoard` #[inline] - pub fn popcnt(&self) -> u32 { + pub const fn popcnt(&self) -> u32 { self.0.count_ones() } /// Reverse this `BitBoard`. Look at it from the opponents perspective. #[inline] - pub fn reverse_colors(&self) -> BitBoard { + pub const fn reverse_colors(&self) -> BitBoard { BitBoard(self.0.swap_bytes()) } /// Convert this `BitBoard` to a `usize` (for table lookups) #[inline] - pub fn to_size(&self, rightshift: u8) -> usize { + pub const fn to_size(&self, rightshift: u8) -> usize { (self.0 >> rightshift) as usize } + + pub(crate) const fn from_array(arr: [u8; 8]) -> Self { + let mut accum = 0u64; + + let mut i = 0; + + loop { + accum |= arr[i] as u64; + i += 1; + if i == 8 { + break; + } else { + accum <<= 8; + } + } + + BitBoard::new(accum) + } } /// For the `BitBoard`, iterate over every `Square` set. diff --git a/src/board.rs b/src/board.rs index 0bd1524e..39f8a6fd 100644 --- a/src/board.rs +++ b/src/board.rs @@ -3,7 +3,7 @@ use crate::board_builder::BoardBuilder; use crate::castle_rights::CastleRights; use crate::chess_move::ChessMove; use crate::color::{Color, ALL_COLORS, NUM_COLORS}; -use crate::error::Error; +use crate::error::InvalidError; use crate::file::File; use crate::magic::{ between, get_adjacent_files, get_bishop_rays, get_castle_moves, get_file, get_king_moves, @@ -34,6 +34,7 @@ pub struct Board { } /// What is the status of this game? +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Debug)] pub enum BoardStatus { Ongoing, @@ -43,10 +44,19 @@ pub enum BoardStatus { /// Construct the initial position. impl Default for Board { + /// ```text + /// ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ + /// ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ + /// . . . . . . . . + /// . . . . . . . . + /// . . . . . . . . + /// . . . . . . . . + /// ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ + /// ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ + /// ``` #[inline] fn default() -> Board { - Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") - .expect("Valid Position") + Self::STARTPOS } } @@ -57,9 +67,125 @@ impl Hash for Board { } impl Board { + /// Represents the start position of a chess game. + /// ```text + /// ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ + /// ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ + /// . . . . . . . . + /// . . . . . . . . + /// . . . . . . . . + /// . . . . . . . . + /// ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ + /// ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ + /// ``` + pub const STARTPOS: Self = { + const PAWN_BITBOARD: BitBoard = BitBoard::from_array([ + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + ]); + const KNIGHT_BITBOARD: BitBoard = BitBoard::from_array([ + 0b0100_0010, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0100_0010, + ]); + const BISHOP_BITBOARD: BitBoard = BitBoard::from_array([ + 0b0010_0100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0010_0100, + ]); + const ROOK_BITBOARD: BitBoard = BitBoard::from_array([ + 0b1000_0001, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1000_0001, + ]); + const QUEEN_BITBOARD: BitBoard = BitBoard::from_array([ + 0b0000_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_1000, + ]); + const KING_BITBOARD: BitBoard = BitBoard::from_array([ + 0b0001_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_0000, + ]); + const BLACK_BITBOARD: BitBoard = BitBoard::from_array([ + 0b1111_1111, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + ]); + const WHITE_BITBOARD: BitBoard = BitBoard::from_array([ + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b1111_1111, + ]); + + const COMBINED_BITBOARD: BitBoard = BitBoard::new(BLACK_BITBOARD.0 | WHITE_BITBOARD.0); + const START_HASH: u64 = 11762218931632540; + + Self { + pieces: [ + PAWN_BITBOARD, + KNIGHT_BITBOARD, + BISHOP_BITBOARD, + ROOK_BITBOARD, + QUEEN_BITBOARD, + KING_BITBOARD, + ], + color_combined: [WHITE_BITBOARD, BLACK_BITBOARD], + combined: COMBINED_BITBOARD, + side_to_move: Color::White, + castle_rights: [CastleRights::Both; NUM_COLORS], + pinned: EMPTY, + checkers: EMPTY, + hash: START_HASH, + en_passant: None, + } + }; + /// Construct a new `Board` that is completely empty. /// Note: This does NOT give you the initial position. Just a blank slate. - fn new() -> Board { + const fn new() -> Board { Board { pieces: [EMPTY; NUM_PIECES], color_combined: [EMPTY; NUM_COLORS], @@ -97,7 +223,7 @@ impl Board { note = "Internally this is a wrapper for `Board::from_str`, please use this function directly instead" )] #[inline] - #[cfg(feature="std")] + #[cfg(feature = "std")] pub fn from_fen(fen: String) -> Option { Board::from_str(&fen).ok() } @@ -159,7 +285,7 @@ impl Board { #[inline] // Todo Optimize -- This function should not generate every legal move, it should generate only a single one. pub fn status(&self) -> BoardStatus { - let moves = MoveGen::new_legal(&self).len(); + let moves = MoveGen::new_legal(self).len(); match moves { 0 => { if self.checkers == EMPTY { @@ -187,7 +313,7 @@ impl Board { /// assert_eq!(*board.combined(), combined_should_be); /// ``` #[inline] - pub fn combined(&self) -> &BitBoard { + pub const fn combined(&self) -> &BitBoard { &self.combined } @@ -210,7 +336,7 @@ impl Board { /// ``` #[inline] pub fn color_combined(&self, color: Color) -> &BitBoard { - unsafe { self.color_combined.get_unchecked(color.to_index()) } + unsafe { self.color_combined.get_unchecked(color.into_index()) } } /// Give me the `Square` the `color` king is on. @@ -245,7 +371,7 @@ impl Board { /// ``` #[inline] pub fn pieces(&self, piece: Piece) -> &BitBoard { - unsafe { self.pieces.get_unchecked(piece.to_index()) } + unsafe { self.pieces.get_unchecked(piece.into_index()) } } /// Grab the `CastleRights` for a particular side. @@ -284,7 +410,7 @@ impl Board { /// ``` #[inline] pub fn castle_rights(&self, color: Color) -> CastleRights { - unsafe { *self.castle_rights.get_unchecked(color.to_index()) } + unsafe { *self.castle_rights.get_unchecked(color.into_index()) } } /// Add castle rights for a particular side. Note: this can create an invalid position. @@ -295,7 +421,7 @@ impl Board { #[inline] pub fn add_castle_rights(&mut self, color: Color, add: CastleRights) { unsafe { - *self.castle_rights.get_unchecked_mut(color.to_index()) = + *self.castle_rights.get_unchecked_mut(color.into_index()) = self.castle_rights(color).add(add); } } @@ -319,7 +445,7 @@ impl Board { #[inline] pub fn remove_castle_rights(&mut self, color: Color, remove: CastleRights) { unsafe { - *self.castle_rights.get_unchecked_mut(color.to_index()) = + *self.castle_rights.get_unchecked_mut(color.into_index()) = self.castle_rights(color).remove(remove); } } @@ -333,7 +459,7 @@ impl Board { /// assert_eq!(board.side_to_move(), Color::White); /// ``` #[inline] - pub fn side_to_move(&self) -> Color { + pub const fn side_to_move(&self) -> Color { self.side_to_move } @@ -442,8 +568,8 @@ impl Board { /// Add or remove a piece from the bitboards in this struct. fn xor(&mut self, piece: Piece, bb: BitBoard, color: Color) { unsafe { - *self.pieces.get_unchecked_mut(piece.to_index()) ^= bb; - *self.color_combined.get_unchecked_mut(color.to_index()) ^= bb; + *self.pieces.get_unchecked_mut(piece.into_index()) ^= bb; + *self.color_combined.get_unchecked_mut(color.into_index()) ^= bb; self.combined ^= bb; self.hash ^= Zobrist::piece(piece, bb.to_square(), color); } @@ -596,10 +722,8 @@ impl Board { // make sure there is no square with multiple pieces on it for x in ALL_PIECES.iter() { for y in ALL_PIECES.iter() { - if *x != *y { - if self.pieces(*x) & self.pieces(*y) != EMPTY { - return false; - } + if *x != *y && self.pieces(*x) & self.pieces(*y) != EMPTY { + return false; } } } @@ -668,12 +792,11 @@ impl Board { } // if we have castle rights, make sure we have a king on the (E, {1,8}) square, // depending on the color - if castle_rights != CastleRights::NoRights { - if self.pieces(Piece::King) & self.color_combined(*color) + if castle_rights != CastleRights::NoRights + && self.pieces(Piece::King) & self.color_combined(*color) != get_file(File::E) & get_rank(color.to_my_backrank()) - { - return false; - } + { + return false; } } @@ -683,7 +806,7 @@ impl Board { } // it checks out - return true; + true } /// Get a hash of the board. @@ -696,11 +819,11 @@ impl Board { 0 } ^ Zobrist::castles( - self.castle_rights[self.side_to_move.to_index()], + self.castle_rights[self.side_to_move.into_index()], self.side_to_move, ) ^ Zobrist::castles( - self.castle_rights[(!self.side_to_move).to_index()], + self.castle_rights[(!self.side_to_move).into_index()], !self.side_to_move, ) ^ if self.side_to_move == Color::Black { @@ -714,7 +837,8 @@ impl Board { /// /// Currently not implemented... #[inline] - pub fn get_pawn_hash(&self) -> u64 { + // Todo Implement + pub const fn get_pawn_hash(&self) -> u64 { 0 } @@ -739,7 +863,7 @@ impl Board { } /// Get the piece on a particular `Square`, defaults to Piece::King after a full search of if/else tree. - /// + /// /// ``` /// use chess::{Board, Piece, Square}; /// @@ -770,24 +894,22 @@ impl Board { } else { Piece::Bishop } + } else if self.pieces(Piece::Rook) & opp != EMPTY { + Piece::Rook + } else if self.pieces(Piece::Queen) & opp != EMPTY { + Piece::Queen } else { - if self.pieces(Piece::Rook) & opp != EMPTY { - Piece::Rook - } else if self.pieces(Piece::Queen) & opp != EMPTY { - Piece::Queen - } else { - Piece::King - } + Piece::King } } /// What color piece is on a particular square? - /// + /// /// ``` /// use chess::{Board, Square, Color}; - /// + /// /// let board = Board::default(); - /// + /// /// assert_eq!(board.color_on(Square::A1), Some(Color::White)); /// assert_eq!(board.color_on(Square::A3), None); /// ``` @@ -836,7 +958,7 @@ impl Board { /// assert_eq!(board.en_passant(), Some(Square::E5)); /// ``` #[inline] - pub fn en_passant(&self) -> Option { + pub const fn en_passant(&self) -> Option { self.en_passant } @@ -909,7 +1031,7 @@ impl Board { /// ``` #[inline] pub fn legal(&self, m: ChessMove) -> bool { - MoveGen::new_legal(&self).find(|x| *x == m).is_some() + MoveGen::new_legal(&self).any(|x| x == m) } /// Make a chess move onto a new board. @@ -1034,7 +1156,7 @@ impl Board { } } else if castles { let my_backrank = self.side_to_move.to_my_backrank(); - let index = dest.get_file().to_index(); + let index = dest.get_file().into_index(); let start = BitBoard::set(my_backrank, unsafe { *CASTLE_ROOK_START.get_unchecked(index) }); @@ -1115,7 +1237,7 @@ impl fmt::Display for Board { } impl TryFrom<&BoardBuilder> for Board { - type Error = Error; + type Error = InvalidError; fn try_from(fen: &BoardBuilder) -> Result { let mut board = Board::new(); @@ -1144,13 +1266,13 @@ impl TryFrom<&BoardBuilder> for Board { if board.is_sane() { Ok(board) } else { - Err(Error::InvalidBoard) + Err(InvalidError::Board) } } } impl TryFrom<&mut BoardBuilder> for Board { - type Error = Error; + type Error = InvalidError; fn try_from(fen: &mut BoardBuilder) -> Result { (&*fen).try_into() @@ -1158,7 +1280,7 @@ impl TryFrom<&mut BoardBuilder> for Board { } impl TryFrom for Board { - type Error = Error; + type Error = InvalidError; fn try_from(fen: BoardBuilder) -> Result { (&fen).try_into() @@ -1166,7 +1288,7 @@ impl TryFrom for Board { } impl FromStr for Board { - type Err = Error; + type Err = InvalidError; fn from_str(value: &str) -> Result { Ok(BoardBuilder::from_str(value)?.try_into()?) @@ -1181,3 +1303,11 @@ fn test_null_move_en_passant() { Board::from_str("rnbqkbnr/pppp2pp/8/4pP2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 0").unwrap(); assert_eq!(start.null_move().unwrap(), expected); } + +#[test] +fn check_startpos_correct() { + let startpos_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + let board = Board::from_str(startpos_fen).unwrap(); + let startpos = Board::STARTPOS; + assert_eq!(board, startpos, "Startpos is not correct"); +} \ No newline at end of file diff --git a/src/board_builder.rs b/src/board_builder.rs index 19808c6a..ecb389f7 100644 --- a/src/board_builder.rs +++ b/src/board_builder.rs @@ -1,11 +1,13 @@ +use arrayvec::ArrayVec; + use crate::board::Board; use crate::castle_rights::CastleRights; use crate::color::Color; -use crate::error::Error; +use crate::error::InvalidError; use crate::file::{File, ALL_FILES}; use crate::piece::Piece; use crate::rank::{Rank, ALL_RANKS}; -use crate::square::{Square, ALL_SQUARES}; +use crate::square::{Square, ALL_SQUARES, NUM_SQUARES}; use std::fmt; use std::ops::{Index, IndexMut}; @@ -75,7 +77,7 @@ impl BoardBuilder { /// # Ok(()) /// # } /// ``` - pub fn new() -> BoardBuilder { + pub const fn new() -> BoardBuilder { BoardBuilder { pieces: [None; 64], side_to_move: Color::White, @@ -113,13 +115,13 @@ impl BoardBuilder { ) -> BoardBuilder { let mut result = BoardBuilder { pieces: [None; 64], - side_to_move: side_to_move, + side_to_move, castle_rights: [white_castle_rights, black_castle_rights], - en_passant: en_passant, + en_passant, }; for piece in pieces.into_iter() { - result.pieces[piece.0.to_index()] = Some((piece.1, piece.2)); + result.pieces[piece.0.into_index()] = Some((piece.1, piece.2)); } result @@ -133,7 +135,7 @@ impl BoardBuilder { /// let bb: BoardBuilder = Board::default().into(); /// assert_eq!(bb.get_side_to_move(), Color::White); /// ``` - pub fn get_side_to_move(&self) -> Color { + pub const fn get_side_to_move(&self) -> Color { self.side_to_move } @@ -145,8 +147,8 @@ impl BoardBuilder { /// let bb: BoardBuilder = Board::default().into(); /// assert_eq!(bb.get_castle_rights(Color::White), CastleRights::Both); /// ``` - pub fn get_castle_rights(&self, color: Color) -> CastleRights { - self.castle_rights[color.to_index()] + pub const fn get_castle_rights(&self, color: Color) -> CastleRights { + self.castle_rights[color.into_index()] } /// Get the current en_passant square @@ -179,7 +181,7 @@ impl BoardBuilder { /// let mut bb = BoardBuilder::new(); /// bb.side_to_move(Color::Black); /// ``` - pub fn side_to_move<'a>(&'a mut self, color: Color) -> &'a mut Self { + pub fn side_to_move(&mut self, color: Color) -> &mut Self { self.side_to_move = color; self } @@ -196,12 +198,8 @@ impl BoardBuilder { /// let mut bb = BoardBuilder::new(); /// bb.castle_rights(Color::Black, CastleRights::Both); /// ``` - pub fn castle_rights<'a>( - &'a mut self, - color: Color, - castle_rights: CastleRights, - ) -> &'a mut Self { - self.castle_rights[color.to_index()] = castle_rights; + pub fn castle_rights(&mut self, color: Color, castle_rights: CastleRights) -> &mut Self { + unsafe { *self.castle_rights.get_unchecked_mut(color.into_index()) = castle_rights }; self } @@ -222,7 +220,7 @@ impl BoardBuilder { /// let mut bb = BoardBuilder::new(); /// bb.piece(Square::A8, Piece::Rook, Color::Black); /// ``` - pub fn piece<'a>(&'a mut self, square: Square, piece: Piece, color: Color) -> &'a mut Self { + pub fn piece(&mut self, square: Square, piece: Piece, color: Color) -> &mut Self { self[square] = Some((piece, color)); self } @@ -239,7 +237,7 @@ impl BoardBuilder { /// let mut bb: BoardBuilder = Board::default().into(); /// bb.clear_square(Square::A1); /// ``` - pub fn clear_square<'a>(&'a mut self, square: Square) -> &'a mut Self { + pub fn clear_square(&mut self, square: Square) -> &mut Self { self[square] = None; self } @@ -255,7 +253,7 @@ impl BoardBuilder { /// .piece(Square::E4, Piece::Pawn, Color::White) /// .en_passant(Some(File::E)); /// ``` - pub fn en_passant<'a>(&'a mut self, file: Option) -> &'a mut Self { + pub fn en_passant(&mut self, file: Option) -> &mut Self { self.en_passant = file; self } @@ -264,14 +262,14 @@ impl BoardBuilder { impl Index for BoardBuilder { type Output = Option<(Piece, Color)>; - fn index<'a>(&'a self, index: Square) -> &'a Self::Output { - &self.pieces[index.to_index()] + fn index(&self, index: Square) -> &Self::Output { + &self.pieces[index.into_index()] } } impl IndexMut for BoardBuilder { - fn index_mut<'a>(&'a mut self, index: Square) -> &'a mut Self::Output { - &mut self.pieces[index.to_index()] + fn index_mut(&mut self, index: Square) -> &mut Self::Output { + &mut self.pieces[index.into_index()] } } @@ -280,7 +278,7 @@ impl fmt::Display for BoardBuilder { let mut count = 0; for rank in ALL_RANKS.iter().rev() { for file in ALL_FILES.iter() { - let square = Square::make_square(*rank, *file).to_index(); + let square = Square::make_square(*rank, *file).into_index(); if self.pieces[square].is_some() && count != 0 { write!(f, "{}", count)?; @@ -315,12 +313,12 @@ impl fmt::Display for BoardBuilder { write!( f, "{}", - self.castle_rights[Color::White.to_index()].with_color(Color::White) + self.castle_rights[Color::White.into_index()].with_color(Color::White) )?; write!( f, "{}", - self.castle_rights[Color::Black.to_index()].with_color(Color::Black) + self.castle_rights[Color::Black.into_index()].with_color(Color::Black) )?; if self.castle_rights[0] == CastleRights::NoRights && self.castle_rights[1] == CastleRights::NoRights @@ -346,24 +344,26 @@ impl Default for BoardBuilder { } impl FromStr for BoardBuilder { - type Err = Error; + type Err = InvalidError; fn from_str(value: &str) -> Result { let mut cur_rank = Rank::Eighth; let mut cur_file = File::A; let mut fen = &mut BoardBuilder::new(); - let tokens: Vec<&str> = value.split(' ').collect(); - if tokens.len() < 4 { - return Err(Error::InvalidFen { - fen: value.to_string(), - }); - } + #[cfg(feature = "std")] + let invalid = || InvalidError::FEN { + fen: value.to_string(), + }; + #[cfg(not(feature = "std"))] + let invalid = || InvalidError::FEN; + + let mut tokens = value.split(' '); - let pieces = tokens[0]; - let side = tokens[1]; - let castles = tokens[2]; - let ep = tokens[3]; + let pieces = tokens.next().ok_or_else(invalid)?; + let side = tokens.next().ok_or_else(invalid)?; + let castles = tokens.next().ok_or_else(invalid)?; + let ep = tokens.next().ok_or_else(invalid)?; for x in pieces.chars() { match x { @@ -373,7 +373,7 @@ impl FromStr for BoardBuilder { } '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => { cur_file = - File::from_index(cur_file.to_index() + (x as usize) - ('0' as usize)); + File::from_index(cur_file.into_index() + (x as usize) - ('0' as usize)); } 'r' => { fen[Square::make_square(cur_rank, cur_file)] = @@ -436,9 +436,7 @@ impl FromStr for BoardBuilder { cur_file = cur_file.right(); } _ => { - return Err(Error::InvalidFen { - fen: value.to_string(), - }); + return Err(invalid()); } } } @@ -446,30 +444,28 @@ impl FromStr for BoardBuilder { "w" | "W" => fen = fen.side_to_move(Color::White), "b" | "B" => fen = fen.side_to_move(Color::Black), _ => { - return Err(Error::InvalidFen { - fen: value.to_string(), - }) + return Err(invalid()) } } - if castles.contains("K") && castles.contains("Q") { - fen.castle_rights[Color::White.to_index()] = CastleRights::Both; - } else if castles.contains("K") { - fen.castle_rights[Color::White.to_index()] = CastleRights::KingSide; - } else if castles.contains("Q") { - fen.castle_rights[Color::White.to_index()] = CastleRights::QueenSide; + if castles.contains('K') && castles.contains('Q') { + fen.castle_rights[Color::White.into_index()] = CastleRights::Both; + } else if castles.contains('K') { + fen.castle_rights[Color::White.into_index()] = CastleRights::KingSide; + } else if castles.contains('Q') { + fen.castle_rights[Color::White.into_index()] = CastleRights::QueenSide; } else { - fen.castle_rights[Color::White.to_index()] = CastleRights::NoRights; + fen.castle_rights[Color::White.into_index()] = CastleRights::NoRights; } - if castles.contains("k") && castles.contains("q") { - fen.castle_rights[Color::Black.to_index()] = CastleRights::Both; - } else if castles.contains("k") { - fen.castle_rights[Color::Black.to_index()] = CastleRights::KingSide; - } else if castles.contains("q") { - fen.castle_rights[Color::Black.to_index()] = CastleRights::QueenSide; + if castles.contains('k') && castles.contains('q') { + fen.castle_rights[Color::Black.into_index()] = CastleRights::Both; + } else if castles.contains('k') { + fen.castle_rights[Color::Black.into_index()] = CastleRights::KingSide; + } else if castles.contains('q') { + fen.castle_rights[Color::Black.into_index()] = CastleRights::QueenSide; } else { - fen.castle_rights[Color::Black.to_index()] = CastleRights::NoRights; + fen.castle_rights[Color::Black.into_index()] = CastleRights::NoRights; } if let Ok(sq) = Square::from_str(&ep) { @@ -482,7 +478,7 @@ impl FromStr for BoardBuilder { impl From<&Board> for BoardBuilder { fn from(board: &Board) -> Self { - let mut pieces = vec![]; + let mut pieces = ArrayVec::<_, NUM_SQUARES>::new(); for sq in ALL_SQUARES.iter() { if let Some(piece) = board.piece_on(*sq) { let color = board.color_on(*sq).unwrap(); diff --git a/src/cache_table.rs b/src/cache_table.rs index d57dc62f..6811d2b3 100644 --- a/src/cache_table.rs +++ b/src/cache_table.rs @@ -48,10 +48,7 @@ impl CacheTable { #[inline] pub fn add(&mut self, hash: u64, entry: T) { let e = unsafe { self.table.get_unchecked_mut((hash as usize) & self.mask) }; - *e = CacheTableEntry { - hash: hash, - entry: entry, - }; + *e = CacheTableEntry { hash, entry }; } /// Replace an entry in the hash table with a user-specified replacement policy specified by @@ -82,10 +79,7 @@ impl CacheTable { pub fn replace_if bool>(&mut self, hash: u64, entry: T, replace: F) { let e = unsafe { self.table.get_unchecked_mut((hash as usize) & self.mask) }; if replace(e.entry) { - *e = CacheTableEntry { - hash: hash, - entry: entry, - }; + *e = CacheTableEntry { hash, entry }; } } } diff --git a/src/castle_rights.rs b/src/castle_rights.rs index f245770b..b48c99f0 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -33,7 +33,7 @@ pub const ALL_CASTLE_RIGHTS: [CastleRights; NUM_CASTLE_RIGHTS] = [ /* fn square_to_castle_rights(color: Color, square: Square) -> CastleRights { let rank = if color.into() { Rank::1 } else { Rank::8 }; - if square.get_rank() != rank.to_index() { + if square.get_rank() != rank.into_index() { CastleRights::NoRights } else { match square.get_file() { @@ -72,46 +72,46 @@ impl CastleRights { /// Can I castle kingside? pub fn has_kingside(&self) -> bool { // Self::Both == 3 -> 0b11 & 0b01 == 0b01 👍 - self.to_index() & 1 == 1 + self.into_index() & 1 == 1 } /// Can I castle queenside? pub fn has_queenside(&self) -> bool { // Self::Both == 3 -> 0b11 & 0b10 == 0b10 👍 - self.to_index() & 2 == 2 + self.into_index() & 2 == 2 } /// What rights does this square enable? pub fn square_to_castle_rights(color: Color, sq: Square) -> CastleRights { CastleRights::from_index(unsafe { *CASTLES_PER_SQUARE - .get_unchecked(color.to_index()) - .get_unchecked(sq.to_index()) + .get_unchecked(color.into_index()) + .get_unchecked(sq.into_index()) } as usize) } /// What squares need to be empty to castle kingside? pub fn kingside_squares(&self, color: Color) -> BitBoard { - unsafe { *KINGSIDE_CASTLE_SQUARES.get_unchecked(color.to_index()) } + unsafe { *KINGSIDE_CASTLE_SQUARES.get_unchecked(color.into_index()) } } /// What squares need to be empty to castle queenside? pub fn queenside_squares(&self, color: Color) -> BitBoard { - unsafe { *QUEENSIDE_CASTLE_SQUARES.get_unchecked(color.to_index()) } + unsafe { *QUEENSIDE_CASTLE_SQUARES.get_unchecked(color.into_index()) } } /// Remove castle rights, and return a new `CastleRights`. pub fn remove(&self, remove: CastleRights) -> CastleRights { - CastleRights::from_index(self.to_index() & !remove.to_index()) + CastleRights::from_index(self.into_index() & !remove.into_index()) } /// Add some castle rights, and return a new `CastleRights`. pub fn add(&self, add: CastleRights) -> CastleRights { - CastleRights::from_index(self.to_index() | add.to_index()) + CastleRights::from_index(self.into_index() | add.into_index()) } /// Convert `CastleRights` to `usize` for table lookups - pub fn to_index(&self) -> usize { + pub fn into_index(&self) -> usize { *self as usize } diff --git a/src/chess_move.rs b/src/chess_move.rs index e18c26b2..1759a613 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -1,17 +1,19 @@ use crate::board::Board; -use crate::error::Error; +use crate::error::InvalidError; use crate::file::File; use crate::movegen::MoveGen; use crate::piece::Piece; use crate::rank::Rank; use crate::square::Square; +#[cfg(test)] +use crate::{ALL_PIECES, ALL_SQUARES}; use std::cmp::Ordering; use std::fmt; use std::str::FromStr; /// Represent a ChessMove in memory -#[derive(Clone, Copy, Eq, PartialOrd, PartialEq, Default, Debug, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Default, Debug, Hash)] pub struct ChessMove { source: Square, dest: Square, @@ -19,10 +21,47 @@ pub struct ChessMove { } impl ChessMove { + /// An invalid move, can make `Option` more efficient. See `into_option`. + pub const NULL_MOVE: ChessMove = ChessMove { + source: Square::A1, + dest: Square::A1, + promotion: None, + }; + + /// Check if this is an invalid (null) move. + #[inline] + pub fn is_null_move(&self) -> bool { + *self == Self::NULL_MOVE + } + + /// Convert this `ChessMove` into a **larger** but more useful `Option` if `self` is `ChessMove::NULL_MOVE`. + /// + /// Call `.from_option` to compress back into a `ChessMove`. + #[inline] + pub fn into_option(self) -> Option { + if self.is_null_move() { + None + } else { + Some(self) + } + } + + /// Convert an `Option` into a *smaller* but potentially invalid `ChessMove`. + /// + /// Call `.into_option` to expand back into an `Option`. + #[inline] + pub const fn from_option(value: Option) -> ChessMove { + if let Some(mov) = value { + mov + } else { + Self::NULL_MOVE + } + } + /// Create a new chess move, given a source `Square`, a destination `Square`, and an optional /// promotion `Piece` #[inline] - pub fn new(source: Square, dest: Square, promotion: Option) -> ChessMove { + pub const fn new(source: Square, dest: Square, promotion: Option) -> ChessMove { ChessMove { source, dest, @@ -32,19 +71,19 @@ impl ChessMove { /// Get the source square (square the piece is currently on). #[inline] - pub fn get_source(&self) -> Square { + pub const fn get_source(&self) -> Square { self.source } /// Get the destination square (square the piece is going to). #[inline] - pub fn get_dest(&self) -> Square { + pub const fn get_dest(&self) -> Square { self.dest } /// Get the promotion piece (maybe). #[inline] - pub fn get_promotion(&self) -> Option { + pub const fn get_promotion(&self) -> Option { self.promotion } /// Convert a SAN (Standard Algebraic Notation) move into a `ChessMove` @@ -58,7 +97,7 @@ impl ChessMove { /// ChessMove::new(Square::E2, Square::E4, None) /// ); /// ``` - pub fn from_san(board: &Board, move_text: &str) -> Result { + pub fn from_san(board: &Board, move_text: &str) -> Result { // Castles first... if move_text == "O-O" || move_text == "O-O-O" { let rank = board.side_to_move().to_my_backrank(); @@ -73,7 +112,7 @@ impl ChessMove { if MoveGen::new_legal(&board).any(|l| l == m) { return Ok(m); } else { - return Err(Error::InvalidSanMove); + return Err(InvalidError::SanMove); } } @@ -127,7 +166,7 @@ impl ChessMove { // [Optional Check(mate) Specifier] ("" | "+" | "#") // [Optional En Passant Specifier] ("" | " e.p.") - let error = Error::InvalidSanMove; + let error = InvalidError::SanMove; let mut cur_index: usize = 0; let moving_piece = match move_text .get(cur_index..(cur_index + 1)) @@ -234,14 +273,9 @@ impl ChessMove { _ => None, }; - let takes = if let Some(s) = move_text.get(cur_index..(cur_index + 1)) { - match s { - "x" => { - cur_index += 1; - true - } - _ => false, - } + let takes = if let Some("x") = move_text.get(cur_index..(cur_index + 1)) { + cur_index += 1; + true } else { false }; @@ -352,17 +386,15 @@ impl ChessMove { return Err(error); } + let piece_exists = board.piece_on(m.get_dest()).is_some(); + // takes is complicated, because of e.p. - if !takes { - if board.piece_on(m.get_dest()).is_some() { - continue; - } + if !takes && piece_exists { + continue; } - if !ep && takes { - if board.piece_on(m.get_dest()).is_none() { - continue; - } + if !ep && takes && !piece_exists { + continue; } found_move = Some(m); @@ -370,13 +402,54 @@ impl ChessMove { found_move.ok_or(error.clone()) } + + /// Encode this `ChessMove` into a `u16`. + /// + /// This will properly encode `Piece::Pawn` and `Piece::King` despite these being illegal promotions. + pub fn encode(&self) -> u16 { + let Self { + source, + dest, + promotion, + } = self; + let mut acc = 0u16; + acc |= (source.to_int() as u16) << 10; + acc |= (dest.to_int() as u16) << 4; + acc |= promotion + .map(|promotion| (promotion as u8) as u16) + .unwrap_or(0); + acc + } + + /// Decode a `u16` into its representative `ChessMove`. + /// + /// Will decode promotions to `Piece::Pawn` and `Piece::King` despite these being illegal promotions. + pub fn decode(coded: u16) -> Self { + const SRCE_MASK: u16 = 0b1111_1100_0000_0000; // << 10 + const DEST_MASK: u16 = 0b0000_0011_1111_0000; // << 4 + const PROM_MASK: u16 = 0b0000_0000_0000_1111; + + Self { + source: Square::new(((coded & SRCE_MASK) >> 10) as u8), + dest: Square::new(((coded & DEST_MASK) >> 4) as u8), + promotion: match coded & PROM_MASK { + 1 => Some(Piece::Pawn), + 2 => Some(Piece::Knight), + 3 => Some(Piece::Bishop), + 4 => Some(Piece::Rook), + 5 => Some(Piece::Queen), + 6 => Some(Piece::King), + _ => None, + }, + } + } } impl fmt::Display for ChessMove { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.promotion { - None => write!(f, "{}{}", self.source, self.dest), Some(x) => write!(f, "{}{}{}", self.source, self.dest, x), + None => write!(f, "{}{}", self.source, self.dest), } } } @@ -403,6 +476,12 @@ impl Ord for ChessMove { } } +impl PartialOrd for ChessMove { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Ord::cmp(self, other)) + } +} + /// Convert a UCI `String` to a move. If invalid, return `None` /// ``` /// use chess::{ChessMove, Square, Piece}; @@ -413,20 +492,20 @@ impl Ord for ChessMove { /// assert_eq!(ChessMove::from_str("e7e8q").expect("Valid Move"), mv); /// ``` impl FromStr for ChessMove { - type Err = Error; + type Err = InvalidError; fn from_str(s: &str) -> Result { - let source = Square::from_str(s.get(0..2).ok_or(Error::InvalidUciMove)?)?; - let dest = Square::from_str(s.get(2..4).ok_or(Error::InvalidUciMove)?)?; + let source = Square::from_str(s.get(0..2).ok_or(InvalidError::UciMove)?)?; + let dest = Square::from_str(s.get(2..4).ok_or(InvalidError::UciMove)?)?; let mut promo = None; if s.len() == 5 { - promo = Some(match s.chars().last().ok_or(Error::InvalidUciMove)? { + promo = Some(match s.chars().last().ok_or(InvalidError::UciMove)? { 'q' => Piece::Queen, 'r' => Piece::Rook, 'n' => Piece::Knight, 'b' => Piece::Bishop, - _ => return Err(Error::InvalidUciMove), + _ => return Err(InvalidError::UciMove), }); } @@ -442,3 +521,17 @@ fn test_basic_moves() { ChessMove::new(Square::E2, Square::E4, None) ); } + +#[test] +fn encoding_decoding() { + for source in ALL_SQUARES { + for dest in ALL_SQUARES { + for promotion in ALL_PIECES.iter().copied().map(Some).chain([None]) { + let mov = ChessMove::new(source, dest, promotion); + let encoded = mov.encode(); + let decoded = ChessMove::decode(encoded); + assert_eq!(mov, decoded, "Successfully decoded"); + } + } + } +} diff --git a/src/color.rs b/src/color.rs index 12e7ab94..8968f7d3 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,6 +2,7 @@ use crate::rank::Rank; use std::ops::Not; /// Represent a color. +#[repr(u8)] #[derive(PartialOrd, PartialEq, Eq, Copy, Clone, Debug, Hash)] pub enum Color { White = 0, @@ -16,14 +17,14 @@ pub const ALL_COLORS: [Color; NUM_COLORS] = [Color::White, Color::Black]; impl Color { /// Convert the `Color` to a `usize` for table lookups. #[inline] - pub fn to_index(&self) -> usize { - *self as usize + pub const fn into_index(self) -> usize { + self as usize } /// Convert a `Color` to my backrank, which represents the starting rank /// for my pieces. #[inline] - pub fn to_my_backrank(&self) -> Rank { + pub const fn to_my_backrank(&self) -> Rank { match *self { Color::White => Rank::First, Color::Black => Rank::Eighth, @@ -33,7 +34,7 @@ impl Color { /// Convert a `Color` to my opponents backrank, which represents the starting rank for the /// opponents pieces. #[inline] - pub fn to_their_backrank(&self) -> Rank { + pub const fn to_their_backrank(&self) -> Rank { match *self { Color::White => Rank::Eighth, Color::Black => Rank::First, @@ -42,7 +43,7 @@ impl Color { /// Convert a `Color` to my second rank, which represents the starting rank for my pawns. #[inline] - pub fn to_second_rank(&self) -> Rank { + pub const fn to_second_rank(&self) -> Rank { match *self { Color::White => Rank::Second, Color::Black => Rank::Seventh, @@ -52,7 +53,7 @@ impl Color { /// Convert a `Color` to my fourth rank, which represents the rank of my pawns when /// moving two squares forward. #[inline] - pub fn to_fourth_rank(&self) -> Rank { + pub const fn to_fourth_rank(&self) -> Rank { match *self { Color::White => Rank::Fourth, Color::Black => Rank::Fifth, @@ -61,7 +62,7 @@ impl Color { /// Convert a `Color` to my seventh rank, which represents the rank before pawn promotion. #[inline] - pub fn to_seventh_rank(&self) -> Rank { + pub const fn to_seventh_rank(&self) -> Rank { match *self { Color::White => Rank::Seventh, Color::Black => Rank::Second, diff --git a/src/error.rs b/src/error.rs index 48ef2261..a71544db 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,52 +2,45 @@ use std::fmt; /// Sometimes, bad stuff happens. #[derive(Clone, Debug)] -#[cfg(feature = "std")] -pub enum Error { +pub enum InvalidError { /// The FEN string is invalid - InvalidFen { fen: String }, + #[cfg(feature = "std")] + FEN { fen: String }, + #[cfg(not(feature = "std"))] + FEN, /// The board created from BoardBuilder was found to be invalid - InvalidBoard, + Board, /// An attempt was made to create a square from an invalid string - InvalidSquare, + Square, /// An attempt was made to create a move from an invalid SAN string - InvalidSanMove, + SanMove, /// An atempt was made to create a move from an invalid UCI string - InvalidUciMove, + UciMove, /// An attempt was made to convert a string not equal to "1"-"8" to a rank - InvalidRank, + Rank, /// An attempt was made to convert a string not equal to "a"-"h" to a file - InvalidFile, + File, } -impl fmt::Display for Error { +impl fmt::Display for InvalidError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::InvalidFen{ fen: s } => write!(f, "Invalid FEN string: {}", s), - Self::InvalidBoard => write!(f, "The board specified did not pass sanity checks. Are you sure the kings exist and the side to move cannot capture the opposing king?"), - Self::InvalidSquare => write!(f, "The string specified does not contain a valid algebraic notation square."), - Self::InvalidSanMove => write!(f, "The string specified does not contain a valid SAN notation move"), - Self::InvalidUciMove => write!(f, "The string specified does not contain a valid UCI notation move"), - Self::InvalidRank => write!(f, "The string specified does not contain a valid rank."), - Self::InvalidFile => write!(f, "The string specified does not contain a valid file.") + #[cfg(feature="std")] + Self::FEN{ fen: s } => write!(f, "Invalid FEN string: {}", s), + #[cfg(not(feature="std"))] + Self::FEN => write!(f, "Invalid FEN string."), + Self::Board => write!(f, "The board specified did not pass sanity checks. Are you sure the kings exist and the side to move cannot capture the opposing king?"), + Self::Square => write!(f, "The string specified does not contain a valid algebraic notation square."), + Self::SanMove => write!(f, "The string specified does not contain a valid SAN notation move"), + Self::UciMove => write!(f, "The string specified does not contain a valid UCI notation move"), + Self::Rank => write!(f, "The string specified does not contain a valid rank."), + Self::File => write!(f, "The string specified does not contain a valid file.") } } } - -#[derive(Clone, Debug)] -#[cfg(not(feature = "std"))] -pub enum Error { - InvalidFen, - InvalidBoard, - InvalidSquare, - InvalidSanMove, - InvalidUciMove, - InvalidRank, - InvalidFile, -} diff --git a/src/file.rs b/src/file.rs index e058084b..e07e56df 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,4 +1,4 @@ -use crate::error::Error; +use crate::error::InvalidError; use std::str::FromStr; /// Describe a file (column) on a chess board @@ -33,7 +33,7 @@ pub const ALL_FILES: [File; NUM_FILES] = [ impl File { /// Convert a `usize` into a `File` (the inverse of to_index). If i > 7, wrap around. #[inline] - pub fn from_index(i: usize) -> File { + pub const fn from_index(i: usize) -> File { // match is optimized to no-op with opt-level=1 with rustc 1.53.0 match i & 7 { 0 => File::A, @@ -50,30 +50,31 @@ impl File { /// Go one file to the left. If impossible, wrap around. #[inline] - pub fn left(&self) -> File { - File::from_index(self.to_index().wrapping_sub(1)) + pub const fn left(&self) -> File { + File::from_index(self.into_index().wrapping_sub(1)) } /// Go one file to the right. If impossible, wrap around. #[inline] - pub fn right(&self) -> File { - File::from_index(self.to_index() + 1) + pub const fn right(&self) -> File { + File::from_index(self.into_index() + 1) } /// Convert this `File` into a `usize` from 0 to 7 inclusive. #[inline] - pub fn to_index(&self) -> usize { - *self as usize + pub const fn into_index(self) -> usize { + self as usize } } impl FromStr for File { - type Err = Error; + type Err = InvalidError; fn from_str(s: &str) -> Result { - if s.len() < 1 { - return Err(Error::InvalidFile); + if s.is_empty() { + return Err(InvalidError::File); } + match s.chars().next().unwrap() { 'a' => Ok(File::A), 'b' => Ok(File::B), @@ -83,7 +84,7 @@ impl FromStr for File { 'f' => Ok(File::F), 'g' => Ok(File::G), 'h' => Ok(File::H), - _ => Err(Error::InvalidFile), + _ => Err(InvalidError::File), } } } diff --git a/src/game.rs b/src/game.rs index bee7ae19..d6f6d0e6 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,7 +1,7 @@ use crate::board::{Board, BoardStatus}; use crate::chess_move::ChessMove; use crate::color::Color; -use crate::error::Error; +use crate::error::InvalidError; use crate::movegen::MoveGen; use crate::piece::Piece; use std::str::FromStr; @@ -105,7 +105,7 @@ impl Game { } BoardStatus::Stalemate => Some(GameResult::Stalemate), BoardStatus::Ongoing => { - if self.moves.len() == 0 { + if self.moves.is_empty() { None } else if self.moves[self.moves.len() - 1] == Action::AcceptDraw { Some(GameResult::DrawAccepted) @@ -159,11 +159,8 @@ impl Game { let mut copy = self.start_pos; for x in self.moves.iter() { - match *x { - Action::MakeMove(m) => { - copy = copy.make_move_new(m); - } - _ => {} + if let Action::MakeMove(m) = x { + copy = copy.make_move_new(*m); } } @@ -211,31 +208,28 @@ impl Game { // and filling a list of legal_moves_per_turn list for 3-fold repitition legal_moves_per_turn.push((board.get_hash(), MoveGen::new_legal(&board).collect())); for x in self.moves.iter() { - match *x { - Action::MakeMove(m) => { - let white_castle_rights = board.castle_rights(Color::White); - let black_castle_rights = board.castle_rights(Color::Black); - if board.piece_on(m.get_source()) == Some(Piece::Pawn) { - reversible_moves = 0; - legal_moves_per_turn.clear(); - } else if board.piece_on(m.get_dest()).is_some() { - reversible_moves = 0; - legal_moves_per_turn.clear(); - } else { - reversible_moves += 1; - } - board = board.make_move_new(m); - - if board.castle_rights(Color::White) != white_castle_rights - || board.castle_rights(Color::Black) != black_castle_rights - { - reversible_moves = 0; - legal_moves_per_turn.clear(); - } - legal_moves_per_turn - .push((board.get_hash(), MoveGen::new_legal(&board).collect())); + if let Action::MakeMove(m) = *x { + let white_castle_rights = board.castle_rights(Color::White); + let black_castle_rights = board.castle_rights(Color::Black); + if board.piece_on(m.get_source()) == Some(Piece::Pawn) { + reversible_moves = 0; + legal_moves_per_turn.clear(); + } else if board.piece_on(m.get_dest()).is_some() { + reversible_moves = 0; + legal_moves_per_turn.clear(); + } else { + reversible_moves += 1; + } + board = board.make_move_new(m); + + if board.castle_rights(Color::White) != white_castle_rights + || board.castle_rights(Color::Black) != black_castle_rights + { + reversible_moves = 0; + legal_moves_per_turn.clear(); } - _ => {} + + legal_moves_per_turn.push((board.get_hash(), MoveGen::new_legal(&board).collect())); } } @@ -254,7 +248,7 @@ impl Game { } } - return false; + false } /// Declare a draw by 3-fold repitition or 50-move rule. @@ -329,10 +323,7 @@ impl Game { let move_count = self .moves .iter() - .filter(|m| match *m { - Action::MakeMove(_) => true, - _ => false, - }) + .filter(|m| matches!(*m, Action::MakeMove(_))) .count() + if self.start_pos.side_to_move() == Color::White { 0 @@ -361,7 +352,7 @@ impl Game { return false; } self.moves.push(Action::OfferDraw(color)); - return true; + true } /// Accept a draw offer from my opponent. @@ -383,20 +374,19 @@ impl Game { if self.result().is_some() { return false; } - if self.moves.len() > 0 { - if self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::White) - || self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::Black) - { - self.moves.push(Action::AcceptDraw); - return true; - } + if !self.moves.is_empty() + && (self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::White) + || self.moves[self.moves.len() - 1] == Action::OfferDraw(Color::Black)) + { + self.moves.push(Action::AcceptDraw); + return true; } - if self.moves.len() > 1 { - if self.moves[self.moves.len() - 2] == Action::OfferDraw(!self.side_to_move()) { - self.moves.push(Action::AcceptDraw); - return true; - } + if self.moves.len() > 1 + && self.moves[self.moves.len() - 2] == Action::OfferDraw(!self.side_to_move()) + { + self.moves.push(Action::AcceptDraw); + return true; } false @@ -415,23 +405,29 @@ impl Game { return false; } self.moves.push(Action::Resign(color)); - return true; + true } } impl FromStr for Game { - type Err = Error; + type Err = InvalidError; fn from_str(fen: &str) -> Result { Ok(Game::new_with_board(Board::from_str(fen)?)) } } +impl Default for Game { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] pub fn fake_pgn_parser(moves: &str) -> Game { moves .split_whitespace() - .filter(|s| !s.ends_with(".")) + .filter(|s| !s.ends_with('.')) .fold(Game::new(), |mut g, m| { g.make_move(ChessMove::from_san(&g.current_position(), m).expect("Valid SAN Move")); g diff --git a/src/gen_tables/between.rs b/src/gen_tables/between.rs index d6f711b1..4f2e42f0 100644 --- a/src/gen_tables/between.rs +++ b/src/gen_tables/between.rs @@ -22,15 +22,15 @@ pub fn gen_between() { for src in ALL_SQUARES.iter() { for dest in ALL_SQUARES.iter() { unsafe { - BETWEEN[src.to_index()][dest.to_index()] = ALL_SQUARES + BETWEEN[src.into_index()][dest.into_index()] = ALL_SQUARES .iter() .filter(|test| { - let src_rank = src.get_rank().to_index() as i8; - let src_file = src.get_file().to_index() as i8; - let dest_rank = dest.get_rank().to_index() as i8; - let dest_file = dest.get_file().to_index() as i8; - let test_rank = test.get_rank().to_index() as i8; - let test_file = test.get_file().to_index() as i8; + let src_rank = src.get_rank().into_index() as i8; + let src_file = src.get_file().into_index() as i8; + let dest_rank = dest.get_rank().into_index() as i8; + let dest_file = dest.get_file().into_index() as i8; + let test_rank = test.get_rank().into_index() as i8; + let test_file = test.get_file().into_index() as i8; // test diagonals first, as above if (src_rank - dest_rank).abs() == (src_file - dest_file).abs() @@ -59,14 +59,14 @@ pub fn gen_between() { // Write the BETWEEN array to the specified file. pub fn write_between(f: &mut File) { - write!(f, "const BETWEEN: [[BitBoard; 64]; 64] = [[\n").unwrap(); + writeln!(f, "const BETWEEN: [[BitBoard; 64]; 64] = [[").unwrap(); for i in 0..64 { for j in 0..64 { - unsafe { write!(f, " BitBoard({}),\n", BETWEEN[i][j].0).unwrap() }; + unsafe { writeln!(f, " BitBoard({}),", BETWEEN[i][j].0).unwrap() }; } if i != 63 { - write!(f, " ], [\n").unwrap(); + writeln!(f, " ], [").unwrap(); } } - write!(f, "]];\n").unwrap(); + writeln!(f, "]];").unwrap(); } diff --git a/src/gen_tables/generate_all_tables.rs b/src/gen_tables/generate_all_tables.rs index f2ed8560..64edd54a 100644 --- a/src/gen_tables/generate_all_tables.rs +++ b/src/gen_tables/generate_all_tables.rs @@ -5,8 +5,6 @@ #![allow(dead_code)] // it to be easily followed. -extern crate rand; - use std::env; use std::fs::File; use std::path::Path; @@ -40,7 +38,7 @@ pub fn generate_all_tables() { let out_dir = env::var("OUT_DIR").unwrap(); let magic_path = Path::new(&out_dir).join("magic_gen.rs"); - let mut f = File::create(&magic_path).unwrap(); + let mut f = File::create(magic_path).unwrap(); write_king_moves(&mut f); write_knight_moves(&mut f); @@ -55,7 +53,7 @@ pub fn generate_all_tables() { write_bitboard_data(&mut f); let zobrist_path = Path::new(&out_dir).join("zobrist_gen.rs"); - let mut z = File::create(&zobrist_path).unwrap(); + let mut z = File::create(zobrist_path).unwrap(); write_zobrist(&mut z); } diff --git a/src/gen_tables/king.rs b/src/gen_tables/king.rs index 2167613e..67f96bc7 100644 --- a/src/gen_tables/king.rs +++ b/src/gen_tables/king.rs @@ -15,13 +15,13 @@ static mut QUEENSIDE_CASTLE_SQUARES: [BitBoard; 2] = [EMPTY; 2]; pub fn gen_king_moves() { for src in ALL_SQUARES.iter() { unsafe { - KING_MOVES[src.to_index()] = ALL_SQUARES + KING_MOVES[src.into_index()] = ALL_SQUARES .iter() .filter(|dest| { - let src_rank = src.get_rank().to_index() as i8; - let src_file = src.get_file().to_index() as i8; - let dest_rank = dest.get_rank().to_index() as i8; - let dest_file = dest.get_file().to_index() as i8; + let src_rank = src.get_rank().into_index() as i8; + let src_file = src.get_file().into_index() as i8; + let dest_rank = dest.get_rank().into_index() as i8; + let dest_file = dest.get_file().into_index() as i8; ((src_rank - dest_rank).abs() == 1 || (src_rank - dest_rank).abs() == 0) && ((src_file - dest_file).abs() == 1 || (src_file - dest_file).abs() == 0) @@ -38,7 +38,7 @@ pub fn gen_king_moves() { fn gen_kingside_castle_squares() { for color in ALL_COLORS.iter() { unsafe { - KINGSIDE_CASTLE_SQUARES[color.to_index()] = + KINGSIDE_CASTLE_SQUARES[color.into_index()] = BitBoard::set(color.to_my_backrank(), ChessFile::F) ^ BitBoard::set(color.to_my_backrank(), ChessFile::G); } @@ -48,7 +48,7 @@ fn gen_kingside_castle_squares() { fn gen_queenside_castle_squares() { for color in ALL_COLORS.iter() { unsafe { - QUEENSIDE_CASTLE_SQUARES[color.to_index()] = + QUEENSIDE_CASTLE_SQUARES[color.into_index()] = BitBoard::set(color.to_my_backrank(), ChessFile::B) ^ BitBoard::set(color.to_my_backrank(), ChessFile::C) ^ BitBoard::set(color.to_my_backrank(), ChessFile::D); @@ -67,37 +67,37 @@ fn gen_castle_moves() -> BitBoard { // Write the KING_MOVES array to the specified file. pub fn write_king_moves(f: &mut File) { - write!(f, "const KING_MOVES: [BitBoard; 64] = [\n").unwrap(); + writeln!(f, "const KING_MOVES: [BitBoard; 64] = [").unwrap(); for i in 0..64 { - unsafe { write!(f, " BitBoard({}),\n", KING_MOVES[i].0).unwrap() }; + unsafe { writeln!(f, " BitBoard({}),", KING_MOVES[i].0).unwrap() }; } - write!(f, "];\n").unwrap(); + writeln!(f, "];").unwrap(); - write!(f, "pub const KINGSIDE_CASTLE_SQUARES: [BitBoard; 2] = [\n").unwrap(); + writeln!(f, "pub const KINGSIDE_CASTLE_SQUARES: [BitBoard; 2] = [").unwrap(); unsafe { - write!( + writeln!( f, - " BitBoard({}), BitBoard({})];\n", + " BitBoard({}), BitBoard({})];", KINGSIDE_CASTLE_SQUARES[0].0, KINGSIDE_CASTLE_SQUARES[1].0 ) .unwrap() }; - write!(f, "pub const QUEENSIDE_CASTLE_SQUARES: [BitBoard; 2] = [\n").unwrap(); + writeln!(f, "pub const QUEENSIDE_CASTLE_SQUARES: [BitBoard; 2] = [").unwrap(); unsafe { - write!( + writeln!( f, - " BitBoard({}), BitBoard({})];\n", + " BitBoard({}), BitBoard({})];", QUEENSIDE_CASTLE_SQUARES[0].0, QUEENSIDE_CASTLE_SQUARES[1].0 ) .unwrap() }; - write!( + writeln!( f, - "const CASTLE_MOVES: BitBoard = BitBoard({});\n", + "const CASTLE_MOVES: BitBoard = BitBoard({});", gen_castle_moves().0 ) .unwrap(); diff --git a/src/gen_tables/knights.rs b/src/gen_tables/knights.rs index 352c3bbf..12d47a79 100644 --- a/src/gen_tables/knights.rs +++ b/src/gen_tables/knights.rs @@ -11,13 +11,13 @@ static mut KNIGHT_MOVES: [BitBoard; 64] = [EMPTY; 64]; pub fn gen_knight_moves() { for src in ALL_SQUARES.iter() { unsafe { - KNIGHT_MOVES[src.to_index()] = ALL_SQUARES + KNIGHT_MOVES[src.into_index()] = ALL_SQUARES .iter() .filter(|dest| { - let src_rank = src.get_rank().to_index() as i8; - let src_file = src.get_file().to_index() as i8; - let dest_rank = dest.get_rank().to_index() as i8; - let dest_file = dest.get_file().to_index() as i8; + let src_rank = src.get_rank().into_index() as i8; + let src_file = src.get_file().into_index() as i8; + let dest_rank = dest.get_rank().into_index() as i8; + let dest_file = dest.get_file().into_index() as i8; ((src_rank - dest_rank).abs() == 2 && (src_file - dest_file).abs() == 1) || ((src_rank - dest_rank).abs() == 1 && (src_file - dest_file).abs() == 2) @@ -29,9 +29,9 @@ pub fn gen_knight_moves() { // Write the KNIGHT_MOVES array to the specified file. pub fn write_knight_moves(f: &mut File) { - write!(f, "const KNIGHT_MOVES: [BitBoard; 64] = [\n").unwrap(); + writeln!(f, "const KNIGHT_MOVES: [BitBoard; 64] = [").unwrap(); for i in 0..64 { - unsafe { write!(f, " BitBoard({}),\n", KNIGHT_MOVES[i].0).unwrap() }; + unsafe { writeln!(f, " BitBoard({}),", KNIGHT_MOVES[i].0).unwrap() }; } - write!(f, "];\n").unwrap(); + writeln!(f, "];").unwrap(); } diff --git a/src/gen_tables/lines.rs b/src/gen_tables/lines.rs index 0b336056..b19a138d 100644 --- a/src/gen_tables/lines.rs +++ b/src/gen_tables/lines.rs @@ -13,15 +13,15 @@ pub fn gen_lines() { for src in ALL_SQUARES.iter() { for dest in ALL_SQUARES.iter() { unsafe { - LINE[src.to_index()][dest.to_index()] = ALL_SQUARES + LINE[src.into_index()][dest.into_index()] = ALL_SQUARES .iter() .filter(|test| { - let src_rank = src.get_rank().to_index() as i8; - let src_file = src.get_file().to_index() as i8; - let dest_rank = dest.get_rank().to_index() as i8; - let dest_file = dest.get_file().to_index() as i8; - let test_rank = test.get_rank().to_index() as i8; - let test_file = test.get_file().to_index() as i8; + let src_rank = src.get_rank().into_index() as i8; + let src_file = src.get_file().into_index() as i8; + let dest_rank = dest.get_rank().into_index() as i8; + let dest_file = dest.get_file().into_index() as i8; + let test_rank = test.get_rank().into_index() as i8; + let test_file = test.get_file().into_index() as i8; // test diagonals first if (src_rank - dest_rank).abs() == (src_file - dest_file).abs() @@ -48,14 +48,14 @@ pub fn gen_lines() { // Write the LINE array to the specified file. pub fn write_lines(f: &mut File) { - write!(f, "const LINE: [[BitBoard; 64]; 64] = [[\n").unwrap(); + writeln!(f, "const LINE: [[BitBoard; 64]; 64] = [[").unwrap(); for i in 0..64 { for j in 0..64 { - unsafe { write!(f, " BitBoard({}),\n", LINE[i][j].0).unwrap() }; + unsafe { writeln!(f, " BitBoard({}),", LINE[i][j].0).unwrap() }; } if i != 63 { - write!(f, " ], [\n").unwrap(); + writeln!(f, " ], [").unwrap(); } } - write!(f, "]];\n").unwrap(); + writeln!(f, "]];").unwrap(); } diff --git a/src/gen_tables/magic.rs b/src/gen_tables/magic.rs index 1eb94399..1f05f20a 100644 --- a/src/gen_tables/magic.rs +++ b/src/gen_tables/magic.rs @@ -75,7 +75,7 @@ fn generate_magic(sq: Square, piece: Piece, cur_offset: usize) -> usize { let mut new_magic = Magic { magic_number: EMPTY, - mask: mask, + mask, offset: new_offset as u32, rightshift: ((questions.len() as u64).leading_zeros() + 1) as u8, }; @@ -107,7 +107,7 @@ fn generate_magic(sq: Square, piece: Piece, cur_offset: usize) -> usize { } unsafe { - MAGIC_NUMBERS[if piece == Piece::Rook { 0 } else { 1 }][sq.to_index()] = new_magic; + MAGIC_NUMBERS[if piece == Piece::Rook { 0 } else { 1 }][sq.into_index()] = new_magic; for i in 0..questions.len() { let j = (new_magic.magic_number * questions[i]).to_size(new_magic.rightshift); @@ -136,19 +136,19 @@ pub fn gen_all_magic() { // Write the MAGIC_NUMBERS and MOVES arrays to the specified file. pub fn write_magic(f: &mut File) { - write!(f, "#[derive(Copy, Clone)]\n").unwrap(); - write!(f, "struct Magic {{\n").unwrap(); - write!(f, " magic_number: BitBoard,\n").unwrap(); - write!(f, " mask: BitBoard,\n").unwrap(); - write!(f, " offset: u32,\n").unwrap(); - write!(f, " rightshift: u8\n").unwrap(); - write!(f, "}}\n\n").unwrap(); - - write!(f, "const MAGIC_NUMBERS: [[Magic; 64]; 2] = [[\n").unwrap(); + writeln!(f, "#[derive(Copy, Clone)]").unwrap(); + writeln!(f, "struct Magic {{").unwrap(); + writeln!(f, " magic_number: BitBoard,").unwrap(); + writeln!(f, " mask: BitBoard,").unwrap(); + writeln!(f, " offset: u32,").unwrap(); + writeln!(f, " rightshift: u8").unwrap(); + writeln!(f, "}}\n").unwrap(); + + writeln!(f, "const MAGIC_NUMBERS: [[Magic; 64]; 2] = [[").unwrap(); for i in 0..2 { for j in 0..64 { unsafe { - write!(f, " Magic {{ magic_number: BitBoard({}), mask: BitBoard({}), offset: {}, rightshift: {} }},\n", + writeln!(f, " Magic {{ magic_number: BitBoard({}), mask: BitBoard({}), offset: {}, rightshift: {} }},", MAGIC_NUMBERS[i][j].magic_number.0, MAGIC_NUMBERS[i][j].mask.0, MAGIC_NUMBERS[i][j].offset, @@ -156,16 +156,16 @@ pub fn write_magic(f: &mut File) { } } if i != 1 { - write!(f, "], [\n").unwrap(); + writeln!(f, "], [").unwrap(); } } - write!(f, "]];\n").unwrap(); + writeln!(f, "]];").unwrap(); unsafe { - write!(f, "const MOVES: [BitBoard; {}] = [\n", GENERATED_NUM_MOVES).unwrap(); + writeln!(f, "const MOVES: [BitBoard; {}] = [", GENERATED_NUM_MOVES).unwrap(); for i in 0..GENERATED_NUM_MOVES { - write!(f, " BitBoard({}),\n", MOVES[i].0).unwrap(); + writeln!(f, " BitBoard({}),", MOVES[i].0).unwrap(); } } - write!(f, "];\n").unwrap(); + writeln!(f, "];").unwrap(); } diff --git a/src/gen_tables/magic_helpers.rs b/src/gen_tables/magic_helpers.rs index 97bc365c..59dbf92a 100644 --- a/src/gen_tables/magic_helpers.rs +++ b/src/gen_tables/magic_helpers.rs @@ -35,16 +35,16 @@ fn rook_directions() -> Vec Option> { // Return a list of directions for the bishop. fn bishop_directions() -> Vec Option> { fn nw(sq: Square) -> Option { - sq.left().map_or(None, |s| s.up()) + sq.left().and_then(|s| s.up()) } fn ne(sq: Square) -> Option { - sq.right().map_or(None, |s| s.up()) + sq.right().and_then(|s| s.up()) } fn sw(sq: Square) -> Option { - sq.left().map_or(None, |s| s.down()) + sq.left().and_then(|s| s.down()) } fn se(sq: Square) -> Option { - sq.right().map_or(None, |s| s.down()) + sq.right().and_then(|s| s.down()) } vec![nw, ne, sw, se] @@ -110,7 +110,7 @@ pub fn questions_and_answers(sq: Square, piece: Piece) -> (Vec, Vec PAWN_MOVES[color.to_index()][src.to_index()] = EMPTY, + None => PAWN_MOVES[color.into_index()][src.into_index()] = EMPTY, Some(x) => { - PAWN_MOVES[color.to_index()][src.to_index()] = BitBoard::from_square(x) + PAWN_MOVES[color.into_index()][src.into_index()] = BitBoard::from_square(x) } }; } @@ -40,21 +40,21 @@ pub fn gen_pawn_attacks() { for color in ALL_COLORS.iter() { for src in ALL_SQUARES.iter() { unsafe { - PAWN_ATTACKS[color.to_index()][src.to_index()] = EMPTY; + PAWN_ATTACKS[color.into_index()][src.into_index()] = EMPTY; match src.forward(*color) { None => {} Some(x) => { match x.left() { None => {} Some(y) => { - PAWN_ATTACKS[color.to_index()][src.to_index()] ^= + PAWN_ATTACKS[color.into_index()][src.into_index()] ^= BitBoard::from_square(y) } }; match x.right() { None => {} Some(y) => { - PAWN_ATTACKS[color.to_index()][src.to_index()] ^= + PAWN_ATTACKS[color.into_index()][src.into_index()] ^= BitBoard::from_square(y) } }; @@ -87,41 +87,41 @@ pub fn gen_dest_double_moves() -> BitBoard { // Write the PAWN_MOVES array to the specified file. pub fn write_pawn_moves(f: &mut File) { - write!(f, "const PAWN_MOVES: [[BitBoard; 64]; 2] = [[\n").unwrap(); + writeln!(f, "const PAWN_MOVES: [[BitBoard; 64]; 2] = [[").unwrap(); for i in 0..2 { for j in 0..64 { - unsafe { write!(f, " BitBoard({}),\n", PAWN_MOVES[i][j].0).unwrap() }; + unsafe { writeln!(f, " BitBoard({}),", PAWN_MOVES[i][j].0).unwrap() }; } if i != 1 { - write!(f, " ], [\n").unwrap(); + writeln!(f, " ], [").unwrap(); } } - write!(f, "]];\n").unwrap(); + writeln!(f, "]];").unwrap(); } // Write the PAWN_ATTACKS array to the specified file. pub fn write_pawn_attacks(f: &mut File) { - write!(f, "const PAWN_ATTACKS: [[BitBoard; 64]; 2] = [[\n").unwrap(); + writeln!(f, "const PAWN_ATTACKS: [[BitBoard; 64]; 2] = [[").unwrap(); for i in 0..2 { for j in 0..64 { - unsafe { write!(f, " BitBoard({}),\n", PAWN_ATTACKS[i][j].0).unwrap() }; + unsafe { writeln!(f, " BitBoard({}),", PAWN_ATTACKS[i][j].0).unwrap() }; } if i != 1 { - write!(f, " ], [\n").unwrap(); + writeln!(f, " ], [").unwrap(); } } - write!(f, "]];\n").unwrap(); + writeln!(f, "]];").unwrap(); - write!( + writeln!( f, - "const PAWN_SOURCE_DOUBLE_MOVES: BitBoard = BitBoard({0});\n", + "const PAWN_SOURCE_DOUBLE_MOVES: BitBoard = BitBoard({0});", gen_source_double_moves().0 ) .unwrap(); - write!( + writeln!( f, - "const PAWN_DEST_DOUBLE_MOVES: BitBoard = BitBoard({0});\n", + "const PAWN_DEST_DOUBLE_MOVES: BitBoard = BitBoard({0});", gen_dest_double_moves().0 ) .unwrap(); diff --git a/src/gen_tables/ranks_files.rs b/src/gen_tables/ranks_files.rs index 06d6c84b..74768451 100644 --- a/src/gen_tables/ranks_files.rs +++ b/src/gen_tables/ranks_files.rs @@ -37,17 +37,17 @@ pub fn gen_bitboard_data() { for i in 0..8 { RANKS[i] = ALL_SQUARES .iter() - .filter(|x| x.get_rank().to_index() == i) + .filter(|x| x.get_rank().into_index() == i) .fold(EMPTY, |v, s| v | BitBoard::from_square(*s)); FILES[i] = ALL_SQUARES .iter() - .filter(|x| x.get_file().to_index() == i) + .filter(|x| x.get_file().into_index() == i) .fold(EMPTY, |v, s| v | BitBoard::from_square(*s)); ADJACENT_FILES[i] = ALL_SQUARES .iter() .filter(|y| { - ((y.get_file().to_index() as i8) == (i as i8) - 1) - || ((y.get_file().to_index() as i8) == (i as i8) + 1) + ((y.get_file().into_index() as i8) == (i as i8) - 1) + || ((y.get_file().into_index() as i8) == (i as i8) + 1) }) .fold(EMPTY, |v, s| v | BitBoard::from_square(*s)); } @@ -57,27 +57,22 @@ pub fn gen_bitboard_data() { // Write the FILES array to the specified file. pub fn write_bitboard_data(f: &mut File) { unsafe { - write!(f, "const FILES: [BitBoard; 8] = [\n").unwrap(); + writeln!(f, "const FILES: [BitBoard; 8] = [").unwrap(); for i in 0..8 { - write!(f, " BitBoard({}),\n", FILES[i].0).unwrap(); + writeln!(f, " BitBoard({}),", FILES[i].0).unwrap(); } - write!(f, "];\n").unwrap(); - write!(f, "const ADJACENT_FILES: [BitBoard; 8] = [\n").unwrap(); + writeln!(f, "];").unwrap(); + writeln!(f, "const ADJACENT_FILES: [BitBoard; 8] = [").unwrap(); for i in 0..8 { - write!(f, " BitBoard({}),\n", ADJACENT_FILES[i].0).unwrap(); + writeln!(f, " BitBoard({}),", ADJACENT_FILES[i].0).unwrap(); } - write!(f, "];\n").unwrap(); - write!(f, "const RANKS: [BitBoard; 8] = [\n").unwrap(); + writeln!(f, "];").unwrap(); + writeln!(f, "const RANKS: [BitBoard; 8] = [").unwrap(); for i in 0..8 { - write!(f, " BitBoard({}),\n", RANKS[i].0).unwrap(); + writeln!(f, " BitBoard({}),", RANKS[i].0).unwrap(); } - write!(f, "];\n").unwrap(); - write!(f, "/// What are all the edge squares on the `BitBoard`?\n").unwrap(); - write!( - f, - "pub const EDGES: BitBoard = BitBoard({});\n", - EDGES.0 - ) - .unwrap(); + writeln!(f, "];").unwrap(); + writeln!(f, "/// What are all the edge squares on the `BitBoard`?").unwrap(); + writeln!(f, "pub const EDGES: BitBoard = BitBoard({});", EDGES.0).unwrap(); } } diff --git a/src/gen_tables/rays.rs b/src/gen_tables/rays.rs index 5d449221..9638e487 100644 --- a/src/gen_tables/rays.rs +++ b/src/gen_tables/rays.rs @@ -16,13 +16,13 @@ const BISHOP: usize = 1; pub fn gen_bishop_rays() { for src in ALL_SQUARES.iter() { unsafe { - RAYS[BISHOP][src.to_index()] = ALL_SQUARES + RAYS[BISHOP][src.into_index()] = ALL_SQUARES .iter() .filter(|dest| { - let src_rank = src.get_rank().to_index() as i8; - let src_file = src.get_file().to_index() as i8; - let dest_rank = dest.get_rank().to_index() as i8; - let dest_file = dest.get_file().to_index() as i8; + let src_rank = src.get_rank().into_index() as i8; + let src_file = src.get_file().into_index() as i8; + let dest_rank = dest.get_rank().into_index() as i8; + let dest_file = dest.get_file().into_index() as i8; (src_rank - dest_rank).abs() == (src_file - dest_file).abs() && *src != **dest }) @@ -35,13 +35,13 @@ pub fn gen_bishop_rays() { pub fn gen_rook_rays() { for src in ALL_SQUARES.iter() { unsafe { - RAYS[ROOK][src.to_index()] = ALL_SQUARES + RAYS[ROOK][src.into_index()] = ALL_SQUARES .iter() .filter(|dest| { - let src_rank = src.get_rank().to_index(); - let src_file = src.get_file().to_index(); - let dest_rank = dest.get_rank().to_index(); - let dest_file = dest.get_file().to_index(); + let src_rank = src.get_rank().into_index(); + let src_file = src.get_file().into_index(); + let dest_rank = dest.get_rank().into_index(); + let dest_file = dest.get_file().into_index(); (src_rank == dest_rank || src_file == dest_file) && *src != **dest }) @@ -51,21 +51,21 @@ pub fn gen_rook_rays() { } pub fn get_rays(sq: Square, piece: Piece) -> BitBoard { - unsafe { RAYS[if piece == Piece::Rook { ROOK } else { BISHOP }][sq.to_index()] } + unsafe { RAYS[if piece == Piece::Rook { ROOK } else { BISHOP }][sq.into_index()] } } // Write the RAYS array to the specified file. pub fn write_rays(f: &mut File) { - write!(f, "const ROOK: usize = {};\n", ROOK).unwrap(); - write!(f, "const BISHOP: usize = {};\n", BISHOP).unwrap(); - write!(f, "const RAYS: [[BitBoard; 64]; 2] = [[\n").unwrap(); + writeln!(f, "const ROOK: usize = {};", ROOK).unwrap(); + writeln!(f, "const BISHOP: usize = {};", BISHOP).unwrap(); + writeln!(f, "const RAYS: [[BitBoard; 64]; 2] = [[").unwrap(); for i in 0..2 { for j in 0..64 { - unsafe { write!(f, " BitBoard({}),\n", RAYS[i][j].0).unwrap() }; + unsafe { writeln!(f, " BitBoard({}),", RAYS[i][j].0).unwrap() }; } if i != 1 { - write!(f, " ], [\n").unwrap(); + writeln!(f, " ], [").unwrap(); } } - write!(f, "]];\n").unwrap(); + writeln!(f, "]];").unwrap(); } diff --git a/src/gen_tables/zobrist.rs b/src/gen_tables/zobrist.rs index 2be1d08a..4e60a905 100644 --- a/src/gen_tables/zobrist.rs +++ b/src/gen_tables/zobrist.rs @@ -13,47 +13,47 @@ use rand::{RngCore, SeedableRng}; pub fn write_zobrist(f: &mut File) { let mut rng = SmallRng::seed_from_u64(0xDEADBEEF12345678); - write!(f, "const SIDE_TO_MOVE: u64 = {};\n\n", rng.next_u64()).unwrap(); + writeln!(f, "const SIDE_TO_MOVE: u64 = {};\n", rng.next_u64()).unwrap(); - write!( + writeln!( f, - "const ZOBRIST_PIECES: [[[u64; NUM_SQUARES]; NUM_PIECES]; NUM_COLORS] = [[[\n" + "const ZOBRIST_PIECES: [[[u64; NUM_SQUARES]; NUM_PIECES]; NUM_COLORS] = [[[" ) .unwrap(); for i in 0..NUM_COLORS { for j in 0..NUM_PIECES { for _ in 0..NUM_SQUARES { - write!(f, " {},\n", rng.next_u64()).unwrap(); + writeln!(f, " {},", rng.next_u64()).unwrap(); } if j != NUM_PIECES - 1 { - write!(f, " ], [\n").unwrap(); + writeln!(f, " ], [").unwrap(); } } if i != NUM_COLORS - 1 { - write!(f, " ]], [[\n").unwrap(); + writeln!(f, " ]], [[").unwrap(); } } - write!(f, "]]];\n\n").unwrap(); + writeln!(f, "]]];\n").unwrap(); - write!(f, "const ZOBRIST_CASTLES: [[u64; 4]; NUM_COLORS] = [[\n").unwrap(); + writeln!(f, "const ZOBRIST_CASTLES: [[u64; 4]; NUM_COLORS] = [[").unwrap(); for i in 0..NUM_COLORS { for _ in 0..4 { - write!(f, " {},\n", rng.next_u64()).unwrap(); + writeln!(f, " {},", rng.next_u64()).unwrap(); } if i != NUM_COLORS - 1 { - write!(f, " ], [\n").unwrap(); + writeln!(f, " ], [").unwrap(); } } - write!(f, "]];\n\n").unwrap(); + writeln!(f, "]];\n").unwrap(); - write!(f, "const ZOBRIST_EP: [[u64; NUM_FILES]; NUM_COLORS] = [[\n").unwrap(); + writeln!(f, "const ZOBRIST_EP: [[u64; NUM_FILES]; NUM_COLORS] = [[").unwrap(); for i in 0..NUM_COLORS { for _ in 0..NUM_FILES { - write!(f, " {},\n", rng.next_u64()).unwrap(); + writeln!(f, " {},", rng.next_u64()).unwrap(); } if i != NUM_COLORS - 1 { - write!(f, "], [\n").unwrap(); + writeln!(f, "], [").unwrap(); } } - write!(f, "]];\n\n").unwrap(); + writeln!(f, "]];\n").unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index 443355e3..8e63b31c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,9 @@ pub use crate::board::*; mod bitboard; pub use crate::bitboard::{BitBoard, EMPTY}; +#[cfg(feature="std")] mod cache_table; +#[cfg(feature="std")] pub use crate::cache_table::*; mod castle_rights; @@ -79,4 +81,4 @@ mod board_builder; pub use crate::board_builder::BoardBuilder; mod error; -pub use crate::error::Error; +pub use crate::error::InvalidError; diff --git a/src/magic.rs b/src/magic.rs index 2b279924..b49ed58c 100644 --- a/src/magic.rs +++ b/src/magic.rs @@ -12,13 +12,13 @@ include!(concat!(env!("OUT_DIR"), "/magic_gen.rs")); /// Get the rays for a bishop on a particular square. #[inline] pub fn get_bishop_rays(sq: Square) -> BitBoard { - unsafe { *RAYS.get_unchecked(BISHOP).get_unchecked(sq.to_index()) } + unsafe { *RAYS.get_unchecked(BISHOP).get_unchecked(sq.into_index()) } } /// Get the rays for a rook on a particular square. #[inline] pub fn get_rook_rays(sq: Square) -> BitBoard { - unsafe { *RAYS.get_unchecked(ROOK).get_unchecked(sq.to_index()) } + unsafe { *RAYS.get_unchecked(ROOK).get_unchecked(sq.into_index()) } } /// Get the moves for a rook on a particular square, given blockers blocking my movement. @@ -84,13 +84,13 @@ pub fn get_bishop_moves_bmi(sq: Square, blockers: BitBoard) -> BitBoard { /// Get the king moves for a particular square. #[inline] pub fn get_king_moves(sq: Square) -> BitBoard { - unsafe { *KING_MOVES.get_unchecked(sq.to_index()) } + unsafe { *KING_MOVES.get_unchecked(sq.into_index()) } } /// Get the knight moves for a particular square. #[inline] pub fn get_knight_moves(sq: Square) -> BitBoard { - unsafe { *KNIGHT_MOVES.get_unchecked(sq.to_index()) } + unsafe { *KNIGHT_MOVES.get_unchecked(sq.into_index()) } } /// Get the pawn capture move for a particular square, given the pawn's color and the potential @@ -99,8 +99,8 @@ pub fn get_knight_moves(sq: Square) -> BitBoard { pub fn get_pawn_attacks(sq: Square, color: Color, blockers: BitBoard) -> BitBoard { unsafe { *PAWN_ATTACKS - .get_unchecked(color.to_index()) - .get_unchecked(sq.to_index()) + .get_unchecked(color.into_index()) + .get_unchecked(sq.into_index()) & blockers } } @@ -119,8 +119,8 @@ pub fn get_pawn_quiets(sq: Square, color: Color, blockers: BitBoard) -> BitBoard EMPTY } else { *PAWN_MOVES - .get_unchecked(color.to_index()) - .get_unchecked(sq.to_index()) + .get_unchecked(color.into_index()) + .get_unchecked(sq.into_index()) & !blockers } } @@ -139,8 +139,8 @@ pub fn get_pawn_moves(sq: Square, color: Color, blockers: BitBoard) -> BitBoard pub fn line(sq1: Square, sq2: Square) -> BitBoard { unsafe { *LINE - .get_unchecked(sq1.to_index()) - .get_unchecked(sq2.to_index()) + .get_unchecked(sq1.into_index()) + .get_unchecked(sq2.into_index()) } } @@ -149,27 +149,27 @@ pub fn line(sq1: Square, sq2: Square) -> BitBoard { pub fn between(sq1: Square, sq2: Square) -> BitBoard { unsafe { *BETWEEN - .get_unchecked(sq1.to_index()) - .get_unchecked(sq2.to_index()) + .get_unchecked(sq1.into_index()) + .get_unchecked(sq2.into_index()) } } /// Get a `BitBoard` that represents all the squares on a particular rank. #[inline] pub fn get_rank(rank: Rank) -> BitBoard { - unsafe { *RANKS.get_unchecked(rank.to_index()) } + unsafe { *RANKS.get_unchecked(rank.into_index()) } } /// Get a `BitBoard` that represents all the squares on a particular file. #[inline] pub fn get_file(file: File) -> BitBoard { - unsafe { *FILES.get_unchecked(file.to_index()) } + unsafe { *FILES.get_unchecked(file.into_index()) } } /// Get a `BitBoard` that represents the squares on the 1 or 2 files next to this file. #[inline] pub fn get_adjacent_files(file: File) -> BitBoard { - unsafe { *ADJACENT_FILES.get_unchecked(file.to_index()) } + unsafe { *ADJACENT_FILES.get_unchecked(file.into_index()) } } #[inline] diff --git a/src/movegen/mod.rs b/src/movegen/mod.rs index 20cfa76c..7a1349c2 100644 --- a/src/movegen/mod.rs +++ b/src/movegen/mod.rs @@ -2,4 +2,3 @@ mod movegen; pub use self::movegen::*; mod piece_type; -pub use self::piece_type::*; diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index 495f2a35..9d07af9d 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -8,7 +8,6 @@ use crate::square::Square; use arrayvec::ArrayVec; use nodrop::NoDrop; use std::iter::ExactSizeIterator; -use std::mem; #[derive(Copy, Clone, PartialEq, PartialOrd)] pub struct SquareAndBitBoard { @@ -96,21 +95,21 @@ impl MoveGen { let mut movelist = NoDrop::new(ArrayVec::::new()); if checkers == EMPTY { - PawnType::legals::(&mut movelist, &board, unoccupied_by_me); - KnightType::legals::(&mut movelist, &board, unoccupied_by_me); - BishopType::legals::(&mut movelist, &board, unoccupied_by_me); - RookType::legals::(&mut movelist, &board, unoccupied_by_me); - QueenType::legals::(&mut movelist, &board, unoccupied_by_me); - KingType::legals::(&mut movelist, &board, unoccupied_by_me); + PawnType::legals::(&mut movelist, board, unoccupied_by_me); + KnightType::legals::(&mut movelist, board, unoccupied_by_me); + BishopType::legals::(&mut movelist, board, unoccupied_by_me); + RookType::legals::(&mut movelist, board, unoccupied_by_me); + QueenType::legals::(&mut movelist, board, unoccupied_by_me); + KingType::legals::(&mut movelist, board, unoccupied_by_me); } else if checkers.popcnt() == 1 { - PawnType::legals::(&mut movelist, &board, unoccupied_by_me); - KnightType::legals::(&mut movelist, &board, unoccupied_by_me); - BishopType::legals::(&mut movelist, &board, unoccupied_by_me); - RookType::legals::(&mut movelist, &board, unoccupied_by_me); - QueenType::legals::(&mut movelist, &board, unoccupied_by_me); - KingType::legals::(&mut movelist, &board, unoccupied_by_me); + PawnType::legals::(&mut movelist, board, unoccupied_by_me); + KnightType::legals::(&mut movelist, board, unoccupied_by_me); + BishopType::legals::(&mut movelist, board, unoccupied_by_me); + RookType::legals::(&mut movelist, board, unoccupied_by_me); + QueenType::legals::(&mut movelist, board, unoccupied_by_me); + KingType::legals::(&mut movelist, board, unoccupied_by_me); } else { - KingType::legals::(&mut movelist, &board, unoccupied_by_me); + KingType::legals::(&mut movelist, board, unoccupied_by_me); } movelist @@ -248,7 +247,7 @@ impl MoveGen { } else { iterable.set_iterator_mask(*targets); for x in &mut iterable { - let mut bresult = mem::MaybeUninit::::uninit(); + let mut bresult = core::mem::MaybeUninit::::uninit(); unsafe { board.make_move(x, &mut *bresult.as_mut_ptr()); result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1); @@ -256,7 +255,7 @@ impl MoveGen { } iterable.set_iterator_mask(!EMPTY); for x in &mut iterable { - let mut bresult = mem::MaybeUninit::::uninit(); + let mut bresult = core::mem::MaybeUninit::::uninit(); unsafe { board.make_move(x, &mut *bresult.as_mut_ptr()); result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1); diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index b9a7e1d9..fc57553b 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -97,22 +97,21 @@ impl PawnType { let rooks = (board.pieces(Piece::Rook) | board.pieces(Piece::Queen)) & board.color_combined(!board.side_to_move()); - if (get_rook_rays(ksq) & rooks) != EMPTY { - if (get_rook_moves(ksq, combined) & rooks) != EMPTY { - return false; - } + if (get_rook_rays(ksq) & rooks) != EMPTY && (get_rook_moves(ksq, combined) & rooks) != EMPTY + { + return false; } let bishops = (board.pieces(Piece::Bishop) | board.pieces(Piece::Queen)) & board.color_combined(!board.side_to_move()); - if (get_bishop_rays(ksq) & bishops) != EMPTY { - if (get_bishop_moves(ksq, combined) & bishops) != EMPTY { - return false; - } + if (get_bishop_rays(ksq) & bishops) != EMPTY + && (get_bishop_moves(ksq, combined) & bishops) != EMPTY + { + return false; } - return true; + true } } @@ -332,7 +331,7 @@ impl KingType { board.pieces(Piece::Pawn) & board.color_combined(!board.side_to_move()), ); - return attackers == EMPTY; + attackers == EMPTY } } diff --git a/src/piece.rs b/src/piece.rs index 8a40767a..fbddafd8 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -2,14 +2,15 @@ use crate::color::Color; use std::fmt; /// Represent a chess piece as a very simple enum +#[repr(u8)] #[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Debug, Hash)] pub enum Piece { - Pawn, - Knight, - Bishop, - Rook, - Queen, - King, + Pawn = 1, + Knight = 2, + Bishop = 3, + Rook = 4, + Queen = 5, + King = 6, } /// How many piece types are there? @@ -34,8 +35,8 @@ pub const PROMOTION_PIECES: [Piece; 4] = [Piece::Queen, Piece::Knight, Piece::Ro impl Piece { /// Convert the `Piece` to a `usize` for table lookups. #[inline] - pub fn to_index(&self) -> usize { - *self as usize + pub const fn into_index(self) -> usize { + self as usize } /// Convert the `Piece` to its FEN `char` @@ -68,7 +69,7 @@ impl Piece { /// ``` #[inline] #[cfg(feature = "std")] - pub fn to_string(&self, color: Color) -> String { + pub fn to_string(self, color: Color) -> String { let piece = format!("{}", self); if color == Color::White { piece.to_uppercase() diff --git a/src/rank.rs b/src/rank.rs index 599fd2fa..b0b9c88a 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -1,4 +1,4 @@ -use crate::error::Error; +use crate::error::InvalidError; use std::str::FromStr; /// Describe a rank (row) on a chess board @@ -31,10 +31,10 @@ pub const ALL_RANKS: [Rank; NUM_RANKS] = [ ]; impl Rank { - /// Convert a `usize` into a `Rank` (the inverse of to_index). If the number is > 7, wrap + /// Convert a `usize` into a `Rank` (the inverse of into_index). If the number is > 7, wrap /// around. #[inline] - pub fn from_index(i: usize) -> Rank { + pub const fn from_index(i: usize) -> Rank { // match is optimized to no-op with opt-level=1 with rustc 1.53.0 match i & 7 { 0 => Rank::First, @@ -51,29 +51,29 @@ impl Rank { /// Go one rank down. If impossible, wrap around. #[inline] - pub fn down(&self) -> Rank { - Rank::from_index(self.to_index().wrapping_sub(1)) + pub const fn down(&self) -> Rank { + Rank::from_index(self.into_index().wrapping_sub(1)) } /// Go one rank up. If impossible, wrap around. #[inline] - pub fn up(&self) -> Rank { - Rank::from_index(self.to_index() + 1) + pub const fn up(&self) -> Rank { + Rank::from_index(self.into_index() + 1) } /// Convert this `Rank` into a `usize` between 0 and 7 (inclusive). #[inline] - pub fn to_index(&self) -> usize { - *self as usize + pub const fn into_index(self) -> usize { + self as usize } } impl FromStr for Rank { - type Err = Error; + type Err = InvalidError; fn from_str(s: &str) -> Result { if s.len() < 1 { - return Err(Error::InvalidRank); + return Err(InvalidError::Rank); } match s.chars().next().unwrap() { '1' => Ok(Rank::First), @@ -84,7 +84,7 @@ impl FromStr for Rank { '6' => Ok(Rank::Sixth), '7' => Ok(Rank::Seventh), '8' => Ok(Rank::Eighth), - _ => Err(Error::InvalidRank), + _ => Err(InvalidError::Rank), } } } diff --git a/src/square.rs b/src/square.rs index d14b5864..42209644 100644 --- a/src/square.rs +++ b/src/square.rs @@ -1,5 +1,5 @@ use crate::color::Color; -use crate::error::Error; +use crate::error::InvalidError; use crate::file::File; use crate::rank::Rank; use std::fmt; @@ -42,7 +42,7 @@ impl Square { /// assert_eq!(Square::default(), bad_sq); /// ``` #[inline] - pub fn new(sq: u8) -> Square { + pub const fn new(sq: u8) -> Square { Square(sq & 63) } @@ -64,7 +64,7 @@ impl Square { /// } /// ``` #[inline] - pub fn make_square(rank: Rank, file: File) -> Square { + pub const fn make_square(rank: Rank, file: File) -> Square { Square((rank as u8) << 3 ^ (file as u8)) } @@ -78,7 +78,7 @@ impl Square { /// assert_eq!(sq.get_rank(), Rank::Seventh); /// ``` #[inline] - pub fn get_rank(&self) -> Rank { + pub const fn get_rank(&self) -> Rank { Rank::from_index((self.0 >> 3) as usize) } @@ -92,7 +92,7 @@ impl Square { /// assert_eq!(sq.get_file(), File::D); /// ``` #[inline] - pub fn get_file(&self) -> File { + pub const fn get_file(&self) -> File { File::from_index((self.0 & 7) as usize) } @@ -234,7 +234,7 @@ impl Square { /// assert_eq!(sq.uup().uup(), Square::make_square(Rank::First, File::D)); /// ``` #[inline] - pub fn uup(&self) -> Square { + pub const fn uup(&self) -> Square { Square::make_square(self.get_rank().up(), self.get_file()) } @@ -250,7 +250,7 @@ impl Square { /// assert_eq!(sq.udown().udown(), Square::make_square(Rank::Eighth, File::D)); /// ``` #[inline] - pub fn udown(&self) -> Square { + pub const fn udown(&self) -> Square { Square::make_square(self.get_rank().down(), self.get_file()) } @@ -266,7 +266,7 @@ impl Square { /// assert_eq!(sq.uleft().uleft(), Square::make_square(Rank::Seventh, File::H)); /// ``` #[inline] - pub fn uleft(&self) -> Square { + pub const fn uleft(&self) -> Square { Square::make_square(self.get_rank(), self.get_file().left()) } @@ -304,7 +304,7 @@ impl Square { /// assert_eq!(sq.uforward(Color::Black).uforward(Color::Black), Square::make_square(Rank::Eighth, File::D)); /// ``` #[inline] - pub fn uforward(&self, color: Color) -> Square { + pub const fn uforward(&self, color: Color) -> Square { match color { Color::White => self.uup(), Color::Black => self.udown(), @@ -328,7 +328,7 @@ impl Square { /// assert_eq!(sq.ubackward(Color::White).ubackward(Color::White), Square::make_square(Rank::Eighth, File::D)); /// ``` #[inline] - pub fn ubackward(&self, color: Color) -> Square { + pub const fn ubackward(&self, color: Color) -> Square { match color { Color::White => self.udown(), Color::Black => self.uup(), @@ -346,7 +346,7 @@ impl Square { /// assert_eq!(Square::make_square(Rank::Eighth, File::H).to_int(), 63); /// ``` #[inline] - pub fn to_int(&self) -> u8 { + pub const fn to_int(&self) -> u8 { self.0 } @@ -355,13 +355,13 @@ impl Square { /// ``` /// use chess::{Square, Rank, File}; /// - /// assert_eq!(Square::make_square(Rank::First, File::A).to_index(), 0); - /// assert_eq!(Square::make_square(Rank::Second, File::A).to_index(), 8); - /// assert_eq!(Square::make_square(Rank::First, File::B).to_index(), 1); - /// assert_eq!(Square::make_square(Rank::Eighth, File::H).to_index(), 63); + /// assert_eq!(Square::make_square(Rank::First, File::A).into_index(), 0); + /// assert_eq!(Square::make_square(Rank::Second, File::A).into_index(), 8); + /// assert_eq!(Square::make_square(Rank::First, File::B).into_index(), 1); + /// assert_eq!(Square::make_square(Rank::Eighth, File::H).into_index(), 63); /// ``` #[inline] - pub fn to_index(&self) -> usize { + pub const fn into_index(self) -> usize { self.0 as usize } @@ -965,18 +965,18 @@ impl fmt::Display for Square { write!( f, "{}{}", - (('a' as u8) + ((self.0 & 7) as u8)) as char, - (('1' as u8) + ((self.0 >> 3) as u8)) as char + (b'a' + ((self.0 & 7) as u8)) as char, + (b'1' + ((self.0 >> 3) as u8)) as char ) } } impl FromStr for Square { - type Err = Error; + type Err = InvalidError; fn from_str(s: &str) -> Result { if s.len() < 2 { - return Err(Error::InvalidSquare); + return Err(InvalidError::Square); } let mut i = s.chars(); @@ -984,13 +984,13 @@ impl FromStr for Square { match c1 { 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {} _ => { - return Err(Error::InvalidSquare); + return Err(InvalidError::Square); } } match c2 { '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {} _ => { - return Err(Error::InvalidSquare); + return Err(InvalidError::Square); } } Ok(Square::make_square( @@ -998,7 +998,7 @@ impl FromStr for Square { File::from_index((c2 as usize) - ('a' as usize)), )) } else { - Err(Error::InvalidSquare) + Err(InvalidError::Square) } } } diff --git a/src/zobrist.rs b/src/zobrist.rs index c718fa68..4d699877 100644 --- a/src/zobrist.rs +++ b/src/zobrist.rs @@ -17,9 +17,9 @@ impl Zobrist { pub fn piece(piece: Piece, square: Square, color: Color) -> u64 { unsafe { *ZOBRIST_PIECES - .get_unchecked(color.to_index()) - .get_unchecked(piece.to_index()) - .get_unchecked(square.to_index()) + .get_unchecked(color.into_index()) + .get_unchecked(piece.into_index()) + .get_unchecked(square.into_index()) } } @@ -27,8 +27,8 @@ impl Zobrist { pub fn castles(castle_rights: CastleRights, color: Color) -> u64 { unsafe { *ZOBRIST_CASTLES - .get_unchecked(color.to_index()) - .get_unchecked(castle_rights.to_index()) + .get_unchecked(color.into_index()) + .get_unchecked(castle_rights.into_index()) } } @@ -36,8 +36,8 @@ impl Zobrist { pub fn en_passant(file: File, color: Color) -> u64 { unsafe { *ZOBRIST_EP - .get_unchecked(color.to_index()) - .get_unchecked(file.to_index()) + .get_unchecked(color.into_index()) + .get_unchecked(file.into_index()) } } From ee32e2c4ed69dfd6515431720f23ef8c245703b7 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 12 Mar 2025 11:16:31 -0700 Subject: [PATCH 47/94] bug fix --- src/board.rs | 4 ++-- src/board_builder.rs | 10 +++++----- src/chess_move.rs | 8 ++++---- src/piece.rs | 12 ++++++------ src/square.rs | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/board.rs b/src/board.rs index 39f8a6fd..a84d19d7 100644 --- a/src/board.rs +++ b/src/board.rs @@ -204,9 +204,9 @@ impl Board { /// ``` /// use chess::Board; /// use std::str::FromStr; - /// # use chess::Error; + /// # use chess::InvalidError; /// - /// # fn main() -> Result<(), Error> { + /// # fn main() -> Result<(), InvalidError> { /// /// // This is no longer supported /// let init_position = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_owned()).expect("Valid FEN"); diff --git a/src/board_builder.rs b/src/board_builder.rs index ecb389f7..52b1b546 100644 --- a/src/board_builder.rs +++ b/src/board_builder.rs @@ -68,8 +68,8 @@ impl BoardBuilder { /// use chess::{BoardBuilder, Board, Square, Color, Piece}; /// use std::convert::TryInto; /// - /// # use chess::Error; - /// # fn main() -> Result<(), Error> { + /// # use chess::InvalidError; + /// # fn main() -> Result<(), InvalidError> { /// let board: Board = BoardBuilder::new() /// .piece(Square::A1, Piece::King, Color::White) /// .piece(Square::A8, Piece::King, Color::Black) @@ -92,8 +92,8 @@ impl BoardBuilder { /// use chess::{BoardBuilder, Board, Square, Color, Piece, CastleRights}; /// use std::convert::TryInto; /// - /// # use chess::Error; - /// # fn main() -> Result<(), Error> { + /// # use chess::InvalidError; + /// # fn main() -> Result<(), InvalidError> { /// let board: Board = BoardBuilder::setup( /// &[ /// (Square::A1, Piece::King, Color::White), @@ -199,7 +199,7 @@ impl BoardBuilder { /// bb.castle_rights(Color::Black, CastleRights::Both); /// ``` pub fn castle_rights(&mut self, color: Color, castle_rights: CastleRights) -> &mut Self { - unsafe { *self.castle_rights.get_unchecked_mut(color.into_index()) = castle_rights }; + self.castle_rights[color.into_index()] = castle_rights; self } diff --git a/src/chess_move.rs b/src/chess_move.rs index 1759a613..5210c2a2 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -295,8 +295,8 @@ impl ChessMove { } } else { let sq = Square::make_square( - source_rank.ok_or(error.clone())?, - source_file.ok_or(error.clone())?, + source_rank.ok_or(error.clone())?, + source_file.ok_or(error.clone())?, ); source_rank = None; source_file = None; @@ -352,7 +352,7 @@ impl ChessMove { //} // Ok, now we have all the data from the SAN move, in the following structures - // moveing_piece, source_rank, source_file, taks, dest, promotion, maybe_check_or_mate, and + // moving_piece, source_rank, source_file, taks, dest, promotion, maybe_check_or_mate, and // ep let mut found_move: Option = None; @@ -416,7 +416,7 @@ impl ChessMove { acc |= (source.to_int() as u16) << 10; acc |= (dest.to_int() as u16) << 4; acc |= promotion - .map(|promotion| (promotion as u8) as u16) + .map(|promotion| promotion as u16 + 1) .unwrap_or(0); acc } diff --git a/src/piece.rs b/src/piece.rs index fbddafd8..feedc757 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -5,12 +5,12 @@ use std::fmt; #[repr(u8)] #[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Debug, Hash)] pub enum Piece { - Pawn = 1, - Knight = 2, - Bishop = 3, - Rook = 4, - Queen = 5, - King = 6, + Pawn, + Knight, + Bishop, + Rook, + Queen, + King, } /// How many piece types are there? diff --git a/src/square.rs b/src/square.rs index 42209644..d2cdd0b0 100644 --- a/src/square.rs +++ b/src/square.rs @@ -994,8 +994,8 @@ impl FromStr for Square { } } Ok(Square::make_square( - Rank::from_index((c1 as usize) - ('1' as usize)), - File::from_index((c2 as usize) - ('a' as usize)), + Rank::from_index((c2 as usize) - ('1' as usize)), + File::from_index((c1 as usize) - ('a' as usize)), )) } else { Err(InvalidError::Square) From 01ca8c475f682752fe53e75ae0c588ddd91ca793 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 12 Mar 2025 12:10:56 -0700 Subject: [PATCH 48/94] Add serde feature flag which allows serialization using Serde --- Cargo.toml | 3 +++ src/bitboard.rs | 1 + src/board.rs | 22 +++++++++++----------- src/castle_rights.rs | 1 + src/chess_move.rs | 1 + src/color.rs | 5 +++-- src/file.rs | 1 + src/game.rs | 2 ++ src/piece.rs | 3 ++- src/rank.rs | 3 ++- src/square.rs | 1 + src/zobrist.rs | 8 ++++++-- 12 files changed, 34 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0daa0c6c..505e8843 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rust-version = "1.56.0" [dependencies] arrayvec = { version = "0.7.2", default-features = false } nodrop = { version = "0.1.14", default-features = false } +serde = { version = "1.0.219", default-features = false, optional = true, features = ["derive"] } [profile.release] opt-level = 3 @@ -40,7 +41,9 @@ opt-level = 3 [build-dependencies] rand = { version = "0.7.2", default-features = false, features = ["small_rng"] } +serde = { version = "1.0.219", defaulte-features = false, optional = true, features = ["derive"] } [features] default = ["std"] std = [] +serde = ["dep:serde"] \ No newline at end of file diff --git a/src/bitboard.rs b/src/bitboard.rs index 337e457e..8d61b643 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -23,6 +23,7 @@ use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, M /// assert_eq!(count, 3); /// ``` /// +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(PartialEq, Eq, PartialOrd, Clone, Copy, Debug, Default, Hash)] pub struct BitBoard(pub u64); diff --git a/src/board.rs b/src/board.rs index a84d19d7..eebadee9 100644 --- a/src/board.rs +++ b/src/board.rs @@ -20,6 +20,7 @@ use std::hash::{Hash, Hasher}; use std::str::FromStr; /// A representation of a chess board. That's why you're here, right? +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Board { pieces: [BitBoard; NUM_PIECES], @@ -35,6 +36,7 @@ pub struct Board { /// What is the status of this game? #[repr(u8)] +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, Debug)] pub enum BoardStatus { Ongoing, @@ -826,20 +828,18 @@ impl Board { self.castle_rights[(!self.side_to_move).into_index()], !self.side_to_move, ) - ^ if self.side_to_move == Color::Black { - Zobrist::color() - } else { - 0 - } + ^ Zobrist::color(self.side_to_move) } /// Get a pawn hash of the board (a hash that only changes on color change and pawn moves). - /// - /// Currently not implemented... #[inline] - // Todo Implement - pub const fn get_pawn_hash(&self) -> u64 { - 0 + pub fn get_pawn_hash(&self) -> u64 { + self.pieces(Piece::Pawn) + .into_iter() + .map(|square| (square, self.color_on(square).unwrap())) + .fold(Zobrist::color(self.side_to_move), |acc, (pawn_square, color)| { + acc ^ Zobrist::piece(Piece::Pawn, pawn_square, color) + }) } /// What piece is on a particular `Square`? Is there even one? @@ -1310,4 +1310,4 @@ fn check_startpos_correct() { let board = Board::from_str(startpos_fen).unwrap(); let startpos = Board::STARTPOS; assert_eq!(board, startpos, "Startpos is not correct"); -} \ No newline at end of file +} diff --git a/src/castle_rights.rs b/src/castle_rights.rs index b48c99f0..75effd7d 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -10,6 +10,7 @@ use crate::magic::{KINGSIDE_CASTLE_SQUARES, QUEENSIDE_CASTLE_SQUARES}; /// What castle rights does a particular player have? #[repr(u8)] +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug, Hash)] pub enum CastleRights { NoRights = 0b00, diff --git a/src/chess_move.rs b/src/chess_move.rs index 5210c2a2..417306e9 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -13,6 +13,7 @@ use std::fmt; use std::str::FromStr; /// Represent a ChessMove in memory +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Copy, Eq, PartialEq, Default, Debug, Hash)] pub struct ChessMove { source: Square, diff --git a/src/color.rs b/src/color.rs index 8968f7d3..1b800b82 100644 --- a/src/color.rs +++ b/src/color.rs @@ -3,6 +3,7 @@ use std::ops::Not; /// Represent a color. #[repr(u8)] +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(PartialOrd, PartialEq, Eq, Copy, Clone, Debug, Hash)] pub enum Color { White = 0, @@ -84,11 +85,11 @@ impl Not for Color { } } -impl From<&Color> for bool { +impl From for bool { /// While in the backend, `Color::White == 0` and `Color::Black == 1`, /// it is more intuitive for `Color::White` to evaluate `true`, as it goes first #[inline] - fn from(value: &Color) -> Self { + fn from(value: Color) -> Self { match value { Color::White => true, Color::Black => false, diff --git a/src/file.rs b/src/file.rs index e07e56df..faf4b9bb 100644 --- a/src/file.rs +++ b/src/file.rs @@ -3,6 +3,7 @@ use std::str::FromStr; /// Describe a file (column) on a chess board #[repr(u8)] +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug, Hash)] pub enum File { A = 0, diff --git a/src/game.rs b/src/game.rs index d6f6d0e6..c686ef57 100644 --- a/src/game.rs +++ b/src/game.rs @@ -7,6 +7,7 @@ use crate::piece::Piece; use std::str::FromStr; /// Contains all actions supported within the game +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq)] pub enum Action { MakeMove(ChessMove), @@ -17,6 +18,7 @@ pub enum Action { } /// What was the result of this game? +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] pub enum GameResult { WhiteCheckmates, diff --git a/src/piece.rs b/src/piece.rs index feedc757..860a4010 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -3,6 +3,7 @@ use std::fmt; /// Represent a chess piece as a very simple enum #[repr(u8)] +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Debug, Hash)] pub enum Piece { Pawn, @@ -95,7 +96,7 @@ impl fmt::Display for PieceWithColor { write!( f, "{}", - if (&self.color).into() { + if self.color.into() { self.piece.to_char().to_ascii_uppercase() } else { self.piece.to_char() diff --git a/src/rank.rs b/src/rank.rs index b0b9c88a..3bc51312 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -2,8 +2,9 @@ use crate::error::InvalidError; use std::str::FromStr; /// Describe a rank (row) on a chess board -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug, Hash)] #[repr(u8)] +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug, Hash)] pub enum Rank { First = 0, Second = 1, diff --git a/src/square.rs b/src/square.rs index d2cdd0b0..47749e9e 100644 --- a/src/square.rs +++ b/src/square.rs @@ -6,6 +6,7 @@ use std::fmt; use std::str::FromStr; /// Represent a square on the chess board +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(PartialEq, Ord, Eq, PartialOrd, Copy, Clone, Debug, Hash)] pub struct Square(u8); diff --git a/src/zobrist.rs b/src/zobrist.rs index 4d699877..90b5d5c5 100644 --- a/src/zobrist.rs +++ b/src/zobrist.rs @@ -42,7 +42,11 @@ impl Zobrist { } #[inline] - pub fn color() -> u64 { - SIDE_TO_MOVE + pub fn color(color: Color) -> u64 { + if (!color).into() { + SIDE_TO_MOVE + } else { + 0 + } } } From 89c26ab365f7eb8700f3c43a7888c4b705746cbf Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 12 Mar 2025 12:23:56 -0700 Subject: [PATCH 49/94] allow static_mut_refs --- src/gen_tables/magic.rs | 1 + src/gen_tables/ranks_files.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/gen_tables/magic.rs b/src/gen_tables/magic.rs index 1f05f20a..11a35702 100644 --- a/src/gen_tables/magic.rs +++ b/src/gen_tables/magic.rs @@ -162,6 +162,7 @@ pub fn write_magic(f: &mut File) { writeln!(f, "]];").unwrap(); unsafe { + #[allow(static_mut_refs)] writeln!(f, "const MOVES: [BitBoard; {}] = [", GENERATED_NUM_MOVES).unwrap(); for i in 0..GENERATED_NUM_MOVES { writeln!(f, " BitBoard({}),", MOVES[i].0).unwrap(); diff --git a/src/gen_tables/ranks_files.rs b/src/gen_tables/ranks_files.rs index 74768451..cbd15700 100644 --- a/src/gen_tables/ranks_files.rs +++ b/src/gen_tables/ranks_files.rs @@ -73,6 +73,7 @@ pub fn write_bitboard_data(f: &mut File) { } writeln!(f, "];").unwrap(); writeln!(f, "/// What are all the edge squares on the `BitBoard`?").unwrap(); + #[allow(static_mut_refs)] writeln!(f, "pub const EDGES: BitBoard = BitBoard({});", EDGES.0).unwrap(); } } From ca8972d26425dcf79c9f764dd9f9a0e3d5aa02c7 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 12 Mar 2025 12:29:14 -0700 Subject: [PATCH 50/94] Make tests that requier feature "std" only available when cfg(feature="std") --- src/board_builder.rs | 1 + src/movegen/movegen.rs | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/board_builder.rs b/src/board_builder.rs index 52b1b546..8d962a79 100644 --- a/src/board_builder.rs +++ b/src/board_builder.rs @@ -507,6 +507,7 @@ use crate::bitboard::BitBoard; #[cfg(test)] use std::convert::TryInto; +#[cfg(feature="std")] #[test] fn check_initial_position() { let initial_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index 9d07af9d..0d86e809 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -335,16 +335,16 @@ impl Iterator for MoveGen { } } -#[cfg(test)] +#[cfg(all(test, feature="std"))] use crate::board_builder::BoardBuilder; -#[cfg(test)] +#[cfg(all(test, feature="std"))] use std::collections::HashSet; -#[cfg(test)] +#[cfg(all(test, feature="std"))] use std::convert::TryInto; -#[cfg(test)] +#[cfg(all(test, feature="std"))] use std::str::FromStr; -#[cfg(test)] +#[cfg(all(test, feature="std"))] fn movegen_perft_test(fen: String, depth: usize, result: usize) { let board: Board = BoardBuilder::from_str(&fen).unwrap().try_into().unwrap(); @@ -352,6 +352,7 @@ fn movegen_perft_test(fen: String, depth: usize, result: usize) { assert_eq!(MoveGen::movegen_perft_test_piecewise(&board, depth), result); } +#[cfg(feature="std")] #[test] fn movegen_perft_kiwipete() { movegen_perft_test( @@ -361,48 +362,57 @@ fn movegen_perft_kiwipete() { ); } +#[cfg(feature="std")] #[test] fn movegen_perft_1() { movegen_perft_test("8/5bk1/8/2Pp4/8/1K6/8/8 w - d6 0 1".to_owned(), 6, 824064); // Invalid FEN } +#[cfg(feature="std")] #[test] fn movegen_perft_2() { movegen_perft_test("8/8/1k6/8/2pP4/8/5BK1/8 b - d3 0 1".to_owned(), 6, 824064); // Invalid FEN } +#[cfg(feature="std")] #[test] fn movegen_perft_3() { movegen_perft_test("8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1".to_owned(), 6, 1440467); } +#[cfg(feature="std")] #[test] fn movegen_perft_4() { movegen_perft_test("8/5k2/8/2Pp4/2B5/1K6/8/8 w - d6 0 1".to_owned(), 6, 1440467); } +#[cfg(feature="std")] #[test] fn movegen_perft_5() { movegen_perft_test("5k2/8/8/8/8/8/8/4K2R w K - 0 1".to_owned(), 6, 661072); } +#[cfg(feature="std")] #[test] fn movegen_perft_6() { movegen_perft_test("4k2r/8/8/8/8/8/8/5K2 b k - 0 1".to_owned(), 6, 661072); } +#[cfg(feature="std")] #[test] fn movegen_perft_7() { movegen_perft_test("3k4/8/8/8/8/8/8/R3K3 w Q - 0 1".to_owned(), 6, 803711); } +#[cfg(feature="std")] #[test] fn movegen_perft_8() { movegen_perft_test("r3k3/8/8/8/8/8/8/3K4 b q - 0 1".to_owned(), 6, 803711); } +#[cfg(feature="std")] #[test] fn movegen_perft_9() { movegen_perft_test( @@ -412,6 +422,7 @@ fn movegen_perft_9() { ); } +#[cfg(feature="std")] #[test] fn movegen_perft_10() { movegen_perft_test( @@ -421,6 +432,7 @@ fn movegen_perft_10() { ); } +#[cfg(feature="std")] #[test] fn movegen_perft_11() { movegen_perft_test( @@ -430,6 +442,7 @@ fn movegen_perft_11() { ); } +#[cfg(feature="std")] #[test] fn movegen_perft_12() { movegen_perft_test( @@ -439,76 +452,91 @@ fn movegen_perft_12() { ); } +#[cfg(feature="std")] #[test] fn movegen_perft_13() { movegen_perft_test("2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1".to_owned(), 6, 3821001); } +#[cfg(feature="std")] #[test] fn movegen_perft_14() { movegen_perft_test("3K4/8/8/8/8/8/4p3/2k2R2 b - - 0 1".to_owned(), 6, 3821001); } +#[cfg(feature="std")] #[test] fn movegen_perft_15() { movegen_perft_test("8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1".to_owned(), 5, 1004658); } +#[cfg(feature="std")] #[test] fn movegen_perft_16() { movegen_perft_test("5K2/8/1Q6/2N5/8/1p2k3/8/8 w - - 0 1".to_owned(), 5, 1004658); } +#[cfg(feature="std")] #[test] fn movegen_perft_17() { movegen_perft_test("4k3/1P6/8/8/8/8/K7/8 w - - 0 1".to_owned(), 6, 217342); } +#[cfg(feature="std")] #[test] fn movegen_perft_18() { movegen_perft_test("8/k7/8/8/8/8/1p6/4K3 b - - 0 1".to_owned(), 6, 217342); } +#[cfg(feature="std")] #[test] fn movegen_perft_19() { movegen_perft_test("8/P1k5/K7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 92683); } +#[cfg(feature="std")] #[test] fn movegen_perft_20() { movegen_perft_test("8/8/8/8/8/k7/p1K5/8 b - - 0 1".to_owned(), 6, 92683); } +#[cfg(feature="std")] #[test] fn movegen_perft_21() { movegen_perft_test("K1k5/8/P7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 2217); } +#[cfg(feature="std")] #[test] fn movegen_perft_22() { movegen_perft_test("8/8/8/8/8/p7/8/k1K5 b - - 0 1".to_owned(), 6, 2217); } +#[cfg(feature="std")] #[test] fn movegen_perft_23() { movegen_perft_test("8/k1P5/8/1K6/8/8/8/8 w - - 0 1".to_owned(), 7, 567584); } +#[cfg(feature="std")] #[test] fn movegen_perft_24() { movegen_perft_test("8/8/8/8/1k6/8/K1p5/8 b - - 0 1".to_owned(), 7, 567584); } +#[cfg(feature="std")] #[test] fn movegen_perft_25() { movegen_perft_test("8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1".to_owned(), 4, 23527); } +#[cfg(feature="std")] #[test] fn movegen_perft_26() { movegen_perft_test("8/5k2/8/5N2/5Q2/2K5/8/8 w - - 0 1".to_owned(), 4, 23527); } +#[cfg(feature="std")] #[test] fn movegen_issue_15() { let board = @@ -519,7 +547,7 @@ fn movegen_issue_15() { let _ = MoveGen::new_legal(&board); } -#[cfg(test)] +#[cfg(all(test, feature="std"))] fn move_of(m: &str) -> ChessMove { let promo = if m.len() > 4 { Some(match m.as_bytes()[4] { @@ -539,6 +567,7 @@ fn move_of(m: &str) -> ChessMove { ) } +#[cfg(feature="std")] #[test] fn test_masked_move_gen() { let board = From 492bd997f588d12482618650d2c72082780dfef5 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Thu, 13 Mar 2025 11:04:45 -0700 Subject: [PATCH 51/94] Fix comment --- src/board.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index eebadee9..69b9f167 100644 --- a/src/board.rs +++ b/src/board.rs @@ -428,7 +428,6 @@ impl Board { } } - // Todo Rewrite -- This doctest uses deprecated functions. /// Remove castle rights for a particular side. /// /// ``` From 8c6c9b702fd89cdc73c9c476e965a239dd6d894f Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Thu, 13 Mar 2025 11:05:07 -0700 Subject: [PATCH 52/94] remove nodrop --- Cargo.toml | 7 +++---- src/movegen/movegen.rs | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 505e8843..51ecad04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ rust-version = "1.56.0" [dependencies] arrayvec = { version = "0.7.2", default-features = false } -nodrop = { version = "0.1.14", default-features = false } serde = { version = "1.0.219", default-features = false, optional = true, features = ["derive"] } [profile.release] @@ -44,6 +43,6 @@ rand = { version = "0.7.2", default-features = false, features = ["small_rng"] } serde = { version = "1.0.219", defaulte-features = false, optional = true, features = ["derive"] } [features] -default = ["std"] -std = [] -serde = ["dep:serde"] \ No newline at end of file +default = ["std", "serde"] +std = ["arrayvec/std", "serde/std"] +serde = ["dep:serde", "arrayvec/serde"] \ No newline at end of file diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index 0d86e809..b37de49f 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -6,9 +6,9 @@ use crate::movegen::piece_type::*; use crate::piece::{Piece, NUM_PROMOTION_PIECES, PROMOTION_PIECES}; use crate::square::Square; use arrayvec::ArrayVec; -use nodrop::NoDrop; use std::iter::ExactSizeIterator; +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, PartialOrd)] pub struct SquareAndBitBoard { square: Square, @@ -26,7 +26,7 @@ impl SquareAndBitBoard { } } -pub type MoveList = NoDrop>; +pub type MoveList = ArrayVec; /// An incremental move generator /// @@ -80,6 +80,7 @@ pub type MoveList = NoDrop>; /// assert_eq!(count, 20); /// /// ``` +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] pub struct MoveGen { moves: MoveList, promotion_index: usize, @@ -92,7 +93,7 @@ impl MoveGen { fn enumerate_moves(board: &Board) -> MoveList { let checkers = *board.checkers(); let unoccupied_by_me = !board.color_combined(board.side_to_move()); - let mut movelist = NoDrop::new(ArrayVec::::new()); + let mut movelist = ArrayVec::::new(); if checkers == EMPTY { PawnType::legals::(&mut movelist, board, unoccupied_by_me); From 55d7ffe9e9004214d29a4df57088953e9f10d9f9 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Thu, 13 Mar 2025 12:00:09 -0700 Subject: [PATCH 53/94] spelling correction --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 51ecad04..a5d4b11c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ opt-level = 3 [build-dependencies] rand = { version = "0.7.2", default-features = false, features = ["small_rng"] } -serde = { version = "1.0.219", defaulte-features = false, optional = true, features = ["derive"] } +serde = { version = "1.0.219", default-features = false, optional = true, features = ["derive"] } [features] default = ["std", "serde"] From 43c0c9a9f3cba74800544beee938a2b7b394e4ca Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Thu, 13 Mar 2025 12:00:20 -0700 Subject: [PATCH 54/94] make static deterministic functions --- src/gen_tables/magic_helpers.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/gen_tables/magic_helpers.rs b/src/gen_tables/magic_helpers.rs index 59dbf92a..79208fc8 100644 --- a/src/gen_tables/magic_helpers.rs +++ b/src/gen_tables/magic_helpers.rs @@ -1,3 +1,5 @@ +use std::sync::LazyLock; + use crate::bitboard::{BitBoard, EMPTY}; use crate::file::File; use crate::gen_tables::rays::get_rays; @@ -32,6 +34,8 @@ fn rook_directions() -> Vec Option> { vec![left, right, up, down] } +static ROOK_DIRECTIONS: LazyLock Option>> = LazyLock::new(|| rook_directions()); + // Return a list of directions for the bishop. fn bishop_directions() -> Vec Option> { fn nw(sq: Square) -> Option { @@ -50,6 +54,8 @@ fn bishop_directions() -> Vec Option> { vec![nw, ne, sw, se] } +static BISHOP_DIRECTIONS: LazyLock Option>> = LazyLock::new(|| bishop_directions()); + // Generate a random bitboard with a small number of bits. pub fn random_bitboard(rng: &mut R) -> BitBoard { BitBoard::new(rng.gen::() & rng.gen::() & rng.gen::()) @@ -59,7 +65,7 @@ pub fn random_bitboard(rng: &mut R) -> BitBoard { pub fn magic_mask(sq: Square, piece: Piece) -> BitBoard { get_rays(sq, piece) & if piece == Piece::Bishop { - !gen_edges() + !*EDGES } else { !ALL_SQUARES .iter() @@ -101,9 +107,9 @@ pub fn questions_and_answers(sq: Square, piece: Piece) -> (Vec, Vec BitBoard { }) .fold(EMPTY, |b, s| b | BitBoard::from_square(*s)) } + +static EDGES: LazyLock = LazyLock::new(|| gen_edges()); From 74d91b93a1c483ec047ac548686949b3df3ffa23 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Thu, 13 Mar 2025 15:34:56 -0700 Subject: [PATCH 55/94] Refactor InCheckType into a const IN_CHECK bool & add a has_legals function which short-circuits at the first move it finds. --- src/board.rs | 30 ++-- src/movegen/movegen.rs | 140 ++++++++++------ src/movegen/piece_type.rs | 328 +++++++++++++++++++++++++++++++------- 3 files changed, 371 insertions(+), 127 deletions(-) diff --git a/src/board.rs b/src/board.rs index 69b9f167..03593be8 100644 --- a/src/board.rs +++ b/src/board.rs @@ -20,7 +20,7 @@ use std::hash::{Hash, Hasher}; use std::str::FromStr; /// A representation of a chess board. That's why you're here, right? -#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Board { pieces: [BitBoard; NUM_PIECES], @@ -36,7 +36,7 @@ pub struct Board { /// What is the status of this game? #[repr(u8)] -#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, Debug)] pub enum BoardStatus { Ongoing, @@ -246,6 +246,8 @@ impl Board { } /// Is this game Ongoing, is it Stalemate, or is it Checkmate? + /// + /// Note: This function is optimized to only find the first possible move to minimize time cost. /// /// ``` /// use chess::{Board, BoardStatus, Square, ChessMove}; @@ -285,18 +287,15 @@ impl Board { /// assert_eq!(board.status(), BoardStatus::Checkmate); /// ``` #[inline] - // Todo Optimize -- This function should not generate every legal move, it should generate only a single one. pub fn status(&self) -> BoardStatus { - let moves = MoveGen::new_legal(self).len(); - match moves { - 0 => { - if self.checkers == EMPTY { - BoardStatus::Stalemate - } else { - BoardStatus::Checkmate - } + if !MoveGen::has_legals(self) { + if self.checkers == EMPTY { + BoardStatus::Stalemate + } else { + BoardStatus::Checkmate } - _ => BoardStatus::Ongoing, + } else { + BoardStatus::Ongoing } } @@ -836,9 +835,10 @@ impl Board { self.pieces(Piece::Pawn) .into_iter() .map(|square| (square, self.color_on(square).unwrap())) - .fold(Zobrist::color(self.side_to_move), |acc, (pawn_square, color)| { - acc ^ Zobrist::piece(Piece::Pawn, pawn_square, color) - }) + .fold( + Zobrist::color(self.side_to_move), + |acc, (pawn_square, color)| acc ^ Zobrist::piece(Piece::Pawn, pawn_square, color), + ) } /// What piece is on a particular `Square`? Is there even one? diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index b37de49f..b98b9861 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -8,7 +8,7 @@ use crate::square::Square; use arrayvec::ArrayVec; use std::iter::ExactSizeIterator; -#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, PartialOrd)] pub struct SquareAndBitBoard { square: Square, @@ -80,7 +80,7 @@ pub type MoveList = ArrayVec; /// assert_eq!(count, 20); /// /// ``` -#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MoveGen { moves: MoveList, promotion_index: usize, @@ -95,27 +95,61 @@ impl MoveGen { let unoccupied_by_me = !board.color_combined(board.side_to_move()); let mut movelist = ArrayVec::::new(); - if checkers == EMPTY { - PawnType::legals::(&mut movelist, board, unoccupied_by_me); - KnightType::legals::(&mut movelist, board, unoccupied_by_me); - BishopType::legals::(&mut movelist, board, unoccupied_by_me); - RookType::legals::(&mut movelist, board, unoccupied_by_me); - QueenType::legals::(&mut movelist, board, unoccupied_by_me); - KingType::legals::(&mut movelist, board, unoccupied_by_me); - } else if checkers.popcnt() == 1 { - PawnType::legals::(&mut movelist, board, unoccupied_by_me); - KnightType::legals::(&mut movelist, board, unoccupied_by_me); - BishopType::legals::(&mut movelist, board, unoccupied_by_me); - RookType::legals::(&mut movelist, board, unoccupied_by_me); - QueenType::legals::(&mut movelist, board, unoccupied_by_me); - KingType::legals::(&mut movelist, board, unoccupied_by_me); - } else { - KingType::legals::(&mut movelist, board, unoccupied_by_me); + match checkers.popcnt() { + 0 => { + PawnType::legals::(&mut movelist, board, unoccupied_by_me); + KnightType::legals::(&mut movelist, board, unoccupied_by_me); + BishopType::legals::(&mut movelist, board, unoccupied_by_me); + RookType::legals::(&mut movelist, board, unoccupied_by_me); + QueenType::legals::(&mut movelist, board, unoccupied_by_me); + KingType::legals::(&mut movelist, board, unoccupied_by_me); + } + 1 => { + PawnType::legals::(&mut movelist, board, unoccupied_by_me); + KnightType::legals::(&mut movelist, board, unoccupied_by_me); + BishopType::legals::(&mut movelist, board, unoccupied_by_me); + RookType::legals::(&mut movelist, board, unoccupied_by_me); + QueenType::legals::(&mut movelist, board, unoccupied_by_me); + KingType::legals::(&mut movelist, board, unoccupied_by_me); + } + _ => { + KingType::legals::(&mut movelist, board, unoccupied_by_me); + } } movelist } + /// Does a particular board have *any* legal moves? + /// + /// This function does not evaluate any moves past the first one it finds and so is guaranteed + /// to take less than or equal to time as `new_legal`. + #[inline(always)] + pub fn has_legals(board: &Board) -> bool { + let checkers = *board.checkers(); + let unoccupied_by_me = !board.color_combined(board.side_to_move()); + + match checkers.popcnt() { + 0 => { + PawnType::has_legals::(board, unoccupied_by_me) + || KnightType::has_legals::(board, unoccupied_by_me) + || BishopType::has_legals::(board, unoccupied_by_me) + || RookType::has_legals::(board, unoccupied_by_me) + || QueenType::has_legals::(board, unoccupied_by_me) + || KingType::has_legals::(board, unoccupied_by_me) + } + 1 => { + PawnType::has_legals::(board, unoccupied_by_me) + || KnightType::has_legals::(board, unoccupied_by_me) + || BishopType::has_legals::(board, unoccupied_by_me) + || RookType::has_legals::(board, unoccupied_by_me) + || QueenType::has_legals::(board, unoccupied_by_me) + || KingType::has_legals::(board, unoccupied_by_me) + } + _ => KingType::has_legals::(board, unoccupied_by_me), + } + } + /// Create a new `MoveGen` structure, only generating legal moves #[inline(always)] pub fn new_legal(board: &Board) -> MoveGen { @@ -336,16 +370,16 @@ impl Iterator for MoveGen { } } -#[cfg(all(test, feature="std"))] +#[cfg(all(test, feature = "std"))] use crate::board_builder::BoardBuilder; -#[cfg(all(test, feature="std"))] +#[cfg(all(test, feature = "std"))] use std::collections::HashSet; -#[cfg(all(test, feature="std"))] +#[cfg(all(test, feature = "std"))] use std::convert::TryInto; -#[cfg(all(test, feature="std"))] +#[cfg(all(test, feature = "std"))] use std::str::FromStr; -#[cfg(all(test, feature="std"))] +#[cfg(all(test, feature = "std"))] fn movegen_perft_test(fen: String, depth: usize, result: usize) { let board: Board = BoardBuilder::from_str(&fen).unwrap().try_into().unwrap(); @@ -353,7 +387,7 @@ fn movegen_perft_test(fen: String, depth: usize, result: usize) { assert_eq!(MoveGen::movegen_perft_test_piecewise(&board, depth), result); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_kiwipete() { movegen_perft_test( @@ -363,57 +397,57 @@ fn movegen_perft_kiwipete() { ); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_1() { movegen_perft_test("8/5bk1/8/2Pp4/8/1K6/8/8 w - d6 0 1".to_owned(), 6, 824064); // Invalid FEN } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_2() { movegen_perft_test("8/8/1k6/8/2pP4/8/5BK1/8 b - d3 0 1".to_owned(), 6, 824064); // Invalid FEN } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_3() { movegen_perft_test("8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1".to_owned(), 6, 1440467); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_4() { movegen_perft_test("8/5k2/8/2Pp4/2B5/1K6/8/8 w - d6 0 1".to_owned(), 6, 1440467); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_5() { movegen_perft_test("5k2/8/8/8/8/8/8/4K2R w K - 0 1".to_owned(), 6, 661072); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_6() { movegen_perft_test("4k2r/8/8/8/8/8/8/5K2 b k - 0 1".to_owned(), 6, 661072); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_7() { movegen_perft_test("3k4/8/8/8/8/8/8/R3K3 w Q - 0 1".to_owned(), 6, 803711); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_8() { movegen_perft_test("r3k3/8/8/8/8/8/8/3K4 b q - 0 1".to_owned(), 6, 803711); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_9() { movegen_perft_test( @@ -423,7 +457,7 @@ fn movegen_perft_9() { ); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_10() { movegen_perft_test( @@ -433,7 +467,7 @@ fn movegen_perft_10() { ); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_11() { movegen_perft_test( @@ -443,7 +477,7 @@ fn movegen_perft_11() { ); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_12() { movegen_perft_test( @@ -453,91 +487,91 @@ fn movegen_perft_12() { ); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_13() { movegen_perft_test("2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1".to_owned(), 6, 3821001); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_14() { movegen_perft_test("3K4/8/8/8/8/8/4p3/2k2R2 b - - 0 1".to_owned(), 6, 3821001); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_15() { movegen_perft_test("8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1".to_owned(), 5, 1004658); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_16() { movegen_perft_test("5K2/8/1Q6/2N5/8/1p2k3/8/8 w - - 0 1".to_owned(), 5, 1004658); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_17() { movegen_perft_test("4k3/1P6/8/8/8/8/K7/8 w - - 0 1".to_owned(), 6, 217342); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_18() { movegen_perft_test("8/k7/8/8/8/8/1p6/4K3 b - - 0 1".to_owned(), 6, 217342); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_19() { movegen_perft_test("8/P1k5/K7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 92683); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_20() { movegen_perft_test("8/8/8/8/8/k7/p1K5/8 b - - 0 1".to_owned(), 6, 92683); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_21() { movegen_perft_test("K1k5/8/P7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 2217); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_22() { movegen_perft_test("8/8/8/8/8/p7/8/k1K5 b - - 0 1".to_owned(), 6, 2217); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_23() { movegen_perft_test("8/k1P5/8/1K6/8/8/8/8 w - - 0 1".to_owned(), 7, 567584); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_24() { movegen_perft_test("8/8/8/8/1k6/8/K1p5/8 b - - 0 1".to_owned(), 7, 567584); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_25() { movegen_perft_test("8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1".to_owned(), 4, 23527); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_perft_26() { movegen_perft_test("8/5k2/8/5N2/5Q2/2K5/8/8 w - - 0 1".to_owned(), 4, 23527); } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn movegen_issue_15() { let board = @@ -548,7 +582,7 @@ fn movegen_issue_15() { let _ = MoveGen::new_legal(&board); } -#[cfg(all(test, feature="std"))] +#[cfg(all(test, feature = "std"))] fn move_of(m: &str) -> ChessMove { let promo = if m.len() > 4 { Some(match m.as_bytes()[4] { @@ -568,7 +602,7 @@ fn move_of(m: &str) -> ChessMove { ) } -#[cfg(feature="std")] +#[cfg(feature = "std")] #[test] fn test_masked_move_gen() { let board = diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index fc57553b..2dfe339e 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -13,18 +13,26 @@ use crate::magic::{ pub trait PieceType { fn into_piece() -> Piece; - //? What is the purpose of this function - #[allow(dead_code)] + + #[allow(dead_code)] //? What is the purpose of this function #[inline(always)] fn is(piece: Piece) -> bool { Self::into_piece() == piece } - fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard; + + fn pseudo_legals( + src: Square, + color: Color, + combined: BitBoard, + unoccupied_by_me: BitBoard, + ) -> BitBoard; + #[inline] - fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) - where - T: CheckType, - { + fn legals( + movelist: &mut MoveList, + board: &Board, + unoccupied_by_me: BitBoard, + ) { let combined = board.combined(); let color = board.side_to_move(); let my_pieces = board.color_combined(color); @@ -34,14 +42,14 @@ pub trait PieceType { let pinned = board.pinned(); let checkers = board.checkers(); - let check_mask = if T::IN_CHECK { + let check_mask = if IN_CHECK { between(checkers.to_square(), ksq) ^ checkers } else { !EMPTY }; for src in pieces & !pinned { - let moves = Self::pseudo_legals(src, color, *combined, mask) & check_mask; + let moves = Self::pseudo_legals(src, color, *combined, unoccupied_by_me) & check_mask; if moves != EMPTY { unsafe { movelist.push_unchecked(SquareAndBitBoard::new(src, moves, false)); @@ -49,9 +57,10 @@ pub trait PieceType { } } - if !T::IN_CHECK { + if !IN_CHECK { for src in pieces & pinned { - let moves = Self::pseudo_legals(src, color, *combined, mask) & line(src, ksq); + let moves = + Self::pseudo_legals(src, color, *combined, unoccupied_by_me) & line(src, ksq); if moves != EMPTY { unsafe { movelist.push_unchecked(SquareAndBitBoard::new(src, moves, false)); @@ -60,6 +69,43 @@ pub trait PieceType { } } } + + #[inline] + fn has_legals(board: &Board, unoccupied_by_me: BitBoard) -> bool { + let combined = board.combined(); + let color = board.side_to_move(); + let my_pieces = board.color_combined(color); + let ksq = board.king_square(color); + + let pieces = board.pieces(Self::into_piece()) & my_pieces; + let pinned = board.pinned(); + let checkers = board.checkers(); + + let check_mask = if IN_CHECK { + between(checkers.to_square(), ksq) ^ checkers + } else { + !EMPTY + }; + + for src in pieces & !pinned { + let moves = Self::pseudo_legals(src, color, *combined, unoccupied_by_me) & check_mask; + if moves != EMPTY { + return true; + } + } + + if !IN_CHECK { + for src in pieces & pinned { + let moves = + Self::pseudo_legals(src, color, *combined, unoccupied_by_me) & line(src, ksq); + if moves != EMPTY { + return true; + } + } + } + + false + } } pub struct PawnType; @@ -69,23 +115,9 @@ pub struct RookType; pub struct QueenType; pub struct KingType; -pub trait CheckType { - const IN_CHECK: bool; -} - -pub struct InCheckType; -pub struct NotInCheckType; - -impl CheckType for InCheckType { - const IN_CHECK: bool = true; -} - -impl CheckType for NotInCheckType { - const IN_CHECK: bool = false; -} - impl PawnType { /// Is a particular en-passant capture legal? + #[inline] pub fn legal_ep_move(board: &Board, source: Square, dest: Square) -> bool { let combined = board.combined() ^ BitBoard::from_square(board.en_passant().unwrap()) @@ -122,15 +154,21 @@ impl PieceType for PawnType { } #[inline(always)] - fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard { - get_pawn_moves(src, color, combined) & mask + fn pseudo_legals( + src: Square, + color: Color, + combined: BitBoard, + unoccupied_by_me: BitBoard, + ) -> BitBoard { + get_pawn_moves(src, color, combined) & unoccupied_by_me } #[inline] - fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) - where - T: CheckType, - { + fn legals( + movelist: &mut MoveList, + board: &Board, + unoccupied_by_me: BitBoard, + ) { let combined = board.combined(); let color = board.side_to_move(); let my_pieces = board.color_combined(color); @@ -140,14 +178,14 @@ impl PieceType for PawnType { let pinned = board.pinned(); let checkers = board.checkers(); - let check_mask = if T::IN_CHECK { + let check_mask = if IN_CHECK { between(checkers.to_square(), ksq) ^ checkers } else { !EMPTY }; for src in pieces & !pinned { - let moves = Self::pseudo_legals(src, color, *combined, mask) & check_mask; + let moves = Self::pseudo_legals(src, color, *combined, unoccupied_by_me) & check_mask; if moves != EMPTY { unsafe { movelist.push_unchecked(SquareAndBitBoard::new( @@ -159,9 +197,10 @@ impl PieceType for PawnType { } } - if !T::IN_CHECK { + if !IN_CHECK { for src in pieces & pinned { - let moves = Self::pseudo_legals(src, color, *combined, mask) & line(ksq, src); + let moves = + Self::pseudo_legals(src, color, *combined, unoccupied_by_me) & line(ksq, src); if moves != EMPTY { unsafe { movelist.push_unchecked(SquareAndBitBoard::new( @@ -192,6 +231,58 @@ impl PieceType for PawnType { } } } + + #[inline] + fn has_legals( + board: &Board, + unoccupied_by_me: BitBoard, + ) -> bool { + let combined = board.combined(); + let color = board.side_to_move(); + let my_pieces = board.color_combined(color); + let ksq = board.king_square(color); + + let pieces = board.pieces(Self::into_piece()) & my_pieces; + let pinned = board.pinned(); + let checkers = board.checkers(); + + let check_mask = if IN_CHECK { + between(checkers.to_square(), ksq) ^ checkers + } else { + !EMPTY + }; + + for src in pieces & !pinned { + let moves = Self::pseudo_legals(src, color, *combined, unoccupied_by_me) & check_mask; + if moves != EMPTY { + return true; + } + } + + if !IN_CHECK { + for src in pieces & pinned { + let moves = + Self::pseudo_legals(src, color, *combined, unoccupied_by_me) & line(ksq, src); + if moves != EMPTY { + return true; + } + } + } + + if board.en_passant().is_some() { + let ep_sq = board.en_passant().unwrap(); + let rank = get_rank(ep_sq.get_rank()); + let files = get_adjacent_files(ep_sq.get_file()); + for src in rank & files & pieces { + let dest = ep_sq.uforward(color); + if PawnType::legal_ep_move(board, src, dest) { + return true; + } + } + } + + false + } } impl PieceType for BishopType { @@ -206,8 +297,13 @@ impl PieceType for BishopType { } #[inline(always)] - fn pseudo_legals(src: Square, _color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard { - get_bishop_moves(src, combined) & mask + fn pseudo_legals( + src: Square, + _color: Color, + combined: BitBoard, + unoccupied_by_me: BitBoard, + ) -> BitBoard { + get_bishop_moves(src, combined) & unoccupied_by_me } } @@ -223,15 +319,21 @@ impl PieceType for KnightType { } #[inline(always)] - fn pseudo_legals(src: Square, _color: Color, _combined: BitBoard, mask: BitBoard) -> BitBoard { - get_knight_moves(src) & mask + fn pseudo_legals( + src: Square, + _color: Color, + _combined: BitBoard, + unoccupied_by_me: BitBoard, + ) -> BitBoard { + get_knight_moves(src) & unoccupied_by_me } #[inline] - fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) - where - T: CheckType, - { + fn legals( + movelist: &mut MoveList, + board: &Board, + unoccupied_by_me: BitBoard, + ) { let combined = board.combined(); let color = board.side_to_move(); let my_pieces = board.color_combined(color); @@ -241,11 +343,12 @@ impl PieceType for KnightType { let pinned = board.pinned(); let checkers = board.checkers(); - if T::IN_CHECK { + if IN_CHECK { let check_mask = between(checkers.to_square(), ksq) ^ checkers; for src in pieces & !pinned { - let moves = Self::pseudo_legals(src, color, *combined, mask & check_mask); + let moves = + Self::pseudo_legals(src, color, *combined, unoccupied_by_me & check_mask); if moves != EMPTY { unsafe { movelist.push_unchecked(SquareAndBitBoard::new(src, moves, false)); @@ -254,7 +357,7 @@ impl PieceType for KnightType { } } else { for src in pieces & !pinned { - let moves = Self::pseudo_legals(src, color, *combined, mask); + let moves = Self::pseudo_legals(src, color, *combined, unoccupied_by_me); if moves != EMPTY { unsafe { movelist.push_unchecked(SquareAndBitBoard::new(src, moves, false)); @@ -263,6 +366,42 @@ impl PieceType for KnightType { } }; } + + #[inline] + fn has_legals( + board: &Board, + unoccupied_by_me: BitBoard, + ) -> bool { + let combined = board.combined(); + let color = board.side_to_move(); + let my_pieces = board.color_combined(color); + let ksq = board.king_square(color); + + let pieces = board.pieces(Self::into_piece()) & my_pieces; + let pinned = board.pinned(); + let checkers = board.checkers(); + + if IN_CHECK { + let check_mask = between(checkers.to_square(), ksq) ^ checkers; + + for src in pieces & !pinned { + let moves = + Self::pseudo_legals(src, color, *combined, unoccupied_by_me & check_mask); + if moves != EMPTY { + return true; + } + } + } else { + for src in pieces & !pinned { + let moves = Self::pseudo_legals(src, color, *combined, unoccupied_by_me); + if moves != EMPTY { + return true; + } + } + }; + + false + } } impl PieceType for RookType { @@ -276,8 +415,13 @@ impl PieceType for RookType { } #[inline(always)] - fn pseudo_legals(src: Square, _color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard { - get_rook_moves(src, combined) & mask + fn pseudo_legals( + src: Square, + _color: Color, + combined: BitBoard, + unoccupied_by_me: BitBoard, + ) -> BitBoard { + get_rook_moves(src, combined) & unoccupied_by_me } } @@ -292,8 +436,13 @@ impl PieceType for QueenType { } #[inline(always)] - fn pseudo_legals(src: Square, _color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard { - (get_rook_moves(src, combined) ^ get_bishop_moves(src, combined)) & mask + fn pseudo_legals( + src: Square, + _color: Color, + combined: BitBoard, + unoccupied_by_me: BitBoard, + ) -> BitBoard { + (get_rook_moves(src, combined) ^ get_bishop_moves(src, combined)) & unoccupied_by_me } } @@ -346,20 +495,26 @@ impl PieceType for KingType { } #[inline(always)] - fn pseudo_legals(src: Square, _color: Color, _combined: BitBoard, mask: BitBoard) -> BitBoard { - get_king_moves(src) & mask + fn pseudo_legals( + src: Square, + _color: Color, + _combined: BitBoard, + unoccupied_by_me: BitBoard, + ) -> BitBoard { + get_king_moves(src) & unoccupied_by_me } #[inline] - fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) - where - T: CheckType, - { + fn legals( + movelist: &mut MoveList, + board: &Board, + unoccupied_by_me: BitBoard, + ) { let combined = board.combined(); let color = board.side_to_move(); let ksq = board.king_square(color); - let mut moves = Self::pseudo_legals(ksq, color, *combined, mask); + let mut moves = Self::pseudo_legals(ksq, color, *combined, unoccupied_by_me); let copy = moves; for dest in copy { @@ -376,7 +531,7 @@ impl PieceType for KingType { // destination square. // ** This is determined by going to the left or right, and calling // 'legal_king_move' for that square. - if !T::IN_CHECK { + if !IN_CHECK { if board.my_castle_rights().has_kingside() && (combined & board.my_castle_rights().kingside_squares(color)) == EMPTY { @@ -407,4 +562,59 @@ impl PieceType for KingType { } } } + + #[inline] + fn has_legals( + board: &Board, + unoccupied_by_me: BitBoard, + ) -> bool { + let combined = board.combined(); + let color = board.side_to_move(); + let ksq = board.king_square(color); + + let mut moves = Self::pseudo_legals(ksq, color, *combined, unoccupied_by_me); + + let copy = moves; + for dest in copy { + if !KingType::legal_king_move(board, dest) { + moves ^= BitBoard::from_square(dest); + } + } + + // If we are not in check, we may be able to castle. + // We can do so iff: + // * the `Board` structure says we can. + // * the squares between my king and my rook are empty. + // * no enemy pieces are attacking the squares between the king, and the kings + // destination square. + // ** This is determined by going to the left or right, and calling + // 'legal_king_move' for that square. + if !IN_CHECK { + if board.my_castle_rights().has_kingside() + && (combined & board.my_castle_rights().kingside_squares(color)) == EMPTY + { + let middle = ksq.uright(); + let right = middle.uright(); + if KingType::legal_king_move(board, middle) + && KingType::legal_king_move(board, right) + { + moves ^= BitBoard::from_square(right); + } + } + + if board.my_castle_rights().has_queenside() + && (combined & board.my_castle_rights().queenside_squares(color)) == EMPTY + { + let middle = ksq.uleft(); + let left = middle.uleft(); + if KingType::legal_king_move(board, middle) + && KingType::legal_king_move(board, left) + { + moves ^= BitBoard::from_square(left); + } + } + } + + moves != EMPTY + } } From 05fc74280d9c2f12ef385a27f288897e551876ad Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 13:17:51 -0700 Subject: [PATCH 56/94] update doctests and add make_move_mutref wrapper function --- src/board.rs | 155 ++++++++++++++++++++------------------------------- 1 file changed, 60 insertions(+), 95 deletions(-) diff --git a/src/board.rs b/src/board.rs index 03593be8..ed606f9e 100644 --- a/src/board.rs +++ b/src/board.rs @@ -246,44 +246,28 @@ impl Board { } /// Is this game Ongoing, is it Stalemate, or is it Checkmate? - /// + /// /// Note: This function is optimized to only find the first possible move to minimize time cost. /// /// ``` /// use chess::{Board, BoardStatus, Square, ChessMove}; /// /// let mut board = Board::default(); - /// /// assert_eq!(board.status(), BoardStatus::Ongoing); /// - /// board = board.make_move_new(ChessMove::new(Square::E2, - /// Square::E4, - /// None)); - /// + /// board = board.make_move_new(ChessMove::new(Square::E2, Square::E4, None)); /// assert_eq!(board.status(), BoardStatus::Ongoing); /// - /// board = board.make_move_new(ChessMove::new(Square::F7, - /// Square::F6, - /// None)); - /// + /// board = board.make_move_new(ChessMove::new(Square::F7, Square::F6, None)); /// assert_eq!(board.status(), BoardStatus::Ongoing); /// - /// board = board.make_move_new(ChessMove::new(Square::D2, - /// Square::D4, - /// None)); - /// + /// board = board.make_move_new(ChessMove::new(Square::D2, Square::D4, None)); /// assert_eq!(board.status(), BoardStatus::Ongoing); /// - /// board = board.make_move_new(ChessMove::new(Square::G7, - /// Square::G5, - /// None)); - /// + /// board = board.make_move_new(ChessMove::new(Square::G7, Square::G5, None)); /// assert_eq!(board.status(), BoardStatus::Ongoing); /// - /// board = board.make_move_new(ChessMove::new(Square::D1, - /// Square::H5, - /// None)); - /// + /// board = board.make_move_new(ChessMove::new(Square::D1, Square::H5, None)); /// assert_eq!(board.status(), BoardStatus::Checkmate); /// ``` #[inline] @@ -380,31 +364,20 @@ impl Board { /// ``` /// use chess::{Board, Square, CastleRights, Color, ChessMove}; /// - /// let move1 = ChessMove::new(Square::A2, - /// Square::A4, - /// None); - /// - /// let move2 = ChessMove::new(Square::E7, - /// Square::E5, - /// None); - /// - /// let move3 = ChessMove::new(Square::A1, - /// Square::A2, - /// None); - /// - /// let move4 = ChessMove::new(Square::E8, - /// Square::E7, - /// None); + /// let m1 = ChessMove::new(Square::A2, Square::A4, None); + /// let m2 = ChessMove::new(Square::E7, Square::E5, None); + /// let m3 = ChessMove::new(Square::A1, Square::A2, None); + /// let m4 = ChessMove::new(Square::E8, Square::E7, None); /// /// let mut board = Board::default(); /// /// assert_eq!(board.castle_rights(Color::White), CastleRights::Both); /// assert_eq!(board.castle_rights(Color::Black), CastleRights::Both); /// - /// board = board.make_move_new(move1) - /// .make_move_new(move2) - /// .make_move_new(move3) - /// .make_move_new(move4); + /// board = board.make_move_new(m1) + /// .make_move_new(m2) + /// .make_move_new(m3) + /// .make_move_new(m4); /// /// assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide); /// assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights); @@ -582,9 +555,7 @@ impl Board { /// /// let board = Board::default(); /// - /// let new_board = board.set_piece(Piece::Queen, - /// Color::White, - /// Square::E4) + /// let new_board = board.set_piece(Piece::Queen, Color::White, Square::E4) /// .expect("Valid Position"); /// /// assert_eq!(new_board.pieces(Piece::Queen).count(), 3); @@ -933,26 +904,15 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square}; /// - /// let move1 = ChessMove::new(Square::D2, - /// Square::D4, - /// None); - /// - /// let move2 = ChessMove::new(Square::H7, - /// Square::H5, - /// None); - /// - /// let move3 = ChessMove::new(Square::D4, - /// Square::D5, - /// None); + /// let m1 = ChessMove::new(Square::D2, Square::D4, None); + /// let m2 = ChessMove::new(Square::H7, Square::H5, None); + /// let m3 = ChessMove::new(Square::D4, Square::D5, None); + /// let m4 = ChessMove::new(Square::E7, Square::E5, None); /// - /// let move4 = ChessMove::new(Square::E7, - /// Square::E5, - /// None); - /// - /// let board = Board::default().make_move_new(move1) - /// .make_move_new(move2) - /// .make_move_new(move3) - /// .make_move_new(move4); + /// let board = Board::default().make_move_new(m1) + /// .make_move_new(m2) + /// .make_move_new(m3) + /// .make_move_new(m4); /// /// assert_eq!(board.en_passant(), Some(Square::E5)); /// ``` @@ -966,26 +926,15 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square}; /// - /// let move1 = ChessMove::new(Square::D2, - /// Square::D4, - /// None); - /// - /// let move2 = ChessMove::new(Square::H7, - /// Square::H5, - /// None); + /// let m1 = ChessMove::new(Square::D2, Square::D4, None); + /// let m2 = ChessMove::new(Square::H7, Square::H5, None); + /// let m3 = ChessMove::new(Square::D4, Square::D5, None); + /// let m4 = ChessMove::new(Square::E7, Square::E5, None); /// - /// let move3 = ChessMove::new(Square::D4, - /// Square::D5, - /// None); - /// - /// let move4 = ChessMove::new(Square::E7, - /// Square::E5, - /// None); - /// - /// let board = Board::default().make_move_new(move1) - /// .make_move_new(move2) - /// .make_move_new(move3) - /// .make_move_new(move4); + /// let board = Board::default().make_move_new(m1) + /// .make_move_new(m2) + /// .make_move_new(m3) + /// .make_move_new(m4); /// /// assert_eq!(board.en_passant_target(), Some(Square::E6)); /// ``` @@ -1015,18 +964,13 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square, MoveGen}; /// - /// let move1 = ChessMove::new(Square::E2, - /// Square::E4, - /// None); - /// - /// let move2 = ChessMove::new(Square::E2, - /// Square::E5, - /// None); + /// let m1 = ChessMove::new(Square::E2, Square::E4, None); + /// let m2 = ChessMove::new(Square::E2, Square::E5, None); /// /// let board = Board::default(); /// - /// assert_eq!(board.legal(move1), true); - /// assert_eq!(board.legal(move2), false); + /// assert_eq!(board.legal(m1), true); + /// assert_eq!(board.legal(m2), false); /// ``` #[inline] pub fn legal(&self, m: ChessMove) -> bool { @@ -1040,20 +984,41 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square, Color}; /// - /// let m = ChessMove::new(Square::D2, - /// Square::D4, - /// None); + /// let m = ChessMove::new(Square::D2, Square::D4, None); /// /// let board = Board::default(); /// assert_eq!(board.make_move_new(m).side_to_move(), Color::Black); /// ``` #[inline] pub fn make_move_new(&self, m: ChessMove) -> Board { - let mut result = *self; + let mut result = Board::new(); self.make_move(m, &mut result); result } + /// Make a chess move on this board without creating a new one, returns `&mut Board` so you can chain operations. + /// + /// panic!() if king is captured. + /// + /// ``` + /// use chess::{Board, ChessMove, Square, Color}; + /// + /// let m1 = ChessMove::new(Square::D2, Square::D4, None); + /// let m2 = ChessMove::new(Square::E7, Square::E5, None); + /// + /// let mut board = Board::default(); + /// + /// board.make_move_mutref(m1).make_move_mutref(m2); + /// + /// assert_eq!(board.side_to_move(), Color::White); + /// ``` + pub fn make_move_mutref(&mut self, m: ChessMove) -> &mut Self { + let mut result = Board::new(); + self.make_move(m, &mut result); + *self = result; + self + } + /// Make a chess move onto an already allocated `Board`. /// /// panic!() if king is captured. From 8ff7f0aa127d704020e99f25454ec35602c60c8f Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 14:22:11 -0700 Subject: [PATCH 57/94] Revert toml --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5d4b11c..4428f3a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,12 @@ authors = ["Jordan Bray ", "Orkking2"] description = "This is a fast chess move generator. It has a very good set of documentation, so you should take advantage of that. It (now) generates all lookup tables with a build.rs file, which means that very little pseudo-legal move generation requires branching. There are some convenience functions that are exposed to, for example, find all the squares between two squares. This uses a copy-on-make style structure, and the Board structure is as slimmed down as possible to reduce the cost of copying the board. There are places to improve perft-test performance further, but I instead opt to be more feature-complete to make it useful in real applications. For example, I generate both a hash of the board and a pawn-hash of the board for use in evaluation lookup tables (using Zobrist hashing). There are two ways to generate moves, one is faster, the other has more features that will be useful if making a chess engine. See the documentation for more details." build = "src/build.rs" -homepage = "https://github.com/Orkking2/chess" -repository = "https://github.com/Orkking2/chess" +homepage = "https://github.com/jordanbray/chess" +repository = "https://github.com/jordanbray/chess" readme = "README.md" keywords = ["chess", "move", "generator"] license = "MIT" -documentation = "https://orkking2.github.io/chess/chess/index.html" +documentation = "https://jordanbray.github.io/chess/chess/index.html" rust-version = "1.56.0" [dependencies] From 7e2377326b5708f71e4db97c36b4a5ffd35a3252 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 14:54:44 -0700 Subject: [PATCH 58/94] Refactor `Board::STARTPOS` into static `crate::STARTPOS` and remove `BitBoard::from_array` --- src/bitboard.rs | 18 ------- src/board.rs | 139 ++++++------------------------------------------ 2 files changed, 15 insertions(+), 142 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index 8d61b643..bf6930a2 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -321,24 +321,6 @@ impl BitBoard { pub const fn to_size(&self, rightshift: u8) -> usize { (self.0 >> rightshift) as usize } - - pub(crate) const fn from_array(arr: [u8; 8]) -> Self { - let mut accum = 0u64; - - let mut i = 0; - - loop { - accum |= arr[i] as u64; - i += 1; - if i == 8 { - break; - } else { - accum <<= 8; - } - } - - BitBoard::new(accum) - } } /// For the `BitBoard`, iterate over every `Square` set. diff --git a/src/board.rs b/src/board.rs index ed606f9e..3d2aab29 100644 --- a/src/board.rs +++ b/src/board.rs @@ -18,6 +18,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; +use std::sync::LazyLock; /// A representation of a chess board. That's why you're here, right? #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -58,7 +59,7 @@ impl Default for Board { /// ``` #[inline] fn default() -> Board { - Self::STARTPOS + *STARTPOS } } @@ -68,123 +69,11 @@ impl Hash for Board { } } -impl Board { - /// Represents the start position of a chess game. - /// ```text - /// ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ - /// ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ - /// . . . . . . . . - /// . . . . . . . . - /// . . . . . . . . - /// . . . . . . . . - /// ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ - /// ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ - /// ``` - pub const STARTPOS: Self = { - const PAWN_BITBOARD: BitBoard = BitBoard::from_array([ - 0b0000_0000, - 0b1111_1111, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b1111_1111, - 0b0000_0000, - ]); - const KNIGHT_BITBOARD: BitBoard = BitBoard::from_array([ - 0b0100_0010, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0100_0010, - ]); - const BISHOP_BITBOARD: BitBoard = BitBoard::from_array([ - 0b0010_0100, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0010_0100, - ]); - const ROOK_BITBOARD: BitBoard = BitBoard::from_array([ - 0b1000_0001, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b1000_0001, - ]); - const QUEEN_BITBOARD: BitBoard = BitBoard::from_array([ - 0b0000_1000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_1000, - ]); - const KING_BITBOARD: BitBoard = BitBoard::from_array([ - 0b0001_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0001_0000, - ]); - const BLACK_BITBOARD: BitBoard = BitBoard::from_array([ - 0b1111_1111, - 0b1111_1111, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - ]); - const WHITE_BITBOARD: BitBoard = BitBoard::from_array([ - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b0000_0000, - 0b1111_1111, - 0b1111_1111, - ]); - - const COMBINED_BITBOARD: BitBoard = BitBoard::new(BLACK_BITBOARD.0 | WHITE_BITBOARD.0); - const START_HASH: u64 = 11762218931632540; - - Self { - pieces: [ - PAWN_BITBOARD, - KNIGHT_BITBOARD, - BISHOP_BITBOARD, - ROOK_BITBOARD, - QUEEN_BITBOARD, - KING_BITBOARD, - ], - color_combined: [WHITE_BITBOARD, BLACK_BITBOARD], - combined: COMBINED_BITBOARD, - side_to_move: Color::White, - castle_rights: [CastleRights::Both; NUM_COLORS], - pinned: EMPTY, - checkers: EMPTY, - hash: START_HASH, - en_passant: None, - } - }; +static STARTPOS: LazyLock = LazyLock::new(|| { + Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").expect("Startpos FEN is valid FEN") +}); +impl Board { /// Construct a new `Board` that is completely empty. /// Note: This does NOT give you the initial position. Just a blank slate. const fn new() -> Board { @@ -1001,16 +890,18 @@ impl Board { /// panic!() if king is captured. /// /// ``` - /// use chess::{Board, ChessMove, Square, Color}; - /// - /// let m1 = ChessMove::new(Square::D2, Square::D4, None); - /// let m2 = ChessMove::new(Square::E7, Square::E5, None); + /// use chess::{Board, ChessMove, Square, Color, BoardStatus}; /// /// let mut board = Board::default(); /// - /// board.make_move_mutref(m1).make_move_mutref(m2); + /// board.make_move_mutref(ChessMove::new(Square::E2, Square::E4, None)) + /// .make_move_mutref(ChessMove::new(Square::F7, Square::F6, None)) + /// .make_move_mutref(ChessMove::new(Square::D2, Square::D4, None)) + /// .make_move_mutref(ChessMove::new(Square::G7, Square::G5, None)) + /// .make_move_mutref(ChessMove::new(Square::D1, Square::H5, None)); /// - /// assert_eq!(board.side_to_move(), Color::White); + /// + /// assert_eq!(board.status(), BoardStatus::Checkmate); /// ``` pub fn make_move_mutref(&mut self, m: ChessMove) -> &mut Self { let mut result = Board::new(); @@ -1272,6 +1163,6 @@ fn test_null_move_en_passant() { fn check_startpos_correct() { let startpos_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; let board = Board::from_str(startpos_fen).unwrap(); - let startpos = Board::STARTPOS; + let startpos = *STARTPOS; assert_eq!(board, startpos, "Startpos is not correct"); } From 9a4139f82aaa6f8838abfe8e9b3a0ae72166265a Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 15:16:59 -0700 Subject: [PATCH 59/94] inline `Board::new` --- src/board.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/board.rs b/src/board.rs index 3d2aab29..0fa8f209 100644 --- a/src/board.rs +++ b/src/board.rs @@ -76,6 +76,7 @@ static STARTPOS: LazyLock = LazyLock::new(|| { impl Board { /// Construct a new `Board` that is completely empty. /// Note: This does NOT give you the initial position. Just a blank slate. + #[inline] const fn new() -> Board { Board { pieces: [EMPTY; NUM_PIECES], From 0b801893169a349d47cb56c0dad7426623b6effd Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 15:33:17 -0700 Subject: [PATCH 60/94] `Board::make_moves` and `Board::make_moves_new` --- src/board.rs | 62 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/src/board.rs b/src/board.rs index 0fa8f209..9a865ec6 100644 --- a/src/board.rs +++ b/src/board.rs @@ -70,14 +70,15 @@ impl Hash for Board { } static STARTPOS: LazyLock = LazyLock::new(|| { - Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").expect("Startpos FEN is valid FEN") + Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + .expect("Startpos FEN is valid FEN") }); impl Board { /// Construct a new `Board` that is completely empty. /// Note: This does NOT give you the initial position. Just a blank slate. #[inline] - const fn new() -> Board { + pub const fn new() -> Board { Board { pieces: [EMPTY; NUM_PIECES], color_combined: [EMPTY; NUM_COLORS], @@ -886,29 +887,47 @@ impl Board { result } - /// Make a chess move on this board without creating a new one, returns `&mut Board` so you can chain operations. - /// - /// panic!() if king is captured. - /// + /// This function exists to mimick the functionality of `make_move`, internally it uses `make_moves_new` + /// /// ``` - /// use chess::{Board, ChessMove, Square, Color, BoardStatus}; - /// - /// let mut board = Board::default(); + /// use chess::{Board, ChessMove, Square, BoardStatus}; + /// + /// let moves = vec![ChessMove::new(Square::E2, Square::E4, None), + /// ChessMove::new(Square::F7, Square::F6, None), + /// ChessMove::new(Square::D2, Square::D4, None), + /// ChessMove::new(Square::G7, Square::G5, None), + /// ChessMove::new(Square::D1, Square::H5, None)]; + /// + /// let board = Board::default(); + /// let mut result = Board::new(); + /// board.make_moves(moves, &mut result); + /// assert_eq!(result.status(), BoardStatus::Checkmate); + /// ``` + #[inline] + pub fn make_moves>(&self, moves: T, result: &mut Board) { + *result = self.make_moves_new(moves); + } + + /// Apply a series of moves to a board. /// - /// board.make_move_mutref(ChessMove::new(Square::E2, Square::E4, None)) - /// .make_move_mutref(ChessMove::new(Square::F7, Square::F6, None)) - /// .make_move_mutref(ChessMove::new(Square::D2, Square::D4, None)) - /// .make_move_mutref(ChessMove::new(Square::G7, Square::G5, None)) - /// .make_move_mutref(ChessMove::new(Square::D1, Square::H5, None)); + /// ``` + /// use chess::{Board, ChessMove, Square, BoardStatus}; /// + /// let moves = vec![ChessMove::new(Square::E2, Square::E4, None), + /// ChessMove::new(Square::F7, Square::F6, None), + /// ChessMove::new(Square::D2, Square::D4, None), + /// ChessMove::new(Square::G7, Square::G5, None), + /// ChessMove::new(Square::D1, Square::H5, None)]; /// - /// assert_eq!(board.status(), BoardStatus::Checkmate); + /// let board = Board::default(); + /// let board2 = board.make_moves_new(moves); + /// assert_eq!(board2.status(), BoardStatus::Checkmate); /// ``` - pub fn make_move_mutref(&mut self, m: ChessMove) -> &mut Self { - let mut result = Board::new(); - self.make_move(m, &mut result); - *self = result; - self + #[inline] + pub fn make_moves_new>(&self, moves: T) -> Board { + moves + .into_iter() + .fold(*self, |acc: Board, m| acc.make_move_new(m)) } /// Make a chess move onto an already allocated `Board`. @@ -923,7 +942,8 @@ impl Board { /// None); /// /// let board = Board::default(); - /// let mut result = Board::default(); + /// // It is generally less expensive to call Board::new() than Board::default() + /// let mut result = Board::new(); /// board.make_move(m, &mut result); /// assert_eq!(result.side_to_move(), Color::Black); /// ``` From b3d58f042615530404c83bef387a293118cf9ab4 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 15:47:41 -0700 Subject: [PATCH 61/94] Make use of the `Self` keyword --- src/chess_move.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/chess_move.rs b/src/chess_move.rs index 417306e9..65ba0902 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -23,7 +23,7 @@ pub struct ChessMove { impl ChessMove { /// An invalid move, can make `Option` more efficient. See `into_option`. - pub const NULL_MOVE: ChessMove = ChessMove { + pub const NULL_MOVE: Self = Self { source: Square::A1, dest: Square::A1, promotion: None, @@ -39,7 +39,7 @@ impl ChessMove { /// /// Call `.from_option` to compress back into a `ChessMove`. #[inline] - pub fn into_option(self) -> Option { + pub fn into_option(self) -> Option { if self.is_null_move() { None } else { @@ -51,7 +51,7 @@ impl ChessMove { /// /// Call `.into_option` to expand back into an `Option`. #[inline] - pub const fn from_option(value: Option) -> ChessMove { + pub const fn from_option(value: Option) -> Self { if let Some(mov) = value { mov } else { @@ -62,8 +62,8 @@ impl ChessMove { /// Create a new chess move, given a source `Square`, a destination `Square`, and an optional /// promotion `Piece` #[inline] - pub const fn new(source: Square, dest: Square, promotion: Option) -> ChessMove { - ChessMove { + pub const fn new(source: Square, dest: Square, promotion: Option) -> Self { + Self { source, dest, promotion, @@ -98,14 +98,14 @@ impl ChessMove { /// ChessMove::new(Square::E2, Square::E4, None) /// ); /// ``` - pub fn from_san(board: &Board, move_text: &str) -> Result { + pub fn from_san(board: &Board, move_text: &str) -> Result { // Castles first... if move_text == "O-O" || move_text == "O-O-O" { let rank = board.side_to_move().to_my_backrank(); let source_file = File::E; let dest_file = if move_text == "O-O" { File::G } else { File::C }; - let m = ChessMove::new( + let m = Self::new( Square::make_square(rank, source_file), Square::make_square(rank, dest_file), None, @@ -356,7 +356,7 @@ impl ChessMove { // moving_piece, source_rank, source_file, taks, dest, promotion, maybe_check_or_mate, and // ep - let mut found_move: Option = None; + let mut found_move: Option = None; for m in &mut MoveGen::new_legal(board) { // check that the move has the properties specified if board.piece_on(m.get_source()) != Some(moving_piece) { @@ -458,7 +458,7 @@ impl fmt::Display for ChessMove { //? Why does this exist? //? What does it even mean for a move to be "less" than another? impl Ord for ChessMove { - fn cmp(&self, other: &ChessMove) -> Ordering { + fn cmp(&self, other: &Self) -> Ordering { if self.source != other.source { self.source.cmp(&other.source) } else if self.dest != other.dest { @@ -510,7 +510,7 @@ impl FromStr for ChessMove { }); } - Ok(ChessMove::new(source, dest, promo)) + Ok(Self::new(source, dest, promo)) } } From f8f4fcea8a9e5707612d7897bed579dbeb8a3da7 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 15:49:56 -0700 Subject: [PATCH 62/94] remove unnessecary `core::` specifier --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index a71544db..a01394d9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,7 +29,7 @@ pub enum InvalidError { } impl fmt::Display for InvalidError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { #[cfg(feature="std")] Self::FEN{ fen: s } => write!(f, "Invalid FEN string: {}", s), From 8be3c20d8759e9b7627844b7a52cd55e85e6f956 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 15:53:02 -0700 Subject: [PATCH 63/94] update liscense --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 32a8f0fa..21e6c1d6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2010-2016 Jordan Bray +Copyright (c) 2010-2025 Jordan Bray Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From a6c9ead22ebd63de1146ee76703318e6196efb03 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 15:59:52 -0700 Subject: [PATCH 64/94] remove unnecessary convertion --- src/gen_tables/between.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gen_tables/between.rs b/src/gen_tables/between.rs index 4f2e42f0..cb001785 100644 --- a/src/gen_tables/between.rs +++ b/src/gen_tables/between.rs @@ -25,12 +25,12 @@ pub fn gen_between() { BETWEEN[src.into_index()][dest.into_index()] = ALL_SQUARES .iter() .filter(|test| { - let src_rank = src.get_rank().into_index() as i8; - let src_file = src.get_file().into_index() as i8; - let dest_rank = dest.get_rank().into_index() as i8; - let dest_file = dest.get_file().into_index() as i8; - let test_rank = test.get_rank().into_index() as i8; - let test_file = test.get_file().into_index() as i8; + let src_rank = src.get_rank() as i8; + let src_file = src.get_file() as i8; + let dest_rank = dest.get_rank() as i8; + let dest_file = dest.get_file() as i8; + let test_rank = test.get_rank() as i8; + let test_file = test.get_file() as i8; // test diagonals first, as above if (src_rank - dest_rank).abs() == (src_file - dest_file).abs() From 40909577108f6dc4958f59b3b8a0f9875c08ab1a Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Fri, 14 Mar 2025 16:04:51 -0700 Subject: [PATCH 65/94] remove more unnecessary convertions --- src/gen_tables/king.rs | 8 ++++---- src/gen_tables/knights.rs | 8 ++++---- src/gen_tables/lines.rs | 12 ++++++------ src/gen_tables/rays.rs | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/gen_tables/king.rs b/src/gen_tables/king.rs index 67f96bc7..2c2bcfaf 100644 --- a/src/gen_tables/king.rs +++ b/src/gen_tables/king.rs @@ -18,10 +18,10 @@ pub fn gen_king_moves() { KING_MOVES[src.into_index()] = ALL_SQUARES .iter() .filter(|dest| { - let src_rank = src.get_rank().into_index() as i8; - let src_file = src.get_file().into_index() as i8; - let dest_rank = dest.get_rank().into_index() as i8; - let dest_file = dest.get_file().into_index() as i8; + let src_rank = src.get_rank() as i8; + let src_file = src.get_file() as i8; + let dest_rank = dest.get_rank() as i8; + let dest_file = dest.get_file() as i8; ((src_rank - dest_rank).abs() == 1 || (src_rank - dest_rank).abs() == 0) && ((src_file - dest_file).abs() == 1 || (src_file - dest_file).abs() == 0) diff --git a/src/gen_tables/knights.rs b/src/gen_tables/knights.rs index 12d47a79..428ab99b 100644 --- a/src/gen_tables/knights.rs +++ b/src/gen_tables/knights.rs @@ -14,10 +14,10 @@ pub fn gen_knight_moves() { KNIGHT_MOVES[src.into_index()] = ALL_SQUARES .iter() .filter(|dest| { - let src_rank = src.get_rank().into_index() as i8; - let src_file = src.get_file().into_index() as i8; - let dest_rank = dest.get_rank().into_index() as i8; - let dest_file = dest.get_file().into_index() as i8; + let src_rank = src.get_rank() as i8; + let src_file = src.get_file() as i8; + let dest_rank = dest.get_rank() as i8; + let dest_file = dest.get_file() as i8; ((src_rank - dest_rank).abs() == 2 && (src_file - dest_file).abs() == 1) || ((src_rank - dest_rank).abs() == 1 && (src_file - dest_file).abs() == 2) diff --git a/src/gen_tables/lines.rs b/src/gen_tables/lines.rs index b19a138d..dbac7e44 100644 --- a/src/gen_tables/lines.rs +++ b/src/gen_tables/lines.rs @@ -16,12 +16,12 @@ pub fn gen_lines() { LINE[src.into_index()][dest.into_index()] = ALL_SQUARES .iter() .filter(|test| { - let src_rank = src.get_rank().into_index() as i8; - let src_file = src.get_file().into_index() as i8; - let dest_rank = dest.get_rank().into_index() as i8; - let dest_file = dest.get_file().into_index() as i8; - let test_rank = test.get_rank().into_index() as i8; - let test_file = test.get_file().into_index() as i8; + let src_rank = src.get_rank() as i8; + let src_file = src.get_file() as i8; + let dest_rank = dest.get_rank() as i8; + let dest_file = dest.get_file() as i8; + let test_rank = test.get_rank() as i8; + let test_file = test.get_file() as i8; // test diagonals first if (src_rank - dest_rank).abs() == (src_file - dest_file).abs() diff --git a/src/gen_tables/rays.rs b/src/gen_tables/rays.rs index 9638e487..65a0998d 100644 --- a/src/gen_tables/rays.rs +++ b/src/gen_tables/rays.rs @@ -19,10 +19,10 @@ pub fn gen_bishop_rays() { RAYS[BISHOP][src.into_index()] = ALL_SQUARES .iter() .filter(|dest| { - let src_rank = src.get_rank().into_index() as i8; - let src_file = src.get_file().into_index() as i8; - let dest_rank = dest.get_rank().into_index() as i8; - let dest_file = dest.get_file().into_index() as i8; + let src_rank = src.get_rank() as i8; + let src_file = src.get_file() as i8; + let dest_rank = dest.get_rank() as i8; + let dest_file = dest.get_file() as i8; (src_rank - dest_rank).abs() == (src_file - dest_file).abs() && *src != **dest }) From 9bb751dd5d4d92c3f9709956405736a6bd2177ff Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 15 Mar 2025 13:33:35 -0700 Subject: [PATCH 66/94] make startpos public --- src/board.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index 9a865ec6..b7fb47f7 100644 --- a/src/board.rs +++ b/src/board.rs @@ -69,7 +69,7 @@ impl Hash for Board { } } -static STARTPOS: LazyLock = LazyLock::new(|| { +pub static STARTPOS: LazyLock = LazyLock::new(|| { Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") .expect("Startpos FEN is valid FEN") }); From 013bea0d1ba4f824597e35c920e6efd3b3efc650 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 15 Mar 2025 15:01:04 -0700 Subject: [PATCH 67/94] doctest for `STARTPOS` --- src/board.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index b7fb47f7..744dc6f1 100644 --- a/src/board.rs +++ b/src/board.rs @@ -69,6 +69,15 @@ impl Hash for Board { } } +/// The starting position of a chess board. +/// This `static` is of type `LazyLock` so that it only has to be computed once. +/// +/// ``` +/// use chess::{Board, STARTPOS}; +/// use std::str::FromStr; +/// +/// assert_eq!(*STARTPOS, Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); +/// ``` pub static STARTPOS: LazyLock = LazyLock::new(|| { Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") .expect("Startpos FEN is valid FEN") @@ -76,7 +85,7 @@ pub static STARTPOS: LazyLock = LazyLock::new(|| { impl Board { /// Construct a new `Board` that is completely empty. - /// Note: This does NOT give you the initial position. Just a blank slate. + /// Note: This does NOT give you the initial position. Just a blank slate. #[inline] pub const fn new() -> Board { Board { From 7dc6e440d567390497e3ae652560a74a8026364f Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 15 Mar 2025 19:00:58 -0700 Subject: [PATCH 68/94] Change doctests for `(my|their)_castle_rights` to increase clarity and not use deprecated functions --- src/board.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/board.rs b/src/board.rs index 744dc6f1..31993638 100644 --- a/src/board.rs +++ b/src/board.rs @@ -336,17 +336,15 @@ impl Board { self.side_to_move } - // Todo Rewrite -- This doctest uses deprecated functions. - /// Literally `board.castle_rights(board.side_to_move())` + /// My `CastleRights` /// /// ``` - /// use chess::{Board, Color, CastleRights}; - /// - /// let mut board = Board::default(); - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); + /// use chess::{Board, CastleRights}; + /// use std::str::FromStr; + /// + /// let board = Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Kq - 0 1").unwrap(); /// - /// assert_eq!(board.my_castle_rights(), board.castle_rights(Color::White)); + /// assert_eq!(board.my_castle_rights(), CastleRights::KingSide); /// ``` #[inline] pub fn my_castle_rights(&self) -> CastleRights { @@ -387,17 +385,15 @@ impl Board { self.remove_castle_rights(color, remove); } - // Todo Rewrite -- This doctest uses deprecated functions. /// My opponents `CastleRights`. /// /// ``` - /// use chess::{Board, Color, CastleRights}; - /// - /// let mut board = Board::default(); - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); + /// use chess::{Board, CastleRights}; + /// use std::str::FromStr; + /// + /// let board = Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Kq - 0 1").unwrap(); /// - /// assert_eq!(board.their_castle_rights(), board.castle_rights(Color::Black)); + /// assert_eq!(board.their_castle_rights(), CastleRights::QueenSide); /// ``` #[inline] pub fn their_castle_rights(&self) -> CastleRights { From ef85cdcf2789d4e131012ab7df0ea604755efab7 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 15 Mar 2025 19:39:02 -0700 Subject: [PATCH 69/94] Make use of `make_moves_new` in doctests along with noting that `vec!` might not be possible given `no_std` --- src/board.rs | 59 ++++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/board.rs b/src/board.rs index 31993638..6efbc9a2 100644 --- a/src/board.rs +++ b/src/board.rs @@ -264,20 +264,17 @@ impl Board { /// ``` /// use chess::{Board, Square, CastleRights, Color, ChessMove}; /// - /// let m1 = ChessMove::new(Square::A2, Square::A4, None); - /// let m2 = ChessMove::new(Square::E7, Square::E5, None); - /// let m3 = ChessMove::new(Square::A1, Square::A2, None); - /// let m4 = ChessMove::new(Square::E8, Square::E7, None); + /// let moves = [ChessMove::new(Square::A2, Square::A4, None), + /// ChessMove::new(Square::E7, Square::E5, None), + /// ChessMove::new(Square::A1, Square::A2, None), + /// ChessMove::new(Square::E8, Square::E7, None)]; /// /// let mut board = Board::default(); /// /// assert_eq!(board.castle_rights(Color::White), CastleRights::Both); /// assert_eq!(board.castle_rights(Color::Black), CastleRights::Both); /// - /// board = board.make_move_new(m1) - /// .make_move_new(m2) - /// .make_move_new(m3) - /// .make_move_new(m4); + /// board = board.make_moves_new(moves); /// /// assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide); /// assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights); @@ -800,15 +797,12 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square}; /// - /// let m1 = ChessMove::new(Square::D2, Square::D4, None); - /// let m2 = ChessMove::new(Square::H7, Square::H5, None); - /// let m3 = ChessMove::new(Square::D4, Square::D5, None); - /// let m4 = ChessMove::new(Square::E7, Square::E5, None); + /// let moves = [ChessMove::new(Square::D2, Square::D4, None), + /// ChessMove::new(Square::H7, Square::H5, None), + /// ChessMove::new(Square::D4, Square::D5, None), + /// ChessMove::new(Square::E7, Square::E5, None)]; /// - /// let board = Board::default().make_move_new(m1) - /// .make_move_new(m2) - /// .make_move_new(m3) - /// .make_move_new(m4); + /// let board = Board::default().make_moves_new(moves); /// /// assert_eq!(board.en_passant(), Some(Square::E5)); /// ``` @@ -822,15 +816,12 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square}; /// - /// let m1 = ChessMove::new(Square::D2, Square::D4, None); - /// let m2 = ChessMove::new(Square::H7, Square::H5, None); - /// let m3 = ChessMove::new(Square::D4, Square::D5, None); - /// let m4 = ChessMove::new(Square::E7, Square::E5, None); + /// let moves = [ChessMove::new(Square::D2, Square::D4, None), + /// ChessMove::new(Square::H7, Square::H5, None), + /// ChessMove::new(Square::D4, Square::D5, None), + /// ChessMove::new(Square::E7, Square::E5, None)]; /// - /// let board = Board::default().make_move_new(m1) - /// .make_move_new(m2) - /// .make_move_new(m3) - /// .make_move_new(m4); + /// let board = Board::default().make_moves_new(moves); /// /// assert_eq!(board.en_passant_target(), Some(Square::E6)); /// ``` @@ -897,11 +888,11 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square, BoardStatus}; /// - /// let moves = vec![ChessMove::new(Square::E2, Square::E4, None), - /// ChessMove::new(Square::F7, Square::F6, None), - /// ChessMove::new(Square::D2, Square::D4, None), - /// ChessMove::new(Square::G7, Square::G5, None), - /// ChessMove::new(Square::D1, Square::H5, None)]; + /// let moves = [ChessMove::new(Square::E2, Square::E4, None), + /// ChessMove::new(Square::F7, Square::F6, None), + /// ChessMove::new(Square::D2, Square::D4, None), + /// ChessMove::new(Square::G7, Square::G5, None), + /// ChessMove::new(Square::D1, Square::H5, None)]; /// /// let board = Board::default(); /// let mut result = Board::new(); @@ -918,11 +909,11 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square, BoardStatus}; /// - /// let moves = vec![ChessMove::new(Square::E2, Square::E4, None), - /// ChessMove::new(Square::F7, Square::F6, None), - /// ChessMove::new(Square::D2, Square::D4, None), - /// ChessMove::new(Square::G7, Square::G5, None), - /// ChessMove::new(Square::D1, Square::H5, None)]; + /// let moves = [ChessMove::new(Square::E2, Square::E4, None), + /// ChessMove::new(Square::F7, Square::F6, None), + /// ChessMove::new(Square::D2, Square::D4, None), + /// ChessMove::new(Square::G7, Square::G5, None), + /// ChessMove::new(Square::D1, Square::H5, None)]; /// /// let board = Board::default(); /// let board2 = board.make_moves_new(moves); From 0537382e18c4eb11d204f2eb0c4c639a4570c752 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 10:03:35 -0700 Subject: [PATCH 70/94] `piece_on_unchecked` should not make guarantees about what type is returned when it is called on an empty square --- src/board.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/board.rs b/src/board.rs index 6efbc9a2..e9fd9002 100644 --- a/src/board.rs +++ b/src/board.rs @@ -725,7 +725,7 @@ impl Board { } } - /// Get the piece on a particular `Square`, defaults to Piece::King after a full search of if/else tree. + /// Get the piece on a particular `Square`, it is undefined behaviour to call this function on an empty square. /// /// ``` /// use chess::{Board, Piece, Square}; @@ -733,8 +733,8 @@ impl Board { /// let board = Board::default(); /// /// assert_eq!(unsafe { board.piece_on_unchecked(Square::A1) }, Piece::Rook); - /// // Would be None if you called Board::piece_on(...), but will default to Piece::King instead. - /// assert_eq!(unsafe { board.piece_on_unchecked(Square::D4) }, Piece::King); + /// // The following is undefined behaviour + /// unsafe { board.piece_on_unchecked(Square::A4) }; /// ``` #[inline] pub unsafe fn piece_on_unchecked(&self, square: Square) -> Piece { From f2258d3d7b38c4532e4810889a6dd4f0becbd90a Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 11:34:48 -0700 Subject: [PATCH 71/94] significantly improve the efficiency of `get_pawn_hash` --- src/board.rs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/board.rs b/src/board.rs index e9fd9002..01ec8223 100644 --- a/src/board.rs +++ b/src/board.rs @@ -71,11 +71,11 @@ impl Hash for Board { /// The starting position of a chess board. /// This `static` is of type `LazyLock` so that it only has to be computed once. -/// +/// /// ``` /// use chess::{Board, STARTPOS}; /// use std::str::FromStr; -/// +/// /// assert_eq!(*STARTPOS, Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); /// ``` pub static STARTPOS: LazyLock = LazyLock::new(|| { @@ -338,7 +338,7 @@ impl Board { /// ``` /// use chess::{Board, CastleRights}; /// use std::str::FromStr; - /// + /// /// let board = Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Kq - 0 1").unwrap(); /// /// assert_eq!(board.my_castle_rights(), CastleRights::KingSide); @@ -387,7 +387,7 @@ impl Board { /// ``` /// use chess::{Board, CastleRights}; /// use std::str::FromStr; - /// + /// /// let board = Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Kq - 0 1").unwrap(); /// /// assert_eq!(board.their_castle_rights(), CastleRights::QueenSide); @@ -696,13 +696,16 @@ impl Board { /// Get a pawn hash of the board (a hash that only changes on color change and pawn moves). #[inline] pub fn get_pawn_hash(&self) -> u64 { - self.pieces(Piece::Pawn) - .into_iter() - .map(|square| (square, self.color_on(square).unwrap())) - .fold( - Zobrist::color(self.side_to_move), - |acc, (pawn_square, color)| acc ^ Zobrist::piece(Piece::Pawn, pawn_square, color), - ) + let white_pawns = self.pieces(Piece::Pawn) & self.color_combined(Color::White); + let black_pawns = self.pieces(Piece::Pawn) & self.color_combined(Color::Black); + + Zobrist::color(self.side_to_move) + ^ white_pawns.into_iter().fold(0, |acc, square| { + acc ^ Zobrist::piece(Piece::Pawn, square, Color::White) + }) + ^ black_pawns.into_iter().fold(0, |acc, square| { + acc ^ Zobrist::piece(Piece::Pawn, square, Color::White) + }) } /// What piece is on a particular `Square`? Is there even one? @@ -884,16 +887,16 @@ impl Board { } /// This function exists to mimick the functionality of `make_move`, internally it uses `make_moves_new` - /// + /// /// ``` /// use chess::{Board, ChessMove, Square, BoardStatus}; - /// + /// /// let moves = [ChessMove::new(Square::E2, Square::E4, None), /// ChessMove::new(Square::F7, Square::F6, None), /// ChessMove::new(Square::D2, Square::D4, None), /// ChessMove::new(Square::G7, Square::G5, None), /// ChessMove::new(Square::D1, Square::H5, None)]; - /// + /// /// let board = Board::default(); /// let mut result = Board::new(); /// board.make_moves(moves, &mut result); @@ -905,16 +908,16 @@ impl Board { } /// Apply a series of moves to a board. - /// + /// /// ``` /// use chess::{Board, ChessMove, Square, BoardStatus}; - /// + /// /// let moves = [ChessMove::new(Square::E2, Square::E4, None), /// ChessMove::new(Square::F7, Square::F6, None), /// ChessMove::new(Square::D2, Square::D4, None), /// ChessMove::new(Square::G7, Square::G5, None), /// ChessMove::new(Square::D1, Square::H5, None)]; - /// + /// /// let board = Board::default(); /// let board2 = board.make_moves_new(moves); /// assert_eq!(board2.status(), BoardStatus::Checkmate); From b8dc5964bbfb3459894989fefbd4ddba385371db Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 11:51:13 -0700 Subject: [PATCH 72/94] Remove serde as default feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4428f3a9..7d8bd22e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,6 @@ rand = { version = "0.7.2", default-features = false, features = ["small_rng"] } serde = { version = "1.0.219", default-features = false, optional = true, features = ["derive"] } [features] -default = ["std", "serde"] +default = ["std"] std = ["arrayvec/std", "serde/std"] serde = ["dep:serde", "arrayvec/serde"] \ No newline at end of file From 401ff025b465061d4027b5c17d6d92de59635009 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 11:51:59 -0700 Subject: [PATCH 73/94] toml revert pt 2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7d8bd22e..63dfafa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "chess" version = "4.0.0" edition = "2021" -authors = ["Jordan Bray ", "Orkking2"] +authors = ["Jordan Bray "] description = "This is a fast chess move generator. It has a very good set of documentation, so you should take advantage of that. It (now) generates all lookup tables with a build.rs file, which means that very little pseudo-legal move generation requires branching. There are some convenience functions that are exposed to, for example, find all the squares between two squares. This uses a copy-on-make style structure, and the Board structure is as slimmed down as possible to reduce the cost of copying the board. There are places to improve perft-test performance further, but I instead opt to be more feature-complete to make it useful in real applications. For example, I generate both a hash of the board and a pawn-hash of the board for use in evaluation lookup tables (using Zobrist hashing). There are two ways to generate moves, one is faster, the other has more features that will be useful if making a chess engine. See the documentation for more details." build = "src/build.rs" From 927e48a979366a3e86c6bd91ecdc195e23836a90 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 12:41:24 -0700 Subject: [PATCH 74/94] Make use of `(my|their)_castle_rights` --- src/board.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/board.rs b/src/board.rs index 01ec8223..246f7c38 100644 --- a/src/board.rs +++ b/src/board.rs @@ -683,11 +683,11 @@ impl Board { 0 } ^ Zobrist::castles( - self.castle_rights[self.side_to_move.into_index()], + self.my_castle_rights(), self.side_to_move, ) ^ Zobrist::castles( - self.castle_rights[(!self.side_to_move).into_index()], + self.their_castle_rights(), !self.side_to_move, ) ^ Zobrist::color(self.side_to_move) From 081dd667d08f4098359f55e50ed9e835c15037ac Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 12:54:02 -0700 Subject: [PATCH 75/94] Add `get_pawn_king_hash` which depends on `get_pawn_hash` and king positioning --- src/board.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/board.rs b/src/board.rs index 246f7c38..5d042eed 100644 --- a/src/board.rs +++ b/src/board.rs @@ -708,6 +708,14 @@ impl Board { }) } + /// Get a hash that depends only on king and pawn placement and color change. + #[inline] + pub fn get_pawn_king_hash(&self) -> u64 { + self.get_pawn_hash() + ^ Zobrist::piece(Piece::King, self.king_square(Color::White), Color::White) + ^ Zobrist::piece(Piece::King, self.king_square(Color::Black), Color::Black) + } + /// What piece is on a particular `Square`? Is there even one? /// /// ``` From d7e5c3641fd8542c56ff41ac1ea86951cfe5e07b Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 13:10:32 -0700 Subject: [PATCH 76/94] refactor `Board::pieces(piece) & Board::color_combined(color)` into `Board::pieces_with_color(piece, color)` --- src/board.rs | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/board.rs b/src/board.rs index 5d042eed..356be586 100644 --- a/src/board.rs +++ b/src/board.rs @@ -224,6 +224,23 @@ impl Board { unsafe { self.color_combined.get_unchecked(color.into_index()) } } + /// Get the set of pieces of a particular color. + /// + /// ``` + /// use chess::{Board, BitBoard, Piece, Color, Square}; + /// + /// let white_rooks = BitBoard::from_square(Square::A1) | + /// BitBoard::from_square(Square::H1); + /// + /// let board = Board::default(); + /// + /// assert_eq!(board.piece_with_color(Piece::Rook, Color::White), white_rooks); + /// ``` + #[inline] + pub fn pieces_with_color(&self, piece: Piece, color: Color) -> BitBoard { + self.pieces(piece) & self.color_combined(color) + } + /// Give me the `Square` the `color` king is on. /// /// ``` @@ -236,7 +253,7 @@ impl Board { /// ``` #[inline] pub fn king_square(&self, color: Color) -> Square { - (self.pieces(Piece::King) & self.color_combined(color)).to_square() + (self.pieces_with_color(Piece::King, color)).to_square() } /// Grab the "pieces" `BitBoard`. This is a `BitBoard` with every piece of a particular type. @@ -608,12 +625,12 @@ impl Board { } // make sure there is exactly one white king - if (self.pieces(Piece::King) & self.color_combined(Color::White)).popcnt() != 1 { + if self.pieces_with_color(Piece::King, Color::White).popcnt() != 1 { return false; } // make sure there is exactly one black king - if (self.pieces(Piece::King) & self.color_combined(Color::Black)).popcnt() != 1 { + if self.pieces_with_color(Piece::King, Color::Black).popcnt() != 1 { return false; } @@ -657,7 +674,7 @@ impl Board { // if we have castle rights, make sure we have a king on the (E, {1,8}) square, // depending on the color if castle_rights != CastleRights::NoRights - && self.pieces(Piece::King) & self.color_combined(*color) + && self.pieces_with_color(Piece::King, *color) != get_file(File::E) & get_rank(color.to_my_backrank()) { return false; @@ -682,23 +699,17 @@ impl Board { } else { 0 } - ^ Zobrist::castles( - self.my_castle_rights(), - self.side_to_move, - ) - ^ Zobrist::castles( - self.their_castle_rights(), - !self.side_to_move, - ) + ^ Zobrist::castles(self.my_castle_rights(), self.side_to_move) + ^ Zobrist::castles(self.their_castle_rights(), !self.side_to_move) ^ Zobrist::color(self.side_to_move) } /// Get a pawn hash of the board (a hash that only changes on color change and pawn moves). #[inline] pub fn get_pawn_hash(&self) -> u64 { - let white_pawns = self.pieces(Piece::Pawn) & self.color_combined(Color::White); - let black_pawns = self.pieces(Piece::Pawn) & self.color_combined(Color::Black); - + let white_pawns = self.pieces_with_color(Piece::Pawn, Color::White); + let black_pawns = self.pieces_with_color(Piece::Pawn, Color::Black); + Zobrist::color(self.side_to_move) ^ white_pawns.into_iter().fold(0, |acc, square| { acc ^ Zobrist::piece(Piece::Pawn, square, Color::White) @@ -985,7 +996,7 @@ impl Board { source, )); - let opp_king = result.pieces(Piece::King) & result.color_combined(!result.side_to_move); + let opp_king = result.pieces_with_color(Piece::King, !result.side_to_move); let castles = moved == Piece::King && (move_bb & get_castle_moves()) == move_bb; From 809c0a016efa3985c9133f2221f0715b4fb1fd7e Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 13:12:58 -0700 Subject: [PATCH 77/94] type --- src/board.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index 356be586..a092683b 100644 --- a/src/board.rs +++ b/src/board.rs @@ -234,7 +234,7 @@ impl Board { /// /// let board = Board::default(); /// - /// assert_eq!(board.piece_with_color(Piece::Rook, Color::White), white_rooks); + /// assert_eq!(board.pieces_with_color(Piece::Rook, Color::White), white_rooks); /// ``` #[inline] pub fn pieces_with_color(&self, piece: Piece, color: Color) -> BitBoard { From d1adddc6748a4856871969be3568b305488d132e Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 13:49:04 -0700 Subject: [PATCH 78/94] remove redundant parenthesis --- src/board.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/board.rs b/src/board.rs index a092683b..11b55dd0 100644 --- a/src/board.rs +++ b/src/board.rs @@ -253,7 +253,7 @@ impl Board { /// ``` #[inline] pub fn king_square(&self, color: Color) -> Square { - (self.pieces_with_color(Piece::King, color)).to_square() + self.pieces_with_color(Piece::King, color).to_square() } /// Grab the "pieces" `BitBoard`. This is a `BitBoard` with every piece of a particular type. From 26d3d159dced41f5325fc80c14bf56fcdf7e4c22 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 14:02:48 -0700 Subject: [PATCH 79/94] change `match` to `if let Some` --- src/board.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/board.rs b/src/board.rs index 11b55dd0..77a461ff 100644 --- a/src/board.rs +++ b/src/board.rs @@ -635,16 +635,13 @@ impl Board { } // make sure the en_passant square has a pawn on it of the right color - match self.en_passant { - None => {} - Some(x) => { - if self.pieces(Piece::Pawn) - & self.color_combined(!self.side_to_move) - & BitBoard::from_square(x) - == EMPTY - { - return false; - } + if let Some(x) = self.en_passant { + if self.pieces(Piece::Pawn) + & self.color_combined(!self.side_to_move) + & BitBoard::from_square(x) + == EMPTY + { + return false; } } From 9deb3dcb9a02c6bb28cf6d4a4d8f2d252b4b595b Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 14:44:28 -0700 Subject: [PATCH 80/94] rewrite doctests --- src/board.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/board.rs b/src/board.rs index 77a461ff..de788185 100644 --- a/src/board.rs +++ b/src/board.rs @@ -47,15 +47,13 @@ pub enum BoardStatus { /// Construct the initial position. impl Default for Board { - /// ```text - /// ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ - /// ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ - /// . . . . . . . . - /// . . . . . . . . - /// . . . . . . . . - /// . . . . . . . . - /// ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ - /// ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ + /// A board set up with the initial position of all chess games. + /// + /// ``` + /// use chess::Board; + /// use std::str::FromStr; + /// + /// assert_eq!(Board::default(), Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); /// ``` #[inline] fn default() -> Board { @@ -85,7 +83,12 @@ pub static STARTPOS: LazyLock = LazyLock::new(|| { impl Board { /// Construct a new `Board` that is completely empty. - /// Note: This does NOT give you the initial position. Just a blank slate. + /// + /// Note: This does **NOT** give you the initial position. Just a blank slate. + /// To obtain the initial position used in normal chess games, call `Board::default()`. + /// + /// `Board::new()` is cheaper than the first call of `Board::default()` or first dereference of `STARTPOS` but is otherwise exactly as expensive, + /// as it is a simple `Copy` of a `Board`. #[inline] pub const fn new() -> Board { Board { From e6c83dd361e07b8eeff70a89031a308f290486e2 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 14:52:27 -0700 Subject: [PATCH 81/94] write -> writeln --- src/gen_tables/bmis.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/gen_tables/bmis.rs b/src/gen_tables/bmis.rs index 4fa57d9a..e37a6f6d 100644 --- a/src/gen_tables/bmis.rs +++ b/src/gen_tables/bmis.rs @@ -74,43 +74,43 @@ pub fn gen_all_bmis() { } pub fn write_bmis(f: &mut File) { - write!(f, "#[derive(Copy, Clone)]\n").unwrap(); - write!(f, "struct BmiMagic {{\n").unwrap(); - write!(f, " blockers_mask: BitBoard,\n").unwrap(); - write!(f, " offset: u32,\n").unwrap(); - write!(f, "}}\n\n").unwrap(); + writeln!(f, "#[derive(Copy, Clone)]").unwrap(); + writeln!(f, "struct BmiMagic {{").unwrap(); + writeln!(f, " blockers_mask: BitBoard,").unwrap(); + writeln!(f, " offset: u32,").unwrap(); + writeln!(f, "}}\n").unwrap(); - write!(f, "const ROOK_BMI_MASK: [BmiMagic; 64] = [\n").unwrap(); + writeln!(f, "const ROOK_BMI_MASK: [BmiMagic; 64] = [").unwrap(); for i in 0..NUM_SQUARES { let bmi = unsafe { ROOK_BMI_MASK[i] }; - write!( + writeln!( f, - " BmiMagic {{ blockers_mask: BitBoard({}),\n", + " BmiMagic {{ blockers_mask: BitBoard({}),", bmi.blockers_mask.0 ) .unwrap(); - write!(f, " offset: {} }},\n", bmi.offset).unwrap(); + writeln!(f, " offset: {} }},", bmi.offset).unwrap(); } - write!(f, "];\n").unwrap(); + writeln!(f, "];").unwrap(); - write!(f, "const BISHOP_BMI_MASK: [BmiMagic; 64] = [\n").unwrap(); + writeln!(f, "const BISHOP_BMI_MASK: [BmiMagic; 64] = [").unwrap(); for i in 0..NUM_SQUARES { let bmi = unsafe { BISHOP_BMI_MASK[i] }; - write!( + writeln!( f, - " BmiMagic {{ blockers_mask: BitBoard({}),\n", + " BmiMagic {{ blockers_mask: BitBoard({}),", bmi.blockers_mask.0 ) .unwrap(); - write!(f, " offset: {} }},\n", bmi.offset).unwrap(); + writeln!(f, " offset: {} }},", bmi.offset).unwrap(); } - write!(f, "];\n").unwrap(); + writeln!(f, "];").unwrap(); let moves = unsafe { GENERATED_BMI_MOVES }; - write!(f, "const BMI_MOVES: [u16; {}] = [\n", moves).unwrap(); + writeln!(f, "const BMI_MOVES: [u16; {}] = [", moves).unwrap(); for i in 0..moves { - write!(f, " {},\n", unsafe { BMI_MOVES[i] }).unwrap(); + writeln!(f, " {},", unsafe { BMI_MOVES[i] }).unwrap(); } - write!(f, "];\n\n").unwrap(); + writeln!(f, "];\n").unwrap(); } From 0c321c83c18a1ec1d0e869d711cf4bd5031d3acc Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 15:02:09 -0700 Subject: [PATCH 82/94] remove dead `#[allow(dead_code)]` --- src/gen_tables/generate_all_tables.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gen_tables/generate_all_tables.rs b/src/gen_tables/generate_all_tables.rs index 64edd54a..6af727d6 100644 --- a/src/gen_tables/generate_all_tables.rs +++ b/src/gen_tables/generate_all_tables.rs @@ -1,10 +1,8 @@ // This file generates 3 giant files, magic_gen.rs and zobrist_gen.rs // The purpose of this file is to create lookup tables that can be used during move generation. // This file has gotten pretty long and complicated, but hopefully the comments should allow - -#![allow(dead_code)] - // it to be easily followed. + use std::env; use std::fs::File; use std::path::Path; From 17af896526bc486672b2c040b7ac03302502272e Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 16 Mar 2025 16:32:38 -0700 Subject: [PATCH 83/94] Add `#[allow(deprecated)]` to deprecated functions' doctests --- src/board.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/board.rs b/src/board.rs index de788185..305b9afd 100644 --- a/src/board.rs +++ b/src/board.rs @@ -325,6 +325,7 @@ impl Board { /// let mut board = Board::default(); /// assert_eq!(board.castle_rights(Color::White), CastleRights::Both); /// + /// #[allow(deprecated)] /// board.remove_castle_rights(Color::White, CastleRights::KingSide); /// assert_eq!(board.castle_rights(Color::White), CastleRights::QueenSide); /// ``` @@ -388,6 +389,7 @@ impl Board { /// let mut board = Board::default(); /// assert_eq!(board.my_castle_rights(), CastleRights::Both); /// + /// #[allow(deprecated)] /// board.remove_my_castle_rights(CastleRights::KingSide); /// assert_eq!(board.my_castle_rights(), CastleRights::QueenSide); /// ``` @@ -437,6 +439,7 @@ impl Board { /// let mut board = Board::default(); /// assert_eq!(board.their_castle_rights(), CastleRights::Both); /// + /// #[allow(deprecated)] /// board.remove_their_castle_rights(CastleRights::KingSide); /// assert_eq!(board.their_castle_rights(), CastleRights::QueenSide); /// ``` @@ -468,6 +471,7 @@ impl Board { /// /// let board = Board::default(); /// + /// #[allow(deprecated)] /// let new_board = board.set_piece(Piece::Queen, Color::White, Square::E4) /// .expect("Valid Position"); /// @@ -517,6 +521,7 @@ impl Board { /// /// let board = Board::default(); /// + /// #[allow(deprecated)] /// let new_board = board.clear_square(Square::A1) /// .expect("Valid Position"); /// From 2903c1e04aee258fd24861571559e2ee861cf720 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Tue, 18 Mar 2025 14:06:49 -0700 Subject: [PATCH 84/94] Revert lib.rs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8e63b31c..bbe5d87b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://orkking2.github.io/chess/")] +#![doc(html_root_url = "https://jordanbray.github.io/chess/")] #![cfg_attr(not(feature="std"), no_std)] //! # Rust Chess Library //! This is a chess move generation library for rust. It is designed to be fast, so that it can be From 704b487a9372720b6e9ea7f69749c98f8fb70787 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 19 Mar 2025 20:08:54 -0700 Subject: [PATCH 85/94] `#[inline(always)]` every `#[inline]` function under 5 lines --- src/bitboard.rs | 68 ++++++++++++++++++++++++------------------------- src/board.rs | 53 ++++++++++++++++++++------------------ src/color.rs | 16 ++++++------ src/file.rs | 6 ++--- src/magic.rs | 24 ++++++++--------- src/piece.rs | 7 +++-- src/rank.rs | 8 +++--- src/square.rs | 39 ++++++++++++++-------------- src/zobrist.rs | 8 +++--- 9 files changed, 118 insertions(+), 111 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index bf6930a2..f7ced5b3 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -42,7 +42,7 @@ pub const EMPTY: BitBoard = BitBoard(0); impl BitAnd for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitand(self, other: BitBoard) -> BitBoard { BitBoard(self.0 & other.0) } @@ -51,7 +51,7 @@ impl BitAnd for BitBoard { impl BitAnd for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitand(self, other: &BitBoard) -> BitBoard { BitBoard(self.0 & other.0) } @@ -60,7 +60,7 @@ impl BitAnd for &BitBoard { impl BitAnd<&BitBoard> for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitand(self, other: &BitBoard) -> BitBoard { BitBoard(self.0 & other.0) } @@ -69,7 +69,7 @@ impl BitAnd<&BitBoard> for BitBoard { impl BitAnd for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitand(self, other: BitBoard) -> BitBoard { BitBoard(self.0 & other.0) } @@ -79,7 +79,7 @@ impl BitAnd for &BitBoard { impl BitOr for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitor(self, other: BitBoard) -> BitBoard { BitBoard(self.0 | other.0) } @@ -88,7 +88,7 @@ impl BitOr for BitBoard { impl BitOr for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitor(self, other: &BitBoard) -> BitBoard { BitBoard(self.0 | other.0) } @@ -97,7 +97,7 @@ impl BitOr for &BitBoard { impl BitOr<&BitBoard> for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitor(self, other: &BitBoard) -> BitBoard { BitBoard(self.0 | other.0) } @@ -106,7 +106,7 @@ impl BitOr<&BitBoard> for BitBoard { impl BitOr for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitor(self, other: BitBoard) -> BitBoard { BitBoard(self.0 | other.0) } @@ -117,7 +117,7 @@ impl BitOr for &BitBoard { impl BitXor for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitxor(self, other: BitBoard) -> BitBoard { BitBoard(self.0 ^ other.0) } @@ -126,7 +126,7 @@ impl BitXor for BitBoard { impl BitXor for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitxor(self, other: &BitBoard) -> BitBoard { BitBoard(self.0 ^ other.0) } @@ -135,7 +135,7 @@ impl BitXor for &BitBoard { impl BitXor<&BitBoard> for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitxor(self, other: &BitBoard) -> BitBoard { BitBoard(self.0 ^ other.0) } @@ -144,7 +144,7 @@ impl BitXor<&BitBoard> for BitBoard { impl BitXor for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn bitxor(self, other: BitBoard) -> BitBoard { BitBoard(self.0 ^ other.0) } @@ -153,14 +153,14 @@ impl BitXor for &BitBoard { // Impl BitAndAssign impl BitAndAssign for BitBoard { - #[inline] + #[inline(always)] fn bitand_assign(&mut self, other: BitBoard) { self.0 &= other.0; } } impl BitAndAssign<&BitBoard> for BitBoard { - #[inline] + #[inline(always)] fn bitand_assign(&mut self, other: &BitBoard) { self.0 &= other.0; } @@ -168,14 +168,14 @@ impl BitAndAssign<&BitBoard> for BitBoard { // Impl BitOrAssign impl BitOrAssign for BitBoard { - #[inline] + #[inline(always)] fn bitor_assign(&mut self, other: BitBoard) { self.0 |= other.0; } } impl BitOrAssign<&BitBoard> for BitBoard { - #[inline] + #[inline(always)] fn bitor_assign(&mut self, other: &BitBoard) { self.0 |= other.0; } @@ -183,14 +183,14 @@ impl BitOrAssign<&BitBoard> for BitBoard { // Impl BitXor Assign impl BitXorAssign for BitBoard { - #[inline] + #[inline(always)] fn bitxor_assign(&mut self, other: BitBoard) { self.0 ^= other.0; } } impl BitXorAssign<&BitBoard> for BitBoard { - #[inline] + #[inline(always)] fn bitxor_assign(&mut self, other: &BitBoard) { self.0 ^= other.0; } @@ -200,7 +200,7 @@ impl BitXorAssign<&BitBoard> for BitBoard { impl Mul for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn mul(self, other: BitBoard) -> BitBoard { BitBoard(self.0.wrapping_mul(other.0)) } @@ -209,7 +209,7 @@ impl Mul for BitBoard { impl Mul for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn mul(self, other: &BitBoard) -> BitBoard { BitBoard(self.0.wrapping_mul(other.0)) } @@ -218,7 +218,7 @@ impl Mul for &BitBoard { impl Mul<&BitBoard> for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn mul(self, other: &BitBoard) -> BitBoard { BitBoard(self.0.wrapping_mul(other.0)) } @@ -227,7 +227,7 @@ impl Mul<&BitBoard> for BitBoard { impl Mul for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn mul(self, other: BitBoard) -> BitBoard { BitBoard(self.0.wrapping_mul(other.0)) } @@ -237,7 +237,7 @@ impl Mul for &BitBoard { impl Not for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn not(self) -> BitBoard { BitBoard(!self.0) } @@ -246,14 +246,14 @@ impl Not for BitBoard { impl Not for &BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn not(self) -> BitBoard { BitBoard(!self.0) } } impl fmt::Display for BitBoard { - #[inline] + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for x in 0..64 { if self.0 & (1u64 << x) == (1u64 << x) { @@ -271,25 +271,25 @@ impl fmt::Display for BitBoard { impl BitBoard { /// Construct a new bitboard from a u64 - #[inline] + #[inline(always)] pub const fn new(b: u64) -> BitBoard { BitBoard(b) } /// Construct a new `BitBoard` with a particular `Square` set - #[inline] + #[inline(always)] pub const fn set(rank: Rank, file: File) -> BitBoard { BitBoard::from_square(Square::make_square(rank, file)) } /// Construct a new `BitBoard` with a particular `Square` set - #[inline] + #[inline(always)] pub const fn from_square(sq: Square) -> BitBoard { BitBoard(1u64 << sq.to_int()) } /// Convert an `Option` to an `Option` - #[inline] + #[inline(always)] #[deprecated( since = "4.0.0", note = "Unnecessary shorthand for `square_option.map(BitBoard::from_square)`.", @@ -299,25 +299,25 @@ impl BitBoard { } /// Convert a `BitBoard` to a `Square`. This grabs the least-significant `Square` - #[inline] + #[inline(always)] pub const fn to_square(&self) -> Square { Square::new(self.0.trailing_zeros() as u8) } /// Count the number of `Squares` set in this `BitBoard` - #[inline] + #[inline(always)] pub const fn popcnt(&self) -> u32 { self.0.count_ones() } /// Reverse this `BitBoard`. Look at it from the opponents perspective. - #[inline] + #[inline(always)] pub const fn reverse_colors(&self) -> BitBoard { BitBoard(self.0.swap_bytes()) } /// Convert this `BitBoard` to a `usize` (for table lookups) - #[inline] + #[inline(always)] pub const fn to_size(&self, rightshift: u8) -> usize { (self.0 >> rightshift) as usize } @@ -327,7 +327,7 @@ impl BitBoard { impl Iterator for BitBoard { type Item = Square; - #[inline] + #[inline(always)] fn next(&mut self) -> Option { if self.0 == 0 { None diff --git a/src/board.rs b/src/board.rs index 305b9afd..19d2e081 100644 --- a/src/board.rs +++ b/src/board.rs @@ -55,7 +55,7 @@ impl Default for Board { /// /// assert_eq!(Board::default(), Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); /// ``` - #[inline] + #[inline(always)] fn default() -> Board { *STARTPOS } @@ -89,7 +89,7 @@ impl Board { /// /// `Board::new()` is cheaper than the first call of `Board::default()` or first dereference of `STARTPOS` but is otherwise exactly as expensive, /// as it is a simple `Copy` of a `Board`. - #[inline] + #[inline(always)] pub const fn new() -> Board { Board { pieces: [EMPTY; NUM_PIECES], @@ -127,7 +127,7 @@ impl Board { since = "3.1.0", note = "Internally this is a wrapper for `Board::from_str`, please use this function directly instead" )] - #[inline] + #[inline(always)] #[cfg(feature = "std")] pub fn from_fen(fen: String) -> Option { Board::from_str(&fen).ok() @@ -137,7 +137,7 @@ impl Board { since = "3.0.0", note = "Internally this wraps `MoveGen::new_legal`, please use this structure instead" )] - #[inline] + #[inline(always)] pub fn enumerate_moves(&self, moves: &mut [ChessMove; 256]) -> usize { let movegen = MoveGen::new_legal(self); let mut size = 0; @@ -173,7 +173,7 @@ impl Board { /// board = board.make_move_new(ChessMove::new(Square::D1, Square::H5, None)); /// assert_eq!(board.status(), BoardStatus::Checkmate); /// ``` - #[inline] + #[inline(always)] pub fn status(&self) -> BoardStatus { if !MoveGen::has_legals(self) { if self.checkers == EMPTY { @@ -200,7 +200,7 @@ impl Board { /// /// assert_eq!(*board.combined(), combined_should_be); /// ``` - #[inline] + #[inline(always)] pub const fn combined(&self) -> &BitBoard { &self.combined } @@ -222,7 +222,7 @@ impl Board { /// assert_eq!(*board.color_combined(Color::White), white_pieces); /// assert_eq!(*board.color_combined(Color::Black), black_pieces); /// ``` - #[inline] + #[inline(always)] pub fn color_combined(&self, color: Color) -> &BitBoard { unsafe { self.color_combined.get_unchecked(color.into_index()) } } @@ -239,7 +239,7 @@ impl Board { /// /// assert_eq!(board.pieces_with_color(Piece::Rook, Color::White), white_rooks); /// ``` - #[inline] + #[inline(always)] pub fn pieces_with_color(&self, piece: Piece, color: Color) -> BitBoard { self.pieces(piece) & self.color_combined(color) } @@ -254,7 +254,7 @@ impl Board { /// assert_eq!(board.king_square(Color::White), Square::E1); /// assert_eq!(board.king_square(Color::Black), Square::E8); /// ``` - #[inline] + #[inline(always)] pub fn king_square(&self, color: Color) -> Square { self.pieces_with_color(Piece::King, color).to_square() } @@ -274,7 +274,7 @@ impl Board { /// /// assert_eq!(*board.pieces(Piece::Rook), rooks); /// ``` - #[inline] + #[inline(always)] pub fn pieces(&self, piece: Piece) -> &BitBoard { unsafe { self.pieces.get_unchecked(piece.into_index()) } } @@ -299,7 +299,7 @@ impl Board { /// assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide); /// assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights); /// ``` - #[inline] + #[inline(always)] pub fn castle_rights(&self, color: Color) -> CastleRights { unsafe { *self.castle_rights.get_unchecked(color.into_index()) } } @@ -349,7 +349,7 @@ impl Board { /// let mut board = Board::default(); /// assert_eq!(board.side_to_move(), Color::White); /// ``` - #[inline] + #[inline(always)] pub const fn side_to_move(&self) -> Color { self.side_to_move } @@ -364,7 +364,7 @@ impl Board { /// /// assert_eq!(board.my_castle_rights(), CastleRights::KingSide); /// ``` - #[inline] + #[inline(always)] pub fn my_castle_rights(&self) -> CastleRights { self.castle_rights(self.side_to_move()) } @@ -414,7 +414,7 @@ impl Board { /// /// assert_eq!(board.their_castle_rights(), CastleRights::QueenSide); /// ``` - #[inline] + #[inline(always)] pub fn their_castle_rights(&self) -> CastleRights { self.castle_rights(!self.side_to_move()) } @@ -455,6 +455,7 @@ impl Board { } /// Add or remove a piece from the bitboards in this struct. + #[inline(always)] fn xor(&mut self, piece: Piece, bb: BitBoard, color: Color) { unsafe { *self.pieces.get_unchecked_mut(piece.into_index()) ^= bb; @@ -579,7 +580,7 @@ impl Board { /// /// assert_eq!(new_board.side_to_move(), Color::Black); /// ``` - #[inline] + #[inline(always)] pub fn null_move(&self) -> Option { if self.checkers != EMPTY { None @@ -725,7 +726,7 @@ impl Board { } /// Get a hash that depends only on king and pawn placement and color change. - #[inline] + #[inline(always)] pub fn get_pawn_king_hash(&self) -> u64 { self.get_pawn_hash() ^ Zobrist::piece(Piece::King, self.king_square(Color::White), Color::White) @@ -742,7 +743,7 @@ impl Board { /// assert_eq!(board.piece_on(Square::A1), Some(Piece::Rook)); /// assert_eq!(board.piece_on(Square::D4), None); /// ``` - #[inline] + #[inline(always)] pub fn piece_on(&self, square: Square) -> Option { let opp = BitBoard::from_square(square); if self.combined() & opp == EMPTY { @@ -815,6 +816,7 @@ impl Board { } /// Unset the en_passant square. + #[inline(always)] fn remove_ep(&mut self) { self.en_passant = None; } @@ -833,7 +835,7 @@ impl Board { /// /// assert_eq!(board.en_passant(), Some(Square::E5)); /// ``` - #[inline] + #[inline(always)] pub const fn en_passant(&self) -> Option { self.en_passant } @@ -852,7 +854,7 @@ impl Board { /// /// assert_eq!(board.en_passant_target(), Some(Square::E6)); /// ``` - #[inline] + #[inline(always)] pub fn en_passant_target(&self) -> Option { let color = !self.side_to_move(); self.en_passant().map(|square| square.ubackward(color)) @@ -860,6 +862,7 @@ impl Board { /// Set the en_passant square. Note: This must only be called when self.en_passant is already /// None. + #[inline] fn set_ep(&mut self, sq: Square) { // Only set self.en_passant if the pawn can actually be captured next move. if get_adjacent_files(sq.get_file()) @@ -886,7 +889,7 @@ impl Board { /// assert_eq!(board.legal(m1), true); /// assert_eq!(board.legal(m2), false); /// ``` - #[inline] + #[inline(always)] pub fn legal(&self, m: ChessMove) -> bool { MoveGen::new_legal(&self).any(|x| x == m) } @@ -903,7 +906,7 @@ impl Board { /// let board = Board::default(); /// assert_eq!(board.make_move_new(m).side_to_move(), Color::Black); /// ``` - #[inline] + #[inline(always)] pub fn make_move_new(&self, m: ChessMove) -> Board { let mut result = Board::new(); self.make_move(m, &mut result); @@ -926,7 +929,7 @@ impl Board { /// board.make_moves(moves, &mut result); /// assert_eq!(result.status(), BoardStatus::Checkmate); /// ``` - #[inline] + #[inline(always)] pub fn make_moves>(&self, moves: T, result: &mut Board) { *result = self.make_moves_new(moves); } @@ -946,7 +949,7 @@ impl Board { /// let board2 = board.make_moves_new(moves); /// assert_eq!(board2.status(), BoardStatus::Checkmate); /// ``` - #[inline] + #[inline(always)] pub fn make_moves_new>(&self, moves: T) -> Board { moves .into_iter() @@ -1116,13 +1119,13 @@ impl Board { } /// Give me the `BitBoard` of my pinned pieces. - #[inline] + #[inline(always)] pub fn pinned(&self) -> &BitBoard { &self.pinned } /// Give me the `Bitboard` of the pieces putting me in check. - #[inline] + #[inline(always)] pub fn checkers(&self) -> &BitBoard { &self.checkers } diff --git a/src/color.rs b/src/color.rs index 1b800b82..684cef03 100644 --- a/src/color.rs +++ b/src/color.rs @@ -17,14 +17,14 @@ pub const ALL_COLORS: [Color; NUM_COLORS] = [Color::White, Color::Black]; impl Color { /// Convert the `Color` to a `usize` for table lookups. - #[inline] + #[inline(always)] pub const fn into_index(self) -> usize { self as usize } /// Convert a `Color` to my backrank, which represents the starting rank /// for my pieces. - #[inline] + #[inline(always)] pub const fn to_my_backrank(&self) -> Rank { match *self { Color::White => Rank::First, @@ -34,7 +34,7 @@ impl Color { /// Convert a `Color` to my opponents backrank, which represents the starting rank for the /// opponents pieces. - #[inline] + #[inline(always)] pub const fn to_their_backrank(&self) -> Rank { match *self { Color::White => Rank::Eighth, @@ -43,7 +43,7 @@ impl Color { } /// Convert a `Color` to my second rank, which represents the starting rank for my pawns. - #[inline] + #[inline(always)] pub const fn to_second_rank(&self) -> Rank { match *self { Color::White => Rank::Second, @@ -53,7 +53,7 @@ impl Color { /// Convert a `Color` to my fourth rank, which represents the rank of my pawns when /// moving two squares forward. - #[inline] + #[inline(always)] pub const fn to_fourth_rank(&self) -> Rank { match *self { Color::White => Rank::Fourth, @@ -62,7 +62,7 @@ impl Color { } /// Convert a `Color` to my seventh rank, which represents the rank before pawn promotion. - #[inline] + #[inline(always)] pub const fn to_seventh_rank(&self) -> Rank { match *self { Color::White => Rank::Seventh, @@ -75,7 +75,7 @@ impl Not for Color { type Output = Color; /// Get the other color. - #[inline] + #[inline(always)] fn not(self) -> Color { if self == Color::White { Color::Black @@ -88,7 +88,7 @@ impl Not for Color { impl From for bool { /// While in the backend, `Color::White == 0` and `Color::Black == 1`, /// it is more intuitive for `Color::White` to evaluate `true`, as it goes first - #[inline] + #[inline(always)] fn from(value: Color) -> Self { match value { Color::White => true, diff --git a/src/file.rs b/src/file.rs index faf4b9bb..670eb1f7 100644 --- a/src/file.rs +++ b/src/file.rs @@ -50,19 +50,19 @@ impl File { } /// Go one file to the left. If impossible, wrap around. - #[inline] + #[inline(always)] pub const fn left(&self) -> File { File::from_index(self.into_index().wrapping_sub(1)) } /// Go one file to the right. If impossible, wrap around. - #[inline] + #[inline(always)] pub const fn right(&self) -> File { File::from_index(self.into_index() + 1) } /// Convert this `File` into a `usize` from 0 to 7 inclusive. - #[inline] + #[inline(always)] pub const fn into_index(self) -> usize { self as usize } diff --git a/src/magic.rs b/src/magic.rs index b49ed58c..895773fd 100644 --- a/src/magic.rs +++ b/src/magic.rs @@ -10,13 +10,13 @@ use std::arch::x86_64::{_pdep_u64, _pext_u64}; include!(concat!(env!("OUT_DIR"), "/magic_gen.rs")); /// Get the rays for a bishop on a particular square. -#[inline] +#[inline(always)] pub fn get_bishop_rays(sq: Square) -> BitBoard { unsafe { *RAYS.get_unchecked(BISHOP).get_unchecked(sq.into_index()) } } /// Get the rays for a rook on a particular square. -#[inline] +#[inline(always)] pub fn get_rook_rays(sq: Square) -> BitBoard { unsafe { *RAYS.get_unchecked(ROOK).get_unchecked(sq.into_index()) } } @@ -82,13 +82,13 @@ pub fn get_bishop_moves_bmi(sq: Square, blockers: BitBoard) -> BitBoard { } /// Get the king moves for a particular square. -#[inline] +#[inline(always)] pub fn get_king_moves(sq: Square) -> BitBoard { unsafe { *KING_MOVES.get_unchecked(sq.into_index()) } } /// Get the knight moves for a particular square. -#[inline] +#[inline(always)] pub fn get_knight_moves(sq: Square) -> BitBoard { unsafe { *KNIGHT_MOVES.get_unchecked(sq.into_index()) } } @@ -105,7 +105,7 @@ pub fn get_pawn_attacks(sq: Square, color: Color, blockers: BitBoard) -> BitBoar } } /// Get the legal destination castle squares for both players -#[inline] +#[inline(always)] pub fn get_castle_moves() -> BitBoard { CASTLE_MOVES } @@ -128,14 +128,14 @@ pub fn get_pawn_quiets(sq: Square, color: Color, blockers: BitBoard) -> BitBoard /// Get all the pawn moves for a particular square, given the pawn's color and the potential /// blocking pieces and victims. -#[inline] +#[inline(always)] pub fn get_pawn_moves(sq: Square, color: Color, blockers: BitBoard) -> BitBoard { get_pawn_attacks(sq, color, blockers) ^ get_pawn_quiets(sq, color, blockers) } /// Get a line (extending to infinity, which in chess is 8 squares), given two squares. /// This line does extend past the squares. -#[inline] +#[inline(always)] pub fn line(sq1: Square, sq2: Square) -> BitBoard { unsafe { *LINE @@ -145,7 +145,7 @@ pub fn line(sq1: Square, sq2: Square) -> BitBoard { } /// Get a line between these two squares, not including the squares themselves. -#[inline] +#[inline(always)] pub fn between(sq1: Square, sq2: Square) -> BitBoard { unsafe { *BETWEEN @@ -155,24 +155,24 @@ pub fn between(sq1: Square, sq2: Square) -> BitBoard { } /// Get a `BitBoard` that represents all the squares on a particular rank. -#[inline] +#[inline(always)] pub fn get_rank(rank: Rank) -> BitBoard { unsafe { *RANKS.get_unchecked(rank.into_index()) } } /// Get a `BitBoard` that represents all the squares on a particular file. -#[inline] +#[inline(always)] pub fn get_file(file: File) -> BitBoard { unsafe { *FILES.get_unchecked(file.into_index()) } } /// Get a `BitBoard` that represents the squares on the 1 or 2 files next to this file. -#[inline] +#[inline(always)] pub fn get_adjacent_files(file: File) -> BitBoard { unsafe { *ADJACENT_FILES.get_unchecked(file.into_index()) } } -#[inline] +#[inline(always)] pub fn get_pawn_source_double_moves() -> BitBoard { PAWN_SOURCE_DOUBLE_MOVES } diff --git a/src/piece.rs b/src/piece.rs index 860a4010..ac429e0f 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -3,7 +3,7 @@ use std::fmt; /// Represent a chess piece as a very simple enum #[repr(u8)] -#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Debug, Hash)] pub enum Piece { Pawn, @@ -35,12 +35,13 @@ pub const PROMOTION_PIECES: [Piece; 4] = [Piece::Queen, Piece::Knight, Piece::Ro impl Piece { /// Convert the `Piece` to a `usize` for table lookups. - #[inline] + #[inline(always)] pub const fn into_index(self) -> usize { self as usize } /// Convert the `Piece` to its FEN `char` + #[inline(always)] pub fn to_char(&self) -> char { match *self { Piece::Pawn => 'p', @@ -52,6 +53,7 @@ impl Piece { } } + #[inline(always)] pub fn with_color(&self, color: Color) -> PieceWithColor { PieceWithColor { piece: *self, @@ -81,6 +83,7 @@ impl Piece { } impl fmt::Display for Piece { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.to_char()) } diff --git a/src/rank.rs b/src/rank.rs index 3bc51312..e083ada9 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -34,7 +34,7 @@ pub const ALL_RANKS: [Rank; NUM_RANKS] = [ impl Rank { /// Convert a `usize` into a `Rank` (the inverse of into_index). If the number is > 7, wrap /// around. - #[inline] + #[inline(always)] pub const fn from_index(i: usize) -> Rank { // match is optimized to no-op with opt-level=1 with rustc 1.53.0 match i & 7 { @@ -51,19 +51,19 @@ impl Rank { } /// Go one rank down. If impossible, wrap around. - #[inline] + #[inline(always)] pub const fn down(&self) -> Rank { Rank::from_index(self.into_index().wrapping_sub(1)) } /// Go one rank up. If impossible, wrap around. - #[inline] + #[inline(always)] pub const fn up(&self) -> Rank { Rank::from_index(self.into_index() + 1) } /// Convert this `Rank` into a `usize` between 0 and 7 (inclusive). - #[inline] + #[inline(always)] pub const fn into_index(self) -> usize { self as usize } diff --git a/src/square.rs b/src/square.rs index 47749e9e..aa908b2a 100644 --- a/src/square.rs +++ b/src/square.rs @@ -6,7 +6,7 @@ use std::fmt; use std::str::FromStr; /// Represent a square on the chess board -#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(PartialEq, Ord, Eq, PartialOrd, Copy, Clone, Debug, Hash)] pub struct Square(u8); @@ -24,6 +24,7 @@ impl Default for Square { /// /// assert_eq!(explicit_sq, implicit_sq); /// ``` + #[inline(always)] fn default() -> Square { Square::new(0) } @@ -42,7 +43,7 @@ impl Square { /// /// assert_eq!(Square::default(), bad_sq); /// ``` - #[inline] + #[inline(always)] pub const fn new(sq: u8) -> Square { Square(sq & 63) } @@ -64,7 +65,7 @@ impl Square { /// assert_eq!(sq, x); /// } /// ``` - #[inline] + #[inline(always)] pub const fn make_square(rank: Rank, file: File) -> Square { Square((rank as u8) << 3 ^ (file as u8)) } @@ -78,7 +79,7 @@ impl Square { /// /// assert_eq!(sq.get_rank(), Rank::Seventh); /// ``` - #[inline] + #[inline(always)] pub const fn get_rank(&self) -> Rank { Rank::from_index((self.0 >> 3) as usize) } @@ -92,7 +93,7 @@ impl Square { /// /// assert_eq!(sq.get_file(), File::D); /// ``` - #[inline] + #[inline(always)] pub const fn get_file(&self) -> File { File::from_index((self.0 & 7) as usize) } @@ -108,7 +109,7 @@ impl Square { /// /// assert_eq!(sq.up().expect("Valid Square").up(), None); /// ``` - #[inline] + #[inline(always)] pub fn up(&self) -> Option { if self.get_rank() == Rank::Eighth { None @@ -128,7 +129,7 @@ impl Square { /// /// assert_eq!(sq.down().expect("Valid Square").down(), None); /// ``` - #[inline] + #[inline(always)] pub fn down(&self) -> Option { if self.get_rank() == Rank::First { None @@ -148,7 +149,7 @@ impl Square { /// /// assert_eq!(sq.left().expect("Valid Square").left(), None); /// ``` - #[inline] + #[inline(always)] pub fn left(&self) -> Option { if self.get_file() == File::A { None @@ -168,7 +169,7 @@ impl Square { /// /// assert_eq!(sq.right().expect("Valid Square").right(), None); /// ``` - #[inline] + #[inline(always)] pub fn right(&self) -> Option { if self.get_file() == File::H { None @@ -192,7 +193,7 @@ impl Square { /// assert_eq!(sq.forward(Color::Black).expect("Valid Square"), Square::make_square(Rank::First, File::D)); /// assert_eq!(sq.forward(Color::Black).expect("Valid Square").forward(Color::Black), None); /// ``` - #[inline] + #[inline(always)] pub fn forward(&self, color: Color) -> Option { match color { Color::White => self.up(), @@ -215,7 +216,7 @@ impl Square { /// assert_eq!(sq.backward(Color::White).expect("Valid Square"), Square::make_square(Rank::First, File::D)); /// assert_eq!(sq.backward(Color::White).expect("Valid Square").backward(Color::White), None); /// ``` - #[inline] + #[inline(always)] pub fn backward(&self, color: Color) -> Option { match color { Color::White => self.down(), @@ -234,7 +235,7 @@ impl Square { /// /// assert_eq!(sq.uup().uup(), Square::make_square(Rank::First, File::D)); /// ``` - #[inline] + #[inline(always)] pub const fn uup(&self) -> Square { Square::make_square(self.get_rank().up(), self.get_file()) } @@ -250,7 +251,7 @@ impl Square { /// /// assert_eq!(sq.udown().udown(), Square::make_square(Rank::Eighth, File::D)); /// ``` - #[inline] + #[inline(always)] pub const fn udown(&self) -> Square { Square::make_square(self.get_rank().down(), self.get_file()) } @@ -266,7 +267,7 @@ impl Square { /// /// assert_eq!(sq.uleft().uleft(), Square::make_square(Rank::Seventh, File::H)); /// ``` - #[inline] + #[inline(always)] pub const fn uleft(&self) -> Square { Square::make_square(self.get_rank(), self.get_file().left()) } @@ -283,7 +284,7 @@ impl Square { /// /// assert_eq!(sq.uright().uright(), Square::make_square(Rank::Seventh, File::A)); /// ``` - #[inline] + #[inline(always)] pub fn uright(&self) -> Square { Square::make_square(self.get_rank(), self.get_file().right()) } @@ -304,7 +305,7 @@ impl Square { /// assert_eq!(sq.uforward(Color::Black), Square::make_square(Rank::First, File::D)); /// assert_eq!(sq.uforward(Color::Black).uforward(Color::Black), Square::make_square(Rank::Eighth, File::D)); /// ``` - #[inline] + #[inline(always)] pub const fn uforward(&self, color: Color) -> Square { match color { Color::White => self.uup(), @@ -328,7 +329,7 @@ impl Square { /// assert_eq!(sq.ubackward(Color::White), Square::make_square(Rank::First, File::D)); /// assert_eq!(sq.ubackward(Color::White).ubackward(Color::White), Square::make_square(Rank::Eighth, File::D)); /// ``` - #[inline] + #[inline(always)] pub const fn ubackward(&self, color: Color) -> Square { match color { Color::White => self.udown(), @@ -346,7 +347,7 @@ impl Square { /// assert_eq!(Square::make_square(Rank::First, File::B).to_int(), 1); /// assert_eq!(Square::make_square(Rank::Eighth, File::H).to_int(), 63); /// ``` - #[inline] + #[inline(always)] pub const fn to_int(&self) -> u8 { self.0 } @@ -361,7 +362,7 @@ impl Square { /// assert_eq!(Square::make_square(Rank::First, File::B).into_index(), 1); /// assert_eq!(Square::make_square(Rank::Eighth, File::H).into_index(), 63); /// ``` - #[inline] + #[inline(always)] pub const fn into_index(self) -> usize { self.0 as usize } diff --git a/src/zobrist.rs b/src/zobrist.rs index 90b5d5c5..6ed2b8a1 100644 --- a/src/zobrist.rs +++ b/src/zobrist.rs @@ -13,7 +13,7 @@ include!(concat!(env!("OUT_DIR"), "/zobrist_gen.rs")); impl Zobrist { /// Get the value for a particular piece - #[inline] + #[inline(always)] pub fn piece(piece: Piece, square: Square, color: Color) -> u64 { unsafe { *ZOBRIST_PIECES @@ -23,7 +23,7 @@ impl Zobrist { } } - #[inline] + #[inline(always)] pub fn castles(castle_rights: CastleRights, color: Color) -> u64 { unsafe { *ZOBRIST_CASTLES @@ -32,7 +32,7 @@ impl Zobrist { } } - #[inline] + #[inline(always)] pub fn en_passant(file: File, color: Color) -> u64 { unsafe { *ZOBRIST_EP @@ -41,7 +41,7 @@ impl Zobrist { } } - #[inline] + #[inline(always)] pub fn color(color: Color) -> u64 { if (!color).into() { SIDE_TO_MOVE From b1aedb2d2486a81f4f91f96f6550372f0038ae8a Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 19 Mar 2025 20:47:38 -0700 Subject: [PATCH 86/94] `#[inline(always)]` legals and has_legals --- src/movegen/piece_type.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index 2dfe339e..88c29be6 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -27,7 +27,7 @@ pub trait PieceType { unoccupied_by_me: BitBoard, ) -> BitBoard; - #[inline] + #[inline(always)] fn legals( movelist: &mut MoveList, board: &Board, @@ -70,7 +70,7 @@ pub trait PieceType { } } - #[inline] + #[inline(always)] fn has_legals(board: &Board, unoccupied_by_me: BitBoard) -> bool { let combined = board.combined(); let color = board.side_to_move(); @@ -117,7 +117,7 @@ pub struct KingType; impl PawnType { /// Is a particular en-passant capture legal? - #[inline] + #[inline(always)] pub fn legal_ep_move(board: &Board, source: Square, dest: Square) -> bool { let combined = board.combined() ^ BitBoard::from_square(board.en_passant().unwrap()) @@ -163,7 +163,7 @@ impl PieceType for PawnType { get_pawn_moves(src, color, combined) & unoccupied_by_me } - #[inline] + #[inline(always)] fn legals( movelist: &mut MoveList, board: &Board, @@ -232,7 +232,7 @@ impl PieceType for PawnType { } } - #[inline] + #[inline(always)] fn has_legals( board: &Board, unoccupied_by_me: BitBoard, @@ -328,7 +328,7 @@ impl PieceType for KnightType { get_knight_moves(src) & unoccupied_by_me } - #[inline] + #[inline(always)] fn legals( movelist: &mut MoveList, board: &Board, @@ -367,7 +367,7 @@ impl PieceType for KnightType { }; } - #[inline] + #[inline(always)] fn has_legals( board: &Board, unoccupied_by_me: BitBoard, @@ -448,7 +448,7 @@ impl PieceType for QueenType { impl KingType { /// Is a particular king move legal? - #[inline] + #[inline(always)] pub fn legal_king_move(board: &Board, dest: Square) -> bool { let combined = board.combined() ^ (board.pieces(Piece::King) & board.color_combined(board.side_to_move())) @@ -504,7 +504,7 @@ impl PieceType for KingType { get_king_moves(src) & unoccupied_by_me } - #[inline] + #[inline(always)] fn legals( movelist: &mut MoveList, board: &Board, @@ -563,7 +563,7 @@ impl PieceType for KingType { } } - #[inline] + #[inline(always)] fn has_legals( board: &Board, unoccupied_by_me: BitBoard, From e62c2fbebb9b4ee4438c3b90e46efd696284ec11 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 19 Mar 2025 20:55:50 -0700 Subject: [PATCH 87/94] Fix no_std bug --- src/board.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/board.rs b/src/board.rs index 19d2e081..c7103d5c 100644 --- a/src/board.rs +++ b/src/board.rs @@ -18,6 +18,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; +#[cfg(feature="std")] use std::sync::LazyLock; /// A representation of a chess board. That's why you're here, right? @@ -56,9 +57,23 @@ impl Default for Board { /// assert_eq!(Board::default(), Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); /// ``` #[inline(always)] + #[cfg(feature="std")] fn default() -> Board { *STARTPOS } + + /// A board set up with the initial position of all chess games. + /// ``` + /// use chess::Board; + /// use std::str::FromStr; + /// + /// assert_eq!(Board::default(), Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); + /// ``` + #[inline(always)] + #[cfg(not(feature="std"))] + fn default() -> Board { + Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap() + } } impl Hash for Board { @@ -76,6 +91,7 @@ impl Hash for Board { /// /// assert_eq!(*STARTPOS, Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); /// ``` +#[cfg(feature="std")] pub static STARTPOS: LazyLock = LazyLock::new(|| { Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") .expect("Startpos FEN is valid FEN") From 6c86d802711d9a6413e7a493ff2cb6e0728e466e Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Wed, 19 Mar 2025 20:58:42 -0700 Subject: [PATCH 88/94] Make `default` consistent with `STARTPOS` and reformat --- src/board.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/board.rs b/src/board.rs index c7103d5c..302bbe8b 100644 --- a/src/board.rs +++ b/src/board.rs @@ -18,7 +18,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; -#[cfg(feature="std")] +#[cfg(feature = "std")] use std::sync::LazyLock; /// A representation of a chess board. That's why you're here, right? @@ -57,7 +57,7 @@ impl Default for Board { /// assert_eq!(Board::default(), Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); /// ``` #[inline(always)] - #[cfg(feature="std")] + #[cfg(feature = "std")] fn default() -> Board { *STARTPOS } @@ -70,9 +70,10 @@ impl Default for Board { /// assert_eq!(Board::default(), Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); /// ``` #[inline(always)] - #[cfg(not(feature="std"))] + #[cfg(not(feature = "std"))] fn default() -> Board { - Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap() + Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + .expect("Startpos FEN is valid FEN") } } @@ -91,7 +92,7 @@ impl Hash for Board { /// /// assert_eq!(*STARTPOS, Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap()); /// ``` -#[cfg(feature="std")] +#[cfg(feature = "std")] pub static STARTPOS: LazyLock = LazyLock::new(|| { Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") .expect("Startpos FEN is valid FEN") @@ -102,7 +103,7 @@ impl Board { /// /// Note: This does **NOT** give you the initial position. Just a blank slate. /// To obtain the initial position used in normal chess games, call `Board::default()`. - /// + /// /// `Board::new()` is cheaper than the first call of `Board::default()` or first dereference of `STARTPOS` but is otherwise exactly as expensive, /// as it is a simple `Copy` of a `Board`. #[inline(always)] From da4b47baee605fa4523b7cf37a4b3fb17d2cfb28 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Thu, 20 Mar 2025 11:12:23 -0700 Subject: [PATCH 89/94] formatting --- src/lib.rs | 12 ++++++------ src/movegen/movegen.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bbe5d87b..bfc0452a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![doc(html_root_url = "https://jordanbray.github.io/chess/")] -#![cfg_attr(not(feature="std"), no_std)] +#![cfg_attr(not(feature = "std"), no_std)] //! # Rust Chess Library //! This is a chess move generation library for rust. It is designed to be fast, so that it can be //! used in a chess engine or UI without performance issues. @@ -19,7 +19,7 @@ //! ``` //! -#[cfg(not(feature="std"))] +#[cfg(not(feature = "std"))] extern crate core as std; mod board; @@ -28,9 +28,9 @@ pub use crate::board::*; mod bitboard; pub use crate::bitboard::{BitBoard, EMPTY}; -#[cfg(feature="std")] +#[cfg(feature = "std")] mod cache_table; -#[cfg(feature="std")] +#[cfg(feature = "std")] pub use crate::cache_table::*; mod castle_rights; @@ -72,9 +72,9 @@ pub use crate::movegen::MoveGen; mod zobrist; -#[cfg(feature="std")] +#[cfg(feature = "std")] mod game; -#[cfg(feature="std")] +#[cfg(feature = "std")] pub use crate::game::{Action, Game, GameResult}; mod board_builder; diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index b98b9861..8ba3eb4e 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -121,7 +121,7 @@ impl MoveGen { } /// Does a particular board have *any* legal moves? - /// + /// /// This function does not evaluate any moves past the first one it finds and so is guaranteed /// to take less than or equal to time as `new_legal`. #[inline(always)] From 1ad08808c18d706f429aac167f17f5dc566f5e2a Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 22 Mar 2025 21:08:37 -0700 Subject: [PATCH 90/94] comment + inline + using rust iterator syntax --- src/movegen/movegen.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index 8ba3eb4e..5973c841 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -123,7 +123,7 @@ impl MoveGen { /// Does a particular board have *any* legal moves? /// /// This function does not evaluate any moves past the first one it finds and so is guaranteed - /// to take less than or equal to time as `new_legal`. + /// to take less than or equal time as `new_legal`. #[inline(always)] pub fn has_legals(board: &Board) -> bool { let checkers = *board.checkers(); @@ -252,16 +252,12 @@ impl MoveGen { /// Fastest perft test with this structure pub fn movegen_perft_test(board: &Board, depth: usize) -> usize { let iterable = MoveGen::new_legal(board); - - let mut result: usize = 0; if depth == 1 { iterable.len() } else { - for m in iterable { - let bresult = board.make_move_new(m); - result += MoveGen::movegen_perft_test(&bresult, depth - 1); - } - result + iterable.fold(0, |acc, m| { + acc + MoveGen::movegen_perft_test(&board.make_move_new(m), depth - 1) + }) } } @@ -330,6 +326,7 @@ impl Iterator for MoveGen { } /// Find the next chess move. + #[inline] fn next(&mut self) -> Option { if self.index >= self.moves.len() || self.moves[self.index].bitboard & self.iterator_mask == EMPTY From ab3fb168297da2636fc2051063026a60e4fcb657 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 22 Mar 2025 21:08:55 -0700 Subject: [PATCH 91/94] inline -> inline(always) --- src/board.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/board.rs b/src/board.rs index 302bbe8b..a3fbb8f1 100644 --- a/src/board.rs +++ b/src/board.rs @@ -414,7 +414,7 @@ impl Board { since = "3.1.0", note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position." )] - #[inline] + #[inline(always)] pub fn remove_my_castle_rights(&mut self, remove: CastleRights) { let color = self.side_to_move(); #[allow(deprecated)] @@ -464,7 +464,7 @@ impl Board { since = "3.1.0", note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position." )] - #[inline] + #[inline(always)] pub fn remove_their_castle_rights(&mut self, remove: CastleRights) { let color = !self.side_to_move(); #[allow(deprecated)] From 74fc83faf5177c3d72742ddcfb1a0e7a11e36dc5 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 22 Mar 2025 21:09:01 -0700 Subject: [PATCH 92/94] ordering --- src/magic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/magic.rs b/src/magic.rs index 895773fd..38bfd940 100644 --- a/src/magic.rs +++ b/src/magic.rs @@ -36,8 +36,8 @@ pub fn get_rook_moves(sq: Square, blockers: BitBoard) -> BitBoard { } /// Get the moves for a rook on a particular square, given blockers blocking my movement. -#[cfg(target_feature = "bmi2")] #[inline] +#[cfg(target_feature = "bmi2")] pub fn get_rook_moves_bmi(sq: Square, blockers: BitBoard) -> BitBoard { unsafe { let bmi2_magic = *ROOK_BMI_MASK.get_unchecked(sq.to_int() as usize); From 07a22fc5c67bc8cb1e2fbb76d1bf7ea9c4da538b Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sat, 22 Mar 2025 21:09:32 -0700 Subject: [PATCH 93/94] Note which static references are mutated in which function call --- src/gen_tables/generate_all_tables.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/gen_tables/generate_all_tables.rs b/src/gen_tables/generate_all_tables.rs index 6af727d6..41b32140 100644 --- a/src/gen_tables/generate_all_tables.rs +++ b/src/gen_tables/generate_all_tables.rs @@ -21,18 +21,18 @@ use crate::gen_tables::bmis::*; use crate::gen_tables::magic::*; pub fn generate_all_tables() { - gen_lines(); - gen_between(); - gen_bishop_rays(); - gen_rook_rays(); - gen_knight_moves(); - gen_king_moves(); - gen_pawn_attacks(); - gen_pawn_moves(); - gen_all_magic(); - gen_bitboard_data(); + gen_lines(); // LINE + gen_between(); // BETWEEN + gen_bishop_rays(); // RAYS (ind of below) + gen_rook_rays(); // RAYS (ind of above) + gen_knight_moves(); // KNIGHT_MOVES + gen_king_moves(); // KING_MOVES + gen_pawn_attacks(); // PAWN_ATTACKS + gen_pawn_moves(); // PAWN_MOVES + gen_all_magic(); // MOVE_RAYS, MAGIC_NUMBERS, MOVES, GENERATED_NUM_MOVES + gen_bitboard_data(); // EDGES, RANKS, ADJACENT_FILES, FILES #[cfg(target_feature = "bmi2")] - gen_all_bmis(); + gen_all_bmis(); // BISHOP_BMI_MASK, ROOK_BMI_MASK, BMI_MOVES, GENERATED_BMI_MOVES let out_dir = env::var("OUT_DIR").unwrap(); let magic_path = Path::new(&out_dir).join("magic_gen.rs"); From 80e73cd93b3ca5dd4dc952c77355e37a5bb61b30 Mon Sep 17 00:00:00 2001 From: Nicolas Bird von Eyben Date: Sun, 23 Mar 2025 13:11:02 -0700 Subject: [PATCH 94/94] Remove empty `construct.rs` --- src/construct.rs | 2 -- src/lib.rs | 3 --- 2 files changed, 5 deletions(-) delete mode 100644 src/construct.rs diff --git a/src/construct.rs b/src/construct.rs deleted file mode 100644 index 112d0520..00000000 --- a/src/construct.rs +++ /dev/null @@ -1,2 +0,0 @@ -/// This is now a no-op. It does not need to be called anymore. -pub fn construct() {} diff --git a/src/lib.rs b/src/lib.rs index bfc0452a..3befa5cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,9 +42,6 @@ pub use crate::chess_move::*; mod color; pub use crate::color::*; -mod construct; -pub use crate::construct::*; - mod file; pub use crate::file::*;