Move 'FromFen' for 'ChessBoard' into 'fen' module
This commit is contained in:
parent
b9cc60be9c
commit
c3be661719
|
@ -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::*;
|
||||||
|
|
||||||
|
|
89
src/fen.rs
89
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.
|
/// 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()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue