diff --git a/Cargo.toml b/Cargo.toml index 1ef97ea8a..63dfafa6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "chess" -version = "3.2.0" -edition = "2018" +version = "4.0.0" +edition = "2021" 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" @@ -12,15 +12,16 @@ readme = "README.md" keywords = ["chess", "move", "generator"] license = "MIT" documentation = "https://jordanbray.github.io/chess/chess/index.html" +rust-version = "1.56.0" [dependencies] -arrayvec = "0.7.2" -nodrop = "0.1.14" -failure = "0.1.6" +arrayvec = { version = "0.7.2", default-features = false } +serde = { version = "1.0.219", default-features = false, optional = true, features = ["derive"] } [profile.release] opt-level = 3 debug = false +lto = true [profile.dev] opt-level = 3 @@ -38,5 +39,10 @@ opt-level = 3 opt-level = 3 [build-dependencies] -rand = { version = "0.7.2", default_features = false, features = ["small_rng"] } -failure = "0.1.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"] +std = ["arrayvec/std", "serde/std"] +serde = ["dep:serde", "arrayvec/serde"] \ No newline at end of file diff --git a/LICENSE b/LICENSE index 32a8f0fa5..21e6c1d69 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 diff --git a/README.md b/README.md index 042ce9ada..9ec70513c 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; diff --git a/src/bitboard.rs b/src/bitboard.rs index aa2a474d1..f7ced5b33 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); @@ -41,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) } @@ -50,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) } @@ -59,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) } @@ -68,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) } @@ -78,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) } @@ -87,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) } @@ -96,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) } @@ -105,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) } @@ -116,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) } @@ -125,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) } @@ -134,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) } @@ -143,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) } @@ -152,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; } @@ -167,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; } @@ -182,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; } @@ -199,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)) } @@ -208,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)) } @@ -217,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)) } @@ -226,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)) } @@ -236,7 +237,7 @@ impl Mul for &BitBoard { impl Not for BitBoard { type Output = BitBoard; - #[inline] + #[inline(always)] fn not(self) -> BitBoard { BitBoard(!self.0) } @@ -245,76 +246,79 @@ 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 { - 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(()) } } impl BitBoard { /// Construct a new bitboard from a u64 - #[inline] - pub fn new(b: u64) -> BitBoard { + #[inline(always)] + 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 { + #[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] - pub fn from_square(sq: Square) -> BitBoard { + #[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)`.", + )] 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 { + #[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] - pub fn popcnt(&self) -> u32 { + #[inline(always)] + 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 { + #[inline(always)] + 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 { + #[inline(always)] + pub const fn to_size(&self, rightshift: u8) -> usize { (self.0 >> rightshift) as usize } } @@ -323,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 b92d4e359..a3fbb8f18 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, @@ -17,10 +17,12 @@ use crate::zobrist::Zobrist; use std::convert::{TryFrom, TryInto}; use std::fmt; use std::hash::{Hash, Hasher}; -use std::mem; use std::str::FromStr; +#[cfg(feature = "std")] +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))] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Board { pieces: [BitBoard; NUM_PIECES], @@ -35,7 +37,9 @@ pub struct Board { } /// What is the status of this game? -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +#[repr(u8)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, PartialEq, Debug)] pub enum BoardStatus { Ongoing, Stalemate, @@ -44,10 +48,32 @@ pub enum BoardStatus { /// Construct the initial position. impl Default for Board { - #[inline] + /// 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(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") - .expect("Valid Position") + .expect("Startpos FEN is valid FEN") } } @@ -57,10 +83,31 @@ 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()); +/// ``` +#[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") +}); + impl Board { /// Construct a new `Board` that is completely empty. - /// Note: This does NOT give you the initial position. Just a blank slate. - fn new() -> 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)] + pub const fn new() -> Board { Board { pieces: [EMPTY; NUM_PIECES], color_combined: [EMPTY; NUM_COLORS], @@ -79,9 +126,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"); @@ -93,17 +140,21 @@ impl Board { /// # Ok(()) /// # } /// ``` - #[deprecated(since = "3.1.0", note = "please use `Board::from_str(fen)?` instead")] - #[inline] + #[deprecated( + since = "3.1.0", + note = "Internally this is a wrapper for `Board::from_str`, please use this function directly instead" + )] + #[inline(always)] + #[cfg(feature = "std")] pub fn from_fen(fen: String) -> Option { Board::from_str(&fen).ok() } #[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] + #[inline(always)] pub fn enumerate_moves(&self, moves: &mut [ChessMove; 256]) -> usize { let movegen = MoveGen::new_legal(self); let mut size = 0; @@ -116,55 +167,39 @@ 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] + #[inline(always)] 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 } } @@ -182,8 +217,8 @@ impl Board { /// /// assert_eq!(*board.combined(), combined_should_be); /// ``` - #[inline] - pub fn combined(&self) -> &BitBoard { + #[inline(always)] + pub const fn combined(&self) -> &BitBoard { &self.combined } @@ -204,9 +239,26 @@ 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.to_index()) } + 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.pieces_with_color(Piece::Rook, Color::White), white_rooks); + /// ``` + #[inline(always)] + 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. @@ -219,9 +271,9 @@ 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(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. @@ -239,9 +291,9 @@ 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.to_index()) } + unsafe { self.pieces.get_unchecked(piece.into_index()) } } /// Grab the `CastleRights` for a particular side. @@ -249,38 +301,24 @@ 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 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(move1) - /// .make_move_new(move2) - /// .make_move_new(move3) - /// .make_move_new(move4); + /// board = board.make_moves_new(moves); /// /// 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.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. @@ -291,7 +329,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); } } @@ -304,6 +342,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); /// ``` @@ -314,7 +353,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); } } @@ -327,23 +366,22 @@ impl Board { /// let mut board = Board::default(); /// assert_eq!(board.side_to_move(), Color::White); /// ``` - #[inline] - pub fn side_to_move(&self) -> Color { + #[inline(always)] + pub const fn side_to_move(&self) -> Color { self.side_to_move } - /// Grab my `CastleRights`. + /// My `CastleRights` /// /// ``` - /// use chess::{Board, Color, CastleRights}; + /// use chess::{Board, CastleRights}; + /// use std::str::FromStr; /// - /// let mut board = Board::default(); - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); + /// 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] + #[inline(always)] pub fn my_castle_rights(&self) -> CastleRights { self.castle_rights(self.side_to_move()) } @@ -368,6 +406,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); /// ``` @@ -375,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)] @@ -385,15 +424,14 @@ impl Board { /// My opponents `CastleRights`. /// /// ``` - /// use chess::{Board, Color, CastleRights}; + /// use chess::{Board, CastleRights}; + /// use std::str::FromStr; /// - /// let mut board = Board::default(); - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); + /// 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] + #[inline(always)] pub fn their_castle_rights(&self) -> CastleRights { self.castle_rights(!self.side_to_move()) } @@ -418,6 +456,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); /// ``` @@ -425,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)] @@ -433,10 +472,11 @@ 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.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); } @@ -449,9 +489,8 @@ impl Board { /// /// let board = Board::default(); /// - /// let new_board = board.set_piece(Piece::Queen, - /// Color::White, - /// Square::E4) + /// #[allow(deprecated)] + /// let new_board = board.set_piece(Piece::Queen, Color::White, Square::E4) /// .expect("Valid Position"); /// /// assert_eq!(new_board.pieces(Piece::Queen).count(), 3); @@ -500,6 +539,7 @@ impl Board { /// /// let board = Board::default(); /// + /// #[allow(deprecated)] /// let new_board = board.clear_square(Square::A1) /// .expect("Valid Position"); /// @@ -557,7 +597,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 @@ -589,10 +629,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; } } } @@ -613,26 +651,23 @@ 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; } // 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; } } @@ -661,12 +696,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_with_color(Piece::King, *color) != get_file(File::E) & get_rank(color.to_my_backrank()) - { - return false; - } + { + return false; } } @@ -676,7 +710,7 @@ impl Board { } // it checks out - return true; + true } /// Get a hash of the board. @@ -688,27 +722,32 @@ impl Board { } else { 0 } - ^ Zobrist::castles( - self.castle_rights[self.side_to_move.to_index()], - self.side_to_move, - ) - ^ Zobrist::castles( - self.castle_rights[(!self.side_to_move).to_index()], - !self.side_to_move, - ) - ^ if self.side_to_move == Color::Black { - Zobrist::color() - } else { - 0 - } + ^ 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). - /// - /// Currently not implemented... #[inline] pub fn get_pawn_hash(&self) -> u64 { - 0 + 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) + }) + ^ black_pawns.into_iter().fold(0, |acc, square| { + acc ^ Zobrist::piece(Piece::Pawn, square, Color::White) + }) + } + + /// Get a hash that depends only on king and pawn placement and color change. + #[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) + ^ Zobrist::piece(Piece::King, self.king_square(Color::Black), Color::Black) } /// What piece is on a particular `Square`? Is there even one? @@ -721,43 +760,67 @@ 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 { 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`, it is undefined behaviour to call this function on an empty square. + /// + /// ``` + /// use chess::{Board, Piece, Square}; + /// + /// let board = Board::default(); + /// + /// assert_eq!(unsafe { board.piece_on_unchecked(Square::A1) }, Piece::Rook); + /// // The following is undefined behaviour + /// unsafe { board.piece_on_unchecked(Square::A4) }; + /// ``` + #[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 } } /// 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 { @@ -770,6 +833,7 @@ impl Board { } /// Unset the en_passant square. + #[inline(always)] fn remove_ep(&mut self) { self.en_passant = None; } @@ -779,36 +843,43 @@ impl Board { /// ``` /// use chess::{Board, ChessMove, Square}; /// - /// let move1 = ChessMove::new(Square::D2, - /// Square::D4, - /// 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_moves_new(moves); /// - /// let move2 = ChessMove::new(Square::H7, - /// Square::H5, - /// None); + /// assert_eq!(board.en_passant(), Some(Square::E5)); + /// ``` + #[inline(always)] + pub const fn en_passant(&self) -> Option { + self.en_passant + } + + /// Give me the en_passant target square, if it exists. /// - /// let move3 = ChessMove::new(Square::D4, - /// Square::D5, - /// None); + /// ``` + /// use chess::{Board, ChessMove, Square}; /// - /// let move4 = 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(move1) - /// .make_move_new(move2) - /// .make_move_new(move3) - /// .make_move_new(move4); + /// let board = Board::default().make_moves_new(moves); /// - /// assert_eq!(board.en_passant(), Some(Square::E5)); + /// assert_eq!(board.en_passant_target(), Some(Square::E6)); /// ``` - #[inline] - pub fn en_passant(self) -> Option { - self.en_passant + #[inline(always)] + 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. + #[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()) @@ -827,22 +898,17 @@ 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] + #[inline(always)] 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. @@ -852,127 +918,59 @@ 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] + #[inline(always)] 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)))); + let mut result = Board::new(); + self.make_move(m, &mut result); + result + } - 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; - } - } + /// 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); + /// assert_eq!(result.status(), BoardStatus::Checkmate); + /// ``` + #[inline(always)] + pub fn make_moves>(&self, moves: T, result: &mut Board) { + *result = self.make_moves_new(moves); + } - result.side_to_move = !result.side_to_move; - result + /// 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); + /// ``` + #[inline(always)] + 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`. @@ -987,11 +985,11 @@ 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); /// ``` - #[inline] pub fn make_move(&self, m: ChessMove, result: &mut Board) { *result = *self; result.remove_ep(); @@ -1023,7 +1021,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; @@ -1077,7 +1075,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) }); @@ -1111,7 +1109,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))) @@ -1138,13 +1136,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 } @@ -1158,7 +1156,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(); @@ -1187,13 +1185,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() @@ -1201,7 +1199,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() @@ -1209,7 +1207,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()?) @@ -1224,3 +1222,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 = *STARTPOS; + assert_eq!(board, startpos, "Startpos is not correct"); +} diff --git a/src/board_builder.rs b/src/board_builder.rs index da2f67953..8d962a791 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}; @@ -66,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) @@ -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, @@ -90,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), @@ -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 { + self.castle_rights[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)?; @@ -288,7 +286,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 +313,12 @@ impl fmt::Display for BoardBuilder { write!( f, "{}", - self.castle_rights[Color::White.to_index()].to_string(Color::White) + self.castle_rights[Color::White.into_index()].with_color(Color::White) )?; write!( f, "{}", - self.castle_rights[Color::Black.to_index()].to_string(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(); @@ -511,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/cache_table.rs b/src/cache_table.rs index d57dc62ff..6811d2b3d 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 efe6808ed..75effd7d0 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}; @@ -9,12 +10,13 @@ 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 = 0, - KingSide = 1, - QueenSide = 2, - Both = 3, + NoRights = 0b00, + Both = 0b11, + KingSide = 0b01, + QueenSide = 0b10, } /// How many different types of `CastleRights` are there? @@ -28,6 +30,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.into_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 @@ -38,7 +56,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,54 +65,67 @@ 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 { /// Can I castle kingside? pub fn has_kingside(&self) -> bool { - self.to_index() & 1 == 1 + // Self::Both == 3 -> 0b11 & 0b01 == 0b01 👍 + self.into_index() & 1 == 1 } /// Can I castle queenside? pub fn has_queenside(&self) -> bool { - self.to_index() & 2 == 2 + // Self::Both == 3 -> 0b11 & 0b10 == 0b10 👍 + 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 } + /// 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 { @@ -108,13 +139,15 @@ 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) + //? Why is this a carrot (^) and not a pipe (|) + ^ BitBoard::set(my_backrank, File::H) } } } @@ -129,6 +162,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 => "", @@ -152,4 +186,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/chess_move.rs b/src/chess_move.rs index 1538aec23..65ba09024 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -1,17 +1,20 @@ 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)] +#[cfg_attr(feature="serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Copy, Eq, PartialEq, Default, Debug, Hash)] pub struct ChessMove { source: Square, dest: Square, @@ -19,32 +22,69 @@ pub struct ChessMove { } impl ChessMove { + /// An invalid move, can make `Option` more efficient. See `into_option`. + pub const NULL_MOVE: Self = Self { + 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) -> Self { + 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 { - ChessMove { - source: source, - dest: dest, - promotion: promotion, + pub const fn new(source: Square, dest: Square, promotion: Option) -> Self { + Self { + source, + dest, + promotion, } } /// 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,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, @@ -73,7 +113,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 +167,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 +274,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 }; @@ -261,8 +296,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; @@ -318,10 +353,10 @@ 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; + 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) { @@ -352,17 +387,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,19 +403,62 @@ 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 u16 + 1) + .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), } } } +//? 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 { @@ -401,6 +477,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}; @@ -411,24 +493,24 @@ 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), }); } - Ok(ChessMove::new(source, dest, promo)) + Ok(Self::new(source, dest, promo)) } } @@ -440,3 +522,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 6da8f596f..684cef03f 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,10 +2,12 @@ use crate::rank::Rank; 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, - Black, + White = 0, + Black = 1, } /// How many colors are there? @@ -15,15 +17,15 @@ 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 + #[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] - pub fn to_my_backrank(&self) -> Rank { + #[inline(always)] + pub const fn to_my_backrank(&self) -> Rank { match *self { Color::White => Rank::First, Color::Black => Rank::Eighth, @@ -32,8 +34,8 @@ 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 { + #[inline(always)] + pub const fn to_their_backrank(&self) -> Rank { match *self { Color::White => Rank::Eighth, Color::Black => Rank::First, @@ -41,8 +43,8 @@ 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 { + #[inline(always)] + pub const fn to_second_rank(&self) -> Rank { match *self { Color::White => Rank::Second, Color::Black => Rank::Seventh, @@ -51,8 +53,8 @@ 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 { + #[inline(always)] + pub const fn to_fourth_rank(&self) -> Rank { match *self { Color::White => Rank::Fourth, Color::Black => Rank::Fifth, @@ -60,8 +62,8 @@ impl Color { } /// Convert a `Color` to my seventh rank, which represents the rank before pawn promotion. - #[inline] - pub fn to_seventh_rank(&self) -> Rank { + #[inline(always)] + pub const fn to_seventh_rank(&self) -> Rank { match *self { Color::White => Rank::Seventh, Color::Black => Rank::Second, @@ -73,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 @@ -82,3 +84,15 @@ 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(always)] + fn from(value: Color) -> Self { + match value { + Color::White => true, + Color::Black => false, + } + } +} diff --git a/src/construct.rs b/src/construct.rs deleted file mode 100644 index 112d0520e..000000000 --- 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/error.rs b/src/error.rs index 185b83a72..a01394d9c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,35 +1,46 @@ -use failure::Fail; +use std::fmt; /// Sometimes, bad stuff happens. -#[derive(Clone, Debug, Fail)] -pub enum Error { +#[derive(Clone, Debug)] +pub enum InvalidError { /// The FEN string is invalid - #[fail(display = "Invalid FEN string: {}", fen)] - InvalidFen { fen: String }, + #[cfg(feature = "std")] + FEN { fen: String }, + #[cfg(not(feature = "std"))] + FEN, /// 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, + Board, /// 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, + Square, /// 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, + SanMove, /// 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, + UciMove, /// 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, + Rank, /// 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, + File, +} + +impl fmt::Display for InvalidError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + #[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.") + } + } } diff --git a/src/file.rs b/src/file.rs index e058084b0..670eb1f7c 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,8 +1,9 @@ -use crate::error::Error; +use crate::error::InvalidError; 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, @@ -33,7 +34,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, @@ -49,31 +50,32 @@ 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)) + #[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] - pub fn right(&self) -> File { - File::from_index(self.to_index() + 1) + #[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] - pub fn to_index(&self) -> usize { - *self as usize + #[inline(always)] + 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 +85,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 bee7ae19c..c686ef573 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,12 +1,13 @@ 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; /// 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, @@ -105,7 +107,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 +161,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 +210,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 +250,7 @@ impl Game { } } - return false; + false } /// Declare a draw by 3-fold repitition or 50-move rule. @@ -329,10 +325,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 +354,7 @@ impl Game { return false; } self.moves.push(Action::OfferDraw(color)); - return true; + true } /// Accept a draw offer from my opponent. @@ -383,20 +376,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 +407,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 d6f711b1d..cb0017854 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() 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() @@ -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/bmis.rs b/src/gen_tables/bmis.rs index 4fa57d9a7..e37a6f6dd 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(); } diff --git a/src/gen_tables/generate_all_tables.rs b/src/gen_tables/generate_all_tables.rs index f2ed8560c..41b321402 100644 --- a/src/gen_tables/generate_all_tables.rs +++ b/src/gen_tables/generate_all_tables.rs @@ -1,11 +1,7 @@ // 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. -extern crate rand; use std::env; use std::fs::File; @@ -25,22 +21,22 @@ 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"); - 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 +51,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 2167613e9..2c2bcfafc 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() 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) @@ -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 352c3bbf9..428ab99b5 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() 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) @@ -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 0b3360562..dbac7e447 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() 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() @@ -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 1eb94399c..11a357022 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,17 @@ 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(); + #[allow(static_mut_refs)] + 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 97bc365c2..79208fc82 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,24 +34,28 @@ 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 { - 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] } +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,16 +107,16 @@ 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()); diff --git a/src/gen_tables/mod.rs b/src/gen_tables/mod.rs index be3ddf353..25aa12061 100644 --- a/src/gen_tables/mod.rs +++ b/src/gen_tables/mod.rs @@ -5,8 +5,6 @@ #![allow(dead_code)] // it to be easily followed. -extern crate rand; - mod between; #[cfg(target_feature = "bmi2")] mod bmis; diff --git a/src/gen_tables/pawns.rs b/src/gen_tables/pawns.rs index 0cbe0ce5d..96ad02ac1 100644 --- a/src/gen_tables/pawns.rs +++ b/src/gen_tables/pawns.rs @@ -19,14 +19,14 @@ pub fn gen_pawn_moves() { for src in ALL_SQUARES.iter() { unsafe { if src.get_rank() == color.to_second_rank() { - PAWN_MOVES[color.to_index()][src.to_index()] = + PAWN_MOVES[color.into_index()][src.into_index()] = BitBoard::from_square(src.uforward(*color)) ^ BitBoard::from_square(src.uforward(*color).uforward(*color)); } else { match src.forward(*color) { - None => 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 06d6c84bb..cbd157003 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,23 @@ 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(); + #[allow(static_mut_refs)] + 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 67085d654..65a0998dd 100644 --- a/src/gen_tables/rays.rs +++ b/src/gen_tables/rays.rs @@ -9,17 +9,20 @@ 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.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() 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 }) @@ -32,13 +35,13 @@ 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.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 }) @@ -48,21 +51,21 @@ 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.into_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 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 2be1d08a1..4e60a905a 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 a9da30c57..3befa5cd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![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 //! used in a chess engine or UI without performance issues. @@ -18,13 +19,18 @@ //! ``` //! +#[cfg(not(feature = "std"))] +extern crate core as std; + mod board; 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; @@ -36,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::*; @@ -66,11 +69,13 @@ 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; 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 2b279924c..38bfd9406 100644 --- a/src/magic.rs +++ b/src/magic.rs @@ -10,15 +10,15 @@ 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.to_index()) } + 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.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. @@ -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); @@ -82,15 +82,15 @@ 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.to_index()) } + 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.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,13 +99,13 @@ 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 } } /// Get the legal destination castle squares for both players -#[inline] +#[inline(always)] pub fn get_castle_moves() -> BitBoard { CASTLE_MOVES } @@ -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 } } @@ -128,51 +128,51 @@ 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 - .get_unchecked(sq1.to_index()) - .get_unchecked(sq2.to_index()) + .get_unchecked(sq1.into_index()) + .get_unchecked(sq2.into_index()) } } /// 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 - .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] +#[inline(always)] 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] +#[inline(always)] 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] +#[inline(always)] 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] +#[inline(always)] pub fn get_pawn_source_double_moves() -> BitBoard { PAWN_SOURCE_DOUBLE_MOVES } diff --git a/src/movegen/mod.rs b/src/movegen/mod.rs index 20cfa76c2..7a1349c2e 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 fa983d1c2..5973c8415 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -6,10 +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; -use std::mem; +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, PartialOrd)] pub struct SquareAndBitBoard { square: Square, @@ -18,22 +17,20 @@ 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, } } } -pub type MoveList = NoDrop>; +pub type MoveList = ArrayVec; /// 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 @@ -83,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, @@ -94,30 +92,64 @@ impl MoveGen { #[inline(always)] fn enumerate_moves(board: &Board) -> MoveList { let checkers = *board.checkers(); - let mask = !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); - } 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); - } else { - KingType::legals::(&mut movelist, &board, mask); + let unoccupied_by_me = !board.color_combined(board.side_to_move()); + let mut movelist = ArrayVec::::new(); + + 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 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 { @@ -220,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) + }) } } @@ -250,7 +278,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); @@ -258,7 +286,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); @@ -298,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 @@ -338,16 +367,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(); @@ -355,6 +384,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( @@ -364,48 +394,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( @@ -415,6 +454,7 @@ fn movegen_perft_9() { ); } +#[cfg(feature = "std")] #[test] fn movegen_perft_10() { movegen_perft_test( @@ -424,6 +464,7 @@ fn movegen_perft_10() { ); } +#[cfg(feature = "std")] #[test] fn movegen_perft_11() { movegen_perft_test( @@ -433,6 +474,7 @@ fn movegen_perft_11() { ); } +#[cfg(feature = "std")] #[test] fn movegen_perft_12() { movegen_perft_test( @@ -442,76 +484,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 = @@ -522,7 +579,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] { @@ -542,6 +599,7 @@ fn move_of(m: &str) -> ChessMove { ) } +#[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 d4e456262..88c29be6d 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -12,15 +12,27 @@ use crate::magic::{ }; pub trait PieceType { - fn is(piece: Piece) -> bool; fn into_piece() -> Piece; + + #[allow(dead_code)] //? What is the purpose of this function #[inline(always)] - fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard; + fn is(piece: Piece) -> bool { + Self::into_piece() == piece + } + + fn pseudo_legals( + src: Square, + color: Color, + combined: BitBoard, + unoccupied_by_me: BitBoard, + ) -> BitBoard; + #[inline(always)] - 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); @@ -30,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)); @@ -45,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)); @@ -56,6 +69,43 @@ pub trait PieceType { } } } + + #[inline(always)] + 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; @@ -65,73 +115,60 @@ 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(always)] pub fn legal_ep_move(board: &Board, source: Square, dest: Square) -> bool { let combined = board.combined() ^ BitBoard::from_square(board.en_passant().unwrap()) ^ 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()); - 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 } } impl PieceType for PawnType { - fn is(piece: Piece) -> bool { - piece == Piece::Pawn - } - + #[inline(always)] fn into_piece() -> Piece { Piece::Pawn } #[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(always)] - 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); @@ -141,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( @@ -160,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( @@ -193,42 +231,109 @@ impl PieceType for PawnType { } } } + + #[inline(always)] + 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 { + #[inline(always)] fn is(piece: Piece) -> bool { piece == Piece::Bishop } + #[inline(always)] fn into_piece() -> Piece { Piece::Bishop } #[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 } } impl PieceType for KnightType { + #[inline(always)] fn is(piece: Piece) -> bool { piece == Piece::Knight } + #[inline(always)] fn into_piece() -> Piece { Piece::Knight } #[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(always)] - 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); @@ -238,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)); @@ -251,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)); @@ -260,6 +366,42 @@ impl PieceType for KnightType { } }; } + + #[inline(always)] + 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 { @@ -267,13 +409,19 @@ impl PieceType for RookType { piece == Piece::Rook } + #[inline(always)] fn into_piece() -> Piece { Piece::Rook } #[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 } } @@ -282,13 +430,19 @@ impl PieceType for QueenType { piece == Piece::Queen } + #[inline(always)] fn into_piece() -> Piece { Piece::Queen } #[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 } } @@ -326,7 +480,7 @@ impl KingType { board.pieces(Piece::Pawn) & board.color_combined(!board.side_to_move()), ); - return attackers == EMPTY; + attackers == EMPTY } } @@ -335,25 +489,32 @@ impl PieceType for KingType { piece == Piece::King } + #[inline(always)] fn into_piece() -> Piece { Piece::King } #[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(always)] - 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 { @@ -370,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 { @@ -401,4 +562,59 @@ impl PieceType for KingType { } } } + + #[inline(always)] + 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 + } } diff --git a/src/piece.rs b/src/piece.rs index c199e9a02..ac429e0f1 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -2,6 +2,8 @@ use crate::color::Color; 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, @@ -33,9 +35,30 @@ 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 + #[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', + Piece::Knight => 'n', + Piece::Bishop => 'b', + Piece::Rook => 'r', + Piece::Queen => 'q', + Piece::King => 'k', + } + } + + #[inline(always)] + 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 @@ -48,7 +71,8 @@ impl Piece { /// assert_eq!(Piece::Knight.to_string(Color::Black), "n"); /// ``` #[inline] - pub fn to_string(&self, color: Color) -> String { + #[cfg(feature = "std")] + pub fn to_string(self, color: Color) -> String { let piece = format!("{}", self); if color == Color::White { piece.to_uppercase() @@ -59,17 +83,26 @@ impl Piece { } impl fmt::Display for Piece { + #[inline(always)] 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 7df67fe0a..e083ada91 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -1,9 +1,10 @@ -use crate::error::Error; +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, @@ -31,10 +32,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 { + #[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 { 0 => Rank::First, @@ -50,30 +51,30 @@ 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)) + #[inline(always)] + pub const fn down(&self) -> Rank { + Rank::from_index(self.into_index().wrapping_sub(1)) } - /// Go one file up. If impossible, wrap around. - #[inline] - pub fn up(&self) -> Rank { - Rank::from_index(self.to_index() + 1) + /// Go one rank up. If impossible, wrap around. + #[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] - pub fn to_index(&self) -> usize { - *self as usize + #[inline(always)] + 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 +85,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 1e02b8039..aa908b2a9 100644 --- a/src/square.rs +++ b/src/square.rs @@ -1,11 +1,12 @@ use crate::color::Color; -use crate::error::Error; +use crate::error::InvalidError; use crate::file::File; use crate::rank::Rank; 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); @@ -23,6 +24,7 @@ impl Default for Square { /// /// assert_eq!(explicit_sq, implicit_sq); /// ``` + #[inline(always)] fn default() -> Square { Square::new(0) } @@ -41,8 +43,8 @@ impl Square { /// /// assert_eq!(Square::default(), bad_sq); /// ``` - #[inline] - pub fn new(sq: u8) -> Square { + #[inline(always)] + pub const fn new(sq: u8) -> Square { Square(sq & 63) } @@ -63,9 +65,9 @@ impl Square { /// assert_eq!(sq, x); /// } /// ``` - #[inline] - pub fn make_square(rank: Rank, file: File) -> Square { - Square((rank.to_index() as u8) << 3 ^ (file.to_index() as u8)) + #[inline(always)] + pub const fn make_square(rank: Rank, file: File) -> Square { + Square((rank as u8) << 3 ^ (file as u8)) } /// Return the rank given this square. @@ -77,8 +79,8 @@ impl Square { /// /// assert_eq!(sq.get_rank(), Rank::Seventh); /// ``` - #[inline] - pub fn get_rank(&self) -> Rank { + #[inline(always)] + pub const fn get_rank(&self) -> Rank { Rank::from_index((self.0 >> 3) as usize) } @@ -91,8 +93,8 @@ impl Square { /// /// assert_eq!(sq.get_file(), File::D); /// ``` - #[inline] - pub fn get_file(&self) -> File { + #[inline(always)] + pub const fn get_file(&self) -> File { File::from_index((self.0 & 7) as usize) } @@ -107,12 +109,12 @@ 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 } else { - Some(Square::make_square(self.get_rank().up(), self.get_file())) + Some(self.uup()) } } @@ -127,12 +129,12 @@ 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 } else { - Some(Square::make_square(self.get_rank().down(), self.get_file())) + Some(self.udown()) } } @@ -147,12 +149,12 @@ 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 } else { - Some(Square::make_square(self.get_rank(), self.get_file().left())) + Some(self.uleft()) } } @@ -167,15 +169,12 @@ 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 } else { - Some(Square::make_square( - self.get_rank(), - self.get_file().right(), - )) + Some(self.uright()) } } @@ -194,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(), @@ -217,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(), @@ -236,8 +235,8 @@ impl Square { /// /// assert_eq!(sq.uup().uup(), Square::make_square(Rank::First, File::D)); /// ``` - #[inline] - pub fn uup(&self) -> Square { + #[inline(always)] + pub const fn uup(&self) -> Square { Square::make_square(self.get_rank().up(), self.get_file()) } @@ -252,8 +251,8 @@ impl Square { /// /// assert_eq!(sq.udown().udown(), Square::make_square(Rank::Eighth, File::D)); /// ``` - #[inline] - pub fn udown(&self) -> Square { + #[inline(always)] + pub const fn udown(&self) -> Square { Square::make_square(self.get_rank().down(), self.get_file()) } @@ -268,8 +267,8 @@ impl Square { /// /// assert_eq!(sq.uleft().uleft(), Square::make_square(Rank::Seventh, File::H)); /// ``` - #[inline] - pub fn uleft(&self) -> Square { + #[inline(always)] + pub const fn uleft(&self) -> Square { Square::make_square(self.get_rank(), self.get_file().left()) } @@ -285,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()) } @@ -306,8 +305,8 @@ 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] - pub fn uforward(&self, color: Color) -> Square { + #[inline(always)] + pub const fn uforward(&self, color: Color) -> Square { match color { Color::White => self.uup(), Color::Black => self.udown(), @@ -330,8 +329,8 @@ 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] - pub fn ubackward(&self, color: Color) -> Square { + #[inline(always)] + pub const fn ubackward(&self, color: Color) -> Square { match color { Color::White => self.udown(), Color::Black => self.uup(), @@ -348,8 +347,8 @@ 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] - pub fn to_int(&self) -> u8 { + #[inline(always)] + pub const fn to_int(&self) -> u8 { self.0 } @@ -358,13 +357,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 { + #[inline(always)] + pub const fn into_index(self) -> usize { self.0 as usize } @@ -381,6 +380,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() } @@ -967,36 +967,41 @@ 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 ch: Vec = s.chars().collect(); - match ch[0] { - 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {} - _ => { - return Err(Error::InvalidSquare); + + 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(InvalidError::Square); + } } - } - match ch[1] { - '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {} - _ => { - return Err(Error::InvalidSquare); + match c2 { + '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {} + _ => { + return Err(InvalidError::Square); + } } + Ok(Square::make_square( + Rank::from_index((c2 as usize) - ('1' as usize)), + File::from_index((c1 as usize) - ('a' as usize)), + )) + } else { + Err(InvalidError::Square) } - Ok(Square::make_square( - Rank::from_index((ch[1] as usize) - ('1' as usize)), - File::from_index((ch[0] as usize) - ('a' as usize)), - )) } } diff --git a/src/zobrist.rs b/src/zobrist.rs index c718fa689..6ed2b8a1c 100644 --- a/src/zobrist.rs +++ b/src/zobrist.rs @@ -13,36 +13,40 @@ 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 - .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()) } } - #[inline] + #[inline(always)] 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()) } } - #[inline] + #[inline(always)] 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()) } } - #[inline] - pub fn color() -> u64 { - SIDE_TO_MOVE + #[inline(always)] + pub fn color(color: Color) -> u64 { + if (!color).into() { + SIDE_TO_MOVE + } else { + 0 + } } }