Move a whole bunch of stuff to the new chessfriend_board package

This commit is contained in:
Eryn Wells 2024-04-25 13:28:24 -07:00
parent 797606785e
commit 1d82d27f84
12 changed files with 1130 additions and 41 deletions

8
Cargo.lock generated
View file

@ -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"

View file

@ -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" }

290
board/src/board.rs Normal file
View file

@ -0,0 +1,290 @@
// Eryn Wells <eryn@erynwells.me>
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<EnPassant>,
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<EnPassant>,
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<PlacedPiece> {
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<PlacedPiece> {
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<EnPassant> {
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))
);
}
}

179
board/src/builder.rs Normal file
View file

@ -0,0 +1,179 @@
// Eryn Wells <eryn@erynwells.me>
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<Square, Piece>,
kings: [Option<Square>; 2],
en_passant: Option<EnPassant>,
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::<BTreeMap<_, _>>();
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<EnPassant>) -> &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::<PieceBitBoards>();
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(&parameters.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));
}
}

View file

@ -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]
}
}

68
board/src/display.rs Normal file
View file

@ -0,0 +1,68 @@
// Eryn Wells <eryn@erynwells.me>
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}");
}
}

48
board/src/en_passant.rs Normal file
View file

@ -0,0 +1,48 @@
// Eryn Wells <eryn@erynwells.me>
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<Square> {
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> {
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
}
}

90
board/src/flags.rs Normal file
View file

@ -0,0 +1,90 @@
// Eryn Wells <eryn@erynwells.me>
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));
}
}

View file

@ -1,14 +1,21 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
// Eryn Wells <eryn@erynwells.me>
#[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;

96
board/src/macros.rs Normal file
View file

@ -0,0 +1,96 @@
// Eryn Wells <eryn@erynwells.me>
#[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
}
};
}

174
board/src/piece_sets.rs Normal file
View file

@ -0,0 +1,174 @@
// Eryn Wells <eryn@erynwells.me>
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<PlacedPiece> for PieceBitBoards {
fn from_iter<T: IntoIterator<Item = PlacedPiece>>(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);
}
}

127
board/src/pieces.rs Normal file
View file

@ -0,0 +1,127 @@
// Eryn Wells <eryn@erynwells.me>
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>,
shape_iterator: Box<dyn Iterator<Item = &'static Shape>>,
square_iterator: Option<Box<dyn Iterator<Item = Square>>>,
}
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<Self::Item> {
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<Shape> = 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<PlacedPiece> = 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::<Vec<String>>()
);
}
}