diff --git a/position/src/fen.rs b/position/src/fen.rs index f21de15..fd7b530 100644 --- a/position/src/fen.rs +++ b/position/src/fen.rs @@ -1,7 +1,7 @@ // Eryn Wells -use crate::{r#move::Castle, Position}; -use chessfriend_core::{Color, File, Piece, PlacedPiece, Rank, Square}; +use crate::{r#move::Castle, Position, PositionBuilder}; +use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square}; use std::fmt::Write; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -9,11 +9,19 @@ 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; @@ -127,14 +135,131 @@ impl ToFen for PlacedPiece { } } +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_square(Some(square)); + } + + 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 = Position::starting(); - println!("{pos:#?}"); + let pos = test_position!(starting); + assert_eq!( pos.to_fen(), Ok(String::from( @@ -142,4 +267,14 @@ mod tests { )) ); } + + #[test] + fn from_starting_fen() { + let pos = + Position::from_fen_str("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/move.rs b/position/src/move.rs index 7bd7052..bf6dc3c 100644 --- a/position/src/move.rs +++ b/position/src/move.rs @@ -31,6 +31,7 @@ mod castle { check_squares: BitBoard, } + #[derive(Debug)] pub(crate) struct Squares { pub king: Square, pub rook: Square, diff --git a/position/src/position/builders/position_builder.rs b/position/src/position/builders/position_builder.rs index 5b0a4e0..f81ac01 100644 --- a/position/src/position/builders/position_builder.rs +++ b/position/src/position/builders/position_builder.rs @@ -13,7 +13,8 @@ pub struct Builder { player_to_move: Color, flags: Flags, pieces: BTreeMap, - kings: [Square; 2], + kings: [Option; 2], + en_passant_square: Option, ply_counter: u16, move_number: u16, } @@ -23,6 +24,18 @@ impl Builder { Self::default() } + pub(crate) fn empty() -> Self { + Self { + player_to_move: Color::default(), + flags: Flags::default(), + pieces: BTreeMap::default(), + kings: [None, None], + en_passant_square: None, + ply_counter: 0, + move_number: 1, + } + } + pub fn from_position(position: &Position) -> Self { let pieces = BTreeMap::from_iter( position @@ -38,7 +51,8 @@ impl Builder { player_to_move: position.player_to_move(), flags: position.flags(), pieces, - kings: [white_king, black_king], + kings: [Some(white_king), Some(black_king)], + en_passant_square: position.en_passant_square(), ply_counter: position.ply_counter(), move_number: position.move_number(), } @@ -59,6 +73,11 @@ impl Builder { self } + pub fn en_passant_square(&mut self, square: Option) -> &mut Self { + self.en_passant_square = square; + self + } + pub fn place_piece(&mut self, piece: PlacedPiece) -> &mut Self { let square = piece.square(); let shape = piece.shape(); @@ -66,8 +85,11 @@ impl Builder { if shape == Shape::King { let color = piece.color(); let color_index: usize = color as usize; - self.pieces.remove(&self.kings[color_index]); - self.kings[color_index] = square; + + if let Some(king_square) = self.kings[color_index] { + self.pieces.remove(&king_square); + } + self.kings[color_index] = Some(square); } self.pieces.insert(square, *piece.piece()); @@ -75,6 +97,17 @@ impl Builder { self } + pub fn player_can_castle(&mut self, color: Color, castle: Castle) -> &mut Self { + self.flags + .set_player_has_right_to_castle_flag(color, castle); + self + } + + pub fn no_castling_rights(&mut self) -> &mut Self { + self.flags.clear_all_castling_rights(); + self + } + pub fn build(&self) -> Position { let pieces = PieceBitBoards::from_iter( self.pieces @@ -93,7 +126,7 @@ impl Builder { .get(&starting_squares.rook) .is_some_and(|piece| piece.shape() == Shape::Rook); let king_is_on_starting_square = - self.kings[color as usize] == starting_squares.king; + self.kings[color as usize] == Some(starting_squares.king); if !king_is_on_starting_square || !has_rook_on_starting_square { flags.clear_player_has_right_to_castle_flag(color, castle); @@ -105,7 +138,7 @@ impl Builder { self.player_to_move, flags, pieces, - None, + self.en_passant_square, self.ply_counter, self.move_number, ) @@ -139,7 +172,8 @@ impl Default for Builder { player_to_move: Color::White, flags: Flags::default(), pieces: pieces, - kings: [white_king_square, black_king_square], + kings: [Some(white_king_square), Some(black_king_square)], + en_passant_square: None, ply_counter: 0, move_number: 1, } diff --git a/position/src/position/flags.rs b/position/src/position/flags.rs index 699a63e..c1fbfbf 100644 --- a/position/src/position/flags.rs +++ b/position/src/position/flags.rs @@ -24,6 +24,10 @@ impl Flags { pub(super) fn clear_player_has_right_to_castle_flag(&mut self, color: Color, castle: Castle) { self.0 &= !(1 << Self::player_has_right_to_castle_flag_offset(color, castle)); } + + pub(super) fn clear_all_castling_rights(&mut self) { + self.0 &= 0b11111100; + } } impl fmt::Debug for Flags { diff --git a/position/src/position/position.rs b/position/src/position/position.rs index 3ee09f1..0443929 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -269,8 +269,8 @@ impl Position { pieces, sight: [OnceCell::new(), OnceCell::new()], moves: OnceCell::new(), - half_move_counter: 0, - full_move_number: 1, + half_move_counter, + full_move_number, } }