Implement thiserror::Error for a bunch of error types, and remove string errors from the implementation of the command handler in explorer. Clean up parsing of basic types all over the place. Update Cargo files to include thiserror and anyhow.
423 lines
10 KiB
Rust
423 lines
10 KiB
Rust
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
use crate::{defs::Kind, Move, PromotionShape};
|
|
use chessfriend_board::{castle, en_passant::EnPassant};
|
|
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square};
|
|
use std::result::Result as StdResult;
|
|
use thiserror::Error;
|
|
|
|
pub type Result = std::result::Result<Move, Error>;
|
|
type EncodedMoveResult = std::result::Result<u16, Error>;
|
|
|
|
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
|
pub enum Error {
|
|
#[error("no origin square")]
|
|
MissingOriginSquare,
|
|
#[error("no target square")]
|
|
MissingTargetSquare,
|
|
#[error("no capture square")]
|
|
MissingCaptureSquare,
|
|
#[error("invalid en passant square")]
|
|
InvalidEnPassantSquare,
|
|
}
|
|
|
|
const MASK: u16 = 0b111_111;
|
|
|
|
fn build_move_bits(origin_square: Square, target_square: Square) -> u16 {
|
|
(origin_square as u16 & MASK) << 4 | (target_square as u16 & MASK) << 10
|
|
}
|
|
|
|
pub trait Style {
|
|
fn origin_square(&self) -> Option<Square> {
|
|
None
|
|
}
|
|
|
|
fn target_square(&self) -> Option<Square> {
|
|
None
|
|
}
|
|
|
|
fn move_bits(&self) -> EncodedMoveResult {
|
|
let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)?;
|
|
let target_square = self.target_square().ok_or(Error::MissingTargetSquare)?;
|
|
|
|
Ok(build_move_bits(origin_square, target_square))
|
|
}
|
|
|
|
unsafe fn move_bits_unchecked(&self) -> u16 {
|
|
let origin_square = self.origin_square().unwrap();
|
|
let target_square = self.target_square().unwrap();
|
|
|
|
build_move_bits(origin_square, target_square)
|
|
}
|
|
}
|
|
|
|
#[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 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 move_bits(&self) -> StdResult<u16, Error> {
|
|
Ok(
|
|
Kind::DoublePush as u16
|
|
| (self.from as u16 & MASK) << 4
|
|
| (self.to as u16 & MASK) << 10,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Style for EnPassantCapture {
|
|
fn origin_square(&self) -> Option<Square> {
|
|
self.push.from
|
|
}
|
|
|
|
fn target_square(&self) -> Option<Square> {
|
|
self.push.to
|
|
}
|
|
|
|
fn move_bits(&self) -> EncodedMoveResult {
|
|
let origin_square = self.origin_square().ok_or(Error::MissingOriginSquare)?;
|
|
let target_square = self.target_square().ok_or(Error::MissingTargetSquare)?;
|
|
|
|
Ok(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 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 & MASK << 4)
|
|
| (target_square & MASK << 10))
|
|
}
|
|
}
|
|
|
|
impl Promotion<Capture> {
|
|
fn 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 & MASK) << 4
|
|
| (target_square & MASK) << 10)
|
|
}
|
|
}
|
|
|
|
impl Builder<Null> {
|
|
#[must_use]
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn push(piece: &PlacedPiece) -> Builder<Push> {
|
|
Builder {
|
|
style: Push {
|
|
from: Some(piece.square),
|
|
to: None,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn double_push(file: File, color: Color) -> Builder<DoublePush> {
|
|
let (from, to) = match color {
|
|
Color::White => (
|
|
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 },
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn castling(color: Color, castle: castle::Castle) -> Builder<Castle> {
|
|
Builder {
|
|
style: Castle { color, castle },
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn capturing_piece(piece: &PlacedPiece, capturing: &PlacedPiece) -> Builder<Capture> {
|
|
Self::push(piece).capturing_piece(capturing)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn from(&self, square: Square) -> Builder<Push> {
|
|
Builder {
|
|
style: Push {
|
|
from: Some(square),
|
|
to: None,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn build(&self) -> Move {
|
|
Move(0)
|
|
}
|
|
}
|
|
|
|
impl Default for Builder<Null> {
|
|
fn default() -> Self {
|
|
Self { style: Null }
|
|
}
|
|
}
|
|
|
|
impl Builder<Push> {
|
|
pub fn from(&mut self, square: Square) -> &mut Self {
|
|
self.style.from = Some(square);
|
|
self
|
|
}
|
|
|
|
pub fn to(&mut self, square: Square) -> &mut Self {
|
|
self.style.to = Some(square);
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
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),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
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!(),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn capturing_piece(&self, piece: &PlacedPiece) -> Builder<Capture> {
|
|
Builder {
|
|
style: Capture {
|
|
push: self.style.clone(),
|
|
capture: Some(piece.square),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
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.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.move_bits()?))
|
|
}
|
|
}
|
|
|
|
impl Builder<Capture> {
|
|
#[must_use]
|
|
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.move_bits()?))
|
|
}
|
|
}
|
|
|
|
impl Builder<DoublePush> {
|
|
pub fn build(&self) -> Result {
|
|
Ok(Move(Kind::DoublePush as u16 | self.style.move_bits()?))
|
|
}
|
|
}
|
|
|
|
impl Builder<EnPassantCapture> {
|
|
/// Builds an en passant move.
|
|
///
|
|
/// ## Safety
|
|
///
|
|
/// This method builds without doing error checking.
|
|
#[must_use]
|
|
pub unsafe fn build_unchecked(&self) -> Move {
|
|
Move(Kind::EnPassantCapture as u16 | self.style.move_bits_unchecked())
|
|
}
|
|
|
|
pub fn build(&self) -> Result {
|
|
Ok(Move(
|
|
Kind::EnPassantCapture as u16 | self.style.move_bits()?,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl Builder<Promotion<Push>> {
|
|
pub fn build(&self) -> Result {
|
|
Ok(Move(self.style.move_bits()?))
|
|
}
|
|
}
|
|
|
|
impl Builder<Promotion<Capture>> {
|
|
pub fn build(&self) -> Result {
|
|
Ok(Move(self.style.move_bits()?))
|
|
}
|
|
}
|