2024-02-03 15:17:40 -08:00
|
|
|
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
|
2024-04-26 09:50:42 -04:00
|
|
|
use crate::{defs::Kind, Move, PromotionShape};
|
|
|
|
use chessfriend_board::{castle, en_passant::EnPassant};
|
2024-02-09 20:00:47 -08:00
|
|
|
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square};
|
|
|
|
use std::result::Result as StdResult;
|
2025-05-19 14:18:31 -07:00
|
|
|
use thiserror::Error;
|
2024-02-03 15:17:40 -08:00
|
|
|
|
2024-02-09 20:00:47 -08:00
|
|
|
pub type Result = std::result::Result<Move, Error>;
|
2024-02-13 11:07:49 -07:00
|
|
|
type EncodedMoveResult = std::result::Result<u16, Error>;
|
2024-02-09 20:00:47 -08:00
|
|
|
|
2025-05-19 14:18:31 -07:00
|
|
|
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub enum Error {
|
2025-05-19 14:18:31 -07:00
|
|
|
#[error("no origin square")]
|
2024-02-09 20:00:47 -08:00
|
|
|
MissingOriginSquare,
|
2025-05-19 14:18:31 -07:00
|
|
|
#[error("no target square")]
|
2024-02-09 20:00:47 -08:00
|
|
|
MissingTargetSquare,
|
2025-05-19 14:18:31 -07:00
|
|
|
#[error("no capture square")]
|
2024-02-09 20:00:47 -08:00
|
|
|
MissingCaptureSquare,
|
2025-05-19 14:18:31 -07:00
|
|
|
#[error("invalid en passant square")]
|
2024-02-09 20:00:47 -08:00
|
|
|
InvalidEnPassantSquare,
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
const MASK: u16 = 0b111_111;
|
|
|
|
|
|
|
|
fn build_move_bits(origin_square: Square, target_square: Square) -> u16 {
|
|
|
|
(origin_square as u16 & MASK) << 4 | (target_square as u16 & MASK) << 10
|
|
|
|
}
|
|
|
|
|
2024-02-09 20:00:47 -08:00
|
|
|
pub trait Style {
|
|
|
|
fn origin_square(&self) -> Option<Square> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn target_square(&self) -> Option<Square> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
fn move_bits(&self) -> EncodedMoveResult {
|
2024-02-13 11:07:49 -07:00
|
|
|
let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)?;
|
|
|
|
let target_square = self.target_square().ok_or(Error::MissingTargetSquare)?;
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
Ok(build_move_bits(origin_square, target_square))
|
2024-02-13 11:07:49 -07:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
unsafe fn move_bits_unchecked(&self) -> u16 {
|
2024-02-13 11:07:49 -07:00
|
|
|
let origin_square = self.origin_square().unwrap();
|
|
|
|
let target_square = self.target_square().unwrap();
|
2024-02-09 20:00:47 -08:00
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
build_move_bits(origin_square, target_square)
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
}
|
2024-02-03 15:17:40 -08:00
|
|
|
|
2024-02-13 11:03:28 -07:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2024-02-03 15:17:40 -08:00
|
|
|
pub struct Builder<S: Style> {
|
|
|
|
style: S,
|
|
|
|
}
|
|
|
|
|
2024-02-13 11:03:28 -07:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2024-02-03 15:17:40 -08:00
|
|
|
pub struct Null;
|
|
|
|
|
2024-02-13 11:03:28 -07:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2024-02-03 15:17:40 -08:00
|
|
|
pub struct Push {
|
|
|
|
from: Option<Square>,
|
|
|
|
to: Option<Square>,
|
|
|
|
}
|
|
|
|
|
2024-02-13 11:03:28 -07:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub struct DoublePush {
|
|
|
|
from: Square,
|
|
|
|
to: Square,
|
|
|
|
}
|
|
|
|
|
2024-02-13 11:03:28 -07:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2024-02-03 15:17:40 -08:00
|
|
|
pub struct Capture {
|
|
|
|
push: Push,
|
|
|
|
capture: Option<Square>,
|
|
|
|
}
|
|
|
|
|
2024-02-13 11:03:28 -07:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub struct EnPassantCapture {
|
|
|
|
push: Push,
|
2024-02-25 12:38:55 -08:00
|
|
|
capture: Option<EnPassant>,
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
|
2024-02-13 11:03:28 -07:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub struct Promotion<S> {
|
|
|
|
style: S,
|
|
|
|
promotion: PromotionShape,
|
|
|
|
}
|
|
|
|
|
2024-02-13 11:03:28 -07:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2024-02-03 15:17:40 -08:00
|
|
|
pub struct Castle {
|
2024-02-25 12:38:55 -08:00
|
|
|
color: Color,
|
2024-02-03 15:17:40 -08:00
|
|
|
castle: castle::Castle,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Style for Null {}
|
2024-02-09 20:00:47 -08:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 12:38:55 -08:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|
2024-02-03 15:17:40 -08:00
|
|
|
|
2024-02-09 20:00:47 -08:00
|
|
|
impl Style for DoublePush {
|
|
|
|
fn origin_square(&self) -> Option<Square> {
|
|
|
|
Some(self.from)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn target_square(&self) -> Option<Square> {
|
|
|
|
Some(self.to)
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
fn move_bits(&self) -> StdResult<u16, Error> {
|
|
|
|
Ok(
|
|
|
|
Kind::DoublePush as u16
|
|
|
|
| (self.from as u16 & MASK) << 4
|
|
|
|
| (self.to as u16 & MASK) << 10,
|
|
|
|
)
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Style for EnPassantCapture {
|
|
|
|
fn origin_square(&self) -> Option<Square> {
|
|
|
|
self.push.from
|
|
|
|
}
|
|
|
|
|
|
|
|
fn target_square(&self) -> Option<Square> {
|
|
|
|
self.push.to
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
fn move_bits(&self) -> EncodedMoveResult {
|
2024-02-13 11:07:49 -07:00
|
|
|
let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)?;
|
|
|
|
let target_square = self.target_square().ok_or(Error::MissingTargetSquare)?;
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
Ok(build_move_bits(origin_square, target_square))
|
2024-02-13 11:07:49 -07:00
|
|
|
}
|
|
|
|
}
|
2024-02-09 20:00:47 -08:00
|
|
|
|
|
|
|
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> {
|
2025-05-08 17:37:51 -07:00
|
|
|
fn move_bits(&self) -> StdResult<u16, Error> {
|
2024-02-09 20:00:47 -08:00
|
|
|
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
|
2025-05-08 17:37:51 -07:00
|
|
|
| (origin_square & MASK << 4)
|
|
|
|
| (target_square & MASK << 10))
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Promotion<Capture> {
|
2025-05-08 17:37:51 -07:00
|
|
|
fn move_bits(&self) -> StdResult<u16, Error> {
|
2024-02-09 20:00:47 -08:00
|
|
|
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
|
2025-05-08 17:37:51 -07:00
|
|
|
| (origin_square & MASK) << 4
|
|
|
|
| (target_square & MASK) << 10)
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-03 15:17:40 -08:00
|
|
|
impl Builder<Null> {
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:40 -08:00
|
|
|
pub fn new() -> Self {
|
2025-05-08 17:37:51 -07:00
|
|
|
Self::default()
|
2024-02-03 15:17:40 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-13 11:07:49 -07:00
|
|
|
pub fn push(piece: &PlacedPiece) -> Builder<Push> {
|
2024-02-03 15:17:40 -08:00
|
|
|
Builder {
|
|
|
|
style: Push {
|
2025-05-08 17:37:51 -07:00
|
|
|
from: Some(piece.square),
|
2024-02-13 11:07:49 -07:00
|
|
|
to: None,
|
2024-02-03 15:17:40 -08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-09 20:00:47 -08:00
|
|
|
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 },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-25 12:38:55 -08:00
|
|
|
pub fn castling(color: Color, castle: castle::Castle) -> Builder<Castle> {
|
2024-02-03 15:17:40 -08:00
|
|
|
Builder {
|
2024-02-25 12:38:55 -08:00
|
|
|
style: Castle { color, castle },
|
2024-02-03 15:17:40 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub fn capturing_piece(piece: &PlacedPiece, capturing: &PlacedPiece) -> Builder<Capture> {
|
2025-05-08 17:37:51 -07:00
|
|
|
Self::push(piece).capturing_piece(capturing)
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-25 08:57:16 -08:00
|
|
|
pub fn from(&self, square: Square) -> Builder<Push> {
|
2024-02-09 20:00:47 -08:00
|
|
|
Builder {
|
|
|
|
style: Push {
|
|
|
|
from: Some(square),
|
|
|
|
to: None,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:40 -08:00
|
|
|
pub fn build(&self) -> Move {
|
|
|
|
Move(0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
impl Default for Builder<Null> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self { style: Null }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-03 15:17:40 -08:00
|
|
|
impl Builder<Push> {
|
2024-02-09 20:00:47 -08:00
|
|
|
pub fn from(&mut self, square: Square) -> &mut Self {
|
2024-02-03 15:17:40 -08:00
|
|
|
self.style.from = Some(square);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-02-25 08:57:16 -08:00
|
|
|
pub fn to(&mut self, square: Square) -> &mut Self {
|
2024-02-03 15:17:40 -08:00
|
|
|
self.style.to = Some(square);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-25 08:57:16 -08:00
|
|
|
pub fn capturing_on(&self, square: Square) -> Builder<Capture> {
|
|
|
|
let mut style = self.style.clone();
|
2024-02-09 20:00:47 -08:00
|
|
|
style.to = Some(square);
|
|
|
|
|
2024-02-03 15:17:40 -08:00
|
|
|
Builder {
|
|
|
|
style: Capture {
|
2024-02-09 20:00:47 -08:00
|
|
|
push: style,
|
|
|
|
capture: Some(square),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-25 12:38:55 -08:00
|
|
|
pub fn capturing_en_passant_on(&self, target_square: Square) -> Builder<EnPassantCapture> {
|
|
|
|
match EnPassant::from_target_square(target_square) {
|
2024-02-13 11:05:02 -07:00
|
|
|
Some(en_passant) => {
|
2024-02-25 08:57:16 -08:00
|
|
|
let mut style = self.style.clone();
|
2024-02-13 11:05:02 -07:00
|
|
|
style.to = Some(en_passant.target_square());
|
|
|
|
|
|
|
|
Builder {
|
|
|
|
style: EnPassantCapture {
|
|
|
|
push: style,
|
2024-02-25 12:38:55 -08:00
|
|
|
capture: Some(en_passant),
|
2024-02-13 11:05:02 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => todo!(),
|
2024-02-03 15:17:40 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-25 08:57:16 -08:00
|
|
|
pub fn capturing_piece(&self, piece: &PlacedPiece) -> Builder<Capture> {
|
2024-02-03 15:17:40 -08:00
|
|
|
Builder {
|
|
|
|
style: Capture {
|
2024-02-25 08:57:16 -08:00
|
|
|
push: self.style.clone(),
|
2025-05-08 17:37:51 -07:00
|
|
|
capture: Some(piece.square),
|
2024-02-03 15:17:40 -08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-02-09 20:00:47 -08:00
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-25 08:57:16 -08:00
|
|
|
pub fn promoting_to(&self, shape: PromotionShape) -> Builder<Promotion<Push>> {
|
2024-02-09 20:00:47 -08:00
|
|
|
Builder {
|
|
|
|
style: Promotion {
|
2024-02-25 08:57:16 -08:00
|
|
|
style: self.style.clone(),
|
2024-02-09 20:00:47 -08:00
|
|
|
promotion: shape,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn build(&self) -> Result {
|
2025-05-08 17:37:51 -07:00
|
|
|
Ok(Move(Kind::Quiet as u16 | self.style.move_bits()?))
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
2024-02-03 15:17:40 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Builder<Castle> {
|
2024-02-09 20:00:47 -08:00
|
|
|
fn bits(&self) -> u16 {
|
|
|
|
let bits = match self.style.castle {
|
|
|
|
castle::Castle::KingSide => Kind::KingSideCastle,
|
|
|
|
castle::Castle::QueenSide => Kind::QueenSideCastle,
|
|
|
|
};
|
|
|
|
|
|
|
|
bits as u16
|
|
|
|
}
|
|
|
|
|
2024-02-25 12:38:55 -08:00
|
|
|
pub fn build(&self) -> Result {
|
2025-05-08 17:37:51 -07:00
|
|
|
Ok(Move(self.bits() | self.style.move_bits()?))
|
2024-02-03 15:17:40 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-09 20:00:47 -08:00
|
|
|
impl Builder<Capture> {
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub fn promoting_to(self, shape: PromotionShape) -> Builder<Promotion<Capture>> {
|
|
|
|
Builder {
|
|
|
|
style: Promotion {
|
|
|
|
style: self.style,
|
|
|
|
promotion: shape,
|
|
|
|
},
|
2024-02-03 15:17:40 -08:00
|
|
|
}
|
|
|
|
}
|
2024-02-09 20:00:47 -08:00
|
|
|
|
|
|
|
pub fn build(&self) -> Result {
|
2025-05-08 17:37:51 -07:00
|
|
|
Ok(Move(Kind::Capture as u16 | self.style.move_bits()?))
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Builder<DoublePush> {
|
|
|
|
pub fn build(&self) -> Result {
|
2025-05-08 17:37:51 -07:00
|
|
|
Ok(Move(Kind::DoublePush as u16 | self.style.move_bits()?))
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Builder<EnPassantCapture> {
|
2025-05-08 17:37:51 -07:00
|
|
|
/// Builds an en passant move.
|
|
|
|
///
|
|
|
|
/// ## Safety
|
|
|
|
///
|
|
|
|
/// This method builds without doing error checking.
|
|
|
|
#[must_use]
|
2024-02-13 11:07:49 -07:00
|
|
|
pub unsafe fn build_unchecked(&self) -> Move {
|
2025-05-08 17:37:51 -07:00
|
|
|
Move(Kind::EnPassantCapture as u16 | self.style.move_bits_unchecked())
|
2024-02-13 11:07:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn build(&self) -> Result {
|
2024-02-09 20:00:47 -08:00
|
|
|
Ok(Move(
|
2025-05-08 17:37:51 -07:00
|
|
|
Kind::EnPassantCapture as u16 | self.style.move_bits()?,
|
2024-02-09 20:00:47 -08:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Builder<Promotion<Push>> {
|
|
|
|
pub fn build(&self) -> Result {
|
2025-05-08 17:37:51 -07:00
|
|
|
Ok(Move(self.style.move_bits()?))
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Builder<Promotion<Capture>> {
|
|
|
|
pub fn build(&self) -> Result {
|
2025-05-08 17:37:51 -07:00
|
|
|
Ok(Move(self.style.move_bits()?))
|
2024-02-09 20:00:47 -08:00
|
|
|
}
|
2024-02-03 15:17:40 -08:00
|
|
|
}
|