UCI uses a move format it calls "long algebraic". They look like either "e2e4" for a regular move, or "h7h8q" for a promotion. Implement parsing these move strings as a two step process. First define an AlgebraicMoveComponents struct in the moves crate that implements FromStr. This struct reads out an origin square, a target square, and an optional promotion shape from a string. Then, implement a pair of methods on Position that take the move components struct and return a fully encoded Move struct with them. This process is required because the algebraic string is not enough by itself to know what kind of move was made. The current position is required to understand that. Implement Shape::is_promotable(). Add a NULL move to the Move struct. I'm not sure what this is used for yet, but the UCI spec specifically calls out a string that encodes a null move, so I added it. It may end up being unused! Do a little bit of cleanup in the core crate as well. Use deeper imports (import std::fmt instead of requring the fully qualified type path) and remove some unnecessary From implementations. This commit is also the first instance (I think) of defining an errors module in lib.rs for the core crate that holds the various error types the crate exports.
163 lines
4 KiB
Rust
163 lines
4 KiB
Rust
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
use std::{array, fmt, slice, str::FromStr};
|
|
use thiserror::Error;
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum Shape {
|
|
Pawn = 0,
|
|
Knight = 1,
|
|
Bishop = 2,
|
|
Rook = 3,
|
|
Queen = 4,
|
|
King = 5,
|
|
}
|
|
|
|
impl Shape {
|
|
/// Number of piece shapes
|
|
pub const NUM: usize = 6;
|
|
|
|
/// A slice of all piece shapes
|
|
pub const ALL: [Shape; Self::NUM] = [
|
|
Shape::Pawn,
|
|
Shape::Knight,
|
|
Shape::Bishop,
|
|
Shape::Rook,
|
|
Shape::Queen,
|
|
Shape::King,
|
|
];
|
|
|
|
const PROMOTABLE_SHAPES: [Shape; 4] = [Shape::Queen, Shape::Rook, Shape::Bishop, Shape::Knight];
|
|
|
|
pub fn iter() -> slice::Iter<'static, Self> {
|
|
Shape::ALL.iter()
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn into_iter() -> array::IntoIter<Self, { Self::NUM }> {
|
|
Shape::ALL.into_iter()
|
|
}
|
|
|
|
/// An iterator over the shapes that a pawn can promote to
|
|
pub fn promotable() -> slice::Iter<'static, Shape> {
|
|
Self::PROMOTABLE_SHAPES.iter()
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn to_ascii(self) -> char {
|
|
match self {
|
|
Shape::Pawn => 'P',
|
|
Shape::Knight => 'N',
|
|
Shape::Bishop => 'B',
|
|
Shape::Rook => 'R',
|
|
Shape::Queen => 'Q',
|
|
Shape::King => 'K',
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn name(self) -> &'static str {
|
|
match self {
|
|
Shape::Pawn => "pawn",
|
|
Shape::Knight => "knight",
|
|
Shape::Bishop => "bishop",
|
|
Shape::Rook => "rook",
|
|
Shape::Queen => "queen",
|
|
Shape::King => "king",
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn is_promotable(&self) -> bool {
|
|
Self::PROMOTABLE_SHAPES.contains(self)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum Slider {
|
|
Bishop,
|
|
Rook,
|
|
Queen,
|
|
}
|
|
|
|
impl From<Slider> for Shape {
|
|
fn from(value: Slider) -> Self {
|
|
match value {
|
|
Slider::Bishop => Shape::Bishop,
|
|
Slider::Rook => Shape::Rook,
|
|
Slider::Queen => Shape::Queen,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Shape> for Slider {
|
|
type Error = ();
|
|
|
|
fn try_from(value: Shape) -> Result<Self, Self::Error> {
|
|
match value {
|
|
Shape::Bishop => Ok(Slider::Bishop),
|
|
Shape::Rook => Ok(Slider::Rook),
|
|
Shape::Queen => Ok(Slider::Queen),
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
|
#[error("no matching piece shape for character '{0:?}'")]
|
|
pub struct ShapeFromCharError(char);
|
|
|
|
impl TryFrom<char> for Shape {
|
|
type Error = ShapeFromCharError;
|
|
|
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
|
match value {
|
|
'P' | 'p' => Ok(Shape::Pawn),
|
|
'N' | 'n' => Ok(Shape::Knight),
|
|
'B' | 'b' => Ok(Shape::Bishop),
|
|
'R' | 'r' => Ok(Shape::Rook),
|
|
'Q' | 'q' => Ok(Shape::Queen),
|
|
'K' | 'k' => Ok(Shape::King),
|
|
_ => Err(ShapeFromCharError(value)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
|
#[error("no matching piece shape for string")]
|
|
pub struct ParseShapeError;
|
|
|
|
impl FromStr for Shape {
|
|
type Err = ParseShapeError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s.to_lowercase().as_str() {
|
|
"p" | "pawn" => Ok(Shape::Pawn),
|
|
"n" | "knight" => Ok(Shape::Knight),
|
|
"b" | "bishop" => Ok(Shape::Bishop),
|
|
"r" | "rook" => Ok(Shape::Rook),
|
|
"q" | "queen" => Ok(Shape::Queen),
|
|
"k" | "king" => Ok(Shape::King),
|
|
_ => Err(ParseShapeError),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&Shape> for char {
|
|
fn from(shape: &Shape) -> char {
|
|
char::from(*shape)
|
|
}
|
|
}
|
|
|
|
impl From<Shape> for char {
|
|
fn from(shape: Shape) -> char {
|
|
shape.to_ascii()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Shape {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let self_char: char = self.into();
|
|
write!(f, "{self_char}")
|
|
}
|
|
}
|