Move 'FromFen' for 'ChessBoard' into 'fen' module

This commit is contained in:
Bruno BELANYI 2024-04-01 21:34:01 +01:00
parent b9cc60be9c
commit c3be661719
2 changed files with 90 additions and 90 deletions

View file

@ -1,7 +1,4 @@
use crate::{ use crate::movegen;
fen::{FenError, FromFen},
movegen,
};
use super::{Bitboard, CastleRights, Color, File, Move, Piece, Rank, Square}; 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<Self, Self::Err> {
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::<Square>::from_fen(en_passant_square)? {
builder.with_en_passant(square);
};
let half_move_clock = half_move_clock
.parse::<u8>()
.map_err(|_| FenError::InvalidFen)?;
builder.with_half_move_clock(half_move_clock);
let full_move_counter = full_move_counter
.parse::<u32>()
.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)] #[cfg(test)]
mod test { mod test {
use crate::board::MoveBuilder; use crate::board::MoveBuilder;
use crate::fen::FromFen;
use super::*; use super::*;

View file

@ -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. /// A trait to mark items that can be converted from a FEN input.
pub trait FromFen: Sized { pub trait FromFen: Sized {
@ -115,3 +117,88 @@ impl FromFen for Piece {
Ok(res) Ok(res)
} }
} }
/// Return a [ChessBoard] from the given FEN string.
impl FromFen for ChessBoard {
type Err = FenError;
fn from_fen(s: &str) -> Result<Self, Self::Err> {
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::<Square>::from_fen(en_passant_square)? {
builder.with_en_passant(square);
};
let half_move_clock = half_move_clock
.parse::<u8>()
.map_err(|_| FenError::InvalidFen)?;
builder.with_half_move_clock(half_move_clock);
let full_move_counter = full_move_counter
.parse::<u32>()
.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()?)
}
}