diff --git a/Cargo.lock b/Cargo.lock index 7414d4e..238efc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "bitflags" version = "2.4.2" @@ -188,6 +194,7 @@ checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" name = "explorer" version = "0.1.0" dependencies = [ + "anyhow", "chessfriend_board", "chessfriend_core", "chessfriend_moves", @@ -195,6 +202,7 @@ dependencies = [ "clap", "rustyline", "shlex", + "thiserror", ] [[package]] diff --git a/core/src/colors.rs b/core/src/colors.rs index fee6558..5cf633b 100644 --- a/core/src/colors.rs +++ b/core/src/colors.rs @@ -1,7 +1,7 @@ // Eryn Wells -use crate::{errors::TryFromCharError, try_from_string, Direction}; -use std::fmt; +use crate::Direction; +use thiserror::Error; #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub enum Color { @@ -48,31 +48,65 @@ impl Color { pub const fn next(&self) -> Color { 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 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl std::fmt::Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { - Color::White => "White", - Color::Black => "Black", + Color::White => "white", + Color::Black => "black", }, ) } } +#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] +#[error("no matching color for character '{0}'")] +pub struct ColorFromCharError(char); + impl TryFrom for Color { - type Error = TryFromCharError; + type Error = ColorFromCharError; fn try_from(value: char) -> Result { match value { 'w' | 'W' => Ok(Color::White), '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 { + 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::try_from(s.to_lowercase().as_str()) + } +} diff --git a/core/src/coordinates.rs b/core/src/coordinates.rs index 0a01ba4..1a13ced 100644 --- a/core/src/coordinates.rs +++ b/core/src/coordinates.rs @@ -1,7 +1,7 @@ // Eryn Wells use crate::Color; -use std::{fmt, str::FromStr}; +use std::fmt; use thiserror::Error; macro_rules! try_from_integer { @@ -10,6 +10,7 @@ macro_rules! try_from_integer { type Error = (); fn try_from(value: $int_type) -> Result { + #[allow(clippy::cast_possible_truncation)] Self::try_from(value as u8) } } @@ -55,6 +56,7 @@ macro_rules! range_bound_struct { #[allow(dead_code)] impl $type { + $vis const NUM: usize = $max; $vis const FIRST: $type = $type(0); $vis const LAST: $type = $type($max - 1); } @@ -148,7 +150,7 @@ impl File { pub const G: File = File(6); pub const H: File = File(7); - pub const ALL: [File; 8] = [ + pub const ALL: [File; File::NUM] = [ File::A, File::B, File::C, @@ -173,7 +175,7 @@ impl Rank { pub const SEVEN: Rank = Rank(6); pub const EIGHT: Rank = Rank(7); - pub const ALL: [Rank; 8] = [ + pub const ALL: [Rank; Self::NUM] = [ Rank::ONE, Rank::TWO, Rank::THREE, @@ -344,7 +346,27 @@ pub enum ParseSquareError { FileError(#[from] ParseFileError), } -impl FromStr for Square { +impl TryFrom<&str> for Square { + type Error = ParseSquareError; + + fn try_from(value: &str) -> Result { + 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; fn from_str(s: &str) -> Result { @@ -377,10 +399,13 @@ impl std::str::FromStr for Rank { .nth(0) .ok_or(ParseRankError) .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) .ok_or(ParseFileError) .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] fn bad_algebraic_input() { - assert!(Square::from_algebraic_str("a0").is_err()); - assert!(Square::from_algebraic_str("j3").is_err()); - assert!(Square::from_algebraic_str("a11").is_err()); - assert!(Square::from_algebraic_str("b-1").is_err()); - assert!(Square::from_algebraic_str("a 1").is_err()); - assert!(Square::from_algebraic_str("").is_err()); + assert!("a0".parse::().is_err()); + assert!("j3".parse::().is_err()); + assert!("a11".parse::().is_err()); + assert!("b-1".parse::().is_err()); + assert!("a 1".parse::().is_err()); + assert!("".parse::().is_err()); } #[test] diff --git a/core/src/errors.rs b/core/src/errors.rs deleted file mode 100644 index c897997..0000000 --- a/core/src/errors.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Eryn Wells - -#[derive(Debug, Eq, PartialEq)] -pub struct TryFromCharError; - -#[derive(Debug, Eq, PartialEq)] -pub struct TryFromStrError; diff --git a/core/src/lib.rs b/core/src/lib.rs index fc3dfbc..4d54037 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,11 +2,12 @@ pub mod colors; pub mod coordinates; -pub mod errors; pub mod pieces; +pub mod shapes; mod macros; pub use colors::Color; pub use coordinates::{Direction, File, Rank, Square}; -pub use pieces::{Piece, PlacedPiece, Shape}; +pub use pieces::{Piece, PlacedPiece}; +pub use shapes::Shape; diff --git a/core/src/macros.rs b/core/src/macros.rs index 9cce129..dc99d9a 100644 --- a/core/src/macros.rs +++ b/core/src/macros.rs @@ -1,26 +1,5 @@ // Eryn Wells -#[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 { - 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_rules! piece { ($color:ident $shape:ident) => { diff --git a/core/src/pieces.rs b/core/src/pieces.rs index dd6de93..a580967 100644 --- a/core/src/pieces.rs +++ b/core/src/pieces.rs @@ -1,124 +1,10 @@ // Eryn Wells -use crate::{errors::TryFromCharError, try_from_string, Color, Square}; -use std::{array, fmt, slice}; +mod display; -trait _Shape { - fn symbol(&self) -> char; - fn index(&self) -> usize; -} +pub use self::display::{PieceDisplay, PieceDisplayStyle}; -macro_rules! shape { - ($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 { - 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 for Shape { - type Error = TryFromCharError; - - fn try_from(value: char) -> Result { - 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 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}") - } -} +use crate::{Color, Shape, Square}; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Piece { @@ -195,9 +81,16 @@ impl Piece { } } -impl fmt::Display for Piece { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_unicode()) +impl std::fmt::Display for Piece { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + PieceDisplay { + piece: *self, + style: PieceDisplayStyle::default() + } + ) } } @@ -268,8 +161,8 @@ impl PlacedPiece { } } -impl fmt::Display for PlacedPiece { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl std::fmt::Display for PlacedPiece { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}{}", self.piece, self.square) } } diff --git a/core/src/pieces/display.rs b/core/src/pieces/display.rs new file mode 100644 index 0000000..77fadb6 --- /dev/null +++ b/core/src/pieces/display.rs @@ -0,0 +1,28 @@ +// Eryn Wells + +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()) + } + } + } +} diff --git a/core/src/shapes.rs b/core/src/shapes.rs new file mode 100644 index 0000000..0bdfea8 --- /dev/null +++ b/core/src/shapes.rs @@ -0,0 +1,137 @@ +// Eryn Wells + +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 { + 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 for Shape { + type Error = ShapeFromCharError; + + fn try_from(value: char) -> Result { + 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 { + 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::try_from(s) + } +} + +impl From<&Shape> for char { + fn from(shape: &Shape) -> char { + char::from(*shape) + } +} + +impl From 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}") + } +} diff --git a/explorer/Cargo.toml b/explorer/Cargo.toml index a81f966..00fa045 100644 --- a/explorer/Cargo.toml +++ b/explorer/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.98" chessfriend_core = { path = "../core" } chessfriend_board = { path = "../board" } chessfriend_moves = { path = "../moves" } @@ -13,3 +14,4 @@ chessfriend_position = { path = "../position" } clap = { version = "4.4.12", features = ["derive"] } rustyline = "13.0.0" shlex = "1.2.0" +thiserror = "2" diff --git a/explorer/src/main.rs b/explorer/src/main.rs index bf688d1..8ba0a6b 100644 --- a/explorer/src/main.rs +++ b/explorer/src/main.rs @@ -4,9 +4,11 @@ use chessfriend_board::{fen::FromFenStr, Board}; use chessfriend_core::{Color, Piece, Shape, Square}; use chessfriend_moves::Builder as MoveBuilder; use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove}; + use clap::{Arg, Command}; use rustyline::error::ReadlineError; use rustyline::DefaultEditor; +use thiserror::Error; struct CommandResult { should_continue: bool, @@ -79,11 +81,17 @@ fn command_line() -> Command { .subcommand(Command::new("starting").about("Reset the board to the starting position")) } -fn respond(line: &str, state: &mut State) -> Result { - let args = shlex::split(line).ok_or("error: Invalid quoting")?; - let matches = command_line() - .try_get_matches_from(args) - .map_err(|e| e.to_string())?; +#[derive(Clone, Debug, Error, Eq, PartialEq)] +enum CommandHandlingError<'a> { + #[error("lexer error")] + LexerError, + #[error("missing {0} argument")] + MissingArgument(&'a str), +} + +fn respond(line: &str, state: &mut State) -> anyhow::Result { + let args = shlex::split(line).ok_or(CommandHandlingError::LexerError)?; + let matches = command_line().try_get_matches_from(args)?; let mut result = CommandResult::default(); @@ -94,68 +102,53 @@ fn respond(line: &str, state: &mut State) -> Result { result.should_print_position = false; } Some(("fen", _matches)) => { - println!( - "{}", - state - .position - .to_fen_str() - .map_err(|_| "error: Unable to generate FEN for current position")? - ); - + println!("{}", state.position.to_fen_str()?); result.should_print_position = false; } Some(("make", matches)) => { let from_square = Square::from_algebraic_str( - matches.get_one::("from").ok_or("Missing square")?, - ) - .map_err(|_| "Error: invalid square specifier")?; + matches + .get_one::("from") + .ok_or(CommandHandlingError::MissingArgument("from"))?, + )?; let to_square = Square::from_algebraic_str( - matches.get_one::("to").ok_or("Missing square")?, - ) - .map_err(|_| "Error: invalid square specifier")?; + matches + .get_one::("to") + .ok_or(CommandHandlingError::MissingArgument("to"))?, + )?; - let ply = MoveBuilder::new() - .from(from_square) - .to(to_square) - .build() - .map_err(|err| format!("Error: {err:?}"))?; + let ply = MoveBuilder::new().from(from_square).to(to_square).build()?; - state - .position - .make_move(ply, ValidateMove::Yes) - .map_err(|err| format!("Error: {err}"))?; + state.position.make_move(ply, ValidateMove::Yes)?; } Some(("place", matches)) => { let color = matches .get_one::("color") - .ok_or("Missing color descriptor")?; - let color = Color::try_from(color).map_err(|_| "Invalid color descriptor")?; + .ok_or(CommandHandlingError::MissingArgument("color"))?; + let color = color.parse::()?; let shape = matches .get_one::("piece") - .ok_or("Missing piece descriptor")?; - let shape = Shape::try_from(shape).map_err(|_| "Invalid piece descriptor")?; + .ok_or(CommandHandlingError::MissingArgument("piece"))?; + let shape = shape.parse::()?; let square = matches .get_one::("square") - .ok_or("Missing square")?; - let square = Square::from_algebraic_str(square) - .map_err(|_| "Error: invalid square specifier")?; + .ok_or(CommandHandlingError::MissingArgument("square"))?; + let square = Square::from_algebraic_str(square)?; let piece = Piece::new(color, shape); state .position - .place_piece(piece, square, PlacePieceStrategy::default()) - .map_err(|err| format!("Error: could not place piece: {err:?}"))?; + .place_piece(piece, square, PlacePieceStrategy::default())?; } Some(("sight", matches)) => { let square = matches .get_one::("square") - .ok_or("Missing square")?; - let square = Square::from_algebraic_str(square) - .map_err(|_| "Error: invalid square specifier")?; + .ok_or(CommandHandlingError::MissingArgument("square"))?; + let square = square.parse::()?; let sight = state.position.sight(square); @@ -167,9 +160,8 @@ fn respond(line: &str, state: &mut State) -> Result { Some(("moves", matches)) => { let square = matches .get_one::("square") - .ok_or("Missing square")?; - let square = Square::from_algebraic_str(square) - .map_err(|_| "Error: invalid square specifier")?; + .ok_or(CommandHandlingError::MissingArgument("square"))?; + let square = square.parse::()?; let movement = state.position.movement(square); @@ -182,7 +174,7 @@ fn respond(line: &str, state: &mut State) -> Result { let starting_position = Position::starting(); 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}"), None => unreachable!("Subcommand required"), } @@ -190,19 +182,24 @@ fn respond(line: &str, state: &mut State) -> 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 { match matches.subcommand() { None | Some(("clear", _)) => state.position = Position::empty(), Some(("starting", _)) => state.position = Position::starting(), Some(("fen", matches)) => { - let fen = matches.get_one::("fen").ok_or("Missing FEN")?; - let board = Board::from_fen_str(fen).map_err(|err| format!("{err}"))?; + let fen = matches + .get_one::("fen") + .ok_or(CommandHandlingError::MissingArgument("fen"))?; + let board = Board::from_fen_str(fen)?; state.position = Position::new(board); } Some((name, _matches)) => unimplemented!("{name}"), } - Ok(()) + Ok(CommandResult::default()) } fn main() -> Result<(), String> { diff --git a/moves/src/builder.rs b/moves/src/builder.rs index e75a511..b6a0794 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -4,15 +4,20 @@ 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; type EncodedMoveResult = std::result::Result; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[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, }