use crate::board::Board;
use crate::types::*;
pub fn parse_fen(fen: &str) -> ChessResult<Board> {
let parts: Vec<&str> = fen.split_whitespace().collect();
if parts.len() < 4 {
return Err(ChessError::new("FEN must have at least 4 fields"));
}
let mut squares = [None; 64];
let mut rank: i32 = 7;
let mut file: i32 = 0;
for ch in parts[0].chars() {
match ch {
'/' => {
rank -= 1;
file = 0;
}
'1'..='8' => {
file += ch as i32 - '0' as i32;
}
_ => {
let piece = Piece::from_fen_char(ch)
.ok_or_else(|| ChessError::new(format!("Unknown FEN char: '{ch}'")))?;
if rank < 0 || file > 7 {
return Err(ChessError::new("FEN piece placement out of bounds"));
}
squares[(rank * 8 + file) as usize] = Some(piece);
file += 1;
}
}
}
let side_to_move = match parts[1] {
"w" => Color::White,
"b" => Color::Black,
s => return Err(ChessError::new(format!("Invalid side to move: '{s}'"))),
};
let mut castling_rights = CastlingRights::none();
for ch in parts[2].chars() {
match ch {
'K' => castling_rights.white_kingside = true,
'Q' => castling_rights.white_queenside = true,
'k' => castling_rights.black_kingside = true,
'q' => castling_rights.black_queenside = true,
'-' => {}
c => return Err(ChessError::new(format!("Invalid castling char: '{c}'"))),
}
}
let en_passant = match parts[3] {
"-" => None,
s => Some(
parse_square(s)
.ok_or_else(|| ChessError::new(format!("Invalid en passant square: '{s}'")))?,
),
};
let halfmove_clock = parts
.get(4)
.and_then(|s| s.parse().ok())
.unwrap_or(0u32);
let fullmove_number = parts
.get(5)
.and_then(|s| s.parse().ok())
.unwrap_or(1u32);
Ok(Board {
squares,
side_to_move,
castling_rights,
en_passant,
halfmove_clock,
fullmove_number,
})
}
pub fn board_to_fen(board: &Board) -> String {
let ep = board
.en_passant
.map(square_name)
.unwrap_or_else(|| "-".to_string());
format!(
"{} {} {} {} {} {}",
board.fen_piece_placement(),
board.side_to_move.to_char(),
board.castling_rights.to_fen_str(),
ep,
board.halfmove_clock,
board.fullmove_number,
)
}