diff --git a/position/src/fen.rs b/position/src/fen.rs deleted file mode 100644 index fde920c..0000000 --- a/position/src/fen.rs +++ /dev/null @@ -1,282 +0,0 @@ -// Eryn Wells - -use crate::{Position, PositionBuilder}; -use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square}; -use chessfriend_moves::{Castle, EnPassant}; -use std::fmt::Write; - -#[macro_export] -macro_rules! fen { - ($fen_string:literal) => { - Position::from_fen_str($fen_string) - }; -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ToFenError { - FmtError(std::fmt::Error), -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct FromFenError; - -pub trait ToFen { - type Error; - fn to_fen(&self) -> Result; -} - -pub trait FromFen: Sized { - type Error; - fn from_fen_str(string: &str) -> Result; -} - -impl ToFen for Position { - type Error = ToFenError; - - fn to_fen(&self) -> Result { - let mut fen_string = String::new(); - - let mut empty_squares: u8 = 0; - for rank in Rank::ALL.iter().rev() { - for file in File::ALL.iter() { - let square = Square::from_file_rank(*file, *rank); - match self.piece_on_square(square) { - Some(piece) => { - if empty_squares > 0 { - write!(fen_string, "{}", empty_squares) - .map_err(|err| ToFenError::FmtError(err))?; - empty_squares = 0; - } - write!(fen_string, "{}", piece.to_fen()?) - .map_err(|err| ToFenError::FmtError(err))?; - } - None => empty_squares += 1, - } - } - - if empty_squares > 0 { - write!(fen_string, "{}", empty_squares).map_err(|err| ToFenError::FmtError(err))?; - empty_squares = 0; - } - if rank != &Rank::ONE { - write!(fen_string, "/").map_err(|err| ToFenError::FmtError(err))?; - } - } - - write!(fen_string, " {}", self.player_to_move().to_fen()?) - .map_err(|err| ToFenError::FmtError(err))?; - - let castling = [ - (Color::White, Castle::KingSide), - (Color::White, Castle::QueenSide), - (Color::Black, Castle::KingSide), - (Color::Black, Castle::QueenSide), - ] - .map(|(color, castle)| { - let can_castle = self.player_has_right_to_castle(color, castle); - if !can_castle { - "" - } else { - match (color, castle) { - (Color::White, Castle::KingSide) => "K", - (Color::White, Castle::QueenSide) => "Q", - (Color::Black, Castle::KingSide) => "k", - (Color::Black, Castle::QueenSide) => "q", - } - } - }) - .concat(); - - write!( - fen_string, - " {}", - if castling.len() > 0 { &castling } else { "-" } - ) - .map_err(|err| ToFenError::FmtError(err))?; - - write!( - fen_string, - " {}", - self.en_passant() - .map_or("-".to_string(), |ep| ep.target_square().to_string()) - ) - .map_err(|err| ToFenError::FmtError(err))?; - - write!(fen_string, " {}", self.ply_counter()).map_err(|err| ToFenError::FmtError(err))?; - write!(fen_string, " {}", self.move_number()).map_err(|err| ToFenError::FmtError(err))?; - - Ok(fen_string) - } -} - -impl ToFen for Color { - type Error = ToFenError; - - fn to_fen(&self) -> Result { - match self { - Color::White => Ok("w".to_string()), - Color::Black => Ok("b".to_string()), - } - } -} - -impl ToFen for Piece { - type Error = ToFenError; - - fn to_fen(&self) -> Result { - let ascii: char = self.to_ascii(); - Ok(String::from(match self.color() { - Color::White => ascii.to_ascii_uppercase(), - Color::Black => ascii.to_ascii_lowercase(), - })) - } -} - -impl ToFen for PlacedPiece { - type Error = ToFenError; - - fn to_fen(&self) -> Result { - Ok(self.piece().to_fen()?) - } -} - -impl FromFen for Position { - type Error = FromFenError; - - fn from_fen_str(string: &str) -> Result { - let mut builder = PositionBuilder::empty(); - - let mut fields = string.split(" "); - - let placements = fields.next().ok_or(FromFenError)?; - let ranks = placements.split("/"); - - for (rank, pieces) in Rank::ALL.iter().rev().zip(ranks) { - let mut files = File::ALL.iter(); - for ch in pieces.chars() { - if let Some(skip) = ch.to_digit(10) { - // TODO: Use advance_by() when it's available. - for _ in 0..skip { - files.next(); - } - - continue; - } - - let file = files.next().ok_or(FromFenError)?; - let piece = Piece::from_fen_str(&ch.to_string())?; - - builder.place_piece(PlacedPiece::new( - piece, - Square::from_file_rank(*file, *rank), - )); - } - - debug_assert_eq!(files.next(), None); - } - - let player_to_move = Color::from_fen_str(fields.next().ok_or(FromFenError)?)?; - builder.to_move(player_to_move); - - let castling_rights = fields.next().ok_or(FromFenError)?; - if castling_rights == "-" { - builder.no_castling_rights(); - } else { - for ch in castling_rights.chars() { - match ch { - 'K' => builder.player_can_castle(Color::White, Castle::KingSide), - 'Q' => builder.player_can_castle(Color::White, Castle::QueenSide), - 'k' => builder.player_can_castle(Color::Black, Castle::KingSide), - 'q' => builder.player_can_castle(Color::Black, Castle::QueenSide), - _ => return Err(FromFenError), - }; - } - } - - let en_passant_square = fields.next().ok_or(FromFenError)?; - if en_passant_square != "-" { - let square = Square::from_algebraic_str(en_passant_square).map_err(|_| FromFenError)?; - builder.en_passant(Some(EnPassant::from_target_square(square).unwrap())); - } - - let half_move_clock = fields.next().ok_or(FromFenError)?; - let half_move_clock: u16 = half_move_clock.parse().map_err(|_| FromFenError)?; - builder.ply_counter(half_move_clock); - - let full_move_counter = fields.next().ok_or(FromFenError)?; - let full_move_counter: u16 = full_move_counter.parse().map_err(|_| FromFenError)?; - builder.move_number(full_move_counter); - - debug_assert_eq!(fields.next(), None); - - Ok(builder.build()) - } -} - -impl FromFen for Color { - type Error = FromFenError; - - fn from_fen_str(string: &str) -> Result { - if string.len() != 1 { - return Err(FromFenError); - } - - match string.chars().take(1).next().unwrap() { - 'w' => Ok(Color::White), - 'b' => Ok(Color::Black), - _ => Err(FromFenError), - } - } -} - -impl FromFen for Piece { - type Error = FromFenError; - - fn from_fen_str(string: &str) -> Result { - if string.len() != 1 { - return Err(FromFenError); - } - - match string.chars().take(1).next().unwrap() { - 'P' => Ok(piece!(White Pawn)), - 'N' => Ok(piece!(White Knight)), - 'B' => Ok(piece!(White Bishop)), - 'R' => Ok(piece!(White Rook)), - 'Q' => Ok(piece!(White Queen)), - 'K' => Ok(piece!(White King)), - 'p' => Ok(piece!(Black Pawn)), - 'n' => Ok(piece!(Black Knight)), - 'b' => Ok(piece!(Black Bishop)), - 'r' => Ok(piece!(Black Rook)), - 'q' => Ok(piece!(Black Queen)), - 'k' => Ok(piece!(Black King)), - _ => Err(FromFenError), - } - } -} - -#[cfg(test)] -mod tests { - use crate::test_position; - - use super::*; - - #[test] - fn starting_position() { - let pos = test_position!(starting); - - assert_eq!( - pos.to_fen(), - Ok(String::from( - "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" - )) - ); - } - - #[test] - fn from_starting_fen() { - let pos = fen!("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap(); - let expected = Position::starting(); - assert_eq!(pos, expected, "{pos:#?}\n{expected:#?}"); - } -} diff --git a/position/src/lib.rs b/position/src/lib.rs index 1995be0..c740782 100644 --- a/position/src/lib.rs +++ b/position/src/lib.rs @@ -1,7 +1,5 @@ // Eryn Wells -pub mod fen; - mod check; mod display; mod move_generator;