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 std::collections::HashSet;
use timecat::*;

fn move_generator_perft_test(fen: &str, depth: usize, expected_result: usize) {
    let position = ChessPosition::from_str(fen).unwrap();
    let result = MoveGenerator::perft_test(&position, depth);
    assert_eq!(
        result, expected_result,
        "Expected result {expected_result} but got {result} in position {fen}"
    );
    let result = MoveGenerator::perft_test_piecewise(&position, depth);
    assert_eq!(
        result, expected_result,
        "Expected result {expected_result} but got {result} in position {fen}"
    );
}

macro_rules! generate_move_generator_functions {
    ($func_name: ident, $fen: expr, $depth: expr, $expected_result: expr) => {
        #[test]
        fn $func_name() {
            move_generator_perft_test($fen, $depth, $expected_result);
        }
    };

    ($func_name: ident, $fen: expr, $depth: expr, $expected_result: expr,) => {
        generate_move_generator_functions!($func_name, $fen, $depth, $expected_result);
    };
}

generate_move_generator_functions!(
    move_generator_perft_kiwipete,
    "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1",
    5,
    193690690,
);
generate_move_generator_functions!(
    move_generator_perft_1,
    "8/5bk1/8/2Pp4/8/1K6/8/8 w - d6 0 1",
    8,
    76172334,
);
generate_move_generator_functions!(
    move_generator_perft_2,
    "8/8/1k6/8/2pP4/8/5BK1/8 b - d3 0 1",
    8,
    76172334,
);
generate_move_generator_functions!(
    move_generator_perft_3,
    "8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1",
    8,
    144302151,
);
generate_move_generator_functions!(
    move_generator_perft_4,
    "8/5k2/8/2Pp4/2B5/1K6/8/8 w - d6 0 1",
    8,
    144302151,
);
generate_move_generator_functions!(
    move_generator_perft_5,
    "5k2/8/8/8/8/8/8/4K2R w K - 0 1",
    8,
    73450134,
);
generate_move_generator_functions!(
    move_generator_perft_6,
    "4k2r/8/8/8/8/8/8/5K2 b k - 0 1",
    8,
    73450134,
);
generate_move_generator_functions!(
    move_generator_perft_7,
    "3k4/8/8/8/8/8/8/R3K3 w Q - 0 1",
    8,
    91628014,
);
generate_move_generator_functions!(
    move_generator_perft_8,
    "r3k3/8/8/8/8/8/8/3K4 b q - 0 1",
    8,
    91628014,
);
generate_move_generator_functions!(
    move_generator_perft_9,
    "r3k2r/1b4bq/8/8/8/8/7B/R3K2R w KQkq - 0 1",
    5,
    31912360,
);
generate_move_generator_functions!(
    move_generator_perft_10,
    "r3k2r/7b/8/8/8/8/1B4BQ/R3K2R b KQkq - 0 1",
    5,
    31912360,
);
generate_move_generator_functions!(
    move_generator_perft_11,
    "r3k2r/8/3Q4/8/8/5q2/8/R3K2R b KQkq - 0 1",
    5,
    58773923,
);
generate_move_generator_functions!(
    move_generator_perft_12,
    "r3k2r/8/5Q2/8/8/3q4/8/R3K2R w KQkq - 0 1",
    5,
    58773923,
);
generate_move_generator_functions!(
    move_generator_perft_13,
    "2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1",
    7,
    60651209,
);
generate_move_generator_functions!(
    move_generator_perft_14,
    "3K4/8/8/8/8/8/4p3/2k2R2 b - - 0 1",
    7,
    60651209,
);
generate_move_generator_functions!(
    move_generator_perft_15,
    "8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1",
    7,
    197013195,
);
generate_move_generator_functions!(
    move_generator_perft_16,
    "5K2/8/1Q6/2N5/8/1p2k3/8/8 w - - 0 1",
    7,
    197013195,
);
generate_move_generator_functions!(
    move_generator_perft_17,
    "4k3/1P6/8/8/8/8/K7/8 w - - 0 1",
    8,
    20625698,
);
generate_move_generator_functions!(
    move_generator_perft_18,
    "8/k7/8/8/8/8/1p6/4K3 b - - 0 1",
    8,
    20625698,
);
generate_move_generator_functions!(
    move_generator_perft_19,
    "8/P1k5/K7/8/8/8/8/8 w - - 0 1",
    8,
    8110830
);
generate_move_generator_functions!(
    move_generator_perft_20,
    "8/8/8/8/8/k7/p1K5/8 b - - 0 1",
    8,
    8110830
);
generate_move_generator_functions!(
    move_generator_perft_21,
    "K1k5/8/P7/8/8/8/8/8 w - - 0 1",
    11,
    85822924
);
generate_move_generator_functions!(
    move_generator_perft_22,
    "8/8/8/8/8/p7/8/k1K5 b - - 0 1",
    11,
    85822924
);
generate_move_generator_functions!(
    move_generator_perft_23,
    "8/k1P5/8/1K6/8/8/8/8 w - - 0 1",
    9,
    37109897,
);
generate_move_generator_functions!(
    move_generator_perft_24,
    "8/8/8/8/1k6/8/K1p5/8 b - - 0 1",
    9,
    37109897,
);
generate_move_generator_functions!(
    move_generator_perft_25,
    "8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1",
    7,
    104644508,
);
generate_move_generator_functions!(
    move_generator_perft_26,
    "8/5k2/8/5N2/5Q2/2K5/8/8 w - - 0 1",
    7,
    104644508,
);

#[test]
fn move_generator_issue_15() {
    let position = ChessPositionBuilder::from_str(
        "rnbqkbnr/ppp2pp1/4p3/3N4/3PpPp1/8/PPP3PP/R1B1KBNR b KQkq f3 0 1",
    )
    .unwrap()
    .try_into()
    .unwrap();
    _ = MoveGenerator::new_legal(&position);
}

fn move_of(m: &str) -> Move {
    let promo = if m.len() > 4 {
        Some(match m.as_bytes()[4] {
            b'q' => Queen,
            b'r' => Rook,
            b'b' => Bishop,
            b'n' => Knight,
            _ => panic!("unrecognized uci move: {}", m),
        })
    } else {
        None
    };
    Move::new(
        Square::from_str(&m[..2]).unwrap(),
        Square::from_str(&m[2..4]).unwrap(),
        promo,
    )
    .unwrap()
}

#[test]
fn test_masked_move_generator() {
    let position = ChessPosition::from_str(
        "r1bqkb1r/pp3ppp/5n2/2ppn1N1/4pP2/1BN1P3/PPPP2PP/R1BQ1RK1 w kq - 0 9",
    )
    .unwrap();

    let attackers = position.get_piece_mask(Knight);
    let targets = position.opponent_occupied();
    let masked_moves = position.generate_masked_legal_moves(attackers, targets);

    let expected = vec![
        move_of("g5e4"),
        move_of("g5f7"),
        move_of("g5h7"),
        move_of("c3e4"),
        move_of("c3d5"),
    ];

    assert_eq!(
        masked_moves.into_iter().collect::<HashSet<_>>(),
        expected.into_iter().collect()
    );
}