[position] Implement FromFen for Position, Piece, and Color
I can now create Positions from FEN strings!
This commit is contained in:
parent
3239f288d7
commit
52b19b87d8
5 changed files with 187 additions and 13 deletions
|
@ -1,7 +1,7 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::{r#move::Castle, Position};
|
use crate::{r#move::Castle, Position, PositionBuilder};
|
||||||
use chessfriend_core::{Color, File, Piece, PlacedPiece, Rank, Square};
|
use chessfriend_core::{piece, Color, File, Piece, PlacedPiece, Rank, Square};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
@ -9,11 +9,19 @@ pub enum ToFenError {
|
||||||
FmtError(std::fmt::Error),
|
FmtError(std::fmt::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct FromFenError;
|
||||||
|
|
||||||
pub trait ToFen {
|
pub trait ToFen {
|
||||||
type Error;
|
type Error;
|
||||||
fn to_fen(&self) -> Result<String, Self::Error>;
|
fn to_fen(&self) -> Result<String, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait FromFen: Sized {
|
||||||
|
type Error;
|
||||||
|
fn from_fen_str(string: &str) -> Result<Self, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
impl ToFen for Position {
|
impl ToFen for Position {
|
||||||
type Error = ToFenError;
|
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<Self, Self::Error> {
|
||||||
|
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<Self, Self::Error> {
|
||||||
|
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<Self, Self::Error> {
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::test_position;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn starting_position() {
|
fn starting_position() {
|
||||||
let pos = Position::starting();
|
let pos = test_position!(starting);
|
||||||
println!("{pos:#?}");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pos.to_fen(),
|
pos.to_fen(),
|
||||||
Ok(String::from(
|
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:#?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ mod castle {
|
||||||
check_squares: BitBoard,
|
check_squares: BitBoard,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct Squares {
|
pub(crate) struct Squares {
|
||||||
pub king: Square,
|
pub king: Square,
|
||||||
pub rook: Square,
|
pub rook: Square,
|
||||||
|
|
|
@ -13,7 +13,8 @@ pub struct Builder {
|
||||||
player_to_move: Color,
|
player_to_move: Color,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
pieces: BTreeMap<Square, Piece>,
|
pieces: BTreeMap<Square, Piece>,
|
||||||
kings: [Square; 2],
|
kings: [Option<Square>; 2],
|
||||||
|
en_passant_square: Option<Square>,
|
||||||
ply_counter: u16,
|
ply_counter: u16,
|
||||||
move_number: u16,
|
move_number: u16,
|
||||||
}
|
}
|
||||||
|
@ -23,6 +24,18 @@ impl Builder {
|
||||||
Self::default()
|
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 {
|
pub fn from_position(position: &Position) -> Self {
|
||||||
let pieces = BTreeMap::from_iter(
|
let pieces = BTreeMap::from_iter(
|
||||||
position
|
position
|
||||||
|
@ -38,7 +51,8 @@ impl Builder {
|
||||||
player_to_move: position.player_to_move(),
|
player_to_move: position.player_to_move(),
|
||||||
flags: position.flags(),
|
flags: position.flags(),
|
||||||
pieces,
|
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(),
|
ply_counter: position.ply_counter(),
|
||||||
move_number: position.move_number(),
|
move_number: position.move_number(),
|
||||||
}
|
}
|
||||||
|
@ -59,6 +73,11 @@ impl Builder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn en_passant_square(&mut self, square: Option<Square>) -> &mut Self {
|
||||||
|
self.en_passant_square = square;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn place_piece(&mut self, piece: PlacedPiece) -> &mut Self {
|
pub fn place_piece(&mut self, piece: PlacedPiece) -> &mut Self {
|
||||||
let square = piece.square();
|
let square = piece.square();
|
||||||
let shape = piece.shape();
|
let shape = piece.shape();
|
||||||
|
@ -66,8 +85,11 @@ impl Builder {
|
||||||
if shape == Shape::King {
|
if shape == Shape::King {
|
||||||
let color = piece.color();
|
let color = piece.color();
|
||||||
let color_index: usize = color as usize;
|
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());
|
self.pieces.insert(square, *piece.piece());
|
||||||
|
@ -75,6 +97,17 @@ impl Builder {
|
||||||
self
|
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 {
|
pub fn build(&self) -> Position {
|
||||||
let pieces = PieceBitBoards::from_iter(
|
let pieces = PieceBitBoards::from_iter(
|
||||||
self.pieces
|
self.pieces
|
||||||
|
@ -93,7 +126,7 @@ impl Builder {
|
||||||
.get(&starting_squares.rook)
|
.get(&starting_squares.rook)
|
||||||
.is_some_and(|piece| piece.shape() == Shape::Rook);
|
.is_some_and(|piece| piece.shape() == Shape::Rook);
|
||||||
let king_is_on_starting_square =
|
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 {
|
if !king_is_on_starting_square || !has_rook_on_starting_square {
|
||||||
flags.clear_player_has_right_to_castle_flag(color, castle);
|
flags.clear_player_has_right_to_castle_flag(color, castle);
|
||||||
|
@ -105,7 +138,7 @@ impl Builder {
|
||||||
self.player_to_move,
|
self.player_to_move,
|
||||||
flags,
|
flags,
|
||||||
pieces,
|
pieces,
|
||||||
None,
|
self.en_passant_square,
|
||||||
self.ply_counter,
|
self.ply_counter,
|
||||||
self.move_number,
|
self.move_number,
|
||||||
)
|
)
|
||||||
|
@ -139,7 +172,8 @@ impl Default for Builder {
|
||||||
player_to_move: Color::White,
|
player_to_move: Color::White,
|
||||||
flags: Flags::default(),
|
flags: Flags::default(),
|
||||||
pieces: pieces,
|
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,
|
ply_counter: 0,
|
||||||
move_number: 1,
|
move_number: 1,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,10 @@ impl Flags {
|
||||||
pub(super) fn clear_player_has_right_to_castle_flag(&mut self, color: Color, castle: Castle) {
|
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));
|
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 {
|
impl fmt::Debug for Flags {
|
||||||
|
|
|
@ -269,8 +269,8 @@ impl Position {
|
||||||
pieces,
|
pieces,
|
||||||
sight: [OnceCell::new(), OnceCell::new()],
|
sight: [OnceCell::new(), OnceCell::new()],
|
||||||
moves: OnceCell::new(),
|
moves: OnceCell::new(),
|
||||||
half_move_counter: 0,
|
half_move_counter,
|
||||||
full_move_number: 1,
|
full_move_number,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue