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