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"
|
||||
|
||||
[[package]]
|
||||
name = "chessfriend_move_generator"
|
||||
name = "chessfriend_moves"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chessfriend_bitboard",
|
||||
"chessfriend_core",
|
||||
"chessfriend_position",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -88,6 +87,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"chessfriend_bitboard",
|
||||
"chessfriend_core",
|
||||
"chessfriend_moves",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -172,6 +172,7 @@ name = "explorer"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chessfriend_core",
|
||||
"chessfriend_moves",
|
||||
"chessfriend_position",
|
||||
"clap",
|
||||
"rustyline",
|
||||
|
|
|
@ -3,6 +3,7 @@ members = [
|
|||
"bitboard",
|
||||
"core",
|
||||
"explorer",
|
||||
"moves",
|
||||
"position",
|
||||
]
|
||||
resolver = "2"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::Color;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -168,6 +169,16 @@ impl Rank {
|
|||
/// 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_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]
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
chessfriend_core = { path = "../core" }
|
||||
chessfriend_moves = { path = "../moves" }
|
||||
chessfriend_position = { path = "../position" }
|
||||
clap = { version = "4.4.12", features = ["derive"] }
|
||||
rustyline = "13.0.0"
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
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 rustyline::error::ReadlineError;
|
||||
use rustyline::DefaultEditor;
|
||||
|
@ -97,16 +100,15 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
|
|||
)
|
||||
.map_err(|_| "Error: invalid square specifier")?;
|
||||
|
||||
let mv = MoveBuilder::new(
|
||||
Piece::new(state.position.player_to_move(), shape),
|
||||
from_square,
|
||||
to_square,
|
||||
)
|
||||
.build();
|
||||
let mv = MoveBuilder::new()
|
||||
.from(from_square)
|
||||
.to(to_square)
|
||||
.build()
|
||||
.map_err(|err| format!("Error: cannot build move: {:?}", err))?;
|
||||
|
||||
state.position = MakeMoveBuilder::new(&state.position)
|
||||
.make(&mv)
|
||||
.map_err(|err| format!("error: Cannot make move: {:?}", err))?
|
||||
.map_err(|err| format!("Error: cannot make move: {:?}", err))?
|
||||
.build();
|
||||
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]
|
||||
chessfriend_core = { path = "../core" }
|
||||
chessfriend_bitboard = { path = "../bitboard" }
|
||||
chessfriend_moves = { path = "../moves" }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Direction, Shape, Square};
|
||||
use chessfriend_core::Shape;
|
||||
|
||||
use crate::sight::SliderRayToSquareExt;
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// 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_moves::{Castle, EnPassant};
|
||||
use std::fmt::Write;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fen {
|
||||
($fen_string:literal) => {
|
||||
Position::from_fen_str($fen_string)
|
||||
|
@ -95,11 +97,8 @@ impl ToFen for Position {
|
|||
write!(
|
||||
fen_string,
|
||||
" {}",
|
||||
if let Some(en_passant_square) = self.en_passant_square() {
|
||||
en_passant_square.to_string()
|
||||
} else {
|
||||
"-".to_string()
|
||||
}
|
||||
self.en_passant()
|
||||
.map_or("-".to_string(), |ep| ep.target_square().to_string())
|
||||
)
|
||||
.map_err(|err| ToFenError::FmtError(err))?;
|
||||
|
||||
|
@ -197,7 +196,7 @@ impl FromFen for Position {
|
|||
let en_passant_square = fields.next().ok_or(FromFenError)?;
|
||||
if en_passant_square != "-" {
|
||||
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)?;
|
||||
|
|
|
@ -4,7 +4,6 @@ pub mod fen;
|
|||
|
||||
mod check;
|
||||
mod display;
|
||||
mod r#move;
|
||||
mod move_generator;
|
||||
mod position;
|
||||
mod sight;
|
||||
|
@ -12,8 +11,8 @@ mod sight;
|
|||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
mod tests;
|
||||
mod testing;
|
||||
|
||||
pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder};
|
||||
pub use r#move::{Castle, MakeMoveError, Move, MoveBuilder};
|
||||
pub use position::{MakeMoveError, MoveBuilder as MakeMoveBuilder, Position, PositionBuilder};
|
||||
|
|
|
@ -16,6 +16,7 @@ macro_rules! position {
|
|||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_export]
|
||||
macro_rules! test_position {
|
||||
($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)
|
||||
.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();
|
||||
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 queen;
|
||||
mod rook;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(crate) use move_set::MoveSet;
|
||||
|
@ -17,9 +19,10 @@ use self::{
|
|||
queen::ClassicalMoveGenerator as QueenMoveGenerator,
|
||||
rook::ClassicalMoveGenerator as RookMoveGenerator,
|
||||
};
|
||||
use crate::{Move, Position};
|
||||
use crate::Position;
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||
use chessfriend_moves::Move;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
trait MoveGenerator {
|
||||
|
@ -53,7 +56,7 @@ macro_rules! move_generator_declaration {
|
|||
push_mask: chessfriend_bitboard::BitBoard,
|
||||
) -> $name {
|
||||
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)
|
||||
} else {
|
||||
|
@ -66,7 +69,7 @@ macro_rules! move_generator_declaration {
|
|||
};
|
||||
($name:ident, getters) => {
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -77,7 +80,8 @@ macro_rules! move_generator_declaration {
|
|||
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(
|
||||
chessfriend_bitboard::BitBoard::empty(),
|
||||
|partial, mv_set| partial | mv_set.bitboard(),
|
||||
|
|
|
@ -72,7 +72,7 @@ mod tests {
|
|||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
BitBoard::new(
|
||||
0b10000000_01000000_00100000_00010000_00001000_00000100_00000010_00000000
|
||||
)
|
||||
|
@ -93,7 +93,7 @@ mod tests {
|
|||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
BitBoard::new(
|
||||
0b00000000_00000000_00000000_00000000_00001000_00000100_00000010_00000000
|
||||
)
|
||||
|
@ -112,7 +112,7 @@ mod tests {
|
|||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
BitBoard::new(
|
||||
0b00000000_00000000_00000000_00000000_00000000_00000100_00000010_00000000
|
||||
)
|
||||
|
@ -129,7 +129,7 @@ mod tests {
|
|||
|
||||
let generator =
|
||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
let bitboard = generator.bitboard();
|
||||
let bitboard = generator._test_bitboard();
|
||||
let expected = BitBoard::new(
|
||||
0b00000001_10000010_01000100_00101000_00000000_00101000_01000100_10000010,
|
||||
);
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
//! generating the possible moves for the king in the given position.
|
||||
|
||||
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
||||
use crate::{r#move::Castle, Position};
|
||||
use crate::Position;
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{PlacedPiece, Shape};
|
||||
use chessfriend_moves::Castle;
|
||||
|
||||
move_generator_declaration!(KingMoveGenerator, struct);
|
||||
move_generator_declaration!(KingMoveGenerator, new);
|
||||
|
@ -57,59 +58,64 @@ impl MoveGeneratorInternal for KingMoveGenerator {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
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_core::{piece, Color, Square};
|
||||
use chessfriend_moves::{Builder as MoveBuilder, Castle, Move};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn one_king() {
|
||||
fn one_king() -> TestResult {
|
||||
let pos = position![White King on E4];
|
||||
|
||||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
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([
|
||||
MoveBuilder::new(piece!(White King), Square::E4, Square::D5).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::E4, Square::E5).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::E4, Square::F5).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::E4, Square::F4).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::E4, Square::F3).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::E4, Square::E3).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::E4, Square::D3).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::E4, Square::D4).build(),
|
||||
builder.clone().to(Square::D5).build()?,
|
||||
builder.clone().to(Square::E5).build()?,
|
||||
builder.clone().to(Square::F5).build()?,
|
||||
builder.clone().to(Square::F4).build()?,
|
||||
builder.clone().to(Square::F3).build()?,
|
||||
builder.clone().to(Square::E3).build()?,
|
||||
builder.clone().to(Square::D3).build()?,
|
||||
builder.clone().to(Square::D4).build()?,
|
||||
]);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||
|
||||
assert_move_list!(generated_moves, expected_moves, pos);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_king_corner() {
|
||||
fn one_king_corner() -> TestResult {
|
||||
let pos = position![White King on A1];
|
||||
|
||||
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];
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
bitboard![A2, B2, B1],
|
||||
"Generated:\n{generated_bitboard}\nExpected:\n{expected_bitboard}"
|
||||
);
|
||||
|
||||
let builder = MoveBuilder::push(&piece!(White King on A1));
|
||||
let expected_moves = [
|
||||
MoveBuilder::new(piece!(White King), Square::A1, Square::A2).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::A1, Square::B1).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::A1, Square::B2).build(),
|
||||
builder.clone().to(Square::A2).build()?,
|
||||
builder.clone().to(Square::B1).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 {
|
||||
assert!(
|
||||
|
@ -124,6 +130,8 @@ mod tests {
|
|||
"Moves unexpectedly present: {:#?}",
|
||||
generated_moves
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -138,7 +146,7 @@ mod tests {
|
|||
assert!(pos.is_king_in_check());
|
||||
|
||||
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];
|
||||
|
||||
|
@ -146,7 +154,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn white_king_unobstructed_castles() {
|
||||
fn white_king_unobstructed_castles() -> TestResult {
|
||||
let pos = test_position!(
|
||||
White King on E1,
|
||||
White Rook on A1,
|
||||
|
@ -159,21 +167,16 @@ mod tests {
|
|||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||
|
||||
let king = piece!(White King);
|
||||
assert!(generated_moves.contains(
|
||||
&MoveBuilder::new(king, Square::E1, Square::G1)
|
||||
.castle(Castle::KingSide)
|
||||
.build()
|
||||
));
|
||||
assert!(generated_moves.contains(
|
||||
&MoveBuilder::new(king, Square::E1, Square::C1)
|
||||
.castle(Castle::QueenSide)
|
||||
.build()
|
||||
));
|
||||
assert!(generated_moves
|
||||
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
|
||||
assert!(generated_moves
|
||||
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_king_obstructed_queenside_castle() {
|
||||
fn white_king_obstructed_queenside_castle() -> TestResult {
|
||||
let pos = test_position!(
|
||||
White King on E1,
|
||||
White Knight on B1,
|
||||
|
@ -187,21 +190,16 @@ mod tests {
|
|||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||
|
||||
let king = piece!(White King);
|
||||
assert!(generated_moves.contains(
|
||||
&MoveBuilder::new(king, Square::E1, Square::G1)
|
||||
.castle(Castle::KingSide)
|
||||
.build()
|
||||
));
|
||||
assert!(!generated_moves.contains(
|
||||
&MoveBuilder::new(king, Square::E1, Square::C1)
|
||||
.castle(Castle::QueenSide)
|
||||
.build()
|
||||
));
|
||||
assert!(generated_moves
|
||||
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
|
||||
assert!(!generated_moves
|
||||
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_king_obstructed_kingside_castle() {
|
||||
fn white_king_obstructed_kingside_castle() -> TestResult {
|
||||
let pos = test_position!(
|
||||
White King on E1,
|
||||
White Rook on A1,
|
||||
|
@ -215,16 +213,11 @@ mod tests {
|
|||
let generator = KingMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||
|
||||
let king = piece!(White King);
|
||||
assert!(!generated_moves.contains(
|
||||
&MoveBuilder::new(king, Square::E1, Square::G1)
|
||||
.castle(Castle::KingSide)
|
||||
.build()
|
||||
));
|
||||
assert!(generated_moves.contains(
|
||||
&MoveBuilder::new(king, Square::E1, Square::C1)
|
||||
.castle(Castle::QueenSide)
|
||||
.build()
|
||||
));
|
||||
assert!(!generated_moves
|
||||
.contains(&MoveBuilder::castling(Color::White, Castle::KingSide).build()?));
|
||||
assert!(generated_moves
|
||||
.contains(&MoveBuilder::castling(Color::White, Castle::QueenSide).build()?));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,54 +34,35 @@ impl MoveGeneratorInternal for KnightMoveGenerator {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{position, Move, MoveBuilder};
|
||||
use crate::{assert_move_list, position, testing::*};
|
||||
use chessfriend_core::{piece, Color, Square};
|
||||
use chessfriend_moves::Builder as MoveBuilder;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn one_knight() {
|
||||
fn one_knight() -> TestResult {
|
||||
let pos = position![
|
||||
White Knight on E4,
|
||||
];
|
||||
|
||||
let generator =
|
||||
KnightMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||
|
||||
/*
|
||||
let bb = generator.bitboard();
|
||||
assert_eq!(
|
||||
bb,
|
||||
BitBoard::new(
|
||||
0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000
|
||||
)
|
||||
);
|
||||
*/
|
||||
let piece = piece!(White Knight on E4);
|
||||
let expected_moves = HashSet::from_iter([
|
||||
MoveBuilder::push(&piece).to(Square::C3).build()?,
|
||||
MoveBuilder::push(&piece).to(Square::D2).build()?,
|
||||
MoveBuilder::push(&piece).to(Square::F2).build()?,
|
||||
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 = [
|
||||
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(),
|
||||
];
|
||||
assert_move_list!(generated_moves, expected_moves, pos);
|
||||
|
||||
let mut generated_moves: HashSet<Move> = generator.iter().collect();
|
||||
|
||||
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
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{r#move::Castle, Move, MoveBuilder};
|
||||
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.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
|
@ -11,12 +11,18 @@ struct BitBoardSet {
|
|||
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.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct MoveSet {
|
||||
piece: PlacedPiece,
|
||||
bitboards: BitBoardSet,
|
||||
special: u8,
|
||||
special: Option<Special>,
|
||||
}
|
||||
|
||||
impl MoveSet {
|
||||
|
@ -24,21 +30,53 @@ impl MoveSet {
|
|||
MoveSet {
|
||||
piece,
|
||||
bitboards: BitBoardSet::default(),
|
||||
special: 0,
|
||||
special: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn can_move_to_square(&self, to: Square) -> bool {
|
||||
self.bitboard().is_set(to)
|
||||
pub(crate) fn can_move_to_square(&self, target_square: Square) -> bool {
|
||||
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 {
|
||||
match castle {
|
||||
Castle::KingSide => (self.special & 0b1) != 0,
|
||||
Castle::QueenSide => (self.special & 0b10) != 0,
|
||||
match self.special {
|
||||
Some(Special::King { castles }) => self.check_castle_field(castles, castle),
|
||||
_ => 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 {
|
||||
self.bitboards.quiet = bitboard;
|
||||
self
|
||||
|
@ -50,72 +88,103 @@ impl 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self.bitboards.captures | self.bitboards.quiet
|
||||
}
|
||||
|
||||
pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
|
||||
let piece = self.piece.piece();
|
||||
let from_square = self.piece.square();
|
||||
let piece = &self.piece;
|
||||
let color = piece.color();
|
||||
|
||||
let is_pawn_on_starting_rank =
|
||||
piece.is_pawn() && piece.square().rank().is_pawn_starting_rank(color);
|
||||
|
||||
self.bitboards
|
||||
.quiet
|
||||
.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(
|
||||
self.bitboards
|
||||
.captures
|
||||
.occupied_squares()
|
||||
.map(move |to_square| {
|
||||
MoveBuilder::new(*piece, from_square, to_square)
|
||||
.capturing(PlacedPiece::new(
|
||||
Piece::new(Color::White, Shape::Pawn),
|
||||
to_square,
|
||||
))
|
||||
.filter_map(|to_square| {
|
||||
MoveBuilder::push(piece)
|
||||
.capturing_on(to_square)
|
||||
.build()
|
||||
.ok()
|
||||
}),
|
||||
)
|
||||
.chain(
|
||||
if (self.special & 0b1) != 0 {
|
||||
Some(())
|
||||
.chain(self.castle_move(Castle::KingSide))
|
||||
.chain(self.castle_move(Castle::QueenSide))
|
||||
.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 {
|
||||
None
|
||||
}
|
||||
.map(|()| {
|
||||
MoveBuilder::new(
|
||||
*piece,
|
||||
from_square,
|
||||
Castle::KingSide.target_squares(piece.color()).king,
|
||||
)
|
||||
.castle(Castle::KingSide)
|
||||
.build()
|
||||
}),
|
||||
)
|
||||
.chain(
|
||||
if (self.special & 0b10) != 0 {
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.map(|()| {
|
||||
MoveBuilder::new(
|
||||
*piece,
|
||||
from_square,
|
||||
Castle::QueenSide.target_squares(piece.color()).king,
|
||||
)
|
||||
.castle(Castle::QueenSide)
|
||||
.build()
|
||||
}),
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn en_passant_move(&self) -> Option<Move> {
|
||||
match self.special {
|
||||
Some(Special::Pawn { en_passant }) => Some(unsafe {
|
||||
MoveBuilder::push(&self.piece)
|
||||
.capturing_en_passant_on(en_passant.target_square())
|
||||
.build_unchecked()
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,20 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
|
|||
use crate::Position;
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
|
||||
use chessfriend_moves::{EnPassant, Move};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
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 {
|
||||
fn shape() -> Shape {
|
||||
|
@ -24,14 +33,61 @@ impl MoveGeneratorInternal for PawnMoveGenerator {
|
|||
let capture_moves = Self::attacks(position, &placed_piece) & capture_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)
|
||||
.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 {
|
||||
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 square = piece.square();
|
||||
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 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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assert_move_list, position::DiagramFormatter, test_position, Move, MoveBuilder};
|
||||
use chessfriend_core::{piece, Color, Piece, Square};
|
||||
use crate::{
|
||||
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;
|
||||
|
||||
#[test]
|
||||
fn one_2square_push() {
|
||||
fn one_double_push() -> TestResult {
|
||||
let pos = test_position![White Pawn on E2];
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
let pawn = piece!(White Pawn on E2);
|
||||
let expected_moves = HashSet::from_iter([
|
||||
MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build(),
|
||||
MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build(),
|
||||
MoveBuilder::push(&pawn).to(Square::E3).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]
|
||||
fn one_1square_push() {
|
||||
fn one_single_push() -> TestResult {
|
||||
let pos = test_position![White Pawn on E3];
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||
|
||||
let expected_moves = HashSet::from_iter([MoveBuilder::new(
|
||||
Piece::pawn(Color::White),
|
||||
Square::E3,
|
||||
Square::E4,
|
||||
)
|
||||
.build()]);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E3))
|
||||
.to(Square::E4)
|
||||
.build()?]);
|
||||
|
||||
assert_move_list!(generated_moves, expected_moves, pos);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_obstructed_2square_push() {
|
||||
fn one_obstructed_2square_push() -> TestResult {
|
||||
let pos = test_position![
|
||||
White Pawn on E2,
|
||||
White Knight on E4,
|
||||
|
@ -124,16 +227,15 @@ mod tests {
|
|||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
let expected_moves = HashSet::from_iter([MoveBuilder::new(
|
||||
Piece::pawn(Color::White),
|
||||
Square::E2,
|
||||
Square::E3,
|
||||
)
|
||||
.build()]);
|
||||
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E2))
|
||||
.to(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);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -145,14 +247,14 @@ mod tests {
|
|||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||
let expected_moves: HashSet<Move> = HashSet::new();
|
||||
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||
let expected_moves: HashSet<_> = HashSet::new();
|
||||
|
||||
assert_move_list!(generated_moves, expected_moves, pos);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_attack() {
|
||||
fn one_attack() -> TestResult {
|
||||
let pos = test_position![
|
||||
White Pawn on E4,
|
||||
White Bishop on E5,
|
||||
|
@ -161,20 +263,19 @@ mod tests {
|
|||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
let expected_moves =
|
||||
HashSet::from_iter(
|
||||
[MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5)
|
||||
.capturing(piece!(Black Knight on D5))
|
||||
.build()],
|
||||
);
|
||||
let expected_moves = HashSet::from_iter([MoveBuilder::push(&piece!(White Pawn on E4))
|
||||
.capturing_on(Square::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]
|
||||
fn one_double_attack() {
|
||||
fn one_double_attack() -> TestResult {
|
||||
let pos = test_position![
|
||||
White Pawn on E4,
|
||||
White Bishop on E5,
|
||||
|
@ -184,21 +285,68 @@ mod tests {
|
|||
|
||||
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([
|
||||
MoveBuilder::new(piece!(White Pawn), Square::E4, Square::D5)
|
||||
.capturing(piece!(Black Knight on D5))
|
||||
.build(),
|
||||
MoveBuilder::new(piece!(White Pawn), Square::E4, Square::F5)
|
||||
.capturing(piece!(Black Queen on F5))
|
||||
.build(),
|
||||
builder.clone().capturing_on(Square::D5).build()?,
|
||||
builder.clone().capturing_on(Square::F5).build()?,
|
||||
]);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.iter().collect();
|
||||
let generated_moves: HashSet<_> = generator.iter().collect();
|
||||
|
||||
assert_eq!(
|
||||
generated_moves, expected_moves,
|
||||
"generated: {:#?}\nexpected: {:#?}",
|
||||
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 =
|
||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
let bitboard = generator.bitboard();
|
||||
let bitboard = generator._test_bitboard();
|
||||
let expected = bitboard![
|
||||
A2, C2, D2, E2, F2, G2, H2, // Rank
|
||||
B1, B3, B4, B5, B6, B7, B8, // File
|
||||
|
@ -102,7 +102,7 @@ mod tests {
|
|||
|
||||
let generator =
|
||||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
let bitboard = generator.bitboard();
|
||||
let bitboard = generator._test_bitboard();
|
||||
let expected = BitBoard::new(
|
||||
0b10000001_01000001_00100001_00010001_00001001_00000101_00000011_00001110,
|
||||
);
|
||||
|
@ -127,7 +127,7 @@ mod tests {
|
|||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
bitboard![
|
||||
A2, C2, D2, E2, F2, G2, H2, // Rank
|
||||
B1, B3, B4, B5, B6, B7, B8, // File
|
||||
|
@ -146,7 +146,7 @@ mod tests {
|
|||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
bitboard![
|
||||
A3, B3, C3, E3, F3, G3, H3, // Rank
|
||||
D1, D2, D4, D5, D6, D7, D8, // File
|
||||
|
|
|
@ -73,7 +73,7 @@ mod tests {
|
|||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
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);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
BitBoard::new(
|
||||
0b00000001_00000001_00000001_00000001_00000001_00000001_00000001_00001110
|
||||
)
|
||||
|
@ -111,7 +111,7 @@ mod tests {
|
|||
ClassicalMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
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);
|
||||
|
||||
assert_eq!(
|
||||
generator.bitboard(),
|
||||
generator._test_bitboard(),
|
||||
bitboard![A4, B4, C4, E4, F4, G4, H4, D1, D2, D3, D5, D6, D7, D8]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,15 +6,13 @@
|
|||
//! [1]: https://peterellisjones.com
|
||||
//! [2]: https://peterellisjones.com/posts/generating-legal-chess-moves-efficiently/
|
||||
|
||||
use crate::{
|
||||
assert_move_list, formatted_move_list, move_generator::Moves, r#move::AlgebraicMoveFormatter,
|
||||
test_position, Move, MoveBuilder,
|
||||
};
|
||||
use crate::{assert_move_list, formatted_move_list, test_position, testing::*};
|
||||
use chessfriend_core::{piece, Square};
|
||||
use chessfriend_moves::Builder as MoveBuilder;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn pseudo_legal_move_generation() -> Result<(), String> {
|
||||
fn pseudo_legal_move_generation() -> TestResult {
|
||||
let pos = test_position!(Black, [
|
||||
Black King on E8,
|
||||
White King on E1,
|
||||
|
@ -24,7 +22,7 @@ fn pseudo_legal_move_generation() -> Result<(), String> {
|
|||
let generated_moves = pos.moves();
|
||||
let king_moves = generated_moves
|
||||
.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::F7));
|
||||
|
@ -33,7 +31,7 @@ fn pseudo_legal_move_generation() -> Result<(), String> {
|
|||
}
|
||||
|
||||
#[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, [
|
||||
Black King on E7,
|
||||
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 king_moves = generated_moves
|
||||
.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));
|
||||
|
||||
|
@ -51,7 +49,7 @@ fn gotcha_king_moves_away_from_a_checking_slider() -> Result<(), String> {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn check_evasions_1() {
|
||||
fn check_evasions_1() -> TestResult {
|
||||
let pos = test_position!(Black, [
|
||||
Black King on E8,
|
||||
White King on E1,
|
||||
|
@ -60,11 +58,12 @@ fn check_evasions_1() {
|
|||
|
||||
let generated_moves = pos.moves();
|
||||
|
||||
let builder = MoveBuilder::push(&piece!(Black King on E8));
|
||||
let expected_moves = HashSet::from_iter([
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::E7).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(),
|
||||
builder.clone().to(Square::D8).build()?,
|
||||
builder.clone().to(Square::E7).build()?,
|
||||
builder.clone().to(Square::F7).build()?,
|
||||
builder.clone().to(Square::F8).build()?,
|
||||
]);
|
||||
|
||||
assert_move_list!(
|
||||
|
@ -72,10 +71,12 @@ fn check_evasions_1() {
|
|||
expected_moves,
|
||||
pos
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_evasions_double_check() {
|
||||
fn check_evasions_double_check() -> TestResult {
|
||||
let pos = test_position!(Black, [
|
||||
Black King on E8,
|
||||
Black Bishop on F6,
|
||||
|
@ -86,11 +87,12 @@ fn check_evasions_double_check() {
|
|||
|
||||
let generated_moves = pos.moves();
|
||||
|
||||
let builder = MoveBuilder::push(&piece!(Black King on E8));
|
||||
let expected_moves = HashSet::from_iter([
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D7).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(),
|
||||
builder.clone().to(Square::D8).build()?,
|
||||
builder.clone().to(Square::D7).build()?,
|
||||
builder.clone().to(Square::F7).build()?,
|
||||
builder.clone().to(Square::F8).build()?,
|
||||
]);
|
||||
|
||||
assert_move_list!(
|
||||
|
@ -98,10 +100,12 @@ fn check_evasions_double_check() {
|
|||
expected_moves,
|
||||
pos
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_check_with_blocker() {
|
||||
fn single_check_with_blocker() -> TestResult {
|
||||
let pos = test_position!(Black, [
|
||||
Black King on E8,
|
||||
Black Knight on G6,
|
||||
|
@ -111,15 +115,15 @@ fn single_check_with_blocker() {
|
|||
|
||||
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([
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D8).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::D7).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F7).build(),
|
||||
MoveBuilder::new(piece!(Black King), Square::E8, Square::F8).build(),
|
||||
MoveBuilder::new(piece!(Black Knight), Square::G6, Square::E7).build(),
|
||||
MoveBuilder::new(piece!(Black Knight), Square::G6, Square::E5)
|
||||
.capturing(piece!(White Rook on E5))
|
||||
.build(),
|
||||
king_builder.clone().to(Square::D8).build()?,
|
||||
king_builder.clone().to(Square::D7).build()?,
|
||||
king_builder.clone().to(Square::F7).build()?,
|
||||
king_builder.clone().to(Square::F8).build()?,
|
||||
knight_builder.clone().to(Square::E7).build()?,
|
||||
knight_builder.clone().capturing_on(Square::E5).build()?,
|
||||
]);
|
||||
|
||||
assert_move_list!(
|
||||
|
@ -127,10 +131,12 @@ fn single_check_with_blocker() {
|
|||
expected_moves,
|
||||
pos
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn en_passant_check_capture() {
|
||||
fn en_passant_check_capture() -> TestResult {
|
||||
let pos = test_position!(Black, [
|
||||
Black King on C5,
|
||||
Black Pawn on E4,
|
||||
|
@ -139,21 +145,23 @@ fn en_passant_check_capture() {
|
|||
|
||||
assert!(pos.is_king_in_check());
|
||||
|
||||
let generated_moves = pos.moves().iter().collect::<HashSet<_>>();
|
||||
let generated_moves: HashSet<_> = pos.moves().iter().collect();
|
||||
|
||||
assert!(
|
||||
generated_moves.contains(
|
||||
&MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3)
|
||||
.capturing_en_passant(piece!(White Pawn on D4))
|
||||
.build()
|
||||
&MoveBuilder::push(&piece!(Black Pawn on E4))
|
||||
.capturing_en_passant_on(Square::D3)
|
||||
.build()?
|
||||
),
|
||||
"Valid moves: {:?}",
|
||||
formatted_move_list!(generated_moves, pos)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn en_passant_check_block() {
|
||||
fn en_passant_check_block() -> TestResult {
|
||||
let pos = test_position!(Black, [
|
||||
Black King on B5,
|
||||
Black Pawn on E4,
|
||||
|
@ -163,21 +171,23 @@ fn en_passant_check_block() {
|
|||
|
||||
assert!(pos.is_king_in_check());
|
||||
|
||||
let generated_moves = pos.moves().iter().collect::<HashSet<_>>();
|
||||
let generated_moves: HashSet<_> = pos.moves().iter().collect();
|
||||
|
||||
assert!(
|
||||
generated_moves.contains(
|
||||
&MoveBuilder::new(piece!(Black Pawn), Square::E4, Square::D3)
|
||||
.capturing_en_passant(piece!(White Pawn on D4))
|
||||
.build()
|
||||
&MoveBuilder::push(&piece!(Black Pawn on E4))
|
||||
.capturing_en_passant_on(Square::D3)
|
||||
.build()?
|
||||
),
|
||||
"Valid moves: {:?}",
|
||||
formatted_move_list!(generated_moves, pos)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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, [
|
||||
Black King on E8,
|
||||
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 rook_moves = generated_moves
|
||||
.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::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::E4));
|
||||
assert!(rook_moves.can_move_to_square(Square::E5));
|
||||
assert!(rook_moves.can_move_to_square(Square::E7));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn en_passant_discovered_check() {
|
||||
fn en_passant_discovered_check() -> TestResult {
|
||||
let pos = test_position!(Black, [
|
||||
Black King on A4,
|
||||
Black Pawn on E4,
|
||||
|
@ -212,15 +222,17 @@ fn en_passant_discovered_check() {
|
|||
White Queen on H4,
|
||||
], 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)
|
||||
.capturing_en_passant(piece!(White Pawn on D4))
|
||||
.build();
|
||||
let unexpected_move = MoveBuilder::push(&piece!(Black Pawn on E4))
|
||||
.capturing_en_passant_on(Square::D3)
|
||||
.build()?;
|
||||
|
||||
assert!(
|
||||
!generated_moves.contains(&unexpected_move),
|
||||
"Valid moves: {:?}",
|
||||
formatted_move_list!(generated_moves, pos)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,36 +1,32 @@
|
|||
// 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_moves::Builder as MoveBuilder;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn one_king() {
|
||||
let pos = position![
|
||||
fn one_king() -> TestResult {
|
||||
let pos = test_position![
|
||||
White King on D3,
|
||||
Black King on H6,
|
||||
];
|
||||
|
||||
let builder = MoveBuilder::push(&piece!(White King on D3));
|
||||
let expected_moves = HashSet::from_iter([
|
||||
MoveBuilder::new(piece!(White King), Square::D3, Square::D4).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::D3, Square::E4).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::D3, Square::E3).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::D3, Square::E2).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::D3, Square::D2).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::D3, Square::C2).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::D3, Square::C3).build(),
|
||||
MoveBuilder::new(piece!(White King), Square::D3, Square::C4).build(),
|
||||
builder.clone().to(Square::D4).build()?,
|
||||
builder.clone().to(Square::E4).build()?,
|
||||
builder.clone().to(Square::E3).build()?,
|
||||
builder.clone().to(Square::E2).build()?,
|
||||
builder.clone().to(Square::D2).build()?,
|
||||
builder.clone().to(Square::C2).build()?,
|
||||
builder.clone().to(Square::C3).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!(
|
||||
generated_moves,
|
||||
expected_moves,
|
||||
"{:?}",
|
||||
generated_moves
|
||||
.symmetric_difference(&expected_moves)
|
||||
.map(|m| format!("{}", AlgebraicMoveFormatter::new(&m, &pos)))
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
assert_move_list!(generated_moves, expected_moves, pos);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
mod move_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;
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{
|
||||
position::flags::Flags,
|
||||
r#move::{AlgebraicMoveFormatter, Castle},
|
||||
MakeMoveError, Move, Position,
|
||||
};
|
||||
use crate::{position::flags::Flags, Position};
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
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.
|
||||
#[derive(Clone)]
|
||||
|
@ -27,7 +34,7 @@ pub enum ValidatedMove {
|
|||
captured_piece: Option<PlacedPiece>,
|
||||
promotion: Option<Shape>,
|
||||
flags: Flags,
|
||||
en_passant_square: Option<Square>,
|
||||
en_passant: Option<EnPassant>,
|
||||
increment_ply: bool,
|
||||
},
|
||||
Castle {
|
||||
|
@ -55,14 +62,14 @@ where
|
|||
M: MoveToMake,
|
||||
{
|
||||
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
|
||||
.position
|
||||
.piece_on_square(from_square)
|
||||
.piece_on_square(origin_square)
|
||||
.ok_or(MakeMoveError::NoPiece)?;
|
||||
|
||||
let to_square = mv.to_square();
|
||||
let target_square = mv.target_square();
|
||||
|
||||
let moves = self
|
||||
.position
|
||||
|
@ -76,8 +83,8 @@ where
|
|||
}
|
||||
}
|
||||
None => {
|
||||
if !moves.can_move_to_square(to_square) {
|
||||
return Err(MakeMoveError::IllegalSquare(to_square));
|
||||
if !moves.can_move_to_square(target_square) {
|
||||
return Err(MakeMoveError::IllegalSquare(target_square));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,8 +94,8 @@ where
|
|||
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.
|
||||
let capture_square = match player {
|
||||
Color::White => to_square.neighbor(Direction::South),
|
||||
Color::Black => to_square.neighbor(Direction::North),
|
||||
Color::White => target_square.neighbor(Direction::South),
|
||||
Color::Black => target_square.neighbor(Direction::North),
|
||||
}
|
||||
.ok_or(MakeMoveError::NoCapturedPiece)?;
|
||||
|
||||
|
@ -100,7 +107,7 @@ where
|
|||
} else if mv.is_capture() {
|
||||
Some(
|
||||
self.position
|
||||
.piece_on_square(to_square)
|
||||
.piece_on_square(target_square)
|
||||
.ok_or(MakeMoveError::NoCapturedPiece)?,
|
||||
)
|
||||
} else {
|
||||
|
@ -142,11 +149,12 @@ where
|
|||
},
|
||||
})
|
||||
} else {
|
||||
let en_passant_square: Option<Square> = if mv.is_double_push() {
|
||||
let en_passant = if mv.is_double_push() {
|
||||
match piece.color() {
|
||||
Color::White => to_square.neighbor(Direction::South),
|
||||
Color::Black => to_square.neighbor(Direction::North),
|
||||
Color::White => target_square.neighbor(Direction::South),
|
||||
Color::Black => target_square.neighbor(Direction::North),
|
||||
}
|
||||
.and_then(EnPassant::from_target_square)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -154,13 +162,13 @@ where
|
|||
Ok(Builder {
|
||||
position: self.position,
|
||||
move_to_make: ValidatedMove::RegularMove {
|
||||
from_square,
|
||||
to_square,
|
||||
from_square: origin_square,
|
||||
to_square: target_square,
|
||||
moving_piece: piece,
|
||||
captured_piece,
|
||||
promotion: mv.promotion(),
|
||||
flags,
|
||||
en_passant_square,
|
||||
en_passant,
|
||||
increment_ply: !(mv.is_capture() || piece.is_pawn()),
|
||||
},
|
||||
})
|
||||
|
@ -183,7 +191,7 @@ impl<'p> Builder<'p, ValidatedMove> {
|
|||
captured_piece,
|
||||
promotion,
|
||||
flags,
|
||||
en_passant_square,
|
||||
en_passant,
|
||||
increment_ply,
|
||||
} => {
|
||||
let mut pieces = self.position.piece_bitboards().clone();
|
||||
|
@ -210,7 +218,7 @@ impl<'p> Builder<'p, ValidatedMove> {
|
|||
self.position.player_to_move().other(),
|
||||
flags,
|
||||
pieces,
|
||||
en_passant_square,
|
||||
en_passant,
|
||||
ply,
|
||||
updated_move_number,
|
||||
)
|
||||
|
@ -223,18 +231,19 @@ impl<'p> Builder<'p, ValidatedMove> {
|
|||
} => {
|
||||
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_to: BitBoard = target_squares.king.into();
|
||||
*pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to;
|
||||
let king_origin_square: BitBoard = king.square().into();
|
||||
let king_target_square: BitBoard = parameters.king_target_square().into();
|
||||
*pieces.bitboard_for_piece_mut(king.piece()) ^=
|
||||
king_origin_square | king_target_square;
|
||||
|
||||
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_color_mut(player) &=
|
||||
!(king_from | rook_from) | (king_to | rook_to);
|
||||
!(king_origin_square | rook_from) | (king_target_square | rook_to);
|
||||
|
||||
Position::new(
|
||||
player.other(),
|
||||
|
@ -261,15 +270,24 @@ impl<'p> From<&'p Position> for Builder<'p, NoMove> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{position, r#move::Castle, MoveBuilder, PositionBuilder};
|
||||
use chessfriend_core::piece;
|
||||
use crate::testing::*;
|
||||
use crate::{position, test_position};
|
||||
use chessfriend_core::{piece, File};
|
||||
use chessfriend_moves::Builder as MoveBuilder;
|
||||
|
||||
#[test]
|
||||
fn move_white_pawn_one_square() -> Result<(), MakeMoveError> {
|
||||
fn move_white_pawn_one_square() -> TestResult {
|
||||
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);
|
||||
|
||||
assert_eq!(
|
||||
|
@ -281,25 +299,32 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn move_white_pawn_two_squares() -> Result<(), MakeMoveError> {
|
||||
let pos = position![White Pawn on E2];
|
||||
let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build();
|
||||
fn move_white_pawn_two_squares() -> TestResult {
|
||||
let pos = test_position![White Pawn on E2];
|
||||
|
||||
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);
|
||||
|
||||
assert_eq!(
|
||||
new_position.piece_on_square(Square::E4),
|
||||
Some(piece!(White Pawn on E4))
|
||||
);
|
||||
assert_eq!(new_position.en_passant_square(), Some(Square::E3));
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_kingside_castle() -> Result<(), MakeMoveError> {
|
||||
let pos = position![
|
||||
fn white_kingside_castle() -> TestResult {
|
||||
let pos = test_position![
|
||||
White King on E1,
|
||||
White Rook on H1,
|
||||
White Pawn on E2,
|
||||
|
@ -307,13 +332,10 @@ mod tests {
|
|||
White Pawn on G2,
|
||||
White Pawn on H2
|
||||
];
|
||||
println!("{}", &pos);
|
||||
|
||||
let mv = MoveBuilder::new(piece!(White King), Square::E1, Square::G1)
|
||||
.castle(Castle::KingSide)
|
||||
.build();
|
||||
let mv = MoveBuilder::castling(Color::White, 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);
|
||||
|
||||
assert_eq!(
|
||||
|
@ -329,15 +351,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn en_passant_capture() -> Result<(), MakeMoveError> {
|
||||
let pos = PositionBuilder::new()
|
||||
.place_piece(piece!(White Pawn on B5))
|
||||
.place_piece(piece!(Black Pawn on A7))
|
||||
.to_move(Color::Black)
|
||||
.build();
|
||||
println!("{pos}");
|
||||
fn en_passant_capture() -> TestResult {
|
||||
let pos = test_position!(Black, [
|
||||
White Pawn on B5,
|
||||
Black Pawn on A7,
|
||||
]);
|
||||
|
||||
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_en_passant());
|
||||
|
||||
|
@ -353,10 +374,10 @@ mod tests {
|
|||
Some(piece!(White Pawn on B5))
|
||||
);
|
||||
|
||||
let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6)
|
||||
.capturing_en_passant(piece!(Black Pawn on A5))
|
||||
.build();
|
||||
let en_passant_capture = Builder::<NoMove>::new(&en_passant_position)
|
||||
let white_pawn_capture = MoveBuilder::push(&piece!(White Pawn on B5))
|
||||
.capturing_en_passant_on(Square::A6)
|
||||
.build()?;
|
||||
let en_passant_capture = Builder::new(&en_passant_position)
|
||||
.make(&white_pawn_capture)?
|
||||
.build();
|
||||
println!("{en_passant_capture}");
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
use crate::{
|
||||
position::{flags::Flags, piece_sets::PieceBitBoards},
|
||||
r#move::Castle,
|
||||
Position,
|
||||
};
|
||||
use chessfriend_core::{piece, Color, Piece, PlacedPiece, Rank, Shape, Square};
|
||||
use chessfriend_moves::{Castle, EnPassant};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -14,7 +14,7 @@ pub struct Builder {
|
|||
flags: Flags,
|
||||
pieces: BTreeMap<Square, Piece>,
|
||||
kings: [Option<Square>; 2],
|
||||
en_passant_square: Option<Square>,
|
||||
en_passant: Option<EnPassant>,
|
||||
ply_counter: u16,
|
||||
move_number: u16,
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ impl Builder {
|
|||
flags: Flags::default(),
|
||||
pieces: BTreeMap::default(),
|
||||
kings: [None, None],
|
||||
en_passant_square: None,
|
||||
en_passant: None,
|
||||
ply_counter: 0,
|
||||
move_number: 1,
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ impl Builder {
|
|||
flags: position.flags(),
|
||||
pieces,
|
||||
kings: [Some(white_king), Some(black_king)],
|
||||
en_passant_square: position.en_passant_square(),
|
||||
en_passant: position.en_passant(),
|
||||
ply_counter: position.ply_counter(),
|
||||
move_number: position.move_number(),
|
||||
}
|
||||
|
@ -73,8 +73,8 @@ impl Builder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn en_passant_square(&mut self, square: Option<Square>) -> &mut Self {
|
||||
self.en_passant_square = square;
|
||||
pub fn en_passant(&mut self, en_passant: Option<EnPassant>) -> &mut Self {
|
||||
self.en_passant = en_passant;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -120,13 +120,13 @@ impl Builder {
|
|||
|
||||
for color in Color::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
|
||||
.pieces
|
||||
.get(&starting_squares.rook)
|
||||
.get(¶meters.rook_origin_square())
|
||||
.is_some_and(|piece| piece.shape() == Shape::Rook);
|
||||
let king_is_on_starting_square =
|
||||
self.kings[color as usize] == Some(starting_squares.king);
|
||||
self.kings[color as usize] == Some(parameters.king_origin_square());
|
||||
|
||||
if !king_is_on_starting_square || !has_rook_on_starting_square {
|
||||
flags.clear_player_has_right_to_castle_flag(color, castle);
|
||||
|
@ -138,7 +138,7 @@ impl Builder {
|
|||
self.player_to_move,
|
||||
flags,
|
||||
pieces,
|
||||
self.en_passant_square,
|
||||
self.en_passant,
|
||||
self.ply_counter,
|
||||
self.move_number,
|
||||
)
|
||||
|
@ -173,7 +173,7 @@ impl Default for Builder {
|
|||
flags: Flags::default(),
|
||||
pieces: pieces,
|
||||
kings: [Some(white_king_square), Some(black_king_square)],
|
||||
en_passant_square: None,
|
||||
en_passant: None,
|
||||
ply_counter: 0,
|
||||
move_number: 1,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::r#move::Castle;
|
||||
use chessfriend_core::Color;
|
||||
use chessfriend_moves::Castle;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
|
@ -10,7 +10,7 @@ pub struct Flags(u8);
|
|||
impl Flags {
|
||||
#[inline]
|
||||
pub(super) fn player_has_right_to_castle_flag_offset(color: Color, castle: Castle) -> usize {
|
||||
((color as usize) << 1) + castle.into_index()
|
||||
((color as usize) << 1) + castle as usize
|
||||
}
|
||||
|
||||
pub(super) fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool {
|
||||
|
@ -45,8 +45,6 @@ impl Default for Flags {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::r#move::Castle;
|
||||
use chessfriend_core::Color;
|
||||
|
||||
#[test]
|
||||
fn castle_flags() {
|
||||
|
|
|
@ -9,7 +9,7 @@ mod pieces;
|
|||
mod position;
|
||||
|
||||
pub use {
|
||||
builders::{MoveBuilder, PositionBuilder},
|
||||
builders::{MakeMoveError, MoveBuilder, PositionBuilder},
|
||||
diagram_formatter::DiagramFormatter,
|
||||
pieces::Pieces,
|
||||
position::Position,
|
||||
|
|
|
@ -5,11 +5,11 @@ use crate::{
|
|||
check::CheckingPieces,
|
||||
move_generator::{MoveSet, Moves},
|
||||
position::DiagramFormatter,
|
||||
r#move::Castle,
|
||||
sight::SightExt,
|
||||
};
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
||||
use chessfriend_moves::{Castle, EnPassant};
|
||||
use std::{cell::OnceCell, fmt};
|
||||
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
|
@ -17,7 +17,7 @@ pub struct Position {
|
|||
color_to_move: Color,
|
||||
flags: Flags,
|
||||
pieces: PieceBitBoards,
|
||||
en_passant_square: Option<Square>,
|
||||
en_passant: Option<EnPassant>,
|
||||
moves: OnceCell<Moves>,
|
||||
half_move_counter: u16,
|
||||
full_move_number: u16,
|
||||
|
@ -92,7 +92,7 @@ impl Position {
|
|||
return false;
|
||||
}
|
||||
|
||||
let castling_parameters = castle.parameters();
|
||||
let castling_parameters = castle.parameters(player);
|
||||
|
||||
let all_pieces = self.occupied_squares();
|
||||
if !(all_pieces & castling_parameters.clear_squares()).is_empty() {
|
||||
|
@ -182,12 +182,16 @@ impl Position {
|
|||
Pieces::new(&self, color)
|
||||
}
|
||||
|
||||
pub fn en_passant_square(&self) -> Option<Square> {
|
||||
self.en_passant_square
|
||||
pub fn has_en_passant_square(&self) -> bool {
|
||||
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 {
|
||||
let en_passant_square = self.en_passant_square;
|
||||
let en_passant_target_square = self.en_passant.map(|ep| ep.target_square());
|
||||
|
||||
Shape::ALL
|
||||
.iter()
|
||||
|
@ -202,7 +206,7 @@ impl Position {
|
|||
})
|
||||
.flat_map(|(piece, &bitboard)| {
|
||||
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)
|
||||
|
@ -214,7 +218,7 @@ impl Position {
|
|||
|
||||
#[cfg(test)]
|
||||
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
|
||||
|
@ -295,14 +299,14 @@ impl Position {
|
|||
player_to_move: Color,
|
||||
flags: Flags,
|
||||
pieces: PieceBitBoards,
|
||||
en_passant_square: Option<Square>,
|
||||
en_passant: Option<EnPassant>,
|
||||
half_move_counter: u16,
|
||||
full_move_number: u16,
|
||||
) -> Self {
|
||||
Self {
|
||||
color_to_move: player_to_move,
|
||||
flags,
|
||||
en_passant_square,
|
||||
en_passant,
|
||||
pieces,
|
||||
half_move_counter,
|
||||
full_move_number,
|
||||
|
@ -332,8 +336,8 @@ impl Position {
|
|||
|
||||
#[cfg(test)]
|
||||
impl Position {
|
||||
pub(crate) fn test_set_en_passant_square(&mut self, square: Square) {
|
||||
self.en_passant_square = Some(square);
|
||||
pub(crate) fn test_set_en_passant(&mut self, en_passant: EnPassant) {
|
||||
self.en_passant = Some(en_passant);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,7 +347,7 @@ impl Default for Position {
|
|||
color_to_move: Color::White,
|
||||
flags: Flags::default(),
|
||||
pieces: PieceBitBoards::default(),
|
||||
en_passant_square: None,
|
||||
en_passant: None,
|
||||
moves: OnceCell::new(),
|
||||
half_move_counter: 0,
|
||||
full_move_number: 1,
|
||||
|
@ -356,7 +360,7 @@ impl PartialEq for Position {
|
|||
self.pieces == other.pieces
|
||||
&& self.color_to_move == other.color_to_move
|
||||
&& 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)]
|
||||
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_core::{piece, Color, Square};
|
||||
use chessfriend_core::piece;
|
||||
|
||||
#[test]
|
||||
fn piece_on_square() {
|
||||
|
|
|
@ -242,6 +242,7 @@ mod tests {
|
|||
use crate::test_position;
|
||||
use chessfriend_bitboard::{bitboard, BitBoard};
|
||||
use chessfriend_core::{piece, Square};
|
||||
use chessfriend_moves::EnPassant;
|
||||
|
||||
sight_test!(e4_pawn, piece!(White Pawn on E4), bitboard!(D5, F5));
|
||||
|
||||
|
@ -289,7 +290,7 @@ mod tests {
|
|||
White Pawn on E5,
|
||||
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 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