timecat 1.52.0

A NNUE-based chess engine that implements the Negamax algorithm and can be integrated into any project as a library. It features move generation, advanced position evaluation through NNUE, and move searching capabilities.
Documentation
use itertools::*;
use std::str::FromStr;
use timecat::*;

macro_rules! assert_error {
    ($uci: expr, $error: pat) => {
        for uci in [$uci.to_lowercase(), $uci.to_uppercase()] {
            assert!(
                matches!(Move::from_str(&uci).unwrap_err(), $error),
                "assert_error failed for {}",
                $uci
            );
        }
    };
}

#[test]
fn test_move_uci_parsing() {
    izip!(
        ALL_SQUARES,
        ALL_SQUARES,
        ALL_PIECE_TYPES
            .iter()
            .copied()
            .map(Some)
            .chain(std::iter::once(None))
    )
    .for_each(|(source, dest, promotion)| {
        if source == dest {
            return;
        }
        for piece_type in [PieceType::Pawn, PieceType::King] {
            assert_error!(
                format!("{}{}{}", source, dest, piece_type).to_lowercase(),
                TimecatError::InvalidPromotion { .. }
            );
        }
        let move_ = Move::new(source, dest, promotion).unwrap();
        let uci_string = move_.uci();
        let parsed_move = Move::from_str(&uci_string).unwrap();
        assert_eq!(
            move_, parsed_move,
            "Failed to parse UCI string {} back to move",
            uci_string
        );
    });

    izip!(ALL_SQUARES, ALL_SQUARES, [King, Pawn],).for_each(|(source, dest, promotion)| {
        if source == dest {
            return;
        }
        assert_error!(
            format!("{}{}{}", source, dest, promotion).to_lowercase(),
            TimecatError::InvalidPromotion { .. }
        );
    });

    assert_eq!(ValidOrNullMove::NullMove.uci(), "0000");
    assert_eq!(
        ValidOrNullMove::NullMove,
        ValidOrNullMove::from_str("0000").unwrap()
    );
    assert_eq!(
        ValidOrNullMove::NullMove,
        ValidOrNullMove::from_str("--").unwrap()
    );

    for square in ALL_SQUARES {
        assert_error!(
            format!("{}{}", square, square).to_lowercase(),
            TimecatError::SameSourceAndDestination { .. }
        );
        for promotion in ALL_PIECE_TYPES {
            assert_error!(
                format!("{}{}{}", square, square, promotion).to_lowercase(),
                TimecatError::SameSourceAndDestination { .. }
            );
        }
    }

    assert_error!("Hello World!", TimecatError::InvalidUciMoveString { .. });
}

macro_rules! generate_parsing_functions {
    ($func_name:ident, $all_values:expr) => {
        #[test]
        fn $func_name() {
            for item in $all_values {
                let item_str = item.to_string();
                let parsed_item = item_str.parse().unwrap();
                assert_eq!(
                    item,
                    parsed_item,
                    "Failed to parse {} back to {}",
                    item_str,
                    stringify!($func_name)
                );
            }
        }
    };
}

generate_parsing_functions!(test_file_parsing, ALL_FILES);
generate_parsing_functions!(test_rank_parsing, ALL_RANKS);
generate_parsing_functions!(test_square_parsing, ALL_SQUARES);
generate_parsing_functions!(test_piece_type_parsing, ALL_PIECE_TYPES);
generate_parsing_functions!(test_piece_parsing, ALL_PIECES);
generate_parsing_functions!(test_color_parsing, ALL_COLORS);

#[test]
fn test_castling_right_parsing() {
    for color in ALL_COLORS {
        for castle_right in [
            CastleRights::None,
            CastleRights::KingSide,
            CastleRights::QueenSide,
            CastleRights::Both,
        ] {
            let castle_right_str = castle_right.to_string(color);
            assert!(
                if color == Color::White {
                    castle_right_str.chars().all(|c| c.is_uppercase())
                } else {
                    castle_right_str.chars().all(|c| c.is_lowercase())
                },
                "Castle right string {} has incorrect casing for color {}",
                castle_right_str,
                color
            );
            let parsed_castle_right = castle_right_str.parse().unwrap();
            assert_eq!(
                castle_right,
                parsed_castle_right,
                "Failed to parse {} back to {}",
                castle_right_str,
                stringify!($func_name)
            );
        }
    }
}