From 6a9039d015965e6f4579b960c9bdd73e1e043bf2 Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Thu, 4 Jun 2020 16:53:03 -0400 Subject: [PATCH 01/10] personal changes --- src/bitboard.rs | 672 ++++++------ src/board.rs | 2264 ++++++++++++++++++++-------------------- src/color.rs | 164 +-- src/game.rs | 4 +- src/movegen/movegen.rs | 1137 ++++++++++---------- src/rank.rs | 112 +- 6 files changed, 2202 insertions(+), 2151 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index 8070cadfe..8241ae9e5 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -1,336 +1,336 @@ -use crate::file::File; -use crate::rank::Rank; -use crate::square::*; -use std::fmt; -use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Mul, Not}; - -/// A good old-fashioned bitboard -/// You *do* have access to the actual value, but you are probably better off -/// using the implemented operators to work with this object. -/// -/// ``` -/// use chess::{BitBoard, Square}; -/// -/// let bb = BitBoard(7); // lower-left 3 squares -/// -/// let mut count = 0; -/// -/// // Iterate over each square in the bitboard -/// for _ in bb { -/// count += 1; -/// } -/// -/// assert_eq!(count, 3); -/// ``` -/// -#[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Default)] -pub struct BitBoard(pub u64); - -/// An empty bitboard. It is sometimes useful to use !EMPTY to get the universe of squares. -/// -/// ``` -/// use chess::EMPTY; -/// -/// assert_eq!(EMPTY.count(), 0); -/// -/// assert_eq!((!EMPTY).count(), 64); -/// ``` -pub const EMPTY: BitBoard = BitBoard(0); - -// Impl BitAnd -impl BitAnd for BitBoard { - type Output = BitBoard; - - #[inline] - fn bitand(self, other: BitBoard) -> BitBoard { - BitBoard(self.0 & other.0) - } -} - -impl BitAnd for &BitBoard { - type Output = BitBoard; - - #[inline] - fn bitand(self, other: &BitBoard) -> BitBoard { - BitBoard(self.0 & other.0) - } -} - -impl BitAnd<&BitBoard> for BitBoard { - type Output = BitBoard; - - #[inline] - fn bitand(self, other: &BitBoard) -> BitBoard { - BitBoard(self.0 & other.0) - } -} - -impl BitAnd for &BitBoard { - type Output = BitBoard; - - #[inline] - fn bitand(self, other: BitBoard) -> BitBoard { - BitBoard(self.0 & other.0) - } -} - -// Impl BitOr -impl BitOr for BitBoard { - type Output = BitBoard; - - #[inline] - fn bitor(self, other: BitBoard) -> BitBoard { - BitBoard(self.0 | other.0) - } -} - -impl BitOr for &BitBoard { - type Output = BitBoard; - - #[inline] - fn bitor(self, other: &BitBoard) -> BitBoard { - BitBoard(self.0 | other.0) - } -} - -impl BitOr<&BitBoard> for BitBoard { - type Output = BitBoard; - - #[inline] - fn bitor(self, other: &BitBoard) -> BitBoard { - BitBoard(self.0 | other.0) - } -} - -impl BitOr for &BitBoard { - type Output = BitBoard; - - #[inline] - fn bitor(self, other: BitBoard) -> BitBoard { - BitBoard(self.0 | other.0) - } -} - -// Impl BitXor - -impl BitXor for BitBoard { - type Output = BitBoard; - - #[inline] - fn bitxor(self, other: BitBoard) -> BitBoard { - BitBoard(self.0 ^ other.0) - } -} - -impl BitXor for &BitBoard { - type Output = BitBoard; - - #[inline] - fn bitxor(self, other: &BitBoard) -> BitBoard { - BitBoard(self.0 ^ other.0) - } -} - -impl BitXor<&BitBoard> for BitBoard { - type Output = BitBoard; - - #[inline] - fn bitxor(self, other: &BitBoard) -> BitBoard { - BitBoard(self.0 ^ other.0) - } -} - -impl BitXor for &BitBoard { - type Output = BitBoard; - - #[inline] - fn bitxor(self, other: BitBoard) -> BitBoard { - BitBoard(self.0 ^ other.0) - } -} - -// Impl BitAndAssign - -impl BitAndAssign for BitBoard { - #[inline] - fn bitand_assign(&mut self, other: BitBoard) { - self.0 &= other.0; - } -} - -impl BitAndAssign<&BitBoard> for BitBoard { - #[inline] - fn bitand_assign(&mut self, other: &BitBoard) { - self.0 &= other.0; - } -} - -// Impl BitOrAssign -impl BitOrAssign for BitBoard { - #[inline] - fn bitor_assign(&mut self, other: BitBoard) { - self.0 |= other.0; - } -} - -impl BitOrAssign<&BitBoard> for BitBoard { - #[inline] - fn bitor_assign(&mut self, other: &BitBoard) { - self.0 |= other.0; - } -} - -// Impl BitXor Assign -impl BitXorAssign for BitBoard { - #[inline] - fn bitxor_assign(&mut self, other: BitBoard) { - self.0 ^= other.0; - } -} - -impl BitXorAssign<&BitBoard> for BitBoard { - #[inline] - fn bitxor_assign(&mut self, other: &BitBoard) { - self.0 ^= other.0; - } -} - -// Impl Mul -impl Mul for BitBoard { - type Output = BitBoard; - - #[inline] - fn mul(self, other: BitBoard) -> BitBoard { - BitBoard(self.0.wrapping_mul(other.0)) - } -} - -impl Mul for &BitBoard { - type Output = BitBoard; - - #[inline] - fn mul(self, other: &BitBoard) -> BitBoard { - BitBoard(self.0.wrapping_mul(other.0)) - } -} - -impl Mul<&BitBoard> for BitBoard { - type Output = BitBoard; - - #[inline] - fn mul(self, other: &BitBoard) -> BitBoard { - BitBoard(self.0.wrapping_mul(other.0)) - } -} - -impl Mul for &BitBoard { - type Output = BitBoard; - - #[inline] - fn mul(self, other: BitBoard) -> BitBoard { - BitBoard(self.0.wrapping_mul(other.0)) - } -} - -// Impl Not -impl Not for BitBoard { - type Output = BitBoard; - - #[inline] - fn not(self) -> BitBoard { - BitBoard(!self.0) - } -} - -impl Not for &BitBoard { - type Output = BitBoard; - - #[inline] - fn not(self) -> BitBoard { - BitBoard(!self.0) - } -} - -impl fmt::Display for BitBoard { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut s: String = "".to_owned(); - for x in 0..64 { - if self.0 & (1u64 << x) == (1u64 << x) { - s.push_str("X "); - } else { - s.push_str(". "); - } - if x % 8 == 7 { - s.push_str("\n"); - } - } - write!(f, "{}", s) - } -} - -impl BitBoard { - /// Construct a new bitboard from a u64 - #[inline] - pub 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 { - 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 { - BitBoard(1u64 << sq.to_int()) - } - - /// Convert an `Option` to an `Option` - #[inline] - pub fn from_maybe_square(sq: Option) -> Option { - sq.map(|s| BitBoard::from_square(s)) - } - - /// Convert a `BitBoard` to a `Square`. This grabs the least-significant `Square` - #[inline] - pub fn to_square(&self) -> Square { - unsafe { Square::new(self.0.trailing_zeros() as u8) } - } - - /// Count the number of `Squares` set in this `BitBoard` - #[inline] - pub 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 { - BitBoard(self.0.swap_bytes()) - } - - /// Convert this `BitBoard` to a `usize` (for table lookups) - #[inline] - pub fn to_size(&self, rightshift: u8) -> usize { - (self.0 >> rightshift) as usize - } -} - -/// For the `BitBoard`, iterate over every `Square` set. -impl Iterator for BitBoard { - type Item = Square; - - #[inline] - fn next(&mut self) -> Option { - if self.0 == 0 { - None - } else { - let result = self.to_square(); - *self ^= BitBoard::from_square(result); - Some(result) - } - } -} +use crate::file::File; +use crate::rank::Rank; +use crate::square::*; +use std::fmt; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Mul, Not}; + +/// A good old-fashioned bitboard +/// You *do* have access to the actual value, but you are probably better off +/// using the implemented operators to work with this object. +/// +/// ``` +/// use chess::{BitBoard, Square}; +/// +/// let bb = BitBoard(7); // lower-left 3 squares +/// +/// let mut count = 0; +/// +/// // Iterate over each square in the bitboard +/// for _ in bb { +/// count += 1; +/// } +/// +/// assert_eq!(count, 3); +/// ``` +/// +#[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Default)] +pub struct BitBoard(pub u64); + +/// An empty bitboard. It is sometimes useful to use !EMPTY to get the universe of squares. +/// +/// ``` +/// use chess::EMPTY; +/// +/// assert_eq!(EMPTY.count(), 0); +/// +/// assert_eq!((!EMPTY).count(), 64); +/// ``` +pub const EMPTY: BitBoard = BitBoard(0); + +// Impl BitAnd +impl BitAnd for BitBoard { + type Output = BitBoard; + + #[inline] + fn bitand(self, other: BitBoard) -> BitBoard { + BitBoard(self.0 & other.0) + } +} + +impl BitAnd for &BitBoard { + type Output = BitBoard; + + #[inline] + fn bitand(self, other: &BitBoard) -> BitBoard { + BitBoard(self.0 & other.0) + } +} + +impl BitAnd<&BitBoard> for BitBoard { + type Output = BitBoard; + + #[inline] + fn bitand(self, other: &BitBoard) -> BitBoard { + BitBoard(self.0 & other.0) + } +} + +impl BitAnd for &BitBoard { + type Output = BitBoard; + + #[inline] + fn bitand(self, other: BitBoard) -> BitBoard { + BitBoard(self.0 & other.0) + } +} + +// Impl BitOr +impl BitOr for BitBoard { + type Output = BitBoard; + + #[inline] + fn bitor(self, other: BitBoard) -> BitBoard { + BitBoard(self.0 | other.0) + } +} + +impl BitOr for &BitBoard { + type Output = BitBoard; + + #[inline] + fn bitor(self, other: &BitBoard) -> BitBoard { + BitBoard(self.0 | other.0) + } +} + +impl BitOr<&BitBoard> for BitBoard { + type Output = BitBoard; + + #[inline] + fn bitor(self, other: &BitBoard) -> BitBoard { + BitBoard(self.0 | other.0) + } +} + +impl BitOr for &BitBoard { + type Output = BitBoard; + + #[inline] + fn bitor(self, other: BitBoard) -> BitBoard { + BitBoard(self.0 | other.0) + } +} + +// Impl BitXor + +impl BitXor for BitBoard { + type Output = BitBoard; + + #[inline] + fn bitxor(self, other: BitBoard) -> BitBoard { + BitBoard(self.0 ^ other.0) + } +} + +impl BitXor for &BitBoard { + type Output = BitBoard; + + #[inline] + fn bitxor(self, other: &BitBoard) -> BitBoard { + BitBoard(self.0 ^ other.0) + } +} + +impl BitXor<&BitBoard> for BitBoard { + type Output = BitBoard; + + #[inline] + fn bitxor(self, other: &BitBoard) -> BitBoard { + BitBoard(self.0 ^ other.0) + } +} + +impl BitXor for &BitBoard { + type Output = BitBoard; + + #[inline] + fn bitxor(self, other: BitBoard) -> BitBoard { + BitBoard(self.0 ^ other.0) + } +} + +// Impl BitAndAssign + +impl BitAndAssign for BitBoard { + #[inline] + fn bitand_assign(&mut self, other: BitBoard) { + self.0 &= other.0; + } +} + +impl BitAndAssign<&BitBoard> for BitBoard { + #[inline] + fn bitand_assign(&mut self, other: &BitBoard) { + self.0 &= other.0; + } +} + +// Impl BitOrAssign +impl BitOrAssign for BitBoard { + #[inline] + fn bitor_assign(&mut self, other: BitBoard) { + self.0 |= other.0; + } +} + +impl BitOrAssign<&BitBoard> for BitBoard { + #[inline] + fn bitor_assign(&mut self, other: &BitBoard) { + self.0 |= other.0; + } +} + +// Impl BitXor Assign +impl BitXorAssign for BitBoard { + #[inline] + fn bitxor_assign(&mut self, other: BitBoard) { + self.0 ^= other.0; + } +} + +impl BitXorAssign<&BitBoard> for BitBoard { + #[inline] + fn bitxor_assign(&mut self, other: &BitBoard) { + self.0 ^= other.0; + } +} + +// Impl Mul +impl Mul for BitBoard { + type Output = BitBoard; + + #[inline] + fn mul(self, other: BitBoard) -> BitBoard { + BitBoard(self.0.wrapping_mul(other.0)) + } +} + +impl Mul for &BitBoard { + type Output = BitBoard; + + #[inline] + fn mul(self, other: &BitBoard) -> BitBoard { + BitBoard(self.0.wrapping_mul(other.0)) + } +} + +impl Mul<&BitBoard> for BitBoard { + type Output = BitBoard; + + #[inline] + fn mul(self, other: &BitBoard) -> BitBoard { + BitBoard(self.0.wrapping_mul(other.0)) + } +} + +impl Mul for &BitBoard { + type Output = BitBoard; + + #[inline] + fn mul(self, other: BitBoard) -> BitBoard { + BitBoard(self.0.wrapping_mul(other.0)) + } +} + +// Impl Not +impl Not for BitBoard { + type Output = BitBoard; + + #[inline] + fn not(self) -> BitBoard { + BitBoard(!self.0) + } +} + +impl Not for &BitBoard { + type Output = BitBoard; + + #[inline] + fn not(self) -> BitBoard { + BitBoard(!self.0) + } +} + +impl fmt::Display for BitBoard { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut s: String = "".to_owned(); + for x in 0..64 { + if self.0 & (1u64 << x) == (1u64 << x) { + s.push_str("X "); + } else { + s.push_str(". "); + } + if x % 8 == 7 { + s.push_str("\n"); + } + } + write!(f, "{}", s) + } +} + +impl BitBoard { + /// Construct a new bitboard from a u64 + #[inline] + pub 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 { + 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 { + BitBoard(1u64 << sq.to_int()) + } + + /// Convert an `Option` to an `Option` + #[inline] + pub fn from_maybe_square(sq: Option) -> Option { + Some(BitBoard::from_square(sq?)) + } + + /// Convert a `BitBoard` to a `Square`. This grabs the least-significant `Square` + #[inline] + pub fn to_square(&self) -> Square { + unsafe { Square::new(self.0.trailing_zeros() as u8) } + } + + /// Count the number of `Squares` set in this `BitBoard` + #[inline] + pub 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 { + BitBoard(self.0.swap_bytes()) + } + + /// Convert this `BitBoard` to a `usize` (for table lookups) + #[inline] + pub fn to_size(&self, rightshift: u8) -> usize { + (self.0 >> rightshift) as usize + } +} + +/// For the `BitBoard`, iterate over every `Square` set. +impl Iterator for BitBoard { + type Item = Square; + + #[inline] + fn next(&mut self) -> Option { + if self.0 == 0 { + None + } else { + let result = self.to_square(); + *self ^= BitBoard::from_square(result); + Some(result) + } + } +} diff --git a/src/board.rs b/src/board.rs index 5d04091b7..4ef8a017c 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,1110 +1,1154 @@ -use crate::bitboard::{BitBoard, EMPTY}; -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::file::File; -use crate::magic::{ - between, get_adjacent_files, get_bishop_rays, get_castle_moves, get_file, get_king_moves, - get_knight_moves, get_pawn_attacks, get_pawn_dest_double_moves, get_pawn_source_double_moves, - get_rank, get_rook_rays, -}; -use crate::movegen::*; -use crate::piece::{Piece, ALL_PIECES, NUM_PIECES}; -use crate::square::{Square, ALL_SQUARES}; -use crate::zobrist::Zobrist; -use std::convert::{TryFrom, TryInto}; -use std::fmt; -use std::mem; -use std::str::FromStr; - -/// A representation of a chess board. That's why you're here, right? -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct Board { - pieces: [BitBoard; NUM_PIECES], - color_combined: [BitBoard; NUM_COLORS], - combined: BitBoard, - side_to_move: Color, - castle_rights: [CastleRights; NUM_COLORS], - pinned: BitBoard, - checkers: BitBoard, - hash: u64, - en_passant: Option, -} - -/// What is the status of this game? -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] -pub enum BoardStatus { - Ongoing, - Stalemate, - Checkmate, -} - -/// Construct the initial position. -impl Default for Board { - #[inline] - fn default() -> Board { - Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") - .expect("Valid Position") - } -} - -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 { - Board { - pieces: [EMPTY; NUM_PIECES], - color_combined: [EMPTY; NUM_COLORS], - combined: EMPTY, - side_to_move: Color::White, - castle_rights: [CastleRights::NoRights; NUM_COLORS], - pinned: EMPTY, - checkers: EMPTY, - hash: 0, - en_passant: None, - } - } - - /// Construct a board from a FEN string. - /// - /// ``` - /// use chess::Board; - /// use std::str::FromStr; - /// # use chess::Error; - /// - /// # fn main() -> Result<(), Error> { - /// - /// // 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"); - /// assert_eq!(init_position, Board::default()); - /// - /// // This is the new way - /// let init_position_2 = Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")?; - /// assert_eq!(init_position_2, Board::default()); - /// # Ok(()) - /// # } - /// ``` - #[deprecated(since = "3.1.0", note = "please use `Board::from_str(fen)?` instead")] - #[inline] - 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." - )] - #[inline] - pub fn enumerate_moves(&self, moves: &mut [ChessMove; 256]) -> usize { - let movegen = MoveGen::new_legal(self); - let mut size = 0; - for m in movegen { - moves[size] = m; - size += 1; - } - size - } - - /// Is this game Ongoing, is it Stalemate, or is it Checkmate? - /// - /// ``` - /// 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)); - /// - /// assert_eq!(board.status(), BoardStatus::Ongoing); - /// - /// 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)); - /// - /// assert_eq!(board.status(), BoardStatus::Ongoing); - /// - /// 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)); - /// - /// assert_eq!(board.status(), BoardStatus::Checkmate); - /// ``` - #[inline] - pub fn status(&self) -> BoardStatus { - let moves = MoveGen::new_legal(&self).len(); - match moves { - 0 => { - if self.checkers == EMPTY { - BoardStatus::Stalemate - } else { - BoardStatus::Checkmate - } - } - _ => BoardStatus::Ongoing, - } - } - - /// Grab the "combined" `BitBoard`. This is a `BitBoard` with every piece. - /// - /// ``` - /// use chess::{Board, BitBoard, Rank, get_rank}; - /// - /// let board = Board::default(); - /// - /// let combined_should_be = get_rank(Rank::First) | - /// get_rank(Rank::Second) | - /// get_rank(Rank::Seventh) | - /// get_rank(Rank::Eighth); - /// - /// assert_eq!(*board.combined(), combined_should_be); - /// ``` - #[inline] - pub fn combined(&self) -> &BitBoard { - &self.combined - } - - /// Grab the "color combined" `BitBoard`. This is a `BitBoard` with every piece of a particular - /// color. - /// - /// ``` - /// use chess::{Board, BitBoard, Rank, get_rank, Color}; - /// - /// let board = Board::default(); - /// - /// let white_pieces = get_rank(Rank::First) | - /// get_rank(Rank::Second); - /// - /// let black_pieces = get_rank(Rank::Seventh) | - /// get_rank(Rank::Eighth); - /// - /// assert_eq!(*board.color_combined(Color::White), white_pieces); - /// assert_eq!(*board.color_combined(Color::Black), black_pieces); - /// ``` - #[inline] - pub fn color_combined(&self, color: Color) -> &BitBoard { - unsafe { self.color_combined.get_unchecked(color.to_index()) } - } - - /// Give me the `Square` the `color` king is on. - /// - /// ``` - /// use chess::{Board, Square, Color}; - /// - /// let board = Board::default(); - /// - /// assert_eq!(board.king_square(Color::White), Square::E1); - /// assert_eq!(board.king_square(Color::Black), Square::E8); - /// ``` - #[inline] - pub fn king_square(&self, color: Color) -> Square { - (self.pieces(Piece::King) & self.color_combined(color)).to_square() - } - - /// Grab the "pieces" `BitBoard`. This is a `BitBoard` with every piece of a particular type. - /// - /// ``` - /// use chess::{Board, BitBoard, Piece, Square}; - /// - /// // The rooks should be in each corner of the board - /// let rooks = BitBoard::from_square(Square::A1) | - /// BitBoard::from_square(Square::H1) | - /// BitBoard::from_square(Square::A8) | - /// BitBoard::from_square(Square::H8); - /// - /// let board = Board::default(); - /// - /// assert_eq!(*board.pieces(Piece::Rook), rooks); - /// ``` - #[inline] - pub fn pieces(&self, piece: Piece) -> &BitBoard { - unsafe { self.pieces.get_unchecked(piece.to_index()) } - } - - /// Grab the `CastleRights` for a particular side. - /// - /// ``` - /// 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 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); - /// - /// assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide); - /// assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights); - /// ``` - #[inline] - pub fn castle_rights(&self, color: Color) -> CastleRights { - unsafe { *self.castle_rights.get_unchecked(color.to_index()) } - } - - /// Add castle rights for a particular side. Note: this can create an invalid position. - #[deprecated( - 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] - pub fn add_castle_rights(&mut self, color: Color, add: CastleRights) { - unsafe { - *self.castle_rights.get_unchecked_mut(color.to_index()) = - self.castle_rights(color).add(add); - } - } - - /// Remove castle rights for a particular side. - /// - /// ``` - /// use chess::{Board, CastleRights, Color}; - /// - /// let mut board = Board::default(); - /// assert_eq!(board.castle_rights(Color::White), CastleRights::Both); - /// - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// assert_eq!(board.castle_rights(Color::White), CastleRights::QueenSide); - /// ``` - #[deprecated( - 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] - pub fn remove_castle_rights(&mut self, color: Color, remove: CastleRights) { - unsafe { - *self.castle_rights.get_unchecked_mut(color.to_index()) = - self.castle_rights(color).remove(remove); - } - } - - /// Who's turn is it? - /// - /// ``` - /// use chess::{Board, Color}; - /// - /// let mut board = Board::default(); - /// assert_eq!(board.side_to_move(), Color::White); - /// ``` - #[inline] - pub fn side_to_move(&self) -> Color { - self.side_to_move - } - - /// Grab my `CastleRights`. - /// - /// ``` - /// use chess::{Board, Color, CastleRights}; - /// - /// let mut board = Board::default(); - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); - /// - /// assert_eq!(board.my_castle_rights(), board.castle_rights(Color::White)); - /// ``` - #[inline] - pub fn my_castle_rights(&self) -> CastleRights { - self.castle_rights(self.side_to_move()) - } - - /// Add to my `CastleRights`. Note: This can make the position invalid. - #[deprecated( - 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] - pub fn add_my_castle_rights(&mut self, add: CastleRights) { - let color = self.side_to_move(); - #[allow(deprecated)] - self.add_castle_rights(color, add); - } - - /// Remove some of my `CastleRights`. - /// - /// ``` - /// use chess::{Board, CastleRights}; - /// - /// let mut board = Board::default(); - /// assert_eq!(board.my_castle_rights(), CastleRights::Both); - /// - /// board.remove_my_castle_rights(CastleRights::KingSide); - /// assert_eq!(board.my_castle_rights(), CastleRights::QueenSide); - /// ``` - #[deprecated( - 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] - pub fn remove_my_castle_rights(&mut self, remove: CastleRights) { - let color = self.side_to_move(); - #[allow(deprecated)] - self.remove_castle_rights(color, remove); - } - - /// My opponents `CastleRights`. - /// - /// ``` - /// use chess::{Board, Color, CastleRights}; - /// - /// let mut board = Board::default(); - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); - /// - /// assert_eq!(board.their_castle_rights(), board.castle_rights(Color::Black)); - /// ``` - #[inline] - pub fn their_castle_rights(&self) -> CastleRights { - self.castle_rights(!self.side_to_move()) - } - - /// Add to my opponents `CastleRights`. Note: This can make the position invalid. - #[deprecated( - 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] - pub fn add_their_castle_rights(&mut self, add: CastleRights) { - let color = !self.side_to_move(); - #[allow(deprecated)] - self.add_castle_rights(color, add) - } - - /// Remove some of my opponents `CastleRights`. - /// - /// ``` - /// use chess::{Board, CastleRights}; - /// - /// let mut board = Board::default(); - /// assert_eq!(board.their_castle_rights(), CastleRights::Both); - /// - /// board.remove_their_castle_rights(CastleRights::KingSide); - /// assert_eq!(board.their_castle_rights(), CastleRights::QueenSide); - /// ``` - #[deprecated( - 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] - pub fn remove_their_castle_rights(&mut self, remove: CastleRights) { - let color = !self.side_to_move(); - #[allow(deprecated)] - self.remove_castle_rights(color, remove); - } - - /// Add or remove a piece from the bitboards in this struct. - fn xor(&mut self, piece: Piece, bb: BitBoard, color: Color) { - unsafe { - *self.pieces.get_unchecked_mut(piece.to_index()) ^= bb; - *self.color_combined.get_unchecked_mut(color.to_index()) ^= bb; - self.combined ^= bb; - self.hash ^= Zobrist::piece(piece, bb.to_square(), color); - } - } - - /// For a chess UI: set a piece on a particular square. - /// - /// ``` - /// use chess::{Board, Piece, Color, Square}; - /// - /// let board = Board::default(); - /// - /// let new_board = board.set_piece(Piece::Queen, - /// Color::White, - /// Square::E4) - /// .expect("Valid Position"); - /// - /// assert_eq!(new_board.pieces(Piece::Queen).count(), 3); - /// ``` - #[deprecated( - 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] - pub fn set_piece(&self, piece: Piece, color: Color, square: Square) -> Option { - let mut result = *self; - let square_bb = BitBoard::from_square(square); - match self.piece_on(square) { - None => result.xor(piece, square_bb, color), - Some(x) => { - // remove x from the bitboard - if self.color_combined(Color::White) & square_bb == square_bb { - result.xor(x, square_bb, Color::White); - } else { - result.xor(x, square_bb, Color::Black); - } - // add piece to the bitboard - result.xor(piece, square_bb, color); - } - } - - // If setting this piece down leaves my opponent in check, and it's my move, then the - // position is not a valid chess board - result.side_to_move = !result.side_to_move; - result.update_pin_info(); - if result.checkers != EMPTY { - return None; - } - - // undo our damage - result.side_to_move = !result.side_to_move; - result.update_pin_info(); - - Some(result) - } - - /// For a chess UI: clear a particular square. - /// - /// ``` - /// use chess::{Board, Square, Piece}; - /// - /// let board = Board::default(); - /// - /// let new_board = board.clear_square(Square::A1) - /// .expect("Valid Position"); - /// - /// assert_eq!(new_board.pieces(Piece::Rook).count(), 3); - /// ``` - #[deprecated( - 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] - pub fn clear_square(&self, square: Square) -> Option { - let mut result = *self; - let square_bb = BitBoard::from_square(square); - match self.piece_on(square) { - None => {} - Some(x) => { - // remove x from the bitboard - if self.color_combined(Color::White) & square_bb == square_bb { - result.xor(x, square_bb, Color::White); - } else { - result.xor(x, square_bb, Color::Black); - } - } - } - - // If setting this piece down leaves my opponent in check, and it's my move, then the - // position is not a valid chess board - result.side_to_move = !result.side_to_move; - result.update_pin_info(); - if result.checkers != EMPTY { - return None; - } - - // undo our damage - result.side_to_move = !result.side_to_move; - result.update_pin_info(); - - Some(result) - } - - /// Switch the color of the player without actually making a move. Returns None if the current - /// player is in check. - /// - /// Note that this erases the en-passant information, so applying this function twice does not - /// always give the same result back. - /// - /// ``` - /// use chess::{Board, Color}; - /// - /// let board = Board::default(); - /// - /// assert_eq!(board.side_to_move(), Color::White); - /// - /// let new_board = board.null_move().expect("Valid Position"); - /// - /// assert_eq!(new_board.side_to_move(), Color::Black); - /// ``` - #[inline] - pub fn null_move(&self) -> Option { - if self.checkers != EMPTY { - None - } else { - let mut result = *self; - result.side_to_move = !result.side_to_move; - result.remove_ep(); - result.update_pin_info(); - Some(result) - } - } - - /// Does this board "make sense"? - /// Do all the pieces make sense, do the bitboards combine correctly, etc? - /// This is for sanity checking. - /// - /// ``` - /// use chess::{Board, Color, Piece, Square}; - /// - /// let board = Board::default(); - /// - /// assert_eq!(board.is_sane(), true); - /// - /// // Remove the king - /// let bad_board = board.clear_square(Square::E1).expect("Valid Position"); - /// assert_eq!(bad_board.is_sane(), false); - /// ``` - pub fn is_sane(&self) -> bool { - // 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; - } - } - } - } - - // make sure the colors don't overlap, either - if self.color_combined(Color::White) & self.color_combined(Color::Black) != EMPTY { - return false; - } - - // grab all the pieces by OR'ing together each piece() BitBoard - let combined = ALL_PIECES - .iter() - .fold(EMPTY, |cur, next| cur | self.pieces(*next)); - - // make sure that's equal to the combined bitboard - if combined != *self.combined() { - return false; - } - - // make sure there is exactly one white king - if (self.pieces(Piece::King) & self.color_combined(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 { - 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; - } - } - } - - // make sure my opponent is not currently in check (because that would be illegal) - let mut board_copy = *self; - board_copy.side_to_move = !board_copy.side_to_move; - board_copy.update_pin_info(); - if board_copy.checkers != EMPTY { - return false; - } - - // for each color, verify that, if they have castle rights, that they haven't moved their - // rooks or king - for color in ALL_COLORS.iter() { - // get the castle rights - let castle_rights = self.castle_rights(*color); - - // the castle rights object will tell us which rooks shouldn't have moved yet. - // verify there are rooks on all those squares - if castle_rights.unmoved_rooks(*color) - & self.pieces(Piece::Rook) - & self.color_combined(*color) - != castle_rights.unmoved_rooks(*color) - { - return false; - } - // 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) - != get_file(File::E) & get_rank(color.to_my_backrank()) - { - return false; - } - } - } - - // we must make sure the kings aren't touching - if get_king_moves(self.king_square(Color::White)) & self.pieces(Piece::King) != EMPTY { - return false; - } - - // it checks out - return true; - } - - /// Get a hash of the board. - #[inline] - pub fn get_hash(&self) -> u64 { - self.hash - ^ if let Some(ep) = self.en_passant { - Zobrist::en_passant(ep.get_file(), !self.side_to_move) - } 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 - } - } - - /// 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 - } - - /// What piece is on a particular `Square`? Is there even one? - /// - /// ``` - /// use chess::{Board, Piece, Square}; - /// - /// let board = Board::default(); - /// - /// assert_eq!(board.piece_on(Square::A1), Some(Piece::Rook)); - /// assert_eq!(board.piece_on(Square::D4), None); - /// ``` - #[inline] - 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) - } - } 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) - } - } - } - } - - /// What color piece is on a particular square? - #[inline] - pub fn color_on(&self, square: Square) -> Option { - if (self.color_combined(Color::White) & BitBoard::from_square(square)) != EMPTY { - Some(Color::White) - } else if (self.color_combined(Color::Black) & BitBoard::from_square(square)) != EMPTY { - Some(Color::Black) - } else { - None - } - } - - /// Unset the en_passant square. - fn remove_ep(&mut self) { - self.en_passant = None; - } - - /// Give me the en_passant square, if it exists. - /// - /// ``` - /// use chess::{Board, ChessMove, Square}; - /// - /// let move1 = ChessMove::new(Square::D2, - /// Square::D4, - /// None); - /// - /// let move2 = ChessMove::new(Square::H7, - /// Square::H5, - /// None); - /// - /// let move3 = ChessMove::new(Square::D4, - /// Square::D5, - /// None); - /// - /// let move4 = ChessMove::new(Square::E7, - /// Square::E5, - /// None); - /// - /// let board = Board::default().make_move_new(move1) - /// .make_move_new(move2) - /// .make_move_new(move3) - /// .make_move_new(move4); - /// - /// assert_eq!(board.en_passant(), Some(Square::E5)); - /// ``` - #[inline] - pub fn en_passant(self) -> Option { - self.en_passant - } - - /// Set the en_passant square. Note: This must only be called when self.en_passant is already - /// None. - fn set_ep(&mut self, sq: Square) { - // Only set self.en_passant if the pawn can actually be captured next move. - if get_adjacent_files(sq.get_file()) - & get_rank(sq.get_rank()) - & self.pieces(Piece::Pawn) - & self.color_combined(!self.side_to_move) - != EMPTY - { - self.en_passant = Some(sq); - } - } - - /// Is a particular move legal? This function is very slow, but will work on unsanitized - /// input. - /// - /// ``` - /// 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 board = Board::default(); - /// - /// assert_eq!(board.legal(move1), true); - /// assert_eq!(board.legal(move2), false); - /// ``` - #[inline] - pub fn legal(&self, m: ChessMove) -> bool { - MoveGen::new_legal(&self).find(|x| *x == m).is_some() - } - - /// Make a chess move onto a new board. - /// - /// panic!() if king is captured. - /// - /// ``` - /// use chess::{Board, ChessMove, Square, Color}; - /// - /// let m = ChessMove::new(Square::D2, - /// Square::D4, - /// None); - /// - /// let board = Board::default(); - /// assert_eq!(board.make_move_new(m).side_to_move(), Color::Black); - /// ``` - #[inline] - pub fn make_move_new(&self, m: ChessMove) -> Board { - let mut result = unsafe { mem::uninitialized() }; - self.make_move(m, &mut result); - result - } - - /// Make a chess move onto an already allocated `Board`. - /// - /// panic!() if king is captured. - /// - /// ``` - /// use chess::{Board, ChessMove, Square, Color}; - /// - /// let m = ChessMove::new(Square::D2, - /// Square::D4, - /// None); - /// - /// let board = Board::default(); - /// let mut result = Board::default(); - /// 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(); - result.checkers = EMPTY; - result.pinned = EMPTY; - let source = m.get_source(); - let dest = m.get_dest(); - - let source_bb = BitBoard::from_square(source); - let dest_bb = BitBoard::from_square(dest); - let move_bb = source_bb ^ dest_bb; - let moved = self.piece_on(source).unwrap(); - - result.xor(moved, source_bb, self.side_to_move); - result.xor(moved, dest_bb, self.side_to_move); - if let Some(captured) = self.piece_on(dest) { - result.xor(captured, dest_bb, !self.side_to_move); - } - - #[allow(deprecated)] - result.remove_their_castle_rights(CastleRights::square_to_castle_rights( - !self.side_to_move, - dest, - )); - - #[allow(deprecated)] - result.remove_my_castle_rights(CastleRights::square_to_castle_rights( - self.side_to_move, - source, - )); - - let opp_king = result.pieces(Piece::King) & result.color_combined(!result.side_to_move); - - let castles = moved == Piece::King && (move_bb & get_castle_moves()) == move_bb; - - let ksq = opp_king.to_square(); - - const CASTLE_ROOK_START: [File; 8] = [ - File::A, - File::A, - File::A, - File::A, - File::H, - File::H, - File::H, - File::H, - ]; - const CASTLE_ROOK_END: [File; 8] = [ - File::D, - File::D, - File::D, - File::D, - File::F, - File::F, - File::F, - File::F, - ]; - - if moved == Piece::Knight { - result.checkers ^= get_knight_moves(ksq) & dest_bb; - } else if moved == Piece::Pawn { - if let Some(Piece::Knight) = m.get_promotion() { - result.xor(Piece::Pawn, dest_bb, self.side_to_move); - result.xor(Piece::Knight, dest_bb, self.side_to_move); - result.checkers ^= get_knight_moves(ksq) & dest_bb; - } else if let Some(promotion) = m.get_promotion() { - result.xor(Piece::Pawn, dest_bb, self.side_to_move); - result.xor(promotion, dest_bb, self.side_to_move); - } else if (source_bb & get_pawn_source_double_moves()) != EMPTY - && (dest_bb & get_pawn_dest_double_moves()) != EMPTY - { - result.set_ep(dest); - result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); - } else if Some(dest.ubackward(self.side_to_move)) == self.en_passant { - result.xor( - Piece::Pawn, - BitBoard::from_square(dest.ubackward(self.side_to_move)), - !self.side_to_move, - ); - result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); - } else { - result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); - } - } else if castles { - let my_backrank = self.side_to_move.to_my_backrank(); - let index = dest.get_file().to_index(); - let start = BitBoard::set(my_backrank, unsafe { - *CASTLE_ROOK_START.get_unchecked(index) - }); - let end = BitBoard::set(my_backrank, unsafe { - *CASTLE_ROOK_END.get_unchecked(index) - }); - result.xor(Piece::Rook, start, self.side_to_move); - result.xor(Piece::Rook, end, self.side_to_move); - } - // now, lets see if we're in check or pinned - let attackers = result.color_combined(result.side_to_move) - & ((get_bishop_rays(ksq) - & (result.pieces(Piece::Bishop) | result.pieces(Piece::Queen))) - | (get_rook_rays(ksq) - & (result.pieces(Piece::Rook) | result.pieces(Piece::Queen)))); - - for sq in attackers { - let between = between(sq, ksq) & result.combined(); - if between == EMPTY { - result.checkers ^= BitBoard::from_square(sq); - } else if between.popcnt() == 1 { - result.pinned ^= between; - } - } - - result.side_to_move = !result.side_to_move; - } - - /// Update the pin information. - fn update_pin_info(&mut self) { - self.pinned = EMPTY; - self.checkers = EMPTY; - - let ksq = (self.pieces(Piece::King) & self.color_combined(self.side_to_move)).to_square(); - - let pinners = self.color_combined(!self.side_to_move) - & ((get_bishop_rays(ksq) & (self.pieces(Piece::Bishop) | self.pieces(Piece::Queen))) - | (get_rook_rays(ksq) & (self.pieces(Piece::Rook) | self.pieces(Piece::Queen)))); - - for sq in pinners { - let between = between(sq, ksq) & self.combined(); - if between == EMPTY { - self.checkers ^= BitBoard::from_square(sq); - } else if between.popcnt() == 1 { - self.pinned ^= between; - } - } - - self.checkers ^= get_knight_moves(ksq) - & self.color_combined(!self.side_to_move) - & self.pieces(Piece::Knight); - - self.checkers ^= get_pawn_attacks( - ksq, - self.side_to_move, - self.color_combined(!self.side_to_move) & self.pieces(Piece::Pawn), - ); - } - - /// Give me the `BitBoard` of my pinned pieces. - #[inline] - pub fn pinned(&self) -> &BitBoard { - &self.pinned - } - - /// Give me the `Bitboard` of the pieces putting me in check. - #[inline] - pub fn checkers(&self) -> &BitBoard { - &self.checkers - } -} - -impl fmt::Display for Board { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let fen: BoardBuilder = self.into(); - write!(f, "{}", fen) - } -} - -impl TryFrom<&BoardBuilder> for Board { - type Error = Error; - - fn try_from(fen: &BoardBuilder) -> Result { - let mut board = Board::new(); - - for sq in ALL_SQUARES.iter() { - if let Some((piece, color)) = fen[*sq] { - board.xor(piece, BitBoard::from_square(*sq), color); - } - } - - board.side_to_move = fen.get_side_to_move(); - - if let Some(ep) = fen.get_en_passant() { - board.side_to_move = !board.side_to_move; - board.set_ep(ep); - board.side_to_move = !board.side_to_move; - } - - #[allow(deprecated)] - board.add_castle_rights(Color::White, fen.get_castle_rights(Color::White)); - #[allow(deprecated)] - board.add_castle_rights(Color::Black, fen.get_castle_rights(Color::Black)); - - board.update_pin_info(); - - if board.is_sane() { - Ok(board) - } else { - Err(Error::InvalidBoard) - } - } -} - -impl TryFrom<&mut BoardBuilder> for Board { - type Error = Error; - - fn try_from(fen: &mut BoardBuilder) -> Result { - (&*fen).try_into() - } -} - -impl TryFrom for Board { - type Error = Error; - - fn try_from(fen: BoardBuilder) -> Result { - (&fen).try_into() - } -} - -impl FromStr for Board { - type Err = Error; - - fn from_str(value: &str) -> Result { - Ok(BoardBuilder::from_str(value)?.try_into()?) - } -} - -#[test] -fn test_null_move_en_passant() { - let start = - Board::from_str("rnbqkbnr/pppp2pp/8/4pP2/8/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 0").unwrap(); - let expected = - Board::from_str("rnbqkbnr/pppp2pp/8/4pP2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 0").unwrap(); - assert_eq!(start.null_move().unwrap(), expected); -} +use crate::bitboard::{BitBoard, EMPTY}; +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::file::File; +use crate::magic::{ + between, get_adjacent_files, get_bishop_rays, get_castle_moves, get_file, get_king_moves, + get_knight_moves, get_pawn_attacks, get_pawn_dest_double_moves, get_pawn_source_double_moves, + get_rank, get_rook_rays, +}; +use crate::movegen::*; +use crate::piece::{Piece, ALL_PIECES, NUM_PIECES}; +use crate::square::{Square, ALL_SQUARES}; +use crate::zobrist::Zobrist; +use std::convert::{TryFrom, TryInto}; +use std::fmt; +use std::mem; +use std::str::FromStr; + +/// A representation of a chess board. That's why you're here, right? +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct Board { + pieces: [BitBoard; NUM_PIECES], + color_combined: [BitBoard; NUM_COLORS], + combined: BitBoard, + side_to_move: Color, + castle_rights: [CastleRights; NUM_COLORS], + pinned: BitBoard, + checkers: BitBoard, + hash: u64, + en_passant: Option, +} + +/// What is the status of this game? +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +pub enum BoardStatus { + Ongoing, + Stalemate, + Checkmate, +} + +/// Construct the initial position. +impl Default for Board { + #[inline] + fn default() -> Board { + Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + .expect("Valid Position") + } +} + +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 { + Board { + pieces: [EMPTY; NUM_PIECES], + color_combined: [EMPTY; NUM_COLORS], + combined: EMPTY, + side_to_move: Color::White, + castle_rights: [CastleRights::NoRights; NUM_COLORS], + pinned: EMPTY, + checkers: EMPTY, + hash: 0, + en_passant: None, + } + } + + /// Construct a board from a FEN string. + /// + /// ``` + /// use chess::Board; + /// use std::str::FromStr; + /// # use chess::Error; + /// + /// # fn main() -> Result<(), Error> { + /// + /// // 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"); + /// assert_eq!(init_position, Board::default()); + /// + /// // This is the new way + /// let init_position_2 = Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")?; + /// assert_eq!(init_position_2, Board::default()); + /// # Ok(()) + /// # } + /// ``` + #[deprecated(since = "3.1.0", note = "please use `Board::from_str(fen)?` instead")] + #[inline] + 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." + )] + #[inline] + pub fn enumerate_moves(&self, moves: &mut [ChessMove; 256]) -> usize { + let movegen = MoveGen::new_legal(self); + let mut size = 0; + for m in movegen { + moves[size] = m; + size += 1; + } + size + } + + /// Is this game Ongoing, is it Stalemate, or is it Checkmate? + /// + /// ``` + /// 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)); + /// + /// assert_eq!(board.status(), BoardStatus::Ongoing); + /// + /// 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)); + /// + /// assert_eq!(board.status(), BoardStatus::Ongoing); + /// + /// 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)); + /// + /// assert_eq!(board.status(), BoardStatus::Checkmate); + /// ``` + #[inline] + pub fn status(&self) -> BoardStatus { + let moves = MoveGen::new_legal(&self).len(); + match moves { + 0 => { + if self.checkers == EMPTY { + BoardStatus::Stalemate + } else { + BoardStatus::Checkmate + } + } + _ => BoardStatus::Ongoing, + } + } + + /// Grab the "combined" `BitBoard`. This is a `BitBoard` with every piece. + /// + /// ``` + /// use chess::{Board, BitBoard, Rank, get_rank}; + /// + /// let board = Board::default(); + /// + /// let combined_should_be = get_rank(Rank::First) | + /// get_rank(Rank::Second) | + /// get_rank(Rank::Seventh) | + /// get_rank(Rank::Eighth); + /// + /// assert_eq!(*board.combined(), combined_should_be); + /// ``` + #[inline] + pub fn combined(&self) -> &BitBoard { + &self.combined + } + + /// Grab the "color combined" `BitBoard`. This is a `BitBoard` with every piece of a particular + /// color. + /// + /// ``` + /// use chess::{Board, BitBoard, Rank, get_rank, Color}; + /// + /// let board = Board::default(); + /// + /// let white_pieces = get_rank(Rank::First) | + /// get_rank(Rank::Second); + /// + /// let black_pieces = get_rank(Rank::Seventh) | + /// get_rank(Rank::Eighth); + /// + /// assert_eq!(*board.color_combined(Color::White), white_pieces); + /// assert_eq!(*board.color_combined(Color::Black), black_pieces); + /// ``` + #[inline] + pub fn color_combined(&self, color: Color) -> &BitBoard { + unsafe { self.color_combined.get_unchecked(color.to_index()) } + } + + /// Give me the `Square` the `color` king is on. + /// + /// ``` + /// use chess::{Board, Square, Color}; + /// + /// let board = Board::default(); + /// + /// assert_eq!(board.king_square(Color::White), Square::E1); + /// assert_eq!(board.king_square(Color::Black), Square::E8); + /// ``` + #[inline] + pub fn king_square(&self, color: Color) -> Square { + (self.pieces(Piece::King) & self.color_combined(color)).to_square() + } + + /// Grab the "pieces" `BitBoard`. This is a `BitBoard` with every piece of a particular type. + /// + /// ``` + /// use chess::{Board, BitBoard, Piece, Square}; + /// + /// // The rooks should be in each corner of the board + /// let rooks = BitBoard::from_square(Square::A1) | + /// BitBoard::from_square(Square::H1) | + /// BitBoard::from_square(Square::A8) | + /// BitBoard::from_square(Square::H8); + /// + /// let board = Board::default(); + /// + /// assert_eq!(*board.pieces(Piece::Rook), rooks); + /// ``` + #[inline] + pub fn pieces(&self, piece: Piece) -> &BitBoard { + unsafe { self.pieces.get_unchecked(piece.to_index()) } + } + + /// Grab the `CastleRights` for a particular side. + /// + /// ``` + /// 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 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); + /// + /// assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide); + /// assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights); + /// ``` + #[inline] + pub fn castle_rights(&self, color: Color) -> CastleRights { + unsafe { *self.castle_rights.get_unchecked(color.to_index()) } + } + + /// Add castle rights for a particular side. Note: this can create an invalid position. + #[deprecated( + 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] + pub fn add_castle_rights(&mut self, color: Color, add: CastleRights) { + unsafe { + *self.castle_rights.get_unchecked_mut(color.to_index()) = + self.castle_rights(color).add(add); + } + } + + /// Remove castle rights for a particular side. + /// + /// ``` + /// use chess::{Board, CastleRights, Color}; + /// + /// let mut board = Board::default(); + /// assert_eq!(board.castle_rights(Color::White), CastleRights::Both); + /// + /// board.remove_castle_rights(Color::White, CastleRights::KingSide); + /// assert_eq!(board.castle_rights(Color::White), CastleRights::QueenSide); + /// ``` + #[deprecated( + 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] + pub fn remove_castle_rights(&mut self, color: Color, remove: CastleRights) { + unsafe { + *self.castle_rights.get_unchecked_mut(color.to_index()) = + self.castle_rights(color).remove(remove); + } + } + + /// Who's turn is it? + /// + /// ``` + /// use chess::{Board, Color}; + /// + /// let mut board = Board::default(); + /// assert_eq!(board.side_to_move(), Color::White); + /// ``` + #[inline] + pub fn side_to_move(&self) -> Color { + self.side_to_move + } + + /// Grab my `CastleRights`. + /// + /// ``` + /// use chess::{Board, Color, CastleRights}; + /// + /// let mut board = Board::default(); + /// board.remove_castle_rights(Color::White, CastleRights::KingSide); + /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); + /// + /// assert_eq!(board.my_castle_rights(), board.castle_rights(Color::White)); + /// ``` + #[inline] + pub fn my_castle_rights(&self) -> CastleRights { + self.castle_rights(self.side_to_move()) + } + + /// Add to my `CastleRights`. Note: This can make the position invalid. + #[deprecated( + 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] + pub fn add_my_castle_rights(&mut self, add: CastleRights) { + let color = self.side_to_move(); + #[allow(deprecated)] + self.add_castle_rights(color, add); + } + + /// Remove some of my `CastleRights`. + /// + /// ``` + /// use chess::{Board, CastleRights}; + /// + /// let mut board = Board::default(); + /// assert_eq!(board.my_castle_rights(), CastleRights::Both); + /// + /// board.remove_my_castle_rights(CastleRights::KingSide); + /// assert_eq!(board.my_castle_rights(), CastleRights::QueenSide); + /// ``` + #[deprecated( + 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] + pub fn remove_my_castle_rights(&mut self, remove: CastleRights) { + let color = self.side_to_move(); + #[allow(deprecated)] + self.remove_castle_rights(color, remove); + } + + /// My opponents `CastleRights`. + /// + /// ``` + /// use chess::{Board, Color, CastleRights}; + /// + /// let mut board = Board::default(); + /// board.remove_castle_rights(Color::White, CastleRights::KingSide); + /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); + /// + /// assert_eq!(board.their_castle_rights(), board.castle_rights(Color::Black)); + /// ``` + #[inline] + pub fn their_castle_rights(&self) -> CastleRights { + self.castle_rights(!self.side_to_move()) + } + + /// Add to my opponents `CastleRights`. Note: This can make the position invalid. + #[deprecated( + 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] + pub fn add_their_castle_rights(&mut self, add: CastleRights) { + let color = !self.side_to_move(); + #[allow(deprecated)] + self.add_castle_rights(color, add) + } + + /// Remove some of my opponents `CastleRights`. + /// + /// ``` + /// use chess::{Board, CastleRights}; + /// + /// let mut board = Board::default(); + /// assert_eq!(board.their_castle_rights(), CastleRights::Both); + /// + /// board.remove_their_castle_rights(CastleRights::KingSide); + /// assert_eq!(board.their_castle_rights(), CastleRights::QueenSide); + /// ``` + #[deprecated( + 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] + pub fn remove_their_castle_rights(&mut self, remove: CastleRights) { + let color = !self.side_to_move(); + #[allow(deprecated)] + self.remove_castle_rights(color, remove); + } + + /// Add or remove a piece from the bitboards in this struct. + fn xor(&mut self, piece: Piece, bb: BitBoard, color: Color) { + unsafe { + *self.pieces.get_unchecked_mut(piece.to_index()) ^= bb; + *self.color_combined.get_unchecked_mut(color.to_index()) ^= bb; + self.combined ^= bb; + self.hash ^= Zobrist::piece(piece, bb.to_square(), color); + } + } + + /// For a chess UI: set a piece on a particular square. + /// + /// ``` + /// use chess::{Board, Piece, Color, Square}; + /// + /// let board = Board::default(); + /// + /// let new_board = board.set_piece(Piece::Queen, + /// Color::White, + /// Square::E4) + /// .expect("Valid Position"); + /// + /// assert_eq!(new_board.pieces(Piece::Queen).count(), 3); + /// ``` + #[deprecated( + 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] + pub fn set_piece(&self, piece: Piece, color: Color, square: Square) -> Option { + let mut result = *self; + let square_bb = BitBoard::from_square(square); + match self.piece_on(square) { + None => result.xor(piece, square_bb, color), + Some(x) => { + // remove x from the bitboard + if self.color_combined(Color::White) & square_bb == square_bb { + result.xor(x, square_bb, Color::White); + } else { + result.xor(x, square_bb, Color::Black); + } + // add piece to the bitboard + result.xor(piece, square_bb, color); + } + } + + // If setting this piece down leaves my opponent in check, and it's my move, then the + // position is not a valid chess board + result.side_to_move = !result.side_to_move; + result.update_pin_info(); + if result.checkers != EMPTY { + return None; + } + + // undo our damage + result.side_to_move = !result.side_to_move; + result.update_pin_info(); + + Some(result) + } + + /// For a chess UI: clear a particular square. + /// + /// ``` + /// use chess::{Board, Square, Piece}; + /// + /// let board = Board::default(); + /// + /// let new_board = board.clear_square(Square::A1) + /// .expect("Valid Position"); + /// + /// assert_eq!(new_board.pieces(Piece::Rook).count(), 3); + /// ``` + #[deprecated( + 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] + pub fn clear_square(&self, square: Square) -> Option { + let mut result = *self; + let square_bb = BitBoard::from_square(square); + match self.piece_on(square) { + None => {} + Some(x) => { + // remove x from the bitboard + if self.color_combined(Color::White) & square_bb == square_bb { + result.xor(x, square_bb, Color::White); + } else { + result.xor(x, square_bb, Color::Black); + } + } + } + + // If setting this piece down leaves my opponent in check, and it's my move, then the + // position is not a valid chess board + result.side_to_move = !result.side_to_move; + result.update_pin_info(); + if result.checkers != EMPTY { + return None; + } + + // undo our damage + result.side_to_move = !result.side_to_move; + result.update_pin_info(); + + Some(result) + } + + /// Switch the color of the player without actually making a move. Returns None if the current + /// player is in check. + /// + /// Note that this erases the en-passant information, so applying this function twice does not + /// always give the same result back. + /// + /// ``` + /// use chess::{Board, Color}; + /// + /// let board = Board::default(); + /// + /// assert_eq!(board.side_to_move(), Color::White); + /// + /// let new_board = board.null_move().expect("Valid Position"); + /// + /// assert_eq!(new_board.side_to_move(), Color::Black); + /// ``` + #[inline] + pub fn null_move(&self) -> Option { + if self.checkers != EMPTY { + None + } else { + let mut result = *self; + result.side_to_move = !result.side_to_move; + result.remove_ep(); + result.update_pin_info(); + Some(result) + } + } + + /// Does this board "make sense"? + /// Do all the pieces make sense, do the bitboards combine correctly, etc? + /// This is for sanity checking. + /// + /// ``` + /// use chess::{Board, Color, Piece, Square}; + /// + /// let board = Board::default(); + /// + /// assert_eq!(board.is_sane(), true); + /// + /// // Remove the king + /// let bad_board = board.clear_square(Square::E1).expect("Valid Position"); + /// assert_eq!(bad_board.is_sane(), false); + /// ``` + pub fn is_sane(&self) -> bool { + // 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; + } + } + } + } + + // make sure the colors don't overlap, either + if self.color_combined(Color::White) & self.color_combined(Color::Black) != EMPTY { + return false; + } + + // grab all the pieces by OR'ing together each piece() BitBoard + let combined = ALL_PIECES + .iter() + .fold(EMPTY, |cur, next| cur | self.pieces(*next)); + + // make sure that's equal to the combined bitboard + if combined != *self.combined() { + return false; + } + + // make sure there is exactly one white king + if (self.pieces(Piece::King) & self.color_combined(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 { + 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; + } + } + } + + // make sure my opponent is not currently in check (because that would be illegal) + let mut board_copy = *self; + board_copy.side_to_move = !board_copy.side_to_move; + board_copy.update_pin_info(); + if board_copy.checkers != EMPTY { + return false; + } + + // for each color, verify that, if they have castle rights, that they haven't moved their + // rooks or king + for color in ALL_COLORS.iter() { + // get the castle rights + let castle_rights = self.castle_rights(*color); + + // the castle rights object will tell us which rooks shouldn't have moved yet. + // verify there are rooks on all those squares + if castle_rights.unmoved_rooks(*color) + & self.pieces(Piece::Rook) + & self.color_combined(*color) + != castle_rights.unmoved_rooks(*color) + { + return false; + } + // 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) + != get_file(File::E) & get_rank(color.to_my_backrank()) + { + return false; + } + } + } + + // we must make sure the kings aren't touching + if get_king_moves(self.king_square(Color::White)) & self.pieces(Piece::King) != EMPTY { + return false; + } + + // it checks out + return true; + } + + /// Get a hash of the board. + #[inline] + pub fn get_hash(&self) -> u64 { + self.hash + ^ if let Some(ep) = self.en_passant { + Zobrist::en_passant(ep.get_file(), !self.side_to_move) + } 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 + } + } + + /// 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 + } + + /// What piece is on a particular `Square`? Is there even one? + /// + /// ``` + /// use chess::{Board, Piece, Square}; + /// + /// let board = Board::default(); + /// + /// assert_eq!(board.piece_on(Square::A1), Some(Piece::Rook)); + /// assert_eq!(board.piece_on(Square::D4), None); + /// ``` + #[inline] + 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) + } + } 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) + } + } + } + } + + /// What color piece is on a particular square? + #[inline] + pub fn color_on(&self, square: Square) -> Option { + if (self.color_combined(Color::White) & BitBoard::from_square(square)) != EMPTY { + Some(Color::White) + } else if (self.color_combined(Color::Black) & BitBoard::from_square(square)) != EMPTY { + Some(Color::Black) + } else { + None + } + } + + /// Unset the en_passant square. + fn remove_ep(&mut self) { + self.en_passant = None; + } + + /// Give me the en_passant square, if it exists. + /// + /// ``` + /// use chess::{Board, ChessMove, Square}; + /// + /// let move1 = ChessMove::new(Square::D2, + /// Square::D4, + /// None); + /// + /// let move2 = ChessMove::new(Square::H7, + /// Square::H5, + /// None); + /// + /// let move3 = ChessMove::new(Square::D4, + /// Square::D5, + /// None); + /// + /// let move4 = ChessMove::new(Square::E7, + /// Square::E5, + /// None); + /// + /// let board = Board::default().make_move_new(move1) + /// .make_move_new(move2) + /// .make_move_new(move3) + /// .make_move_new(move4); + /// + /// assert_eq!(board.en_passant(), Some(Square::E5)); + /// ``` + #[inline] + pub fn en_passant(self) -> Option { + self.en_passant + } + + /// Set the en_passant square. Note: This must only be called when self.en_passant is already + /// None. + fn set_ep(&mut self, sq: Square) { + // Only set self.en_passant if the pawn can actually be captured next move. + if get_adjacent_files(sq.get_file()) + & get_rank(sq.get_rank()) + & self.pieces(Piece::Pawn) + & self.color_combined(!self.side_to_move) + != EMPTY + { + self.en_passant = Some(sq); + } + } + + /// Is a particular move legal? This function is very slow, but will work on unsanitized + /// input. + /// + /// ``` + /// 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 board = Board::default(); + /// + /// assert_eq!(board.legal(move1), true); + /// assert_eq!(board.legal(move2), false); + /// ``` + #[inline] + pub fn legal(&self, m: ChessMove) -> bool { + MoveGen::new_legal(&self).find(|x| *x == m).is_some() + } + + /// Make a chess move onto a new board. + /// + /// panic!() if king is captured. + /// + /// ``` + /// use chess::{Board, ChessMove, Square, Color}; + /// + /// let m = ChessMove::new(Square::D2, + /// Square::D4, + /// None); + /// + /// let board = Board::default(); + /// assert_eq!(board.make_move_new(m).side_to_move(), Color::Black); + /// ``` + #[inline] + pub fn make_move_new(&self, m: ChessMove) -> Board { + let mut result = unsafe { mem::uninitialized() }; + self.make_move(m, &mut result); + result + } + + /// Make a chess move onto an already allocated `Board`. + /// + /// panic!() if king is captured. + /// + /// ``` + /// use chess::{Board, ChessMove, Square, Color}; + /// + /// let m = ChessMove::new(Square::D2, + /// Square::D4, + /// None); + /// + /// let board = Board::default(); + /// let mut result = Board::default(); + /// 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(); + result.checkers = EMPTY; + result.pinned = EMPTY; + let source = m.get_source(); + let dest = m.get_dest(); + + let source_bb = BitBoard::from_square(source); + let dest_bb = BitBoard::from_square(dest); + let move_bb = source_bb ^ dest_bb; + let moved = self.piece_on(source).unwrap(); + + result.xor(moved, source_bb, self.side_to_move); + result.xor(moved, dest_bb, self.side_to_move); + if let Some(captured) = self.piece_on(dest) { + result.xor(captured, dest_bb, !self.side_to_move); + } + + #[allow(deprecated)] + result.remove_their_castle_rights(CastleRights::square_to_castle_rights( + !self.side_to_move, + dest, + )); + + #[allow(deprecated)] + result.remove_my_castle_rights(CastleRights::square_to_castle_rights( + self.side_to_move, + source, + )); + + let opp_king = result.pieces(Piece::King) & result.color_combined(!result.side_to_move); + + let castles = moved == Piece::King && (move_bb & get_castle_moves()) == move_bb; + + let ksq = opp_king.to_square(); + + const CASTLE_ROOK_START: [File; 8] = [ + File::A, + File::A, + File::A, + File::A, + File::H, + File::H, + File::H, + File::H, + ]; + const CASTLE_ROOK_END: [File; 8] = [ + File::D, + File::D, + File::D, + File::D, + File::F, + File::F, + File::F, + File::F, + ]; + + if moved == Piece::Knight { + result.checkers ^= get_knight_moves(ksq) & dest_bb; + } else if moved == Piece::Pawn { + if let Some(Piece::Knight) = m.get_promotion() { + result.xor(Piece::Pawn, dest_bb, self.side_to_move); + result.xor(Piece::Knight, dest_bb, self.side_to_move); + result.checkers ^= get_knight_moves(ksq) & dest_bb; + } else if let Some(promotion) = m.get_promotion() { + result.xor(Piece::Pawn, dest_bb, self.side_to_move); + result.xor(promotion, dest_bb, self.side_to_move); + } else if (source_bb & get_pawn_source_double_moves()) != EMPTY + && (dest_bb & get_pawn_dest_double_moves()) != EMPTY + { + result.set_ep(dest); + result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); + } else if Some(dest.ubackward(self.side_to_move)) == self.en_passant { + result.xor( + Piece::Pawn, + BitBoard::from_square(dest.ubackward(self.side_to_move)), + !self.side_to_move, + ); + result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); + } else { + result.checkers ^= get_pawn_attacks(ksq, !result.side_to_move, dest_bb); + } + } else if castles { + let my_backrank = self.side_to_move.to_my_backrank(); + let index = dest.get_file().to_index(); + let start = BitBoard::set(my_backrank, unsafe { + *CASTLE_ROOK_START.get_unchecked(index) + }); + let end = BitBoard::set(my_backrank, unsafe { + *CASTLE_ROOK_END.get_unchecked(index) + }); + result.xor(Piece::Rook, start, self.side_to_move); + result.xor(Piece::Rook, end, self.side_to_move); + } + // now, lets see if we're in check or pinned + let attackers = result.color_combined(result.side_to_move) + & ((get_bishop_rays(ksq) + & (result.pieces(Piece::Bishop) | result.pieces(Piece::Queen))) + | (get_rook_rays(ksq) + & (result.pieces(Piece::Rook) | result.pieces(Piece::Queen)))); + + for sq in attackers { + let between = between(sq, ksq) & result.combined(); + if between == EMPTY { + result.checkers ^= BitBoard::from_square(sq); + } else if between.popcnt() == 1 { + result.pinned ^= between; + } + } + + result.side_to_move = !result.side_to_move; + } + + /// Update the pin information. + fn update_pin_info(&mut self) { + self.pinned = EMPTY; + self.checkers = EMPTY; + + let ksq = (self.pieces(Piece::King) & self.color_combined(self.side_to_move)).to_square(); + + let pinners = self.color_combined(!self.side_to_move) + & ((get_bishop_rays(ksq) & (self.pieces(Piece::Bishop) | self.pieces(Piece::Queen))) + | (get_rook_rays(ksq) & (self.pieces(Piece::Rook) | self.pieces(Piece::Queen)))); + + for sq in pinners { + let between = between(sq, ksq) & self.combined(); + if between == EMPTY { + self.checkers ^= BitBoard::from_square(sq); + } else if between.popcnt() == 1 { + self.pinned ^= between; + } + } + + self.checkers ^= get_knight_moves(ksq) + & self.color_combined(!self.side_to_move) + & self.pieces(Piece::Knight); + + self.checkers ^= get_pawn_attacks( + ksq, + self.side_to_move, + self.color_combined(!self.side_to_move) & self.pieces(Piece::Pawn), + ); + } + + /// Give me the `BitBoard` of my pinned pieces. + #[inline] + pub fn pinned(&self) -> &BitBoard { + &self.pinned + } + + /// Give me the `Bitboard` of the pieces putting me in check. + #[inline] + pub fn checkers(&self) -> &BitBoard { + &self.checkers + } +} + +impl fmt::Display for Board { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let fen: BoardBuilder = self.into(); + write!(f, "{}", fen) + } +} + +impl TryFrom<&BoardBuilder> for Board { + type Error = Error; + + fn try_from(fen: &BoardBuilder) -> Result { + let mut board = Board::new(); + + for sq in ALL_SQUARES.iter() { + if let Some((piece, color)) = fen[*sq] { + board.xor(piece, BitBoard::from_square(*sq), color); + } + } + + board.side_to_move = fen.get_side_to_move(); + + if let Some(ep) = fen.get_en_passant() { + board.side_to_move = !board.side_to_move; + board.set_ep(ep); + board.side_to_move = !board.side_to_move; + } + + #[allow(deprecated)] + board.add_castle_rights(Color::White, fen.get_castle_rights(Color::White)); + #[allow(deprecated)] + board.add_castle_rights(Color::Black, fen.get_castle_rights(Color::Black)); + + board.update_pin_info(); + + if board.is_sane() { + Ok(board) + } else { + Err(Error::InvalidBoard) + } + } +} + +impl TryFrom<&mut BoardBuilder> for Board { + type Error = Error; + + fn try_from(fen: &mut BoardBuilder) -> Result { + (&*fen).try_into() + } +} + +impl TryFrom for Board { + type Error = Error; + + fn try_from(fen: BoardBuilder) -> Result { + (&fen).try_into() + } +} + +impl FromStr for Board { + type Err = Error; + + fn from_str(value: &str) -> Result { + Ok(BoardBuilder::from_str(value)?.try_into()?) + } +} + +#[test] +fn test_null_move_en_passant() { + let start = + Board::from_str("rnbqkbnr/pppp2pp/8/4pP2/8/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 0").unwrap(); + let expected = + Board::from_str("rnbqkbnr/pppp2pp/8/4pP2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 0").unwrap(); + assert_eq!(start.null_move().unwrap(), expected); +} + +#[cfg(test)] +fn move_of(m: &str) -> ChessMove { + let promo = if m.len() > 4 { + Some(match m.as_bytes()[4] { + b'q' => Piece::Queen, + b'r' => Piece::Rook, + b'b' => Piece::Bishop, + b'n' => Piece::Knight, + _ => panic!("unrecognized uci move: {}", m), + }) + } else { + None + }; + ChessMove::new( + Square::from_string(m[..2].to_string()).unwrap(), + Square::from_string(m[2..4].to_string()).unwrap(), + promo, + ) +} + +#[test] +fn test_hash_behavior() { + let test_cases = [ + ("r1bq1rk1/3pb1pp/1p2p3/p2pPp1Q/3P1B2/4R3/PPPN1PPP/R5K1 b - - 3 16", vec!["d8e8", "h5e8", "f8e8", "h2h3"], + "r1b1r1k1/3pb1pp/1p2p3/p2pPp2/3P1B2/4R2P/PPPN1PP1/R5K1 b - - 0 18"), + ("6k1/1R2bp2/4p1p1/2p1P2p/2K2P2/r1P1B2P/2P5/8 b - - 4 37", vec!["f7f5", "e5f6", "e7f6", "b7b4", "g8g7"], + "8/6k1/4pbp1/2p4p/1RK2P2/r1P1B2P/2P5/8 w - - 2 40"), + ("r1b1r1k1/pp4p1/4p2p/3pPP2/2p4P/P1PB4/2PB1P2/R3K2R b KQ - 0 17", vec!["c4d3", "c2d3", "g8h7", "e1g1", "d5d4"], + "r1b1r3/pp4pk/4p2p/4PP2/3p3P/P1PP4/3B1P2/R4RK1 w - - 0 20"), + ("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", vec!["c2c4", "d7d5", "c4c5", "b7b5"], + "rnbqkbnr/p1p1pppp/8/1pPp4/8/8/PP1PPPPP/RNBQKBNR w KQkq b6 0 3") + ]; + for (init_fen, moves, final_fen) in &test_cases{ + let init_board = Board::from_str(init_fen).unwrap(); + let final_board = moves.iter() + .map(|m| move_of(m)) + .fold(init_board, |b,m| b.make_move_new(m)); + let final_board_direct = Board::from_str(final_fen).unwrap(); + + assert_eq!(final_board.get_hash(), final_board_direct.get_hash(), + "final_board: {}, final_board_direct: {}", final_board, final_board_direct); + } +} \ No newline at end of file diff --git a/src/color.rs b/src/color.rs index a71298291..ed364083a 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,82 +1,82 @@ -use crate::rank::Rank; -use std::ops::Not; - -/// Represent a color. -#[derive(PartialOrd, PartialEq, Eq, Copy, Clone, Debug, Hash)] -pub enum Color { - White, - Black, -} - -/// How many colors are there? -pub const NUM_COLORS: usize = 2; -/// List all colors -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 - } - - /// Covert the `Color` to a rank, which reperesnts the starting position - /// for that colors pieces. - #[inline] - pub fn to_my_backrank(&self) -> Rank { - match *self { - Color::White => Rank::First, - Color::Black => Rank::Eighth, - } - } - - /// Convert a `Color` to my opponents backrank, which represents the starting position for the - /// opponents pieces. - #[inline] - pub fn to_their_backrank(&self) -> Rank { - match *self { - Color::White => Rank::Eighth, - Color::Black => Rank::First, - } - } - - /// Convert a `Color` to my second rank, which represents the starting position for my pawns. - #[inline] - pub fn to_second_rank(&self) -> Rank { - match *self { - Color::White => Rank::Second, - Color::Black => Rank::Seventh, - } - } - - #[inline] - pub fn to_fourth_rank(&self) -> Rank { - match *self { - Color::White => Rank::Fourth, - Color::Black => Rank::Fifth, - } - } - - /// Convert a `Color` to my seventh rank, which represents the rank before pawn promotion. - #[inline] - pub fn to_seventh_rank(&self) -> Rank { - match *self { - Color::White => Rank::Seventh, - Color::Black => Rank::Second, - } - } -} - -impl Not for Color { - type Output = Color; - - /// Get the other color. - #[inline] - fn not(self) -> Color { - if self == Color::White { - Color::Black - } else { - Color::White - } - } -} +use crate::rank::Rank; +use std::ops::Not; + +/// Represent a color. +#[derive(PartialOrd, PartialEq, Eq, Copy, Clone, Debug, Hash)] +pub enum Color { + White, + Black, +} + +/// How many colors are there? +pub const NUM_COLORS: usize = 2; +/// List all colors +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 + } + + /// Covert the `Color` to a rank, which reperesnts the starting position + /// for that colors pieces. + #[inline] + pub fn to_my_backrank(&self) -> Rank { + match *self { + Color::White => Rank::First, + Color::Black => Rank::Eighth, + } + } + + /// Convert a `Color` to my opponents backrank, which represents the starting position for the + /// opponents pieces. + #[inline] + pub fn to_their_backrank(&self) -> Rank { + match *self { + Color::White => Rank::Eighth, + Color::Black => Rank::First, + } + } + + /// Convert a `Color` to my second rank, which represents the starting position for my pawns. + #[inline] + pub fn to_second_rank(&self) -> Rank { + match *self { + Color::White => Rank::Second, + Color::Black => Rank::Seventh, + } + } + + #[inline] + pub fn to_fourth_rank(&self) -> Rank { + match *self { + Color::White => Rank::Fourth, + Color::Black => Rank::Fifth, + } + } + + /// Convert a `Color` to my seventh rank, which represents the rank before pawn promotion. + #[inline] + pub fn to_seventh_rank(&self) -> Rank { + match *self { + Color::White => Rank::Seventh, + Color::Black => Rank::Second, + } + } +} + +impl Not for Color { + type Output = Color; + + /// Get the other color. + #[inline] + fn not(self) -> Color { + if self == Color::White { + Color::Black + } else { + Color::White + } + } +} diff --git a/src/game.rs b/src/game.rs index b218f355e..c8e06feb6 100644 --- a/src/game.rs +++ b/src/game.rs @@ -35,8 +35,8 @@ pub enum GameResult { /// not recommended for engines. #[derive(Clone, Debug)] pub struct Game { - start_pos: Board, - moves: Vec, + pub start_pos: Board, + pub moves: Vec, } impl Game { diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index c2643dd30..f3f1655af 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -1,565 +1,572 @@ -use crate::bitboard::{BitBoard, EMPTY}; -use crate::board::Board; -use crate::chess_move::ChessMove; -use crate::magic::between; -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; - -#[derive(Copy, Clone, PartialEq, PartialOrd)] -pub struct SquareAndBitBoard { - square: Square, - bitboard: BitBoard, - promotion: bool, -} - -impl SquareAndBitBoard { - pub fn new(sq: Square, bb: BitBoard, promotion: bool) -> SquareAndBitBoard { - SquareAndBitBoard { - square: sq, - bitboard: bb, - promotion: promotion, - } - } -} - -pub type MoveList = NoDrop>; - -/// An incremental move generator -/// -/// This structure enumerates moves slightly slower than board.enumerate_moves(...), -/// but has some extra features, such as: -/// -/// * Being an iterator -/// * Not requiring you to create a buffer -/// * Only iterating moves that match a certain pattern -/// * Being iterable multiple times (such as, iterating once for all captures, then iterating again -/// for all quiets) -/// * Doing as little work early on as possible, so that if you are not going to look at every move, the -/// struture moves faster -/// * Being able to iterate pseudo legal moves, while keeping the (nearly) free legality checks in -/// place -/// -/// # Examples -/// -/// ``` -/// use chess::MoveGen; -/// use chess::Board; -/// use chess::EMPTY; -/// use chess::construct; -/// -/// // create a board with the initial position -/// let board = Board::default(); -/// -/// // create an iterable -/// let mut iterable = MoveGen::new_legal(&board); -/// -/// // make sure .len() works. -/// assert_eq!(iterable.len(), 20); // the .len() function does *not* consume the iterator -/// -/// // lets iterate over targets. -/// let targets = board.color_combined(!board.side_to_move()); -/// iterable.set_iterator_mask(*targets); -/// -/// // count the number of targets -/// let mut count = 0; -/// for _ in &mut iterable { -/// count += 1; -/// // This move captures one of my opponents pieces (with the exception of en passant) -/// } -/// -/// // now, iterate over the rest of the moves -/// iterable.set_iterator_mask(!EMPTY); -/// for _ in &mut iterable { -/// count += 1; -/// // This move does not capture anything -/// } -/// -/// // make sure it works -/// assert_eq!(count, 20); -/// -/// ``` -pub struct MoveGen { - moves: MoveList, - promotion_index: usize, - iterator_mask: BitBoard, - index: usize, -} - -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::<[SquareAndBitBoard; 18]>::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); - } - - movelist - } - - /// Create a new `MoveGen` structure, only generating legal moves - #[inline(always)] - pub fn new_legal(board: &Board) -> MoveGen { - MoveGen { - moves: MoveGen::enumerate_moves(board), - promotion_index: 0, - iterator_mask: !EMPTY, - index: 0, - } - } - - /// Never, ever, iterate any moves that land on the following squares - pub fn remove_mask(&mut self, mask: BitBoard) { - for x in 0..self.moves.len() { - self.moves[x].bitboard &= !mask; - } - } - - /// Never, ever, iterate this move - pub fn remove_move(&mut self, chess_move: ChessMove) -> bool { - for x in 0..self.moves.len() { - if self.moves[x].square == chess_move.get_source() { - self.moves[x].bitboard &= !BitBoard::from_square(chess_move.get_dest()); - return true; - } - } - false - } - - /// For now, Only iterate moves that land on the following squares - /// Note: Once iteration is completed, you can pass in a mask of ! `EMPTY` - /// to get the remaining moves, or another mask - pub fn set_iterator_mask(&mut self, mask: BitBoard) { - self.iterator_mask = mask; - self.index = 0; - - // the iterator portion of this struct relies on the invariant that - // the bitboards at the beginning of the moves[] array are the only - // ones used. As a result, we must partition the list such that the - // assumption is true. - - // first, find the first non-used moves index, and store that in i - let mut i = 0; - while i < self.moves.len() && self.moves[i].bitboard & self.iterator_mask != EMPTY { - i += 1; - } - - // next, find each element past i where the moves are used, and store - // that in i. Then, increment i to point to a new unused slot. - for j in (i + 1)..self.moves.len() { - if self.moves[j].bitboard & self.iterator_mask != EMPTY { - let backup = self.moves[i]; - self.moves[i] = self.moves[j]; - self.moves[j] = backup; - i += 1; - } - } - } - - /// This function checks the legality *only for moves generated by `MoveGen`*. - /// - /// Calling this function for moves not generated by `MoveGen` will result in possibly - /// incorrect results, and making that move on the `Board` will result in undefined behavior. - /// This function may panic! if these rules are not followed. - /// - /// If you are validating a move from a user, you should call the .legal() function. - pub fn legal_quick(board: &Board, chess_move: ChessMove) -> bool { - let piece = board.piece_on(chess_move.get_source()).unwrap(); - match piece { - Piece::Rook => true, - Piece::Bishop => true, - Piece::Knight => true, - Piece::Queen => true, - Piece::Pawn => { - if chess_move.get_source().get_file() != chess_move.get_dest().get_file() - && board.piece_on(chess_move.get_dest()).is_none() - { - // en-passant - PawnType::legal_ep_move(board, chess_move.get_source(), chess_move.get_dest()) - } else { - true - } - } - Piece::King => { - let bb = between(chess_move.get_source(), chess_move.get_dest()); - if bb.popcnt() == 1 { - // castles - if !KingType::legal_king_move(board, bb.to_square()) { - false - } else { - KingType::legal_king_move(board, chess_move.get_dest()) - } - } else { - KingType::legal_king_move(board, chess_move.get_dest()) - } - } - } - } - - /// 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 mut bresult = unsafe { mem::uninitialized() }; - board.make_move(m, &mut bresult); - let cur = MoveGen::movegen_perft_test(&bresult, depth - 1); - result += cur; - } - result - } - } - - #[cfg(test)] - /// Do a perft test after splitting the moves up into two groups - pub fn movegen_perft_test_piecewise(board: &Board, depth: usize) -> usize { - let mut iterable = MoveGen::new_legal(board); - - let targets = board.color_combined(!board.side_to_move()); - let mut result: usize = 0; - - if depth == 1 { - iterable.set_iterator_mask(*targets); - result += iterable.len(); - iterable.set_iterator_mask(!targets); - result += iterable.len(); - result - } else { - iterable.set_iterator_mask(*targets); - for x in &mut iterable { - let mut bresult = unsafe { mem::uninitialized() }; - board.make_move(x, &mut bresult); - result += MoveGen::movegen_perft_test_piecewise(&bresult, depth - 1); - } - iterable.set_iterator_mask(!EMPTY); - for x in &mut iterable { - let mut bresult = unsafe { mem::uninitialized() }; - board.make_move(x, &mut bresult); - result += MoveGen::movegen_perft_test_piecewise(&bresult, depth - 1); - } - result - } - } -} - -impl ExactSizeIterator for MoveGen { - /// Give the exact length of this iterator - fn len(&self) -> usize { - let mut result = 0; - for i in 0..self.moves.len() { - if self.moves[i].bitboard & self.iterator_mask == EMPTY { - break; - } - if self.moves[i].promotion { - result += ((self.moves[i].bitboard & self.iterator_mask).popcnt() as usize) - * NUM_PROMOTION_PIECES; - } else { - result += (self.moves[i].bitboard & self.iterator_mask).popcnt() as usize; - } - } - result - } -} - -impl Iterator for MoveGen { - type Item = ChessMove; - - /// Give a size_hint to some functions that need it - fn size_hint(&self) -> (usize, Option) { - let len = self.len(); - (len, Some(len)) - } - - /// Find the next chess move. - fn next(&mut self) -> Option { - if self.index >= self.moves.len() - || self.moves[self.index].bitboard & self.iterator_mask == EMPTY - { - // are we done? - None - } else if self.moves[self.index].promotion { - let moves = &mut self.moves[self.index]; - - let dest = (moves.bitboard & self.iterator_mask).to_square(); - - // deal with potential promotions for this pawn - let result = ChessMove::new( - moves.square, - dest, - Some(PROMOTION_PIECES[self.promotion_index]), - ); - self.promotion_index += 1; - if self.promotion_index >= NUM_PROMOTION_PIECES { - moves.bitboard ^= BitBoard::from_square(dest); - self.promotion_index = 0; - if moves.bitboard & self.iterator_mask == EMPTY { - self.index += 1; - } - } - Some(result) - } else { - // not a promotion move, so its a 'normal' move as far as this function is concerned - let moves = &mut self.moves[self.index]; - let dest = (moves.bitboard & self.iterator_mask).to_square(); - - moves.bitboard ^= BitBoard::from_square(dest); - if moves.bitboard & self.iterator_mask == EMPTY { - self.index += 1; - } - Some(ChessMove::new(moves.square, dest, None)) - } - } -} - -#[cfg(test)] -use crate::board_builder::BoardBuilder; -#[cfg(test)] -use std::collections::HashSet; -#[cfg(test)] -use std::convert::TryInto; -#[cfg(test)] -use std::str::FromStr; - -#[cfg(test)] -fn movegen_perft_test(fen: String, depth: usize, result: usize) { - let board: Board = BoardBuilder::from_str(&fen).unwrap().try_into().unwrap(); - - assert_eq!(MoveGen::movegen_perft_test(&board, depth), result); - assert_eq!(MoveGen::movegen_perft_test_piecewise(&board, depth), result); -} - -#[test] -fn movegen_perft_kiwipete() { - movegen_perft_test( - "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1".to_owned(), - 5, - 193690690, - ); -} - -#[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 -} - -#[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 -} - -#[test] -fn movegen_perft_3() { - movegen_perft_test("8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1".to_owned(), 6, 1440467); -} - -#[test] -fn movegen_perft_4() { - movegen_perft_test("8/5k2/8/2Pp4/2B5/1K6/8/8 w - d6 0 1".to_owned(), 6, 1440467); -} - -#[test] -fn movegen_perft_5() { - movegen_perft_test("5k2/8/8/8/8/8/8/4K2R w K - 0 1".to_owned(), 6, 661072); -} - -#[test] -fn movegen_perft_6() { - movegen_perft_test("4k2r/8/8/8/8/8/8/5K2 b k - 0 1".to_owned(), 6, 661072); -} - -#[test] -fn movegen_perft_7() { - movegen_perft_test("3k4/8/8/8/8/8/8/R3K3 w Q - 0 1".to_owned(), 6, 803711); -} - -#[test] -fn movegen_perft_8() { - movegen_perft_test("r3k3/8/8/8/8/8/8/3K4 b q - 0 1".to_owned(), 6, 803711); -} - -#[test] -fn movegen_perft_9() { - movegen_perft_test( - "r3k2r/1b4bq/8/8/8/8/7B/R3K2R w KQkq - 0 1".to_owned(), - 4, - 1274206, - ); -} - -#[test] -fn movegen_perft_10() { - movegen_perft_test( - "r3k2r/7b/8/8/8/8/1B4BQ/R3K2R b KQkq - 0 1".to_owned(), - 4, - 1274206, - ); -} - -#[test] -fn movegen_perft_11() { - movegen_perft_test( - "r3k2r/8/3Q4/8/8/5q2/8/R3K2R b KQkq - 0 1".to_owned(), - 4, - 1720476, - ); -} - -#[test] -fn movegen_perft_12() { - movegen_perft_test( - "r3k2r/8/5Q2/8/8/3q4/8/R3K2R w KQkq - 0 1".to_owned(), - 4, - 1720476, - ); -} - -#[test] -fn movegen_perft_13() { - movegen_perft_test("2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1".to_owned(), 6, 3821001); -} - -#[test] -fn movegen_perft_14() { - movegen_perft_test("3K4/8/8/8/8/8/4p3/2k2R2 b - - 0 1".to_owned(), 6, 3821001); -} - -#[test] -fn movegen_perft_15() { - movegen_perft_test("8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1".to_owned(), 5, 1004658); -} - -#[test] -fn movegen_perft_16() { - movegen_perft_test("5K2/8/1Q6/2N5/8/1p2k3/8/8 w - - 0 1".to_owned(), 5, 1004658); -} - -#[test] -fn movegen_perft_17() { - movegen_perft_test("4k3/1P6/8/8/8/8/K7/8 w - - 0 1".to_owned(), 6, 217342); -} - -#[test] -fn movegen_perft_18() { - movegen_perft_test("8/k7/8/8/8/8/1p6/4K3 b - - 0 1".to_owned(), 6, 217342); -} - -#[test] -fn movegen_perft_19() { - movegen_perft_test("8/P1k5/K7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 92683); -} - -#[test] -fn movegen_perft_20() { - movegen_perft_test("8/8/8/8/8/k7/p1K5/8 b - - 0 1".to_owned(), 6, 92683); -} - -#[test] -fn movegen_perft_21() { - movegen_perft_test("K1k5/8/P7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 2217); -} - -#[test] -fn movegen_perft_22() { - movegen_perft_test("8/8/8/8/8/p7/8/k1K5 b - - 0 1".to_owned(), 6, 2217); -} - -#[test] -fn movegen_perft_23() { - movegen_perft_test("8/k1P5/8/1K6/8/8/8/8 w - - 0 1".to_owned(), 7, 567584); -} - -#[test] -fn movegen_perft_24() { - movegen_perft_test("8/8/8/8/1k6/8/K1p5/8 b - - 0 1".to_owned(), 7, 567584); -} - -#[test] -fn movegen_perft_25() { - movegen_perft_test("8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1".to_owned(), 4, 23527); -} - -#[test] -fn movegen_perft_26() { - movegen_perft_test("8/5k2/8/5N2/5Q2/2K5/8/8 w - - 0 1".to_owned(), 4, 23527); -} - -#[test] -fn movegen_issue_15() { - let board = - BoardBuilder::from_str("rnbqkbnr/ppp2pp1/4p3/3N4/3PpPp1/8/PPP3PP/R1B1KBNR b KQkq f3 0 1") - .unwrap() - .try_into() - .unwrap(); - let _ = MoveGen::new_legal(&board); -} - -#[cfg(test)] -fn move_of(m: &str) -> ChessMove { - let promo = if m.len() > 4 { - Some(match m.as_bytes()[4] { - b'q' => Piece::Queen, - b'r' => Piece::Rook, - b'b' => Piece::Bishop, - b'n' => Piece::Knight, - _ => panic!("unrecognized uci move: {}", m), - }) - } else { - None - }; - ChessMove::new( - Square::from_string(m[..2].to_string()).unwrap(), - Square::from_string(m[2..4].to_string()).unwrap(), - promo, - ) -} - -#[test] -fn test_masked_move_gen() { - let board = - Board::from_str("r1bqkb1r/pp3ppp/5n2/2ppn1N1/4pP2/1BN1P3/PPPP2PP/R1BQ1RK1 w kq - 0 9") - .unwrap(); - - let mut capture_moves = MoveGen::new_legal(&board); - let targets = *board.color_combined(!board.side_to_move()); - capture_moves.set_iterator_mask(targets); - - let expected = vec![ - move_of("f4e5"), - move_of("b3d5"), - move_of("g5e4"), - move_of("g5f7"), - move_of("g5h7"), - move_of("c3e4"), - move_of("c3d5"), - ]; - - assert_eq!( - capture_moves.collect::>(), - expected.into_iter().collect() - ); -} +use crate::bitboard::{BitBoard, EMPTY}; +use crate::board::Board; +use crate::chess_move::ChessMove; +use crate::magic::between; +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; + +#[derive(Copy, Clone, PartialEq, PartialOrd)] +pub struct SquareAndBitBoard { + square: Square, + bitboard: BitBoard, + promotion: bool, +} + +impl SquareAndBitBoard { + pub fn new(sq: Square, bb: BitBoard, promotion: bool) -> SquareAndBitBoard { + SquareAndBitBoard { + square: sq, + bitboard: bb, + promotion: promotion, + } + } +} + +pub type MoveList = NoDrop>; + +/// An incremental move generator +/// +/// This structure enumerates moves slightly slower than board.enumerate_moves(...), +/// but has some extra features, such as: +/// +/// * Being an iterator +/// * Not requiring you to create a buffer +/// * Only iterating moves that match a certain pattern +/// * Being iterable multiple times (such as, iterating once for all captures, then iterating again +/// for all quiets) +/// * Doing as little work early on as possible, so that if you are not going to look at every move, the +/// struture moves faster +/// * Being able to iterate pseudo legal moves, while keeping the (nearly) free legality checks in +/// place +/// +/// # Examples +/// +/// ``` +/// use chess::MoveGen; +/// use chess::Board; +/// use chess::EMPTY; +/// use chess::construct; +/// +/// // create a board with the initial position +/// let board = Board::default(); +/// +/// // create an iterable +/// let mut iterable = MoveGen::new_legal(&board); +/// +/// // make sure .len() works. +/// assert_eq!(iterable.len(), 20); // the .len() function does *not* consume the iterator +/// +/// // lets iterate over targets. +/// let targets = board.color_combined(!board.side_to_move()); +/// iterable.set_iterator_mask(*targets); +/// +/// // count the number of targets +/// let mut count = 0; +/// for _ in &mut iterable { +/// count += 1; +/// // This move captures one of my opponents pieces (with the exception of en passant) +/// } +/// +/// // now, iterate over the rest of the moves +/// iterable.set_iterator_mask(!EMPTY); +/// for _ in &mut iterable { +/// count += 1; +/// // This move does not capture anything +/// } +/// +/// // make sure it works +/// assert_eq!(count, 20); +/// +/// ``` +pub struct MoveGen { + moves: MoveList, + promotion_index: usize, + iterator_mask: BitBoard, + index: usize, +} + +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::<[SquareAndBitBoard; 18]>::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); + } + + movelist + } + + /// Create a new `MoveGen` structure, only generating legal moves + #[inline(always)] + pub fn new_legal(board: &Board) -> MoveGen { + MoveGen { + moves: MoveGen::enumerate_moves(board), + promotion_index: 0, + iterator_mask: !EMPTY, + index: 0, + } + } + + /// Never, ever, iterate any moves that land on the following squares + pub fn remove_mask(&mut self, mask: BitBoard) { + for x in 0..self.moves.len() { + self.moves[x].bitboard &= !mask; + } + } + + /// Never, ever, iterate this move + /// If this move was to be iterated, returns true; otherwise returns false. + pub fn remove_move(&mut self, chess_move: ChessMove) -> bool { + for x in 0..self.moves.len() { + if self.moves[x].square == chess_move.get_source() { + let dest_bb = BitBoard::from_square(chess_move.get_dest()); + if self.moves[x].bitboard & dest_bb != EMPTY { + self.moves[x].bitboard &= !dest_bb; + return true; + } else { + return false; + } + } + } + false + } + + /// For now, Only iterate moves that land on the following squares + /// Note: Once iteration is completed, you can pass in a mask of ! `EMPTY` + /// to get the remaining moves, or another mask + pub fn set_iterator_mask(&mut self, mask: BitBoard) { + self.iterator_mask = mask; + self.index = 0; + + // the iterator portion of this struct relies on the invariant that + // the bitboards at the beginning of the moves[] array are the only + // ones used. As a result, we must partition the list such that the + // assumption is true. + + // first, find the first non-used moves index, and store that in i + let mut i = 0; + while i < self.moves.len() && self.moves[i].bitboard & self.iterator_mask != EMPTY { + i += 1; + } + + // next, find each element past i where the moves are used, and store + // that in i. Then, increment i to point to a new unused slot. + for j in (i + 1)..self.moves.len() { + if self.moves[j].bitboard & self.iterator_mask != EMPTY { + // self.moves.swap(i, j); + let backup = self.moves[i]; + self.moves[i] = self.moves[j]; + self.moves[j] = backup; + i += 1; + } + } + } + + /// This function checks the legality *only for moves generated by `MoveGen`*. + /// + /// Calling this function for moves not generated by `MoveGen` will result in possibly + /// incorrect results, and making that move on the `Board` will result in undefined behavior. + /// This function may panic! if these rules are not followed. + /// + /// If you are validating a move from a user, you should call the .legal() function. + pub fn legal_quick(board: &Board, chess_move: ChessMove) -> bool { + let piece = board.piece_on(chess_move.get_source()).unwrap(); + match piece { + Piece::Rook => true, + Piece::Bishop => true, + Piece::Knight => true, + Piece::Queen => true, + Piece::Pawn => { + if chess_move.get_source().get_file() != chess_move.get_dest().get_file() + && board.piece_on(chess_move.get_dest()).is_none() + { + // en-passant + PawnType::legal_ep_move(board, chess_move.get_source(), chess_move.get_dest()) + } else { + true + } + } + Piece::King => { + let bb = between(chess_move.get_source(), chess_move.get_dest()); + if bb.popcnt() == 1 { + // castles + if !KingType::legal_king_move(board, bb.to_square()) { + false + } else { + KingType::legal_king_move(board, chess_move.get_dest()) + } + } else { + KingType::legal_king_move(board, chess_move.get_dest()) + } + } + } + } + + /// 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 mut bresult = unsafe { mem::uninitialized() }; + board.make_move(m, &mut bresult); + let cur = MoveGen::movegen_perft_test(&bresult, depth - 1); + result += cur; + } + result + } + } + + #[cfg(test)] + /// Do a perft test after splitting the moves up into two groups + pub fn movegen_perft_test_piecewise(board: &Board, depth: usize) -> usize { + let mut iterable = MoveGen::new_legal(board); + + let targets = board.color_combined(!board.side_to_move()); + let mut result: usize = 0; + + if depth == 1 { + iterable.set_iterator_mask(*targets); + result += iterable.len(); + iterable.set_iterator_mask(!targets); + result += iterable.len(); + result + } else { + iterable.set_iterator_mask(*targets); + for x in &mut iterable { + let mut bresult = unsafe { mem::uninitialized() }; + board.make_move(x, &mut bresult); + result += MoveGen::movegen_perft_test_piecewise(&bresult, depth - 1); + } + iterable.set_iterator_mask(!EMPTY); + for x in &mut iterable { + let mut bresult = unsafe { mem::uninitialized() }; + board.make_move(x, &mut bresult); + result += MoveGen::movegen_perft_test_piecewise(&bresult, depth - 1); + } + result + } + } +} + +impl ExactSizeIterator for MoveGen { + /// Give the exact length of this iterator + fn len(&self) -> usize { + let mut result = 0; + for i in 0..self.moves.len() { + if self.moves[i].bitboard & self.iterator_mask == EMPTY { + break; + } + if self.moves[i].promotion { + result += ((self.moves[i].bitboard & self.iterator_mask).popcnt() as usize) + * NUM_PROMOTION_PIECES; + } else { + result += (self.moves[i].bitboard & self.iterator_mask).popcnt() as usize; + } + } + result + } +} + +impl Iterator for MoveGen { + type Item = ChessMove; + + /// Give a size_hint to some functions that need it + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } + + /// Find the next chess move. + fn next(&mut self) -> Option { + if self.index >= self.moves.len() + || self.moves[self.index].bitboard & self.iterator_mask == EMPTY + { + // are we done? + None + } else if self.moves[self.index].promotion { + let moves = &mut self.moves[self.index]; + + let dest = (moves.bitboard & self.iterator_mask).to_square(); + + // deal with potential promotions for this pawn + let result = ChessMove::new( + moves.square, + dest, + Some(PROMOTION_PIECES[self.promotion_index]), + ); + self.promotion_index += 1; + if self.promotion_index >= NUM_PROMOTION_PIECES { + moves.bitboard ^= BitBoard::from_square(dest); + self.promotion_index = 0; + if moves.bitboard & self.iterator_mask == EMPTY { + self.index += 1; + } + } + Some(result) + } else { + // not a promotion move, so its a 'normal' move as far as this function is concerned + let moves = &mut self.moves[self.index]; + let dest = (moves.bitboard & self.iterator_mask).to_square(); + + moves.bitboard ^= BitBoard::from_square(dest); + if moves.bitboard & self.iterator_mask == EMPTY { + self.index += 1; + } + Some(ChessMove::new(moves.square, dest, None)) + } + } +} + +#[cfg(test)] +use crate::board_builder::BoardBuilder; +#[cfg(test)] +use std::collections::HashSet; +#[cfg(test)] +use std::convert::TryInto; +#[cfg(test)] +use std::str::FromStr; + +#[cfg(test)] +fn movegen_perft_test(fen: String, depth: usize, result: usize) { + let board: Board = BoardBuilder::from_str(&fen).unwrap().try_into().unwrap(); + + assert_eq!(MoveGen::movegen_perft_test(&board, depth), result); + assert_eq!(MoveGen::movegen_perft_test_piecewise(&board, depth), result); +} + +#[test] +fn movegen_perft_kiwipete() { + movegen_perft_test( + "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1".to_owned(), + 5, + 193690690, + ); +} + +#[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 +} + +#[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 +} + +#[test] +fn movegen_perft_3() { + movegen_perft_test("8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1".to_owned(), 6, 1440467); +} + +#[test] +fn movegen_perft_4() { + movegen_perft_test("8/5k2/8/2Pp4/2B5/1K6/8/8 w - d6 0 1".to_owned(), 6, 1440467); +} + +#[test] +fn movegen_perft_5() { + movegen_perft_test("5k2/8/8/8/8/8/8/4K2R w K - 0 1".to_owned(), 6, 661072); +} + +#[test] +fn movegen_perft_6() { + movegen_perft_test("4k2r/8/8/8/8/8/8/5K2 b k - 0 1".to_owned(), 6, 661072); +} + +#[test] +fn movegen_perft_7() { + movegen_perft_test("3k4/8/8/8/8/8/8/R3K3 w Q - 0 1".to_owned(), 6, 803711); +} + +#[test] +fn movegen_perft_8() { + movegen_perft_test("r3k3/8/8/8/8/8/8/3K4 b q - 0 1".to_owned(), 6, 803711); +} + +#[test] +fn movegen_perft_9() { + movegen_perft_test( + "r3k2r/1b4bq/8/8/8/8/7B/R3K2R w KQkq - 0 1".to_owned(), + 4, + 1274206, + ); +} + +#[test] +fn movegen_perft_10() { + movegen_perft_test( + "r3k2r/7b/8/8/8/8/1B4BQ/R3K2R b KQkq - 0 1".to_owned(), + 4, + 1274206, + ); +} + +#[test] +fn movegen_perft_11() { + movegen_perft_test( + "r3k2r/8/3Q4/8/8/5q2/8/R3K2R b KQkq - 0 1".to_owned(), + 4, + 1720476, + ); +} + +#[test] +fn movegen_perft_12() { + movegen_perft_test( + "r3k2r/8/5Q2/8/8/3q4/8/R3K2R w KQkq - 0 1".to_owned(), + 4, + 1720476, + ); +} + +#[test] +fn movegen_perft_13() { + movegen_perft_test("2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1".to_owned(), 6, 3821001); +} + +#[test] +fn movegen_perft_14() { + movegen_perft_test("3K4/8/8/8/8/8/4p3/2k2R2 b - - 0 1".to_owned(), 6, 3821001); +} + +#[test] +fn movegen_perft_15() { + movegen_perft_test("8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1".to_owned(), 5, 1004658); +} + +#[test] +fn movegen_perft_16() { + movegen_perft_test("5K2/8/1Q6/2N5/8/1p2k3/8/8 w - - 0 1".to_owned(), 5, 1004658); +} + +#[test] +fn movegen_perft_17() { + movegen_perft_test("4k3/1P6/8/8/8/8/K7/8 w - - 0 1".to_owned(), 6, 217342); +} + +#[test] +fn movegen_perft_18() { + movegen_perft_test("8/k7/8/8/8/8/1p6/4K3 b - - 0 1".to_owned(), 6, 217342); +} + +#[test] +fn movegen_perft_19() { + movegen_perft_test("8/P1k5/K7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 92683); +} + +#[test] +fn movegen_perft_20() { + movegen_perft_test("8/8/8/8/8/k7/p1K5/8 b - - 0 1".to_owned(), 6, 92683); +} + +#[test] +fn movegen_perft_21() { + movegen_perft_test("K1k5/8/P7/8/8/8/8/8 w - - 0 1".to_owned(), 6, 2217); +} + +#[test] +fn movegen_perft_22() { + movegen_perft_test("8/8/8/8/8/p7/8/k1K5 b - - 0 1".to_owned(), 6, 2217); +} + +#[test] +fn movegen_perft_23() { + movegen_perft_test("8/k1P5/8/1K6/8/8/8/8 w - - 0 1".to_owned(), 7, 567584); +} + +#[test] +fn movegen_perft_24() { + movegen_perft_test("8/8/8/8/1k6/8/K1p5/8 b - - 0 1".to_owned(), 7, 567584); +} + +#[test] +fn movegen_perft_25() { + movegen_perft_test("8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1".to_owned(), 4, 23527); +} + +#[test] +fn movegen_perft_26() { + movegen_perft_test("8/5k2/8/5N2/5Q2/2K5/8/8 w - - 0 1".to_owned(), 4, 23527); +} + +#[test] +fn movegen_issue_15() { + let board = + BoardBuilder::from_str("rnbqkbnr/ppp2pp1/4p3/3N4/3PpPp1/8/PPP3PP/R1B1KBNR b KQkq f3 0 1") + .unwrap() + .try_into() + .unwrap(); + let _ = MoveGen::new_legal(&board); +} + +#[cfg(test)] +fn move_of(m: &str) -> ChessMove { + let promo = if m.len() > 4 { + Some(match m.as_bytes()[4] { + b'q' => Piece::Queen, + b'r' => Piece::Rook, + b'b' => Piece::Bishop, + b'n' => Piece::Knight, + _ => panic!("unrecognized uci move: {}", m), + }) + } else { + None + }; + ChessMove::new( + Square::from_string(m[..2].to_string()).unwrap(), + Square::from_string(m[2..4].to_string()).unwrap(), + promo, + ) +} + +#[test] +fn test_masked_move_gen() { + let board = + Board::from_str("r1bqkb1r/pp3ppp/5n2/2ppn1N1/4pP2/1BN1P3/PPPP2PP/R1BQ1RK1 w kq - 0 9") + .unwrap(); + + let mut capture_moves = MoveGen::new_legal(&board); + let targets = *board.color_combined(!board.side_to_move()); + capture_moves.set_iterator_mask(targets); + + let expected = vec![ + move_of("f4e5"), + move_of("b3d5"), + move_of("g5e4"), + move_of("g5f7"), + move_of("g5h7"), + move_of("c3e4"), + move_of("c3d5"), + ]; + + assert_eq!( + capture_moves.collect::>(), + expected.into_iter().collect() + ); +} diff --git a/src/rank.rs b/src/rank.rs index c4320f05b..1bbb4779c 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -1,56 +1,56 @@ -use std::mem::transmute; - -/// Describe a rank (row) on a chess board -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Hash)] -pub enum Rank { - First, - Second, - Third, - Fourth, - Fifth, - Sixth, - Seventh, - Eighth, -} - -/// How many ranks are there? -pub const NUM_RANKS: usize = 8; - -/// Enumerate all ranks -pub const ALL_RANKS: [Rank; NUM_RANKS] = [ - Rank::First, - Rank::Second, - Rank::Third, - Rank::Fourth, - Rank::Fifth, - Rank::Sixth, - Rank::Seventh, - Rank::Eighth, -]; - -impl Rank { - /// Convert a `usize` into a `Rank` (the inverse of to_index). If the number is > 7, wrap - /// around. - #[inline] - pub fn from_index(i: usize) -> Rank { - unsafe { transmute((i as u8) & 7) } - } - - /// Go one rank down. If impossible, wrap around. - #[inline] - pub fn down(&self) -> Rank { - Rank::from_index(self.to_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) - } - - /// Convert this `Rank` into a `usize` between 0 and 7 (inclusive). - #[inline] - pub fn to_index(&self) -> usize { - *self as usize - } -} +use std::mem::transmute; + +/// Describe a rank (row) on a chess board +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Hash)] +pub enum Rank { + First, + Second, + Third, + Fourth, + Fifth, + Sixth, + Seventh, + Eighth, +} + +/// How many ranks are there? +pub const NUM_RANKS: usize = 8; + +/// Enumerate all ranks +pub const ALL_RANKS: [Rank; NUM_RANKS] = [ + Rank::First, + Rank::Second, + Rank::Third, + Rank::Fourth, + Rank::Fifth, + Rank::Sixth, + Rank::Seventh, + Rank::Eighth, +]; + +impl Rank { + /// Convert a `usize` into a `Rank` (the inverse of to_index). If the number is > 7, wrap + /// around. + #[inline] + pub fn from_index(i: usize) -> Rank { + unsafe { transmute((i as u8) & 7) } + } + + /// Go one rank down. If impossible, wrap around. + #[inline] + pub fn down(&self) -> Rank { + Rank::from_index(self.to_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) + } + + /// Convert this `Rank` into a `usize` between 0 and 7 (inclusive). + #[inline] + pub fn to_index(&self) -> usize { + *self as usize + } +} From 67b2e70f5a02a299dc3f03e8faba55da26406db3 Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Wed, 9 Dec 2020 16:29:32 -0500 Subject: [PATCH 02/10] updates for new compiler + tweaks --- Cargo.toml | 66 ++++++++++++++++++++++++++-------------------------- src/board.rs | 2 +- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 95a8cbe9d..f9fe6bff2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,33 +1,33 @@ -[package] -name = "chess" -version = "3.1.1" -edition = "2018" -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 tabels with a build.rs file, which means that very little pseudo-legal move generation requires branching. There are some convenience functions that are exposed to, for example, find all the squares between two squares. This uses a copy-on-make style structure, and the Board structure is as slimmed down as possible to reduce the cost of copying the board. There are places to improve perft-test performance further, but I instead opt to be more feature-complete to make it useful in real applications. For example, I generate both a hash of the board and a pawn-hash of the board for use in evaluation lookup tables (using Zobrist hashing). There are two ways to generate moves, one is faster, the other has more features that will be useful if making a chess engine. See the documentation for more details." -build = "src/build.rs" - -homepage = "https://github.com/jordanbray/chess" -repository = "https://github.com/jordanbray/chess" -readme = "README.md" -keywords = ["chess", "move", "generator"] -license = "MIT" -documentation = "https://jordanbray.github.io/chess/chess/index.html" - -[dependencies] -arrayvec = "0.4.10" -nodrop = "0.1.13" -failure = "0.1.5" - -[profile.release] -opt-level = 3 -debug = false - -[profile.dev] -opt-level = 3 -debug = true - -[profile.test] -opt-level = 3 - -[build-dependencies] -rand = "0.4.2" +[package] +name = "chess" +version = "3.1.1" +edition = "2018" +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 tabels with a build.rs file, which means that very little pseudo-legal move generation requires branching. There are some convenience functions that are exposed to, for example, find all the squares between two squares. This uses a copy-on-make style structure, and the Board structure is as slimmed down as possible to reduce the cost of copying the board. There are places to improve perft-test performance further, but I instead opt to be more feature-complete to make it useful in real applications. For example, I generate both a hash of the board and a pawn-hash of the board for use in evaluation lookup tables (using Zobrist hashing). There are two ways to generate moves, one is faster, the other has more features that will be useful if making a chess engine. See the documentation for more details." +build = "src/build.rs" + +homepage = "https://github.com/jordanbray/chess" +repository = "https://github.com/jordanbray/chess" +readme = "README.md" +keywords = ["chess", "move", "generator"] +license = "MIT" +documentation = "https://jordanbray.github.io/chess/chess/index.html" + +[dependencies] +arrayvec = "0.5.2" +nodrop = "0.1.14" +failure = "0.1.8" + +[profile.release] +opt-level = 3 +debug = false + +[profile.dev] +opt-level = 3 +debug = true + +[profile.test] +opt-level = 3 + +[build-dependencies] +rand = "0.4.2" diff --git a/src/board.rs b/src/board.rs index 4ef8a017c..07473cbd0 100644 --- a/src/board.rs +++ b/src/board.rs @@ -854,7 +854,7 @@ impl Board { /// ``` #[inline] pub fn make_move_new(&self, m: ChessMove) -> Board { - let mut result = unsafe { mem::uninitialized() }; + let mut result = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; self.make_move(m, &mut result); result } From c8b7b87c2bfdcea5b6abe4765e08664f3fbc09af Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Sun, 17 Jan 2021 17:39:06 -0500 Subject: [PATCH 03/10] fixed a bug in remove_move --- src/movegen/movegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index f3f1655af..8de0feb9f 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -146,7 +146,7 @@ impl MoveGen { self.moves[x].bitboard &= !dest_bb; return true; } else { - return false; + //return false; } } } From a0349a6d4faaaa9c8bdfa6cd9d47e24655d40346 Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Fri, 21 May 2021 00:48:03 -0400 Subject: [PATCH 04/10] constifying things --- src/bitboard.rs | 10 ++++++++-- src/file.rs | 2 +- src/lib.rs | 2 +- src/magic.rs | 4 ++++ src/rank.rs | 19 ++++++++++++++++++- src/square.rs | 10 +++++----- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index 5537b6129..ad9047a7a 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -83,6 +83,12 @@ impl BitOr for BitBoard { BitBoard(self.0 | other.0) } } +impl BitBoard { + #[inline] + pub const fn bit_or(self, other: BitBoard) -> BitBoard { + BitBoard(self.0 | other.0) + } +} impl BitOr for &BitBoard { type Output = BitBoard; @@ -283,8 +289,8 @@ impl BitBoard { } /// 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()) } diff --git a/src/file.rs b/src/file.rs index 0116ebbf9..e6b3c3c15 100644 --- a/src/file.rs +++ b/src/file.rs @@ -51,7 +51,7 @@ impl File { /// Convert this `File` into a `usize` from 0 to 7 inclusive. #[inline] - pub fn to_index(&self) -> usize { + pub const fn to_index(&self) -> usize { *self as usize } } diff --git a/src/lib.rs b/src/lib.rs index a9da30c57..23bcc602d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ pub use crate::file::*; mod magic; pub use crate::magic::{ - between, get_adjacent_files, get_bishop_moves, get_bishop_rays, get_file, get_king_moves, + between, between_const, get_adjacent_files, get_bishop_moves, get_bishop_rays, get_file, get_king_moves, get_knight_moves, get_pawn_attacks, get_pawn_moves, get_pawn_quiets, get_rank, get_rook_moves, get_rook_rays, line, EDGES, }; diff --git a/src/magic.rs b/src/magic.rs index 2b279924c..720364cb8 100644 --- a/src/magic.rs +++ b/src/magic.rs @@ -153,6 +153,10 @@ pub fn between(sq1: Square, sq2: Square) -> BitBoard { .get_unchecked(sq2.to_index()) } } +#[inline] +pub const fn between_const(sq1: Square, sq2: Square) -> BitBoard { + BETWEEN[sq1.to_index()][sq2.to_index()] +} /// Get a `BitBoard` that represents all the squares on a particular rank. #[inline] diff --git a/src/rank.rs b/src/rank.rs index 4581fbf32..dd7a06855 100644 --- a/src/rank.rs +++ b/src/rank.rs @@ -38,6 +38,23 @@ impl Rank { unsafe { transmute((i as u8) & 7) } } + #[inline] + pub const fn from_index_const(i: usize) -> Rank { + match i & 7 { + 0 => Rank::First, + 1 => Rank::Second, + 2 => Rank::Third, + 3 => Rank::Fourth, + 4 => Rank::Fifth, + 5 => Rank::Sixth, + 6 => Rank::Seventh, + 7 => Rank::Eighth, + // unreachable: + _ => Rank::First, + } + // unsafe { transmute((i as u8) & 7) } + } + /// Go one rank down. If impossible, wrap around. #[inline] pub fn down(&self) -> Rank { @@ -52,7 +69,7 @@ impl Rank { /// Convert this `Rank` into a `usize` between 0 and 7 (inclusive). #[inline] - pub fn to_index(&self) -> usize { + pub const fn to_index(&self) -> usize { *self as usize } } diff --git a/src/square.rs b/src/square.rs index 218f3fa24..29e0f8c95 100644 --- a/src/square.rs +++ b/src/square.rs @@ -7,7 +7,7 @@ use std::str::FromStr; /// Represent a square on the chess board #[derive(PartialEq, Ord, Eq, PartialOrd, Copy, Clone, Debug, Hash)] -pub struct Square(u8); +pub struct Square(pub u8); /// How many squares are there? pub const NUM_SQUARES: usize = 64; @@ -46,7 +46,7 @@ impl Square { /// } /// ``` #[inline] - pub unsafe fn new(sq: u8) -> Square { + pub const unsafe fn new(sq: u8) -> Square { Square(sq) } @@ -68,7 +68,7 @@ impl Square { /// } /// ``` #[inline] - pub fn make_square(rank: Rank, file: File) -> Square { + pub const fn make_square(rank: Rank, file: File) -> Square { Square((rank.to_index() as u8) << 3 ^ (file.to_index() as u8)) } @@ -353,7 +353,7 @@ impl Square { /// assert_eq!(Square::make_square(Rank::Eighth, File::H).to_int(), 63); /// ``` #[inline] - pub fn to_int(&self) -> u8 { + pub const fn to_int(&self) -> u8 { self.0 } @@ -368,7 +368,7 @@ impl Square { /// assert_eq!(Square::make_square(Rank::Eighth, File::H).to_index(), 63); /// ``` #[inline] - pub fn to_index(&self) -> usize { + pub const fn to_index(&self) -> usize { self.0 as usize } From fd7009a24ba6a878afd9a7bb89d53c23e5dae6ad Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Sat, 25 Sep 2021 21:20:11 -0400 Subject: [PATCH 05/10] experimental impl of `unmake_move` --- src/board.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/board.rs b/src/board.rs index 62f3eceae..47dc43bcd 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1000,6 +1000,21 @@ impl Board { result.side_to_move = !result.side_to_move; } + + /// EXPERIMENTAL CODE + pub fn unmake_move(&mut self, m: ChessMovePlus) { + let moved = self.piece_on(m.dest).unwrap(); + self.xor(moved, BitBoard::from_square(m.dest), !self.side_to_move); + self.xor(moved, BitBoard::from_square(m.source), !self.side_to_move); + if let Some(captured_piece) = m.captured_piece { + self.xor(captured_piece, BitBoard::from_square(m.dest), self.side_to_move); + } + self.side_to_move = !self.side_to_move; + self.en_passant = m.pre_move_en_passant_square; + self.update_pin_info(); + } + + /// Update the pin information. fn update_pin_info(&mut self) { self.pinned = EMPTY; @@ -1161,4 +1176,35 @@ fn test_hash_behavior() { assert_eq!(final_board.get_hash(), final_board_direct.get_hash(), "final_board: {}, final_board_direct: {}", final_board, final_board_direct); } +} + +/// EXPERIMENTAL CODE +pub struct ChessMovePlus { + pub source: Square, + pub dest: Square, + pub promotion: Option, + pub captured_piece: Option, + pub is_en_passant: bool, + pub pre_move_en_passant_square: Option +} + +impl ChessMovePlus { + #[inline(always)] + pub fn is_en_passant(m: ChessMove, board: &Board) -> bool{ + if let Some(ep) = board.en_passant(){ + let ep_target_sq = ep.backward(!board.side_to_move()).unwrap(); + ep_target_sq == m.get_dest() && BitBoard::from_square(m.get_source()) & board.pieces(Piece::Pawn) != EMPTY + } else {false} + } + + pub fn new(m: ChessMove, board: &Board) -> Self { + ChessMovePlus{ + source: m.get_source(), + dest: m.get_dest(), + promotion: m.get_promotion(), + captured_piece: board.piece_on(m.get_dest()), + is_en_passant: Self::is_en_passant(m, board), + pre_move_en_passant_square: board.en_passant + } + } } \ No newline at end of file From fd2b2457116a8dba395d93de105aa2d721468c7e Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Wed, 1 Dec 2021 00:09:39 -0500 Subject: [PATCH 06/10] fixing `PartialOrd` impl for `CastleRights` --- src/castle_rights.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 14674ea80..1c475680e 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -8,7 +8,7 @@ use crate::square::Square; use crate::magic::{KINGSIDE_CASTLE_SQUARES, QUEENSIDE_CASTLE_SQUARES}; /// What castle rights does a particular player have? -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum CastleRights { NoRights, KingSide, @@ -16,6 +16,17 @@ pub enum CastleRights { Both, } +impl PartialOrd for CastleRights { + fn partial_cmp(&self, other: &Self) -> Option { + let (self_index, other_index) = (self.to_index(), other.to_index()); + if self_index * other_index == 2 { // if one is KingSide and the other is QueenSide + None + } else { + self_index.partial_cmp(&other_index) + } + } +} + /// How many different types of `CastleRights` are there? pub const NUM_CASTLE_RIGHTS: usize = 4; From 5fab809d5b039e015f8e345ae891a9cbdd2f0614 Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Fri, 18 Feb 2022 22:12:26 -0500 Subject: [PATCH 07/10] constifying more things updating dependencies --- Cargo.toml | 8 ++++---- src/lib.rs | 2 +- src/magic.rs | 6 ++++++ src/movegen/movegen.rs | 46 +++++++++++++++++++++--------------------- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 264f1344d..28dc3db9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,9 @@ license = "MIT" documentation = "https://jordanbray.github.io/chess/chess/index.html" [dependencies] -arrayvec = "0.5.2" +arrayvec = "0.7" nodrop = "0.1.14" -failure = "0.1.8" +failure = "0.1" [profile.release] opt-level = 3 @@ -30,5 +30,5 @@ debug = true opt-level = 3 [build-dependencies] -rand = { version = "0.7.2", default_features = false, features = ["small_rng"] } -failure = "0.1.6" +rand = { version = "0.8", default_features = false, features = ["small_rng"] } +failure = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 23bcc602d..478ef0dcc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ pub use crate::file::*; mod magic; pub use crate::magic::{ - between, between_const, get_adjacent_files, get_bishop_moves, get_bishop_rays, get_file, get_king_moves, + between, between_const, get_adjacent_files, get_bishop_moves, get_bishop_rays, get_file, get_file_const, get_king_moves, get_knight_moves, get_pawn_attacks, get_pawn_moves, get_pawn_quiets, get_rank, get_rook_moves, get_rook_rays, line, EDGES, }; diff --git a/src/magic.rs b/src/magic.rs index 720364cb8..b36b42880 100644 --- a/src/magic.rs +++ b/src/magic.rs @@ -170,6 +170,12 @@ pub fn get_file(file: File) -> BitBoard { unsafe { *FILES.get_unchecked(file.to_index()) } } +/// Get a `BitBoard` that represents all the squares on a particular file. +#[inline] +pub const fn get_file_const(file: File) -> BitBoard { + FILES[file.to_index()] +} + /// Get a `BitBoard` that represents the squares on the 1 or 2 files next to this file. #[inline] pub fn get_adjacent_files(file: File) -> BitBoard { diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index 7e81d918c..14cd2e396 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -27,7 +27,7 @@ impl SquareAndBitBoard { } } -pub type MoveList = NoDrop>; +pub type MoveList = NoDrop>; /// An incremental move generator /// @@ -95,7 +95,7 @@ impl MoveGen { 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::<[SquareAndBitBoard; 18]>::new()); + let mut movelist = NoDrop::new(ArrayVec::::new()); if checkers == EMPTY { PawnType::legals::(&mut movelist, &board, mask); @@ -233,11 +233,11 @@ impl MoveGen { iterable.len() } else { for m in iterable { - let mut bresult = mem::MaybeUninit::::uninit(); - unsafe { - board.make_move(m, &mut *bresult.as_mut_ptr()); - result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1); - } + let mut bresult = mem::MaybeUninit::::uninit(); + unsafe { + board.make_move(m, &mut *bresult.as_mut_ptr()); + result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1); + } } result } @@ -260,19 +260,19 @@ impl MoveGen { } else { iterable.set_iterator_mask(*targets); for x in &mut iterable { - let mut bresult = mem::MaybeUninit::::uninit(); - unsafe { - board.make_move(x, &mut *bresult.as_mut_ptr()); - result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1); - } + let mut bresult = mem::MaybeUninit::::uninit(); + unsafe { + board.make_move(x, &mut *bresult.as_mut_ptr()); + result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1); + } } iterable.set_iterator_mask(!EMPTY); for x in &mut iterable { - let mut bresult = mem::MaybeUninit::::uninit(); - unsafe { - board.make_move(x, &mut *bresult.as_mut_ptr()); - result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1); - } + let mut bresult = mem::MaybeUninit::::uninit(); + unsafe { + board.make_move(x, &mut *bresult.as_mut_ptr()); + result += MoveGen::movegen_perft_test(&*bresult.as_ptr(), depth - 1); + } } result } @@ -376,14 +376,14 @@ fn movegen_perft_kiwipete() { #[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 + movegen_perft_test("8/5bk1/8/2Pp4/8/1K6/8/8 w - d6 0 1".to_owned(), 6, 824064); + // Invalid FEN } #[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 + movegen_perft_test("8/8/1k6/8/2pP4/8/5BK1/8 b - d3 0 1".to_owned(), 6, 824064); + // Invalid FEN } #[test] @@ -546,8 +546,8 @@ fn move_of(m: &str) -> ChessMove { None }; ChessMove::new( - Square::from_str(&m[..2]).unwrap(), - Square::from_str(&m[2..4]).unwrap(), + Square::from_str(&m[..2]).unwrap(), + Square::from_str(&m[2..4]).unwrap(), promo, ) } From a3701dc20ed38b1ece5434dc11f31c072c5ab705 Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Sun, 20 Feb 2022 15:33:09 -0500 Subject: [PATCH 08/10] supporting chess 960: updated MoveGen, Board, CastleRights, etc. --- src/board.rs | 228 +++++++++++++++++--------------- src/board_builder.rs | 117 +++++++++++++---- src/castle_rights.rs | 265 ++++++++++++++++++++------------------ src/chess_move.rs | 20 ++- src/file.rs | 7 + src/gen_tables/king.rs | 68 +--------- src/magic.rs | 5 - src/movegen/piece_type.rs | 55 ++++---- 8 files changed, 419 insertions(+), 346 deletions(-) diff --git a/src/board.rs b/src/board.rs index 47dc43bcd..97c4d96aa 100644 --- a/src/board.rs +++ b/src/board.rs @@ -6,7 +6,7 @@ use crate::color::{Color, ALL_COLORS, NUM_COLORS}; use crate::error::Error; use crate::file::File; use crate::magic::{ - between, get_adjacent_files, get_bishop_rays, get_castle_moves, get_file, get_king_moves, + between, get_adjacent_files, get_bishop_rays, get_king_moves, get_knight_moves, get_pawn_attacks, get_pawn_dest_double_moves, get_pawn_source_double_moves, get_rank, get_rook_rays, }; @@ -267,15 +267,16 @@ impl Board { /// /// let mut board = Board::default(); /// - /// assert_eq!(board.castle_rights(Color::White), CastleRights::Both); - /// assert_eq!(board.castle_rights(Color::Black), CastleRights::Both); + /// assert!(board.castle_rights(Color::White).has_both()); + /// assert!(board.castle_rights(Color::Black).has_both()); /// /// board = board.make_move_new(move1) /// .make_move_new(move2) /// .make_move_new(move3) /// .make_move_new(move4); /// - /// assert_eq!(board.castle_rights(Color::White), CastleRights::KingSide); + /// assert!(board.castle_rights(Color::White).has_kingside()); + /// assert!(!board.castle_rights(Color::White).has_queenside()); /// assert_eq!(board.castle_rights(Color::Black), CastleRights::NoRights); /// ``` #[inline] @@ -283,36 +284,31 @@ impl Board { unsafe { *self.castle_rights.get_unchecked(color.to_index()) } } - /// Add castle rights for a particular side. Note: this can create an invalid position. - #[deprecated( - 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] - pub fn add_castle_rights(&mut self, color: Color, add: CastleRights) { + fn set_castle_rights(&mut self, color: Color, castle_rights: CastleRights) { unsafe { *self.castle_rights.get_unchecked_mut(color.to_index()) = - self.castle_rights(color).add(add); + castle_rights; } } /// Remove castle rights for a particular side. /// /// ``` - /// use chess::{Board, CastleRights, Color}; + /// use chess::{Board, CastleRights, Color, File}; /// /// let mut board = Board::default(); - /// assert_eq!(board.castle_rights(Color::White), CastleRights::Both); + /// assert!( board.castle_rights(Color::White).has_kingside()); /// - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// assert_eq!(board.castle_rights(Color::White), CastleRights::QueenSide); + /// board.remove_castle_rights(Color::White, File::H); + /// assert!(!board.castle_rights(Color::White).has_kingside()); /// ``` #[deprecated( 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] - pub fn remove_castle_rights(&mut self, color: Color, remove: CastleRights) { + pub fn remove_castle_rights(&mut self, color: Color, remove: File) { unsafe { *self.castle_rights.get_unchecked_mut(color.to_index()) = self.castle_rights(color).remove(remove); @@ -335,11 +331,11 @@ impl Board { /// Grab my `CastleRights`. /// /// ``` - /// use chess::{Board, Color, CastleRights}; + /// use chess::{Board, Color, CastleRights, File}; /// /// let mut board = Board::default(); - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); + /// board.remove_castle_rights(Color::White, File::H); + /// board.remove_castle_rights(Color::Black, File::A); /// /// assert_eq!(board.my_castle_rights(), board.castle_rights(Color::White)); /// ``` @@ -354,29 +350,30 @@ impl Board { note = "When doing board setup, use the BoardBuilder structure. It ensures you don't end up with an invalid position." )] #[inline] - pub fn add_my_castle_rights(&mut self, add: CastleRights) { + pub fn set_my_castle_rights(&mut self, castle_rights: CastleRights) { let color = self.side_to_move(); #[allow(deprecated)] - self.add_castle_rights(color, add); + self.set_castle_rights(color, castle_rights); } /// Remove some of my `CastleRights`. /// /// ``` - /// use chess::{Board, CastleRights}; + /// use chess::{Board, CastleRights, File}; /// /// let mut board = Board::default(); - /// assert_eq!(board.my_castle_rights(), CastleRights::Both); + /// assert!(board.my_castle_rights().has_both()); /// - /// board.remove_my_castle_rights(CastleRights::KingSide); - /// assert_eq!(board.my_castle_rights(), CastleRights::QueenSide); + /// board.remove_my_castle_rights(File::H); + /// assert!(board.my_castle_rights().has_queenside()); + /// assert!(!board.my_castle_rights().has_kingside()); /// ``` #[deprecated( 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] - pub fn remove_my_castle_rights(&mut self, remove: CastleRights) { + pub fn remove_my_castle_rights(&mut self, remove: File) { let color = self.side_to_move(); #[allow(deprecated)] self.remove_castle_rights(color, remove); @@ -385,11 +382,11 @@ impl Board { /// My opponents `CastleRights`. /// /// ``` - /// use chess::{Board, Color, CastleRights}; + /// use chess::{Board, Color, CastleRights, File}; /// /// let mut board = Board::default(); - /// board.remove_castle_rights(Color::White, CastleRights::KingSide); - /// board.remove_castle_rights(Color::Black, CastleRights::QueenSide); + /// board.remove_castle_rights(Color::White, File::H); + /// board.remove_castle_rights(Color::Black, File::A); /// /// assert_eq!(board.their_castle_rights(), board.castle_rights(Color::Black)); /// ``` @@ -398,35 +395,24 @@ impl Board { self.castle_rights(!self.side_to_move()) } - /// Add to my opponents `CastleRights`. Note: This can make the position invalid. - #[deprecated( - 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] - pub fn add_their_castle_rights(&mut self, add: CastleRights) { - let color = !self.side_to_move(); - #[allow(deprecated)] - self.add_castle_rights(color, add) - } - /// Remove some of my opponents `CastleRights`. /// /// ``` - /// use chess::{Board, CastleRights}; + /// use chess::{Board, CastleRights, File}; /// /// let mut board = Board::default(); - /// assert_eq!(board.their_castle_rights(), CastleRights::Both); + /// assert!(board.their_castle_rights().has_both()); /// - /// board.remove_their_castle_rights(CastleRights::KingSide); - /// assert_eq!(board.their_castle_rights(), CastleRights::QueenSide); + /// board.remove_their_castle_rights(File::H); + /// assert!(board.their_castle_rights().has_queenside()); + /// assert!(!board.their_castle_rights().has_kingside()); /// ``` #[deprecated( 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] - pub fn remove_their_castle_rights(&mut self, remove: CastleRights) { + pub fn remove_their_castle_rights(&mut self, remove: File) { let color = !self.side_to_move(); #[allow(deprecated)] self.remove_castle_rights(color, remove); @@ -650,7 +636,7 @@ impl Board { for color in ALL_COLORS.iter() { // get the castle rights let castle_rights = self.castle_rights(*color); - + let king_square = self.king_square(*color); // the castle rights object will tell us which rooks shouldn't have moved yet. // verify there are rooks on all those squares if castle_rights.unmoved_rooks(*color) @@ -660,12 +646,17 @@ impl Board { { return false; } - // 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) - != get_file(File::E) & get_rank(color.to_my_backrank()) - { + + if castle_rights != CastleRights::NoRights && king_square.get_rank() != color.to_my_backrank() { + return false; + } + if let Some(file) = castle_rights.kingside { + if king_square.get_file() >= file { + return false; + } + } + if let Some(file) = castle_rights.queenside { + if king_square.get_file() <= file { return false; } } @@ -896,54 +887,41 @@ impl Board { 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(); + let castles_target_rook = dest_bb & self.pieces(Piece::Rook) & self.color_combined(self.side_to_move); + let castles = moved == Piece::King && castles_target_rook == dest_bb; + 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); + + if !castles { + if let Some(captured) = self.piece_on(dest) { + result.xor(captured, dest_bb, !self.side_to_move); + } + result.xor(moved, dest_bb, self.side_to_move); + } else { + let dest_file = if dest_bb.0 > source_bb.0 {File::G} else {File::C}; + result.xor(moved, BitBoard::set(self.side_to_move.to_my_backrank(), dest_file), self.side_to_move); } - #[allow(deprecated)] - result.remove_their_castle_rights(CastleRights::square_to_castle_rights( - !self.side_to_move, - dest, - )); + if dest.get_rank() == self.side_to_move().to_their_backrank() { + #[allow(deprecated)] + result.remove_their_castle_rights(dest.get_file()); + } - #[allow(deprecated)] - result.remove_my_castle_rights(CastleRights::square_to_castle_rights( - self.side_to_move, - source, - )); + if moved == Piece::King { + #[allow(deprecated)] + result.set_castle_rights(self.side_to_move(), CastleRights::NoRights); + } else if source.get_rank() == self.side_to_move().to_my_backrank(){ + #[allow(deprecated)] + result.remove_my_castle_rights(source.get_file()); + } 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 { @@ -971,13 +949,8 @@ impl Board { } } 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) - }); + let start = castles_target_rook; + let end = BitBoard::set(my_backrank, if dest_bb.0 > source_bb.0 {File::F} else {File::D}); result.xor(Piece::Rook, start, self.side_to_move); result.xor(Piece::Rook, end, self.side_to_move); } @@ -1087,9 +1060,9 @@ impl TryFrom<&BoardBuilder> for Board { } #[allow(deprecated)] - board.add_castle_rights(Color::White, fen.get_castle_rights(Color::White)); + board.set_castle_rights(Color::White, fen.get_castle_rights(Color::White)); #[allow(deprecated)] - board.add_castle_rights(Color::Black, fen.get_castle_rights(Color::Black)); + board.set_castle_rights(Color::Black, fen.get_castle_rights(Color::Black)); board.update_pin_info(); @@ -1148,8 +1121,8 @@ fn move_of(m: &str) -> ChessMove { None }; ChessMove::new( - Square::from_string(m[..2].to_string()).unwrap(), - Square::from_string(m[2..4].to_string()).unwrap(), + Square::from_str(&m[..2]).unwrap(), + Square::from_str(&m[2..4].to_string()).unwrap(), promo, ) } @@ -1161,7 +1134,7 @@ fn test_hash_behavior() { "r1b1r1k1/3pb1pp/1p2p3/p2pPp2/3P1B2/4R2P/PPPN1PP1/R5K1 b - - 0 18"), ("6k1/1R2bp2/4p1p1/2p1P2p/2K2P2/r1P1B2P/2P5/8 b - - 4 37", vec!["f7f5", "e5f6", "e7f6", "b7b4", "g8g7"], "8/6k1/4pbp1/2p4p/1RK2P2/r1P1B2P/2P5/8 w - - 2 40"), - ("r1b1r1k1/pp4p1/4p2p/3pPP2/2p4P/P1PB4/2PB1P2/R3K2R b KQ - 0 17", vec!["c4d3", "c2d3", "g8h7", "e1g1", "d5d4"], + ("r1b1r1k1/pp4p1/4p2p/3pPP2/2p4P/P1PB4/2PB1P2/R3K2R b KQ - 0 17", vec!["c4d3", "c2d3", "g8h7", "e1h1", "d5d4"], "r1b1r3/pp4pk/4p2p/4PP2/3p3P/P1PP4/3B1P2/R4RK1 w - - 0 20"), ("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", vec!["c2c4", "d7d5", "c4c5", "b7b5"], "rnbqkbnr/p1p1pppp/8/1pPp4/8/8/PP1PPPPP/RNBQKBNR w KQkq b6 0 3") @@ -1174,7 +1147,58 @@ fn test_hash_behavior() { let final_board_direct = Board::from_str(final_fen).unwrap(); assert_eq!(final_board.get_hash(), final_board_direct.get_hash(), - "final_board: {}, final_board_direct: {}", final_board, final_board_direct); + "\n final_board: {},\nfinal_board_direct: {}", final_board, final_board_direct); + } +} + +#[test] +fn test_960_castling() { + let test_cases = vec![ + // (fen, after_ks_castling, after_qs_castling) + ("rnb1kbnr/pppppppp/8/8/8/3N4/PPPPPPPP/RK2BBNR w KQkq - 0 1", None, Some("rnb1kbnr/pppppppp/8/8/8/3N4/PPPPPPPP/2KRBBNR b kq - 1 1")), + ("rnb1k2r/ppppbppp/4pn2/8/4P3/3N1P2/PPPPN1PP/2KRBB1R b kq - 2 4", Some("rnb2rk1/ppppbppp/4pn2/8/4P3/3N1P2/PPPPN1PP/2KRBB1R w - - 3 5"), None), + ("2rk2r1/pppb1ppp/n2pnb2/8/1N1Pp3/2P1P1N1/PP1BBPPP/1RK1R3 b KQkq - 1 1", Some("2r2rk1/pppb1ppp/n2pnb2/8/1N1Pp3/2P1P1N1/PP1BBPPP/1RK1R3 w KQ - 2 2"), Some("2kr2r1/pppb1ppp/n2pnb2/8/1N1Pp3/2P1P1N1/PP1BBPPP/1RK1R3 w KQ - 2 2")), + ("2rk2r1/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/1RK1R3 w KQkq - 0 3", Some("2rk2r1/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/1R3RK1 b kq - 1 3"), Some("2rk2r1/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/2KRR3 b kq - 1 3")), + ("2rk2r1/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1B1PPP/RBK1R3 w KQkq - 1 3", Some("2rk2r1/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1B1PPP/RB3RK1 b kq - 2 3"), None), + ("2rk2r1/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/4RKR1 w KQkq - 0 3", Some("2rk2r1/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/4RRK1 b kq - 1 3"), Some("2rk2r1/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/2KR2R1 b kq - 1 3")), + ("1r1rk3/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/2KR2R1 b Kq - 1 3", None, None), + ("1r1rk3/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/2KR2R1 b Kd - 1 3", None, Some("1rkr4/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/2KR2R1 w K - 2 4")) + ]; + + for (i, (fen, after_ks_castling, after_qs_castling)) in test_cases.into_iter().enumerate() { + println!("{}- {}", i + 1, fen); + let board = Board::from_str(fen).unwrap(); + let color = board.side_to_move(); + let moves = MoveGen::new_legal(&board).collect::>(); + if let Some(after_ks_castling) = after_ks_castling { + assert!(board.castle_rights(board.side_to_move()).has_kingside()); + let ks_castles = ChessMove::new(board.king_square(color), + Square::make_square(color.to_my_backrank(), board.castle_rights(color).kingside.unwrap()), + None); + assert!(moves.contains(&ks_castles)); + let ks_castled_board = board.make_move_new(ks_castles); + assert_eq!(ks_castled_board, Board::from_str(after_ks_castling).unwrap()); + } else if let Some(ks_castling_rook) = board.castle_rights(color).kingside { + let ks_castles = ChessMove::new(board.king_square(color), + Square::make_square(color.to_my_backrank(), ks_castling_rook), + None); + assert!(!moves.contains(&ks_castles)); + } + if let Some(after_qs_castling) = after_qs_castling { + assert!(board.castle_rights(board.side_to_move()).has_queenside()); + let qs_castles = ChessMove::new(board.king_square(color), + Square::make_square(color.to_my_backrank(), board.castle_rights(color).queenside.unwrap()), + None); + assert!(moves.contains(&qs_castles)); + let qs_castled_board = board.make_move_new(qs_castles); + assert_eq!(qs_castled_board, Board::from_str(after_qs_castling).unwrap(), + "actual: {},\nexpected: {}", qs_castled_board, Board::from_str(after_qs_castling).unwrap()); + } else if let Some(qs_castling_rook) = board.castle_rights(color).queenside { + let qs_castles = ChessMove::new(board.king_square(color), + Square::make_square(color.to_my_backrank(), qs_castling_rook), + None); + assert!(!moves.contains(&qs_castles)); + } } } diff --git a/src/board_builder.rs b/src/board_builder.rs index da2f67953..7eeabde22 100644 --- a/src/board_builder.rs +++ b/src/board_builder.rs @@ -1,3 +1,4 @@ +use crate::CastleType; use crate::board::Board; use crate::castle_rights::CastleRights; use crate::color::Color; @@ -143,7 +144,7 @@ impl BoardBuilder { /// use chess::{BoardBuilder, Board, CastleRights, Color}; /// /// let bb: BoardBuilder = Board::default().into(); - /// assert_eq!(bb.get_castle_rights(Color::White), CastleRights::Both); + /// assert!(bb.get_castle_rights(Color::White).has_both()); /// ``` pub fn get_castle_rights(&self, color: Color) -> CastleRights { self.castle_rights[color.to_index()] @@ -189,12 +190,12 @@ impl BoardBuilder { /// This function can be used on self directly or in a builder pattern. /// /// ``` - /// use chess::{BoardBuilder, Color, CastleRights}; + /// use chess::{BoardBuilder, Color, CastleRights, File}; /// BoardBuilder::new() /// .castle_rights(Color::White, CastleRights::NoRights); /// /// let mut bb = BoardBuilder::new(); - /// bb.castle_rights(Color::Black, CastleRights::Both); + /// bb.castle_rights(Color::Black, CastleRights::new(Some(File::H), Some(File::A))); /// ``` pub fn castle_rights<'a>( &'a mut self, @@ -312,15 +313,46 @@ impl fmt::Display for BoardBuilder { write!(f, "b ")?; } + // If the rook for a given side is the outermost rook, use 'K' or 'Q' (respectively 'k' or 'q' for black), + // otherwise, use the rook's file + let castle_rights_to_str = |color: Color| { + let mut res = String::new(); + let rank = color.to_my_backrank(); + for castle_side in [CastleType::Kingside, CastleType::Queenside] { + if let Some(file) = self.get_castle_rights(color).get(castle_side){ + let mut outer_most_rook = true; + let file_ind = file.to_index(); + let file_ind = if castle_side == CastleType::Kingside {file_ind} else {7 - file_ind}; + for outside_file in (file_ind + 1) ..=7 { + let outside_file = if castle_side == CastleType::Kingside {outside_file} else {7 - outside_file}; + if self[Square::make_square(rank, File::from_index(outside_file))] == Some((Piece::Rook, color)) { + outer_most_rook = false; + break; + } + } + let castle_right_char = if outer_most_rook { + if castle_side == CastleType::Kingside {'k'} else {'q'} + } else { + ('a' as u8 + file.to_index() as u8) as char + }; + let castle_right_char = if color == Color::White {castle_right_char.to_ascii_uppercase()} else {castle_right_char}; + use std::fmt::Write; + write!(&mut res, "{}", castle_right_char).unwrap(); + + } + } + res + }; + write!( f, "{}", - self.castle_rights[Color::White.to_index()].to_string(Color::White) + castle_rights_to_str(Color::White) )?; write!( f, "{}", - self.castle_rights[Color::Black.to_index()].to_string(Color::Black) + castle_rights_to_str(Color::Black) )?; if self.castle_rights[0] == CastleRights::NoRights && self.castle_rights[1] == CastleRights::NoRights @@ -365,6 +397,8 @@ impl FromStr for BoardBuilder { let castles = tokens[2]; let ep = tokens[3]; + let mut king_squares = [None; 2]; + for x in pieces.chars() { match x { '/' => { @@ -428,11 +462,13 @@ impl FromStr for BoardBuilder { 'k' => { fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::King, Color::Black)); + king_squares[Color::Black.to_index()] = Some(Square::make_square(cur_rank, cur_file)); cur_file = cur_file.right(); } 'K' => { fen[Square::make_square(cur_rank, cur_file)] = Some((Piece::King, Color::White)); + king_squares[Color::White.to_index()] = Some(Square::make_square(cur_rank, cur_file)); cur_file = cur_file.right(); } _ => { @@ -452,25 +488,43 @@ impl FromStr for BoardBuilder { } } - 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; - } else { - fen.castle_rights[Color::White.to_index()] = CastleRights::NoRights; - } + let find_rook_file = |bb: &BoardBuilder, color: Color, kingside: bool| -> Result { + let rank = color.to_my_backrank(); + for file in 0..6 { + let file = if kingside {7 - file} else {file}; + let file = File::from_index(file); + if bb[Square::make_square(rank, file)] == Some((Piece::Rook, color)) { + return Ok(file); + } + } + return Err(Error::InvalidFen { fen: value.to_string()}) + }; - 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; - } else { - fen.castle_rights[Color::Black.to_index()] = CastleRights::NoRights; + for c in castles.chars() { + if c == 'K' { + fen.castle_rights[Color::White.to_index()].kingside = Some(find_rook_file(&fen, Color::White, true)?); + } else if c == 'Q' { + fen.castle_rights[Color::White.to_index()].queenside = Some(find_rook_file(&fen, Color::White, false)?); + } else if c == 'k' { + fen.castle_rights[Color::Black.to_index()].kingside = Some(find_rook_file(&fen, Color::Black, true)?); + } else if c == 'q' { + fen.castle_rights[Color::Black.to_index()].queenside = Some(find_rook_file(&fen, Color::Black, false)?); + } else if ('a' .. 'h').contains(&c.to_ascii_lowercase()){ + let file = File::from_index((c.to_ascii_lowercase() as u8 - 'a' as u8) as usize); + let color = if c.is_ascii_lowercase() {Color::Black} else {Color::White}; + let ksq = match king_squares[color.to_index()]{ + Some(ksq) => ksq, + None => return Err(Error::InvalidFen {fen: value.to_string()}) + }; + let is_kingside = file > ksq.get_file(); + if is_kingside { + fen.castle_rights[color.to_index()].kingside= Some(file); + } else { + fen.castle_rights[color.to_index()].queenside = Some(file); + }; + } } + if let Ok(sq) = Square::from_str(&ep) { fen = fen.en_passant(Some(sq.get_file())); @@ -525,9 +579,9 @@ fn check_initial_position() { #[test] fn invalid_castle_rights() { let res: Result = BoardBuilder::new() - .piece(Square::A1, Piece::King, Color::White) + .piece(Square::E2, Piece::King, Color::White) .piece(Square::A8, Piece::King, Color::Black) - .castle_rights(Color::White, CastleRights::Both) + .castle_rights(Color::White, CastleRights{kingside: Some(File::H), queenside: Some(File::A)}) .try_into(); assert!(res.is_err()); } @@ -555,3 +609,18 @@ fn test_in_check() { let res: Result = bb.try_into(); assert!(res.is_err()); // My opponent cannot be in check when it's my move. } + +#[test] +fn test_castle_rights_in_fen_output() { + let test_cases = vec![ + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "1r1rk3/pppb1p1p/3pnb2/6p1/1P1Pp3/4P1N1/PP1BBPPP/2KR2R1 b Kd - 0 1", + "1r1rk3/ppp2p1p/2bpnb2/6p1/1P1Pp3/2B1P1N1/PP2BPPP/2KR2R1 b Kq - 0 1", + "nqbn1krr/ppp2pb1/3p4/4p1pp/3PP1PP/8/PPP2PB1/NQBN1KRR w Gg - 0 1", + "1rr1k3/ppp2pb1/1nqpbn2/4p1pp/3PP1PP/1B1QN3/PPP1NPB1/2R1RK2 w Eq - 0 1" + ]; + for fen in test_cases { + let board = Board::from_str(fen).unwrap(); + assert_eq!(board.to_string(), fen); + } +} \ No newline at end of file diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 1c475680e..fc8751575 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -1,166 +1,187 @@ -use std::hint::unreachable_unchecked; - +use crate::between; use crate::bitboard::{BitBoard, EMPTY}; use crate::color::Color; use crate::file::File; use crate::square::Square; -use crate::magic::{KINGSIDE_CASTLE_SQUARES, QUEENSIDE_CASTLE_SQUARES}; - -/// What castle rights does a particular player have? -#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] -pub enum CastleRights { - NoRights, - KingSide, - QueenSide, - Both, +/// Represents the possible castle types. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum CastleType { + Kingside, + Queenside } -impl PartialOrd for CastleRights { - fn partial_cmp(&self, other: &Self) -> Option { - let (self_index, other_index) = (self.to_index(), other.to_index()); - if self_index * other_index == 2 { // if one is KingSide and the other is QueenSide - None - } else { - self_index.partial_cmp(&other_index) +impl CastleType { + /// The file the king ends up on after this type of castling + #[inline(always)] + #[allow(dead_code)] + pub fn king_dest_file(&self) -> File { + match self { + CastleType::Kingside => File::G, + CastleType::Queenside => File::C, } } -} -/// How many different types of `CastleRights` are there? -pub const NUM_CASTLE_RIGHTS: usize = 4; - -/// Enumerate all castle rights. -pub const ALL_CASTLE_RIGHTS: [CastleRights; NUM_CASTLE_RIGHTS] = [ - CastleRights::NoRights, - CastleRights::KingSide, - CastleRights::QueenSide, - CastleRights::Both, -]; - -const CASTLES_PER_SQUARE: [[u8; 64]; 2] = [ - [ - 2, 0, 0, 0, 3, 0, 0, 1, // 1 - 0, 0, 0, 0, 0, 0, 0, 0, // 2 - 0, 0, 0, 0, 0, 0, 0, 0, // 3 - 0, 0, 0, 0, 0, 0, 0, 0, // 4 - 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 - 0, 0, 0, 0, 0, 0, 0, 0, // 8 - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, // 1 - 0, 0, 0, 0, 0, 0, 0, 0, // 2 - 0, 0, 0, 0, 0, 0, 0, 0, // 3 - 0, 0, 0, 0, 0, 0, 0, 0, // 4 - 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, - ], -]; - -impl CastleRights { - /// Can I castle kingside? - pub fn has_kingside(&self) -> bool { - self.to_index() & 1 == 1 + /// The square the king ends up on after this type of castling + #[inline(always)] + #[allow(dead_code)] + pub fn king_dest(&self, color: Color) -> Square { + Square::make_square(color.to_my_backrank(), self.king_dest_file()) } - /// Can I castle queenside? - pub fn has_queenside(&self) -> bool { - self.to_index() & 2 == 2 + /// The file the rook ends up on after this type of castling + #[inline(always)] + pub fn rook_dest_file(&self) -> File { + match self { + CastleType::Kingside => File::F, + CastleType::Queenside => File::D, + } } - 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()) - } as usize) + /// The square the rook ends up on after this type of castling + #[inline(always)] + pub fn rook_dest(&self, color: Color) -> Square { + Square::make_square(color.to_my_backrank(), self.rook_dest_file()) } + /// 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()) } + fn kingside_squares(king_square: Square) -> BitBoard { + let dest = Square::make_square(king_square.get_rank(), File::G); + between(king_square, dest) | BitBoard::from_square(dest) } /// 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()) } + fn queenside_squares(king_square: Square) -> BitBoard { + let dest = Square::make_square(king_square.get_rank(), File::C); + between(king_square, dest) | BitBoard::from_square(dest) } - /// Remove castle rights, and return a new `CastleRights`. - pub fn remove(&self, remove: CastleRights) -> CastleRights { - CastleRights::from_index(self.to_index() & !remove.to_index()) + /// The squares that the must be clear and not under attack for the king + /// to be able to castle + #[inline(always)] + pub fn king_journey_squares(&self, king_square: Square) -> BitBoard { + match self { + CastleType::Kingside => Self::kingside_squares(king_square), + CastleType::Queenside => Self::queenside_squares(king_square), + } + } +} + +/// What castle rights does a particular player have? +/// +/// `CastleRights` is a pair of `Option` values, one for kingside castling, +/// and one for queenside castling. +/// +/// If the value is `None`, we don't have that castling right. If it is `Some(file)`, we can +/// castle with our rook positioned on the given `file`. +/// +/// This allows supporting chess 960 positions in addition to normal chess positions. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub struct CastleRights { + pub(crate) kingside: Option, + pub(crate) queenside: Option +} + +impl Default for CastleRights { + #[inline] + fn default() -> Self { + Self { queenside: None, kingside: None } + } +} + +impl CastleRights { + + /// Crate a new `CastleRights` value given the kingside and queenside castle rights. + pub fn new(kingside: Option, queenside: Option) -> CastleRights { + CastleRights{kingside, queenside} + } + + /// Can I castle kingside? + #[inline(always)] + pub fn has_kingside(&self) -> bool { + self.kingside.is_some() } - /// 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()) + /// Can I castle queenside? + #[inline(always)] + pub fn has_queenside(&self) -> bool { + self.queenside.is_some() } - /// Convert `CastleRights` to `usize` for table lookups - pub fn to_index(&self) -> usize { - *self as usize + /// Can I castle both sides? + #[inline(always)] + pub fn has_both(&self) -> bool { + self.has_kingside() & self.has_queenside() } - /// Convert `usize` to `CastleRights`. Panic if invalid number. - pub fn from_index(i: usize) -> CastleRights { - match i { - 0 => CastleRights::NoRights, - 1 => CastleRights::KingSide, - 2 => CastleRights::QueenSide, - 3 => CastleRights::Both, - _ => unsafe { unreachable_unchecked() }, + /// Can I castle either side? + #[inline(always)] + pub fn has_any(&self) -> bool { + self.has_kingside() | self.has_queenside() + } + + /// Do I have the given castle right? + #[inline(always)] + pub fn has(&self, castle_type: CastleType) -> bool { + match castle_type { + CastleType::Kingside => self.kingside.is_some(), + CastleType::Queenside => self.queenside.is_some(), } } - /// Which rooks can we "guarantee" we haven't moved yet? - pub fn unmoved_rooks(&self, color: Color) -> BitBoard { - 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::Both => { - BitBoard::set(color.to_my_backrank(), File::A) - ^ BitBoard::set(color.to_my_backrank(), File::H) - } + /// Return the value for the given castle type + #[inline(always)] + pub fn get(&self, castle_type: CastleType) -> Option { + match castle_type { + CastleType::Kingside => self.kingside, + CastleType::Queenside => self.queenside, } } - /// Convert the castle rights to an FEN compatible string. - /// - /// ``` - /// use chess::{CastleRights, Color}; - /// - /// assert_eq!(CastleRights::NoRights.to_string(Color::White), ""); - /// assert_eq!(CastleRights::Both.to_string(Color::Black), "kq"); - /// assert_eq!(CastleRights::KingSide.to_string(Color::White), "K"); - /// assert_eq!(CastleRights::QueenSide.to_string(Color::Black), "q"); - /// ``` pub fn to_string(&self, color: Color) -> String { - let result = match *self { - CastleRights::NoRights => "", - CastleRights::KingSide => "k", - CastleRights::QueenSide => "q", - CastleRights::Both => "kq", + let queenside = match self.queenside { + Some(file) => file.to_string(), + None => "".to_string(), + }; + let kingside = match self.kingside { + Some(file) => file.to_string(), + None => "".to_string() }; + let result = format!("{}{}", queenside, kingside); - if color == Color::White { - result.to_uppercase() + if color == Color::Black { + result.to_lowercase() } else { - result.to_string() + result } } - /// Given a square of a rook, which side is it on? - /// Note: It is invalid to pass in a non-rook square. The code may panic. - pub fn rook_square_to_castle_rights(square: Square) -> CastleRights { - match square.get_file() { - File::A => CastleRights::QueenSide, - File::H => CastleRights::KingSide, - _ => unsafe { unreachable_unchecked() }, + /// Remove castle rights, and return a new `CastleRights`. + pub fn remove(&self, remove: File) -> CastleRights { + let mut res = *self; + if res.kingside == Some(remove) { res.kingside = None; } + else if res.queenside == Some(remove) {res.queenside = None;} + res + } + + #[allow(non_upper_case_globals)] + pub const NoRights: Self = CastleRights{ kingside: None, queenside: None}; + + /// What rooks are guaranteed to not have moved given this castle rights? + pub fn unmoved_rooks(&self, color: Color) -> BitBoard { + let mut res = EMPTY; + if let Some(file) = self.kingside { + res |= BitBoard::set(color.to_my_backrank(), file); + } + if let Some(file) = self.queenside { + res |= BitBoard::set(color.to_my_backrank(), file); } + res + } + + /// for hashing purposes only + pub(crate) fn to_index(&self) -> usize { + self.kingside.is_some() as usize + self.queenside.is_some() as usize * 2 } } diff --git a/src/chess_move.rs b/src/chess_move.rs index 1538aec23..1aa9ed102 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -11,6 +11,8 @@ use std::fmt; use std::str::FromStr; /// Represent a ChessMove in memory +/// +/// Castling moves are encoded as king takes rook #[derive(Clone, Copy, Eq, PartialOrd, PartialEq, Default, Debug, Hash)] pub struct ChessMove { source: Square, @@ -62,9 +64,10 @@ impl ChessMove { // 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 source_file = board.king_square(board.side_to_move()).get_file(); + let board_castle_rights = board.castle_rights(board.side_to_move()); + let dest_file = if move_text == "O-O" { board_castle_rights.kingside} else { board_castle_rights.queenside }; + let dest_file = dest_file.ok_or(Error::InvalidSanMove)?; let m = ChessMove::new( Square::make_square(rank, source_file), Square::make_square(rank, dest_file), @@ -370,6 +373,17 @@ impl ChessMove { found_move.ok_or(error.clone()) } + + pub fn is_castles(&self, board: &Board) -> bool { + let color = board.side_to_move(); + self.source == board.king_square(color) && + self.dest.get_rank() == color.to_my_backrank() && + { + let dest_file = Some(self.dest.get_file()); + let castle_rights = board.castle_rights(color); + dest_file == castle_rights.kingside || dest_file == castle_rights.queenside + } + } } impl fmt::Display for ChessMove { diff --git a/src/file.rs b/src/file.rs index e6b3c3c15..8d230e16e 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,4 +1,5 @@ use crate::error::Error; +use std::fmt::Display; use std::mem::transmute; use std::str::FromStr; @@ -15,6 +16,12 @@ pub enum File { H, } +impl Display for File { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", ('A' as u8 + self.to_index() as u8) as char) + } +} + /// How many files are there? pub const NUM_FILES: usize = 8; diff --git a/src/gen_tables/king.rs b/src/gen_tables/king.rs index 2167613e9..978e8153f 100644 --- a/src/gen_tables/king.rs +++ b/src/gen_tables/king.rs @@ -2,14 +2,10 @@ use std::fs::File; use std::io::Write; use crate::bitboard::{BitBoard, EMPTY}; -use crate::color::ALL_COLORS; -use crate::file::File as ChessFile; -use crate::square::{Square, ALL_SQUARES}; +use crate::square::ALL_SQUARES; // Given a square, what are the valid king moves? static mut KING_MOVES: [BitBoard; 64] = [EMPTY; 64]; -static mut KINGSIDE_CASTLE_SQUARES: [BitBoard; 2] = [EMPTY; 2]; -static mut QUEENSIDE_CASTLE_SQUARES: [BitBoard; 2] = [EMPTY; 2]; // Generate the KING_MOVES array. pub fn gen_king_moves() { @@ -30,39 +26,6 @@ pub fn gen_king_moves() { .fold(EMPTY, |b, s| b | BitBoard::from_square(*s)); } } - - gen_kingside_castle_squares(); - gen_queenside_castle_squares(); -} - -fn gen_kingside_castle_squares() { - for color in ALL_COLORS.iter() { - unsafe { - KINGSIDE_CASTLE_SQUARES[color.to_index()] = - BitBoard::set(color.to_my_backrank(), ChessFile::F) - ^ BitBoard::set(color.to_my_backrank(), ChessFile::G); - } - } -} - -fn gen_queenside_castle_squares() { - for color in ALL_COLORS.iter() { - unsafe { - QUEENSIDE_CASTLE_SQUARES[color.to_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); - } - } -} - -fn gen_castle_moves() -> BitBoard { - BitBoard::from_square(Square::C1) - ^ BitBoard::from_square(Square::C8) - ^ BitBoard::from_square(Square::E1) - ^ BitBoard::from_square(Square::E8) - ^ BitBoard::from_square(Square::G1) - ^ BitBoard::from_square(Square::G8) } // Write the KING_MOVES array to the specified file. @@ -72,33 +35,4 @@ pub fn write_king_moves(f: &mut File) { unsafe { write!(f, " BitBoard({}),\n", KING_MOVES[i].0).unwrap() }; } write!(f, "];\n").unwrap(); - - write!(f, "pub const KINGSIDE_CASTLE_SQUARES: [BitBoard; 2] = [\n").unwrap(); - unsafe { - write!( - f, - " BitBoard({}), BitBoard({})];\n", - KINGSIDE_CASTLE_SQUARES[0].0, - KINGSIDE_CASTLE_SQUARES[1].0 - ) - .unwrap() - }; - - write!(f, "pub const QUEENSIDE_CASTLE_SQUARES: [BitBoard; 2] = [\n").unwrap(); - unsafe { - write!( - f, - " BitBoard({}), BitBoard({})];\n", - QUEENSIDE_CASTLE_SQUARES[0].0, - QUEENSIDE_CASTLE_SQUARES[1].0 - ) - .unwrap() - }; - - write!( - f, - "const CASTLE_MOVES: BitBoard = BitBoard({});\n", - gen_castle_moves().0 - ) - .unwrap(); } diff --git a/src/magic.rs b/src/magic.rs index b36b42880..9b2cb13ef 100644 --- a/src/magic.rs +++ b/src/magic.rs @@ -104,11 +104,6 @@ pub fn get_pawn_attacks(sq: Square, color: Color, blockers: BitBoard) -> BitBoar & blockers } } -/// Get the legal destination castle squares for both players -#[inline] -pub fn get_castle_moves() -> BitBoard { - CASTLE_MOVES -} /// Get the quiet pawn moves (non-captures) for a particular square, given the pawn's color and /// the potential blocking pieces. diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index d4e456262..30b704680 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -1,3 +1,4 @@ +use crate::{CastleType}; use crate::bitboard::{BitBoard, EMPTY}; use crate::board::Board; use crate::color::Color; @@ -14,7 +15,6 @@ use crate::magic::{ pub trait PieceType { fn is(piece: Piece) -> bool; fn into_piece() -> Piece; - #[inline(always)] fn pseudo_legals(src: Square, color: Color, combined: BitBoard, mask: BitBoard) -> BitBoard; #[inline(always)] fn legals(movelist: &mut MoveList, board: &Board, mask: BitBoard) @@ -365,33 +365,42 @@ impl PieceType for KingType { // 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. + // * the squares between my king and king dest are empty (except for the castling rook). + // * the square between the castling rook and the rook dest are empty (except for the king). // * 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 !T::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); + for castle_type in [CastleType::Kingside, CastleType::Queenside] { + if let Some(rook_file) = board.my_castle_rights().get(castle_type) { + // println!("has castle rights: {:?}, rook file: {}", castle_type, rook_file); + let rook_sq = Square::make_square(color.to_my_backrank(), rook_file); + let rook_sq_bb = BitBoard::from_square(rook_sq); + let king_sq_bb = BitBoard::from_square(ksq); + let king_journey_squares = castle_type.king_journey_squares(ksq); + // println!("king path clear: {}", combined & !rook_sq_bb & !king_sq_bb & king_journey_squares == EMPTY); + if combined & !rook_sq_bb & !king_sq_bb & king_journey_squares == EMPTY { + let rook_dest_sq = castle_type.rook_dest(color); + let rook_path = between(rook_sq, rook_dest_sq) | BitBoard::from_square(rook_dest_sq); + + let rook_path_clear = combined & !rook_sq_bb & !king_sq_bb & rook_path == EMPTY; + // println!("rook path clear: {}", rook_path_clear); + if rook_path_clear { + + let mut journey_squares_not_attacked = true; + for sq in king_journey_squares { + if !KingType::legal_king_move(board, sq) { + journey_squares_not_attacked = false; + break; + } + } + // println!("journey_squares_not_attacked: {}", journey_squares_not_attacked); + if journey_squares_not_attacked { + moves ^= rook_sq_bb; + } + } + } } } } From bd21ff1880d97c0db5db6eb1637bb7a533632b64 Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Tue, 22 Feb 2022 16:51:47 -0500 Subject: [PATCH 09/10] Fixed a 960 castling bug Where we would castle into check by enemy heavy pieces on the other side of the castling rook --- src/movegen/piece_type.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/movegen/piece_type.rs b/src/movegen/piece_type.rs index 30b704680..e52e36753 100644 --- a/src/movegen/piece_type.rs +++ b/src/movegen/piece_type.rs @@ -1,4 +1,4 @@ -use crate::{CastleType}; +use crate::{CastleType, File}; use crate::bitboard::{BitBoard, EMPTY}; use crate::board::Board; use crate::color::Color; @@ -374,18 +374,16 @@ impl PieceType for KingType { if !T::IN_CHECK { for castle_type in [CastleType::Kingside, CastleType::Queenside] { if let Some(rook_file) = board.my_castle_rights().get(castle_type) { - // println!("has castle rights: {:?}, rook file: {}", castle_type, rook_file); - let rook_sq = Square::make_square(color.to_my_backrank(), rook_file); + let backrank = color.to_my_backrank(); + let rook_sq = Square::make_square(backrank, rook_file); let rook_sq_bb = BitBoard::from_square(rook_sq); let king_sq_bb = BitBoard::from_square(ksq); let king_journey_squares = castle_type.king_journey_squares(ksq); - // println!("king path clear: {}", combined & !rook_sq_bb & !king_sq_bb & king_journey_squares == EMPTY); if combined & !rook_sq_bb & !king_sq_bb & king_journey_squares == EMPTY { let rook_dest_sq = castle_type.rook_dest(color); let rook_path = between(rook_sq, rook_dest_sq) | BitBoard::from_square(rook_dest_sq); let rook_path_clear = combined & !rook_sq_bb & !king_sq_bb & rook_path == EMPTY; - // println!("rook path clear: {}", rook_path_clear); if rook_path_clear { let mut journey_squares_not_attacked = true; @@ -395,7 +393,26 @@ impl PieceType for KingType { break; } } - // println!("journey_squares_not_attacked: {}", journey_squares_not_attacked); + // check that there are no enemy heavy pieces waiting for the king on the other side! + if castle_type == CastleType::Kingside { + if rook_file != File::H { + let their_heavy_pieces = (board.pieces(Piece::Rook) | board.pieces(Piece::Queen)) & board.color_combined(!color); + if their_heavy_pieces & BitBoard::set(backrank, File::H) != EMPTY { + continue; + } + } + } else { + if rook_file != File::A { + let their_heavy_pieces = (board.pieces(Piece::Rook) | board.pieces(Piece::Queen)) & board.color_combined(!color); + if their_heavy_pieces & BitBoard::set(backrank, File::B) != EMPTY { + continue; + } + if their_heavy_pieces & BitBoard::set(backrank, File::A) != EMPTY && + combined & !rook_sq_bb & BitBoard::set(backrank, File::B) == EMPTY { + continue; + } + } + } if journey_squares_not_attacked { moves ^= rook_sq_bb; } From 6df6a0d3e05691368eb4c600231d04b0f126cd39 Mon Sep 17 00:00:00 2001 From: Arash Sahebolamri Date: Mon, 28 Feb 2022 22:06:18 -0500 Subject: [PATCH 10/10] - Fixed a fen parsing bug - Added `get_castles()` to `ChessMove` --- src/board_builder.rs | 2 +- src/chess_move.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/board_builder.rs b/src/board_builder.rs index 7eeabde22..6721b2aa5 100644 --- a/src/board_builder.rs +++ b/src/board_builder.rs @@ -509,7 +509,7 @@ impl FromStr for BoardBuilder { fen.castle_rights[Color::Black.to_index()].kingside = Some(find_rook_file(&fen, Color::Black, true)?); } else if c == 'q' { fen.castle_rights[Color::Black.to_index()].queenside = Some(find_rook_file(&fen, Color::Black, false)?); - } else if ('a' .. 'h').contains(&c.to_ascii_lowercase()){ + } else if ('a' ..= 'h').contains(&c.to_ascii_lowercase()){ let file = File::from_index((c.to_ascii_lowercase() as u8 - 'a' as u8) as usize); let color = if c.is_ascii_lowercase() {Color::Black} else {Color::White}; let ksq = match king_squares[color.to_index()]{ diff --git a/src/chess_move.rs b/src/chess_move.rs index 1aa9ed102..86fde365e 100644 --- a/src/chess_move.rs +++ b/src/chess_move.rs @@ -1,3 +1,4 @@ +use crate::CastleType; use crate::board::Board; use crate::error::Error; use crate::file::File; @@ -384,6 +385,25 @@ impl ChessMove { dest_file == castle_rights.kingside || dest_file == castle_rights.queenside } } + + pub fn get_castles(&self, board: &Board) -> Option { + let color = board.side_to_move(); + if self.source == board.king_square(color) && + self.dest.get_rank() == color.to_my_backrank() + { + let dest_file = Some(self.dest.get_file()); + let castle_rights = board.castle_rights(color); + if dest_file == castle_rights.kingside { + Some(CastleType::Kingside) + } else if dest_file == castle_rights.queenside { + Some(CastleType::Queenside) + } else { + None + } + } else { + None + } + } } impl fmt::Display for ChessMove {