diff --git a/Cargo.lock b/Cargo.lock index 3049c76..ffc42b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,7 @@ name = "chessfriend_bitboard" version = "0.1.0" dependencies = [ "chessfriend_core", + "forward_ref", ] [[package]] @@ -95,6 +96,7 @@ name = "chessfriend_position" version = "0.1.0" dependencies = [ "chessfriend_bitboard", + "chessfriend_board", "chessfriend_core", "chessfriend_moves", ] @@ -199,6 +201,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + [[package]] name = "heck" version = "0.4.1" diff --git a/bitboard/src/bit_scanner.rs b/bitboard/src/bit_scanner.rs index c9d5190..7287d27 100644 --- a/bitboard/src/bit_scanner.rs +++ b/bitboard/src/bit_scanner.rs @@ -1,8 +1,10 @@ // Eryn Wells +use chessfriend_core::Square; + macro_rules! bit_scanner { ($name:ident) => { - pub(crate) struct $name { + pub struct $name { bits: u64, shift: usize, } @@ -18,8 +20,15 @@ macro_rules! bit_scanner { bit_scanner!(LeadingBitScanner); bit_scanner!(TrailingBitScanner); +fn _index_to_square(index: usize) -> Square { + unsafe { + #[allow(clippy::cast_possible_truncation)] + Square::from_index_unchecked(index as u8) + } +} + impl Iterator for LeadingBitScanner { - type Item = usize; + type Item = Square; fn next(&mut self) -> Option { let u64bits = u64::BITS as usize; @@ -40,12 +49,12 @@ impl Iterator for LeadingBitScanner { // Shift 1 additional place to account for the 1 that `leading_zeros` found. self.shift += leading_zeros + 1; - Some(position) + Some(_index_to_square(position)) } } impl Iterator for TrailingBitScanner { - type Item = usize; + type Item = Square; fn next(&mut self) -> Option { let u64bits = u64::BITS as usize; @@ -66,7 +75,7 @@ impl Iterator for TrailingBitScanner { // Shift 1 additional place to account for the 1 that `leading_zeros` found. self.shift += trailing_zeros + 1; - Some(position) + Some(_index_to_square(position)) } } @@ -83,17 +92,17 @@ mod tests { #[test] fn leading_one() { let mut scanner = LeadingBitScanner::new(1); - assert_eq!(scanner.next(), Some(0)); + assert_eq!(scanner.next(), Some(Square::A1)); assert_eq!(scanner.next(), None); } #[test] fn leading_complex() { let mut scanner = LeadingBitScanner::new(0b_1100_0101); - assert_eq!(scanner.next(), Some(7)); - assert_eq!(scanner.next(), Some(6)); - assert_eq!(scanner.next(), Some(2)); - assert_eq!(scanner.next(), Some(0)); + assert_eq!(scanner.next(), Some(Square::H1)); + assert_eq!(scanner.next(), Some(Square::G1)); + assert_eq!(scanner.next(), Some(Square::C1)); + assert_eq!(scanner.next(), Some(Square::A1)); assert_eq!(scanner.next(), None); } @@ -106,17 +115,17 @@ mod tests { #[test] fn trailing_one() { let mut scanner = TrailingBitScanner::new(1); - assert_eq!(scanner.next(), Some(0)); + assert_eq!(scanner.next(), Some(Square::A1)); assert_eq!(scanner.next(), None); } #[test] fn trailing_complex() { let mut scanner = TrailingBitScanner::new(0b_1100_0101); - assert_eq!(scanner.next(), Some(0)); - assert_eq!(scanner.next(), Some(2)); - assert_eq!(scanner.next(), Some(6)); - assert_eq!(scanner.next(), Some(7)); + assert_eq!(scanner.next(), Some(Square::A1)); + assert_eq!(scanner.next(), Some(Square::C1)); + assert_eq!(scanner.next(), Some(Square::G1)); + assert_eq!(scanner.next(), Some(Square::H1)); assert_eq!(scanner.next(), None); } } diff --git a/bitboard/src/bitboard.rs b/bitboard/src/bitboard.rs index ce1852d..ea1233c 100644 --- a/bitboard/src/bitboard.rs +++ b/bitboard/src/bitboard.rs @@ -144,7 +144,7 @@ impl BitBoard { self.0 != 0 } - /// Returns `true` if this [BitBoard] has the bit corresponding to `square` set. + /// Returns `true` if this [`BitBoard`] has the bit corresponding to `square` set. /// /// ## Examples /// @@ -179,7 +179,7 @@ impl BitBoard { self.0.count_ones() } - /// Set a square in this [BitBoard] by toggling the corresponding bit to 1. + /// Set a square in this [`BitBoard`] by toggling the corresponding bit to 1. /// This always succeeds, even if the bit was already set. /// /// ## Examples @@ -237,21 +237,20 @@ impl BitBoard { &self, direction: &IterationDirection, ) -> Box> { - #[allow(clippy::cast_possible_truncation)] - fn index_to_square(index: usize) -> Square { - unsafe { Square::from_index_unchecked(index as u8) } - } - match direction { - IterationDirection::Leading => { - Box::new(LeadingBitScanner::new(self.0).map(index_to_square)) - } - IterationDirection::Trailing => { - Box::new(TrailingBitScanner::new(self.0).map(index_to_square)) - } + IterationDirection::Leading => Box::new(self.occupied_squares_leading()), + IterationDirection::Trailing => Box::new(self.occupied_squares_trailing()), } } + pub fn occupied_squares_leading(&self) -> LeadingBitScanner { + LeadingBitScanner::new(self.0) + } + + pub fn occupied_squares_trailing(&self) -> TrailingBitScanner { + TrailingBitScanner::new(self.0) + } + #[must_use] pub fn first_occupied_square(&self, direction: &IterationDirection) -> Option { match direction { @@ -264,7 +263,7 @@ impl BitBoard { /// board, starting at the leading (most-significant) end of the board. If /// the board is empty, returns `None`. #[must_use] - fn first_occupied_square_leading(self) -> Option { + pub fn first_occupied_square_leading(self) -> Option { let leading_zeros = self._leading_zeros(); if leading_zeros < SQUARES_NUM { unsafe { @@ -281,7 +280,7 @@ impl BitBoard { /// board, starting at the trailing (least-significant) end of the board. /// If the board is empty, returns `None`. #[must_use] - fn first_occupied_square_trailing(self) -> Option { + pub fn first_occupied_square_trailing(self) -> Option { let trailing_zeros = self._trailing_zeros(); if trailing_zeros < SQUARES_NUM { @@ -496,12 +495,9 @@ mod tests { let bb = BitBoard(0b01010100); let expected_squares = [Square::G1, Square::E1, Square::C1]; - for (a, b) in bb - .occupied_squares(&IterationDirection::Leading) - .zip(expected_squares.iter().copied()) - { - assert_eq!(a, b); - } + bb.occupied_squares(&IterationDirection::Leading) + .zip(expected_squares) + .for_each(|(a, b)| assert_eq!(a, b)); } #[test] @@ -512,12 +508,9 @@ mod tests { let expected_squares = [Square::H8, Square::F6, Square::C5, Square::E2, Square::D1]; - for (a, b) in bb - .occupied_squares(&IterationDirection::Leading) - .zip(expected_squares.iter().cloned()) - { - assert_eq!(a, b); - } + bb.occupied_squares(&IterationDirection::Leading) + .zip(expected_squares) + .for_each(|(a, b)| assert_eq!(a, b)); } #[test] diff --git a/board/src/board.rs b/board/src/board.rs index 334885e..ed719a5 100644 --- a/board/src/board.rs +++ b/board/src/board.rs @@ -1,16 +1,22 @@ // Eryn Wells -use crate::{castle, display::DiagramFormatter, Castle, Clock, PieceSet}; +use crate::{ + castle, + display::DiagramFormatter, + piece_sets::{PlacePieceError, PlacePieceStrategy}, + PieceSet, +}; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; -use std::iter::Iterator; +use chessfriend_core::{Color, Piece, Shape, Square}; -#[derive(Clone, Debug, Default, Eq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Board { - pub clock: Clock, + pub active_color: Color, pub pieces: PieceSet, pub castling_rights: castle::Rights, pub en_passant_target: Option, + pub half_move_clock: u32, + pub full_move_number: u32, } impl Board { @@ -46,12 +52,44 @@ impl Board { ..Default::default() } } +} +impl Board { #[must_use] - pub fn player_to_move(&self) -> Color { - self.clock.active_color() + pub fn get_piece(&self, square: Square) -> Option { + self.pieces.get(square) } + /// Place a piece on the board. + /// + /// ## Errors + /// + /// When is called with [`PlacePieceStrategy::PreserveExisting`], and a piece already exists on + /// `square`, this method returns a [`PlacePieceError::ExistingPiece`] error. + /// + pub fn place_piece( + &mut self, + piece: Piece, + square: Square, + strategy: PlacePieceStrategy, + ) -> Result<(), PlacePieceError> { + self.pieces.place(piece, square, strategy) + } + + pub fn remove_piece(&mut self, square: Square) -> Option { + self.pieces.remove(square) + } +} + +impl Board { + #[must_use] + pub fn display(&self) -> DiagramFormatter<'_> { + DiagramFormatter::new(self) + } +} + +/* +impl Board { /// The rook to use for a castling move. #[must_use] pub fn rook_for_castle(&self, player: Color, castle: Castle) -> Option { @@ -71,73 +109,26 @@ impl Board { /// through a square that an enemy piece can see #[must_use] pub fn player_can_castle(&self, player: Color, castle: Castle) -> bool { - if !self.castling_rights.is_set(player, castle.into()) { + if !self.castling_rights.is_set(player, castle) { return false; } let castling_parameters = castle.parameters(player); - let all_pieces = self.all_pieces_bitboard(); + let all_pieces = self.pieces.all_pieces(); if !(all_pieces & castling_parameters.clear_squares()).is_empty() { return false; } - let danger_squares = self.king_danger(player); - if !(danger_squares & castling_parameters.check_squares()).is_empty() { - return false; - } + // TODO: Reimplement king_danger here or in Position. + // let danger_squares = self.king_danger(player); + // if !(danger_squares & castling_parameters.check_squares()).is_empty() { + // return false; + // } true } - /// A [`BitBoard`] representing the set of squares containing a piece. This - /// set is the inverse of [`Board::empty_squares`]. - #[must_use] - pub fn occupied_squares(&self) -> BitBoard { - self.pieces.all_pieces() - } - - #[must_use] - pub fn friendly_pieces_bitboard(&self) -> BitBoard { - self.pieces.all_pieces_of_color(self.clock.active_color()) - } - - #[must_use] - pub fn opposing_pieces_bitboard(&self) -> BitBoard { - self.pieces - .all_pieces_of_color(self.clock.active_color().other()) - } - - #[must_use] - pub fn all_pieces(&self) -> (BitBoard, BitBoard) { - ( - self.friendly_pieces_bitboard(), - self.opposing_pieces_bitboard(), - ) - } - - pub fn all_pieces_bitboard(&self) -> BitBoard { - self.pieces.all_pieces() - } - - pub fn all_pieces_of_color_bitboard(&self, color: Color) -> BitBoard { - self.pieces.all_pieces_of_color(color) - } - - /// A [`BitBoard`] representing the set of squares containing a piece. This - /// set is the inverse of [`Board::occupied_squares`]. - #[must_use] - pub fn empty_squares(&self) -> BitBoard { - !self.occupied_squares() - } - - #[must_use] - pub fn piece_on_square(&self, square: Square) -> Option { - self.pieces - .get(square) - .map(|piece| PlacedPiece::new(piece, square)) - } - pub fn iter_all_pieces(&self) -> impl Iterator + '_ { self.pieces.iter() } @@ -147,58 +138,8 @@ impl Board { .iter() .filter(move |piece| piece.color() == color) } - - #[must_use] - pub fn en_passant_target(&self) -> Option { - self.en_passant_target - } - - fn king_bitboard(&self, player: Color) -> BitBoard { - self.pieces.bitboard_for_piece(Piece::king(player)) - } - - pub(crate) fn king_square(&self, player: Color) -> Square { - self.king_bitboard(player).try_into().unwrap() - } - - #[must_use] - pub fn display(&self) -> DiagramFormatter<'_> { - DiagramFormatter::new(self) - } - - #[must_use] - pub fn bitboard_for_color(&self, color: Color) -> BitBoard { - self.pieces.bitboard_for_color(color) - } - - #[must_use] - pub fn bitboard_for_piece(&self, piece: Piece) -> BitBoard { - self.pieces.bitboard_for_piece(piece) - } - - /// A [`BitBoard`] representing the squares where a king of the given color will - /// be in danger of being captured by the opposing player. If the king is on - /// one of these squares, it is in check. The king cannot move to these - /// squares. - pub(crate) fn king_danger(&self, color: Color) -> BitBoard { - let board_without_king = { - let mut cloned_board = self.clone(); - cloned_board.pieces.remove(self.king_square(color)); - cloned_board - }; - - BitBoard::full() - } -} - -impl PartialEq for Board { - fn eq(&self, other: &Self) -> bool { - self.pieces == other.pieces - && self.castling_rights == other.castling_rights - && self.en_passant_target == other.en_passant_target - && self.clock == other.clock - } } +*/ #[cfg(test)] mod tests { @@ -207,26 +148,11 @@ mod tests { use chessfriend_core::piece; #[test] - fn piece_on_square() { - let pos = test_board![ + fn get_piece_on_square() { + let board = test_board![ Black Bishop on F7, ]; - let piece = pos.piece_on_square(Square::F7); - assert_eq!(piece, Some(piece!(Black Bishop on F7))); - } - - #[test] - fn piece_in_starting_position() { - let board = test_board!(starting); - - assert_eq!( - board.piece_on_square(Square::H1), - Some(piece!(White Rook on H1)) - ); - assert_eq!( - board.piece_on_square(Square::A8), - Some(piece!(Black Rook on A8)) - ); + assert_eq!(board.get_piece(Square::F7), Some(piece!(Black Bishop))); } } diff --git a/board/src/castle/rights.rs b/board/src/castle/rights.rs index c32a46e..e79aa86 100644 --- a/board/src/castle/rights.rs +++ b/board/src/castle/rights.rs @@ -13,22 +13,25 @@ impl Rights { /// as long as they have not moved their king, or the rook on that side of /// the board. #[must_use] - pub fn is_set(self, color: Color, castle: Castle) -> bool { + pub fn color_has_right(self, color: Color, castle: Castle) -> bool { (self.0 & (1 << Self::flag_offset(color, castle))) != 0 } - pub fn set(&mut self, color: Color, castle: Castle) { + pub fn grant(&mut self, color: Color, castle: Castle) { self.0 |= 1 << Self::flag_offset(color, castle); } - pub fn clear(&mut self, color: Color, castle: Castle) { + pub fn revoke(&mut self, color: Color, castle: Castle) { self.0 &= !(1 << Self::flag_offset(color, castle)); } - pub fn clear_all(&mut self) { + /// Revoke castling rights for all colors and all sides of the board. + pub fn revoke_all(&mut self) { self.0 = 0; } +} +impl Rights { fn flag_offset(color: Color, castle: Castle) -> usize { ((color as usize) << 1) + castle as usize } @@ -61,21 +64,21 @@ mod tests { #[test] fn default_rights() { let mut rights = Rights::default(); - assert!(rights.is_set(Color::White, Castle::KingSide)); - assert!(rights.is_set(Color::White, Castle::QueenSide)); - assert!(rights.is_set(Color::Black, Castle::KingSide)); - assert!(rights.is_set(Color::Black, Castle::QueenSide)); + assert!(rights.color_has_right(Color::White, Castle::KingSide)); + assert!(rights.color_has_right(Color::White, Castle::QueenSide)); + assert!(rights.color_has_right(Color::Black, Castle::KingSide)); + assert!(rights.color_has_right(Color::Black, Castle::QueenSide)); - rights.clear(Color::White, Castle::QueenSide); - assert!(rights.is_set(Color::White, Castle::KingSide)); - assert!(!rights.is_set(Color::White, Castle::QueenSide)); - assert!(rights.is_set(Color::Black, Castle::KingSide)); - assert!(rights.is_set(Color::Black, Castle::QueenSide)); + rights.revoke(Color::White, Castle::QueenSide); + assert!(rights.color_has_right(Color::White, Castle::KingSide)); + assert!(!rights.color_has_right(Color::White, Castle::QueenSide)); + assert!(rights.color_has_right(Color::Black, Castle::KingSide)); + assert!(rights.color_has_right(Color::Black, Castle::QueenSide)); - rights.set(Color::White, Castle::QueenSide); - assert!(rights.is_set(Color::White, Castle::KingSide)); - assert!(rights.is_set(Color::White, Castle::QueenSide)); - assert!(rights.is_set(Color::Black, Castle::KingSide)); - assert!(rights.is_set(Color::Black, Castle::QueenSide)); + rights.grant(Color::White, Castle::QueenSide); + assert!(rights.color_has_right(Color::White, Castle::KingSide)); + assert!(rights.color_has_right(Color::White, Castle::QueenSide)); + assert!(rights.color_has_right(Color::Black, Castle::KingSide)); + assert!(rights.color_has_right(Color::Black, Castle::QueenSide)); } } diff --git a/board/src/display.rs b/board/src/display.rs index 053f9a0..8736b84 100644 --- a/board/src/display.rs +++ b/board/src/display.rs @@ -4,6 +4,7 @@ use crate::Board; use chessfriend_core::{File, Rank, Square}; use std::fmt; +#[must_use] pub struct DiagramFormatter<'a>(&'a Board); impl<'a> DiagramFormatter<'a> { @@ -21,8 +22,8 @@ impl<'a> fmt::Display for DiagramFormatter<'a> { for file in File::ALL { let square = Square::from_file_rank(file, rank); - match self.0.piece_on_square(square) { - Some(placed_piece) => write!(f, "{} ", placed_piece.piece())?, + match self.0.get_piece(square) { + Some(piece) => write!(f, "{piece} ")?, None => write!(f, "· ")?, } } diff --git a/board/src/fen.rs b/board/src/fen.rs index a370702..6fe1c08 100644 --- a/board/src/fen.rs +++ b/board/src/fen.rs @@ -1,6 +1,6 @@ // Eryn Wells -use crate::{piece_sets::PlacePieceStrategy, Board, Castle, EnPassant}; +use crate::{piece_sets::PlacePieceStrategy, Board, Castle}; use chessfriend_core::{ coordinates::ParseSquareError, piece, Color, File, Piece, PlacedPiece, Rank, Square, }; @@ -69,7 +69,7 @@ impl ToFenStr for Board { for rank in Rank::ALL.into_iter().rev() { for file in File::ALL { let square = Square::from_file_rank(file, rank); - match self.piece_on_square(square) { + match self.get_piece(square) { Some(piece) => { if empty_squares > 0 { write!(fen_string, "{empty_squares}") @@ -93,7 +93,7 @@ impl ToFenStr for Board { } } - write!(fen_string, " {}", self.player_to_move().to_fen_str()?) + write!(fen_string, " {}", self.active_color.to_fen_str()?) .map_err(ToFenStrError::FmtError)?; let castling = [ @@ -103,7 +103,7 @@ impl ToFenStr for Board { (Color::Black, Castle::QueenSide), ] .map(|(color, castle)| { - if !self.castling_rights.is_set(color, castle) { + if !self.castling_rights.color_has_right(color, castle) { return ""; } @@ -131,10 +131,8 @@ impl ToFenStr for Board { ) .map_err(ToFenStrError::FmtError)?; - write!(fen_string, " {}", self.clock.half_move_number()) - .map_err(ToFenStrError::FmtError)?; - write!(fen_string, " {}", self.clock.full_move_number()) - .map_err(ToFenStrError::FmtError)?; + write!(fen_string, " {}", self.half_move_clock).map_err(ToFenStrError::FmtError)?; + write!(fen_string, " {}", self.full_move_number).map_err(ToFenStrError::FmtError)?; Ok(fen_string) } @@ -156,7 +154,7 @@ impl ToFenStr for Piece { fn to_fen_str(&self) -> Result { let ascii: char = self.to_ascii(); - Ok(String::from(match self.color() { + Ok(String::from(match self.color { Color::White => ascii.to_ascii_uppercase(), Color::Black => ascii.to_ascii_lowercase(), })) @@ -214,20 +212,20 @@ impl FromFenStr for Board { .next() .ok_or(FromFenStrError::MissingField(Field::PlayerToMove))?, )?; - board.clock.active_color = active_color; + board.active_color = active_color; let castling_rights = fields .next() .ok_or(FromFenStrError::MissingField(Field::CastlingRights))?; if castling_rights == "-" { - board.castling_rights.clear_all(); + board.castling_rights.revoke_all(); } else { for ch in castling_rights.chars() { match ch { - 'K' => board.castling_rights.set(Color::White, Castle::KingSide), - 'Q' => board.castling_rights.set(Color::White, Castle::QueenSide), - 'k' => board.castling_rights.set(Color::Black, Castle::KingSide), - 'q' => board.castling_rights.set(Color::Black, Castle::QueenSide), + 'K' => board.castling_rights.grant(Color::White, Castle::KingSide), + 'Q' => board.castling_rights.grant(Color::White, Castle::QueenSide), + 'k' => board.castling_rights.grant(Color::Black, Castle::KingSide), + 'q' => board.castling_rights.grant(Color::Black, Castle::QueenSide), _ => return Err(FromFenStrError::InvalidValue), }; } @@ -245,18 +243,18 @@ impl FromFenStr for Board { let half_move_clock = fields .next() .ok_or(FromFenStrError::MissingField(Field::HalfMoveClock))?; - let half_move_clock: u16 = half_move_clock + let half_move_clock: u32 = half_move_clock .parse() .map_err(FromFenStrError::ParseIntError)?; - board.clock.half_move_number = half_move_clock; + board.half_move_clock = half_move_clock; let full_move_counter = fields .next() .ok_or(FromFenStrError::MissingField(Field::FullMoveCounter))?; - let full_move_counter: u16 = full_move_counter + let full_move_counter: u32 = full_move_counter .parse() .map_err(FromFenStrError::ParseIntError)?; - board.clock.full_move_number = full_move_counter; + board.full_move_number = full_move_counter; debug_assert_eq!(fields.next(), None); diff --git a/board/src/lib.rs b/board/src/lib.rs index 6a5bc2a..f6c8adb 100644 --- a/board/src/lib.rs +++ b/board/src/lib.rs @@ -7,12 +7,14 @@ pub mod fen; pub mod macros; mod board; -mod move_counter; mod piece_sets; pub use board::Board; -pub use move_counter::Clock; use castle::Castle; use en_passant::EnPassant; -use piece_sets::{PieceSet, PlacePieceError, PlacePieceStrategy}; +use piece_sets::PieceSet; + +// Used by macros. +#[allow(unused_imports)] +use piece_sets::{PlacePieceError, PlacePieceStrategy}; diff --git a/board/src/macros.rs b/board/src/macros.rs index f3ca5dc..da1984b 100644 --- a/board/src/macros.rs +++ b/board/src/macros.rs @@ -1,34 +1,19 @@ // Eryn Wells -#[macro_export] -macro_rules! board { - [$($color:ident $shape:ident on $square:ident),* $(,)?] => { - $crate::Builder::new() - $(.place_piece( - chessfriend_core::PlacedPiece::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape), - chessfriend_core::Square::$square - ) - ))* - .build() - }; -} - #[macro_export] macro_rules! test_board { ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => { { let mut board = $crate::Board::empty(); - $(let _ = board.pieces.place( + $(let _ = board.place_piece( chessfriend_core::Piece::new( chessfriend_core::Color::$color, chessfriend_core::Shape::$shape ), - chessfriend_core::Square::$square); + chessfriend_core::Square::$square, + $crate::PlacePieceStrategy::default()); )* - board.clock.active_color = chessfriend_core::Color::$to_move; + board.active_color = chessfriend_core::Color::$to_move; board.en_passant_target = Some(chessfriend_core::Square::$en_passant); println!("{}", board.display()); @@ -39,7 +24,7 @@ macro_rules! test_board { ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ]) => { { let mut board = $crate::Board::empty(); - $(let _ = board.pieces.place( + $(let _ = board.place_piece( chessfriend_core::Piece::new( chessfriend_core::Color::$color, chessfriend_core::Shape::$shape @@ -47,7 +32,7 @@ macro_rules! test_board { chessfriend_core::Square::$square, $crate::PlacePieceStrategy::default()); )* - board.clock.active_color = chessfriend_core::Color::$to_move; + board.active_color = chessfriend_core::Color::$to_move; println!("{}", board.display()); @@ -57,7 +42,7 @@ macro_rules! test_board { ($($color:ident $shape:ident on $square:ident),* $(,)?) => { { let mut board = $crate::Board::empty(); - $(let _ = board.pieces.place( + $(let _ = board.place_piece( chessfriend_core::Piece::new( chessfriend_core::Color::$color, chessfriend_core::Shape::$shape diff --git a/board/src/move_counter.rs b/board/src/move_counter.rs index cd24bdf..5de7760 100644 --- a/board/src/move_counter.rs +++ b/board/src/move_counter.rs @@ -9,32 +9,14 @@ pub enum AdvanceHalfMove { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct Clock { - /// The player who's turn it is to move. - pub(crate) active_color: Color, - /// The number of completed turns. A turn finishes when every player has moved. - pub(crate) full_move_number: u16, + pub full_move_number: u16, /// The number of moves by all players since the last pawn advance or capture. - pub(crate) half_move_number: u16, + pub half_move_number: u16, } impl Clock { - #[must_use] - pub fn active_color(&self) -> Color { - self.active_color - } - - #[must_use] - pub fn full_move_number(&self) -> u16 { - self.full_move_number - } - - #[must_use] - pub fn half_move_number(&self) -> u16 { - self.half_move_number - } - pub fn advance(&mut self, advance_half_move: &AdvanceHalfMove) { let next_color = self.active_color.next(); diff --git a/board/src/piece_sets.rs b/board/src/piece_sets.rs index 2467b9d..8096100 100644 --- a/board/src/piece_sets.rs +++ b/board/src/piece_sets.rs @@ -1,16 +1,15 @@ // Eryn Wells -mod bitboards; mod mailbox; -pub(crate) use mailbox::Mailbox; - -use bitboards::{ByColor, ByColorAndShape}; +use self::mailbox::Mailbox; use chessfriend_bitboard::{BitBoard, IterationDirection}; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; +use std::ops::BitOr; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum PlacePieceStrategy { + #[default] Replace, PreserveExisting, } @@ -20,73 +19,54 @@ pub enum PlacePieceError { ExisitingPiece(PlacedPiece), } -impl Default for PlacePieceStrategy { - fn default() -> Self { - Self::Replace - } -} - /// The internal data structure of a [Board] that efficiently manages the /// placement of pieces on the board. #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] pub struct PieceSet { mailbox: Mailbox, - by_color: ByColor, - by_color_and_shape: ByColorAndShape, + color_occupancy: [BitBoard; Color::NUM], + shape_occupancy: [BitBoard; Shape::NUM], } impl PieceSet { pub(crate) fn new(pieces: [[BitBoard; Shape::NUM]; Color::NUM]) -> Self { - use std::ops::BitOr; - - let white_pieces = pieces[Color::White as usize] - .iter() - .fold(BitBoard::empty(), BitOr::bitor); - let black_pieces = pieces[Color::Black as usize] - .iter() - .fold(BitBoard::empty(), BitOr::bitor); - - let all_pieces = white_pieces | black_pieces; - let mut mailbox = Mailbox::default(); - for c in Color::into_iter() { - for s in Shape::into_iter() { - let bitboard = pieces[c as usize][s as usize]; + let mut color_occupancy: [BitBoard; Color::NUM] = Default::default(); + let mut shape_occupancy: [BitBoard; Shape::NUM] = Default::default(); + + for (color_index, color) in Color::iter().enumerate() { + for (shape_index, shape) in Shape::into_iter().enumerate() { + let bitboard = pieces[color_index][shape_index]; + + color_occupancy[color_index] |= bitboard; + shape_occupancy[shape_index] |= bitboard; + for square in bitboard.occupied_squares(&IterationDirection::default()) { - mailbox.set(Piece::new(c, s), square); + let piece = Piece::new(*color, shape); + mailbox.set(piece, square); } } } Self { - by_color: ByColor::new(all_pieces, [white_pieces, black_pieces]), - by_color_and_shape: ByColorAndShape::new(pieces), mailbox, + color_occupancy, + shape_occupancy, } } /// A [`BitBoard`] representing all the pieces currently on the board. Other /// engines might refer to this concept as 'occupancy'. - pub(crate) fn all_pieces(&self) -> BitBoard { - self.by_color.all() - } - - pub(crate) fn all_pieces_of_color(&self, color: Color) -> BitBoard { - self.by_color.bitboard(color) + pub fn all_pieces(&self) -> BitBoard { + self.color_occupancy + .iter() + .fold(BitBoard::empty(), BitOr::bitor) } pub(crate) fn iter(&self) -> impl Iterator { self.mailbox.iter() } - pub(super) fn bitboard_for_color(&self, color: Color) -> BitBoard { - self.by_color.bitboard(color) - } - - pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard { - self.by_color_and_shape.bitboard_for_piece(piece) - } - pub(crate) fn get(&self, square: Square) -> Option { self.mailbox.get(square) } @@ -96,9 +76,7 @@ impl PieceSet { piece: Piece, square: Square, strategy: PlacePieceStrategy, - ) -> Result { - let color = piece.color(); - + ) -> Result<(), PlacePieceError> { if strategy == PlacePieceStrategy::PreserveExisting { if let Some(existing_piece) = self.mailbox.get(square) { return Err(PlacePieceError::ExisitingPiece(PlacedPiece::new( @@ -108,35 +86,78 @@ impl PieceSet { } } - let piece: Piece = piece.into(); - self.by_color_and_shape.set_square(square, piece); - self.by_color.set_square(square, color); + let color = piece.color; + let shape = piece.shape; + + self.color_occupancy[color as usize].set(square); + self.shape_occupancy[shape as usize].set(square); self.mailbox.set(piece, square); - Ok(PlacedPiece::new(piece, square)) + Ok(()) } - pub(crate) fn remove(&mut self, square: Square) -> Option { + pub(crate) fn remove(&mut self, square: Square) -> Option { if let Some(piece) = self.mailbox.get(square) { - self.by_color_and_shape.clear_square(square, piece.into()); - self.by_color.clear_square(square, piece.color()); + self.color_occupancy[piece.color as usize].clear(square); + self.shape_occupancy[piece.shape as usize].clear(square); self.mailbox.remove(square); - Some(PlacedPiece::new(piece, square)) + Some(piece) } else { None } } } +impl PieceSet { + pub fn color_bitboard(&self, color: Color) -> BitBoard { + self.color_occupancy[color as usize] + } + + pub fn piece_bitboard(&self, piece: Piece) -> BitBoard { + let color_occupancy = self.color_occupancy[piece.color as usize]; + let shape_occupancy = self.shape_occupancy[piece.shape as usize]; + color_occupancy & shape_occupancy + } +} + impl FromIterator for PieceSet { fn from_iter>(iter: T) -> Self { let mut pieces: Self = Self::default(); for piece in iter { - let _ = pieces.place(piece.piece(), piece.square(), PlacePieceStrategy::default()); + let _ = pieces.place(piece.piece, piece.square, PlacePieceStrategy::default()); } pieces } } + +#[cfg(test)] +mod tests { + use super::*; + use chessfriend_bitboard::bitboard; + + #[test] + fn place_piece() -> Result<(), PlacePieceError> { + let mut pieces = PieceSet::default(); + + pieces.place( + Piece::king(Color::White), + Square::F5, + PlacePieceStrategy::default(), + )?; + + assert_eq!( + pieces.mailbox.get(Square::F5), + Some(Piece::king(Color::White)) + ); + assert_eq!(pieces.color_bitboard(Color::White), bitboard![F5]); + assert_eq!( + pieces.piece_bitboard(Piece::king(Color::White)), + bitboard![F5] + ); + + Ok(()) + } +} diff --git a/board/src/piece_sets/bitboards.rs b/board/src/piece_sets/bitboards.rs index 5c09432..c8c8959 100644 --- a/board/src/piece_sets/bitboards.rs +++ b/board/src/piece_sets/bitboards.rs @@ -39,11 +39,11 @@ impl ByColorAndShape { } pub(super) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard { - self.0[piece.color() as usize][piece.shape() as usize] + self.0[piece.color as usize][piece.shape as usize] } pub(super) fn bitboard_for_piece_mut(&mut self, piece: Piece) -> &mut BitBoard { - &mut self.0[piece.color() as usize][piece.shape() as usize] + &mut self.0[piece.color as usize][piece.shape as usize] } pub(super) fn set_square(&mut self, square: Square, piece: Piece) { diff --git a/board/src/piece_sets/mailbox.rs b/board/src/piece_sets/mailbox.rs index 4fc9ec4..0ab8e21 100644 --- a/board/src/piece_sets/mailbox.rs +++ b/board/src/piece_sets/mailbox.rs @@ -7,23 +7,23 @@ use std::iter::FromIterator; pub(crate) struct Mailbox([Option; Square::NUM]); impl Mailbox { - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self::default() } - pub(crate) fn get(&self, square: Square) -> Option { + pub fn get(&self, square: Square) -> Option { self.0[square as usize] } - pub(crate) fn set(&mut self, piece: Piece, square: Square) { + pub fn set(&mut self, piece: Piece, square: Square) { self.0[square as usize] = Some(piece); } - pub(crate) fn remove(&mut self, square: Square) { + pub fn remove(&mut self, square: Square) { self.0[square as usize] = None; } - pub(crate) fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0 .into_iter() .flatten() // Remove the Nones @@ -50,7 +50,7 @@ impl FromIterator for Mailbox { fn from_iter>(iter: T) -> Self { iter.into_iter() .fold(Self::new(), |mut mailbox, placed_piece| { - mailbox.set(placed_piece.piece(), placed_piece.square()); + mailbox.set(placed_piece.piece(), placed_piece.square); mailbox }) } diff --git a/core/src/colors.rs b/core/src/colors.rs index 9d67877..fee6558 100644 --- a/core/src/colors.rs +++ b/core/src/colors.rs @@ -1,6 +1,6 @@ // Eryn Wells -use crate::{errors::TryFromCharError, try_from_string}; +use crate::{errors::TryFromCharError, try_from_string, Direction}; use std::fmt; #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] @@ -21,6 +21,7 @@ impl Color { Color::ALL.iter() } + #[must_use] pub fn into_iter() -> std::array::IntoIter { Color::ALL.into_iter() } @@ -33,6 +34,20 @@ impl Color { Color::Black => Color::White, } } + + /// "Forward" direction of pawn pushes for this color. + #[must_use] + pub fn push_direction(&self) -> Direction { + match self { + Color::White => Direction::North, + Color::Black => Direction::South, + } + } + + #[must_use] + pub const fn next(&self) -> Color { + Self::ALL[((*self as usize) + 1) % Self::NUM] + } } impl fmt::Display for Color { diff --git a/core/src/coordinates.rs b/core/src/coordinates.rs index e796949..9bdfc23 100644 --- a/core/src/coordinates.rs +++ b/core/src/coordinates.rs @@ -217,6 +217,43 @@ coordinate_enum!(Square, [ A8, B8, C8, D8, E8, F8, G8, H8 ]); +/// Generate an enum that maps its values to variants of [Square]. +macro_rules! to_square_enum { + ($vis:vis $name:ident { $($variant:ident)* }) => { + #[repr(u8)] + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + $vis enum $name { + $($variant = Square::$variant as u8,)* + } + + impl From<$name> for Square { + fn from(value: $name) -> Self { + unsafe { Square::from_index_unchecked(value as u8) } + } + } + }; +} + +to_square_enum!( + pub EnPassantTargetSquare { + A3 B3 C3 D3 E3 F3 G3 H3 + A6 B6 C6 D6 E6 F6 G6 H6 + } +); + +// impl TryFrom for EnPassantTargetSquare { +// type Error = (); + +// fn try_from(value: Square) -> Result { +// let square = Self::ALL[value as usize]; +// if square as usize == value as usize { +// Ok(square) +// } else { +// Err(()) +// } +// } +// } + impl Square { /// # Safety /// @@ -240,20 +277,24 @@ impl Square { s.parse() } + #[must_use] #[inline] pub fn file(self) -> File { unsafe { File::new_unchecked((self as u8) & 0b000_00111) } } + #[must_use] #[inline] pub fn rank(self) -> Rank { unsafe { Rank::new_unchecked((self as u8) >> 3) } } + #[must_use] pub fn file_rank(&self) -> (File, Rank) { (self.file(), self.rank()) } + #[must_use] pub fn neighbor(self, direction: Direction) -> Option { let index: u8 = self as u8; let dir: i8 = direction.to_offset(); @@ -347,10 +388,9 @@ impl From for char { } } -impl Into for Rank { - fn into(self) -> char { - let value: u8 = self.into(); - (value + b'1') as char +impl From for char { + fn from(value: Rank) -> Self { + Self::from(value.0) } } diff --git a/core/src/pieces.rs b/core/src/pieces.rs index 00d0536..dd6de93 100644 --- a/core/src/pieces.rs +++ b/core/src/pieces.rs @@ -3,6 +3,34 @@ use crate::{errors::TryFromCharError, try_from_string, Color, Square}; use std::{array, fmt, slice}; +trait _Shape { + fn symbol(&self) -> char; + fn index(&self) -> usize; +} + +macro_rules! shape { + ($name:ident, $index:expr, $symbol:expr) => { + struct $name; + + impl _Shape for $name { + fn symbol(&self) -> char { + $symbol + } + + fn index(&self) -> usize { + $index + } + } + }; +} + +shape!(Pawn, 0, 'P'); +shape!(Knight, 1, 'K'); +shape!(Bishop, 2, 'B'); +shape!(Rook, 3, 'R'); +shape!(Queen, 4, 'Q'); +shape!(King, 5, 'K'); + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Shape { Pawn = 0, @@ -94,8 +122,8 @@ impl fmt::Display for Shape { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Piece { - color: Color, - shape: Shape, + pub color: Color, + pub shape: Shape, } macro_rules! piece_constructor { @@ -132,16 +160,6 @@ impl Piece { piece_constructor!(queen, Queen); piece_constructor!(king, King); - #[must_use] - pub fn color(&self) -> Color { - self.color - } - - #[must_use] - pub fn shape(&self) -> Shape { - self.shape - } - is_shape!(is_pawn, Pawn); is_shape!(is_knight, Knight); is_shape!(is_bishop, Bishop); @@ -195,10 +213,11 @@ impl From<&PlacedPiece> for Piece { } } +#[deprecated] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct PlacedPiece { - piece: Piece, - square: Square, + pub piece: Piece, + pub square: Square, } macro_rules! is_shape { @@ -223,27 +242,6 @@ impl PlacedPiece { self.piece } - /// The square the piece is on - #[inline] - #[must_use] - pub fn square(&self) -> Square { - self.square - } - - /// The piece's [Color] - #[inline] - #[must_use] - pub fn color(&self) -> Color { - self.piece.color - } - - /// The piece's [Shape] - #[inline] - #[must_use] - pub fn shape(&self) -> Shape { - self.piece.shape - } - is_shape!(is_pawn, Pawn); is_shape!(is_knight, Knight); is_shape!(is_bishop, Bishop); @@ -254,7 +252,7 @@ impl PlacedPiece { #[must_use] pub fn is_kingside_rook(&self) -> bool { self.is_rook() - && match self.color() { + && match self.piece.color { Color::White => self.square == Square::H1, Color::Black => self.square == Square::H8, } @@ -263,7 +261,7 @@ impl PlacedPiece { #[must_use] pub fn is_queenside_rook(&self) -> bool { self.is_rook() - && match self.color() { + && match self.piece.color { Color::White => self.square == Square::A1, Color::Black => self.square == Square::A8, } @@ -294,6 +292,6 @@ mod tests { #[test] fn shape_into_char() { - assert_eq!(>::into(Shape::Pawn) as char, 'P'); + assert_eq!(>::into(Shape::Pawn), 'P'); } } diff --git a/moves/src/builder.rs b/moves/src/builder.rs index 0ead866..e75a511 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -16,6 +16,12 @@ pub enum Error { InvalidEnPassantSquare, } +const MASK: u16 = 0b111_111; + +fn build_move_bits(origin_square: Square, target_square: Square) -> u16 { + (origin_square as u16 & MASK) << 4 | (target_square as u16 & MASK) << 10 +} + pub trait Style { fn origin_square(&self) -> Option { None @@ -25,22 +31,18 @@ pub trait Style { None } - fn into_move_bits(&self) -> EncodedMoveResult { + fn move_bits(&self) -> EncodedMoveResult { let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)?; let target_square = self.target_square().ok_or(Error::MissingTargetSquare)?; - Ok(self._build_move_bits(origin_square, target_square)) + Ok(build_move_bits(origin_square, target_square)) } - unsafe fn into_move_bits_unchecked(&self) -> u16 { + unsafe fn move_bits_unchecked(&self) -> u16 { let origin_square = self.origin_square().unwrap(); let target_square = self.target_square().unwrap(); - self._build_move_bits(origin_square, target_square) - } - - fn _build_move_bits(&self, origin_square: Square, target_square: Square) -> u16 { - (origin_square as u16 & 0b111111) << 4 | (target_square as u16 & 0b111111) << 10 + build_move_bits(origin_square, target_square) } } @@ -88,12 +90,6 @@ pub struct Castle { castle: castle::Castle, } -impl EnPassantCapture { - fn _build_move_bits(&self, origin_square: Square, target_square: Square) -> u16 { - (origin_square as u16 & 0b111111) << 4 | (target_square as u16 & 0b111111) << 10 - } -} - impl Style for Null {} impl Style for Push { @@ -137,10 +133,12 @@ impl Style for DoublePush { Some(self.to) } - fn into_move_bits(&self) -> StdResult { - Ok(Kind::DoublePush as u16 - | (self.from as u16 & 0b111111) << 4 - | (self.to as u16 & 0b111111) << 10) + fn move_bits(&self) -> StdResult { + Ok( + Kind::DoublePush as u16 + | (self.from as u16 & MASK) << 4 + | (self.to as u16 & MASK) << 10, + ) } } @@ -153,11 +151,11 @@ impl Style for EnPassantCapture { self.push.to } - fn into_move_bits(&self) -> EncodedMoveResult { + fn move_bits(&self) -> EncodedMoveResult { let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)?; let target_square = self.target_square().ok_or(Error::MissingTargetSquare)?; - Ok(self._build_move_bits(origin_square, target_square)) + Ok(build_move_bits(origin_square, target_square)) } } @@ -182,7 +180,7 @@ impl Style for Promotion { } impl Promotion { - fn into_move_bits(&self) -> StdResult { + fn move_bits(&self) -> StdResult { let origin_square = self .style .origin_square() @@ -194,13 +192,13 @@ impl Promotion { Ok(Kind::Promotion as u16 | self.promotion as u16 - | (origin_square & 0b111111) << 4 - | (target_square & 0b111111) << 10) + | (origin_square & MASK << 4) + | (target_square & MASK << 10)) } } impl Promotion { - fn into_move_bits(&self) -> StdResult { + fn move_bits(&self) -> StdResult { let origin_square = self .style .origin_square() @@ -212,25 +210,28 @@ impl Promotion { Ok(Kind::CapturePromotion as u16 | self.promotion as u16 - | (origin_square & 0b111111) << 4 - | (target_square & 0b111111) << 10) + | (origin_square & MASK) << 4 + | (target_square & MASK) << 10) } } impl Builder { + #[must_use] pub fn new() -> Self { - Self { style: Null } + Self::default() } + #[must_use] pub fn push(piece: &PlacedPiece) -> Builder { Builder { style: Push { - from: Some(piece.square()), + from: Some(piece.square), to: None, }, } } + #[must_use] pub fn double_push(file: File, color: Color) -> Builder { let (from, to) = match color { Color::White => ( @@ -248,16 +249,19 @@ impl Builder { } } + #[must_use] pub fn castling(color: Color, castle: castle::Castle) -> Builder { Builder { style: Castle { color, castle }, } } + #[must_use] pub fn capturing_piece(piece: &PlacedPiece, capturing: &PlacedPiece) -> Builder { - Self::push(piece).capturing_piece(&capturing) + Self::push(piece).capturing_piece(capturing) } + #[must_use] pub fn from(&self, square: Square) -> Builder { Builder { style: Push { @@ -267,11 +271,18 @@ impl Builder { } } + #[must_use] pub fn build(&self) -> Move { Move(0) } } +impl Default for Builder { + fn default() -> Self { + Self { style: Null } + } +} + impl Builder { pub fn from(&mut self, square: Square) -> &mut Self { self.style.from = Some(square); @@ -283,6 +294,7 @@ impl Builder { self } + #[must_use] pub fn capturing_on(&self, square: Square) -> Builder { let mut style = self.style.clone(); style.to = Some(square); @@ -295,6 +307,7 @@ impl Builder { } } + #[must_use] pub fn capturing_en_passant_on(&self, target_square: Square) -> Builder { match EnPassant::from_target_square(target_square) { Some(en_passant) => { @@ -312,15 +325,17 @@ impl Builder { } } + #[must_use] pub fn capturing_piece(&self, piece: &PlacedPiece) -> Builder { Builder { style: Capture { push: self.style.clone(), - capture: Some(piece.square()), + capture: Some(piece.square), }, } } + #[must_use] pub fn promoting_to(&self, shape: PromotionShape) -> Builder> { Builder { style: Promotion { @@ -331,7 +346,7 @@ impl Builder { } pub fn build(&self) -> Result { - Ok(Move(Kind::Quiet as u16 | self.style.into_move_bits()?)) + Ok(Move(Kind::Quiet as u16 | self.style.move_bits()?)) } } @@ -346,11 +361,12 @@ impl Builder { } pub fn build(&self) -> Result { - Ok(Move(self.bits() | self.style.into_move_bits()?)) + Ok(Move(self.bits() | self.style.move_bits()?)) } } impl Builder { + #[must_use] pub fn promoting_to(self, shape: PromotionShape) -> Builder> { Builder { style: Promotion { @@ -361,36 +377,42 @@ impl Builder { } pub fn build(&self) -> Result { - Ok(Move(Kind::Capture as u16 | self.style.into_move_bits()?)) + Ok(Move(Kind::Capture as u16 | self.style.move_bits()?)) } } impl Builder { pub fn build(&self) -> Result { - Ok(Move(Kind::DoublePush as u16 | self.style.into_move_bits()?)) + Ok(Move(Kind::DoublePush as u16 | self.style.move_bits()?)) } } impl Builder { + /// Builds an en passant move. + /// + /// ## Safety + /// + /// This method builds without doing error checking. + #[must_use] pub unsafe fn build_unchecked(&self) -> Move { - Move(Kind::EnPassantCapture as u16 | self.style.into_move_bits_unchecked()) + Move(Kind::EnPassantCapture as u16 | self.style.move_bits_unchecked()) } pub fn build(&self) -> Result { Ok(Move( - Kind::EnPassantCapture as u16 | self.style.into_move_bits()?, + Kind::EnPassantCapture as u16 | self.style.move_bits()?, )) } } impl Builder> { pub fn build(&self) -> Result { - Ok(Move(self.style.into_move_bits()?)) + Ok(Move(self.style.move_bits()?)) } } impl Builder> { pub fn build(&self) -> Result { - Ok(Move(self.style.into_move_bits()?)) + Ok(Move(self.style.move_bits()?)) } } diff --git a/moves/src/defs.rs b/moves/src/defs.rs index 337f30e..8254cb9 100644 --- a/moves/src/defs.rs +++ b/moves/src/defs.rs @@ -2,6 +2,7 @@ use chessfriend_core::Shape; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum Kind { Quiet = 0b0000, DoublePush = 0b0001, @@ -13,6 +14,12 @@ pub(crate) enum Kind { CapturePromotion = 0b1100, } +impl Kind { + fn is_reversible(self) -> bool { + (self as u16) & 0b1100 == 0 + } +} + #[repr(u16)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum PromotionShape { @@ -32,3 +39,17 @@ impl From for Shape { } } } + +impl TryFrom for PromotionShape { + type Error = (); + + fn try_from(value: Shape) -> Result { + match value { + Shape::Knight => Ok(PromotionShape::Knight), + Shape::Bishop => Ok(PromotionShape::Bishop), + Shape::Rook => Ok(PromotionShape::Rook), + Shape::Queen => Ok(PromotionShape::Queen), + _ => Err(()), + } + } +} diff --git a/moves/src/moves.rs b/moves/src/moves.rs index 8e7c926..b7d2539 100644 --- a/moves/src/moves.rs +++ b/moves/src/moves.rs @@ -5,19 +5,29 @@ use chessfriend_board::castle::Castle; use chessfriend_core::{Rank, Shape, Square}; use std::fmt; -/// A single player's move. In chess parlance, this is a "ply". +/// A single player's move. In game theory parlance, this is a "ply". +/// +/// ## TODO +/// +/// - Rename this class `Ply`. +/// #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct Move(pub(crate) u16); impl Move { + #[must_use] + #[allow(clippy::missing_panics_doc)] pub fn origin_square(&self) -> Square { - ((self.0 >> 4) & 0b111111).try_into().unwrap() + ((self.0 >> 4) & 0b111_111).try_into().unwrap() } + #[must_use] + #[allow(clippy::missing_panics_doc)] pub fn target_square(&self) -> Square { (self.0 >> 10).try_into().unwrap() } + #[must_use] pub fn capture_square(&self) -> Option { if self.is_en_passant() { let target_square = self.target_square(); @@ -35,18 +45,22 @@ impl Move { None } + #[must_use] pub fn is_quiet(&self) -> bool { self.flags() == Kind::Quiet as u16 } + #[must_use] pub fn is_double_push(&self) -> bool { self.flags() == Kind::DoublePush as u16 } + #[must_use] pub fn is_castle(&self) -> bool { self.castle().is_some() } + #[must_use] pub fn castle(&self) -> Option { match self.flags() { 0b0010 => Some(Castle::KingSide), @@ -55,18 +69,22 @@ impl Move { } } + #[must_use] pub fn is_capture(&self) -> bool { (self.0 & 0b0100) != 0 } + #[must_use] pub fn is_en_passant(&self) -> bool { self.flags() == 0b0101 } + #[must_use] pub fn is_promotion(&self) -> bool { (self.0 & 0b1000) != 0 } + #[must_use] pub fn promotion(&self) -> Option { if !self.is_promotion() { return None; @@ -80,18 +98,22 @@ impl Move { _ => unreachable!(), }) } +} +impl Move { #[inline] - fn flags(&self) -> u16 { + fn flags(self) -> u16 { self.0 & 0b1111 } #[inline] - fn special(&self) -> u16 { + fn special(self) -> u16 { self.0 & 0b11 } +} - fn _transfer_char(&self) -> char { +impl Move { + fn _transfer_char(self) -> char { if self.is_capture() || self.is_en_passant() { 'x' } else { @@ -103,22 +125,19 @@ impl Move { impl fmt::Display for Move { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(castle) = self.castle() { - match castle { - Castle::KingSide => return write!(f, "0-0"), - Castle::QueenSide => return write!(f, "0-0-0"), - } + return match castle { + Castle::KingSide => write!(f, "0-0"), + Castle::QueenSide => write!(f, "0-0-0"), + }; } - write!( - f, - "{}{}{}", - self.origin_square(), - self._transfer_char(), - self.target_square() - )?; + let origin = self.origin_square(); + let target = self.target_square(); + let transfer_char = self._transfer_char(); + write!(f, "{origin}{transfer_char}{target}")?; if let Some(promotion) = self.promotion() { - write!(f, "={}", promotion)?; + write!(f, "={promotion}")?; } else if self.is_en_passant() { write!(f, " e.p.")?; } diff --git a/moves/tests/flags.rs b/moves/tests/flags.rs index 6d001cb..c611f31 100644 --- a/moves/tests/flags.rs +++ b/moves/tests/flags.rs @@ -1,7 +1,8 @@ // Eryn Wells +use chessfriend_board::castle::Castle; use chessfriend_core::{piece, Color, File, Shape, Square}; -use chessfriend_moves::{testing::*, Builder, Castle, PromotionShape}; +use chessfriend_moves::{testing::*, Builder, PromotionShape}; macro_rules! assert_flag { ($move:expr, $left:expr, $right:expr, $desc:expr) => { diff --git a/position/Cargo.toml b/position/Cargo.toml index 07157eb..949e00c 100644 --- a/position/Cargo.toml +++ b/position/Cargo.toml @@ -8,4 +8,5 @@ edition = "2021" [dependencies] chessfriend_core = { path = "../core" } chessfriend_bitboard = { path = "../bitboard" } +chessfriend_board = { path = "../board" } chessfriend_moves = { path = "../moves" } diff --git a/position/src/check.rs b/position/src/check.rs index 06baf74..8619e29 100644 --- a/position/src/check.rs +++ b/position/src/check.rs @@ -40,8 +40,8 @@ impl CheckingPieces { /// A BitBoard representing the set of squares to which a player can move a piece to block a /// checking piece. - pub fn push_mask(&self, king: &BitBoard) -> BitBoard { - let target = king.first_occupied_square().unwrap(); + pub fn push_mask(&self, king: BitBoard) -> BitBoard { + let target = king.first_occupied_square_leading().unwrap(); macro_rules! push_mask_for_shape { ($push_mask:expr, $shape:ident, $king:expr) => {{ diff --git a/position/src/lib.rs b/position/src/lib.rs index c740782..4584f3b 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -13,4 +13,4 @@ mod macros; #[macro_use] mod testing; -pub use position::{MakeMoveError, MoveBuilder as MakeMoveBuilder, Position, PositionBuilder}; +pub use position::{MakeMoveError, MoveBuilder as MakeMoveBuilder, Position}; diff --git a/position/src/macros.rs b/position/src/macros.rs index 260a502..3bce9c1 100644 --- a/position/src/macros.rs +++ b/position/src/macros.rs @@ -3,16 +3,7 @@ #[macro_export] macro_rules! position { [$($color:ident $shape:ident on $square:ident),* $(,)?] => { - $crate::PositionBuilder::new() - $(.place_piece( - chessfriend_core::PlacedPiece::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape), - chessfriend_core::Square::$square - ) - ))* - .build() + $crate::Position::new(chessfriend_board::board!($($color $shape on $square),*)) }; } @@ -21,72 +12,26 @@ macro_rules! position { macro_rules! test_position { ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => { { - let pos = $crate::PositionBuilder::new() - $(.place_piece( - chessfriend_core::PlacedPiece::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape - ), - chessfriend_core::Square::$square - )) - )* - .to_move(chessfriend_core::Color::$to_move) - .en_passant(Some(chessfriend_moves::EnPassant::from_target_square(chessfriend_core::Square::$en_passant)).unwrap()) - .build(); - println!("{pos}"); - - pos + let board = chessfriend_board::test_board!($to_move, [ $($color $shape on $square),*], $en_passant); + $crate::Position::new(board) } }; ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ]) => { { - let pos = $crate::PositionBuilder::new() - $(.place_piece( - chessfriend_core::PlacedPiece::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape - ), - chessfriend_core::Square::$square - )) - )* - .to_move(chessfriend_core::Color::$to_move) - .build(); - println!("{pos}"); - - pos + let board = chessfriend_board::test_board!($to_move, [ $($color $shape on $square),* ]); + $crate::Position::new(board) } }; ($($color:ident $shape:ident on $square:ident),* $(,)?) => { { - let pos = $crate::PositionBuilder::new() - $(.place_piece( - chessfriend_core::PlacedPiece::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape - ), - chessfriend_core::Square::$square - )) - )* - .build(); - println!("{pos}"); - pos + let board = chessfriend_board::test_board!($($color $shape on $square),*); + $crate::Position::new(board) } }; (empty) => { - { - let pos = Position::empty(); - println!("{pos}"); - pos - } + Position::new(chessfriend_board::test_board!(empty)) }; (starting) => { - { - let pos = Position::starting(); - println!("{pos}"); - pos - } + Position::new(chessfriend_board::test_board!(starting)) }; } diff --git a/position/src/move_generator.rs b/position/src/move_generator.rs index c4271ce..2f23616 100644 --- a/position/src/move_generator.rs +++ b/position/src/move_generator.rs @@ -21,6 +21,7 @@ use self::{ }; use crate::Position; use chessfriend_bitboard::BitBoard; +use chessfriend_board::Board; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; use chessfriend_moves::Move; use std::collections::BTreeMap; @@ -50,7 +51,7 @@ macro_rules! move_generator_declaration { ($name:ident, new) => { impl $name { pub(super) fn new( - position: &$crate::Position, + board: &chessfriend_board::Board, color: chessfriend_core::Color, capture_mask: chessfriend_bitboard::BitBoard, push_mask: chessfriend_bitboard::BitBoard, @@ -58,7 +59,7 @@ macro_rules! move_generator_declaration { let move_sets = if Self::shape() == chessfriend_core::Shape::King || !(capture_mask.is_empty() && push_mask.is_empty()) { - Self::move_sets(position, color, capture_mask, push_mask) + Self::move_sets(board, color, capture_mask, push_mask) } else { std::collections::BTreeMap::new() }; @@ -101,27 +102,26 @@ trait MoveGeneratorInternal { } fn move_sets( - position: &Position, + board: &Board, color: Color, capture_mask: BitBoard, push_mask: BitBoard, ) -> BTreeMap { let piece = Self::piece(color); BTreeMap::from_iter( - position + board .bitboard_for_piece(piece) .occupied_squares() .map(|square| { let piece = PlacedPiece::new(piece, square); - let move_set = - Self::move_set_for_piece(position, &piece, capture_mask, push_mask); + let move_set = Self::move_set_for_piece(board, &piece, capture_mask, push_mask); (square, move_set) }), ) } fn move_set_for_piece( - position: &Position, + board: &Board, piece: &PlacedPiece, capture_mask: BitBoard, push_mask: BitBoard, @@ -139,19 +139,14 @@ pub struct Moves { } impl Moves { - pub fn new( - position: &Position, - color: Color, - capture_mask: BitBoard, - push_mask: BitBoard, - ) -> Moves { + pub fn new(board: &Board, color: Color, capture_mask: BitBoard, push_mask: BitBoard) -> Moves { Moves { - pawn_moves: PawnMoveGenerator::new(position, color, capture_mask, push_mask), - knight_moves: KnightMoveGenerator::new(position, color, capture_mask, push_mask), - bishop_moves: BishopMoveGenerator::new(position, color, capture_mask, push_mask), - rook_moves: RookMoveGenerator::new(position, color, capture_mask, push_mask), - queen_moves: QueenMoveGenerator::new(position, color, capture_mask, push_mask), - king_moves: KingMoveGenerator::new(position, color, capture_mask, push_mask), + pawn_moves: PawnMoveGenerator::new(board, color, capture_mask, push_mask), + knight_moves: KnightMoveGenerator::new(board, color, capture_mask, push_mask), + bishop_moves: BishopMoveGenerator::new(board, color, capture_mask, push_mask), + rook_moves: RookMoveGenerator::new(board, color, capture_mask, push_mask), + queen_moves: QueenMoveGenerator::new(board, color, capture_mask, push_mask), + king_moves: KingMoveGenerator::new(board, color, capture_mask, push_mask), } } diff --git a/position/src/move_generator/bishop.rs b/position/src/move_generator/bishop.rs index a57b1a4..45d9086 100644 --- a/position/src/move_generator/bishop.rs +++ b/position/src/move_generator/bishop.rs @@ -1,8 +1,8 @@ // Eryn Wells use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::Position; use chessfriend_bitboard::BitBoard; +use chessfriend_board::Board; use chessfriend_core::{Direction, PlacedPiece, Shape}; move_generator_declaration!(ClassicalMoveGenerator); @@ -13,16 +13,16 @@ impl MoveGeneratorInternal for ClassicalMoveGenerator { } fn move_set_for_piece( - position: &Position, + board: &Board, piece: &PlacedPiece, capture_mask: BitBoard, push_mask: BitBoard, ) -> MoveSet { let square = piece.square(); - let blockers = position.occupied_squares(); + let blockers = board.occupied_squares(); let empty_squares = !blockers; - let (friendly_pieces, opposing_pieces) = position.all_pieces(); + let (friendly_pieces, opposing_pieces) = board.all_pieces(); let mut all_moves = BitBoard::empty(); @@ -58,7 +58,7 @@ impl MoveGeneratorInternal for ClassicalMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{position, position::DiagramFormatter}; + use crate::position; use chessfriend_bitboard::BitBoard; use chessfriend_core::Color; @@ -69,7 +69,7 @@ mod tests { ]; let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), @@ -87,10 +87,10 @@ mod tests { White Knight on E5, ]; - println!("{}", DiagramFormatter::new(&pos)); + println!("{}", pos.display()); let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), @@ -109,7 +109,7 @@ mod tests { ]; let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), @@ -125,10 +125,10 @@ mod tests { White Bishop on E4, ]; - println!("{}", DiagramFormatter::new(&pos)); + println!("{}", pos.display()); let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let bitboard = generator._test_bitboard(); let expected = BitBoard::new( 0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010, diff --git a/position/src/move_generator/king.rs b/position/src/move_generator/king.rs index 3443d46..4daa155 100644 --- a/position/src/move_generator/king.rs +++ b/position/src/move_generator/king.rs @@ -4,10 +4,9 @@ //! generating the possible moves for the king in the given position. use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::Position; use chessfriend_bitboard::BitBoard; +use chessfriend_board::{castle::Castle, Board}; use chessfriend_core::{PlacedPiece, Shape}; -use chessfriend_moves::Castle; move_generator_declaration!(KingMoveGenerator, struct); move_generator_declaration!(KingMoveGenerator, new); @@ -19,7 +18,7 @@ impl MoveGeneratorInternal for KingMoveGenerator { } fn move_set_for_piece( - position: &Position, + board: &Board, placed_piece: &PlacedPiece, _capture_mask: BitBoard, _push_mask: BitBoard, @@ -28,13 +27,13 @@ impl MoveGeneratorInternal for KingMoveGenerator { let color = piece.color(); let square = placed_piece.square(); - let safe_squares = !position.king_danger(color); + let safe_squares = BitBoard::FULL; let all_king_moves = BitBoard::king_moves(square); - let empty_squares = position.empty_squares(); + let empty_squares = board.empty_squares(); let safe_empty_squares = empty_squares & safe_squares; - let opposing_pieces = position.bitboard_for_color(color.other()); + let opposing_pieces = board.bitboard_for_color(color.other()); let opposing_pieces_on_safe_squares = opposing_pieces & safe_squares; let quiet_moves = all_king_moves & safe_empty_squares; @@ -44,10 +43,10 @@ impl MoveGeneratorInternal for KingMoveGenerator { .quiet_moves(quiet_moves) .capture_moves(capture_moves); - if position.player_can_castle(color, Castle::KingSide) { + if board.player_can_castle(color, Castle::KingSide) { move_set.kingside_castle(); } - if position.player_can_castle(color, Castle::QueenSide) { + if board.player_can_castle(color, Castle::QueenSide) { move_set.queenside_castle(); } @@ -58,21 +57,23 @@ impl MoveGeneratorInternal for KingMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{assert_move_list, position, test_position, testing::*, PositionBuilder}; + use crate::{assert_move_list, test_position, testing::*}; use chessfriend_bitboard::bitboard; + use chessfriend_board::castle::Castle; use chessfriend_core::{piece, Color, Square}; - use chessfriend_moves::{Builder as MoveBuilder, Castle, Move}; + use chessfriend_moves::{Builder as MoveBuilder, Move}; use std::collections::HashSet; #[test] fn one_king() -> TestResult { - let pos = position![White King on E4]; + let pos = test_position![White King on E4]; - let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), - bitboard![E5, F5, F4, F3, E3, D3, D4, D5] + bitboard![E5 F5 F4 F3 E3 D3 D4 D5] ); let builder = MoveBuilder::push(&piece!(White King on E4)); @@ -96,15 +97,16 @@ mod tests { #[test] fn one_king_corner() -> TestResult { - let pos = position![White King on A1]; + let pos = test_position![White King on A1]; - let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_bitboard = generator._test_bitboard(); - let expected_bitboard = bitboard![A2, B2, B1]; + let expected_bitboard = bitboard![A2 B2 B1]; assert_eq!( generator._test_bitboard(), - bitboard![A2, B2, B1], + bitboard![A2 B2 B1], "Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}" ); @@ -136,19 +138,19 @@ mod tests { #[test] fn black_king_in_check_by_rook() { - let pos = PositionBuilder::new() - .place_piece(piece!(White King on E1)) - .place_piece(piece!(White Rook on E4)) - .place_piece(piece!(Black King on E7)) - .to_move(Color::Black) - .build(); + let pos = test_position!(Black, [ + White King on E1, + White Rook on E4, + Black King on E7, + ]); assert!(pos.is_king_in_check()); - let generator = KingMoveGenerator::new(&pos, Color::Black, BitBoard::FULL, BitBoard::FULL); + let generator = + KingMoveGenerator::new(&pos.board, Color::Black, BitBoard::FULL, BitBoard::FULL); let generated_moves = generator._test_bitboard(); - let expected_moves = bitboard![F8, F7, F6, D6, D7, D8]; + let expected_moves = bitboard![F8 F7 F6 D6 D7 D8]; assert_eq!(generated_moves, expected_moves); } @@ -164,7 +166,8 @@ mod tests { assert!(pos.player_can_castle(Color::White, Castle::KingSide)); assert!(pos.player_can_castle(Color::White, Castle::QueenSide)); - let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); assert!(generated_moves @@ -187,7 +190,8 @@ mod tests { assert!(pos.player_can_castle(Color::White, Castle::KingSide)); assert!(!pos.player_can_castle(Color::White, Castle::QueenSide)); - let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); assert!(generated_moves @@ -210,7 +214,8 @@ mod tests { assert!(!pos.player_can_castle(Color::White, Castle::KingSide)); assert!(pos.player_can_castle(Color::White, Castle::QueenSide)); - let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + KingMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); assert!(!generated_moves diff --git a/position/src/move_generator/knight.rs b/position/src/move_generator/knight.rs index 2b385ec..76afa44 100644 --- a/position/src/move_generator/knight.rs +++ b/position/src/move_generator/knight.rs @@ -1,8 +1,8 @@ // Eryn Wells use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::Position; use chessfriend_bitboard::BitBoard; +use chessfriend_board::Board; use chessfriend_core::{PlacedPiece, Shape}; move_generator_declaration!(KnightMoveGenerator); @@ -13,13 +13,13 @@ impl MoveGeneratorInternal for KnightMoveGenerator { } fn move_set_for_piece( - position: &Position, + board: &Board, placed_piece: &PlacedPiece, capture_mask: BitBoard, push_mask: BitBoard, ) -> MoveSet { - let opposing_pieces = position.bitboard_for_color(placed_piece.piece().color().other()); - let empty_squares = position.empty_squares(); + let opposing_pieces = board.bitboard_for_color(placed_piece.piece().color().other()); + let empty_squares = board.empty_squares(); let knight_moves = BitBoard::knight_moves(placed_piece.square()); let quiet_moves = knight_moves & empty_squares & push_mask; @@ -46,7 +46,7 @@ mod tests { ]; let generator = - KnightMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + KnightMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet<_> = generator.iter().collect(); let piece = piece!(White Knight on E4); diff --git a/position/src/move_generator/move_set.rs b/position/src/move_generator/move_set.rs index 61adb03..b4ae2d3 100644 --- a/position/src/move_generator/move_set.rs +++ b/position/src/move_generator/move_set.rs @@ -1,8 +1,9 @@ // Eryn Wells use chessfriend_bitboard::BitBoard; +use chessfriend_board::{castle::Castle, en_passant::EnPassant}; use chessfriend_core::{PlacedPiece, Square}; -use chessfriend_moves::{Builder as MoveBuilder, Castle, EnPassant, Move}; +use chessfriend_moves::{Builder as MoveBuilder, Move}; /// A set of bitboards defining the moves for a single piece on the board. #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -63,7 +64,7 @@ impl MoveSet { None => {} } - self.bitboard().is_set(target_square) + self.bitboard().contains(target_square) } pub(crate) fn can_castle(&self, castle: Castle) -> bool { diff --git a/position/src/move_generator/pawn.rs b/position/src/move_generator/pawn.rs index 1a07c4c..ddc1767 100644 --- a/position/src/move_generator/pawn.rs +++ b/position/src/move_generator/pawn.rs @@ -1,10 +1,10 @@ // Eryn Wells use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::Position; use chessfriend_bitboard::BitBoard; +use chessfriend_board::{en_passant::EnPassant, Board}; use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square}; -use chessfriend_moves::{EnPassant, Move}; +use chessfriend_moves::Move; use std::collections::BTreeMap; #[derive(Debug)] @@ -25,21 +25,19 @@ impl MoveGeneratorInternal for PawnMoveGenerator { } fn move_set_for_piece( - position: &Position, + board: &Board, placed_piece: &PlacedPiece, capture_mask: BitBoard, push_mask: BitBoard, ) -> MoveSet { - let capture_moves = Self::attacks(position, &placed_piece) & capture_mask; - let quiet_moves = Self::pushes(position, &placed_piece) & push_mask; + let capture_moves = Self::attacks(board, &placed_piece) & capture_mask; + let quiet_moves = Self::pushes(board, &placed_piece) & push_mask; let mut move_set = MoveSet::new(*placed_piece) .quiet_moves(quiet_moves) .capture_moves(capture_moves); - if let Some(en_passant) = - Self::en_passant(position, placed_piece, &push_mask, &capture_mask) - { + if let Some(en_passant) = Self::en_passant(board, placed_piece, &push_mask, &capture_mask) { move_set.en_passant(en_passant); } @@ -49,13 +47,13 @@ impl MoveGeneratorInternal for PawnMoveGenerator { impl PawnMoveGenerator { pub(super) fn new( - position: &Position, + board: &Board, player_to_move: Color, capture_mask: BitBoard, push_mask: BitBoard, ) -> Self { let move_sets = if !(capture_mask.is_empty() && push_mask.is_empty()) { - Self::move_sets(position, player_to_move, capture_mask, push_mask) + Self::move_sets(board, player_to_move, capture_mask, push_mask) } else { std::collections::BTreeMap::new() }; @@ -68,32 +66,33 @@ impl PawnMoveGenerator { } fn move_sets( - position: &Position, + board: &Board, color: Color, capture_mask: BitBoard, push_mask: BitBoard, ) -> BTreeMap { let piece = Self::piece(color); - let moves_for_pieces = - BTreeMap::from_iter(position.bitboard_for_piece(piece).occupied_squares().map( - |square| { + let moves_for_pieces = BTreeMap::from_iter( + board + .bitboard_for_piece(piece) + .occupied_squares() + .map(|square| { let piece = PlacedPiece::new(piece, square); - let move_set = - Self::move_set_for_piece(position, &piece, capture_mask, push_mask); + let move_set = Self::move_set_for_piece(board, &piece, capture_mask, push_mask); (square, move_set) - }, - )); + }), + ); moves_for_pieces } - fn pushes(position: &Position, piece: &PlacedPiece) -> BitBoard { + fn pushes(board: &Board, piece: &PlacedPiece) -> BitBoard { let color = piece.color(); let square = piece.square(); let bitboard: BitBoard = square.into(); let starting_rank = Rank::PAWN_STARTING_RANKS[color as usize]; - let empty_squares = position.empty_squares(); + let empty_squares = board.empty_squares(); match color { Color::White => { @@ -115,21 +114,21 @@ impl PawnMoveGenerator { } } - fn attacks(position: &Position, piece: &PlacedPiece) -> BitBoard { + fn attacks(board: &Board, piece: &PlacedPiece) -> BitBoard { let color = piece.color(); - let opponent_pieces = position.bitboard_for_color(color.other()); + let opponent_pieces = board.bitboard_for_color(color.other()); BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces } fn en_passant( - position: &Position, + board: &Board, piece: &PlacedPiece, push_mask: &BitBoard, capture_mask: &BitBoard, ) -> Option { - match position.en_passant() { + match board.en_passant() { Some(en_passant) => { let target_square: BitBoard = en_passant.target_square().into(); let capture_square: BitBoard = en_passant.capture_square().into(); @@ -147,7 +146,7 @@ impl PawnMoveGenerator { return None; } - match position.piece_on_square(en_passant.capture_square()) { + match board.piece_on_square(en_passant.capture_square()) { Some(_) => Some(en_passant), None => None, } @@ -173,10 +172,7 @@ impl PawnMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{ - assert_move_list, formatted_move_list, position::DiagramFormatter, test_position, - testing::*, - }; + use crate::{assert_move_list, formatted_move_list, test_position, testing::*}; use chessfriend_core::{piece, Color, Square}; use chessfriend_moves::{Builder as MoveBuilder, Move}; use std::collections::HashSet; @@ -185,7 +181,8 @@ mod tests { fn one_double_push() -> TestResult { let pos = test_position![White Pawn on E2]; - let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let pawn = piece!(White Pawn on E2); let expected_moves = HashSet::from_iter([ @@ -204,7 +201,8 @@ mod tests { fn one_single_push() -> TestResult { let pos = test_position![White Pawn on E3]; - let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet<_> = generator.iter().collect(); let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E3)) @@ -223,9 +221,8 @@ mod tests { White Knight on E4, ]; - println!("{}", DiagramFormatter::new(&pos)); - - let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E2)) .to(Square::E3) @@ -245,7 +242,8 @@ mod tests { White Knight on E3, ]; - let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet<_> = generator.iter().collect(); let expected_moves: HashSet<_> = HashSet::new(); @@ -261,7 +259,8 @@ mod tests { Black Knight on D5, ]; - let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E4)) .capturing_on(Square::D5) @@ -283,7 +282,8 @@ mod tests { Black Queen on F5, ]; - let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + let generator = + PawnMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let builder = MoveBuilder::push(&piece!(White Pawn on E4)); let expected_moves = HashSet::from_iter([ @@ -309,7 +309,8 @@ mod tests { Black Pawn on E4, ], D3); - let generator = PawnMoveGenerator::new(&pos, Color::Black, BitBoard::FULL, BitBoard::FULL); + let generator = + PawnMoveGenerator::new(&pos.board, Color::Black, BitBoard::FULL, BitBoard::FULL); let generated_moves: HashSet = generator.iter().collect(); let builder = MoveBuilder::push(&piece!(Black Pawn on E4)); diff --git a/position/src/move_generator/queen.rs b/position/src/move_generator/queen.rs index e0817cb..b189805 100644 --- a/position/src/move_generator/queen.rs +++ b/position/src/move_generator/queen.rs @@ -1,8 +1,8 @@ // Eryn Wells use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::Position; use chessfriend_bitboard::BitBoard; +use chessfriend_board::Board; use chessfriend_core::{Direction, PlacedPiece, Shape}; move_generator_declaration!(ClassicalMoveGenerator); @@ -13,7 +13,7 @@ impl MoveGeneratorInternal for ClassicalMoveGenerator { } fn move_set_for_piece( - position: &Position, + board: &Board, placed_piece: &PlacedPiece, capture_mask: BitBoard, push_mask: BitBoard, @@ -22,10 +22,10 @@ impl MoveGeneratorInternal for ClassicalMoveGenerator { let color = piece.color(); let square = placed_piece.square(); - let blockers = position.occupied_squares(); + let blockers = board.occupied_squares(); let empty_squares = !blockers; - let friendly_pieces = position.bitboard_for_color(color); - let opposing_pieces = position.bitboard_for_color(color.other()); + let friendly_pieces = board.bitboard_for_color(color); + let opposing_pieces = board.bitboard_for_color(color.other()); let mut all_moves = BitBoard::empty(); @@ -65,22 +65,22 @@ impl MoveGeneratorInternal for ClassicalMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{position, position::DiagramFormatter}; + use crate::test_position; use chessfriend_bitboard::{bitboard, BitBoard}; use chessfriend_core::Color; #[test] fn classical_single_queen_bitboard() { - let pos = position![White Queen on B2]; + let pos = test_position![White Queen on B2]; let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let bitboard = generator._test_bitboard(); let expected = bitboard![ - A2, C2, D2, E2, F2, G2, H2, // Rank - B1, B3, B4, B5, B6, B7, B8, // File - A1, C3, D4, E5, F6, G7, H8, // Diagonal - C1, A3 // Anti-diagonal + A2 C2 D2 E2 F2 G2 H2 // Rank + B1 B3 B4 B5 B6 B7 B8 // File + A1 C3 D4 E5 F6 G7 H8 // Diagonal + C1 A3 // Anti-diagonal ]; assert_eq!( @@ -93,15 +93,13 @@ mod tests { /// Test that a rook can move up to, but not onto, a friendly piece. #[test] fn classical_single_queen_with_same_color_blocker_bitboard() { - let pos = position![ + let pos = test_position![ White Queen on A1, White Knight on E1, ]; - println!("{}", DiagramFormatter::new(&pos)); - let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); let bitboard = generator._test_bitboard(); let expected = BitBoard::new( 0b10000001_01000001_00100001_00010001_00001001_00000101_00000011_00001110, @@ -117,41 +115,39 @@ mod tests { /// Test that a rook can move up to, and then capture, an enemy piece. #[test] fn classical_single_queen_with_opposing_color_blocker_bitboard() { - let pos = position![ + let pos = test_position![ White Queen on B2, Black Knight on E5, ]; - println!("{}", DiagramFormatter::new(&pos)); let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), bitboard![ - A2, C2, D2, E2, F2, G2, H2, // Rank - B1, B3, B4, B5, B6, B7, B8, // File - A1, C3, D4, E5, // Diagonal - C1, A3 // Anti-diagonal + A2 C2 D2 E2 F2 G2 H2 // Rank + B1 B3 B4 B5 B6 B7 B8 // File + A1 C3 D4 E5 // Diagonal + C1 A3 // Anti-diagonal ] ); } #[test] fn classical_single_queen_in_center() { - let pos = position![White Queen on D3]; - println!("{}", DiagramFormatter::new(&pos)); + let pos = test_position![White Queen on D3]; let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), bitboard![ - A3, B3, C3, E3, F3, G3, H3, // Rank - D1, D2, D4, D5, D6, D7, D8, // File - B1, C2, E4, F5, G6, H7, // Diagonal - F1, E2, C4, B5, A6, // Anti-diagonal + A3 B3 C3 E3 F3 G3 H3 // Rank + D1 D2 D4 D5 D6 D7 D8 // File + B1 C2 E4 F5 G6 H7 // Diagonal + F1 E2 C4 B5 A6 // Anti-diagonal ] ); } diff --git a/position/src/move_generator/rook.rs b/position/src/move_generator/rook.rs index f692da5..e01f733 100644 --- a/position/src/move_generator/rook.rs +++ b/position/src/move_generator/rook.rs @@ -1,8 +1,8 @@ // Eryn Wells use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet}; -use crate::Position; use chessfriend_bitboard::BitBoard; +use chessfriend_board::Board; use chessfriend_core::{Direction, PlacedPiece, Shape}; move_generator_declaration!(ClassicalMoveGenerator); @@ -13,7 +13,7 @@ impl MoveGeneratorInternal for ClassicalMoveGenerator { } fn move_set_for_piece( - position: &Position, + board: &Board, placed_piece: &PlacedPiece, capture_mask: BitBoard, push_mask: BitBoard, @@ -22,10 +22,10 @@ impl MoveGeneratorInternal for ClassicalMoveGenerator { let color = piece.color(); let square = placed_piece.square(); - let blockers = position.occupied_squares(); + let blockers = board.occupied_squares(); let empty_squares = !blockers; - let friendly_pieces = position.bitboard_for_color(color); - let opposing_pieces = position.bitboard_for_color(color.other()); + let friendly_pieces = board.bitboard_for_color(color); + let opposing_pieces = board.bitboard_for_color(color.other()); let mut all_moves = BitBoard::empty(); @@ -61,7 +61,7 @@ impl MoveGeneratorInternal for ClassicalMoveGenerator { #[cfg(test)] mod tests { use super::*; - use crate::{position::DiagramFormatter, test_position}; + use crate::test_position; use chessfriend_bitboard::{bitboard, BitBoard}; use chessfriend_core::Color; @@ -70,11 +70,11 @@ mod tests { let pos = test_position![White Rook on A2]; let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), - bitboard![A1, A3, A4, A5, A6, A7, A8, B2, C2, D2, E2, F2, G2, H2] + bitboard![A1 A3 A4 A5 A6 A7 A8 B2 C2 D2 E2 F2 G2 H2] ); } @@ -86,10 +86,10 @@ mod tests { White Knight on E1, ]; - println!("{}", DiagramFormatter::new(&pos)); + println!("{}", pos.display()); let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), @@ -108,11 +108,11 @@ mod tests { ]; let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), - bitboard![A2, A3, A4, A5, A6, A7, A8, B1, C1, D1, E1] + bitboard![A2 A3 A4 A5 A6 A7 A8 B1 C1 D1 E1] ); } @@ -121,11 +121,11 @@ mod tests { let pos = test_position![White Rook on D4]; let generator = - ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL); + ClassicalMoveGenerator::new(&pos.board, Color::White, BitBoard::FULL, BitBoard::FULL); assert_eq!( generator._test_bitboard(), - bitboard![A4, B4, C4, E4, F4, G4, H4, D1, D2, D3, D5, D6, D7, D8] + bitboard![A4 B4 C4 E4 F4 G4 H4 D1 D2 D3 D5 D6 D7 D8] ); } } diff --git a/position/src/position/builders/mod.rs b/position/src/position/builders/mod.rs index ea1f60d..67ee23e 100644 --- a/position/src/position/builders/mod.rs +++ b/position/src/position/builders/mod.rs @@ -1,7 +1,5 @@ // Eryn Wells mod move_builder; -mod position_builder; pub use move_builder::{Builder as MoveBuilder, MakeMoveError}; -pub use position_builder::Builder as PositionBuilder; diff --git a/position/src/position/builders/move_builder.rs b/position/src/position/builders/move_builder.rs index 307bae6..9cf0806 100644 --- a/position/src/position/builders/move_builder.rs +++ b/position/src/position/builders/move_builder.rs @@ -1,9 +1,10 @@ // Eryn Wells -use crate::{position::flags::Flags, Position}; +use crate::Position; use chessfriend_bitboard::BitBoard; +use chessfriend_board::{castle, castle::Castle, en_passant::EnPassant}; use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square}; -use chessfriend_moves::{Castle, EnPassant, Move}; +use chessfriend_moves::Move; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum MakeMoveError { @@ -33,15 +34,15 @@ pub enum ValidatedMove { moving_piece: PlacedPiece, captured_piece: Option, promotion: Option, - flags: Flags, + castling_rights: castle::Rights, en_passant: Option, - increment_ply: bool, + should_increment_ply: bool, }, Castle { castle: Castle, king: PlacedPiece, rook: PlacedPiece, - flags: Flags, + castling_rights: castle::Rights, }, } @@ -49,7 +50,7 @@ impl MoveToMake for NoMove {} impl MoveToMake for ValidatedMove {} impl<'p> Builder<'p, NoMove> { - pub fn new(position: &'p Position) -> Builder<'p, NoMove> { + pub fn new(position: &'p Position) -> Self { Builder { position, move_to_make: NoMove, @@ -66,6 +67,7 @@ where let piece = self .position + .board .piece_on_square(origin_square) .ok_or(MakeMoveError::NoPiece)?; @@ -101,12 +103,14 @@ where Some( self.position + .board .piece_on_square(capture_square) .ok_or(MakeMoveError::NoCapturedPiece)?, ) } else if mv.is_capture() { Some( self.position + .board .piece_on_square(target_square) .ok_or(MakeMoveError::NoCapturedPiece)?, ) @@ -117,15 +121,15 @@ where // TODO: Check whether the move is legal. let piece_is_king = piece.is_king(); - let mut flags = self.position.flags().clone(); + let mut castling_rights = self.position.board.castling_rights; if piece_is_king { - flags.clear_player_has_right_to_castle_flag(player, Castle::KingSide); - flags.clear_player_has_right_to_castle_flag(player, Castle::QueenSide); + castling_rights.clear_player_has_right_to_castle_flag(player, Castle::KingSide); + castling_rights.clear_player_has_right_to_castle_flag(player, Castle::QueenSide); } else if piece.is_kingside_rook() { - flags.clear_player_has_right_to_castle_flag(player, Castle::KingSide); + castling_rights.clear_player_has_right_to_castle_flag(player, Castle::KingSide); } else if piece.is_queenside_rook() { - flags.clear_player_has_right_to_castle_flag(player, Castle::QueenSide); + castling_rights.clear_player_has_right_to_castle_flag(player, Castle::QueenSide); } if let Some(castle) = mv.castle() { @@ -145,7 +149,7 @@ where castle, king: piece, rook, - flags, + castling_rights, }, }) } else { @@ -167,9 +171,9 @@ where moving_piece: piece, captured_piece, promotion: mv.promotion(), - flags, + castling_rights, en_passant, - increment_ply: !(mv.is_capture() || piece.is_pawn()), + should_increment_ply: !(mv.is_capture() || piece.is_pawn()), }, }) } @@ -180,8 +184,8 @@ impl<'p> Builder<'p, ValidatedMove> { pub fn build(&self) -> Position { let player = self.position.player_to_move(); - let updated_move_number = - self.position.move_number() + if player == Color::Black { 1 } else { 0 }; + let updated_move_number = self.position.board.move_counter.fullmove_number + + if player == Color::Black { 1 } else { 0 }; match self.move_to_make { ValidatedMove::RegularMove { @@ -190,69 +194,57 @@ impl<'p> Builder<'p, ValidatedMove> { moving_piece, captured_piece, promotion, - flags, + castling_rights, en_passant, - increment_ply, + should_increment_ply: increment_ply, } => { - let mut pieces = self.position.piece_bitboards().clone(); + let mut board = self.position.board.clone(); + + board.castling_rights = castling_rights; + board.en_passant = en_passant; if let Some(captured_piece) = captured_piece { - pieces.remove_piece(&captured_piece); + board.remove_piece_from_square(captured_piece.square()); } if let Some(promotion) = promotion { - pieces.remove_piece(&moving_piece); - let _ = pieces - .place_piece(&PlacedPiece::new(Piece::new(player, promotion), to_square)); + board.remove_piece_from_square(moving_piece.square()); + board.place_piece_on_square(Piece::new(player, promotion), to_square); } else { - pieces.move_piece(moving_piece.piece(), from_square, to_square); + board.remove_piece_from_square(from_square); + board.place_piece_on_square(moving_piece.piece(), to_square); } let ply = if increment_ply { - self.position.ply_counter() + 1 + self.position.board.move_counter.halfmove_number + 1 } else { 0 }; - Position::new( - self.position.player_to_move().other(), - flags, - pieces, - en_passant, - ply, - updated_move_number, - ) + Position::new(board) } ValidatedMove::Castle { castle, king, rook, - flags, + castling_rights, } => { - let mut pieces = self.position.piece_bitboards().clone(); + let mut board = self.position.board.clone(); + + board.castling_rights = castling_rights; + + let next_active_color = board.move_counter.active_color.next(); + board.move_counter.active_color = next_active_color; let parameters = castle.parameters(player); - let king_origin_square: BitBoard = king.square().into(); - let king_target_square: BitBoard = parameters.king_target_square().into(); - *pieces.bitboard_for_piece_mut(king.piece()) ^= - king_origin_square | king_target_square; + board.remove_piece_from_square(king.square()); + board.place_piece_on_square(king.piece(), parameters.king_target_square()); - let rook_from: BitBoard = rook.square().into(); - let rook_to: BitBoard = parameters.rook_target_square().into(); - *pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to; + board.remove_piece_from_square(rook.square()); + board.place_piece_on_square(rook.piece(), parameters.rook_target_square()); - *pieces.bitboard_for_color_mut(player) &= - !(king_origin_square | rook_from) | (king_target_square | rook_to); - - Position::new( - player.other(), - flags, - pieces, - None, - self.position.ply_counter() + 1, - updated_move_number, - ) + Position::new(board) } } } @@ -291,7 +283,7 @@ mod tests { println!("{}", &new_position); assert_eq!( - new_position.piece_on_square(Square::E3), + new_position.board.piece_on_square(Square::E3), Some(piece!(White Pawn on E3)) ); @@ -308,16 +300,13 @@ mod tests { println!("{}", &new_position); assert_eq!( - new_position.piece_on_square(Square::E4), + new_position.board.piece_on_square(Square::E4), Some(piece!(White Pawn on E4)) ); let en_passant = new_position.en_passant(); assert!(en_passant.is_some()); - assert_eq!( - en_passant.as_ref().map(EnPassant::target_square), - Some(Square::E3) - ); + assert_eq!(en_passant.map(EnPassant::target_square), Some(Square::E3)); Ok(()) } @@ -339,11 +328,11 @@ mod tests { println!("{}", &new_position); assert_eq!( - new_position.piece_on_square(Square::G1), + new_position.board.piece_on_square(Square::G1), Some(piece!(White King on G1)) ); assert_eq!( - new_position.piece_on_square(Square::F1), + new_position.board.piece_on_square(Square::F1), Some(piece!(White Rook on F1)) ); @@ -366,11 +355,11 @@ mod tests { println!("{en_passant_position}"); assert_eq!( - en_passant_position.piece_on_square(Square::A5), + en_passant_position.board.piece_on_square(Square::A5), Some(piece!(Black Pawn on A5)) ); assert_eq!( - en_passant_position.piece_on_square(Square::B5), + en_passant_position.board.piece_on_square(Square::B5), Some(piece!(White Pawn on B5)) ); @@ -382,10 +371,10 @@ mod tests { .build(); println!("{en_passant_capture}"); - assert_eq!(en_passant_capture.piece_on_square(Square::A5), None); - assert_eq!(en_passant_capture.piece_on_square(Square::B5), None); + assert_eq!(en_passant_capture.board.piece_on_square(Square::A5), None); + assert_eq!(en_passant_capture.board.piece_on_square(Square::B5), None); assert_eq!( - en_passant_capture.piece_on_square(Square::A6), + en_passant_capture.board.piece_on_square(Square::A6), Some(piece!(White Pawn on A6)) ); diff --git a/position/src/position/builders/position_builder.rs b/position/src/position/builders/position_builder.rs deleted file mode 100644 index 08fc193..0000000 --- a/position/src/position/builders/position_builder.rs +++ /dev/null @@ -1,194 +0,0 @@ -// Eryn Wells - -use crate::{ - position::{flags::Flags, piece_sets::PieceBitBoards}, - Position, -}; -use chessfriend_core::{piece, Color, Piece, PlacedPiece, Rank, Shape, Square}; -use chessfriend_moves::{Castle, EnPassant}; -use std::collections::BTreeMap; - -#[derive(Clone)] -pub struct Builder { - player_to_move: Color, - flags: Flags, - pieces: BTreeMap, - kings: [Option; 2], - en_passant: Option, - ply_counter: u16, - move_number: u16, -} - -impl Builder { - pub fn new() -> Self { - Self::default() - } - - pub(crate) fn empty() -> Self { - Self { - player_to_move: Color::default(), - flags: Flags::default(), - pieces: BTreeMap::default(), - kings: [None, None], - en_passant: None, - ply_counter: 0, - move_number: 1, - } - } - - pub fn from_position(position: &Position) -> Self { - let pieces = BTreeMap::from_iter( - position - .pieces(Color::White) - .chain(position.pieces(Color::Black)) - .map(|placed_piece| (placed_piece.square(), *placed_piece.piece())), - ); - - let white_king = position.king_square(Color::White); - let black_king = position.king_square(Color::Black); - - Self { - player_to_move: position.player_to_move(), - flags: position.flags(), - pieces, - kings: [Some(white_king), Some(black_king)], - en_passant: position.en_passant(), - ply_counter: position.ply_counter(), - move_number: position.move_number(), - } - } - - pub fn to_move(&mut self, player: Color) -> &mut Self { - self.player_to_move = player; - self - } - - pub fn ply_counter(&mut self, num: u16) -> &mut Self { - self.ply_counter = num; - self - } - - pub fn move_number(&mut self, num: u16) -> &mut Self { - self.move_number = num; - self - } - - pub fn en_passant(&mut self, en_passant: Option) -> &mut Self { - self.en_passant = en_passant; - self - } - - pub fn place_piece(&mut self, piece: PlacedPiece) -> &mut Self { - let square = piece.square(); - let shape = piece.shape(); - - if shape == Shape::King { - let color = piece.color(); - let color_index: usize = color as usize; - - if let Some(king_square) = self.kings[color_index] { - self.pieces.remove(&king_square); - } - self.kings[color_index] = Some(square); - } - - self.pieces.insert(square, *piece.piece()); - - self - } - - pub fn player_can_castle(&mut self, color: Color, castle: Castle) -> &mut Self { - self.flags - .set_player_has_right_to_castle_flag(color, castle); - self - } - - pub fn no_castling_rights(&mut self) -> &mut Self { - self.flags.clear_all_castling_rights(); - self - } - - pub fn build(&self) -> Position { - let pieces = PieceBitBoards::from_iter( - self.pieces - .iter() - .map(PlacedPiece::from) - .filter(Self::is_piece_placement_valid), - ); - - let mut flags = self.flags; - - for color in Color::ALL { - for castle in Castle::ALL { - let parameters = castle.parameters(color); - let has_rook_on_starting_square = self - .pieces - .get(¶meters.rook_origin_square()) - .is_some_and(|piece| piece.shape() == Shape::Rook); - let king_is_on_starting_square = - self.kings[color as usize] == Some(parameters.king_origin_square()); - - if !king_is_on_starting_square || !has_rook_on_starting_square { - flags.clear_player_has_right_to_castle_flag(color, castle); - } - } - } - - Position::new( - self.player_to_move, - flags, - pieces, - self.en_passant, - self.ply_counter, - self.move_number, - ) - } -} - -impl Builder { - fn is_piece_placement_valid(piece: &PlacedPiece) -> bool { - if piece.shape() == Shape::Pawn { - // Pawns cannot be placed on the first (back) rank of their side, - // and cannot be placed on the final rank without a promotion. - let rank = piece.square().rank(); - return rank != Rank::ONE && rank != Rank::EIGHT; - } - - true - } -} - -impl Default for Builder { - fn default() -> Self { - let white_king_square = Square::E1; - let black_king_square = Square::E8; - - let pieces = BTreeMap::from_iter([ - (white_king_square, piece!(White King)), - (black_king_square, piece!(Black King)), - ]); - - Self { - player_to_move: Color::White, - flags: Flags::default(), - pieces: pieces, - kings: [Some(white_king_square), Some(black_king_square)], - en_passant: None, - ply_counter: 0, - move_number: 1, - } - } -} - -#[cfg(test)] -mod tests { - use crate::PositionBuilder; - use chessfriend_core::piece; - - #[test] - fn place_piece() { - let piece = piece!(White Queen on E4); - let builder = PositionBuilder::new().place_piece(piece).build(); - assert_eq!(builder.piece_on_square(piece.square()), Some(piece)); - } -} diff --git a/position/src/position/diagram_formatter.rs b/position/src/position/diagram_formatter.rs deleted file mode 100644 index 64eb507..0000000 --- a/position/src/position/diagram_formatter.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Eryn Wells - -use crate::Position; -use chessfriend_core::{File, Rank, Square}; -use std::fmt; - -pub struct DiagramFormatter<'a>(&'a Position); - -impl<'a> DiagramFormatter<'a> { - pub fn new(position: &'a Position) -> DiagramFormatter { - DiagramFormatter(position) - } -} - -impl<'a> fmt::Display for DiagramFormatter<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, " ╔═════════════════╗\n")?; - - for rank in Rank::ALL.iter().rev() { - write!(f, "{rank} ║ ")?; - - for file in File::ALL.iter() { - let square = Square::from_file_rank(*file, *rank); - match self.0.piece_on_square(square) { - Some(placed_piece) => write!(f, "{} ", placed_piece.piece())?, - None => write!(f, "· ")?, - } - } - - write!(f, "║\n")?; - } - - write!(f, " ╚═════════════════╝\n")?; - write!(f, " a b c d e f g h\n")?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{position, Position}; - - #[test] - #[ignore] - fn empty() { - let pos = Position::empty(); - let diagram = DiagramFormatter(&pos); - println!("{}", diagram); - } - - #[test] - #[ignore] - fn one_king() { - let pos = position![Black King on H3]; - let diagram = DiagramFormatter(&pos); - println!("{}", diagram); - } - - #[test] - #[ignore] - fn starting() { - let pos = Position::starting(); - let diagram = DiagramFormatter(&pos); - println!("{}", diagram); - } -} diff --git a/position/src/position/flags.rs b/position/src/position/flags.rs deleted file mode 100644 index 19d3165..0000000 --- a/position/src/position/flags.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Eryn Wells - -use chessfriend_core::Color; -use chessfriend_moves::Castle; -use std::fmt; - -#[derive(Clone, Copy, Eq, Hash, PartialEq)] -pub struct Flags(u8); - -impl Flags { - #[inline] - pub(super) fn player_has_right_to_castle_flag_offset(color: Color, castle: Castle) -> usize { - ((color as usize) << 1) + castle as usize - } - - pub(super) fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool { - (self.0 & (1 << Self::player_has_right_to_castle_flag_offset(color, castle))) != 0 - } - - pub(super) fn set_player_has_right_to_castle_flag(&mut self, color: Color, castle: Castle) { - self.0 |= 1 << Self::player_has_right_to_castle_flag_offset(color, castle); - } - - pub(super) fn clear_player_has_right_to_castle_flag(&mut self, color: Color, castle: Castle) { - self.0 &= !(1 << Self::player_has_right_to_castle_flag_offset(color, castle)); - } - - pub(super) fn clear_all_castling_rights(&mut self) { - self.0 &= 0b11111100; - } -} - -impl fmt::Debug for Flags { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Flags({:08b})", self.0) - } -} - -impl Default for Flags { - fn default() -> Self { - Flags(0b00001111) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn castle_flags() { - assert_eq!( - Flags::player_has_right_to_castle_flag_offset(Color::White, Castle::KingSide), - 0 - ); - assert_eq!( - Flags::player_has_right_to_castle_flag_offset(Color::White, Castle::QueenSide), - 1 - ); - assert_eq!( - Flags::player_has_right_to_castle_flag_offset(Color::Black, Castle::KingSide), - 2 - ); - assert_eq!( - Flags::player_has_right_to_castle_flag_offset(Color::Black, Castle::QueenSide), - 3 - ); - } - - #[test] - fn defaults() { - let mut flags: Flags = Default::default(); - assert!(flags.player_has_right_to_castle(Color::White, Castle::KingSide)); - assert!(flags.player_has_right_to_castle(Color::White, Castle::QueenSide)); - assert!(flags.player_has_right_to_castle(Color::Black, Castle::KingSide)); - assert!(flags.player_has_right_to_castle(Color::Black, Castle::QueenSide)); - - flags.clear_player_has_right_to_castle_flag(Color::White, Castle::QueenSide); - assert!(flags.player_has_right_to_castle(Color::White, Castle::KingSide)); - assert!(!flags.player_has_right_to_castle(Color::White, Castle::QueenSide)); - assert!(flags.player_has_right_to_castle(Color::Black, Castle::KingSide)); - assert!(flags.player_has_right_to_castle(Color::Black, Castle::QueenSide)); - - flags.set_player_has_right_to_castle_flag(Color::White, Castle::QueenSide); - assert!(flags.player_has_right_to_castle(Color::White, Castle::KingSide)); - assert!(flags.player_has_right_to_castle(Color::White, Castle::QueenSide)); - assert!(flags.player_has_right_to_castle(Color::Black, Castle::KingSide)); - assert!(flags.player_has_right_to_castle(Color::Black, Castle::QueenSide)); - } -} diff --git a/position/src/position/mod.rs b/position/src/position/mod.rs index 644ccc1..0782305 100644 --- a/position/src/position/mod.rs +++ b/position/src/position/mod.rs @@ -1,16 +1,9 @@ // Eryn Wells -pub mod piece_sets; - mod builders; -mod diagram_formatter; -mod flags; -mod pieces; mod position; pub use { - builders::{MakeMoveError, MoveBuilder, PositionBuilder}, - diagram_formatter::DiagramFormatter, - pieces::Pieces, + builders::{MakeMoveError, MoveBuilder}, position::Position, }; diff --git a/position/src/position/piece_sets.rs b/position/src/position/piece_sets.rs deleted file mode 100644 index fee1a3f..0000000 --- a/position/src/position/piece_sets.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Eryn Wells - -use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Square}; - -#[derive(Debug, Eq, PartialEq)] -pub enum PlacePieceStrategy { - Replace, - PreserveExisting, -} - -#[derive(Debug, Eq, PartialEq)] -pub enum PlacePieceError { - ExisitingPiece, -} - -impl Default for PlacePieceStrategy { - fn default() -> Self { - Self::Replace - } -} - -#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] -pub(crate) struct PieceBitBoards { - by_color: ByColor, - by_color_and_shape: ByColorAndShape, -} - -#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] -struct ByColor(BitBoard, [BitBoard; 2]); - -#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] -struct ByColorAndShape([[BitBoard; 6]; 2]); - -impl PieceBitBoards { - pub(super) fn new(pieces: [[BitBoard; 6]; 2]) -> Self { - use std::ops::BitOr; - - let white_pieces = pieces[Color::White as usize] - .iter() - .fold(BitBoard::empty(), BitOr::bitor); - let black_pieces = pieces[Color::Black as usize] - .iter() - .fold(BitBoard::empty(), BitOr::bitor); - - let all_pieces = white_pieces | black_pieces; - - Self { - by_color: ByColor(all_pieces, [white_pieces, black_pieces]), - by_color_and_shape: ByColorAndShape(pieces), - } - } - - /// A BitBoard representing all the pieces currently on the board. Other - /// engines might refer to this concept as 'occupancy'. - pub(crate) fn all_pieces(&self) -> &BitBoard { - self.by_color.all() - } - - pub(crate) fn all_pieces_of_color(&self, color: Color) -> &BitBoard { - self.by_color.bitboard(color) - } - - pub(super) fn bitboard_for_color(&self, color: Color) -> &BitBoard { - self.by_color.bitboard(color) - } - - pub(super) fn bitboard_for_color_mut(&mut self, color: Color) -> &mut BitBoard { - self.by_color.bitboard_mut(color) - } - - pub(super) fn bitboard_for_piece(&self, piece: &Piece) -> &BitBoard { - self.by_color_and_shape.bitboard_for_piece(piece) - } - - pub(super) fn bitboard_for_piece_mut(&mut self, piece: &Piece) -> &mut BitBoard { - self.by_color_and_shape.bitboard_for_piece_mut(piece) - } - - pub(super) fn place_piece(&mut self, piece: &PlacedPiece) -> Result<(), PlacePieceError> { - self.place_piece_with_strategy(piece, PlacePieceStrategy::default()) - } - - pub(super) fn place_piece_with_strategy( - &mut self, - piece: &PlacedPiece, - strategy: PlacePieceStrategy, - ) -> Result<(), PlacePieceError> { - let color = piece.color(); - let square = piece.square(); - - if strategy == PlacePieceStrategy::PreserveExisting - && self.by_color.bitboard(color).is_set(piece.square()) - { - return Err(PlacePieceError::ExisitingPiece); - } - - self.by_color_and_shape.set_square(square, piece.piece()); - self.by_color.set_square(square, color); - - Ok(()) - } - - pub(super) fn remove_piece(&mut self, piece: &PlacedPiece) { - let color = piece.color(); - let square = piece.square(); - - self.by_color_and_shape.clear_square(square, piece.piece()); - self.by_color.clear_square(square, color); - } - - pub(super) fn move_piece(&mut self, piece: &Piece, from_square: Square, to_square: Square) { - let color = piece.color(); - - self.by_color_and_shape.clear_square(from_square, piece); - self.by_color.clear_square(from_square, color); - self.by_color_and_shape.set_square(to_square, piece); - self.by_color.set_square(to_square, color); - } -} - -impl FromIterator for PieceBitBoards { - fn from_iter>(iter: T) -> Self { - let mut pieces: Self = Default::default(); - - for piece in iter { - let _ = pieces.place_piece(&piece); - } - - pieces - } -} - -impl ByColor { - fn all(&self) -> &BitBoard { - &self.0 - } - - pub(super) fn bitboard(&self, color: Color) -> &BitBoard { - &self.1[color as usize] - } - - pub(super) fn bitboard_mut(&mut self, color: Color) -> &mut BitBoard { - &mut self.1[color as usize] - } - - fn set_square(&mut self, square: Square, color: Color) { - self.0.set_square(square); - self.1[color as usize].set_square(square) - } - - fn clear_square(&mut self, square: Square, color: Color) { - self.0.clear_square(square); - self.1[color as usize].clear_square(square); - } -} - -impl ByColorAndShape { - fn bitboard_for_piece(&self, piece: &Piece) -> &BitBoard { - &self.0[piece.color() as usize][piece.shape() as usize] - } - - fn bitboard_for_piece_mut(&mut self, piece: &Piece) -> &mut BitBoard { - &mut self.0[piece.color() as usize][piece.shape() as usize] - } - - fn set_square(&mut self, square: Square, piece: &Piece) { - self.bitboard_for_piece_mut(piece).set_square(square); - } - - fn clear_square(&mut self, square: Square, piece: &Piece) { - self.bitboard_for_piece_mut(piece).clear_square(square); - } -} diff --git a/position/src/position/pieces.rs b/position/src/position/pieces.rs deleted file mode 100644 index 655aca2..0000000 --- a/position/src/position/pieces.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Eryn Wells - -use super::Position; -use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; - -pub struct Pieces<'a> { - color: Color, - position: &'a Position, - - current_shape: Option, - - shape_iterator: Box>, - square_iterator: Option>>, -} - -impl<'a> Pieces<'a> { - pub(crate) fn new(position: &Position, color: Color) -> Pieces { - Pieces { - color, - position, - current_shape: None, - shape_iterator: Box::new(Shape::iter()), - square_iterator: None, - } - } -} - -impl<'a> Iterator for Pieces<'a> { - type Item = PlacedPiece; - - fn next(&mut self) -> Option { - if let Some(square_iterator) = &mut self.square_iterator { - if let (Some(square), Some(shape)) = (square_iterator.next(), self.current_shape) { - return Some(PlacedPiece::new(Piece::new(self.color, shape), square)); - } - } - - let mut current_shape: Option = None; - let mut next_nonempty_bitboard: Option<&BitBoard> = None; - - while let Some(shape) = self.shape_iterator.next() { - let piece = Piece::new(self.color, *shape); - - let bitboard = self.position.bitboard_for_piece(piece); - if bitboard.is_empty() { - continue; - } - - next_nonempty_bitboard = Some(bitboard); - current_shape = Some(*shape); - - break; - } - - if let (Some(bitboard), Some(shape)) = (next_nonempty_bitboard, current_shape) { - let mut square_iterator = bitboard.occupied_squares(); - - let mut next_placed_piece: Option = None; - if let Some(square) = square_iterator.next() { - next_placed_piece = Some(PlacedPiece::new(Piece::new(self.color, shape), square)); - } - - self.square_iterator = Some(Box::new(square_iterator)); - self.current_shape = Some(shape); - - return next_placed_piece; - } - - None - } -} - -#[cfg(test)] -mod tests { - use crate::{Position, PositionBuilder}; - use chessfriend_core::{piece, Color}; - use std::collections::HashSet; - - #[test] - fn empty() { - let pos = Position::empty(); - let mut pieces = pos.pieces(Color::White); - assert_eq!(pieces.next(), None); - } - - #[test] - fn one() { - let pos = PositionBuilder::new() - .place_piece(piece!(White Queen on E4)) - .build(); - println!("{:#?}", &pos); - - let mut pieces = pos.pieces(Color::White); - assert_eq!(pieces.next(), Some(piece!(White Queen on E4))); - assert_eq!(pieces.next(), Some(piece!(White King on E1))); - assert_eq!(pieces.next(), None); - } - - #[test] - fn multiple_pieces() { - let pos = PositionBuilder::new() - .place_piece(piece!(White Queen on E4)) - .place_piece(piece!(White King on A1)) - .place_piece(piece!(White Pawn on B2)) - .place_piece(piece!(White Pawn on C2)) - .build(); - println!("{}", crate::position::DiagramFormatter::new(&pos)); - - let expected_placed_pieces = HashSet::from([ - piece!(White Queen on E4), - piece!(White King on A1), - piece!(White Pawn on B2), - piece!(White Pawn on C2), - ]); - - let placed_pieces = HashSet::from_iter(pos.pieces(Color::White)); - - assert_eq!( - placed_pieces, - expected_placed_pieces, - "{:#?}", - placed_pieces - .symmetric_difference(&expected_placed_pieces) - .into_iter() - .map(|pp| format!("{}", pp)) - .collect::>() - ); - } -} diff --git a/position/src/position/position.rs b/position/src/position/position.rs index f7e05d7..8c34449 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -1,112 +1,45 @@ // Eryn Wells -use super::{flags::Flags, piece_sets::PieceBitBoards, Pieces}; use crate::{ check::CheckingPieces, move_generator::{MoveSet, Moves}, - position::DiagramFormatter, sight::SightExt, }; use chessfriend_bitboard::BitBoard; +use chessfriend_board::{castle::Castle, display::DiagramFormatter, en_passant::EnPassant, Board}; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; -use chessfriend_moves::{Castle, EnPassant}; +use chessfriend_moves::Move; use std::{cell::OnceCell, fmt}; #[derive(Clone, Debug, Eq)] pub struct Position { - color_to_move: Color, - flags: Flags, - pieces: PieceBitBoards, - en_passant: Option, + pub board: Board, moves: OnceCell, - half_move_counter: u16, - full_move_number: u16, } impl Position { - pub fn empty() -> Position { + pub fn empty() -> Self { Default::default() } /// Return a starting position. pub fn starting() -> Self { - const BLACK_PIECES: [BitBoard; 6] = [ - BitBoard::new(0b0000000011111111 << 48), - BitBoard::new(0b0100001000000000 << 48), - BitBoard::new(0b0010010000000000 << 48), - BitBoard::new(0b1000000100000000 << 48), - BitBoard::new(0b0000100000000000 << 48), - BitBoard::new(0b0001000000000000 << 48), - ]; - - const WHITE_PIECES: [BitBoard; 6] = [ - BitBoard::new(0b1111111100000000), - BitBoard::new(0b0000000001000010), - BitBoard::new(0b0000000000100100), - BitBoard::new(0b0000000010000001), - BitBoard::new(0b0000000000001000), - BitBoard::new(0b0000000000010000), - ]; - Self { - color_to_move: Color::White, - pieces: PieceBitBoards::new([WHITE_PIECES, BLACK_PIECES]), + board: Board::starting(), ..Default::default() } } - pub fn player_to_move(&self) -> Color { - self.color_to_move - } - - pub fn move_number(&self) -> u16 { - self.full_move_number - } - - pub fn ply_counter(&self) -> u16 { - self.half_move_counter - } - - /// Returns true if the player has the right to castle on the given side of - /// the board. - /// - /// The right to castle on a particular side of the board is retained as - /// long as the player has not moved their king, or the rook on that side of - /// the board. - pub(crate) fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool { - self.flags.player_has_right_to_castle(color, castle) - } - - /// Returns `true` if the player is able to castle on the given side of the board. - /// - /// The following requirements must be met: - /// - /// 1. The player must still have the right to castle on that side of the - /// board. The king and rook involved in the castle must not have moved. - /// 1. The spaces between the king and rook must be clear - /// 2. The king must not be in check - /// 3. In the course of castling on that side, the king must not pass - /// through a square that an enemy piece can see - pub(crate) fn player_can_castle(&self, player: Color, castle: Castle) -> bool { - if !self.player_has_right_to_castle(player, castle.into()) { - return false; + pub fn new(board: Board) -> Self { + Self { + board, + ..Default::default() } - - let castling_parameters = castle.parameters(player); - - let all_pieces = self.occupied_squares(); - if !(all_pieces & castling_parameters.clear_squares()).is_empty() { - return false; - } - - let danger_squares = self.king_danger(player); - if !(danger_squares & castling_parameters.check_squares()).is_empty() { - return false; - } - - true } +} +/* +impl Position { /// Return a PlacedPiece representing the rook to use for a castling move. pub(crate) fn rook_for_castle(&self, player: Color, castle: Castle) -> Option { let square = match (player, castle) { @@ -116,103 +49,68 @@ impl Position { (Color::Black, Castle::QueenSide) => Square::A8, }; - self.piece_on_square(square) + self.board.piece_on_square(square) } pub fn moves(&self) -> &Moves { self.moves.get_or_init(|| { + let player_to_move = self.player_to_move(); let checking_pieces = self.checking_pieces(); match checking_pieces.count() { // Normal, unrestricted move generation - 0 => Moves::new(self, self.color_to_move, BitBoard::FULL, BitBoard::FULL), + 0 => Moves::new( + &self.board, + player_to_move, + BitBoard::full(), + BitBoard::full(), + ), 1 => { - // Calculate push and capture masks for checking piece. Moves are restricted to those that intersect those masks. + // Calculate push and capture masks for checking piece. Moves are restricted to + // those that intersect those masks. let capture_mask = checking_pieces.capture_mask(); - let push_mask = - checking_pieces.push_mask(self.king_bitboard(self.color_to_move)); - Moves::new(self, self.color_to_move, capture_mask, push_mask) + let push_mask = checking_pieces.push_mask(self.king_bitboard(player_to_move)); + Moves::new(&self.board, player_to_move, capture_mask, push_mask) } // With more than one checking piece, the only legal moves are king moves. - _ => Moves::new(self, self.color_to_move, BitBoard::EMPTY, BitBoard::EMPTY), + _ => Moves::new( + &self.board, + player_to_move, + BitBoard::empty(), + BitBoard::empty(), + ), } }) } - /// A [BitBoard] representing the set of squares containing a piece. - #[inline] - #[must_use] - pub(crate) fn occupied_squares(&self) -> &BitBoard { - &self.pieces.all_pieces() - } - - #[inline] - #[must_use] - pub(crate) fn friendly_pieces(&self) -> &BitBoard { - self.pieces.all_pieces_of_color(self.color_to_move) - } - - #[inline] - #[must_use] - pub(crate) fn opposing_pieces(&self) -> &BitBoard { - self.pieces.all_pieces_of_color(self.color_to_move.other()) - } - - #[inline] - #[must_use] - pub(crate) fn all_pieces(&self) -> (&BitBoard, &BitBoard) { - (self.friendly_pieces(), self.opposing_pieces()) - } - - /// Return a BitBoard representing the set of squares containing a piece. - /// This set is the inverse of `occupied_squares`. - #[inline] - #[must_use] - pub(crate) fn empty_squares(&self) -> BitBoard { - !self.occupied_squares() - } - - pub fn piece_on_square(&self, sq: Square) -> Option { - for color in Color::iter() { - for shape in Shape::iter() { - let piece = Piece::new(*color, *shape); - if self.pieces.bitboard_for_piece(&piece).is_set(sq) { - return Some(PlacedPiece::new(piece, sq)); - } - } - } - - None - } - - pub fn pieces(&self, color: Color) -> Pieces { - Pieces::new(&self, color) - } - pub fn has_en_passant_square(&self) -> bool { - self.en_passant.is_some() + self.board.en_passant().is_some() } pub fn en_passant(&self) -> Option { - self.en_passant + self.board.en_passant() } - fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard { - let en_passant_target_square = self.en_passant.map(|ep| ep.target_square()); + fn _en_passant_target_square(&self) -> Option { + self.board.en_passant().map(EnPassant::target_square) + } + + fn _sight_of_player(&self, player: Color, board: &Board) -> BitBoard { + let en_passant_target_square = self._en_passant_target_square(); Shape::ALL .iter() .filter_map(|&shape| { let piece = Piece::new(player, shape); - let bitboard = pieces.bitboard_for_piece(&piece); + let bitboard = board.bitboard_for_piece(piece); if !bitboard.is_empty() { Some((piece, bitboard)) } else { None } }) - .flat_map(|(piece, &bitboard)| { + .flat_map(|(piece, bitboard)| { bitboard.occupied_squares().map(move |square| { - PlacedPiece::new(piece, square).sight(pieces, en_passant_target_square) + PlacedPiece::new(piece, square).sight(board, en_passant_target_square) }) }) .fold(BitBoard::empty(), |acc, sight| acc | sight) @@ -224,52 +122,37 @@ impl Position { #[cfg(test)] pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard { - piece.sight(&self.pieces, self.en_passant.map(|ep| ep.target_square())) - } - - /// A bitboard representing the squares where a king of the given color will - /// be in danger of being captured by the opposing player. If the king is on - /// one of these squares, it is in check. The king cannot move to these - /// squares. - pub(crate) fn king_danger(&self, color: Color) -> BitBoard { - let pieces_without_king = { - let mut cloned_pieces = self.pieces.clone(); - let placed_king = PlacedPiece::new(Piece::king(color), self.king_square(color)); - cloned_pieces.remove_piece(&placed_king); - - cloned_pieces - }; - - self._sight_of_player(color.other(), &pieces_without_king) + piece.sight(&self.board, self._en_passant_target_square()) } #[cfg(test)] pub(crate) fn is_king_in_check(&self) -> bool { - let danger_squares = self.king_danger(self.color_to_move); - !(danger_squares & self.king_bitboard(self.color_to_move)).is_empty() + let danger_squares = self.king_danger(self.player_to_move()); + !(danger_squares & self.king_bitboard(self.player_to_move())).is_empty() } - fn king_bitboard(&self, player: Color) -> &BitBoard { - self.pieces.bitboard_for_piece(&Piece::king(player)) + fn king_bitboard(&self, player: Color) -> BitBoard { + self.board.pieces.bitboard_for_piece(Piece::king(player)) } pub(crate) fn king_square(&self, player: Color) -> Square { self.king_bitboard(player) - .occupied_squares() + .occupied_squares(&IterationDirection::default()) .next() .unwrap() } pub(crate) fn checking_pieces(&self) -> CheckingPieces { - let opponent = self.color_to_move.other(); - let king_square = self.king_square(self.color_to_move); + let opponent = self.player_to_move().other(); + let king_square = self.king_square(self.player_to_move()); let checking_pawns = { // The current player's pawn attack moves *from* this square are the // same as the pawn moves for the opposing player attacking this square. - let pawn_moves_to_king_square = BitBoard::pawn_attacks(king_square, self.color_to_move); + let pawn_moves_to_king_square = + BitBoard::pawn_attacks(king_square, self.player_to_move()); let opposing_pawn = Piece::pawn(opponent); - let opposing_pawns = self.pieces.bitboard_for_piece(&opposing_pawn); + let opposing_pawns = self.board.bitboard_for_piece(opposing_pawn); pawn_moves_to_king_square & opposing_pawns }; @@ -278,7 +161,7 @@ impl Position { ($moves_bb_fn:path, $piece_fn:ident) => {{ let moves_from_opposing_square = $moves_bb_fn(king_square); let piece = Piece::$piece_fn(opponent); - let opposing_pieces = self.pieces.bitboard_for_piece(&piece); + let opposing_pieces = self.board.bitboard_for_piece(piece); moves_from_opposing_square & opposing_pieces }}; @@ -298,88 +181,49 @@ impl Position { ) } } +*/ -// crate::position methods impl Position { - pub(super) fn new( - player_to_move: Color, - flags: Flags, - pieces: PieceBitBoards, - en_passant: Option, - half_move_counter: u16, - full_move_number: u16, - ) -> Self { - Self { - color_to_move: player_to_move, - flags, - en_passant, - pieces, - half_move_counter, - full_move_number, - ..Default::default() - } - } - - pub(super) fn flags(&self) -> Flags { - self.flags - } - - pub(super) fn piece_bitboards(&self) -> &PieceBitBoards { - &self.pieces + pub fn display(&self) -> DiagramFormatter { + self.board.display() } } -// crate methods impl Position { - pub(crate) fn bitboard_for_color(&self, color: Color) -> &BitBoard { - self.pieces.bitboard_for_color(color) + pub fn make_move(&mut self, ply: &Move) -> Result<(), MakeMoveError> { + Ok(()) } - pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> &BitBoard { - self.pieces.bitboard_for_piece(&piece) - } -} - -#[cfg(test)] -impl Position { - pub(crate) fn test_set_en_passant(&mut self, en_passant: EnPassant) { - self.en_passant = Some(en_passant); + pub fn unmake_move(&mut self, ply: &Move) -> Result<(), UnmakeMoveError> { + Ok(()) } } impl Default for Position { fn default() -> Self { Self { - color_to_move: Color::White, - flags: Flags::default(), - pieces: PieceBitBoards::default(), - en_passant: None, + board: Board::default(), moves: OnceCell::new(), - half_move_counter: 0, - full_move_number: 1, } } } impl PartialEq for Position { fn eq(&self, other: &Self) -> bool { - self.pieces == other.pieces - && self.color_to_move == other.color_to_move - && self.flags == other.flags - && self.en_passant == other.en_passant + self.board == other.board } } impl fmt::Display for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", DiagramFormatter::new(self)) + write!(f, "{}", self.board.display()) } } #[cfg(test)] mod tests { use super::*; - use crate::{assert_eq_bitboards, position, test_position, Position, PositionBuilder}; + use crate::{assert_eq_bitboards, position, test_position, Position}; use chessfriend_bitboard::bitboard; use chessfriend_core::piece; @@ -389,7 +233,7 @@ mod tests { Black Bishop on F7, ]; - let piece = pos.piece_on_square(Square::F7); + let piece = pos.board.piece_on_square(Square::F7); assert_eq!(piece, Some(piece!(Black Bishop on F7))); } @@ -398,11 +242,11 @@ mod tests { let pos = test_position!(starting); assert_eq!( - pos.piece_on_square(Square::H1), + pos.board.piece_on_square(Square::H1), Some(piece!(White Rook on H1)) ); assert_eq!( - pos.piece_on_square(Square::A8), + pos.board.piece_on_square(Square::A8), Some(piece!(Black Rook on A8)) ); } @@ -464,16 +308,14 @@ mod tests { #[test] fn danger_squares() { - let pos = PositionBuilder::new() - .place_piece(piece!(White King on E1)) - .place_piece(piece!(Black King on E7)) - .place_piece(piece!(White Rook on E4)) - .to_move(Color::Black) - .build(); + let pos = test_position!(Black, [ + White King on E1, + Black King on E7, + White Rook on E4, + ]); let danger_squares = pos.king_danger(Color::Black); - let expected = - bitboard![D1, F1, D2, E2, F2, E3, A4, B4, C4, D4, F4, G4, H4, E5, E6, E7, E8]; + let expected = bitboard![D1 F1 D2 E2 F2 E3 A4 B4 C4 D4 F4 G4 H4 E5 E6 E7 E8]; assert_eq_bitboards!(danger_squares, expected); } } diff --git a/position/src/sight.rs b/position/src/sight.rs index 072806e..bff21d6 100644 --- a/position/src/sight.rs +++ b/position/src/sight.rs @@ -9,15 +9,15 @@ use chessfriend_board::Board; use chessfriend_core::{Color, Direction, PlacedPiece, Shape, Square}; macro_rules! ray_in_direction { - ($square:expr, $blockers:expr, $direction:ident, $occupied_squares:tt) => {{ + ($square:expr, $blockers:expr, $direction:ident, $first_occupied_square:tt) => {{ let ray = BitBoard::ray($square, Direction::$direction); - if let Some(first_occupied_square) = BitBoard::$occupied_squares(&(ray & $blockers)).next() - { + let ray_blockers = ray & $blockers; + if let Some(first_occupied_square) = ray_blockers.$first_occupied_square() { let remainder = BitBoard::ray(first_occupied_square, Direction::$direction); let attack_ray = ray & !remainder; attack_ray } else { - *ray + ray } }}; } @@ -51,34 +51,34 @@ fn _knight_sight(knight_square: Square, blockers: BitBoard) -> BitBoard { fn _bishop_sight(bishop_square: Square, occupancy: BitBoard) -> BitBoard { #[rustfmt::skip] - let sight = ray_in_direction!(bishop_square, occupancy, NorthEast, occupied_squares_trailing) - | ray_in_direction!(bishop_square, occupancy, NorthWest, occupied_squares_trailing) - | ray_in_direction!(bishop_square, occupancy, SouthEast, occupied_squares) - | ray_in_direction!(bishop_square, occupancy, SouthWest, occupied_squares); + let sight = ray_in_direction!(bishop_square, occupancy, NorthEast, first_occupied_square_trailing) + | ray_in_direction!(bishop_square, occupancy, NorthWest, first_occupied_square_trailing) + | ray_in_direction!(bishop_square, occupancy, SouthEast, first_occupied_square_leading) + | ray_in_direction!(bishop_square, occupancy, SouthWest, first_occupied_square_leading); sight } fn _rook_sight(rook_square: Square, occupancy: BitBoard) -> BitBoard { #[rustfmt::skip] - let sight = ray_in_direction!(rook_square, occupancy, North, occupied_squares_trailing) - | ray_in_direction!(rook_square, occupancy, East, occupied_squares_trailing) - | ray_in_direction!(rook_square, occupancy, South, occupied_squares) - | ray_in_direction!(rook_square, occupancy, West, occupied_squares); + let sight = ray_in_direction!(rook_square, occupancy, North, first_occupied_square_trailing) + | ray_in_direction!(rook_square, occupancy, East, first_occupied_square_trailing) + | ray_in_direction!(rook_square, occupancy, South, first_occupied_square_leading) + | ray_in_direction!(rook_square, occupancy, West, first_occupied_square_leading); sight } fn _queen_sight(queen_square: Square, occupancy: BitBoard) -> BitBoard { #[rustfmt::skip] - let sight = ray_in_direction!(queen_square, occupancy, NorthWest, occupied_squares_trailing) - | ray_in_direction!(queen_square, occupancy, North, occupied_squares_trailing) - | ray_in_direction!(queen_square, occupancy, NorthEast, occupied_squares_trailing) - | ray_in_direction!(queen_square, occupancy, East, occupied_squares_trailing) - | ray_in_direction!(queen_square, occupancy, SouthEast, occupied_squares) - | ray_in_direction!(queen_square, occupancy, South, occupied_squares) - | ray_in_direction!(queen_square, occupancy, SouthWest, occupied_squares) - | ray_in_direction!(queen_square, occupancy, West, occupied_squares); + let sight = ray_in_direction!(queen_square, occupancy, NorthWest, first_occupied_square_trailing) + | ray_in_direction!(queen_square, occupancy, North, first_occupied_square_trailing) + | ray_in_direction!(queen_square, occupancy, NorthEast, first_occupied_square_trailing) + | ray_in_direction!(queen_square, occupancy, East, first_occupied_square_trailing) + | ray_in_direction!(queen_square, occupancy, SouthEast, first_occupied_square_leading) + | ray_in_direction!(queen_square, occupancy, South, first_occupied_square_leading) + | ray_in_direction!(queen_square, occupancy, SouthWest, first_occupied_square_leading) + | ray_in_direction!(queen_square, occupancy, West, first_occupied_square_leading); sight } @@ -129,9 +129,9 @@ impl SightExt for PlacedPiece { Color::Black => self.black_pawn_sight(board, en_passant_square), }, Shape::Knight => self.knight_sight(board), - Shape::Bishop => self.bishop_sight(board.all_pieces_bitboard()), - Shape::Rook => self.rook_sight(board.all_pieces_bitboard()), - Shape::Queen => self.queen_sight(board.all_pieces_bitboard()), + Shape::Bishop => self.bishop_sight(board.pieces.all_pieces()), + Shape::Rook => self.rook_sight(board.pieces.all_pieces()), + Shape::Queen => self.queen_sight(board.pieces.all_pieces()), Shape::King => self.king_sight(board), } } @@ -139,46 +139,40 @@ impl SightExt for PlacedPiece { impl KingSightExt for PlacedPiece { fn king_sight(&self, board: &Board) -> BitBoard { - _king_sight( - self.square(), - board.all_pieces_of_color_bitboard(self.color()), - ) + _king_sight(self.square, board.pieces.all_pieces_of_color(self.color)) } } impl KnightSightExt for PlacedPiece { fn knight_sight(&self, board: &Board) -> BitBoard { - _knight_sight( - self.square(), - board.all_pieces_of_color_bitboard(self.color()), - ) + _knight_sight(self.square, board.pieces.all_pieces_of_color(self.color)) } } impl PawnSightExt for PlacedPiece { fn pawn_sight(&self, board: &Board, en_passant_square: Option) -> BitBoard { - match self.color() { + match self.color { Color::White => self.white_pawn_sight(board, en_passant_square), Color::Black => self.black_pawn_sight(board, en_passant_square), } } fn white_pawn_sight(&self, board: &Board, en_passant_square: Option) -> BitBoard { - let opponent = self.color().other(); + let opponent = self.color.other(); _white_pawn_sight( - self.square().into(), - board.all_pieces_bitboard(), - board.all_pieces_of_color_bitboard(opponent), + self.square.into(), + board.pieces.all_pieces(), + board.pieces.all_pieces_of_color(opponent), en_passant_square.into(), ) } fn black_pawn_sight(&self, board: &Board, en_passant_square: Option) -> BitBoard { - let opponent = self.color().other(); + let opponent = self.piece.color.other(); _black_pawn_sight( - self.square().into(), - board.all_pieces_bitboard(), - board.all_pieces_of_color_bitboard(opponent), + self.square.into(), + board.pieces.all_pieces(), + board.pieces.all_pieces_of_color(opponent), en_passant_square.into(), ) } @@ -186,19 +180,19 @@ impl PawnSightExt for PlacedPiece { impl BishopSightExt for PlacedPiece { fn bishop_sight(&self, occupancy: BitBoard) -> BitBoard { - _bishop_sight(self.square(), occupancy) + _bishop_sight(self.square, occupancy) } } impl RookSightExt for PlacedPiece { fn rook_sight(&self, occupancy: BitBoard) -> BitBoard { - _rook_sight(self.square(), occupancy) + _rook_sight(self.square, occupancy) } } impl QueenSightExt for PlacedPiece { fn queen_sight(&self, occupancy: BitBoard) -> BitBoard { - _queen_sight(self.square(), occupancy) + _queen_sight(self.square, occupancy) } } @@ -225,7 +219,7 @@ impl SliderRayToSquareExt for Shape { ray!(origin, NorthWest), ] .into_iter() - .find(|(&ray, _)| !(target_bitboard & ray).is_empty()), + .find(|(ray, _)| !(target_bitboard & ray).is_empty()), Shape::Rook => [ ray!(origin, North), ray!(origin, East), @@ -233,7 +227,7 @@ impl SliderRayToSquareExt for Shape { ray!(origin, West), ] .into_iter() - .find(|(&ray, _)| !(target_bitboard & ray).is_empty()), + .find(|(ray, _)| !(target_bitboard & ray).is_empty()), Shape::Queen => [ ray!(origin, North), ray!(origin, NorthEast), @@ -245,7 +239,7 @@ impl SliderRayToSquareExt for Shape { ray!(origin, NorthWest), ] .into_iter() - .find(|(&ray, _)| !(target_bitboard & ray).is_empty()), + .find(|(ray, _)| !(target_bitboard & ray).is_empty()), _ => None, }; @@ -307,10 +301,9 @@ mod tests { mod pawn { use crate::test_position; use chessfriend_bitboard::{bitboard, BitBoard}; - use chessfriend_core::{piece, Square}; - use chessfriend_moves::EnPassant; + use chessfriend_core::piece; - sight_test!(e4_pawn, piece!(White Pawn on E4), bitboard!(D5, F5)); + sight_test!(e4_pawn, piece!(White Pawn on E4), bitboard![D5 F5]); sight_test!( e4_pawn_one_blocker, @@ -352,15 +345,14 @@ mod tests { #[test] fn e5_en_passant() { - let mut pos = test_position!( + let pos = test_position!(White, [ White Pawn on E5, Black Pawn on D5, - ); - pos.test_set_en_passant(EnPassant::from_target_square(Square::D6).unwrap()); + ], D6); let piece = piece!(White Pawn on E5); let sight = pos.sight_of_piece(&piece); - assert_eq!(sight, bitboard!(D6, F6)); + assert_eq!(sight, bitboard!(D6 F6)); } } @@ -372,7 +364,7 @@ mod tests { sight_test!( f6_knight, piece!(Black Knight on F6), - bitboard!(H7, G8, E8, D7, D5, E4, G4, H5) + bitboard![H7 G8 E8 D7 D5 E4 G4 H5] ); } @@ -382,13 +374,13 @@ mod tests { sight_test!( c2_bishop, piece!(Black Bishop on C2), - bitboard!(D1, B3, A4, B1, D3, E4, F5, G6, H7) + bitboard![D1 B3 A4 B1 D3 E4 F5 G6 H7] ); #[test] fn ray_to_square() { let generated_ray = Shape::Bishop.ray_to_square(Square::C5, Square::E7); - let expected_ray = bitboard![D6, E7]; + let expected_ray = bitboard![D6 E7]; assert_eq!(generated_ray, Some(expected_ray)); } } @@ -400,7 +392,7 @@ mod tests { sight_test!( g3_rook, piece!(White Rook on G3), - bitboard!(G1, G2, G4, G5, G6, G7, G8, A3, B3, C3, D3, E3, F3, H3) + bitboard![G1 G2 G4 G5 G6 G7 G8 A3 B3 C3 D3 E3 F3 H3] ); sight_test!( @@ -411,25 +403,25 @@ mod tests { Black King on E7, ], piece!(White Rook on E4), - bitboard!(A4, B4, C4, D4, F4, G4, H4, E2, E3, E5, E6, E7, E1) + bitboard![A4 B4 C4 D4 F4 G4 H4 E2 E3 E5 E6 E7 E1] ); #[test] fn ray_to_square() { let generated_ray = Shape::Rook.ray_to_square(Square::C2, Square::C6); - let expected_ray = bitboard![C3, C4, C5, C6]; + let expected_ray = bitboard![C3 C4 C5 C6]; assert_eq!(generated_ray, Some(expected_ray)); let generated_ray = Shape::Rook.ray_to_square(Square::D2, Square::H2); - let expected_ray = bitboard![E2, F2, G2, H2]; + let expected_ray = bitboard![E2 F2 G2 H2]; assert_eq!(generated_ray, Some(expected_ray)); let generated_ray = Shape::Rook.ray_to_square(Square::G6, Square::B6); - let expected_ray = bitboard![B6, C6, D6, E6, F6]; + let expected_ray = bitboard![B6 C6 D6 E6 F6]; assert_eq!(generated_ray, Some(expected_ray)); let generated_ray = Shape::Rook.ray_to_square(Square::A6, Square::A3); - let expected_ray = bitboard![A5, A4, A3]; + let expected_ray = bitboard![A5 A4 A3]; assert_eq!(generated_ray, Some(expected_ray)); } } @@ -438,10 +430,6 @@ mod tests { use chessfriend_bitboard::bitboard; use chessfriend_core::piece; - sight_test!( - e1_king, - piece!(White King on E1), - bitboard![D1, D2, E2, F2, F1] - ); + sight_test!(e1_king, piece!(White King on E1), bitboard![D1 D2 E2 F2 F1]); } }