Directly rename board -> position

This commit is contained in:
Eryn Wells 2024-01-28 09:56:57 -08:00
parent 569693bda9
commit 220da08727
25 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,7 @@
// Eryn Wells <eryn@erynwells.me>
mod move_builder;
mod position_builder;
pub use move_builder::Builder as MoveBuilder;
pub use position_builder::Builder as PositionBuilder;

View file

@ -0,0 +1,358 @@
// Eryn Wells <eryn@erynwells.me>
use crate::{
position::flags::Flags, r#move::Castle, sight::SightExt, MakeMoveError, Move, Position,
};
use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square};
/// A position builder that builds a new position by making a move.
#[derive(Clone)]
pub struct Builder<'p, M: MoveToMake> {
position: &'p Position,
move_to_make: M,
}
pub trait MoveToMake {}
pub struct NoMove;
pub enum ValidatedMove {
RegularMove {
from_square: Square,
to_square: Square,
moving_piece: PlacedPiece,
captured_piece: Option<PlacedPiece>,
promotion: Option<Shape>,
flags: Flags,
en_passant_square: Option<Square>,
increment_ply: bool,
},
Castle {
castle: Castle,
king: PlacedPiece,
rook: PlacedPiece,
flags: Flags,
},
}
impl MoveToMake for NoMove {}
impl MoveToMake for ValidatedMove {}
impl<'p> Builder<'p, NoMove> {
pub fn new(position: &'p Position) -> Builder<'p, NoMove> {
Builder {
position,
move_to_make: NoMove,
}
}
}
impl<'p, M> Builder<'p, M>
where
M: MoveToMake,
{
pub fn make(self, mv: &Move) -> Result<Builder<'p, ValidatedMove>, MakeMoveError> {
let from_square = mv.from_square();
let piece = self
.position
.piece_on_square(from_square)
.ok_or(MakeMoveError::NoPiece)?;
let to_square = mv.to_square();
let sight = self.position.sight_of_piece(&piece);
if !sight.is_set(to_square) {
return Err(MakeMoveError::IllegalSquare(to_square));
}
let player = self.position.player_to_move();
let captured_piece: Option<PlacedPiece> = if mv.is_en_passant() {
// En passant captures the pawn directly ahead (in the player's direction) of the en passant square.
let capture_square = match player {
Color::White => to_square.neighbor(Direction::South),
Color::Black => to_square.neighbor(Direction::North),
}
.ok_or(MakeMoveError::NoCapturedPiece)?;
Some(
self.position
.piece_on_square(capture_square)
.ok_or(MakeMoveError::NoCapturedPiece)?,
)
} else if mv.is_capture() {
Some(
self.position
.piece_on_square(to_square)
.ok_or(MakeMoveError::NoCapturedPiece)?,
)
} else {
None
};
// TODO: Check whether the move is legal.
let piece_is_king = piece.is_king();
let mut flags = self.position.flags().clone();
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);
} else if piece.is_kingside_rook() {
flags.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);
}
if let Some(castle) = mv.castle() {
println!("piece is king: {}", piece_is_king);
if !piece_is_king || !self.position.player_can_castle(player, castle) {
return Err(MakeMoveError::IllegalCastle);
}
let rook = self
.position
.rook_for_castle(player, castle)
.ok_or(MakeMoveError::NoPiece)?;
Ok(Builder {
position: self.position,
move_to_make: ValidatedMove::Castle {
castle,
king: piece,
rook,
flags,
},
})
} else {
let en_passant_square: Option<Square> = if mv.is_double_push() {
match piece.color() {
Color::White => to_square.neighbor(Direction::South),
Color::Black => to_square.neighbor(Direction::North),
}
} else {
None
};
Ok(Builder {
position: self.position,
move_to_make: ValidatedMove::RegularMove {
from_square,
to_square,
moving_piece: piece,
captured_piece,
promotion: mv.promotion(),
flags,
en_passant_square,
increment_ply: !(mv.is_capture() || piece.is_pawn()),
},
})
}
}
}
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 };
match self.move_to_make {
ValidatedMove::RegularMove {
from_square,
to_square,
moving_piece,
captured_piece,
promotion,
flags,
en_passant_square,
increment_ply,
} => {
let mut pieces = self.position.piece_bitboards().clone();
if let Some(captured_piece) = captured_piece {
pieces.remove_piece(&captured_piece);
}
if let Some(promotion) = promotion {
pieces.remove_piece(&moving_piece);
let _ = pieces
.place_piece(&PlacedPiece::new(Piece::new(player, promotion), to_square));
} else {
pieces.move_piece(moving_piece.piece(), from_square, to_square);
}
let ply = if increment_ply {
self.position.ply_counter() + 1
} else {
0
};
Position::new(
self.position.player_to_move().other(),
flags,
pieces,
en_passant_square,
ply,
updated_move_number,
)
}
ValidatedMove::Castle {
castle,
king,
rook,
flags,
} => {
let mut pieces = self.position.piece_bitboards().clone();
let target_squares = castle.target_squares(player);
let king_from: BitBoard = king.square().into();
let king_to: BitBoard = target_squares.king.into();
*pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to;
let rook_from: BitBoard = rook.square().into();
let rook_to: BitBoard = target_squares.rook.into();
*pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to;
*pieces.bitboard_for_color_mut(player) &=
!(king_from | rook_from) | (king_to | rook_to);
Position::new(
player.other(),
flags,
pieces,
None,
self.position.ply_counter() + 1,
updated_move_number,
)
}
}
}
}
impl<'p> From<&'p Position> for Builder<'p, NoMove> {
fn from(position: &'p Position) -> Self {
Self {
position,
move_to_make: NoMove,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{position, r#move::Castle, MoveBuilder, PositionBuilder};
use chessfriend_core::piece;
#[test]
fn move_white_pawn_one_square() -> Result<(), MakeMoveError> {
let pos = position![White Pawn on E2];
let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build();
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
println!("{}", &new_position);
assert_eq!(
new_position.piece_on_square(Square::E3),
Some(piece!(White Pawn on E3))
);
Ok(())
}
#[test]
fn move_white_pawn_two_squares() -> Result<(), MakeMoveError> {
let pos = position![White Pawn on E2];
let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build();
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
println!("{}", &new_position);
assert_eq!(
new_position.piece_on_square(Square::E4),
Some(piece!(White Pawn on E4))
);
assert_eq!(new_position.en_passant_square(), Some(Square::E3));
Ok(())
}
#[test]
fn white_kingside_castle() -> Result<(), MakeMoveError> {
let pos = position![
White King on E1,
White Rook on H1,
White Pawn on E2,
White Pawn on F2,
White Pawn on G2,
White Pawn on H2
];
println!("{}", &pos);
let mv = MoveBuilder::new(piece!(White King), Square::E1, Square::G1)
.castle(Castle::KingSide)
.build();
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
println!("{}", &new_position);
assert_eq!(
new_position.piece_on_square(Square::G1),
Some(piece!(White King on G1))
);
assert_eq!(
new_position.piece_on_square(Square::F1),
Some(piece!(White Rook on F1))
);
Ok(())
}
#[test]
fn en_passant_capture() -> Result<(), MakeMoveError> {
let pos = PositionBuilder::new()
.place_piece(piece!(White Pawn on B5))
.place_piece(piece!(Black Pawn on A7))
.to_move(Color::Black)
.build();
println!("{pos}");
let black_pawn_move = MoveBuilder::new(piece!(Black Pawn), Square::A7, Square::A5).build();
assert!(black_pawn_move.is_double_push());
assert!(!black_pawn_move.is_en_passant());
let en_passant_position = Builder::<NoMove>::new(&pos).make(&black_pawn_move)?.build();
println!("{en_passant_position}");
assert_eq!(
en_passant_position.piece_on_square(Square::A5),
Some(piece!(Black Pawn on A5))
);
assert_eq!(
en_passant_position.piece_on_square(Square::B5),
Some(piece!(White Pawn on B5))
);
let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6)
.capturing_en_passant(piece!(Black Pawn on A5))
.build();
let en_passant_capture = Builder::<NoMove>::new(&en_passant_position)
.make(&white_pawn_capture)?
.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.piece_on_square(Square::A6),
Some(piece!(White Pawn on A6))
);
Ok(())
}
}

