diff --git a/src/board/chess_board/mod.rs b/src/board/chess_board/mod.rs index c502ada..824426f 100644 --- a/src/board/chess_board/mod.rs +++ b/src/board/chess_board/mod.rs @@ -1,7 +1,4 @@ -use crate::{ - fen::{FenError, FromFen}, - movegen, -}; +use crate::movegen; use super::{Bitboard, CastleRights, Color, File, Move, Piece, Rank, Square}; @@ -403,94 +400,10 @@ impl Default for ChessBoard { } } -/// Return a [ChessBoard] from the given FEN string. -impl FromFen for ChessBoard { - type Err = FenError; - - fn from_fen(s: &str) -> Result { - let mut split = s.split_ascii_whitespace(); - - let piece_placement = split.next().ok_or(FenError::InvalidFen)?; - let side_to_move = split.next().ok_or(FenError::InvalidFen)?; - let castling_rights = split.next().ok_or(FenError::InvalidFen)?; - let en_passant_square = split.next().ok_or(FenError::InvalidFen)?; - let half_move_clock = split.next().ok_or(FenError::InvalidFen)?; - let full_move_counter = split.next().ok_or(FenError::InvalidFen)?; - - let mut builder = ChessBoardBuilder::new(); - - let castle_rights = <[CastleRights; 2]>::from_fen(castling_rights)?; - for color in Color::iter() { - builder.with_castle_rights(castle_rights[color.index()], color); - } - - let side = Color::from_fen(side_to_move)?; - builder.with_current_player(side); - - if let Some(square) = Option::::from_fen(en_passant_square)? { - builder.with_en_passant(square); - }; - - let half_move_clock = half_move_clock - .parse::() - .map_err(|_| FenError::InvalidFen)?; - builder.with_half_move_clock(half_move_clock); - - let full_move_counter = full_move_counter - .parse::() - .map_err(|_| FenError::InvalidFen)?; - builder.with_total_plies( - (full_move_counter - 1) * 2 + if side == Color::White { 0 } else { 1 }, - ); - - { - let mut rank: usize = 8; - for rank_str in piece_placement.split('/') { - rank -= 1; - let mut file: usize = 0; - for c in rank_str.chars() { - let color = if c.is_uppercase() { - Color::White - } else { - Color::Black - }; - let piece = match c { - digit @ '1'..='8' => { - // Unwrap is fine since this arm is only matched by digits - file += digit.to_digit(10).unwrap() as usize; - continue; - } - _ => Piece::from_fen(&c.to_string())?, - }; - - // Only need to worry about underflow since those are `usize` values. - if file >= 8 || rank >= 8 { - return Err(FenError::InvalidFen); - }; - - let square = Square::new(File::from_index(file), Rank::from_index(rank)); - - builder[square] = Some((piece, color)); - file += 1; - } - // We haven't read exactly 8 files. - if file != 8 { - return Err(FenError::InvalidFen); - } - } - // We haven't read exactly 8 ranks - if rank != 0 { - return Err(FenError::InvalidFen); - } - }; - - Ok(builder.try_into()?) - } -} - #[cfg(test)] mod test { use crate::board::MoveBuilder; + use crate::fen::FromFen; use super::*; diff --git a/src/fen.rs b/src/fen.rs index 78452c2..8baf4af 100644 --- a/src/fen.rs +++ b/src/fen.rs @@ -1,4 +1,6 @@ -use crate::board::{CastleRights, Color, File, InvalidError, Piece, Rank, Square}; +use crate::board::{ + CastleRights, ChessBoard, ChessBoardBuilder, Color, File, InvalidError, Piece, Rank, Square, +}; /// A trait to mark items that can be converted from a FEN input. pub trait FromFen: Sized { @@ -115,3 +117,88 @@ impl FromFen for Piece { Ok(res) } } + +/// Return a [ChessBoard] from the given FEN string. +impl FromFen for ChessBoard { + type Err = FenError; + + fn from_fen(s: &str) -> Result { + let mut split = s.split_ascii_whitespace(); + + let piece_placement = split.next().ok_or(FenError::InvalidFen)?; + let side_to_move = split.next().ok_or(FenError::InvalidFen)?; + let castling_rights = split.next().ok_or(FenError::InvalidFen)?; + let en_passant_square = split.next().ok_or(FenError::InvalidFen)?; + let half_move_clock = split.next().ok_or(FenError::InvalidFen)?; + let full_move_counter = split.next().ok_or(FenError::InvalidFen)?; + + let mut builder = ChessBoardBuilder::new(); + + let castle_rights = <[CastleRights; 2]>::from_fen(castling_rights)?; + for color in Color::iter() { + builder.with_castle_rights(castle_rights[color.index()], color); + } + + let side = Color::from_fen(side_to_move)?; + builder.with_current_player(side); + + if let Some(square) = Option::::from_fen(en_passant_square)? { + builder.with_en_passant(square); + }; + + let half_move_clock = half_move_clock + .parse::() + .map_err(|_| FenError::InvalidFen)?; + builder.with_half_move_clock(half_move_clock); + + let full_move_counter = full_move_counter + .parse::() + .map_err(|_| FenError::InvalidFen)?; + builder.with_total_plies( + (full_move_counter - 1) * 2 + if side == Color::White { 0 } else { 1 }, + ); + + { + let mut rank: usize = 8; + for rank_str in piece_placement.split('/') { + rank -= 1; + let mut file: usize = 0; + for c in rank_str.chars() { + let color = if c.is_uppercase() { + Color::White + } else { + Color::Black + }; + let piece = match c { + digit @ '1'..='8' => { + // Unwrap is fine since this arm is only matched by digits + file += digit.to_digit(10).unwrap() as usize; + continue; + } + _ => Piece::from_fen(&c.to_string())?, + }; + + // Only need to worry about underflow since those are `usize` values. + if file >= 8 || rank >= 8 { + return Err(FenError::InvalidFen); + }; + + let square = Square::new(File::from_index(file), Rank::from_index(rank)); + + builder[square] = Some((piece, color)); + file += 1; + } + // We haven't read exactly 8 files. + if file != 8 { + return Err(FenError::InvalidFen); + } + } + // We haven't read exactly 8 ranks + if rank != 0 { + return Err(FenError::InvalidFen); + } + }; + + Ok(builder.try_into()?) + } +}