2024-01-21 15:10:59 -08:00
|
|
|
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
|
|
2024-01-26 12:59:05 -08:00
|
|
|
use crate::{r#move::Castle, Position};
|
|
|
|
|
use chessfriend_core::{Color, File, Piece, PlacedPiece, Rank, Square};
|
2024-01-21 15:10:59 -08:00
|
|
|
use std::fmt::Write;
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
2024-01-28 10:02:53 -08:00
|
|
|
pub enum ToFenError {
|
2024-01-21 15:10:59 -08:00
|
|
|
FmtError(std::fmt::Error),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub trait ToFen {
|
2024-01-28 10:02:53 -08:00
|
|
|
type Error;
|
|
|
|
|
fn to_fen(&self) -> Result<String, Self::Error>;
|
2024-01-21 15:10:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToFen for Position {
|
2024-01-28 10:02:53 -08:00
|
|
|
type Error = ToFenError;
|
|
|
|
|
|
|
|
|
|
fn to_fen(&self) -> Result<String, Self::Error> {
|
2024-01-21 15:10:59 -08:00
|
|
|
let mut fen_string = String::new();
|
|
|
|
|
|
|
|
|
|
let mut empty_squares: u8 = 0;
|
|
|
|
|
for rank in Rank::ALL.iter().rev() {
|
|
|
|
|
for file in File::ALL.iter() {
|
|
|
|
|
let square = Square::from_file_rank(*file, *rank);
|
|
|
|
|
match self.piece_on_square(square) {
|
|
|
|
|
Some(piece) => {
|
|
|
|
|
if empty_squares > 0 {
|
|
|
|
|
write!(fen_string, "{}", empty_squares)
|
2024-01-28 10:02:53 -08:00
|
|
|
.map_err(|err| ToFenError::FmtError(err))?;
|
2024-01-21 15:10:59 -08:00
|
|
|
empty_squares = 0;
|
|
|
|
|
}
|
|
|
|
|
write!(fen_string, "{}", piece.to_fen()?)
|
2024-01-28 10:02:53 -08:00
|
|
|
.map_err(|err| ToFenError::FmtError(err))?;
|
2024-01-21 15:10:59 -08:00
|
|
|
}
|
|
|
|
|
None => empty_squares += 1,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if empty_squares > 0 {
|
2024-01-28 10:02:53 -08:00
|
|
|
write!(fen_string, "{}", empty_squares).map_err(|err| ToFenError::FmtError(err))?;
|
2024-01-21 15:10:59 -08:00
|
|
|
empty_squares = 0;
|
|
|
|
|
}
|
2024-01-24 08:32:09 -08:00
|
|
|
if rank != &Rank::ONE {
|
2024-01-28 10:02:53 -08:00
|
|
|
write!(fen_string, "/").map_err(|err| ToFenError::FmtError(err))?;
|
2024-01-21 15:10:59 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write!(fen_string, " {}", self.player_to_move().to_fen()?)
|
2024-01-28 10:02:53 -08:00
|
|
|
.map_err(|err| ToFenError::FmtError(err))?;
|
2024-01-21 15:10:59 -08:00
|
|
|
|
|
|
|
|
let castling = [
|
|
|
|
|
(Color::White, Castle::KingSide),
|
|
|
|
|
(Color::White, Castle::QueenSide),
|
|
|
|
|
(Color::Black, Castle::KingSide),
|
|
|
|
|
(Color::Black, Castle::QueenSide),
|
|
|
|
|
]
|
|
|
|
|
.map(|(color, castle)| {
|
|
|
|
|
let can_castle = self.player_has_right_to_castle(color, castle);
|
|
|
|
|
if !can_castle {
|
|
|
|
|
""
|
|
|
|
|
} else {
|
|
|
|
|
match (color, castle) {
|
|
|
|
|
(Color::White, Castle::KingSide) => "K",
|
|
|
|
|
(Color::White, Castle::QueenSide) => "Q",
|
|
|
|
|
(Color::Black, Castle::KingSide) => "k",
|
|
|
|
|
(Color::Black, Castle::QueenSide) => "q",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.concat();
|
|
|
|
|
|
|
|
|
|
write!(
|
|
|
|
|
fen_string,
|
|
|
|
|
" {}",
|
|
|
|
|
if castling.len() > 0 { &castling } else { "-" }
|
|
|
|
|
)
|
2024-01-28 10:02:53 -08:00
|
|
|
.map_err(|err| ToFenError::FmtError(err))?;
|
2024-01-21 15:10:59 -08:00
|
|
|
|
|
|
|
|
write!(
|
|
|
|
|
fen_string,
|
|
|
|
|
" {}",
|
|
|
|
|
if let Some(en_passant_square) = self.en_passant_square() {
|
|
|
|
|
en_passant_square.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
"-".to_string()
|
|
|
|
|
}
|
|
|
|
|
)
|
2024-01-28 10:02:53 -08:00
|
|
|
.map_err(|err| ToFenError::FmtError(err))?;
|
2024-01-21 15:10:59 -08:00
|
|
|
|
2024-01-28 10:02:53 -08:00
|
|
|
write!(fen_string, " {}", self.ply_counter()).map_err(|err| ToFenError::FmtError(err))?;
|
|
|
|
|
write!(fen_string, " {}", self.move_number()).map_err(|err| ToFenError::FmtError(err))?;
|
2024-01-21 15:10:59 -08:00
|
|
|
|
|
|
|
|
Ok(fen_string)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToFen for Color {
|
2024-01-28 10:02:53 -08:00
|
|
|
type Error = ToFenError;
|
|
|
|
|
|
|
|
|
|
fn to_fen(&self) -> Result<String, Self::Error> {
|
2024-01-21 15:10:59 -08:00
|
|
|
match self {
|
|
|
|
|
Color::White => Ok("w".to_string()),
|
|
|
|
|
Color::Black => Ok("b".to_string()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToFen for Piece {
|
2024-01-28 10:02:53 -08:00
|
|
|
type Error = ToFenError;
|
|
|
|
|
|
|
|
|
|
fn to_fen(&self) -> Result<String, Self::Error> {
|
2024-01-21 15:10:59 -08:00
|
|
|
let ascii: char = self.to_ascii();
|
|
|
|
|
Ok(String::from(match self.color() {
|
|
|
|
|
Color::White => ascii.to_ascii_uppercase(),
|
|
|
|
|
Color::Black => ascii.to_ascii_lowercase(),
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToFen for PlacedPiece {
|
2024-01-28 10:02:53 -08:00
|
|
|
type Error = ToFenError;
|
|
|
|
|
|
|
|
|
|
fn to_fen(&self) -> Result<String, Self::Error> {
|
2024-01-21 15:10:59 -08:00
|
|
|
Ok(self.piece().to_fen()?)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn starting_position() {
|
|
|
|
|
let pos = Position::starting();
|
|
|
|
|
println!("{pos:#?}");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
pos.to_fen(),
|
|
|
|
|
Ok(String::from(
|
|
|
|
|
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
|
|
|
|
))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|