View file

@ -0,0 +1,140 @@
// Eryn Wells <eryn@erynwells.me>
use crate::{
position::{flags::Flags, piece_sets::PieceBitBoards},
r#move::Castle,
MakeMoveError, Move, Position,
};
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: [Square; 2],
ply_counter: u16,
move_number: u16,
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
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: [white_king, black_king],
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 place_piece(&mut self, piece: PlacedPiece) -> &mut Self {
let square = piece.square();
if piece.shape() == Shape::King {
let color_index: usize = piece.color() as usize;
self.pieces.remove(&self.kings[color_index]);
self.kings[color_index] = square;
}
self.pieces.insert(square, *piece.piece());
self
}
pub fn build(&self) -> Position {
let pieces = PieceBitBoards::from_iter(
self.pieces
.iter()
.map(PlacedPiece::from)
.filter(Self::is_piece_placement_valid),
);
Position::new(
self.player_to_move,
self.flags,
pieces,
None,
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: [white_king_square, black_king_square],
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));
}
}

View file

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

View file

@ -0,0 +1,87 @@
// Eryn Wells <eryn@erynwells.me>
use crate::r#move::Castle;
use chessfriend_core::Color;
use std::fmt;
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub(super) 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.into_index()
}
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));
}
}
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::*;
use crate::r#move::Castle;
use chessfriend_core::Color;
#[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

