lazychess 0.1.1

A fast, memory-efficient chess engine library for Rust
Documentation
use crate::board::Board;
use crate::types::*;

/// Parses a FEN string into a `Board`.
///
/// Full FEN format:
/// `<placement> <color> <castling> <en_passant> <halfmove> <fullmove>`
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];

    // Piece placement – FEN lists rank 8 first, rank 1 last.
    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;
            }
        }
    }

    // Side to move
    let side_to_move = match parts[1] {
        "w" => Color::White,
        "b" => Color::Black,
        s => return Err(ChessError::new(format!("Invalid side to move: '{s}'"))),
    };

    // Castling rights
    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}'"))),
        }
    }

    // En passant
    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,
    })
}

/// Serialises a `Board` to a complete FEN string.
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,
    )
}