diff --git a/board/src/fen.rs b/board/src/fen.rs new file mode 100644 index 0000000..b212c94 --- /dev/null +++ b/board/src/fen.rs @@ -0,0 +1,139 @@ +// Eryn Wells + +use crate::{ + piece::{Piece, PlacedPiece}, + r#move::Castle, + Color, File, Position, Rank, Square, +}; +use std::fmt::Write; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FenError { + FmtError(std::fmt::Error), +} + +pub trait ToFen { + fn to_fen(&self) -> Result; +} + +impl ToFen for Position { + 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| FenError::FmtError(err))?; + empty_squares = 0; + } + write!(fen_string, "{}", piece.to_fen()?) + .map_err(|err| FenError::FmtError(err))?; + } + None => empty_squares += 1, + } + } + + if empty_squares > 0 { + write!(fen_string, "{}", empty_squares).map_err(|err| FenError::FmtError(err))?; + empty_squares = 0; + } + if rank != &Rank::One { + write!(fen_string, "/").map_err(|err| FenError::FmtError(err))?; + } + } + + write!(fen_string, " {}", self.player_to_move().to_fen()?) + .map_err(|err| FenError::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| FenError::FmtError(err))?; + + write!( + fen_string, + " {}", + if let Some(en_passant_square) = self.en_passant_square() { + en_passant_square.to_string() + } else { + "-".to_string() + } + ) + .map_err(|err| FenError::FmtError(err))?; + + write!(fen_string, " {}", self.ply_counter()).map_err(|err| FenError::FmtError(err))?; + write!(fen_string, " {}", self.move_number()).map_err(|err| FenError::FmtError(err))?; + + Ok(fen_string) + } +} + +impl ToFen for Color { + fn to_fen(&self) -> Result { + match self { + Color::White => Ok("w".to_string()), + Color::Black => Ok("b".to_string()), + } + } +} + +impl ToFen for Piece { + 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 { + fn to_fen(&self) -> Result { + Ok(self.piece().to_fen()?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn starting_position() { + let pos = Position::starting(); + println!("{pos:#?}"); + assert_eq!( + pos.to_fen(), + Ok(String::from( + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + )) + ); + } +} diff --git a/board/src/lib.rs b/board/src/lib.rs index b267517..ad3abf0 100644 --- a/board/src/lib.rs +++ b/board/src/lib.rs @@ -3,6 +3,7 @@ #[macro_use] mod bitboard; mod display; +mod fen; mod r#move; #[macro_use] mod macros;