@ -0,0 +1,16 @@
// Eryn Wells <eryn@erynwells.me>
pub mod piece_sets;
mod builders;
mod diagram_formatter;
mod flags;
mod pieces;
mod position;
pub use {
builders::{MoveBuilder, PositionBuilder},
diagram_formatter::DiagramFormatter,
pieces::Pieces,
position::Position,
};

View file

@ -0,0 +1,181 @@
// Eryn Wells <eryn@erynwells.me>
use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, 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),
}
}
pub(super) fn king(&self, color: Color) -> &BitBoard {
self.by_color_and_shape
.bitboard_for_piece(&Piece::new(color, Shape::King))
}
pub(crate) fn all_pieces(&self) -> &BitBoard {
self.by_color.all()
}
pub(crate) fn empty_squares(&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, Default::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);
}
}

View file

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

View file

@ -0,0 +1,402 @@
// Eryn Wells <eryn@erynwells.me>
use super::{flags::Flags, piece_sets::PieceBitBoards, Pieces};
use crate::{
move_generator::{MoveSet, Moves},
position::DiagramFormatter,
r#move::Castle,
sight::SightExt,
Move,
};
use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
use std::{cell::OnceCell, fmt};
#[derive(Clone, Debug, Eq)]
pub struct Position {
color_to_move: Color,
flags: Flags,
pieces: PieceBitBoards,
en_passant_square: Option<Square>,
sight: [OnceCell<BitBoard>; 2],
moves: OnceCell<Moves>,
half_move_counter: u16,
full_move_number: u16,
}
impl Position {
pub fn empty() -> Position {
Position {
color_to_move: Color::White,
flags: Default::default(),
pieces: PieceBitBoards::default(),
en_passant_square: None,
sight: [OnceCell::new(), OnceCell::new()],
moves: OnceCell::new(),
half_move_counter: 0,
full_move_number: 1,
}
}
/// Return a starting position.
pub fn starting() -> Self {
let black_pieces = [
BitBoard::new(0b0000000011111111 << 48),
BitBoard::new(0b0100001000000000 << 48),
BitBoard::new(0b0010010000000000 << 48),
BitBoard::new(0b1000000100000000 << 48),
BitBoard::new(0b0000100000000000 << 48),
BitBoard::new(0b0001000000000000 << 48),
];
let white_pieces = [
BitBoard::new(0b1111111100000000),
BitBoard::new(0b0000000001000010),
BitBoard::new(0b0000000000100100),
BitBoard::new(0b0000000010000001),
BitBoard::new(0b0000000000001000),
BitBoard::new(0b0000000000010000),
];
Self {
color_to_move: Color::White,
flags: Flags::default(),
pieces: PieceBitBoards::new([white_pieces, black_pieces]),
en_passant_square: None,
sight: [OnceCell::new(), OnceCell::new()],
moves: OnceCell::new(),
half_move_counter: 0,
full_move_number: 1,
}
}
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;
}
// TODO: Perform a real check that the player can castle.
true
}
/// Return a PlacedPiece representing the rook to use for a castling move.
pub(crate) fn rook_for_castle(&self, player: Color, castle: Castle) -> Option<PlacedPiece> {
let square = match (player, castle) {
(Color::White, Castle::KingSide) => Square::H1,
(Color::White, Castle::QueenSide) => Square::A1,
(Color::Black, Castle::KingSide) => Square::H8,
(Color::Black, Castle::QueenSide) => Square::A8,
};
self.piece_on_square(square)
}
pub fn moves(&self) -> &Moves {
self.moves
.get_or_init(|| Moves::new(self, self.color_to_move))
}
/// Return a BitBoard representing the set of squares containing a piece.
#[inline]
pub(crate) fn occupied_squares(&self) -> &BitBoard {
&self.pieces.all_pieces()
}
#[inline]
pub(crate) fn friendly_pieces(&self) -> &BitBoard {
self.pieces.all_pieces_of_color(self.color_to_move)
}
#[inline]
pub(crate) fn opposing_pieces(&self) -> &BitBoard {
self.pieces.all_pieces_of_color(self.color_to_move.other())
}
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]
pub(crate) fn empty_squares(&self) -> BitBoard {
!self.occupied_squares()
}
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
}
pub fn pieces(&self, color: Color) -> Pieces {
Pieces::new(&self, color)
}
pub fn en_passant_square(&self) -> Option<Square> {
self.en_passant_square
}
/// A bitboard representing the squares the pieces of the given color can
/// see. This is synonymous with the squares attacked by the player's
/// pieces.
pub(crate) fn sight_of_player(&self, color: Color) -> BitBoard {
*self.sight[color as usize].get_or_init(|| self._sight_of_player(color, &self.pieces))
}
fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard {
let en_passant_square = self.en_passant_square;
Shape::ALL
.iter()
.filter_map(|&shape| {
let piece = Piece::new(player, shape);
let bitboard = pieces.bitboard_for_piece(&piece);
if !bitboard.is_empty() {
Some((piece, bitboard))
} else {
None
}
})
.flat_map(|(piece, &bitboard)| {
bitboard.occupied_squares().map(move |square| {
PlacedPiece::new(piece, square).sight(pieces, en_passant_square)
})
})
.fold(BitBoard::empty(), |acc, sight| acc | sight)
}
pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard {
piece.sight(&self.pieces, self.en_passant_square)
}
/// A bitboard representing the squares where a king of the given color will
/// be in danger. The king cannot move to these squares.
pub(crate) fn king_danger(&self) -> BitBoard {
let pieces_without_king = {
let mut cloned_pieces = self.pieces.clone();
let placed_king = PlacedPiece::new(
Piece::king(self.color_to_move),
self.king_square(self.color_to_move),
);
cloned_pieces.remove_piece(&placed_king);
cloned_pieces
};
self._sight_of_player(self.color_to_move.other(), &pieces_without_king)
}
pub(crate) fn is_king_in_check(&self) -> bool {
let sight_of_opposing_player = self.sight_of_player(self.color_to_move.other());
sight_of_opposing_player.is_set(self.king_square(self.color_to_move))
}
pub(crate) fn king_square(&self, player: Color) -> Square {
self.pieces
.bitboard_for_piece(&Piece::king(player))
.occupied_squares()
.next()
.unwrap()
}
pub(crate) fn move_is_legal(&self, mv: Move) -> bool {
true
}
}
// crate::position methods
impl Position {
pub(super) fn new(
player_to_move: Color,
flags: Flags,
pieces: PieceBitBoards,
en_passant_square: Option<Square>,
half_move_counter: u16,
full_move_number: u16,
) -> Self {
Self {
color_to_move: player_to_move,
flags,
en_passant_square,
pieces,
sight: [OnceCell::new(), OnceCell::new()],
moves: OnceCell::new(),
half_move_counter: 0,
full_move_number: 1,
}
}
pub(super) fn flags(&self) -> Flags {
self.flags
}
pub(super) fn piece_bitboards(&self) -> &PieceBitBoards {
&self.pieces
}
}
// crate methods
impl Position {
pub(crate) fn bitboard_for_color(&self, color: Color) -> &BitBoard {
self.pieces.bitboard_for_color(color)
}
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_square(&mut self, square: Square) {
self.en_passant_square = Some(square);
}
}
impl Default for Position {
fn default() -> Self {
Self::empty()
}
}
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_square == other.en_passant_square
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", DiagramFormatter::new(self))
}
}
#[cfg(test)]
mod tests {
use crate::{position, test_position, Castle, Position, PositionBuilder};
use chessfriend_bitboard::bitboard;
use chessfriend_core::{piece, Color, Square};
#[test]
fn piece_on_square() {
let pos = test_position![
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 pos = test_position!(starting);
assert_eq!(
pos.piece_on_square(Square::H1),
Some(piece!(White Rook on H1))
);
assert_eq!(
pos.piece_on_square(Square::A8),
Some(piece!(Black Rook on A8))
);
}
#[test]
fn king_is_in_check() {
let pos = position![
White King on E1,
Black Rook on E8,
];
assert!(pos.is_king_in_check());
}
#[test]
fn king_is_not_in_check() {
let pos = position![
White King on F1,
Black Rook on E8,
];
assert!(!pos.is_king_in_check());
}
#[test]
fn rook_for_castle() {
let pos = position![
White King on E1,
White Rook on H1,
White Rook on A1,
];
assert_eq!(
pos.rook_for_castle(Color::White, Castle::KingSide),
Some(piece!(White Rook on H1))
);
assert_eq!(
pos.rook_for_castle(Color::White, Castle::QueenSide),
Some(piece!(White Rook on A1))
);
}
#[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 danger_squares = pos.king_danger();
let expected =
bitboard![D1, F1, D2, E2, F2, E3, A4, B4, C4, D4, F4, G4, H4, E5, E6, E7, E8];
assert_eq!(
danger_squares, expected,
"Actual:\n{}\n\nExpected:\n{}",
danger_squares, expected
);
}
}