[explorer, moves, core] Improve error handling in explorer
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.
This commit is contained in:
parent
72eeba84ba
commit
9010f1e9c2
12 changed files with 331 additions and 226 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -50,6 +50,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.4.2"
|
version = "2.4.2"
|
||||||
|
@ -188,6 +194,7 @@ checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
|
||||||
name = "explorer"
|
name = "explorer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"chessfriend_board",
|
"chessfriend_board",
|
||||||
"chessfriend_core",
|
"chessfriend_core",
|
||||||
"chessfriend_moves",
|
"chessfriend_moves",
|
||||||
|
@ -195,6 +202,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"shlex",
|
"shlex",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{errors::TryFromCharError, try_from_string, Direction};
|
use crate::Direction;
|
||||||
use std::fmt;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
|
@ -48,31 +48,65 @@ impl Color {
|
||||||
pub const fn next(&self) -> Color {
|
pub const fn next(&self) -> Color {
|
||||||
Self::ALL[((*self as usize) + 1) % Self::NUM]
|
Self::ALL[((*self as usize) + 1) % Self::NUM]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Color::White => "white",
|
||||||
|
Color::Black => "black",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Color {
|
impl std::fmt::Display for Color {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
match self {
|
match self {
|
||||||
Color::White => "White",
|
Color::White => "white",
|
||||||
Color::Black => "Black",
|
Color::Black => "black",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
||||||
|
#[error("no matching color for character '{0}'")]
|
||||||
|
pub struct ColorFromCharError(char);
|
||||||
|
|
||||||
impl TryFrom<char> for Color {
|
impl TryFrom<char> for Color {
|
||||||
type Error = TryFromCharError;
|
type Error = ColorFromCharError;
|
||||||
|
|
||||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
'w' | 'W' => Ok(Color::White),
|
'w' | 'W' => Ok(Color::White),
|
||||||
'b' | 'B' => Ok(Color::Black),
|
'b' | 'B' => Ok(Color::Black),
|
||||||
_ => Err(TryFromCharError),
|
_ => Err(ColorFromCharError(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try_from_string!(Color);
|
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
||||||
|
#[error("no matching color for string")]
|
||||||
|
pub struct ColorFromStrError;
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Color {
|
||||||
|
type Error = ColorFromStrError;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
"w" | "white" => Ok(Color::White),
|
||||||
|
"b" | "black" => Ok(Color::Black),
|
||||||
|
_ => Err(ColorFromStrError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Color {
|
||||||
|
type Err = ColorFromStrError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Self::try_from(s.to_lowercase().as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
use std::{fmt, str::FromStr};
|
use std::fmt;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
macro_rules! try_from_integer {
|
macro_rules! try_from_integer {
|
||||||
|
@ -10,6 +10,7 @@ macro_rules! try_from_integer {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn try_from(value: $int_type) -> Result<Self, Self::Error> {
|
fn try_from(value: $int_type) -> Result<Self, Self::Error> {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
Self::try_from(value as u8)
|
Self::try_from(value as u8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +56,7 @@ macro_rules! range_bound_struct {
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl $type {
|
impl $type {
|
||||||
|
$vis const NUM: usize = $max;
|
||||||
$vis const FIRST: $type = $type(0);
|
$vis const FIRST: $type = $type(0);
|
||||||
$vis const LAST: $type = $type($max - 1);
|
$vis const LAST: $type = $type($max - 1);
|
||||||
}
|
}
|
||||||
|
@ -148,7 +150,7 @@ impl File {
|
||||||
pub const G: File = File(6);
|
pub const G: File = File(6);
|
||||||
pub const H: File = File(7);
|
pub const H: File = File(7);
|
||||||
|
|
||||||
pub const ALL: [File; 8] = [
|
pub const ALL: [File; File::NUM] = [
|
||||||
File::A,
|
File::A,
|
||||||
File::B,
|
File::B,
|
||||||
File::C,
|
File::C,
|
||||||
|
@ -173,7 +175,7 @@ impl Rank {
|
||||||
pub const SEVEN: Rank = Rank(6);
|
pub const SEVEN: Rank = Rank(6);
|
||||||
pub const EIGHT: Rank = Rank(7);
|
pub const EIGHT: Rank = Rank(7);
|
||||||
|
|
||||||
pub const ALL: [Rank; 8] = [
|
pub const ALL: [Rank; Self::NUM] = [
|
||||||
Rank::ONE,
|
Rank::ONE,
|
||||||
Rank::TWO,
|
Rank::TWO,
|
||||||
Rank::THREE,
|
Rank::THREE,
|
||||||
|
@ -344,7 +346,27 @@ pub enum ParseSquareError {
|
||||||
FileError(#[from] ParseFileError),
|
FileError(#[from] ParseFileError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Square {
|
impl TryFrom<&str> for Square {
|
||||||
|
type Error = ParseSquareError;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
let mut chars = value.chars();
|
||||||
|
|
||||||
|
let file: File = chars
|
||||||
|
.next()
|
||||||
|
.and_then(|c| c.try_into().ok())
|
||||||
|
.ok_or(ParseSquareError::FileError(ParseFileError))?;
|
||||||
|
|
||||||
|
let rank: Rank = chars
|
||||||
|
.next()
|
||||||
|
.and_then(|c| c.try_into().ok())
|
||||||
|
.ok_or(ParseSquareError::RankError(ParseRankError))?;
|
||||||
|
|
||||||
|
Ok(Square::from_file_rank(file, rank))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Square {
|
||||||
type Err = ParseSquareError;
|
type Err = ParseSquareError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
@ -377,10 +399,13 @@ impl std::str::FromStr for Rank {
|
||||||
.nth(0)
|
.nth(0)
|
||||||
.ok_or(ParseRankError)
|
.ok_or(ParseRankError)
|
||||||
.map(|ch| ch.to_ascii_lowercase())?;
|
.map(|ch| ch.to_ascii_lowercase())?;
|
||||||
let offset = 'a' as usize - (ch as usize);
|
|
||||||
let rank = Rank::ALL[offset];
|
|
||||||
|
|
||||||
Ok(rank)
|
let offset = 'a' as usize - (ch as usize);
|
||||||
|
if offset >= Rank::ALL.len() {
|
||||||
|
return Err(ParseRankError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Rank::ALL[offset])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,10 +422,13 @@ impl std::str::FromStr for File {
|
||||||
.nth(0)
|
.nth(0)
|
||||||
.ok_or(ParseFileError)
|
.ok_or(ParseFileError)
|
||||||
.map(|ch| ch.to_ascii_lowercase())?;
|
.map(|ch| ch.to_ascii_lowercase())?;
|
||||||
let offset = '1' as usize - (ch as usize);
|
|
||||||
let file = File::ALL[offset];
|
|
||||||
|
|
||||||
Ok(file)
|
let offset = '1' as usize - (ch as usize);
|
||||||
|
if offset >= File::ALL.len() {
|
||||||
|
return Err(ParseFileError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(File::ALL[offset])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,12 +517,12 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bad_algebraic_input() {
|
fn bad_algebraic_input() {
|
||||||
assert!(Square::from_algebraic_str("a0").is_err());
|
assert!("a0".parse::<Square>().is_err());
|
||||||
assert!(Square::from_algebraic_str("j3").is_err());
|
assert!("j3".parse::<Square>().is_err());
|
||||||
assert!(Square::from_algebraic_str("a11").is_err());
|
assert!("a11".parse::<Square>().is_err());
|
||||||
assert!(Square::from_algebraic_str("b-1").is_err());
|
assert!("b-1".parse::<Square>().is_err());
|
||||||
assert!(Square::from_algebraic_str("a 1").is_err());
|
assert!("a 1".parse::<Square>().is_err());
|
||||||
assert!(Square::from_algebraic_str("").is_err());
|
assert!("".parse::<Square>().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
|
||||||
pub struct TryFromCharError;
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
|
||||||
pub struct TryFromStrError;
|
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
pub mod colors;
|
pub mod colors;
|
||||||
pub mod coordinates;
|
pub mod coordinates;
|
||||||
pub mod errors;
|
|
||||||
pub mod pieces;
|
pub mod pieces;
|
||||||
|
pub mod shapes;
|
||||||
|
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub use colors::Color;
|
pub use colors::Color;
|
||||||
pub use coordinates::{Direction, File, Rank, Square};
|
pub use coordinates::{Direction, File, Rank, Square};
|
||||||
pub use pieces::{Piece, PlacedPiece, Shape};
|
pub use pieces::{Piece, PlacedPiece};
|
||||||
|
pub use shapes::Shape;
|
||||||
|
|
|
@ -1,26 +1,5 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! try_from_string {
|
|
||||||
($type:ty) => {
|
|
||||||
try_from_string!($type, &str);
|
|
||||||
try_from_string!($type, &String);
|
|
||||||
};
|
|
||||||
($type:ty, $from_type:ty) => {
|
|
||||||
impl TryFrom<$from_type> for $type {
|
|
||||||
type Error = $crate::errors::TryFromStrError;
|
|
||||||
|
|
||||||
fn try_from(value: $from_type) -> Result<Self, Self::Error> {
|
|
||||||
let first_char = value
|
|
||||||
.chars()
|
|
||||||
.nth(0)
|
|
||||||
.ok_or($crate::errors::TryFromStrError)?;
|
|
||||||
Self::try_from(first_char).map_err(|_| $crate::errors::TryFromStrError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! piece {
|
macro_rules! piece {
|
||||||
($color:ident $shape:ident) => {
|
($color:ident $shape:ident) => {
|
||||||
|
|
|
@ -1,124 +1,10 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{errors::TryFromCharError, try_from_string, Color, Square};
|
mod display;
|
||||||
use std::{array, fmt, slice};
|
|
||||||
|
|
||||||
trait _Shape {
|
pub use self::display::{PieceDisplay, PieceDisplayStyle};
|
||||||
fn symbol(&self) -> char;
|
|
||||||
fn index(&self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! shape {
|
use crate::{Color, Shape, Square};
|
||||||
($name:ident, $index:expr, $symbol:expr) => {
|
|
||||||
struct $name;
|
|
||||||
|
|
||||||
impl _Shape for $name {
|
|
||||||
fn symbol(&self) -> char {
|
|
||||||
$symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(&self) -> usize {
|
|
||||||
$index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
shape!(Pawn, 0, 'P');
|
|
||||||
shape!(Knight, 1, 'K');
|
|
||||||
shape!(Bishop, 2, 'B');
|
|
||||||
shape!(Rook, 3, 'R');
|
|
||||||
shape!(Queen, 4, 'Q');
|
|
||||||
shape!(King, 5, 'K');
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn iter() -> slice::Iter<'static, Self> {
|
|
||||||
Shape::ALL.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
|
||||||
const PROMOTABLE_SHAPES: [Shape; 4] =
|
|
||||||
[Shape::Queen, Shape::Rook, Shape::Bishop, Shape::Knight];
|
|
||||||
|
|
||||||
PROMOTABLE_SHAPES.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
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',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<char> for Shape {
|
|
||||||
type Error = TryFromCharError;
|
|
||||||
|
|
||||||
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(TryFromCharError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try_from_string!(Shape);
|
|
||||||
|
|
||||||
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 fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let self_char: char = self.into();
|
|
||||||
write!(f, "{self_char}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct Piece {
|
pub struct Piece {
|
||||||
|
@ -195,9 +81,16 @@ impl Piece {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Piece {
|
impl std::fmt::Display for Piece {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.to_unicode())
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
PieceDisplay {
|
||||||
|
piece: *self,
|
||||||
|
style: PieceDisplayStyle::default()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,8 +161,8 @@ impl PlacedPiece {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for PlacedPiece {
|
impl std::fmt::Display for PlacedPiece {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}{}", self.piece, self.square)
|
write!(f, "{}{}", self.piece, self.square)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
28
core/src/pieces/display.rs
Normal file
28
core/src/pieces/display.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use super::Piece;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum PieceDisplayStyle {
|
||||||
|
#[default]
|
||||||
|
Unicode,
|
||||||
|
ASCII,
|
||||||
|
LongForm,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PieceDisplay {
|
||||||
|
pub(super) piece: Piece,
|
||||||
|
pub(super) style: PieceDisplayStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for PieceDisplay {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self.style {
|
||||||
|
PieceDisplayStyle::Unicode => write!(f, "{}", self.piece.to_unicode()),
|
||||||
|
PieceDisplayStyle::ASCII => write!(f, "{}", self.piece.to_ascii()),
|
||||||
|
PieceDisplayStyle::LongForm => {
|
||||||
|
write!(f, "{} {}", self.piece.color.name(), self.piece.shape.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
core/src/shapes.rs
Normal file
137
core/src/shapes.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use std::{array, slice};
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
|
||||||
|
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> {
|
||||||
|
const PROMOTABLE_SHAPES: [Shape; 4] =
|
||||||
|
[Shape::Queen, Shape::Rook, Shape::Bishop, Shape::Knight];
|
||||||
|
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 ShapeFromStrError;
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Shape {
|
||||||
|
type Error = ShapeFromStrError;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value.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(ShapeFromStrError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Shape {
|
||||||
|
type Err = ShapeFromStrError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Self::try_from(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 std::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}")
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.98"
|
||||||
chessfriend_core = { path = "../core" }
|
chessfriend_core = { path = "../core" }
|
||||||
chessfriend_board = { path = "../board" }
|
chessfriend_board = { path = "../board" }
|
||||||
chessfriend_moves = { path = "../moves" }
|
chessfriend_moves = { path = "../moves" }
|
||||||
|
@ -13,3 +14,4 @@ chessfriend_position = { path = "../position" }
|
||||||
clap = { version = "4.4.12", features = ["derive"] }
|
clap = { version = "4.4.12", features = ["derive"] }
|
||||||
rustyline = "13.0.0"
|
rustyline = "13.0.0"
|
||||||
shlex = "1.2.0"
|
shlex = "1.2.0"
|
||||||
|
thiserror = "2"
|
||||||
|
|
|
@ -4,9 +4,11 @@ use chessfriend_board::{fen::FromFenStr, Board};
|
||||||
use chessfriend_core::{Color, Piece, Shape, Square};
|
use chessfriend_core::{Color, Piece, Shape, Square};
|
||||||
use chessfriend_moves::Builder as MoveBuilder;
|
use chessfriend_moves::Builder as MoveBuilder;
|
||||||
use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove};
|
use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove};
|
||||||
|
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
struct CommandResult {
|
struct CommandResult {
|
||||||
should_continue: bool,
|
should_continue: bool,
|
||||||
|
@ -79,11 +81,17 @@ fn command_line() -> Command {
|
||||||
.subcommand(Command::new("starting").about("Reset the board to the starting position"))
|
.subcommand(Command::new("starting").about("Reset the board to the starting position"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
|
#[derive(Clone, Debug, Error, Eq, PartialEq)]
|
||||||
let args = shlex::split(line).ok_or("error: Invalid quoting")?;
|
enum CommandHandlingError<'a> {
|
||||||
let matches = command_line()
|
#[error("lexer error")]
|
||||||
.try_get_matches_from(args)
|
LexerError,
|
||||||
.map_err(|e| e.to_string())?;
|
#[error("missing {0} argument")]
|
||||||
|
MissingArgument(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn respond(line: &str, state: &mut State) -> anyhow::Result<CommandResult> {
|
||||||
|
let args = shlex::split(line).ok_or(CommandHandlingError::LexerError)?;
|
||||||
|
let matches = command_line().try_get_matches_from(args)?;
|
||||||
|
|
||||||
let mut result = CommandResult::default();
|
let mut result = CommandResult::default();
|
||||||
|
|
||||||
|
@ -94,68 +102,53 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
|
||||||
result.should_print_position = false;
|
result.should_print_position = false;
|
||||||
}
|
}
|
||||||
Some(("fen", _matches)) => {
|
Some(("fen", _matches)) => {
|
||||||
println!(
|
println!("{}", state.position.to_fen_str()?);
|
||||||
"{}",
|
|
||||||
state
|
|
||||||
.position
|
|
||||||
.to_fen_str()
|
|
||||||
.map_err(|_| "error: Unable to generate FEN for current position")?
|
|
||||||
);
|
|
||||||
|
|
||||||
result.should_print_position = false;
|
result.should_print_position = false;
|
||||||
}
|
}
|
||||||
Some(("make", matches)) => {
|
Some(("make", matches)) => {
|
||||||
let from_square = Square::from_algebraic_str(
|
let from_square = Square::from_algebraic_str(
|
||||||
matches.get_one::<String>("from").ok_or("Missing square")?,
|
matches
|
||||||
)
|
.get_one::<String>("from")
|
||||||
.map_err(|_| "Error: invalid square specifier")?;
|
.ok_or(CommandHandlingError::MissingArgument("from"))?,
|
||||||
|
)?;
|
||||||
|
|
||||||
let to_square = Square::from_algebraic_str(
|
let to_square = Square::from_algebraic_str(
|
||||||
matches.get_one::<String>("to").ok_or("Missing square")?,
|
matches
|
||||||
)
|
.get_one::<String>("to")
|
||||||
.map_err(|_| "Error: invalid square specifier")?;
|
.ok_or(CommandHandlingError::MissingArgument("to"))?,
|
||||||
|
)?;
|
||||||
|
|
||||||
let ply = MoveBuilder::new()
|
let ply = MoveBuilder::new().from(from_square).to(to_square).build()?;
|
||||||
.from(from_square)
|
|
||||||
.to(to_square)
|
|
||||||
.build()
|
|
||||||
.map_err(|err| format!("Error: {err:?}"))?;
|
|
||||||
|
|
||||||
state
|
state.position.make_move(ply, ValidateMove::Yes)?;
|
||||||
.position
|
|
||||||
.make_move(ply, ValidateMove::Yes)
|
|
||||||
.map_err(|err| format!("Error: {err}"))?;
|
|
||||||
}
|
}
|
||||||
Some(("place", matches)) => {
|
Some(("place", matches)) => {
|
||||||
let color = matches
|
let color = matches
|
||||||
.get_one::<String>("color")
|
.get_one::<String>("color")
|
||||||
.ok_or("Missing color descriptor")?;
|
.ok_or(CommandHandlingError::MissingArgument("color"))?;
|
||||||
let color = Color::try_from(color).map_err(|_| "Invalid color descriptor")?;
|
let color = color.parse::<Color>()?;
|
||||||
|
|
||||||
let shape = matches
|
let shape = matches
|
||||||
.get_one::<String>("piece")
|
.get_one::<String>("piece")
|
||||||
.ok_or("Missing piece descriptor")?;
|
.ok_or(CommandHandlingError::MissingArgument("piece"))?;
|
||||||
let shape = Shape::try_from(shape).map_err(|_| "Invalid piece descriptor")?;
|
let shape = shape.parse::<Shape>()?;
|
||||||
|
|
||||||
let square = matches
|
let square = matches
|
||||||
.get_one::<String>("square")
|
.get_one::<String>("square")
|
||||||
.ok_or("Missing square")?;
|
.ok_or(CommandHandlingError::MissingArgument("square"))?;
|
||||||
let square = Square::from_algebraic_str(square)
|
let square = Square::from_algebraic_str(square)?;
|
||||||
.map_err(|_| "Error: invalid square specifier")?;
|
|
||||||
|
|
||||||
let piece = Piece::new(color, shape);
|
let piece = Piece::new(color, shape);
|
||||||
|
|
||||||
state
|
state
|
||||||
.position
|
.position
|
||||||
.place_piece(piece, square, PlacePieceStrategy::default())
|
.place_piece(piece, square, PlacePieceStrategy::default())?;
|
||||||
.map_err(|err| format!("Error: could not place piece: {err:?}"))?;
|
|
||||||
}
|
}
|
||||||
Some(("sight", matches)) => {
|
Some(("sight", matches)) => {
|
||||||
let square = matches
|
let square = matches
|
||||||
.get_one::<String>("square")
|
.get_one::<String>("square")
|
||||||
.ok_or("Missing square")?;
|
.ok_or(CommandHandlingError::MissingArgument("square"))?;
|
||||||
let square = Square::from_algebraic_str(square)
|
let square = square.parse::<Square>()?;
|
||||||
.map_err(|_| "Error: invalid square specifier")?;
|
|
||||||
|
|
||||||
let sight = state.position.sight(square);
|
let sight = state.position.sight(square);
|
||||||
|
|
||||||
|
@ -167,9 +160,8 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
|
||||||
Some(("moves", matches)) => {
|
Some(("moves", matches)) => {
|
||||||
let square = matches
|
let square = matches
|
||||||
.get_one::<String>("square")
|
.get_one::<String>("square")
|
||||||
.ok_or("Missing square")?;
|
.ok_or(CommandHandlingError::MissingArgument("square"))?;
|
||||||
let square = Square::from_algebraic_str(square)
|
let square = square.parse::<Square>()?;
|
||||||
.map_err(|_| "Error: invalid square specifier")?;
|
|
||||||
|
|
||||||
let movement = state.position.movement(square);
|
let movement = state.position.movement(square);
|
||||||
|
|
||||||
|
@ -182,7 +174,7 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
|
||||||
let starting_position = Position::starting();
|
let starting_position = Position::starting();
|
||||||
state.position = starting_position;
|
state.position = starting_position;
|
||||||
}
|
}
|
||||||
Some(("reset", matches)) => do_reset_command(state, matches)?,
|
Some(("reset", matches)) => result = do_reset_command(state, matches)?,
|
||||||
Some((name, _matches)) => unimplemented!("{name}"),
|
Some((name, _matches)) => unimplemented!("{name}"),
|
||||||
None => unreachable!("Subcommand required"),
|
None => unreachable!("Subcommand required"),
|
||||||
}
|
}
|
||||||
|
@ -190,19 +182,24 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_reset_command(state: &mut State, matches: &clap::ArgMatches) -> Result<(), String> {
|
fn do_reset_command(
|
||||||
|
state: &mut State,
|
||||||
|
matches: &clap::ArgMatches,
|
||||||
|
) -> anyhow::Result<CommandResult> {
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
None | Some(("clear", _)) => state.position = Position::empty(),
|
None | Some(("clear", _)) => state.position = Position::empty(),
|
||||||
Some(("starting", _)) => state.position = Position::starting(),
|
Some(("starting", _)) => state.position = Position::starting(),
|
||||||
Some(("fen", matches)) => {
|
Some(("fen", matches)) => {
|
||||||
let fen = matches.get_one::<String>("fen").ok_or("Missing FEN")?;
|
let fen = matches
|
||||||
let board = Board::from_fen_str(fen).map_err(|err| format!("{err}"))?;
|
.get_one::<String>("fen")
|
||||||
|
.ok_or(CommandHandlingError::MissingArgument("fen"))?;
|
||||||
|
let board = Board::from_fen_str(fen)?;
|
||||||
state.position = Position::new(board);
|
state.position = Position::new(board);
|
||||||
}
|
}
|
||||||
Some((name, _matches)) => unimplemented!("{name}"),
|
Some((name, _matches)) => unimplemented!("{name}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(CommandResult::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), String> {
|
fn main() -> Result<(), String> {
|
||||||
|
|
|
@ -4,15 +4,20 @@ use crate::{defs::Kind, Move, PromotionShape};
|
||||||
use chessfriend_board::{castle, en_passant::EnPassant};
|
use chessfriend_board::{castle, en_passant::EnPassant};
|
||||||
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square};
|
use chessfriend_core::{Color, File, PlacedPiece, Rank, Square};
|
||||||
use std::result::Result as StdResult;
|
use std::result::Result as StdResult;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub type Result = std::result::Result<Move, Error>;
|
pub type Result = std::result::Result<Move, Error>;
|
||||||
type EncodedMoveResult = std::result::Result<u16, Error>;
|
type EncodedMoveResult = std::result::Result<u16, Error>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error("no origin square")]
|
||||||
MissingOriginSquare,
|
MissingOriginSquare,
|
||||||
|
#[error("no target square")]
|
||||||
MissingTargetSquare,
|
MissingTargetSquare,
|
||||||
|
#[error("no capture square")]
|
||||||
MissingCaptureSquare,
|
MissingCaptureSquare,
|
||||||
|
#[error("invalid en passant square")]
|
||||||
InvalidEnPassantSquare,
|
InvalidEnPassantSquare,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue