Merge branch 'moves-crate'
This commit is contained in:
commit
0fc90a4a2e
40 changed files with 1599 additions and 1029 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -74,12 +74,11 @@ name = "chessfriend_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chessfriend_move_generator"
|
name = "chessfriend_moves"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chessfriend_bitboard",
|
"chessfriend_bitboard",
|
||||||
"chessfriend_core",
|
"chessfriend_core",
|
||||||
"chessfriend_position",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -88,6 +87,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chessfriend_bitboard",
|
"chessfriend_bitboard",
|
||||||
"chessfriend_core",
|
"chessfriend_core",
|
||||||
|
"chessfriend_moves",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -172,6 +172,7 @@ name = "explorer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chessfriend_core",
|
"chessfriend_core",
|
||||||
|
"chessfriend_moves",
|
||||||
"chessfriend_position",
|
"chessfriend_position",
|
||||||
"clap",
|
"clap",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
|
|
|
@ -3,6 +3,7 @@ members = [
|
||||||
"bitboard",
|
"bitboard",
|
||||||
"core",
|
"core",
|
||||||
"explorer",
|
"explorer",
|
||||||
|
"moves",
|
||||||
"position",
|
"position",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::Color;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -168,6 +169,16 @@ impl Rank {
|
||||||
/// assert_eq!(Rank::PAWN_STARTING_RANKS[Color::Black as usize], Rank::SEVEN);
|
/// assert_eq!(Rank::PAWN_STARTING_RANKS[Color::Black as usize], Rank::SEVEN);
|
||||||
/// ```
|
/// ```
|
||||||
pub const PAWN_STARTING_RANKS: [Rank; 2] = [Rank::TWO, Rank::SEVEN];
|
pub const PAWN_STARTING_RANKS: [Rank; 2] = [Rank::TWO, Rank::SEVEN];
|
||||||
|
|
||||||
|
pub const PAWN_DOUBLE_PUSH_TARGET_RANKS: [Rank; 2] = [Rank::FOUR, Rank::FIVE];
|
||||||
|
|
||||||
|
pub fn is_pawn_starting_rank(&self, color: Color) -> bool {
|
||||||
|
self == &Self::PAWN_STARTING_RANKS[color as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_pawn_double_push_target_rank(&self, color: Color) -> bool {
|
||||||
|
self == &Self::PAWN_DOUBLE_PUSH_TARGET_RANKS[color as usize]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chessfriend_core = { path = "../core" }
|
chessfriend_core = { path = "../core" }
|
||||||
|
chessfriend_moves = { path = "../moves" }
|
||||||
chessfriend_position = { path = "../position" }
|
chessfriend_position = { path = "../position" }
|
||||||
clap = { version = "4.4.12", features = ["derive"] }
|
clap = { version = "4.4.12", features = ["derive"] }
|
||||||
rustyline = "13.0.0"
|
rustyline = "13.0.0"
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||||
use chessfriend_position::{fen::ToFen, MakeMoveBuilder, MoveBuilder, Position, PositionBuilder};
|
use chessfriend_moves::Builder as MoveBuilder;
|
||||||
|
use chessfriend_position::{fen::ToFen, MakeMoveBuilder, Position, PositionBuilder};
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
|
@ -97,16 +100,15 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
|
||||||
)
|
)
|
||||||
.map_err(|_| "Error: invalid square specifier")?;
|
.map_err(|_| "Error: invalid square specifier")?;
|
||||||
|
|
||||||
let mv = MoveBuilder::new(
|
let mv = MoveBuilder::new()
|
||||||
Piece::new(state.position.player_to_move(), shape),
|
.from(from_square)
|
||||||
from_square,
|
.to(to_square)
|
||||||
to_square,
|
.build()
|
||||||
)
|
.map_err(|err| format!("Error: cannot build move: {:?}", err))?;
|
||||||
.build();
|
|
||||||
|
|
||||||
state.position = MakeMoveBuilder::new(&state.position)
|
state.position = MakeMoveBuilder::new(&state.position)
|
||||||
.make(&mv)
|
.make(&mv)
|
||||||
.map_err(|err| format!("error: Cannot make move: {:?}", err))?
|
.map_err(|err| format!("Error: cannot make move: {:?}", err))?
|
||||||
.build();
|
.build();
|
||||||
state.builder = PositionBuilder::from_position(&state.position);
|
state.builder = PositionBuilder::from_position(&state.position);
|
||||||
}
|
}
|
||||||
|
|
10
moves/Cargo.toml
Normal file
10
moves/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "chessfriend_moves"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chessfriend_core = { path = "../core" }
|
||||||
|
chessfriend_bitboard = { path = "../bitboard" }
|
395
moves/src/builder.rs
Normal file
395
moves/src/builder.rs
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::{castle, defs::Kind, EnPassant, Move, PromotionShape};
|
||||||
|
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square};
|
||||||
|
use std::result::Result as StdResult;
|
||||||
|
|
||||||
|
pub type Result = std::result::Result<Move, Error>;
|
||||||
|
type EncodedMoveResult = std::result::Result<u16, Error>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
MissingOriginSquare,
|
||||||
|
MissingTargetSquare,
|
||||||
|
MissingCaptureSquare,
|
||||||
|
InvalidEnPassantSquare,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Style {
|
||||||
|
fn origin_square(&self) -> Option<Square> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_square(&self) -> Option<Square> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_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))
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn into_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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Builder<S: Style> {
|
||||||
|
style: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Null;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Push {
|
||||||
|
from: Option<Square>,
|
||||||
|
to: Option<Square>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DoublePush {
|
||||||
|
from: Square,
|
||||||
|
to: Square,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Capture {
|
||||||
|
push: Push,
|
||||||
|
capture: Option<Square>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct EnPassantCapture {
|
||||||
|
push: Push,
|
||||||
|
capture: Option<EnPassant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Promotion<S> {
|
||||||
|
style: S,
|
||||||
|
promotion: PromotionShape,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Castle {
|
||||||
|
color: Color,
|
||||||
|
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 {
|
||||||
|
fn origin_square(&self) -> Option<Square> {
|
||||||
|
self.from
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_square(&self) -> Option<Square> {
|
||||||
|
self.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style for Capture {
|
||||||
|
fn origin_square(&self) -> Option<Square> {
|
||||||
|
self.push.from
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_square(&self) -> Option<Square> {
|
||||||
|
self.push.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style for Castle {
|
||||||
|
fn origin_square(&self) -> Option<Square> {
|
||||||
|
let parameters = self.castle.parameters(self.color);
|
||||||
|
Some(parameters.king_origin_square())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_square(&self) -> Option<Square> {
|
||||||
|
let parameters = self.castle.parameters(self.color);
|
||||||
|
Some(parameters.king_target_square())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style for DoublePush {
|
||||||
|
fn origin_square(&self) -> Option<Square> {
|
||||||
|
Some(self.from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_square(&self) -> Option<Square> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style for EnPassantCapture {
|
||||||
|
fn origin_square(&self) -> Option<Square> {
|
||||||
|
self.push.from
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_square(&self) -> Option<Square> {
|
||||||
|
self.push.to
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style for Promotion<Push> {
|
||||||
|
fn origin_square(&self) -> Option<Square> {
|
||||||
|
self.style.from
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_square(&self) -> Option<Square> {
|
||||||
|
self.style.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style for Promotion<Capture> {
|
||||||
|
fn origin_square(&self) -> Option<Square> {
|
||||||
|
self.style.push.from
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_square(&self) -> Option<Square> {
|
||||||
|
self.style.push.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Promotion<Push> {
|
||||||
|
fn into_move_bits(&self) -> StdResult<u16, Error> {
|
||||||
|
let origin_square = self
|
||||||
|
.style
|
||||||
|
.origin_square()
|
||||||
|
.ok_or(Error::MissingOriginSquare)? as u16;
|
||||||
|
let target_square = self
|
||||||
|
.style
|
||||||
|
.target_square()
|
||||||
|
.ok_or(Error::MissingTargetSquare)? as u16;
|
||||||
|
|
||||||
|
Ok(Kind::Promotion as u16
|
||||||
|
| self.promotion as u16
|
||||||
|
| (origin_square & 0b111111) << 4
|
||||||
|
| (target_square & 0b111111) << 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Promotion<Capture> {
|
||||||
|
fn into_move_bits(&self) -> StdResult<u16, Error> {
|
||||||
|
let origin_square = self
|
||||||
|
.style
|
||||||
|
.origin_square()
|
||||||
|
.ok_or(Error::MissingOriginSquare)? as u16;
|
||||||
|
let target_square = self
|
||||||
|
.style
|
||||||
|
.target_square()
|
||||||
|
.ok_or(Error::MissingTargetSquare)? as u16;
|
||||||
|
|
||||||
|
Ok(Kind::CapturePromotion as u16
|
||||||
|
| self.promotion as u16
|
||||||
|
| (origin_square & 0b111111) << 4
|
||||||
|
| (target_square & 0b111111) << 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<Null> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { style: Null }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(piece: &PlacedPiece) -> Builder<Push> {
|
||||||
|
Builder {
|
||||||
|
style: Push {
|
||||||
|
from: Some(piece.square()),
|
||||||
|
to: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn double_push(file: File, color: Color) -> Builder<DoublePush> {
|
||||||
|
let (from, to) = match color {
|
||||||
|
Color::White => (
|
||||||
|
Square::from_file_rank(file, Rank::TWO),
|
||||||
|
Square::from_file_rank(file, Rank::FOUR),
|
||||||
|
),
|
||||||
|
Color::Black => (
|
||||||
|
Square::from_file_rank(file, Rank::SEVEN),
|
||||||
|
Square::from_file_rank(file, Rank::FIVE),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
Builder {
|
||||||
|
style: DoublePush { from, to },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn castling(color: Color, castle: castle::Castle) -> Builder<Castle> {
|
||||||
|
Builder {
|
||||||
|
style: Castle { color, castle },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capturing_piece(piece: &PlacedPiece, capturing: &PlacedPiece) -> Builder<Capture> {
|
||||||
|
Self::push(piece).capturing_piece(&capturing)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(&self, square: Square) -> Builder<Push> {
|
||||||
|
Builder {
|
||||||
|
style: Push {
|
||||||
|
from: Some(square),
|
||||||
|
to: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Move {
|
||||||
|
Move(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<Push> {
|
||||||
|
pub fn from(&mut self, square: Square) -> &mut Self {
|
||||||
|
self.style.from = Some(square);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to(&mut self, square: Square) -> &mut Self {
|
||||||
|
self.style.to = Some(square);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capturing_on(&self, square: Square) -> Builder<Capture> {
|
||||||
|
let mut style = self.style.clone();
|
||||||
|
style.to = Some(square);
|
||||||
|
|
||||||
|
Builder {
|
||||||
|
style: Capture {
|
||||||
|
push: style,
|
||||||
|
capture: Some(square),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capturing_en_passant_on(&self, target_square: Square) -> Builder<EnPassantCapture> {
|
||||||
|
match EnPassant::from_target_square(target_square) {
|
||||||
|
Some(en_passant) => {
|
||||||
|
let mut style = self.style.clone();
|
||||||
|
style.to = Some(en_passant.target_square());
|
||||||
|
|
||||||
|
Builder {
|
||||||
|
style: EnPassantCapture {
|
||||||
|
push: style,
|
||||||
|
capture: Some(en_passant),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capturing_piece(&self, piece: &PlacedPiece) -> Builder<Capture> {
|
||||||
|
Builder {
|
||||||
|
style: Capture {
|
||||||
|
push: self.style.clone(),
|
||||||
|
capture: Some(piece.square()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn promoting_to(&self, shape: PromotionShape) -> Builder<Promotion<Push>> {
|
||||||
|
Builder {
|
||||||
|
style: Promotion {
|
||||||
|
style: self.style.clone(),
|
||||||
|
promotion: shape,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Result {
|
||||||
|
Ok(Move(Kind::Quiet as u16 | self.style.into_move_bits()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<Castle> {
|
||||||
|
fn bits(&self) -> u16 {
|
||||||
|
let bits = match self.style.castle {
|
||||||
|
castle::Castle::KingSide => Kind::KingSideCastle,
|
||||||
|
castle::Castle::QueenSide => Kind::QueenSideCastle,
|
||||||
|
};
|
||||||
|
|
||||||
|
bits as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Result {
|
||||||
|
Ok(Move(self.bits() | self.style.into_move_bits()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<Capture> {
|
||||||
|
pub fn promoting_to(self, shape: PromotionShape) -> Builder<Promotion<Capture>> {
|
||||||
|
Builder {
|
||||||
|
style: Promotion {
|
||||||
|
style: self.style,
|
||||||
|
promotion: shape,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Result {
|
||||||
|
Ok(Move(Kind::Capture as u16 | self.style.into_move_bits()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<DoublePush> {
|
||||||
|
pub fn build(&self) -> Result {
|
||||||
|
Ok(Move(Kind::DoublePush as u16 | self.style.into_move_bits()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<EnPassantCapture> {
|
||||||
|
pub unsafe fn build_unchecked(&self) -> Move {
|
||||||
|
Move(Kind::EnPassantCapture as u16 | self.style.into_move_bits_unchecked())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Result {
|
||||||
|
Ok(Move(
|
||||||
|
Kind::EnPassantCapture as u16 | self.style.into_move_bits()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<Promotion<Push>> {
|
||||||
|
pub fn build(&self) -> Result {
|
||||||
|
Ok(Move(self.style.into_move_bits()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<Promotion<Capture>> {
|
||||||
|
pub fn build(&self) -> Result {
|
||||||
|
Ok(Move(self.style.into_move_bits()?))
|
||||||
|
}
|
||||||
|
}
|
121
moves/src/castle.rs
Normal file
121
moves/src/castle.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use chessfriend_bitboard::BitBoard;
|
||||||
|
use chessfriend_core::{Color, Square};
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Castle {
|
||||||
|
KingSide = 0,
|
||||||
|
QueenSide = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Parameters {
|
||||||
|
/// Origin squares of the king and rook.
|
||||||
|
origin_squares: Squares,
|
||||||
|
|
||||||
|
/// Target or destination squares for the king and rook.
|
||||||
|
target_squares: Squares,
|
||||||
|
|
||||||
|
/// The set of squares that must be clear of any pieces in order to perform this castle.
|
||||||
|
clear_squares: BitBoard,
|
||||||
|
|
||||||
|
/// The set of squares that must not be attacked in order to perform this castle.
|
||||||
|
check_squares: BitBoard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parameters {
|
||||||
|
pub fn king_origin_square(&self) -> Square {
|
||||||
|
self.origin_squares.king
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rook_origin_square(&self) -> Square {
|
||||||
|
self.origin_squares.rook
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn king_target_square(&self) -> Square {
|
||||||
|
self.target_squares.king
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rook_target_square(&self) -> Square {
|
||||||
|
self.target_squares.rook
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_squares(&self) -> &BitBoard {
|
||||||
|
&self.clear_squares
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_squares(&self) -> &BitBoard {
|
||||||
|
&self.check_squares
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Squares {
|
||||||
|
king: Square,
|
||||||
|
rook: Square,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Castle {
|
||||||
|
pub const ALL: [Castle; 2] = [Castle::KingSide, Castle::QueenSide];
|
||||||
|
|
||||||
|
/// Parameters for each castling move, organized by color and board-side.
|
||||||
|
const PARAMETERS: [[Parameters; 2]; 2] = [
|
||||||
|
[
|
||||||
|
Parameters {
|
||||||
|
origin_squares: Squares {
|
||||||
|
king: Square::E1,
|
||||||
|
rook: Square::H1,
|
||||||
|
},
|
||||||
|
target_squares: Squares {
|
||||||
|
king: Square::G1,
|
||||||
|
rook: Square::F1,
|
||||||
|
},
|
||||||
|
clear_squares: BitBoard::new(0b01100000),
|
||||||
|
check_squares: BitBoard::new(0b01110000),
|
||||||
|
},
|
||||||
|
Parameters {
|
||||||
|
origin_squares: Squares {
|
||||||
|
king: Square::E1,
|
||||||
|
rook: Square::A1,
|
||||||
|
},
|
||||||
|
target_squares: Squares {
|
||||||
|
king: Square::C1,
|
||||||
|
rook: Square::D1,
|
||||||
|
},
|
||||||
|
clear_squares: BitBoard::new(0b00001110),
|
||||||
|
check_squares: BitBoard::new(0b00011100),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Parameters {
|
||||||
|
origin_squares: Squares {
|
||||||
|
king: Square::E8,
|
||||||
|
rook: Square::H8,
|
||||||
|
},
|
||||||
|
target_squares: Squares {
|
||||||
|
king: Square::G8,
|
||||||
|
rook: Square::F8,
|
||||||
|
},
|
||||||
|
clear_squares: BitBoard::new(0b01100000 << 8 * 7),
|
||||||
|
check_squares: BitBoard::new(0b01110000 << 8 * 7),
|
||||||
|
},
|
||||||
|
Parameters {
|
||||||
|
origin_squares: Squares {
|
||||||
|
king: Square::E8,
|
||||||
|
rook: Square::A8,
|
||||||
|
},
|
||||||
|
target_squares: Squares {
|
||||||
|
king: Square::C8,
|
||||||
|
rook: Square::D8,
|
||||||
|
},
|
||||||
|
clear_squares: BitBoard::new(0b00001110 << 8 * 7),
|
||||||
|
check_squares: BitBoard::new(0b00011100 << 8 * 7),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn parameters(&self, color: Color) -> &'static Parameters {
|
||||||
|
&Castle::PARAMETERS[color as usize][*self as usize]
|
||||||
|
}
|
||||||
|
}
|
34
moves/src/defs.rs
Normal file
34
moves/src/defs.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use chessfriend_core::Shape;
|
||||||
|
|
||||||
|
pub(crate) enum Kind {
|
||||||
|
Quiet = 0b0000,
|
||||||
|
DoublePush = 0b0001,
|
||||||
|
KingSideCastle = 0b0010,
|
||||||
|
QueenSideCastle = 0b0011,
|
||||||
|
Capture = 0b0100,
|
||||||
|
EnPassantCapture = 0b0101,
|
||||||
|
Promotion = 0b1000,
|
||||||
|
CapturePromotion = 0b1100,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum PromotionShape {
|
||||||
|
Knight = 0b00,
|
||||||
|
Bishop = 0b01,
|
||||||
|
Rook = 0b10,
|
||||||
|
Queen = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PromotionShape> for Shape {
|
||||||
|
fn from(value: PromotionShape) -> Self {
|
||||||
|
match value {
|
||||||
|
PromotionShape::Knight => Shape::Knight,
|
||||||
|
PromotionShape::Bishop => Shape::Bishop,
|
||||||
|
PromotionShape::Rook => Shape::Rook,
|
||||||
|
PromotionShape::Queen => Shape::Queen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
moves/src/en_passant.rs
Normal file
51
moves/src/en_passant.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use chessfriend_core::{Rank, Square};
|
||||||
|
|
||||||
|
/// En passant information.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct EnPassant {
|
||||||
|
target: Square,
|
||||||
|
capture: Square,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnPassant {
|
||||||
|
fn _capture_square(target: Square) -> Option<Square> {
|
||||||
|
let (file, rank) = target.file_rank();
|
||||||
|
match rank {
|
||||||
|
Rank::THREE => Some(Square::from_file_rank(file, Rank::FOUR)),
|
||||||
|
Rank::SIX => Some(Square::from_file_rank(file, Rank::FIVE)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return en passant information for a particular target square. The target
|
||||||
|
/// square is the square a pawn capturing en passant will move to.
|
||||||
|
///
|
||||||
|
/// Return `None` if the square is not eligible for en passant.
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use chessfriend_core::Square;
|
||||||
|
/// use chessfriend_moves::EnPassant;
|
||||||
|
/// assert!(EnPassant::from_target_square(Square::E3).is_some());
|
||||||
|
/// assert!(EnPassant::from_target_square(Square::B4).is_none());
|
||||||
|
/// ```
|
||||||
|
pub fn from_target_square(target: Square) -> Option<Self> {
|
||||||
|
match Self::_capture_square(target) {
|
||||||
|
Some(capture) => Some(Self { target, capture }),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The square the capturing piece will move to.
|
||||||
|
pub fn target_square(&self) -> Square {
|
||||||
|
self.target
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The square on which the captured pawn sits.
|
||||||
|
pub fn capture_square(&self) -> Square {
|
||||||
|
self.capture
|
||||||
|
}
|
||||||
|
}
|
15
moves/src/lib.rs
Normal file
15
moves/src/lib.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
pub mod testing;
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
mod castle;
|
||||||
|
mod defs;
|
||||||
|
mod en_passant;
|
||||||
|
mod moves;
|
||||||
|
|
||||||
|
pub use builder::{Builder, Error as BuildMoveError, Result as BuildMoveResult};
|
||||||
|
pub use castle::Castle;
|
||||||
|
pub use defs::PromotionShape;
|
||||||
|
pub use en_passant::EnPassant;
|
||||||
|
pub use moves::Move;
|
135
moves/src/moves.rs
Normal file
135
moves/src/moves.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::{castle::Castle, defs::Kind};
|
||||||
|
use chessfriend_core::{Rank, Shape, Square};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// A single player's move. In chess parlance, this is a "ply".
|
||||||
|
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||||
|
pub struct Move(pub(crate) u16);
|
||||||
|
|
||||||
|
impl Move {
|
||||||
|
pub fn origin_square(&self) -> Square {
|
||||||
|
((self.0 >> 4) & 0b111111).try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target_square(&self) -> Square {
|
||||||
|
(self.0 >> 10).try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capture_square(&self) -> Option<Square> {
|
||||||
|
if self.is_en_passant() {
|
||||||
|
let target_square = self.target_square();
|
||||||
|
return Some(match target_square.rank() {
|
||||||
|
Rank::THREE => Square::from_file_rank(target_square.file(), Rank::FOUR),
|
||||||
|
Rank::SIX => Square::from_file_rank(target_square.file(), Rank::FIVE),
|
||||||
|
_ => unreachable!(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_capture() {
|
||||||
|
return Some(self.target_square());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_quiet(&self) -> bool {
|
||||||
|
self.flags() == Kind::Quiet as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_double_push(&self) -> bool {
|
||||||
|
self.flags() == Kind::DoublePush as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_castle(&self) -> bool {
|
||||||
|
self.castle().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn castle(&self) -> Option<Castle> {
|
||||||
|
match self.flags() {
|
||||||
|
0b0010 => Some(Castle::KingSide),
|
||||||
|
0b0011 => Some(Castle::QueenSide),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_capture(&self) -> bool {
|
||||||
|
(self.0 & 0b0100) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_en_passant(&self) -> bool {
|
||||||
|
self.flags() == 0b0101
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_promotion(&self) -> bool {
|
||||||
|
(self.0 & 0b1000) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn promotion(&self) -> Option<Shape> {
|
||||||
|
if !self.is_promotion() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(match self.special() {
|
||||||
|
0b00 => Shape::Knight,
|
||||||
|
0b01 => Shape::Bishop,
|
||||||
|
0b10 => Shape::Rook,
|
||||||
|
0b11 => Shape::Queen,
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn flags(&self) -> u16 {
|
||||||
|
self.0 & 0b1111
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn special(&self) -> u16 {
|
||||||
|
self.0 & 0b11
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _transfer_char(&self) -> char {
|
||||||
|
if self.is_capture() || self.is_en_passant() {
|
||||||
|
'x'
|
||||||
|
} else {
|
||||||
|
'-'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{}{}",
|
||||||
|
self.origin_square(),
|
||||||
|
self._transfer_char(),
|
||||||
|
self.target_square()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(promotion) = self.promotion() {
|
||||||
|
write!(f, "={}", promotion)?;
|
||||||
|
} else if self.is_en_passant() {
|
||||||
|
write!(f, " e.p.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Move {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("Move")
|
||||||
|
.field(&format_args!("{:08b}", &self.0))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
17
moves/src/testing.rs
Normal file
17
moves/src/testing.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::BuildMoveError;
|
||||||
|
|
||||||
|
pub type TestResult = Result<(), TestError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum TestError {
|
||||||
|
BuildMove(BuildMoveError),
|
||||||
|
NoLegalMoves,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BuildMoveError> for TestError {
|
||||||
|
fn from(value: BuildMoveError) -> Self {
|
||||||
|
TestError::BuildMove(value)
|
||||||
|
}
|
||||||
|
}
|
109
moves/tests/flags.rs
Normal file
109
moves/tests/flags.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use chessfriend_core::{piece, Color, File, Shape, Square};
|
||||||
|
use chessfriend_moves::{testing::*, Builder, Castle, PromotionShape};
|
||||||
|
|
||||||
|
macro_rules! assert_flag {
|
||||||
|
($move:expr, $left:expr, $right:expr, $desc:expr) => {
|
||||||
|
assert_eq!($left, $right, "{:?} -> {}", $move, stringify!($desc))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_flags {
|
||||||
|
($move:expr, $quiet:expr, $double_push:expr, $en_passant:expr, $capture:expr, $castle:expr, $promotion:expr) => {
|
||||||
|
assert_flag!($move, $move.is_quiet(), $quiet, "is_quiet");
|
||||||
|
assert_flag!(
|
||||||
|
$move,
|
||||||
|
$move.is_double_push(),
|
||||||
|
$double_push,
|
||||||
|
"is_double_push"
|
||||||
|
);
|
||||||
|
assert_flag!($move, $move.is_en_passant(), $en_passant, "is_en_passant");
|
||||||
|
assert_flag!($move, $move.is_capture(), $capture, "is_capture");
|
||||||
|
assert_flag!($move, $move.is_castle(), $castle, "is_castle");
|
||||||
|
assert_flag!($move, $move.is_promotion(), $promotion, "is_promotion");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_flags_quiet() -> TestResult {
|
||||||
|
let mv = Builder::push(&piece!(White Pawn on A4))
|
||||||
|
.to(Square::A5)
|
||||||
|
.build()?;
|
||||||
|
assert_flags!(mv, true, false, false, false, false, false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_flags_double_push() -> TestResult {
|
||||||
|
let mv = Builder::double_push(File::C, Color::White).build()?;
|
||||||
|
assert_flags!(mv, false, true, false, false, false, false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_flags_capture() -> TestResult {
|
||||||
|
let mv = Builder::new()
|
||||||
|
.from(Square::A4)
|
||||||
|
.capturing_on(Square::B5)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
assert_flags!(mv, false, false, false, true, false, false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_flags_en_passant_capture() -> TestResult {
|
||||||
|
let mv = unsafe {
|
||||||
|
Builder::new()
|
||||||
|
.from(Square::A4)
|
||||||
|
.capturing_en_passant_on(Square::B3)
|
||||||
|
.build_unchecked()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_flags!(mv, false, false, true, true, false, false);
|
||||||
|
assert_eq!(mv.origin_square(), Square::A4);
|
||||||
|
assert_eq!(mv.target_square(), Square::B3);
|
||||||
|
assert_eq!(mv.capture_square(), Some(Square::B4));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_flags_promotion() -> TestResult {
|
||||||
|
let mv = Builder::push(&piece!(White Pawn on H7))
|
||||||
|
.to(Square::H8)
|
||||||
|
.promoting_to(PromotionShape::Queen)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
assert_flags!(mv, false, false, false, false, false, true);
|
||||||
|
assert_eq!(mv.promotion(), Some(Shape::Queen));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_flags_capture_promotion() -> TestResult {
|
||||||
|
let mv = Builder::push(&piece!(White Pawn on H7))
|
||||||
|
.to(Square::H8)
|
||||||
|
.capturing_piece(&piece!(Black Knight on G8))
|
||||||
|
.promoting_to(PromotionShape::Queen)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
assert_flags!(mv, false, false, false, true, false, true);
|
||||||
|
assert_eq!(mv.promotion(), Some(Shape::Queen));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_flags_castle() -> TestResult {
|
||||||
|
let mv = Builder::castling(Color::White, Castle::KingSide).build()?;
|
||||||
|
|
||||||
|
assert_flags!(mv, false, false, false, false, true, false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
17
moves/tests/pushes.rs
Normal file
17
moves/tests/pushes.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use chessfriend_core::{piece, Square};
|
||||||
|
use chessfriend_moves::{testing::*, Builder};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pawn_push() -> TestResult {
|
||||||
|
let mv = Builder::push(&piece!(White Pawn on A3))
|
||||||
|
.to(Square::A4)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
assert!(mv.is_quiet());
|
||||||
|
assert_eq!(mv.origin_square(), Square::A3);
|
||||||
|
assert_eq!(mv.target_square(), Square::A4);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -8,3 +8,4 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chessfriend_core = { path = "../core" }
|
chessfriend_core = { path = "../core" }
|
||||||
chessfriend_bitboard = { path = "../bitboard" }
|
chessfriend_bitboard = { path = "../bitboard" }
|
||||||
|
chessfriend_moves = { path = "../moves" }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, Direction, Shape, Square};
|
use chessfriend_core::Shape;
|
||||||
|
|
||||||
use crate::sight::SliderRayToSquareExt;
|
use crate::sight::SliderRayToSquareExt;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{r#move::Castle, Position, PositionBuilder};
|
use crate::{Position, PositionBuilder};
|
||||||
use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square};
|
use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square};
|
||||||
|
use chessfriend_moves::{Castle, EnPassant};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! fen {
|
macro_rules! fen {
|
||||||
($fen_string:literal) => {
|
($fen_string:literal) => {
|
||||||
Position::from_fen_str($fen_string)
|
Position::from_fen_str($fen_string)
|
||||||
|
@ -95,11 +97,8 @@ impl ToFen for Position {
|
||||||
write!(
|
write!(
|
||||||
fen_string,
|
fen_string,
|
||||||
" {}",
|
" {}",
|
||||||
if let Some(en_passant_square) = self.en_passant_square() {
|
self.en_passant()
|
||||||
en_passant_square.to_string()
|
.map_or("-".to_string(), |ep| ep.target_square().to_string())
|
||||||
} else {
|
|
||||||
"-".to_string()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.map_err(|err| ToFenError::FmtError(err))?;
|
.map_err(|err| ToFenError::FmtError(err))?;
|
||||||
|
|
||||||
|
@ -197,7 +196,7 @@ impl FromFen for Position {
|
||||||
let en_passant_square = fields.next().ok_or(FromFenError)?;
|
let en_passant_square = fields.next().ok_or(FromFenError)?;
|
||||||
if en_passant_square != "-" {
|
if en_passant_square != "-" {
|
||||||
let square = Square::from_algebraic_str(en_passant_square).map_err(|_| FromFenError)?;
|
let square = Square::from_algebraic_str(en_passant_square).map_err(|_| FromFenError)?;
|
||||||
builder.en_passant_square(Some(square));
|
builder.en_passant(Some(EnPassant::from_target_square(square).unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let half_move_clock = fields.next().ok_or(FromFenError)?;
|
let half_move_clock = fields.next().ok_or(FromFenError)?;
|
||||||
|
|
|
@ -4,7 +4,6 @@ pub mod fen;
|
||||||
|
|
||||||
mod check;
|
mod check;
|
||||||
mod display;
|
mod display;
|
||||||
mod r#move;
|
|
||||||
mod move_generator;
|
mod move_generator;
|
||||||
mod position;
|
mod position;
|
||||||
mod sight;
|
mod sight;
|
||||||
|
@ -12,8 +11,8 @@ mod sight;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod tests;
|
mod testing;
|
||||||
|
|
||||||
pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder};
|
pub use position::{MakeMoveError, MoveBuilder as MakeMoveBuilder, Position, PositionBuilder};
|
||||||
pub use r#move::{Castle, MakeMoveError, Move, MoveBuilder};
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ macro_rules! position {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! test_position {
|
macro_rules! test_position {
|
||||||
($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => {
|
($to_move:ident, [ $($color:ident $shape:ident on $square:ident),* $(,)? ], $en_passant:ident) => {
|
||||||
|
@ -31,7 +32,7 @@ macro_rules! test_position {
|
||||||
))
|
))
|
||||||
)*
|
)*
|
||||||
.to_move(chessfriend_core::Color::$to_move)
|
.to_move(chessfriend_core::Color::$to_move)
|
||||||
.en_passant_square(Some(chessfriend_core::Square::$en_passant))
|
.en_passant(Some(chessfriend_moves::EnPassant::from_target_square(chessfriend_core::Square::$en_passant)).unwrap())
|
||||||
.build();
|
.build();
|
||||||
println!("{pos}");
|
println!("{pos}");
|
||||||
|
|
||||||
|
|
|
@ -1,602 +0,0 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
|
||||||
|
|
||||||
use chessfriend_core::{Piece, PlacedPiece, Rank, Shape, Square};
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
pub use castle::Castle;
|
|
||||||
pub(crate) use move_formatter::AlgebraicMoveFormatter;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum MakeMoveError {
|
|
||||||
PlayerOutOfTurn,
|
|
||||||
NoPiece,
|
|
||||||
NoCapturedPiece,
|
|
||||||
NoLegalMoves,
|
|
||||||
IllegalCastle,
|
|
||||||
IllegalSquare(Square),
|
|
||||||
}
|
|
||||||
|
|
||||||
mod castle {
|
|
||||||
use chessfriend_bitboard::BitBoard;
|
|
||||||
use chessfriend_core::{Color, Square};
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum Castle {
|
|
||||||
KingSide = 0b10,
|
|
||||||
QueenSide = 0b11,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct CastlingParameters {
|
|
||||||
clear_squares: BitBoard,
|
|
||||||
check_squares: BitBoard,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Squares {
|
|
||||||
pub king: Square,
|
|
||||||
pub rook: Square,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Castle {
|
|
||||||
pub const ALL: [Castle; 2] = [Castle::KingSide, Castle::QueenSide];
|
|
||||||
|
|
||||||
const STARTING_SQUARES: [[Squares; 2]; 2] = [
|
|
||||||
[
|
|
||||||
Squares {
|
|
||||||
king: Square::E1,
|
|
||||||
rook: Square::H1,
|
|
||||||
},
|
|
||||||
Squares {
|
|
||||||
king: Square::E1,
|
|
||||||
rook: Square::A1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
Squares {
|
|
||||||
king: Square::E8,
|
|
||||||
rook: Square::H8,
|
|
||||||
},
|
|
||||||
Squares {
|
|
||||||
king: Square::E8,
|
|
||||||
rook: Square::A8,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
const TARGET_SQUARES: [[Squares; 2]; 2] = [
|
|
||||||
[
|
|
||||||
Squares {
|
|
||||||
king: Square::G1,
|
|
||||||
rook: Square::F1,
|
|
||||||
},
|
|
||||||
Squares {
|
|
||||||
king: Square::C1,
|
|
||||||
rook: Square::D1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
Squares {
|
|
||||||
king: Square::G8,
|
|
||||||
rook: Square::F8,
|
|
||||||
},
|
|
||||||
Squares {
|
|
||||||
king: Square::C8,
|
|
||||||
rook: Square::D8,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
pub(crate) fn starting_squares(&self, color: Color) -> &'static Squares {
|
|
||||||
&Castle::STARTING_SQUARES[color as usize][self.into_index()]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn target_squares(&self, color: Color) -> &'static Squares {
|
|
||||||
&Castle::TARGET_SQUARES[color as usize][self.into_index()]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn into_index(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Castle::KingSide => 0,
|
|
||||||
Castle::QueenSide => 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parameters(&self) -> CastlingParameters {
|
|
||||||
match self {
|
|
||||||
Castle::KingSide => CastlingParameters {
|
|
||||||
clear_squares: BitBoard::new(0b01100000),
|
|
||||||
check_squares: BitBoard::new(0b01110000),
|
|
||||||
},
|
|
||||||
Castle::QueenSide => CastlingParameters {
|
|
||||||
clear_squares: BitBoard::new(0b00001110),
|
|
||||||
check_squares: BitBoard::new(0b00011100),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CastlingParameters {
|
|
||||||
pub fn clear_squares(&self) -> &BitBoard {
|
|
||||||
&self.clear_squares
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_squares(&self) -> &BitBoard {
|
|
||||||
&self.check_squares
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum PromotableShape {
|
|
||||||
Knight = 0b00,
|
|
||||||
Bishop = 0b01,
|
|
||||||
Rook = 0b10,
|
|
||||||
Queen = 0b11,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Shape> for PromotableShape {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: Shape) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Shape::Knight => Ok(PromotableShape::Knight),
|
|
||||||
Shape::Bishop => Ok(PromotableShape::Bishop),
|
|
||||||
Shape::Rook => Ok(PromotableShape::Rook),
|
|
||||||
Shape::Queen => Ok(PromotableShape::Queen),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
enum Kind {
|
|
||||||
Quiet = 0b00,
|
|
||||||
DoublePush = 0b01,
|
|
||||||
Castle(Castle),
|
|
||||||
Capture(PlacedPiece) = 0b0100,
|
|
||||||
EnPassantCapture(PlacedPiece) = 0b0101,
|
|
||||||
Promotion(PromotableShape) = 0b1000,
|
|
||||||
CapturePromotion(PlacedPiece, PromotableShape) = 0b1100,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Kind {
|
|
||||||
fn bits(&self) -> u16 {
|
|
||||||
match self {
|
|
||||||
Self::Promotion(shape) => self.discriminant() | *shape as u16,
|
|
||||||
Self::CapturePromotion(_, shape) => self.discriminant() | *shape as u16,
|
|
||||||
Self::Castle(castle) => *castle as u16,
|
|
||||||
_ => self.discriminant(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the discriminant value. This implementation is copied from the Rust docs.
|
|
||||||
/// See https://doc.rust-lang.org/std/mem/fn.discriminant.html
|
|
||||||
fn discriminant(&self) -> u16 {
|
|
||||||
unsafe { *<*const _>::from(self).cast::<u16>() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Kind {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Quiet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A single player's move. In chess parlance, this is a "ply".
|
|
||||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
|
||||||
pub struct Move(u16);
|
|
||||||
|
|
||||||
impl Move {
|
|
||||||
pub fn from_square(&self) -> Square {
|
|
||||||
((self.0 >> 4) & 0b111111).try_into().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_square(&self) -> Square {
|
|
||||||
(self.0 >> 10).try_into().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_quiet(&self) -> bool {
|
|
||||||
self.flags() == Kind::Quiet.discriminant()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_double_push(&self) -> bool {
|
|
||||||
self.flags() == Kind::DoublePush.discriminant()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_castle(&self) -> bool {
|
|
||||||
self.castle().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn castle(&self) -> Option<Castle> {
|
|
||||||
match self.flags() {
|
|
||||||
0b0010 => Some(Castle::KingSide),
|
|
||||||
0b0011 => Some(Castle::QueenSide),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_capture(&self) -> bool {
|
|
||||||
(self.0 & 0b0100) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_en_passant(&self) -> bool {
|
|
||||||
self.flags() == 0b0101
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_promotion(&self) -> bool {
|
|
||||||
(self.0 & 0b1000) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn promotion(&self) -> Option<Shape> {
|
|
||||||
if !self.is_promotion() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(match self.special() {
|
|
||||||
0b00 => Shape::Knight,
|
|
||||||
0b01 => Shape::Bishop,
|
|
||||||
0b10 => Shape::Rook,
|
|
||||||
0b11 => Shape::Queen,
|
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn flags(&self) -> u16 {
|
|
||||||
self.0 & 0b1111
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn special(&self) -> u16 {
|
|
||||||
self.0 & 0b11
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Move {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_tuple("Move")
|
|
||||||
.field(&format_args!("{:08b}", &self.0))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct MoveBuilder {
|
|
||||||
piece: Piece,
|
|
||||||
from: Square,
|
|
||||||
to: Square,
|
|
||||||
kind: Kind,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MoveBuilder {
|
|
||||||
pub fn new(piece: Piece, from: Square, to: Square) -> Self {
|
|
||||||
let kind = match piece.shape() {
|
|
||||||
Shape::Pawn => {
|
|
||||||
let from_rank = from.rank();
|
|
||||||
let to_rank = to.rank();
|
|
||||||
let is_white_double_push = from_rank == Rank::TWO && to_rank == Rank::FOUR;
|
|
||||||
let is_black_double_push = from_rank == Rank::SEVEN && to_rank == Rank::FIVE;
|
|
||||||
if is_white_double_push || is_black_double_push {
|
|
||||||
Kind::DoublePush
|
|
||||||
} else {
|
|
||||||
Kind::Quiet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Kind::Quiet,
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
piece,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn castle(mut self, castle: Castle) -> Self {
|
|
||||||
self.kind = Kind::Castle(castle);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn capturing(mut self, captured_piece: PlacedPiece) -> Self {
|
|
||||||
self.kind = match self.kind {
|
|
||||||
Kind::Promotion(shape) => Kind::CapturePromotion(captured_piece, shape),
|
|
||||||
_ => Kind::Capture(captured_piece),
|
|
||||||
};
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn capturing_en_passant(mut self, captured_piece: PlacedPiece) -> Self {
|
|
||||||
self.kind = Kind::EnPassantCapture(captured_piece);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn promoting_to(mut self, shape: Shape) -> Self {
|
|
||||||
if let Some(shape) = PromotableShape::try_from(shape).ok() {
|
|
||||||
self.kind = match self.kind {
|
|
||||||
Kind::Capture(piece) => Kind::CapturePromotion(piece, shape),
|
|
||||||
Kind::CapturePromotion(piece, _) => Kind::CapturePromotion(piece, shape),
|
|
||||||
_ => Kind::Promotion(shape),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(&self) -> Move {
|
|
||||||
Move(
|
|
||||||
self.kind.bits()
|
|
||||||
| ((self.from as u16 & 0b111111) << 4)
|
|
||||||
| ((self.to as u16 & 0b111111) << 10),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod move_formatter {
|
|
||||||
use super::{Castle, Move};
|
|
||||||
use crate::Position;
|
|
||||||
use chessfriend_core::Shape;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
enum Style {
|
|
||||||
Short,
|
|
||||||
Long,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Style {
|
|
||||||
fn default() -> Self {
|
|
||||||
Style::Long
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct AlgebraicMoveFormatter<'m, 'pos> {
|
|
||||||
position: &'pos Position,
|
|
||||||
r#move: &'m Move,
|
|
||||||
style: Style,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'pos, 'm> AlgebraicMoveFormatter<'m, 'pos> {
|
|
||||||
pub(crate) fn new(
|
|
||||||
mv: &'m Move,
|
|
||||||
position: &'pos Position,
|
|
||||||
) -> AlgebraicMoveFormatter<'m, 'pos> {
|
|
||||||
AlgebraicMoveFormatter {
|
|
||||||
position,
|
|
||||||
r#move: mv,
|
|
||||||
style: Style::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn style(mut self, style: Style) -> Self {
|
|
||||||
self.style = style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_kingside_castle(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "0-0")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_queenside_castle(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "0-0-0")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_short(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_long(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
// TODO: Figure out how to write the short algebraic form, where a
|
|
||||||
// disambiguating coordiate is specified when two of the same piece
|
|
||||||
// cam move to the same square.
|
|
||||||
|
|
||||||
// TODO: Write better pawn moves.
|
|
||||||
|
|
||||||
let mv = self.r#move;
|
|
||||||
let from_square = mv.from_square();
|
|
||||||
let to_square = mv.to_square();
|
|
||||||
|
|
||||||
let piece = self
|
|
||||||
.position
|
|
||||||
.piece_on_square(from_square)
|
|
||||||
.expect(&format!("No piece on {}", from_square));
|
|
||||||
if piece.shape() != Shape::Pawn {
|
|
||||||
write!(f, "{}", piece.shape())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}{}{}",
|
|
||||||
from_square,
|
|
||||||
if mv.is_capture() { 'x' } else { '-' },
|
|
||||||
to_square,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if let Some(promotion) = mv.promotion() {
|
|
||||||
write!(f, "={}", promotion)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Write check (+) and checkmate (#) symbols
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'pos, 'mv> fmt::Display for AlgebraicMoveFormatter<'mv, 'pos> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let mv = self.r#move;
|
|
||||||
match mv.castle() {
|
|
||||||
Some(Castle::KingSide) => return self.fmt_kingside_castle(f),
|
|
||||||
Some(Castle::QueenSide) => return self.fmt_queenside_castle(f),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.style {
|
|
||||||
Style::Short => self.fmt_short(f),
|
|
||||||
Style::Long => self.fmt_long(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::{AlgebraicMoveFormatter, Style};
|
|
||||||
use crate::position;
|
|
||||||
use chessfriend_core::piece;
|
|
||||||
|
|
||||||
macro_rules! chess_move {
|
|
||||||
($color:ident $shape:ident $from_square:ident - $to_square:ident) => {
|
|
||||||
$crate::MoveBuilder::new(
|
|
||||||
chessfriend_core::Piece::new(
|
|
||||||
chessfriend_core::Color::$color,
|
|
||||||
chessfriend_core::Shape::$shape,
|
|
||||||
),
|
|
||||||
chessfriend_core::Square::$from_square,
|
|
||||||
chessfriend_core::Square::$to_square,
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
};
|
|
||||||
($color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident) => {
|
|
||||||
$crate::MoveBuilder::new(
|
|
||||||
chessfriend_core::Piece::new(
|
|
||||||
chessfriend_core::Color::$color,
|
|
||||||
chessfriend_core::Shape::$shape,
|
|
||||||
),
|
|
||||||
chessfriend_core::Square::$from_square,
|
|
||||||
chessfriend_core::Square::$to_square,
|
|
||||||
)
|
|
||||||
.capturing(chessfriend_core::PlacedPiece::new(
|
|
||||||
chessfriend_core::Piece::new(
|
|
||||||
chessfriend_core::Color::$captured_color,
|
|
||||||
chessfriend_core::Shape::$captured_shape,
|
|
||||||
),
|
|
||||||
chessfriend_core::Square::$to_square,
|
|
||||||
))
|
|
||||||
.build()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! test_algebraic_formatter {
|
|
||||||
($test_name:ident, $style:ident, $color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident, $output:expr) => {
|
|
||||||
#[test]
|
|
||||||
fn $test_name() {
|
|
||||||
let pos = position![
|
|
||||||
$color $shape on $from_square,
|
|
||||||
$captured_color $captured_shape on $to_square,
|
|
||||||
];
|
|
||||||
let mv = chess_move!(
|
|
||||||
$color $shape $from_square x $to_square,
|
|
||||||
$captured_color $captured_shape
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("{:?}", &mv);
|
|
||||||
|
|
||||||
let formatter = AlgebraicMoveFormatter::new(&mv, &pos).style(Style::$style);
|
|
||||||
assert_eq!(format!("{}", formatter), $output);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($test_name:ident, $style:ident, $color:ident $shape:ident $from_square:ident - $to_square:ident, $output:expr) => {
|
|
||||||
#[test]
|
|
||||||
fn $test_name() {
|
|
||||||
let pos = position![
|
|
||||||
$color $shape on $from_square,
|
|
||||||
];
|
|
||||||
|
|
||||||
let mv = chess_move!($color $shape $from_square-$to_square);
|
|
||||||
println!("{:?}", &mv);
|
|
||||||
|
|
||||||
let formatter = AlgebraicMoveFormatter::new(&mv, &pos).style(Style::$style);
|
|
||||||
assert_eq!(format!("{}", formatter), $output);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test_algebraic_formatter!(long_pawn_move, Long, White Pawn E4 - E5, "e4-e5");
|
|
||||||
test_algebraic_formatter!(long_bishop_move, Long, White Bishop A4 - D7, "Ba4-d7");
|
|
||||||
test_algebraic_formatter!(long_bishop_capture, Long, White Bishop A2 x E6, Black Knight, "Ba2xe6");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use chessfriend_core::piece;
|
|
||||||
|
|
||||||
macro_rules! assert_flag {
|
|
||||||
($move:expr, $left:expr, $right:expr, $desc:expr) => {
|
|
||||||
assert_eq!($left, $right, "{:?} -> {}", $move, stringify!($desc))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! assert_flags {
|
|
||||||
($move:expr, $quiet:expr, $double_push:expr, $en_passant:expr, $capture:expr, $castle:expr, $promotion:expr) => {
|
|
||||||
assert_flag!($move, $move.is_quiet(), $quiet, "is_quiet");
|
|
||||||
assert_flag!(
|
|
||||||
$move,
|
|
||||||
$move.is_double_push(),
|
|
||||||
$double_push,
|
|
||||||
"is_double_push"
|
|
||||||
);
|
|
||||||
assert_flag!($move, $move.is_en_passant(), $en_passant, "is_en_passant");
|
|
||||||
assert_flag!($move, $move.is_capture(), $capture, "is_capture");
|
|
||||||
assert_flag!($move, $move.is_castle(), $castle, "is_castle");
|
|
||||||
assert_flag!($move, $move.is_promotion(), $promotion, "is_promotion");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn move_flags_quiet() {
|
|
||||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::A4, Square::A5).build();
|
|
||||||
assert_flags!(mv, true, false, false, false, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn move_flags_double_push() {
|
|
||||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::C2, Square::C4).build();
|
|
||||||
assert_flags!(mv, false, true, false, false, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn move_flags_capture() {
|
|
||||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::A4, Square::B5)
|
|
||||||
.capturing(piece!(Black Bishop on B5))
|
|
||||||
.build();
|
|
||||||
assert_flags!(mv, false, false, false, true, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn move_flags_en_passant_capture() {
|
|
||||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::A5, Square::B6)
|
|
||||||
.capturing_en_passant(piece!(Black Pawn on B5))
|
|
||||||
.build();
|
|
||||||
assert_flags!(mv, false, false, true, true, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn move_flags_promotion() {
|
|
||||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::H7, Square::H8)
|
|
||||||
.promoting_to(Shape::Queen)
|
|
||||||
.build();
|
|
||||||
assert_flags!(mv, false, false, false, false, false, true);
|
|
||||||
assert_eq!(mv.promotion(), Some(Shape::Queen));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn move_flags_capture_promotion() {
|
|
||||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::H7, Square::G8)
|
|
||||||
.capturing(piece!(Black Knight on G8))
|
|
||||||
.promoting_to(Shape::Queen)
|
|
||||||
.build();
|
|
||||||
assert_flags!(mv, false, false, false, true, false, true);
|
|
||||||
assert_eq!(mv.promotion(), Some(Shape::Queen));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn move_flags_castle() {
|
|
||||||
let mv = MoveBuilder::new(piece!(Black King), Square::E8, Square::G8)
|
|
||||||
.castle(Castle::KingSide)
|
|
||||||
.build();
|
|
||||||
assert_flags!(mv, false, false, false, false, true, false);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,6 +7,8 @@ mod move_set;
|
||||||
mod pawn;
|
mod pawn;
|
||||||
mod queen;
|
mod queen;
|
||||||
mod rook;
|
mod rook;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub(crate) use move_set::MoveSet;
|
pub(crate) use move_set::MoveSet;
|
||||||
|
@ -17,9 +19,10 @@ use self::{
|
||||||
queen::ClassicalMoveGenerator as QueenMoveGenerator,
|
queen::ClassicalMoveGenerator as QueenMoveGenerator,
|
||||||
rook::ClassicalMoveGenerator as RookMoveGenerator,
|
rook::ClassicalMoveGenerator as RookMoveGenerator,
|
||||||
};
|
};
|
||||||
use crate::{Move, Position};
|
use crate::Position;
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||||
|
use chessfriend_moves::Move;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
trait MoveGenerator {
|
trait MoveGenerator {
|
||||||
|
@ -53,7 +56,7 @@ macro_rules! move_generator_declaration {
|
||||||
push_mask: chessfriend_bitboard::BitBoard,
|
push_mask: chessfriend_bitboard::BitBoard,
|
||||||
) -> $name {
|
) -> $name {
|
||||||
let move_sets = if Self::shape() == chessfriend_core::Shape::King
|
let move_sets = if Self::shape() == chessfriend_core::Shape::King
|
||||||
|| (!capture_mask.is_empty() && !push_mask.is_empty())
|
|| !(capture_mask.is_empty() && push_mask.is_empty())
|
||||||
{
|
{
|
||||||
Self::move_sets(position, color, capture_mask, push_mask)
|
Self::move_sets(position, color, capture_mask, push_mask)
|
||||||
} else {
|
} else {
|
||||||
|
@ -66,7 +69,7 @@ macro_rules! move_generator_declaration {
|
||||||
};
|
};
|
||||||
($name:ident, getters) => {
|
($name:ident, getters) => {
|
||||||
impl $name {
|
impl $name {
|
||||||
pub(super) fn iter(&self) -> impl Iterator<Item = $crate::Move> + '_ {
|
pub(super) fn iter(&self) -> impl Iterator<Item = chessfriend_moves::Move> + '_ {
|
||||||
self.move_sets.values().flat_map(|set| set.moves())
|
self.move_sets.values().flat_map(|set| set.moves())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +80,8 @@ macro_rules! move_generator_declaration {
|
||||||
self.move_sets.get(&piece.square())
|
self.move_sets.get(&piece.square())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bitboard(&self) -> chessfriend_bitboard::BitBoard {
|
#[cfg(test)]
|
||||||
|
fn _test_bitboard(&self) -> chessfriend_bitboard::BitBoard {
|
||||||
self.move_sets.values().fold(
|
self.move_sets.values().fold(
|
||||||
chessfriend_bitboard::BitBoard::empty(),
|
chessfriend_bitboard::BitBoard::empty(),
|
||||||
|partial, mv_set| partial | mv_set.bitboard(),
|
|partial, mv_set| partial | mv_set.bitboard(),
|
||||||
|
|
|
@ -72,7 +72,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
generator._test_bitboard(),
|
||||||
BitBoard::new(
|
BitBoard::new(
|
||||||
0b10000000_01000000_00100000_00010000_00001000_00000100_00000010_00000000
|
0b10000000_01000000_00100000_00010000_00001000_00000100_00000010_00000000
|
||||||
)
|
)
|
||||||
|
@ -93,7 +93,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
generator._test_bitboard(),
|
||||||
BitBoard::new(
|
BitBoard::new(
|
||||||
0b00000000_00000000_00000000_00000000_00001000_00000100_00000010_00000000
|
0b00000000_00000000_00000000_00000000_00001000_00000100_00000010_00000000
|
||||||
)
|
)
|
||||||
|
@ -112,7 +112,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
generator._test_bitboard(),
|
||||||
BitBoard::new(
|
BitBoard::new(
|
||||||
0b00000000_00000000_00000000_00000000_00000000_00000100_00000010_00000000
|
0b00000000_00000000_00000000_00000000_00000000_00000100_00000010_00000000
|
||||||
)
|
)
|
||||||
|
@ -129,7 +129,7 @@ mod tests {
|
||||||
|
|
||||||
let generator =
|
let generator =
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
let bitboard = generator.bitboard();
|
let bitboard = generator._test_bitboard();
|
||||||
let expected = BitBoard::new(
|
let expected = BitBoard::new(
|
||||||
0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010,
|
0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010,
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
//! generating the possible moves for the king in the given position.
|
//! generating the possible moves for the king in the given position.
|
||||||
|
|
||||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||||
use crate::{r#move::Castle, Position};
|
use crate::Position;
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{PlacedPiece, Shape};
|
use chessfriend_core::{PlacedPiece, Shape};
|
||||||
|
use chessfriend_moves::Castle;
|
||||||
|
|
||||||
move_generator_declaration!(KingMoveGenerator, struct);
|
move_generator_declaration!(KingMoveGenerator, struct);
|
||||||
move_generator_declaration!(KingMoveGenerator, new);
|
move_generator_declaration!(KingMoveGenerator, new);
|
||||||
|
@ -57,59 +58,64 @@ impl MoveGeneratorInternal for KingMoveGenerator {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{assert_move_list, position, test_position, Move, MoveBuilder, PositionBuilder};
|
use crate::{assert_move_list, position, test_position, testing::*, PositionBuilder};
|
||||||
use chessfriend_bitboard::bitboard;
|
use chessfriend_bitboard::bitboard;
|
||||||
use chessfriend_core::{piece, Color, Square};
|
use chessfriend_core::{piece, Color, Square};
|
||||||
|
use chessfriend_moves::{Builder as MoveBuilder, Castle, Move};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_king() {
|
fn one_king() -> TestResult {
|
||||||
let pos = position![White King on E4];
|
let pos = position![White King on E4];
|
||||||
|
|
||||||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
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));
|
||||||
let expected_moves: HashSet<Move> = HashSet::from_iter([
|
let expected_moves: HashSet<Move> = HashSet::from_iter([
|
||||||
MoveBuilder::new(piece!(White King), Square::E4, Square::D5).build(),
|
builder.clone().to(Square::D5).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::E4, Square::E5).build(),
|
builder.clone().to(Square::E5).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::E4, Square::F5).build(),
|
builder.clone().to(Square::F5).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::E4, Square::F4).build(),
|
builder.clone().to(Square::F4).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::E4, Square::F3).build(),
|
builder.clone().to(Square::F3).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::E4, Square::E3).build(),
|
builder.clone().to(Square::E3).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::E4, Square::D3).build(),
|
builder.clone().to(Square::D3).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::E4, Square::D4).build(),
|
builder.clone().to(Square::D4).build()?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||||
|
|
||||||
assert_move_list!(generated_moves, expected_moves, pos);
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_king_corner() {
|
fn one_king_corner() -> TestResult {
|
||||||
let pos = position![White King on A1];
|
let pos = position![White King on A1];
|
||||||
|
|
||||||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
let generated_bitboard = generator.bitboard();
|
let generated_bitboard = generator._test_bitboard();
|
||||||
let expected_bitboard = bitboard![A2, B2, B1];
|
let expected_bitboard = bitboard![A2, B2, B1];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
generator._test_bitboard(),
|
||||||
bitboard![A2, B2, B1],
|
bitboard![A2, B2, B1],
|
||||||
"Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}"
|
"Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let builder = MoveBuilder::push(&piece!(White King on A1));
|
||||||
let expected_moves = [
|
let expected_moves = [
|
||||||
MoveBuilder::new(piece!(White King), Square::A1, Square::A2).build(),
|
builder.clone().to(Square::A2).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::A1, Square::B1).build(),
|
builder.clone().to(Square::B1).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::A1, Square::B2).build(),
|
builder.clone().to(Square::B2).build()?,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut generated_moves: HashSet<Move> = generator.iter().collect();
|
let mut generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
|
|
||||||
for ex_move in expected_moves {
|
for ex_move in expected_moves {
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -124,6 +130,8 @@ mod tests {
|
||||||
"Moves unexpectedly present: {:#?}",
|
"Moves unexpectedly present: {:#?}",
|
||||||
generated_moves
|
generated_moves
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -138,7 +146,7 @@ mod tests {
|
||||||
assert!(pos.is_king_in_check());
|
assert!(pos.is_king_in_check());
|
||||||
|
|
||||||
let generator = KingMoveGenerator::new(&pos, Color::Black, BitBoard::FULL, BitBoard::FULL);
|
let generator = KingMoveGenerator::new(&pos, Color::Black, BitBoard::FULL, BitBoard::FULL);
|
||||||
let generated_moves = generator.bitboard();
|
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];
|
||||||
|
|
||||||
|
@ -146,7 +154,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn white_king_unobstructed_castles() {
|
fn white_king_unobstructed_castles() -> TestResult {
|
||||||
let pos = test_position!(
|
let pos = test_position!(
|
||||||
White King on E1,
|
White King on E1,
|
||||||
White Rook on A1,
|
White Rook on A1,
|
||||||
|
@ -159,21 +167,16 @@ mod tests {
|
||||||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||||
|
|
||||||
let king = piece!(White King);
|
assert!(generated_moves
|
||||||
assert!(generated_moves.contains(
|
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
|
||||||
&MoveBuilder::new(king, Square::E1, Square::G1)
|
assert!(generated_moves
|
||||||
.castle(Castle::KingSide)
|
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
|
||||||
.build()
|
|
||||||
));
|
Ok(())
|
||||||
assert!(generated_moves.contains(
|
|
||||||
&MoveBuilder::new(king, Square::E1, Square::C1)
|
|
||||||
.castle(Castle::QueenSide)
|
|
||||||
.build()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn white_king_obstructed_queenside_castle() {
|
fn white_king_obstructed_queenside_castle() -> TestResult {
|
||||||
let pos = test_position!(
|
let pos = test_position!(
|
||||||
White King on E1,
|
White King on E1,
|
||||||
White Knight on B1,
|
White Knight on B1,
|
||||||
|
@ -187,21 +190,16 @@ mod tests {
|
||||||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||||
|
|
||||||
let king = piece!(White King);
|
assert!(generated_moves
|
||||||
assert!(generated_moves.contains(
|
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
|
||||||
&MoveBuilder::new(king, Square::E1, Square::G1)
|
assert!(!generated_moves
|
||||||
.castle(Castle::KingSide)
|
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
|
||||||
.build()
|
|
||||||
));
|
Ok(())
|
||||||
assert!(!generated_moves.contains(
|
|
||||||
&MoveBuilder::new(king, Square::E1, Square::C1)
|
|
||||||
.castle(Castle::QueenSide)
|
|
||||||
.build()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn white_king_obstructed_kingside_castle() {
|
fn white_king_obstructed_kingside_castle() -> TestResult {
|
||||||
let pos = test_position!(
|
let pos = test_position!(
|
||||||
White King on E1,
|
White King on E1,
|
||||||
White Rook on A1,
|
White Rook on A1,
|
||||||
|
@ -215,16 +213,11 @@ mod tests {
|
||||||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||||
|
|
||||||
let king = piece!(White King);
|
assert!(!generated_moves
|
||||||
assert!(!generated_moves.contains(
|
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
|
||||||
&MoveBuilder::new(king, Square::E1, Square::G1)
|
assert!(generated_moves
|
||||||
.castle(Castle::KingSide)
|
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
|
||||||
.build()
|
|
||||||
));
|
Ok(())
|
||||||
assert!(generated_moves.contains(
|
|
||||||
&MoveBuilder::new(king, Square::E1, Square::C1)
|
|
||||||
.castle(Castle::QueenSide)
|
|
||||||
.build()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,54 +34,35 @@ impl MoveGeneratorInternal for KnightMoveGenerator {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{position, Move, MoveBuilder};
|
use crate::{assert_move_list, position, testing::*};
|
||||||
use chessfriend_core::{piece, Color, Square};
|
use chessfriend_core::{piece, Color, Square};
|
||||||
|
use chessfriend_moves::Builder as MoveBuilder;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_knight() {
|
fn one_knight() -> TestResult {
|
||||||
let pos = position![
|
let pos = position![
|
||||||
White Knight on E4,
|
White Knight on E4,
|
||||||
];
|
];
|
||||||
|
|
||||||
let generator =
|
let generator =
|
||||||
KnightMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
KnightMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
|
|
||||||
/*
|
let piece = piece!(White Knight on E4);
|
||||||
let bb = generator.bitboard();
|
let expected_moves = HashSet::from_iter([
|
||||||
assert_eq!(
|
MoveBuilder::push(&piece).to(Square::C3).build()?,
|
||||||
bb,
|
MoveBuilder::push(&piece).to(Square::D2).build()?,
|
||||||
BitBoard::new(
|
MoveBuilder::push(&piece).to(Square::F2).build()?,
|
||||||
0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000
|
MoveBuilder::push(&piece).to(Square::G3).build()?,
|
||||||
)
|
MoveBuilder::push(&piece).to(Square::C5).build()?,
|
||||||
);
|
MoveBuilder::push(&piece).to(Square::D6).build()?,
|
||||||
*/
|
MoveBuilder::push(&piece).to(Square::G5).build()?,
|
||||||
|
MoveBuilder::push(&piece).to(Square::F6).build()?,
|
||||||
|
]);
|
||||||
|
|
||||||
let expected_moves = [
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
MoveBuilder::new(piece!(White Knight), Square::E4, Square::C3).build(),
|
|
||||||
MoveBuilder::new(piece!(White Knight), Square::E4, Square::D2).build(),
|
|
||||||
MoveBuilder::new(piece!(White Knight), Square::E4, Square::F2).build(),
|
|
||||||
MoveBuilder::new(piece!(White Knight), Square::E4, Square::G3).build(),
|
|
||||||
MoveBuilder::new(piece!(White Knight), Square::E4, Square::C5).build(),
|
|
||||||
MoveBuilder::new(piece!(White Knight), Square::E4, Square::D6).build(),
|
|
||||||
MoveBuilder::new(piece!(White Knight), Square::E4, Square::G5).build(),
|
|
||||||
MoveBuilder::new(piece!(White Knight), Square::E4, Square::F6).build(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut generated_moves: HashSet<Move> = generator.iter().collect();
|
Ok(())
|
||||||
|
|
||||||
for ex_move in expected_moves {
|
|
||||||
assert!(
|
|
||||||
generated_moves.remove(&ex_move),
|
|
||||||
"{:#?} was not generated",
|
|
||||||
&ex_move
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
generated_moves.is_empty(),
|
|
||||||
"Moves unexpectedly present: {:#?}",
|
|
||||||
generated_moves
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{r#move::Castle, Move, MoveBuilder};
|
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
use chessfriend_core::{PlacedPiece, Square};
|
||||||
|
use chessfriend_moves::{Builder as MoveBuilder, Castle, EnPassant, Move};
|
||||||
|
|
||||||
/// A set of bitboards defining the moves for a single piece on the board.
|
/// A set of bitboards defining the moves for a single piece on the board.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
@ -11,12 +11,18 @@ struct BitBoardSet {
|
||||||
captures: BitBoard,
|
captures: BitBoard,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum Special {
|
||||||
|
Pawn { en_passant: EnPassant },
|
||||||
|
King { castles: u8 },
|
||||||
|
}
|
||||||
|
|
||||||
/// A set of moves for a single piece on the board.
|
/// A set of moves for a single piece on the board.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub(crate) struct MoveSet {
|
pub(crate) struct MoveSet {
|
||||||
piece: PlacedPiece,
|
piece: PlacedPiece,
|
||||||
bitboards: BitBoardSet,
|
bitboards: BitBoardSet,
|
||||||
special: u8,
|
special: Option<Special>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MoveSet {
|
impl MoveSet {
|
||||||
|
@ -24,21 +30,53 @@ impl MoveSet {
|
||||||
MoveSet {
|
MoveSet {
|
||||||
piece,
|
piece,
|
||||||
bitboards: BitBoardSet::default(),
|
bitboards: BitBoardSet::default(),
|
||||||
special: 0,
|
special: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn can_move_to_square(&self, to: Square) -> bool {
|
pub(crate) fn can_move_to_square(&self, target_square: Square) -> bool {
|
||||||
self.bitboard().is_set(to)
|
match self.special {
|
||||||
|
Some(Special::King { castles }) => {
|
||||||
|
if self.check_castle_field(castles, Castle::KingSide)
|
||||||
|
&& target_square
|
||||||
|
== Castle::KingSide
|
||||||
|
.parameters(self.piece.color())
|
||||||
|
.king_target_square()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.check_castle_field(castles, Castle::KingSide)
|
||||||
|
&& target_square
|
||||||
|
== Castle::QueenSide
|
||||||
|
.parameters(self.piece.color())
|
||||||
|
.king_target_square()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Special::Pawn { en_passant }) => {
|
||||||
|
if target_square == en_passant.target_square() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bitboard().is_set(target_square)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn can_castle(&self, castle: Castle) -> bool {
|
pub(crate) fn can_castle(&self, castle: Castle) -> bool {
|
||||||
match castle {
|
match self.special {
|
||||||
Castle::KingSide => (self.special & 0b1) != 0,
|
Some(Special::King { castles }) => self.check_castle_field(castles, castle),
|
||||||
Castle::QueenSide => (self.special & 0b10) != 0,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_castle_field(&self, castle_field: u8, castle: Castle) -> bool {
|
||||||
|
(castle_field & 1 << castle as u8) != 0
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn quiet_moves(mut self, bitboard: BitBoard) -> MoveSet {
|
pub(super) fn quiet_moves(mut self, bitboard: BitBoard) -> MoveSet {
|
||||||
self.bitboards.quiet = bitboard;
|
self.bitboards.quiet = bitboard;
|
||||||
self
|
self
|
||||||
|
@ -50,72 +88,103 @@ impl MoveSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn kingside_castle(&mut self) -> &mut MoveSet {
|
pub(super) fn kingside_castle(&mut self) -> &mut MoveSet {
|
||||||
self.special |= 0b1;
|
match self.special {
|
||||||
|
Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::KingSide as u8,
|
||||||
|
_ => {
|
||||||
|
self.special = Some(Special::King {
|
||||||
|
castles: 1 << Castle::KingSide as u8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn queenside_castle(&mut self) -> &mut MoveSet {
|
pub(super) fn queenside_castle(&mut self) -> &mut MoveSet {
|
||||||
self.special |= 0b10;
|
match self.special {
|
||||||
|
Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::QueenSide as u8,
|
||||||
|
_ => {
|
||||||
|
self.special = Some(Special::King {
|
||||||
|
castles: 1 << Castle::QueenSide as u8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a BitBoard representing all possible moves.
|
pub(super) fn en_passant(&mut self, en_passant: EnPassant) -> &mut MoveSet {
|
||||||
|
self.special = Some(Special::Pawn { en_passant });
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `BitBoard` representing all possible moves.
|
||||||
pub(super) fn bitboard(&self) -> BitBoard {
|
pub(super) fn bitboard(&self) -> BitBoard {
|
||||||
self.bitboards.captures | self.bitboards.quiet
|
self.bitboards.captures | self.bitboards.quiet
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
|
pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
|
||||||
let piece = self.piece.piece();
|
let piece = &self.piece;
|
||||||
let from_square = self.piece.square();
|
let color = piece.color();
|
||||||
|
|
||||||
|
let is_pawn_on_starting_rank =
|
||||||
|
piece.is_pawn() && piece.square().rank().is_pawn_starting_rank(color);
|
||||||
|
|
||||||
self.bitboards
|
self.bitboards
|
||||||
.quiet
|
.quiet
|
||||||
.occupied_squares()
|
.occupied_squares()
|
||||||
.map(move |to_square| MoveBuilder::new(*piece, from_square, to_square).build())
|
.filter_map(move |to_square| {
|
||||||
|
if is_pawn_on_starting_rank
|
||||||
|
&& to_square.rank().is_pawn_double_push_target_rank(color)
|
||||||
|
{
|
||||||
|
MoveBuilder::double_push(piece.square().file(), color)
|
||||||
|
.build()
|
||||||
|
.ok()
|
||||||
|
} else {
|
||||||
|
MoveBuilder::push(piece).to(to_square).build().ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
.chain(
|
.chain(
|
||||||
self.bitboards
|
self.bitboards
|
||||||
.captures
|
.captures
|
||||||
.occupied_squares()
|
.occupied_squares()
|
||||||
.map(move |to_square| {
|
.filter_map(|to_square| {
|
||||||
MoveBuilder::new(*piece, from_square, to_square)
|
MoveBuilder::push(piece)
|
||||||
.capturing(PlacedPiece::new(
|
.capturing_on(to_square)
|
||||||
Piece::new(Color::White, Shape::Pawn),
|
|
||||||
to_square,
|
|
||||||
))
|
|
||||||
.build()
|
.build()
|
||||||
|
.ok()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.chain(
|
.chain(self.castle_move(Castle::KingSide))
|
||||||
if (self.special & 0b1) != 0 {
|
.chain(self.castle_move(Castle::QueenSide))
|
||||||
Some(())
|
.chain(self.en_passant_move())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn castle_move(&self, castle: Castle) -> Option<Move> {
|
||||||
|
match self.special {
|
||||||
|
Some(Special::King { castles }) => {
|
||||||
|
if (castles & 1 << castle as u8) != 0 {
|
||||||
|
Some(
|
||||||
|
MoveBuilder::castling(self.piece.color(), castle)
|
||||||
|
.build()
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
.map(|()| {
|
}
|
||||||
MoveBuilder::new(
|
_ => None,
|
||||||
*piece,
|
}
|
||||||
from_square,
|
}
|
||||||
Castle::KingSide.target_squares(piece.color()).king,
|
|
||||||
)
|
fn en_passant_move(&self) -> Option<Move> {
|
||||||
.castle(Castle::KingSide)
|
match self.special {
|
||||||
.build()
|
Some(Special::Pawn { en_passant }) => Some(unsafe {
|
||||||
}),
|
MoveBuilder::push(&self.piece)
|
||||||
)
|
.capturing_en_passant_on(en_passant.target_square())
|
||||||
.chain(
|
.build_unchecked()
|
||||||
if (self.special & 0b10) != 0 {
|
}),
|
||||||
Some(())
|
_ => None,
|
||||||
} else {
|
}
|
||||||
None
|
|
||||||
}
|
|
||||||
.map(|()| {
|
|
||||||
MoveBuilder::new(
|
|
||||||
*piece,
|
|
||||||
from_square,
|
|
||||||
Castle::QueenSide.target_squares(piece.color()).king,
|
|
||||||
)
|
|
||||||
.castle(Castle::QueenSide)
|
|
||||||
.build()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,20 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||||
use crate::Position;
|
use crate::Position;
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
|
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
|
||||||
|
use chessfriend_moves::{EnPassant, Move};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MoveIterator(usize, usize);
|
struct MoveIterator(usize, usize);
|
||||||
|
|
||||||
move_generator_declaration!(PawnMoveGenerator);
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) struct PawnMoveGenerator {
|
||||||
|
color: chessfriend_core::Color,
|
||||||
|
move_sets: BTreeMap<Square, MoveSet>,
|
||||||
|
en_passant_captures: Vec<Move>,
|
||||||
|
}
|
||||||
|
|
||||||
|
move_generator_declaration!(PawnMoveGenerator, getters);
|
||||||
|
|
||||||
impl MoveGeneratorInternal for PawnMoveGenerator {
|
impl MoveGeneratorInternal for PawnMoveGenerator {
|
||||||
fn shape() -> Shape {
|
fn shape() -> Shape {
|
||||||
|
@ -24,14 +33,61 @@ impl MoveGeneratorInternal for PawnMoveGenerator {
|
||||||
let capture_moves = Self::attacks(position, &placed_piece) & capture_mask;
|
let capture_moves = Self::attacks(position, &placed_piece) & capture_mask;
|
||||||
let quiet_moves = Self::pushes(position, &placed_piece) & push_mask;
|
let quiet_moves = Self::pushes(position, &placed_piece) & push_mask;
|
||||||
|
|
||||||
MoveSet::new(*placed_piece)
|
let mut move_set = MoveSet::new(*placed_piece)
|
||||||
.quiet_moves(quiet_moves)
|
.quiet_moves(quiet_moves)
|
||||||
.capture_moves(capture_moves)
|
.capture_moves(capture_moves);
|
||||||
|
|
||||||
|
if let Some(en_passant) =
|
||||||
|
Self::en_passant(position, placed_piece, &push_mask, &capture_mask)
|
||||||
|
{
|
||||||
|
move_set.en_passant(en_passant);
|
||||||
|
}
|
||||||
|
|
||||||
|
move_set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PawnMoveGenerator {
|
impl PawnMoveGenerator {
|
||||||
fn pushes(position: &Position, piece: PlacedPiece) -> BitBoard {
|
pub(super) fn new(
|
||||||
|
position: &Position,
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
std::collections::BTreeMap::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
color: player_to_move,
|
||||||
|
move_sets,
|
||||||
|
en_passant_captures: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_sets(
|
||||||
|
position: &Position,
|
||||||
|
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 piece = PlacedPiece::new(piece, square);
|
||||||
|
let move_set =
|
||||||
|
Self::move_set_for_piece(position, &piece, capture_mask, push_mask);
|
||||||
|
(square, move_set)
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
moves_for_pieces
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pushes(position: &Position, piece: &PlacedPiece) -> BitBoard {
|
||||||
let color = piece.color();
|
let color = piece.color();
|
||||||
let square = piece.square();
|
let square = piece.square();
|
||||||
let bitboard: BitBoard = square.into();
|
let bitboard: BitBoard = square.into();
|
||||||
|
@ -59,62 +115,109 @@ impl PawnMoveGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attacks(position: &Position, piece: PlacedPiece) -> BitBoard {
|
fn attacks(position: &Position, piece: &PlacedPiece) -> BitBoard {
|
||||||
let color = piece.color();
|
let color = piece.color();
|
||||||
|
|
||||||
let opponent_pieces = position.bitboard_for_color(color.other());
|
let opponent_pieces = position.bitboard_for_color(color.other());
|
||||||
let en_passant_bitboard = match position.en_passant_square() {
|
|
||||||
Some(square) => <Square as Into<BitBoard>>::into(square),
|
|
||||||
None => BitBoard::empty(),
|
|
||||||
};
|
|
||||||
|
|
||||||
BitBoard::pawn_attacks(piece.square(), color) & (opponent_pieces | en_passant_bitboard)
|
BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces
|
||||||
|
}
|
||||||
|
|
||||||
|
fn en_passant(
|
||||||
|
position: &Position,
|
||||||
|
piece: &PlacedPiece,
|
||||||
|
push_mask: &BitBoard,
|
||||||
|
capture_mask: &BitBoard,
|
||||||
|
) -> Option<EnPassant> {
|
||||||
|
match position.en_passant() {
|
||||||
|
Some(en_passant) => {
|
||||||
|
let target_square: BitBoard = en_passant.target_square().into();
|
||||||
|
let capture_square: BitBoard = en_passant.capture_square().into();
|
||||||
|
|
||||||
|
if (target_square & push_mask).is_empty()
|
||||||
|
&& (capture_square & capture_mask).is_empty()
|
||||||
|
{
|
||||||
|
// Do not allow en passant if capturing would not either
|
||||||
|
// block an active check, or capture a checking pawn.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let capture = BitBoard::pawn_attacks(piece.square(), piece.color()) & target_square;
|
||||||
|
if capture.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match position.piece_on_square(en_passant.capture_square()) {
|
||||||
|
Some(_) => Some(en_passant),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(none)]
|
||||||
|
fn does_en_passant_reveal_check(&self, position: &Position) -> bool {
|
||||||
|
let player_to_move = position.player_to_move();
|
||||||
|
let opposing_player = player_to_move.other();
|
||||||
|
|
||||||
|
if position.king_square(opposing_player).rank()
|
||||||
|
!= Rank::PAWN_DOUBLE_PUSH_TARGET_RANKS[player_to_move as usize]
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{assert_move_list, position::DiagramFormatter, test_position, Move, MoveBuilder};
|
use crate::{
|
||||||
use chessfriend_core::{piece, Color, Piece, Square};
|
assert_move_list, formatted_move_list, position::DiagramFormatter, test_position,
|
||||||
|
testing::*,
|
||||||
|
};
|
||||||
|
use chessfriend_core::{piece, Color, Square};
|
||||||
|
use chessfriend_moves::{Builder as MoveBuilder, Move};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_2square_push() {
|
fn one_double_push() -> TestResult {
|
||||||
let pos = test_position![White Pawn on E2];
|
let pos = test_position![White Pawn on E2];
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
|
let pawn = piece!(White Pawn on E2);
|
||||||
let expected_moves = HashSet::from_iter([
|
let expected_moves = HashSet::from_iter([
|
||||||
MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build(),
|
MoveBuilder::push(&pawn).to(Square::E3).build()?,
|
||||||
MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(),
|
MoveBuilder::double_push(pawn.square().file(), pawn.color()).build()?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
|
|
||||||
assert_eq!(generated_moves, expected_moves);
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_1square_push() {
|
fn one_single_push() -> TestResult {
|
||||||
let pos = test_position![White Pawn on E3];
|
let pos = test_position![White Pawn on E3];
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
|
|
||||||
let expected_moves = HashSet::from_iter([MoveBuilder::new(
|
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E3))
|
||||||
Piece::pawn(Color::White),
|
.to(Square::E4)
|
||||||
Square::E3,
|
.build()?]);
|
||||||
Square::E4,
|
|
||||||
)
|
|
||||||
.build()]);
|
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
|
||||||
|
|
||||||
assert_move_list!(generated_moves, expected_moves, pos);
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_obstructed_2square_push() {
|
fn one_obstructed_2square_push() -> TestResult {
|
||||||
let pos = test_position![
|
let pos = test_position![
|
||||||
White Pawn on E2,
|
White Pawn on E2,
|
||||||
White Knight on E4,
|
White Knight on E4,
|
||||||
|
@ -124,16 +227,15 @@ mod tests {
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
let expected_moves = HashSet::from_iter([MoveBuilder::new(
|
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E2))
|
||||||
Piece::pawn(Color::White),
|
.to(Square::E3)
|
||||||
Square::E2,
|
.build()?]);
|
||||||
Square::E3,
|
|
||||||
)
|
|
||||||
.build()]);
|
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
|
|
||||||
assert_move_list!(generated_moves, expected_moves, pos);
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -145,14 +247,14 @@ mod tests {
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
let expected_moves: HashSet<Move> = HashSet::new();
|
let expected_moves: HashSet<_> = HashSet::new();
|
||||||
|
|
||||||
assert_move_list!(generated_moves, expected_moves, pos);
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_attack() {
|
fn one_attack() -> TestResult {
|
||||||
let pos = test_position![
|
let pos = test_position![
|
||||||
White Pawn on E4,
|
White Pawn on E4,
|
||||||
White Bishop on E5,
|
White Bishop on E5,
|
||||||
|
@ -161,20 +263,19 @@ mod tests {
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
let expected_moves =
|
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E4))
|
||||||
HashSet::from_iter(
|
.capturing_on(Square::D5)
|
||||||
[MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5)
|
.build()?]);
|
||||||
.capturing(piece!(Black Knight on D5))
|
|
||||||
.build()],
|
|
||||||
);
|
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
|
|
||||||
assert_eq!(generated_moves, expected_moves);
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_double_attack() {
|
fn one_double_attack() -> TestResult {
|
||||||
let pos = test_position![
|
let pos = test_position![
|
||||||
White Pawn on E4,
|
White Pawn on E4,
|
||||||
White Bishop on E5,
|
White Bishop on E5,
|
||||||
|
@ -184,21 +285,68 @@ mod tests {
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
|
let builder = MoveBuilder::push(&piece!(White Pawn on E4));
|
||||||
let expected_moves = HashSet::from_iter([
|
let expected_moves = HashSet::from_iter([
|
||||||
MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5)
|
builder.clone().capturing_on(Square::D5).build()?,
|
||||||
.capturing(piece!(Black Knight on D5))
|
builder.clone().capturing_on(Square::F5).build()?,
|
||||||
.build(),
|
|
||||||
MoveBuilder::new(piece!(White Pawn), Square::E4, Square::F5)
|
|
||||||
.capturing(piece!(Black Queen on F5))
|
|
||||||
.build(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generated_moves, expected_moves,
|
generated_moves, expected_moves,
|
||||||
"generated: {:#?}\nexpected: {:#?}",
|
"generated: {:#?}\nexpected: {:#?}",
|
||||||
generated_moves, expected_moves
|
generated_moves, expected_moves
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_en_passant_attack() -> TestResult {
|
||||||
|
let pos = test_position!(Black, [
|
||||||
|
White Pawn on D4,
|
||||||
|
Black Pawn on E4,
|
||||||
|
], D3);
|
||||||
|
|
||||||
|
let generator = PawnMoveGenerator::new(&pos, Color::Black, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||||
|
|
||||||
|
let builder = MoveBuilder::push(&piece!(Black Pawn on E4));
|
||||||
|
let expected_moves = HashSet::from_iter([
|
||||||
|
builder.capturing_en_passant_on(Square::D3).build()?,
|
||||||
|
builder.clone().to(Square::E3).build()?,
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make sure the player cannot capture en passant if doing so would not resolve the check.
|
||||||
|
#[test]
|
||||||
|
fn cannot_capture_en_passant_while_in_check() -> TestResult {
|
||||||
|
let pos = test_position!(Black, [
|
||||||
|
Black King on B5,
|
||||||
|
Black Pawn on E4,
|
||||||
|
White Pawn on D4,
|
||||||
|
White Rook on B1,
|
||||||
|
], D3);
|
||||||
|
|
||||||
|
assert!(pos.is_king_in_check());
|
||||||
|
|
||||||
|
let generated_moves: HashSet<_> = pos.moves().iter().collect();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!generated_moves.contains(
|
||||||
|
&MoveBuilder::push(&piece!(Black Pawn on E4))
|
||||||
|
.capturing_en_passant_on(Square::D3)
|
||||||
|
.build()?
|
||||||
|
),
|
||||||
|
"Valid moves: {:?}",
|
||||||
|
formatted_move_list!(generated_moves, pos)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ mod tests {
|
||||||
|
|
||||||
let generator =
|
let generator =
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
let bitboard = generator.bitboard();
|
let bitboard = generator._test_bitboard();
|
||||||
let expected = bitboard![
|
let expected = bitboard![
|
||||||
A2, C2, D2, E2, F2, G2, H2, // Rank
|
A2, C2, D2, E2, F2, G2, H2, // Rank
|
||||||
B1, B3, B4, B5, B6, B7, B8, // File
|
B1, B3, B4, B5, B6, B7, B8, // File
|
||||||
|
@ -102,7 +102,7 @@ mod tests {
|
||||||
|
|
||||||
let generator =
|
let generator =
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
let bitboard = generator.bitboard();
|
let bitboard = generator._test_bitboard();
|
||||||
let expected = BitBoard::new(
|
let expected = BitBoard::new(
|
||||||
0b10000001_01000001_00100001_00010001_00001001_00000101_00000011_00001110,
|
0b10000001_01000001_00100001_00010001_00001001_00000101_00000011_00001110,
|
||||||
);
|
);
|
||||||
|
@ -127,7 +127,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
generator._test_bitboard(),
|
||||||
bitboard![
|
bitboard![
|
||||||
A2, C2, D2, E2, F2, G2, H2, // Rank
|
A2, C2, D2, E2, F2, G2, H2, // Rank
|
||||||
B1, B3, B4, B5, B6, B7, B8, // File
|
B1, B3, B4, B5, B6, B7, B8, // File
|
||||||
|
@ -146,7 +146,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
generator._test_bitboard(),
|
||||||
bitboard![
|
bitboard![
|
||||||
A3, B3, C3, E3, F3, G3, H3, // Rank
|
A3, B3, C3, E3, F3, G3, H3, // Rank
|
||||||
D1, D2, D4, D5, D6, D7, D8, // File
|
D1, D2, D4, D5, D6, D7, D8, // File
|
||||||
|
|
|
@ -73,7 +73,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
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]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
generator._test_bitboard(),
|
||||||
BitBoard::new(
|
BitBoard::new(
|
||||||
0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00001110
|
0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00001110
|
||||||
)
|
)
|
||||||
|
@ -111,7 +111,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
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]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ mod tests {
|
||||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
generator.bitboard(),
|
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]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,13 @@
|
||||||
//! [1]: https://peterellisjones.com
|
//! [1]: https://peterellisjones.com
|
||||||
//! [2]: https://peterellisjones.com/posts/generating-legal-chess-moves-efficiently/
|
//! [2]: https://peterellisjones.com/posts/generating-legal-chess-moves-efficiently/
|
||||||
|
|
||||||
use crate::{
|
use crate::{assert_move_list, formatted_move_list, test_position, testing::*};
|
||||||
assert_move_list, formatted_move_list, move_generator::Moves, r#move::AlgebraicMoveFormatter,
|
|
||||||
test_position, Move, MoveBuilder,
|
|
||||||
};
|
|
||||||
use chessfriend_core::{piece, Square};
|
use chessfriend_core::{piece, Square};
|
||||||
|
use chessfriend_moves::Builder as MoveBuilder;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pseudo_legal_move_generation() -> Result<(), String> {
|
fn pseudo_legal_move_generation() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on E8,
|
Black King on E8,
|
||||||
White King on E1,
|
White King on E1,
|
||||||
|
@ -24,7 +22,7 @@ fn pseudo_legal_move_generation() -> Result<(), String> {
|
||||||
let generated_moves = pos.moves();
|
let generated_moves = pos.moves();
|
||||||
let king_moves = generated_moves
|
let king_moves = generated_moves
|
||||||
.moves_for_piece(&piece!(Black King on E8))
|
.moves_for_piece(&piece!(Black King on E8))
|
||||||
.ok_or("No valid king moves")?;
|
.ok_or(TestError::NoLegalMoves)?;
|
||||||
|
|
||||||
assert!(!king_moves.can_move_to_square(Square::F8));
|
assert!(!king_moves.can_move_to_square(Square::F8));
|
||||||
assert!(!king_moves.can_move_to_square(Square::F7));
|
assert!(!king_moves.can_move_to_square(Square::F7));
|
||||||
|
@ -33,7 +31,7 @@ fn pseudo_legal_move_generation() -> Result<(), String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> {
|
fn gotcha_king_moves_away_from_a_checking_slider() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on E7,
|
Black King on E7,
|
||||||
White King on E1,
|
White King on E1,
|
||||||
|
@ -43,7 +41,7 @@ fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> {
|
||||||
let generated_moves = pos.moves();
|
let generated_moves = pos.moves();
|
||||||
let king_moves = generated_moves
|
let king_moves = generated_moves
|
||||||
.moves_for_piece(&piece!(Black King on E7))
|
.moves_for_piece(&piece!(Black King on E7))
|
||||||
.ok_or("No valid king moves")?;
|
.ok_or(TestError::NoLegalMoves)?;
|
||||||
|
|
||||||
assert!(!king_moves.can_move_to_square(Square::E8));
|
assert!(!king_moves.can_move_to_square(Square::E8));
|
||||||
|
|
||||||
|
@ -51,7 +49,7 @@ fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_evasions_1() {
|
fn check_evasions_1() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on E8,
|
Black King on E8,
|
||||||
White King on E1,
|
White King on E1,
|
||||||
|
@ -60,11 +58,12 @@ fn check_evasions_1() {
|
||||||
|
|
||||||
let generated_moves = pos.moves();
|
let generated_moves = pos.moves();
|
||||||
|
|
||||||
|
let builder = MoveBuilder::push(&piece!(Black King on E8));
|
||||||
let expected_moves = HashSet::from_iter([
|
let expected_moves = HashSet::from_iter([
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(),
|
builder.clone().to(Square::D8).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::E7).build(),
|
builder.clone().to(Square::E7).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(),
|
builder.clone().to(Square::F7).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(),
|
builder.clone().to(Square::F8).build()?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_move_list!(
|
assert_move_list!(
|
||||||
|
@ -72,10 +71,12 @@ fn check_evasions_1() {
|
||||||
expected_moves,
|
expected_moves,
|
||||||
pos
|
pos
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_evasions_double_check() {
|
fn check_evasions_double_check() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on E8,
|
Black King on E8,
|
||||||
Black Bishop on F6,
|
Black Bishop on F6,
|
||||||
|
@ -86,11 +87,12 @@ fn check_evasions_double_check() {
|
||||||
|
|
||||||
let generated_moves = pos.moves();
|
let generated_moves = pos.moves();
|
||||||
|
|
||||||
|
let builder = MoveBuilder::push(&piece!(Black King on E8));
|
||||||
let expected_moves = HashSet::from_iter([
|
let expected_moves = HashSet::from_iter([
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(),
|
builder.clone().to(Square::D8).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D7).build(),
|
builder.clone().to(Square::D7).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(),
|
builder.clone().to(Square::F7).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(),
|
builder.clone().to(Square::F8).build()?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_move_list!(
|
assert_move_list!(
|
||||||
|
@ -98,10 +100,12 @@ fn check_evasions_double_check() {
|
||||||
expected_moves,
|
expected_moves,
|
||||||
pos
|
pos
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_check_with_blocker() {
|
fn single_check_with_blocker() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on E8,
|
Black King on E8,
|
||||||
Black Knight on G6,
|
Black Knight on G6,
|
||||||
|
@ -111,15 +115,15 @@ fn single_check_with_blocker() {
|
||||||
|
|
||||||
let generated_moves = pos.moves();
|
let generated_moves = pos.moves();
|
||||||
|
|
||||||
|
let king_builder = MoveBuilder::push(&piece!(Black King on E8));
|
||||||
|
let knight_builder = MoveBuilder::push(&piece!(Black Knight on G6));
|
||||||
let expected_moves = HashSet::from_iter([
|
let expected_moves = HashSet::from_iter([
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(),
|
king_builder.clone().to(Square::D8).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D7).build(),
|
king_builder.clone().to(Square::D7).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(),
|
king_builder.clone().to(Square::F7).build()?,
|
||||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(),
|
king_builder.clone().to(Square::F8).build()?,
|
||||||
MoveBuilder::new(piece!(Black Knight), Square::G6, Square::E7).build(),
|
knight_builder.clone().to(Square::E7).build()?,
|
||||||
MoveBuilder::new(piece!(Black Knight), Square::G6, Square::E5)
|
knight_builder.clone().capturing_on(Square::E5).build()?,
|
||||||
.capturing(piece!(White Rook on E5))
|
|
||||||
.build(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_move_list!(
|
assert_move_list!(
|
||||||
|
@ -127,10 +131,12 @@ fn single_check_with_blocker() {
|
||||||
expected_moves,
|
expected_moves,
|
||||||
pos
|
pos
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn en_passant_check_capture() {
|
fn en_passant_check_capture() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on C5,
|
Black King on C5,
|
||||||
Black Pawn on E4,
|
Black Pawn on E4,
|
||||||
|
@ -139,21 +145,23 @@ fn en_passant_check_capture() {
|
||||||
|
|
||||||
assert!(pos.is_king_in_check());
|
assert!(pos.is_king_in_check());
|
||||||
|
|
||||||
let generated_moves = pos.moves().iter().collect::<HashSet<_>>();
|
let generated_moves: HashSet<_> = pos.moves().iter().collect();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
generated_moves.contains(
|
generated_moves.contains(
|
||||||
&MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3)
|
&MoveBuilder::push(&piece!(Black Pawn on E4))
|
||||||
.capturing_en_passant(piece!(White Pawn on D4))
|
.capturing_en_passant_on(Square::D3)
|
||||||
.build()
|
.build()?
|
||||||
),
|
),
|
||||||
"Valid moves: {:?}",
|
"Valid moves: {:?}",
|
||||||
formatted_move_list!(generated_moves, pos)
|
formatted_move_list!(generated_moves, pos)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn en_passant_check_block() {
|
fn en_passant_check_block() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on B5,
|
Black King on B5,
|
||||||
Black Pawn on E4,
|
Black Pawn on E4,
|
||||||
|
@ -163,21 +171,23 @@ fn en_passant_check_block() {
|
||||||
|
|
||||||
assert!(pos.is_king_in_check());
|
assert!(pos.is_king_in_check());
|
||||||
|
|
||||||
let generated_moves = pos.moves().iter().collect::<HashSet<_>>();
|
let generated_moves: HashSet<_> = pos.moves().iter().collect();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
generated_moves.contains(
|
generated_moves.contains(
|
||||||
&MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3)
|
&MoveBuilder::push(&piece!(Black Pawn on E4))
|
||||||
.capturing_en_passant(piece!(White Pawn on D4))
|
.capturing_en_passant_on(Square::D3)
|
||||||
.build()
|
.build()?
|
||||||
),
|
),
|
||||||
"Valid moves: {:?}",
|
"Valid moves: {:?}",
|
||||||
formatted_move_list!(generated_moves, pos)
|
formatted_move_list!(generated_moves, pos)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> {
|
fn pinned_pieces_rook_cannot_move_out_of_pin() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on E8,
|
Black King on E8,
|
||||||
Black Rook on E6,
|
Black Rook on E6,
|
||||||
|
@ -190,21 +200,21 @@ fn pinned_pieces_rook_cannot_move_out_of_pin() -> Result<(), String> {
|
||||||
let generated_moves = pos.moves();
|
let generated_moves = pos.moves();
|
||||||
let rook_moves = generated_moves
|
let rook_moves = generated_moves
|
||||||
.moves_for_piece(&piece!(Black Rook on E6))
|
.moves_for_piece(&piece!(Black Rook on E6))
|
||||||
.ok_or("No valid rook moves")?;
|
.ok_or(TestError::NoLegalMoves)?;
|
||||||
|
|
||||||
assert!(!rook_moves.can_move_to_square(Square::D6));
|
assert!(!rook_moves.can_move_to_square(Square::D6));
|
||||||
assert!(!rook_moves.can_move_to_square(Square::F6));
|
assert!(!rook_moves.can_move_to_square(Square::F6));
|
||||||
|
|
||||||
assert!(rook_moves.can_move_to_square(Square::E7));
|
|
||||||
assert!(rook_moves.can_move_to_square(Square::E5));
|
|
||||||
assert!(rook_moves.can_move_to_square(Square::E4));
|
|
||||||
assert!(rook_moves.can_move_to_square(Square::E3));
|
assert!(rook_moves.can_move_to_square(Square::E3));
|
||||||
|
assert!(rook_moves.can_move_to_square(Square::E4));
|
||||||
|
assert!(rook_moves.can_move_to_square(Square::E5));
|
||||||
|
assert!(rook_moves.can_move_to_square(Square::E7));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn en_passant_discovered_check() {
|
fn en_passant_discovered_check() -> TestResult {
|
||||||
let pos = test_position!(Black, [
|
let pos = test_position!(Black, [
|
||||||
Black King on A4,
|
Black King on A4,
|
||||||
Black Pawn on E4,
|
Black Pawn on E4,
|
||||||
|
@ -212,15 +222,17 @@ fn en_passant_discovered_check() {
|
||||||
White Queen on H4,
|
White Queen on H4,
|
||||||
], D3);
|
], D3);
|
||||||
|
|
||||||
let generated_moves = pos.moves().iter().collect::<HashSet<_>>();
|
let generated_moves: HashSet<_> = pos.moves().iter().collect();
|
||||||
|
|
||||||
let unexpected_move = MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3)
|
let unexpected_move = MoveBuilder::push(&piece!(Black Pawn on E4))
|
||||||
.capturing_en_passant(piece!(White Pawn on D4))
|
.capturing_en_passant_on(Square::D3)
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
!generated_moves.contains(&unexpected_move),
|
!generated_moves.contains(&unexpected_move),
|
||||||
"Valid moves: {:?}",
|
"Valid moves: {:?}",
|
||||||
formatted_move_list!(generated_moves, pos)
|
formatted_move_list!(generated_moves, pos)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,32 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{position, r#move::AlgebraicMoveFormatter, Move, MoveBuilder};
|
use crate::{assert_move_list, test_position, testing::*};
|
||||||
use chessfriend_core::{piece, Square};
|
use chessfriend_core::{piece, Square};
|
||||||
|
use chessfriend_moves::Builder as MoveBuilder;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_king() {
|
fn one_king() -> TestResult {
|
||||||
let pos = position![
|
let pos = test_position![
|
||||||
White King on D3,
|
White King on D3,
|
||||||
Black King on H6,
|
Black King on H6,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let builder = MoveBuilder::push(&piece!(White King on D3));
|
||||||
let expected_moves = HashSet::from_iter([
|
let expected_moves = HashSet::from_iter([
|
||||||
MoveBuilder::new(piece!(White King), Square::D3, Square::D4).build(),
|
builder.clone().to(Square::D4).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::D3, Square::E4).build(),
|
builder.clone().to(Square::E4).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::D3, Square::E3).build(),
|
builder.clone().to(Square::E3).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::D3, Square::E2).build(),
|
builder.clone().to(Square::E2).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::D3, Square::D2).build(),
|
builder.clone().to(Square::D2).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::D3, Square::C2).build(),
|
builder.clone().to(Square::C2).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::D3, Square::C3).build(),
|
builder.clone().to(Square::C3).build()?,
|
||||||
MoveBuilder::new(piece!(White King), Square::D3, Square::C4).build(),
|
builder.clone().to(Square::C4).build()?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let generated_moves: HashSet<Move> = pos.moves().iter().collect();
|
let generated_moves: HashSet<_> = pos.moves().iter().collect();
|
||||||
|
|
||||||
assert_eq!(
|
assert_move_list!(generated_moves, expected_moves, pos);
|
||||||
generated_moves,
|
|
||||||
expected_moves,
|
Ok(())
|
||||||
"{:?}",
|
|
||||||
generated_moves
|
|
||||||
.symmetric_difference(&expected_moves)
|
|
||||||
.map(|m| format!("{}", AlgebraicMoveFormatter::new(&m, &pos)))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
mod move_builder;
|
mod move_builder;
|
||||||
mod position_builder;
|
mod position_builder;
|
||||||
|
|
||||||
pub use move_builder::Builder as MoveBuilder;
|
pub use move_builder::{Builder as MoveBuilder, MakeMoveError};
|
||||||
pub use position_builder::Builder as PositionBuilder;
|
pub use position_builder::Builder as PositionBuilder;
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{
|
use crate::{position::flags::Flags, Position};
|
||||||
position::flags::Flags,
|
|
||||||
r#move::{AlgebraicMoveFormatter, Castle},
|
|
||||||
MakeMoveError, Move, Position,
|
|
||||||
};
|
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square};
|
use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square};
|
||||||
|
use chessfriend_moves::{Castle, EnPassant, Move};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum MakeMoveError {
|
||||||
|
PlayerOutOfTurn,
|
||||||
|
NoPiece,
|
||||||
|
NoCapturedPiece,
|
||||||
|
NoLegalMoves,
|
||||||
|
IllegalCastle,
|
||||||
|
IllegalSquare(Square),
|
||||||
|
}
|
||||||
|
|
||||||
/// A position builder that builds a new position by making a move.
|
/// A position builder that builds a new position by making a move.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -27,7 +34,7 @@ pub enum ValidatedMove {
|
||||||
captured_piece: Option<PlacedPiece>,
|
captured_piece: Option<PlacedPiece>,
|
||||||
promotion: Option<Shape>,
|
promotion: Option<Shape>,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
en_passant_square: Option<Square>,
|
en_passant: Option<EnPassant>,
|
||||||
increment_ply: bool,
|
increment_ply: bool,
|
||||||
},
|
},
|
||||||
Castle {
|
Castle {
|
||||||
|
@ -55,14 +62,14 @@ where
|
||||||
M: MoveToMake,
|
M: MoveToMake,
|
||||||
{
|
{
|
||||||
pub fn make(self, mv: &Move) -> Result<Builder<'p, ValidatedMove>, MakeMoveError> {
|
pub fn make(self, mv: &Move) -> Result<Builder<'p, ValidatedMove>, MakeMoveError> {
|
||||||
let from_square = mv.from_square();
|
let origin_square = mv.origin_square();
|
||||||
|
|
||||||
let piece = self
|
let piece = self
|
||||||
.position
|
.position
|
||||||
.piece_on_square(from_square)
|
.piece_on_square(origin_square)
|
||||||
.ok_or(MakeMoveError::NoPiece)?;
|
.ok_or(MakeMoveError::NoPiece)?;
|
||||||
|
|
||||||
let to_square = mv.to_square();
|
let target_square = mv.target_square();
|
||||||
|
|
||||||
let moves = self
|
let moves = self
|
||||||
.position
|
.position
|
||||||
|
@ -76,8 +83,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if !moves.can_move_to_square(to_square) {
|
if !moves.can_move_to_square(target_square) {
|
||||||
return Err(MakeMoveError::IllegalSquare(to_square));
|
return Err(MakeMoveError::IllegalSquare(target_square));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,8 +94,8 @@ where
|
||||||
let captured_piece = if mv.is_en_passant() {
|
let captured_piece = if mv.is_en_passant() {
|
||||||
// En passant captures the pawn directly ahead (in the player's direction) of the en passant square.
|
// En passant captures the pawn directly ahead (in the player's direction) of the en passant square.
|
||||||
let capture_square = match player {
|
let capture_square = match player {
|
||||||
Color::White => to_square.neighbor(Direction::South),
|
Color::White => target_square.neighbor(Direction::South),
|
||||||
Color::Black => to_square.neighbor(Direction::North),
|
Color::Black => target_square.neighbor(Direction::North),
|
||||||
}
|
}
|
||||||
.ok_or(MakeMoveError::NoCapturedPiece)?;
|
.ok_or(MakeMoveError::NoCapturedPiece)?;
|
||||||
|
|
||||||
|
@ -100,7 +107,7 @@ where
|
||||||
} else if mv.is_capture() {
|
} else if mv.is_capture() {
|
||||||
Some(
|
Some(
|
||||||
self.position
|
self.position
|
||||||
.piece_on_square(to_square)
|
.piece_on_square(target_square)
|
||||||
.ok_or(MakeMoveError::NoCapturedPiece)?,
|
.ok_or(MakeMoveError::NoCapturedPiece)?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -142,11 +149,12 @@ where
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let en_passant_square: Option<Square> = if mv.is_double_push() {
|
let en_passant = if mv.is_double_push() {
|
||||||
match piece.color() {
|
match piece.color() {
|
||||||
Color::White => to_square.neighbor(Direction::South),
|
Color::White => target_square.neighbor(Direction::South),
|
||||||
Color::Black => to_square.neighbor(Direction::North),
|
Color::Black => target_square.neighbor(Direction::North),
|
||||||
}
|
}
|
||||||
|
.and_then(EnPassant::from_target_square)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -154,13 +162,13 @@ where
|
||||||
Ok(Builder {
|
Ok(Builder {
|
||||||
position: self.position,
|
position: self.position,
|
||||||
move_to_make: ValidatedMove::RegularMove {
|
move_to_make: ValidatedMove::RegularMove {
|
||||||
from_square,
|
from_square: origin_square,
|
||||||
to_square,
|
to_square: target_square,
|
||||||
moving_piece: piece,
|
moving_piece: piece,
|
||||||
captured_piece,
|
captured_piece,
|
||||||
promotion: mv.promotion(),
|
promotion: mv.promotion(),
|
||||||
flags,
|
flags,
|
||||||
en_passant_square,
|
en_passant,
|
||||||
increment_ply: !(mv.is_capture() || piece.is_pawn()),
|
increment_ply: !(mv.is_capture() || piece.is_pawn()),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -183,7 +191,7 @@ impl<'p> Builder<'p, ValidatedMove> {
|
||||||
captured_piece,
|
captured_piece,
|
||||||
promotion,
|
promotion,
|
||||||
flags,
|
flags,
|
||||||
en_passant_square,
|
en_passant,
|
||||||
increment_ply,
|
increment_ply,
|
||||||
} => {
|
} => {
|
||||||
let mut pieces = self.position.piece_bitboards().clone();
|
let mut pieces = self.position.piece_bitboards().clone();
|
||||||
|
@ -210,7 +218,7 @@ impl<'p> Builder<'p, ValidatedMove> {
|
||||||
self.position.player_to_move().other(),
|
self.position.player_to_move().other(),
|
||||||
flags,
|
flags,
|
||||||
pieces,
|
pieces,
|
||||||
en_passant_square,
|
en_passant,
|
||||||
ply,
|
ply,
|
||||||
updated_move_number,
|
updated_move_number,
|
||||||
)
|
)
|
||||||
|
@ -223,18 +231,19 @@ impl<'p> Builder<'p, ValidatedMove> {
|
||||||
} => {
|
} => {
|
||||||
let mut pieces = self.position.piece_bitboards().clone();
|
let mut pieces = self.position.piece_bitboards().clone();
|
||||||
|
|
||||||
let target_squares = castle.target_squares(player);
|
let parameters = castle.parameters(player);
|
||||||
|
|
||||||
let king_from: BitBoard = king.square().into();
|
let king_origin_square: BitBoard = king.square().into();
|
||||||
let king_to: BitBoard = target_squares.king.into();
|
let king_target_square: BitBoard = parameters.king_target_square().into();
|
||||||
*pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to;
|
*pieces.bitboard_for_piece_mut(king.piece()) ^=
|
||||||
|
king_origin_square | king_target_square;
|
||||||
|
|
||||||
let rook_from: BitBoard = rook.square().into();
|
let rook_from: BitBoard = rook.square().into();
|
||||||
let rook_to: BitBoard = target_squares.rook.into();
|
let rook_to: BitBoard = parameters.rook_target_square().into();
|
||||||
*pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to;
|
*pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to;
|
||||||
|
|
||||||
*pieces.bitboard_for_color_mut(player) &=
|
*pieces.bitboard_for_color_mut(player) &=
|
||||||
!(king_from | rook_from) | (king_to | rook_to);
|
!(king_origin_square | rook_from) | (king_target_square | rook_to);
|
||||||
|
|
||||||
Position::new(
|
Position::new(
|
||||||
player.other(),
|
player.other(),
|
||||||
|
@ -261,15 +270,24 @@ impl<'p> From<&'p Position> for Builder<'p, NoMove> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{position, r#move::Castle, MoveBuilder, PositionBuilder};
|
use crate::testing::*;
|
||||||
use chessfriend_core::piece;
|
use crate::{position, test_position};
|
||||||
|
use chessfriend_core::{piece, File};
|
||||||
|
use chessfriend_moves::Builder as MoveBuilder;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_white_pawn_one_square() -> Result<(), MakeMoveError> {
|
fn move_white_pawn_one_square() -> TestResult {
|
||||||
let pos = position![White Pawn on E2];
|
let pos = position![White Pawn on E2];
|
||||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build();
|
let mv = MoveBuilder::new()
|
||||||
|
.from(Square::E2)
|
||||||
|
.to(Square::E3)
|
||||||
|
.build()
|
||||||
|
.map_err(TestError::BuildMove)?;
|
||||||
|
|
||||||
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
|
let new_position = Builder::<NoMove>::new(&pos)
|
||||||
|
.make(&mv)
|
||||||
|
.map_err(|err| TestError::MakeMove(err))?
|
||||||
|
.build();
|
||||||
println!("{}", &new_position);
|
println!("{}", &new_position);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -281,25 +299,32 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_white_pawn_two_squares() -> Result<(), MakeMoveError> {
|
fn move_white_pawn_two_squares() -> TestResult {
|
||||||
let pos = position![White Pawn on E2];
|
let pos = test_position![White Pawn on E2];
|
||||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build();
|
|
||||||
|
|
||||||
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
|
let mv = MoveBuilder::double_push(File::E, Color::White).build()?;
|
||||||
|
|
||||||
|
let new_position = Builder::new(&pos).make(&mv)?.build();
|
||||||
println!("{}", &new_position);
|
println!("{}", &new_position);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
new_position.piece_on_square(Square::E4),
|
new_position.piece_on_square(Square::E4),
|
||||||
Some(piece!(White Pawn on E4))
|
Some(piece!(White Pawn on E4))
|
||||||
);
|
);
|
||||||
assert_eq!(new_position.en_passant_square(), Some(Square::E3));
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn white_kingside_castle() -> Result<(), MakeMoveError> {
|
fn white_kingside_castle() -> TestResult {
|
||||||
let pos = position![
|
let pos = test_position![
|
||||||
White King on E1,
|
White King on E1,
|
||||||
White Rook on H1,
|
White Rook on H1,
|
||||||
White Pawn on E2,
|
White Pawn on E2,
|
||||||
|
@ -307,13 +332,10 @@ mod tests {
|
||||||
White Pawn on G2,
|
White Pawn on G2,
|
||||||
White Pawn on H2
|
White Pawn on H2
|
||||||
];
|
];
|
||||||
println!("{}", &pos);
|
|
||||||
|
|
||||||
let mv = MoveBuilder::new(piece!(White King), Square::E1, Square::G1)
|
let mv = MoveBuilder::castling(Color::White, Castle::KingSide).build()?;
|
||||||
.castle(Castle::KingSide)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
|
let new_position = Builder::new(&pos).make(&mv)?.build();
|
||||||
println!("{}", &new_position);
|
println!("{}", &new_position);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -329,15 +351,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn en_passant_capture() -> Result<(), MakeMoveError> {
|
fn en_passant_capture() -> TestResult {
|
||||||
let pos = PositionBuilder::new()
|
let pos = test_position!(Black, [
|
||||||
.place_piece(piece!(White Pawn on B5))
|
White Pawn on B5,
|
||||||
.place_piece(piece!(Black Pawn on A7))
|
Black Pawn on A7,
|
||||||
.to_move(Color::Black)
|
]);
|
||||||
.build();
|
|
||||||
println!("{pos}");
|
let black_pawn_move = MoveBuilder::double_push(File::A, Color::Black).build()?;
|
||||||
|
|
||||||
let black_pawn_move = MoveBuilder::new(piece!(Black Pawn), Square::A7, Square::A5).build();
|
|
||||||
assert!(black_pawn_move.is_double_push());
|
assert!(black_pawn_move.is_double_push());
|
||||||
assert!(!black_pawn_move.is_en_passant());
|
assert!(!black_pawn_move.is_en_passant());
|
||||||
|
|
||||||
|
@ -353,10 +374,10 @@ mod tests {
|
||||||
Some(piece!(White Pawn on B5))
|
Some(piece!(White Pawn on B5))
|
||||||
);
|
);
|
||||||
|
|
||||||
let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6)
|
let white_pawn_capture = MoveBuilder::push(&piece!(White Pawn on B5))
|
||||||
.capturing_en_passant(piece!(Black Pawn on A5))
|
.capturing_en_passant_on(Square::A6)
|
||||||
.build();
|
.build()?;
|
||||||
let en_passant_capture = Builder::<NoMove>::new(&en_passant_position)
|
let en_passant_capture = Builder::new(&en_passant_position)
|
||||||
.make(&white_pawn_capture)?
|
.make(&white_pawn_capture)?
|
||||||
.build();
|
.build();
|
||||||
println!("{en_passant_capture}");
|
println!("{en_passant_capture}");
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
position::{flags::Flags, piece_sets::PieceBitBoards},
|
position::{flags::Flags, piece_sets::PieceBitBoards},
|
||||||
r#move::Castle,
|
|
||||||
Position,
|
Position,
|
||||||
};
|
};
|
||||||
use chessfriend_core::{piece, Color, Piece, PlacedPiece, Rank, Shape, Square};
|
use chessfriend_core::{piece, Color, Piece, PlacedPiece, Rank, Shape, Square};
|
||||||
|
use chessfriend_moves::{Castle, EnPassant};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -14,7 +14,7 @@ pub struct Builder {
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
pieces: BTreeMap<Square, Piece>,
|
pieces: BTreeMap<Square, Piece>,
|
||||||
kings: [Option<Square>; 2],
|
kings: [Option<Square>; 2],
|
||||||
en_passant_square: Option<Square>,
|
en_passant: Option<EnPassant>,
|
||||||
ply_counter: u16,
|
ply_counter: u16,
|
||||||
move_number: u16,
|
move_number: u16,
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ impl Builder {
|
||||||
flags: Flags::default(),
|
flags: Flags::default(),
|
||||||
pieces: BTreeMap::default(),
|
pieces: BTreeMap::default(),
|
||||||
kings: [None, None],
|
kings: [None, None],
|
||||||
en_passant_square: None,
|
en_passant: None,
|
||||||
ply_counter: 0,
|
ply_counter: 0,
|
||||||
move_number: 1,
|
move_number: 1,
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ impl Builder {
|
||||||
flags: position.flags(),
|
flags: position.flags(),
|
||||||
pieces,
|
pieces,
|
||||||
kings: [Some(white_king), Some(black_king)],
|
kings: [Some(white_king), Some(black_king)],
|
||||||
en_passant_square: position.en_passant_square(),
|
en_passant: position.en_passant(),
|
||||||
ply_counter: position.ply_counter(),
|
ply_counter: position.ply_counter(),
|
||||||
move_number: position.move_number(),
|
move_number: position.move_number(),
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,8 @@ impl Builder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn en_passant_square(&mut self, square: Option<Square>) -> &mut Self {
|
pub fn en_passant(&mut self, en_passant: Option<EnPassant>) -> &mut Self {
|
||||||
self.en_passant_square = square;
|
self.en_passant = en_passant;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,13 +120,13 @@ impl Builder {
|
||||||
|
|
||||||
for color in Color::ALL {
|
for color in Color::ALL {
|
||||||
for castle in Castle::ALL {
|
for castle in Castle::ALL {
|
||||||
let starting_squares = castle.starting_squares(color);
|
let parameters = castle.parameters(color);
|
||||||
let has_rook_on_starting_square = self
|
let has_rook_on_starting_square = self
|
||||||
.pieces
|
.pieces
|
||||||
.get(&starting_squares.rook)
|
.get(¶meters.rook_origin_square())
|
||||||
.is_some_and(|piece| piece.shape() == Shape::Rook);
|
.is_some_and(|piece| piece.shape() == Shape::Rook);
|
||||||
let king_is_on_starting_square =
|
let king_is_on_starting_square =
|
||||||
self.kings[color as usize] == Some(starting_squares.king);
|
self.kings[color as usize] == Some(parameters.king_origin_square());
|
||||||
|
|
||||||
if !king_is_on_starting_square || !has_rook_on_starting_square {
|
if !king_is_on_starting_square || !has_rook_on_starting_square {
|
||||||
flags.clear_player_has_right_to_castle_flag(color, castle);
|
flags.clear_player_has_right_to_castle_flag(color, castle);
|
||||||
|
@ -138,7 +138,7 @@ impl Builder {
|
||||||
self.player_to_move,
|
self.player_to_move,
|
||||||
flags,
|
flags,
|
||||||
pieces,
|
pieces,
|
||||||
self.en_passant_square,
|
self.en_passant,
|
||||||
self.ply_counter,
|
self.ply_counter,
|
||||||
self.move_number,
|
self.move_number,
|
||||||
)
|
)
|
||||||
|
@ -173,7 +173,7 @@ impl Default for Builder {
|
||||||
flags: Flags::default(),
|
flags: Flags::default(),
|
||||||
pieces: pieces,
|
pieces: pieces,
|
||||||
kings: [Some(white_king_square), Some(black_king_square)],
|
kings: [Some(white_king_square), Some(black_king_square)],
|
||||||
en_passant_square: None,
|
en_passant: None,
|
||||||
ply_counter: 0,
|
ply_counter: 0,
|
||||||
move_number: 1,
|
move_number: 1,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::r#move::Castle;
|
|
||||||
use chessfriend_core::Color;
|
use chessfriend_core::Color;
|
||||||
|
use chessfriend_moves::Castle;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||||
|
@ -10,7 +10,7 @@ pub struct Flags(u8);
|
||||||
impl Flags {
|
impl Flags {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn player_has_right_to_castle_flag_offset(color: Color, castle: Castle) -> usize {
|
pub(super) fn player_has_right_to_castle_flag_offset(color: Color, castle: Castle) -> usize {
|
||||||
((color as usize) << 1) + castle.into_index()
|
((color as usize) << 1) + castle as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool {
|
pub(super) fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool {
|
||||||
|
@ -45,8 +45,6 @@ impl Default for Flags {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::r#move::Castle;
|
|
||||||
use chessfriend_core::Color;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn castle_flags() {
|
fn castle_flags() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ mod pieces;
|
||||||
mod position;
|
mod position;
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
builders::{MoveBuilder, PositionBuilder},
|
builders::{MakeMoveError, MoveBuilder, PositionBuilder},
|
||||||
diagram_formatter::DiagramFormatter,
|
diagram_formatter::DiagramFormatter,
|
||||||
pieces::Pieces,
|
pieces::Pieces,
|
||||||
position::Position,
|
position::Position,
|
||||||
|
|
|
@ -5,11 +5,11 @@ use crate::{
|
||||||
check::CheckingPieces,
|
check::CheckingPieces,
|
||||||
move_generator::{MoveSet, Moves},
|
move_generator::{MoveSet, Moves},
|
||||||
position::DiagramFormatter,
|
position::DiagramFormatter,
|
||||||
r#move::Castle,
|
|
||||||
sight::SightExt,
|
sight::SightExt,
|
||||||
};
|
};
|
||||||
use chessfriend_bitboard::BitBoard;
|
use chessfriend_bitboard::BitBoard;
|
||||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||||
|
use chessfriend_moves::{Castle, EnPassant};
|
||||||
use std::{cell::OnceCell, fmt};
|
use std::{cell::OnceCell, fmt};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq)]
|
#[derive(Clone, Debug, Eq)]
|
||||||
|
@ -17,7 +17,7 @@ pub struct Position {
|
||||||
color_to_move: Color,
|
color_to_move: Color,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
pieces: PieceBitBoards,
|
pieces: PieceBitBoards,
|
||||||
en_passant_square: Option<Square>,
|
en_passant: Option<EnPassant>,
|
||||||
moves: OnceCell<Moves>,
|
moves: OnceCell<Moves>,
|
||||||
half_move_counter: u16,
|
half_move_counter: u16,
|
||||||
full_move_number: u16,
|
full_move_number: u16,
|
||||||
|
@ -92,7 +92,7 @@ impl Position {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let castling_parameters = castle.parameters();
|
let castling_parameters = castle.parameters(player);
|
||||||
|
|
||||||
let all_pieces = self.occupied_squares();
|
let all_pieces = self.occupied_squares();
|
||||||
if !(all_pieces & castling_parameters.clear_squares()).is_empty() {
|
if !(all_pieces & castling_parameters.clear_squares()).is_empty() {
|
||||||
|
@ -182,12 +182,16 @@ impl Position {
|
||||||
Pieces::new(&self, color)
|
Pieces::new(&self, color)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn en_passant_square(&self) -> Option<Square> {
|
pub fn has_en_passant_square(&self) -> bool {
|
||||||
self.en_passant_square
|
self.en_passant.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn en_passant(&self) -> Option<EnPassant> {
|
||||||
|
self.en_passant
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard {
|
fn _sight_of_player(&self, player: Color, pieces: &PieceBitBoards) -> BitBoard {
|
||||||
let en_passant_square = self.en_passant_square;
|
let en_passant_target_square = self.en_passant.map(|ep| ep.target_square());
|
||||||
|
|
||||||
Shape::ALL
|
Shape::ALL
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -202,7 +206,7 @@ impl Position {
|
||||||
})
|
})
|
||||||
.flat_map(|(piece, &bitboard)| {
|
.flat_map(|(piece, &bitboard)| {
|
||||||
bitboard.occupied_squares().map(move |square| {
|
bitboard.occupied_squares().map(move |square| {
|
||||||
PlacedPiece::new(piece, square).sight(pieces, en_passant_square)
|
PlacedPiece::new(piece, square).sight(pieces, en_passant_target_square)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.fold(BitBoard::empty(), |acc, sight| acc | sight)
|
.fold(BitBoard::empty(), |acc, sight| acc | sight)
|
||||||
|
@ -214,7 +218,7 @@ impl Position {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard {
|
pub(crate) fn sight_of_piece(&self, piece: &PlacedPiece) -> BitBoard {
|
||||||
piece.sight(&self.pieces, self.en_passant_square)
|
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
|
/// A bitboard representing the squares where a king of the given color will
|
||||||
|
@ -295,14 +299,14 @@ impl Position {
|
||||||
player_to_move: Color,
|
player_to_move: Color,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
pieces: PieceBitBoards,
|
pieces: PieceBitBoards,
|
||||||
en_passant_square: Option<Square>,
|
en_passant: Option<EnPassant>,
|
||||||
half_move_counter: u16,
|
half_move_counter: u16,
|
||||||
full_move_number: u16,
|
full_move_number: u16,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
color_to_move: player_to_move,
|
color_to_move: player_to_move,
|
||||||
flags,
|
flags,
|
||||||
en_passant_square,
|
en_passant,
|
||||||
pieces,
|
pieces,
|
||||||
half_move_counter,
|
half_move_counter,
|
||||||
full_move_number,
|
full_move_number,
|
||||||
|
@ -332,8 +336,8 @@ impl Position {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl Position {
|
impl Position {
|
||||||
pub(crate) fn test_set_en_passant_square(&mut self, square: Square) {
|
pub(crate) fn test_set_en_passant(&mut self, en_passant: EnPassant) {
|
||||||
self.en_passant_square = Some(square);
|
self.en_passant = Some(en_passant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +347,7 @@ impl Default for Position {
|
||||||
color_to_move: Color::White,
|
color_to_move: Color::White,
|
||||||
flags: Flags::default(),
|
flags: Flags::default(),
|
||||||
pieces: PieceBitBoards::default(),
|
pieces: PieceBitBoards::default(),
|
||||||
en_passant_square: None,
|
en_passant: None,
|
||||||
moves: OnceCell::new(),
|
moves: OnceCell::new(),
|
||||||
half_move_counter: 0,
|
half_move_counter: 0,
|
||||||
full_move_number: 1,
|
full_move_number: 1,
|
||||||
|
@ -356,7 +360,7 @@ impl PartialEq for Position {
|
||||||
self.pieces == other.pieces
|
self.pieces == other.pieces
|
||||||
&& self.color_to_move == other.color_to_move
|
&& self.color_to_move == other.color_to_move
|
||||||
&& self.flags == other.flags
|
&& self.flags == other.flags
|
||||||
&& self.en_passant_square == other.en_passant_square
|
&& self.en_passant == other.en_passant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,9 +372,10 @@ impl fmt::Display for Position {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{position, test_position, Castle, Position, PositionBuilder};
|
use super::*;
|
||||||
|
use crate::{position, test_position, Position, PositionBuilder};
|
||||||
use chessfriend_bitboard::bitboard;
|
use chessfriend_bitboard::bitboard;
|
||||||
use chessfriend_core::{piece, Color, Square};
|
use chessfriend_core::piece;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn piece_on_square() {
|
fn piece_on_square() {
|
||||||
|
|
|
@ -242,6 +242,7 @@ mod tests {
|
||||||
use crate::test_position;
|
use crate::test_position;
|
||||||
use chessfriend_bitboard::{bitboard, BitBoard};
|
use chessfriend_bitboard::{bitboard, BitBoard};
|
||||||
use chessfriend_core::{piece, Square};
|
use chessfriend_core::{piece, Square};
|
||||||
|
use chessfriend_moves::EnPassant;
|
||||||
|
|
||||||
sight_test!(e4_pawn, piece!(White Pawn on E4), bitboard!(D5, F5));
|
sight_test!(e4_pawn, piece!(White Pawn on E4), bitboard!(D5, F5));
|
||||||
|
|
||||||
|
@ -289,7 +290,7 @@ mod tests {
|
||||||
White Pawn on E5,
|
White Pawn on E5,
|
||||||
Black Pawn on D5,
|
Black Pawn on D5,
|
||||||
);
|
);
|
||||||
pos.test_set_en_passant_square(Square::D6);
|
pos.test_set_en_passant(EnPassant::from_target_square(Square::D6).unwrap());
|
||||||
let piece = piece!(White Pawn on E5);
|
let piece = piece!(White Pawn on E5);
|
||||||
let sight = pos.sight_of_piece(&piece);
|
let sight = pos.sight_of_piece(&piece);
|
||||||
|
|
||||||
|
|
58
position/src/testing.rs
Normal file
58
position/src/testing.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::MakeMoveError;
|
||||||
|
use chessfriend_moves::BuildMoveError;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_move_list {
|
||||||
|
($generated:expr, $expected:expr, $position:expr) => {
|
||||||
|
assert_eq!(
|
||||||
|
$generated,
|
||||||
|
$expected,
|
||||||
|
"\n\tMatching: {:?}\n\tGenerated, not expected: {:?}\n\tExpected, not generated: {:?}",
|
||||||
|
$generated
|
||||||
|
.intersection(&$expected)
|
||||||
|
.map(|mv| format!("{}", mv))
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
$generated
|
||||||
|
.difference(&$expected)
|
||||||
|
.map(|mv| format!("{}", mv))
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
$expected
|
||||||
|
.difference(&$generated)
|
||||||
|
.map(|mv| format!("{}", mv))
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! formatted_move_list {
|
||||||
|
($move_list:expr, $position:expr) => {
|
||||||
|
$move_list
|
||||||
|
.iter()
|
||||||
|
.map(|mv| format!("{}", mv))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type TestResult = Result<(), TestError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum TestError {
|
||||||
|
BuildMove(BuildMoveError),
|
||||||
|
MakeMove(MakeMoveError),
|
||||||
|
NoLegalMoves,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BuildMoveError> for TestError {
|
||||||
|
fn from(value: BuildMoveError) -> Self {
|
||||||
|
TestError::BuildMove(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MakeMoveError> for TestError {
|
||||||
|
fn from(value: MakeMoveError) -> Self {
|
||||||
|
TestError::MakeMove(value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,34 +0,0 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! assert_move_list {
|
|
||||||
($generated:expr, $expected:expr, $position:expr) => {
|
|
||||||
assert_eq!(
|
|
||||||
$generated,
|
|
||||||
$expected,
|
|
||||||
"Difference: {:?}",
|
|
||||||
$generated
|
|
||||||
.symmetric_difference(&$expected)
|
|
||||||
.map(|mv| format!(
|
|
||||||
"{}",
|
|
||||||
$crate::r#move::AlgebraicMoveFormatter::new(mv, &$position)
|
|
||||||
))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! formatted_move_list {
|
|
||||||
($move_list:expr, $position:expr) => {
|
|
||||||
$move_list
|
|
||||||
.iter()
|
|
||||||
.map(|mv| {
|
|
||||||
format!(
|
|
||||||
"{}",
|
|
||||||
$crate::r#move::AlgebraicMoveFormatter::new(mv, &$position)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue