testsAndMisc-archive/C/lichess_random_engine/test_search.c
Krzysztof kuhy Rudnicki 01091c09ce Add tests and fix pre-commit issues across all projects
- C/lichess_random_engine, vocabulary_curve, misc/split,
  1dvelocitysimulator, opening_learner: test suites added
- CPP/miscelanious: tests added
- TS/battery-status, champions_leauge_scores, two-inputs: tests added
- python_pkg/fm24_searcher, wake_alarm: new packages added
- Fix ruff/cppcheck/eslint/clang-format failures
- Update .gitignore for C/C++ build artifacts
2026-04-12 20:45:24 +02:00

191 lines
5.7 KiB
C

/*
* test_search.c - Unit tests for search.c (evaluate, alphabeta).
*/
#include "movegen.h"
#include "search.h"
#include <assert.h>
#include <stdio.h>
/* =========================================================================
* evaluate tests
* ========================================================================= */
static void test_evaluate_startpos_equal(void)
{
Position pos;
set_startpos(&pos);
/* Symmetric position: score from white perspective should be 0 */
assert(evaluate(&pos) == 0);
}
static void test_evaluate_white_extra_queen(void)
{
Position pos;
/* White has an extra queen */
parse_fen(&pos, "4k3/8/8/8/8/8/8/Q3K3 w - - 0 1");
int score = evaluate(&pos);
assert(score > 0); /* White favored */
}
static void test_evaluate_black_extra_queen(void)
{
Position pos;
/* Black has an extra queen */
parse_fen(&pos, "4k1q1/8/8/8/8/8/8/4K3 w - - 0 1");
int score = evaluate(&pos);
assert(score < 0); /* Black favored from white's perspective */
}
static void test_evaluate_symmetric_material(void)
{
Position pos;
/* Equal material: rook vs rook, same side */
parse_fen(&pos, "4k2r/8/8/8/8/8/8/4K2R w - - 0 1");
assert(evaluate(&pos) == 0);
}
static void test_evaluate_pawn_advantage(void)
{
Position pos;
/* White has 2 extra pawns */
parse_fen(&pos, "4k3/8/8/8/8/8/1PP5/4K3 w - - 0 1");
int white_score = evaluate(&pos);
pos.side = BLACK;
int black_score = evaluate(&pos);
assert(white_score > 0);
assert(black_score < 0); /* same position but from black's perspective */
}
static void test_evaluate_all_piece_types(void)
{
Position pos;
/* White: K+Q+R+B+N vs Black: K only */
parse_fen(&pos, "4k3/8/8/8/8/8/8/QRBN1K2 w - - 0 1");
int score = evaluate(&pos);
assert(score > 0);
/* White material: Q=900 + R=500 + B=330 + N=320 = 2050 */
assert(score == 900 + 500 + 330 + 320);
}
static void test_evaluate_black_pieces(void)
{
Position pos;
/* Black: K+Q+R+B+N vs White: K only */
parse_fen(&pos, "4kqrb/4n3/8/8/8/8/8/4K3 w - - 0 1");
int score = evaluate(&pos);
/* From white perspective, should be heavily negative */
assert(score < -1000);
}
/* =========================================================================
* alphabeta tests
* ========================================================================= */
static void test_alphabeta_single_capture(void)
{
Position pos;
/* White rook can capture black queen - best move immediately obvious at depth 1 */
parse_fen(&pos, "4k3/8/8/3q4/3R4/8/8/4K3 w - - 0 1");
PrincipalVariation pv = {.from = -1, .to = -1};
int score = alphabeta(pos, 1, -30000, 30000, &pv);
assert(score > 0); /* Should find winning position */
/* Best move should be d4d5 (rook captures queen on d5) */
assert(pv.from >= 0);
assert(pv.to >= 0);
}
static void test_alphabeta_checkmate_in_one(void)
{
Position pos;
/* White queen delivers checkmate at f7 */
parse_fen(&pos, "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 4 4");
PrincipalVariation pv = {.from = -1, .to = -1};
int score = alphabeta(pos, 2, -30000, 30000, &pv);
/* Depth 2: should find the mating sequence */
(void)score;
assert(pv.from >= 0);
}
static void test_alphabeta_stalemate_score(void)
{
Position pos;
/* Black king stalemated: ensure score is 0 (stalemate = draw)
* k on a8, white queen on c7, white king on c6 */
parse_fen(&pos, "k7/2Q5/2K5/8/8/8/8/8 b - - 0 1");
/* Black to move, stalemated */
PrincipalVariation pv = {.from = -1, .to = -1};
int score = alphabeta(pos, 1, -30000, 30000, &pv);
assert(score == 0); /* stalemate */
}
static void test_alphabeta_checkmate_score(void)
{
Position pos;
/* Black king checkmated (fool's mate) */
parse_fen(&pos, "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3");
/* White is mated; at depth 0 just evaluates material */
PrincipalVariation pv = {.from = -1, .to = -1};
int score = alphabeta(pos, 1, -30000, 30000, &pv);
/* White has no legal moves - should return very negative score */
assert(score < -20000);
}
static void test_alphabeta_depth_zero(void)
{
Position pos;
set_startpos(&pos);
PrincipalVariation pv = {.from = -1, .to = -1};
int score = alphabeta(pos, 0, -30000, 30000, &pv);
/* At depth 0, returns leaf evaluation */
assert(score == 0); /* startpos is equal */
}
static void test_alphabeta_no_pv_null(void)
{
Position pos;
set_startpos(&pos);
/* Pass NULL for pv - should not crash */
int score = alphabeta(pos, 1, -30000, 30000, NULL);
(void)score;
/* Just checking no crash */
assert(1);
}
static void test_alphabeta_beta_cutoff(void)
{
Position pos;
/* Need a position where beta cutoff fires: search at depth 2+ */
set_startpos(&pos);
PrincipalVariation pv = {.from = -1, .to = -1};
int score = alphabeta(pos, 2, -30000, 30000, &pv);
/* Symmetric start - should be near 0 */
(void)score;
assert(pv.from >= 0);
}
int main(void)
{
/* evaluate */
test_evaluate_startpos_equal();
test_evaluate_white_extra_queen();
test_evaluate_black_extra_queen();
test_evaluate_symmetric_material();
test_evaluate_pawn_advantage();
test_evaluate_all_piece_types();
test_evaluate_black_pieces();
/* alphabeta */
test_alphabeta_single_capture();
test_alphabeta_checkmate_in_one();
test_alphabeta_stalemate_score();
test_alphabeta_checkmate_score();
test_alphabeta_depth_zero();
test_alphabeta_no_pv_null();
test_alphabeta_beta_cutoff();
printf("All tests passed (%d tests).\n", 14);
return 0;
}