From 1d82d27f842fd39ffe6e7ba4722f0e118a075800 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 25 Apr 2024 13:28:24 -0700 Subject: [PATCH] Move a whole bunch of stuff to the new chessfriend_board package --- Cargo.lock | 8 + board/Cargo.toml | 4 +- board/src/board.rs | 290 +++++++++++++++++++++++++++++++++ board/src/builder.rs | 179 ++++++++++++++++++++ {moves => board}/src/castle.rs | 56 +++---- board/src/display.rs | 68 ++++++++ board/src/en_passant.rs | 48 ++++++ board/src/flags.rs | 90 ++++++++++ board/src/lib.rs | 31 ++-- board/src/macros.rs | 96 +++++++++++ board/src/piece_sets.rs | 174 ++++++++++++++++++++ board/src/pieces.rs | 127 +++++++++++++++ 12 files changed, 1130 insertions(+), 41 deletions(-) create mode 100644 board/src/board.rs create mode 100644 board/src/builder.rs rename {moves => board}/src/castle.rs (62%) create mode 100644 board/src/display.rs create mode 100644 board/src/en_passant.rs create mode 100644 board/src/flags.rs create mode 100644 board/src/macros.rs create mode 100644 board/src/piece_sets.rs create mode 100644 board/src/pieces.rs diff --git a/Cargo.lock b/Cargo.lock index 53be692..50995a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,14 @@ dependencies = [ "chessfriend_core", ] +[[package]] +name = "chessfriend_board" +version = "0.1.0" +dependencies = [ + "chessfriend_bitboard", + "chessfriend_core", +] + [[package]] name = "chessfriend_core" version = "0.1.0" diff --git a/board/Cargo.toml b/board/Cargo.toml index 6b600a9..54a0dd4 100644 --- a/board/Cargo.toml +++ b/board/Cargo.toml @@ -1,8 +1,10 @@ [package] -name = "board" +name = "chessfriend_board" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chessfriend_bitboard = { path = "../bitboard" } +chessfriend_core = { path = "../core" } diff --git a/board/src/board.rs b/board/src/board.rs new file mode 100644 index 0000000..94c2d97 --- /dev/null +++ b/board/src/board.rs @@ -0,0 +1,290 @@ +// Eryn Wells + +use crate::{display::DiagramFormatter, Castle, EnPassant, Flags, PieceBitBoards, Pieces}; +use chessfriend_bitboard::BitBoard; +use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; + +#[derive(Clone, Debug, Eq)] +pub struct Board { + player_to_move: Color, + flags: Flags, + pieces: PieceBitBoards, + en_passant: Option, + half_move_counter: u16, + full_move_number: u16, +} + +impl Board { + /// An empty board + #[must_use] + pub fn empty() -> Self { + Board::default() + } + + /// The starting position + #[must_use] + pub fn starting() -> Self { + const BLACK_PIECES: [BitBoard; Shape::NUM] = [ + BitBoard::new(0b0000_0000_1111_1111 << 48), + BitBoard::new(0b0100_0010_0000_0000 << 48), + BitBoard::new(0b0010_0100_0000_0000 << 48), + BitBoard::new(0b1000_0001_0000_0000 << 48), + BitBoard::new(0b0000_1000_0000_0000 << 48), + BitBoard::new(0b0001_0000_0000_0000 << 48), + ]; + + const WHITE_PIECES: [BitBoard; Shape::NUM] = [ + BitBoard::new(0b1111_1111_0000_0000), + BitBoard::new(0b0000_0000_0100_0010), + BitBoard::new(0b0000_0000_0010_0100), + BitBoard::new(0b0000_0000_1000_0001), + BitBoard::new(0b0000_0000_0000_1000), + BitBoard::new(0b0000_0000_0001_0000), + ]; + + Self { + player_to_move: Color::White, + pieces: PieceBitBoards::new([WHITE_PIECES, BLACK_PIECES]), + ..Default::default() + } + } + + pub(crate) fn new( + player_to_move: Color, + flags: Flags, + pieces: PieceBitBoards, + en_passant: Option, + half_move_counter: u16, + full_move_number: u16, + ) -> Self { + Self { + player_to_move, + flags, + pieces, + en_passant, + half_move_counter, + full_move_number, + } + } + + #[must_use] + pub fn player_to_move(&self) -> Color { + self.player_to_move + } + + #[must_use] + pub fn move_number(&self) -> u16 { + self.full_move_number + } + + #[must_use] + pub fn ply_counter(&self) -> u16 { + self.half_move_counter + } + + #[must_use] + pub(crate) fn flags(&self) -> &Flags { + &self.flags + } + + /// 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. + #[must_use] + pub fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool { + self.flags.player_has_right_to_castle(color, castle) + } + + /// The rook to use for a castling move. + #[must_use] + pub fn rook_for_castle(&self, player: Color, castle: Castle) -> Option { + let square = castle.parameters(player).rook_origin_square(); + self.piece_on_square(square) + } + + /// A [`BitBoard`] representing the set of squares containing a piece. + #[inline] + #[must_use] + pub fn occupied_squares(&self) -> &BitBoard { + self.pieces.all_pieces() + } + + #[inline] + #[must_use] + pub fn friendly_pieces(&self) -> &BitBoard { + self.pieces.all_pieces_of_color(self.player_to_move) + } + + #[inline] + #[must_use] + pub fn opposing_pieces(&self) -> &BitBoard { + self.pieces.all_pieces_of_color(self.player_to_move.other()) + } + + #[inline] + #[must_use] + pub fn all_pieces(&self) -> (&BitBoard, &BitBoard) { + (self.friendly_pieces(), self.opposing_pieces()) + } + + /// A [`BitBoard`] representing the set of squares containing a piece. This set is the inverse of + /// `Board::occupied_squares`. + #[inline] + #[must_use] + pub fn empty_squares(&self) -> BitBoard { + !self.occupied_squares() + } + + #[must_use] + 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 + } + + #[must_use] + pub fn pieces(&self, color: Color) -> Pieces { + Pieces::new(self, color) + } + + #[must_use] + pub fn has_en_passant_square(&self) -> bool { + self.en_passant.is_some() + } + + #[must_use] + pub fn en_passant(&self) -> Option { + self.en_passant + } + + 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) + .occupied_squares() + .next() + .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) + } +} + +#[cfg(test)] +impl Board { + pub(crate) fn test_set_en_passant(&mut self, en_passant: EnPassant) { + self.en_passant = Some(en_passant); + } +} + +impl Default for Board { + fn default() -> Self { + Self { + player_to_move: Color::White, + flags: Flags::default(), + pieces: PieceBitBoards::default(), + en_passant: None, + half_move_counter: 0, + full_move_number: 1, + } + } +} + +impl PartialEq for Board { + fn eq(&self, other: &Self) -> bool { + self.pieces == other.pieces + && self.player_to_move == other.player_to_move + && self.flags == other.flags + && self.en_passant == other.en_passant + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_board; + use chessfriend_core::piece; + + #[test] + fn piece_on_square() { + let pos = 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)) + ); + } + + #[test] + fn king_not_on_starting_square_cannot_castle() { + let board = test_board!(White King on E4); + assert!(!board.player_has_right_to_castle(Color::White, Castle::KingSide)); + assert!(!board.player_has_right_to_castle(Color::White, Castle::QueenSide)); + } + + #[test] + fn king_on_starting_square_can_castle() { + let board = test_board!( + White King on E1, + White Rook on A1, + White Rook on H1 + ); + + assert!(board.player_has_right_to_castle(Color::White, Castle::KingSide)); + assert!(board.player_has_right_to_castle(Color::White, Castle::QueenSide)); + } + + #[test] + fn rook_for_castle() { + let board = test_board![ + White King on E1, + White Rook on H1, + White Rook on A1, + ]; + + assert_eq!( + board.rook_for_castle(Color::White, Castle::KingSide), + Some(piece!(White Rook on H1)) + ); + assert_eq!( + board.rook_for_castle(Color::White, Castle::QueenSide), + Some(piece!(White Rook on A1)) + ); + } +} diff --git a/board/src/builder.rs b/board/src/builder.rs new file mode 100644 index 0000000..f52d18c --- /dev/null +++ b/board/src/builder.rs @@ -0,0 +1,179 @@ +// Eryn Wells + +use crate::{Board, Castle, EnPassant, Flags, PieceBitBoards}; +use chessfriend_core::{piece, Color, Piece, PlacedPiece, Rank, Shape, Square}; +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 { + #[must_use] + pub fn new() -> Self { + Self::default() + } + + #[must_use] + pub fn from_board(board: &Board) -> Self { + let pieces = board + .pieces(Color::White) + .chain(board.pieces(Color::Black)) + .map(|placed_piece| (placed_piece.square(), *placed_piece.piece())) + .collect::>(); + + let white_king = board.king_square(Color::White); + let black_king = board.king_square(Color::Black); + + Self { + player_to_move: board.player_to_move(), + flags: *board.flags(), + pieces, + kings: [Some(white_king), Some(black_king)], + en_passant: board.en_passant(), + ply_counter: board.ply_counter(), + move_number: board.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) -> Board { + let pieces = self + .pieces + .iter() + .map(PlacedPiece::from) + .filter(Self::is_piece_placement_valid) + .collect::(); + + 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); + } + } + } + + Board::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, + kings: [Some(white_king_square), Some(black_king_square)], + en_passant: None, + ply_counter: 0, + move_number: 1, + } + } +} + +#[cfg(test)] +mod tests { + use crate::Builder; + use chessfriend_core::piece; + + #[test] + fn place_piece() { + let piece = piece!(White Queen on E4); + let builder = Builder::new().place_piece(piece).build(); + assert_eq!(builder.piece_on_square(piece.square()), Some(piece)); + } +} diff --git a/moves/src/castle.rs b/board/src/castle.rs similarity index 62% rename from moves/src/castle.rs rename to board/src/castle.rs index 731f25f..17b2942 100644 --- a/moves/src/castle.rs +++ b/board/src/castle.rs @@ -12,41 +12,41 @@ pub enum Castle { pub struct Parameters { /// Origin squares of the king and rook. - origin_squares: Squares, + origin: Squares, /// Target or destination squares for the king and rook. - target_squares: Squares, + target: Squares, /// The set of squares that must be clear of any pieces in order to perform this castle. - clear_squares: BitBoard, + clear: BitBoard, /// The set of squares that must not be attacked in order to perform this castle. - check_squares: BitBoard, + check: BitBoard, } impl Parameters { pub fn king_origin_square(&self) -> Square { - self.origin_squares.king + self.origin.king } pub fn rook_origin_square(&self) -> Square { - self.origin_squares.rook + self.origin.rook } pub fn king_target_square(&self) -> Square { - self.target_squares.king + self.target.king } pub fn rook_target_square(&self) -> Square { - self.target_squares.rook + self.target.rook } pub fn clear_squares(&self) -> &BitBoard { - &self.clear_squares + &self.clear } pub fn check_squares(&self) -> &BitBoard { - &self.check_squares + &self.check } } @@ -63,59 +63,59 @@ impl Castle { const PARAMETERS: [[Parameters; 2]; 2] = [ [ Parameters { - origin_squares: Squares { + origin: Squares { king: Square::E1, rook: Square::H1, }, - target_squares: Squares { + target: Squares { king: Square::G1, rook: Square::F1, }, - clear_squares: BitBoard::new(0b01100000), - check_squares: BitBoard::new(0b01110000), + clear: BitBoard::new(0b0110_0000), + check: BitBoard::new(0b0111_0000), }, Parameters { - origin_squares: Squares { + origin: Squares { king: Square::E1, rook: Square::A1, }, - target_squares: Squares { + target: Squares { king: Square::C1, rook: Square::D1, }, - clear_squares: BitBoard::new(0b00001110), - check_squares: BitBoard::new(0b00011100), + clear: BitBoard::new(0b0000_1110), + check: BitBoard::new(0b0001_1100), }, ], [ Parameters { - origin_squares: Squares { + origin: Squares { king: Square::E8, rook: Square::H8, }, - target_squares: Squares { + target: Squares { king: Square::G8, rook: Square::F8, }, - clear_squares: BitBoard::new(0b01100000 << 8 * 7), - check_squares: BitBoard::new(0b01110000 << 8 * 7), + clear: BitBoard::new(0b0110_0000 << (8 * 7)), + check: BitBoard::new(0b0111_0000 << (8 * 7)), }, Parameters { - origin_squares: Squares { + origin: Squares { king: Square::E8, rook: Square::A8, }, - target_squares: Squares { + target: Squares { king: Square::C8, rook: Square::D8, }, - clear_squares: BitBoard::new(0b00001110 << 8 * 7), - check_squares: BitBoard::new(0b00011100 << 8 * 7), + clear: BitBoard::new(0b0000_1110 << (8 * 7)), + check: BitBoard::new(0b0001_1100 << (8 * 7)), }, ], ]; - pub fn parameters(&self, color: Color) -> &'static Parameters { - &Castle::PARAMETERS[color as usize][*self as usize] + pub fn parameters(self, color: Color) -> &'static Parameters { + &Castle::PARAMETERS[color as usize][self as usize] } } diff --git a/board/src/display.rs b/board/src/display.rs new file mode 100644 index 0000000..053f9a0 --- /dev/null +++ b/board/src/display.rs @@ -0,0 +1,68 @@ +// Eryn Wells + +use crate::Board; +use chessfriend_core::{File, Rank, Square}; +use std::fmt; + +pub struct DiagramFormatter<'a>(&'a Board); + +impl<'a> DiagramFormatter<'a> { + pub fn new(board: &'a Board) -> Self { + Self(board) + } +} + +impl<'a> fmt::Display for DiagramFormatter<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, " ╔═════════════════╗")?; + + for rank in Rank::ALL.into_iter().rev() { + write!(f, "{rank} ║ ")?; + + 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())?, + None => write!(f, "· ")?, + } + } + + writeln!(f, "║")?; + } + + writeln!(f, " ╚═════════════════╝")?; + writeln!(f, " a b c d e f g h")?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test_board, Board}; + + #[test] + #[ignore] + fn empty() { + let pos = test_board!(empty); + let diagram = DiagramFormatter(&pos); + println!("{diagram}"); + } + + #[test] + #[ignore] + fn one_king() { + let pos = test_board![Black King on H3]; + let diagram = DiagramFormatter(&pos); + println!("{diagram}"); + } + + #[test] + #[ignore] + fn starting() { + let pos = test_board!(starting); + let diagram = DiagramFormatter(&pos); + println!("{diagram}"); + } +} diff --git a/board/src/en_passant.rs b/board/src/en_passant.rs new file mode 100644 index 0000000..2da5abc --- /dev/null +++ b/board/src/en_passant.rs @@ -0,0 +1,48 @@ +// Eryn Wells + +use chessfriend_core::{Rank, Square}; + +/// En passant information. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct EnPassant { + target: Square, + capture: Square, +} + +impl EnPassant { + fn _capture_square(target: Square) -> Option { + let (file, rank) = target.file_rank(); + match rank { + Rank::THREE => Some(Square::from_file_rank(file, Rank::FOUR)), + Rank::SIX => Some(Square::from_file_rank(file, Rank::FIVE)), + _ => None, + } + } + + /// Return en passant information for a particular target square. The target + /// square is the square a pawn capturing en passant will move to. + /// + /// Return `None` if the square is not eligible for en passant. + /// + /// ## Examples + /// + /// ``` + /// use chessfriend_board::en_passant::EnPassant; + /// use chessfriend_core::Square; + /// assert!(EnPassant::from_target_square(Square::E3).is_some()); + /// assert!(EnPassant::from_target_square(Square::B4).is_none()); + /// ``` + pub fn from_target_square(target: Square) -> Option { + Self::_capture_square(target).map(|capture| Self { target, capture }) + } + + /// The square the capturing piece will move to. + pub fn target_square(self) -> Square { + self.target + } + + /// The square on which the captured pawn sits. + pub fn capture_square(self) -> Square { + self.capture + } +} diff --git a/board/src/flags.rs b/board/src/flags.rs new file mode 100644 index 0000000..548c00f --- /dev/null +++ b/board/src/flags.rs @@ -0,0 +1,90 @@ +// Eryn Wells + +use crate::Castle; +use chessfriend_core::Color; +use std::fmt; + +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +pub struct Flags(u8); + +impl Flags { + #[inline] + fn player_has_right_to_castle_flag_offset(color: Color, castle: Castle) -> usize { + ((color as usize) << 1) + castle as usize + } + + #[allow(dead_code)] + 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 &= 0b1111_1100; + } +} + +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(0b0000_1111) + } +} + +#[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/board/src/lib.rs b/board/src/lib.rs index 7d12d9a..67f2c6f 100644 --- a/board/src/lib.rs +++ b/board/src/lib.rs @@ -1,14 +1,21 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} +// Eryn Wells -#[cfg(test)] -mod tests { - use super::*; +pub mod en_passant; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +mod board; +mod builder; +mod castle; +mod display; +mod flags; +mod macros; +mod piece_sets; +mod pieces; + +pub use board::Board; +pub use builder::Builder; + +use castle::Castle; +use en_passant::EnPassant; +use flags::Flags; +use piece_sets::PieceBitBoards; +use pieces::Pieces; diff --git a/board/src/macros.rs b/board/src/macros.rs new file mode 100644 index 0000000..547071b --- /dev/null +++ b/board/src/macros.rs @@ -0,0 +1,96 @@ +// 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() + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! test_board { + ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => { + { + let board = $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 + )) + )* + .to_move(chessfriend_core::Color::$to_move) + .en_passant(Some(chessfriend_moves::EnPassant::from_target_square(chessfriend_core::Square::$en_passant)).unwrap()) + .build(); + + println!("{}", board.display()); + + board + } + }; + ($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ]) => { + { + let board = $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 + )) + )* + .to_move(chessfriend_core::Color::$to_move) + .build(); + + println!("{}", board.display()); + + pos + } + }; + ($($color:ident $shape:ident on $square:ident),* $(,)?) => { + { + let board = $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(); + + println!("{}", board.display()); + + board + } + }; + (empty) => { + { + let board = Board::empty(); + println!("{}", board.display()); + board + } + }; + (starting) => { + { + let board = Board::starting(); + println!("{}", board.display()); + board + } + }; +} diff --git a/board/src/piece_sets.rs b/board/src/piece_sets.rs new file mode 100644 index 0000000..fee1a3f --- /dev/null +++ b/board/src/piece_sets.rs @@ -0,0 +1,174 @@ +// 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/board/src/pieces.rs b/board/src/pieces.rs new file mode 100644 index 0000000..537db20 --- /dev/null +++ b/board/src/pieces.rs @@ -0,0 +1,127 @@ +// Eryn Wells + +use super::Board; +use chessfriend_bitboard::BitBoard; +use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; + +pub struct Pieces<'a> { + color: Color, + board: &'a Board, + current_shape: Option, + shape_iterator: Box>, + square_iterator: Option>>, +} + +impl<'a> Pieces<'a> { + pub(crate) fn new(board: &Board, color: Color) -> Pieces { + Pieces { + color, + board, + 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; + + for shape in self.shape_iterator.by_ref() { + let piece = Piece::new(self.color, *shape); + + let bitboard = self.board.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::{test_board, Board, Builder}; + use chessfriend_core::{piece, Color}; + use std::collections::HashSet; + + #[test] + fn empty() { + let board = Board::empty(); + let mut pieces = board.pieces(Color::White); + assert_eq!(pieces.next(), None); + } + + #[test] + fn one() { + let pos = Builder::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 board = test_board![ + White Queen on E4, + White King on A1, + White Pawn on B2, + White Pawn on C2, + ]; + + 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(board.pieces(Color::White)); + + assert_eq!( + placed_pieces, + expected_placed_pieces, + "{:#?}", + placed_pieces + .symmetric_difference(&expected_placed_pieces) + .into_iter() + .map(|pp| format!("{}", pp)) + .collect::>() + ); + } +}