selftest.c (22348B)
1 /* selftest.c - pruebas del nucleo: perft, reglas, notacion, partida, motor. */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <time.h> 6 #include "../core/chess.h" 7 #include "app.h" 8 9 static int checks, fails; 10 #define CHECK(c) do { checks++; if (!(c)) { fails++; printf("FALLA %s:%d: %s\n", __FILE__, __LINE__, #c); } } while (0) 11 12 static uint32_t mono_ms(void) 13 { 14 struct timespec ts; 15 clock_gettime(CLOCK_MONOTONIC, &ts); 16 return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); 17 } 18 19 /* juega una lista de jugadas UCI/SAN separadas por espacios; false si alguna no vale */ 20 static bool play(Game *g, const char *moves) 21 { 22 char buf[1024]; 23 snprintf(buf, sizeof buf, "%s", moves); 24 for (char *t = strtok(buf, " "); t; t = strtok(0, " ")) { 25 Move m = move_from_san(&g->pos, t); 26 if (!m) { printf(" jugada invalida: %s\n", t); return false; } 27 Act a = { ACT_MOVE, (int8_t)g->pos.side, m, 0 }; 28 if (game_check(g, &a)) return false; 29 game_apply(g, &a); 30 } 31 return true; 32 } 33 34 static void start_fen(Game *g, const char *fen) 35 { 36 Pos p; 37 if (!pos_from_fen(&p, fen)) { printf(" FEN invalido: %s\n", fen); fails++; pos_start(&p); } 38 game_init(g, &p, 0, true); 39 } 40 41 static int sq(const char *s) { return SQ(s[0] - 'a', s[1] - '1'); } 42 static int at(const Game *g, const char *s) { return g->pos.sq[sq(s)]; } 43 44 static char san_buf[16]; 45 static const char *san(const char *fen, const char *uci) 46 { 47 Pos p; 48 pos_from_fen(&p, fen); 49 Move m = move_from_uci(&p, uci); 50 if (!m) return "(ilegal)"; 51 move_san(&p, m, san_buf, sizeof san_buf); 52 return san_buf; 53 } 54 55 /* ------------------------------------------------------------------ perft */ 56 57 /* perft que ademas verifica hash incremental y que deshacer deja todo igual */ 58 static uint64_t perft_check(Pos *p, int depth, int *bad) 59 { 60 MoveList l; 61 pos_moves(p, &l); 62 uint64_t n = 0; 63 int s = p->side; 64 for (int i = 0; i < l.n; i++) { 65 Pos before = *p; 66 Undo u; 67 pos_make(p, l.m[i], &u); 68 if (p->hash != pos_hash(p)) (*bad)++; 69 if (!pos_attacked(p, p->king[s], s ^ 1)) n += depth <= 1 ? 1 : perft_check(p, depth - 1, bad); 70 pos_unmake(p, l.m[i], &u); 71 if (memcmp(&before, p, sizeof before)) (*bad)++; 72 } 73 return n; 74 } 75 76 static void test_perft(bool deep) 77 { 78 static const struct { const char *fen; int depth; uint64_t nodes[6]; } T[] = { 79 { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 5, { 20, 400, 8902, 197281, 4865609 } }, 80 { "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", 4, { 48, 2039, 97862, 4085603 } }, 81 { "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1", 5, { 14, 191, 2812, 43238, 674624 } }, 82 { "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1", 4, { 6, 264, 9467, 422333 } }, 83 { "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8", 4, { 44, 1486, 62379, 2103487 } }, 84 { "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10", 4, { 46, 2079, 89890, 3894594 } }, 85 }; 86 for (size_t i = 0; i < sizeof T / sizeof T[0]; i++) { 87 Pos p; 88 CHECK(pos_from_fen(&p, T[i].fen)); 89 int maxd = deep ? T[i].depth : (T[i].depth > 3 ? 3 : T[i].depth); 90 for (int d = 1; d <= maxd; d++) { 91 uint64_t n; 92 if (d <= 3) { int bad = 0; n = perft_check(&p, d, &bad); CHECK(bad == 0); } 93 else n = perft(&p, d); 94 if (n != T[i].nodes[d - 1]) 95 printf(" perft %zu prof %d: %llu (esperaba %llu)\n", i, d, (unsigned long long)n, (unsigned long long)T[i].nodes[d - 1]); 96 CHECK(n == T[i].nodes[d - 1]); 97 } 98 } 99 } 100 101 /* ------------------------------------------------------------------ reglas (las del bash) */ 102 103 static void test_rules(void) 104 { 105 Game g; 106 /* 1. mate del pastor */ 107 game_init(&g, 0, 0, true); 108 CHECK(play(&g, "e4 e5 Bc4 Nc6 Qh5 Nf6 Qxf7")); 109 CHECK(g.result == R_WHITE && g.reason == RS_MATE); 110 CHECK(at(&g, "f7") == PIECE(WHITE, QUEEN)); 111 /* 2. al paso */ 112 game_init(&g, 0, 0, true); 113 CHECK(play(&g, "e2e4 a7a6 e4e5 d7d5")); 114 CHECK(g.pos.ep == sq("d6")); 115 CHECK(play(&g, "e5d6")); 116 CHECK(at(&g, "d6") == PIECE(WHITE, PAWN) && !at(&g, "d5") && !at(&g, "e5")); 117 /* 3. enroque corto */ 118 start_fen(&g, "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"); 119 CHECK(play(&g, "e1g1")); 120 CHECK(at(&g, "g1") == PIECE(WHITE, KING) && at(&g, "f1") == PIECE(WHITE, ROOK) && !at(&g, "h1") && !at(&g, "e1")); 121 CHECK(g.pos.castle == (CASTLE_BK | CASTLE_BQ)); 122 /* enroque largo negro y derechos al mover la torre */ 123 CHECK(play(&g, "e8c8")); 124 CHECK(at(&g, "c8") == PIECE(BLACK, KING) && at(&g, "d8") == PIECE(BLACK, ROOK) && !at(&g, "a8")); 125 start_fen(&g, "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"); 126 CHECK(play(&g, "h1h2")); 127 CHECK(g.pos.castle == (CASTLE_WQ | CASTLE_BK | CASTLE_BQ)); 128 /* no se enroca en jaque ni pasando por casilla atacada */ 129 start_fen(&g, "4k3/8/8/8/8/8/5r2/R3K2R w KQ - 0 1"); 130 CHECK(!move_from_uci(&g.pos, "e1g1")); 131 CHECK(move_from_uci(&g.pos, "e1c1")); 132 /* 4. coronacion */ 133 start_fen(&g, "4k3/P7/8/8/8/8/8/4K3 w - - 0 1"); 134 CHECK(play(&g, "a7a8q")); 135 CHECK(at(&g, "a8") == PIECE(WHITE, QUEEN) && !at(&g, "a7")); 136 start_fen(&g, "4k3/P7/8/8/8/8/8/4K3 w - - 0 1"); 137 CHECK(play(&g, "a7a8n")); 138 CHECK(at(&g, "a8") == PIECE(WHITE, KNIGHT)); 139 /* 5. ahogado */ 140 start_fen(&g, "7k/8/6K1/8/8/8/5Q2/8 w - - 0 1"); 141 CHECK(play(&g, "f2f7")); 142 CHECK(g.result == R_DRAW && g.reason == RS_STALEMATE); 143 /* 6. 20 jugadas iniciales */ 144 MoveList l; 145 game_init(&g, 0, 0, true); 146 pos_legal(&g.pos, &l); 147 CHECK(l.n == 20); 148 /* 7. repeticiones: 3 se reclaman, 5 terminan solas */ 149 game_init(&g, 0, 0, true); 150 CHECK(play(&g, "g1f3 g8f6 f3g1 f6g8 g1f3 g8f6 f3g1 f6g8")); 151 CHECK(game_repetitions(&g) == 3 && game_can_claim(&g) && g.result == R_NONE); 152 Act claim = { ACT_CLAIM, WHITE, 0, 0 }; 153 CHECK(game_check(&g, &claim) == E_OK); 154 CHECK(play(&g, "g1f3 g8f6 f3g1 f6g8 g1f3 g8f6 f3g1 f6g8")); 155 CHECK(g.result == R_DRAW && g.reason == RS_REP5); 156 game_init(&g, 0, 0, true); 157 CHECK(play(&g, "g1f3 g8f6 f3g1")); 158 CHECK(game_check(&g, &claim) == E_NOCLAIM); 159 /* 8. regla de las 75 jugadas (y las 50 se reclaman) */ 160 start_fen(&g, "4k3/8/8/8/8/8/8/4K2R w - - 149 200"); 161 CHECK(play(&g, "h1h2")); 162 CHECK(g.result == R_DRAW && g.reason == RS_MOVE75); 163 start_fen(&g, "4k3/8/8/8/8/8/8/4K2R w - - 99 200"); 164 CHECK(play(&g, "h1h2")); 165 CHECK(g.result == R_NONE && game_can_claim(&g)); 166 /* material insuficiente */ 167 static const struct { const char *fen; bool dead; } M[] = { 168 { "4k3/8/8/8/8/8/8/4K3 w - - 0 1", true }, 169 { "4k3/8/8/8/8/8/8/4KB2 w - - 0 1", true }, 170 { "4k3/8/8/8/8/8/8/4KN2 w - - 0 1", true }, 171 { "4kb2/8/8/8/8/8/8/4KB2 w - - 0 1", false }, /* alfiles de distinto color */ 172 { "4k1b1/8/8/8/8/8/8/4KB2 w - - 0 1", true }, /* mismo color */ 173 { "4k3/8/8/8/8/8/8/3NKN2 w - - 0 1", false }, 174 { "4k3/8/8/8/8/8/4P3/4K3 w - - 0 1", false }, 175 }; 176 for (size_t i = 0; i < sizeof M / sizeof M[0]; i++) { 177 Pos p; 178 CHECK(pos_from_fen(&p, M[i].fen)); 179 if (pos_insufficient(&p) != M[i].dead) printf(" material: %s\n", M[i].fen); 180 CHECK(pos_insufficient(&p) == M[i].dead); 181 } 182 start_fen(&g, "4k3/8/8/8/8/8/3r4/4KB2 w - - 0 1"); 183 CHECK(play(&g, "e1d2")); 184 CHECK(g.result == R_DRAW && g.reason == RS_INSUFFICIENT); 185 } 186 187 /* ------------------------------------------------------------------ FEN */ 188 189 static void test_fen(void) 190 { 191 static const char *OK[] = { 192 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 193 "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", 194 "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1", 195 "rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3", 196 }; 197 for (size_t i = 0; i < sizeof OK / sizeof OK[0]; i++) { 198 Pos p; 199 char out[100]; 200 CHECK(pos_from_fen(&p, OK[i])); 201 pos_fen(&p, out, sizeof out); 202 if (strcmp(out, OK[i])) printf(" fen: %s -> %s\n", OK[i], out); 203 CHECK(!strcmp(out, OK[i])); 204 } 205 static const char *BAD[] = { 206 "", 207 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP w KQkq - 0 1", /* 7 filas */ 208 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN w KQkq - 0 1", /* fila corta */ 209 "rnbq1bnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1", /* sin rey negro */ 210 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKKBNR w kq - 0 1", /* dos reyes */ 211 "Pnbqkbnr/pppppppp/8/8/8/8/1PPPPPPP/RNBQKBNR w KQkq - 0 1", /* peon en la 8 */ 212 "4k3/8/8/8/8/8/8/4K2R b - - 0 1x", /* basura: pasa (numero raro) */ 213 "4k2R/8/8/8/8/8/8/4K3 w - - 0 1", /* negro en jaque y mueven blancas */ 214 "4k3/8/8/8/8/8/8/4K3 x - - 0 1", 215 "4k3/8/8/8/8/8/8/4K3 w - e5 0 1", 216 "4k3/8/8/8/8/8/8/4K3 w XY - 0 1", 217 "4k3/8/8/8/8/8/8/4K3 w - - 0 0", 218 }; 219 for (size_t i = 0; i < sizeof BAD / sizeof BAD[0]; i++) { 220 Pos p; 221 bool ok = pos_from_fen(&p, BAD[i]); 222 if (i == 6) continue; 223 if (ok) printf(" fen aceptado y no deberia: %s\n", BAD[i]); 224 CHECK(!ok); 225 } 226 /* enroques incoherentes se descartan; al paso sin peon que lo haya hecho tambien */ 227 Pos p; 228 CHECK(pos_from_fen(&p, "4k3/8/8/8/8/8/8/4K3 w KQkq e6 0 1")); 229 CHECK(p.castle == 0 && p.ep == -1); 230 } 231 232 /* ------------------------------------------------------------------ notacion */ 233 234 static void test_san(void) 235 { 236 const char *START = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; 237 CHECK(!strcmp(san(START, "e2e4"), "e4")); 238 CHECK(!strcmp(san(START, "g1f3"), "Nf3")); 239 CHECK(!strcmp(san("rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2", "e4d5"), "exd5")); 240 CHECK(!strcmp(san("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1", "e1g1"), "O-O")); 241 CHECK(!strcmp(san("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1", "e1c1"), "O-O-O")); 242 CHECK(!strcmp(san("8/P7/7k/8/8/8/8/K7 w - - 0 1", "a7a8q"), "a8=Q")); 243 CHECK(!strcmp(san("7k/P7/8/8/8/8/8/K7 w - - 0 1", "a7a8q"), "a8=Q+")); 244 CHECK(!strcmp(san("6k1/8/8/8/8/8/8/R5K1 w - - 0 1", "a1a8"), "Ra8+")); 245 CHECK(!strcmp(san("6k1/5ppp/8/8/8/8/8/R5K1 w - - 0 1", "a1a8"), "Ra8#")); 246 CHECK(!strcmp(san("4k3/8/8/8/8/1N3N2/8/4K3 w - - 0 1", "b3d4"), "Nbd4")); 247 CHECK(!strcmp(san("4k3/8/8/1N6/8/1N6/8/4K3 w - - 0 1", "b3d4"), "N3d4")); 248 CHECK(!strcmp(san("4k3/8/8/1N6/8/1N3N2/8/4K3 w - - 0 1", "b3d4"), "Nb3d4")); 249 /* lectura: SAN con adornos, 0-0, UCI, coronacion sin "=" */ 250 Pos p; 251 pos_from_fen(&p, "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"); 252 CHECK(move_from_san(&p, "0-0") == MOVE(4, 6, MF_OO)); 253 CHECK(move_from_san(&p, "O-O-O!?") == MOVE(4, 2, MF_OOO)); 254 pos_from_fen(&p, "8/P7/7k/8/8/8/8/K7 w - - 0 1"); 255 CHECK(MPROMO(move_from_san(&p, "a8Q")) == QUEEN); 256 CHECK(MPROMO(move_from_san(&p, "a8=N")) == KNIGHT); 257 CHECK(MPROMO(move_from_san(&p, "a7a8r")) == ROOK); 258 CHECK(!move_from_san(&p, "a8")); 259 CHECK(!move_from_san(&p, "Qh5")); 260 CHECK(!move_from_san(&p, "e2e4")); 261 } 262 263 /* ------------------------------------------------------------------ partida */ 264 265 static void test_game(void) 266 { 267 Game g; 268 /* reloj: 5 min + 5 s */ 269 TimeCtl tc = { 300000, 5000, 0 }; 270 game_init(&g, 0, &tc, true); 271 Act a = { ACT_MOVE, WHITE, move_from_uci(&g.pos, "e2e4"), 10000 }; 272 CHECK(game_check(&g, &a) == E_OK); 273 game_apply(&g, &a); 274 CHECK(game_clock(&g, WHITE) == 295000 && game_clock(&g, BLACK) == 300000); 275 Act b = { ACT_MOVE, BLACK, move_from_uci(&g.pos, "e7e5"), 2000 }; 276 game_apply(&g, &b); 277 CHECK(game_clock(&g, BLACK) == 303000); 278 /* jugar fuera de turno */ 279 Act w = { ACT_MOVE, BLACK, move_from_uci(&g.pos, "g1f3"), 0 }; 280 CHECK(game_check(&g, &w) == E_TURN); 281 /* deshacer: pide blancas despues de que negras contestaron -> vuelven 2 jugadas y el tiempo */ 282 Act u = { ACT_UNDO, WHITE, 0, 0 }; 283 CHECK(game_check(&g, &u) == E_OK); 284 game_apply(&g, &u); 285 Act uok = { ACT_UNDOOK, BLACK, 0, 0 }; 286 CHECK(game_check(&g, &uok) == E_OK); 287 game_apply(&g, &uok); 288 CHECK(g.nply == 0 && game_clock(&g, WHITE) == 300000 && g.pos.hash == g.start.hash); 289 /* pide negras justo despues de jugar -> vuelve 1 */ 290 CHECK(play(&g, "e4 e5")); 291 Act ub = { ACT_UNDO, BLACK, 0, 0 }; 292 game_apply(&g, &ub); 293 Act ubok = { ACT_UNDOOK, WHITE, 0, 0 }; 294 game_apply(&g, &ubok); 295 CHECK(g.nply == 1 && g.pos.side == BLACK); 296 /* deshacer despues del mate (terminada por el tablero) */ 297 game_init(&g, 0, 0, true); 298 CHECK(play(&g, "e4 e5 Bc4 Nc6 Qh5 Nf6 Qxf7")); 299 CHECK(g.result == R_WHITE && g.reason == RS_MATE); 300 Act um = { ACT_UNDO, BLACK, 0, 0 }; /* le toca a negras: vuelven 2 */ 301 CHECK(game_check(&g, &um) == E_OK); 302 game_apply(&g, &um); 303 Act umok = { ACT_UNDOOK, WHITE, 0, 0 }; 304 CHECK(game_check(&g, &umok) == E_OK); 305 game_apply(&g, &umok); 306 CHECK(g.result == R_NONE && g.nply == 5 && g.pos.side == BLACK); 307 CHECK(play(&g, "g6")); 308 /* por abandono no se deshace */ 309 Act rs = { ACT_RESIGN, BLACK, 0, 0 }; 310 game_apply(&g, &rs); 311 Act ur = { ACT_UNDO, BLACK, 0, 0 }; 312 CHECK(game_check(&g, &ur) == E_OVER); 313 /* sin permiso no se deshace */ 314 game_init(&g, 0, 0, false); 315 CHECK(play(&g, "e4")); 316 Act u2 = { ACT_UNDO, WHITE, 0, 0 }; 317 CHECK(game_check(&g, &u2) == E_NOUNDO); 318 /* tablas de comun acuerdo, rechazo, abandono */ 319 game_init(&g, 0, 0, true); 320 Act d = { ACT_DRAW, WHITE, 0, 0 }, dno = { ACT_DRAWNO, BLACK, 0, 0 }, dok = { ACT_DRAWOK, BLACK, 0, 0 }; 321 Act dself = { ACT_DRAWOK, WHITE, 0, 0 }; 322 CHECK(game_check(&g, &dok) == E_NOTHING); 323 game_apply(&g, &d); 324 CHECK(game_check(&g, &dself) == E_NOTHING); 325 game_apply(&g, &dno); 326 CHECK(g.draw_offer == 0 && g.result == R_NONE); 327 game_apply(&g, &d); 328 CHECK(play(&g, "e4")); /* jugar cancela la oferta */ 329 CHECK(g.draw_offer == 0); 330 Act d2 = { ACT_DRAW, BLACK, 0, 0 }; 331 game_apply(&g, &d2); 332 Act dok2 = { ACT_DRAWOK, WHITE, 0, 0 }; 333 CHECK(game_check(&g, &dok2) == E_OK); 334 game_apply(&g, &dok2); 335 CHECK(g.result == R_DRAW && g.reason == RS_AGREED); 336 CHECK(game_check(&g, &d) == E_OVER); 337 game_init(&g, 0, 0, true); 338 Act r = { ACT_RESIGN, WHITE, 0, 0 }; 339 game_apply(&g, &r); 340 CHECK(g.result == R_BLACK && g.reason == RS_RESIGN); 341 /* bandera: sin material para ganar es tablas */ 342 Pos p; 343 pos_from_fen(&p, "4k3/8/8/8/8/8/8/3QK3 b - - 0 1"); 344 TimeCtl t1 = { 60000, 0, 0 }; 345 game_init(&g, &p, &t1, false); 346 Act fl = { ACT_FLAG, WHITE, 0, 61000 }; 347 CHECK(game_check(&g, &fl) == E_ARGS); /* no le toca a blancas */ 348 fl.c = BLACK; 349 CHECK(game_check(&g, &fl) == E_OK); 350 game_apply(&g, &fl); 351 CHECK(g.result == R_WHITE && g.reason == RS_TIMEOUT); 352 pos_from_fen(&p, "4k3/8/8/8/8/8/8/3QK3 w - - 0 1"); 353 game_init(&g, &p, &t1, false); 354 fl.c = WHITE; 355 game_apply(&g, &fl); 356 CHECK(g.result == R_DRAW && g.reason == RS_TIMEOUT_DRAW); 357 /* jugada hecha con el tiempo pasado pierde */ 358 game_init(&g, 0, &t1, false); 359 Act late = { ACT_MOVE, WHITE, move_from_uci(&g.pos, "e2e4"), 70000 }; 360 game_apply(&g, &late); 361 CHECK(g.result == R_BLACK && g.reason == RS_TIMEOUT); 362 /* correspondencia: cada jugada tiene su plazo */ 363 TimeCtl cor = { 0, 0, 3 * 86400000 }; 364 game_init(&g, 0, &cor, false); 365 Act c1 = { ACT_MOVE, WHITE, move_from_uci(&g.pos, "e2e4"), 2 * 86400000 }; 366 game_apply(&g, &c1); 367 CHECK(g.result == R_NONE && game_clock(&g, BLACK) == 3 * 86400000); 368 369 /* texto de acciones y cabecera: ida y vuelta, y replay igual */ 370 TimeCtl tc2 = { 180000, 2000, 0 }; 371 Pos sp; 372 pos_from_fen(&sp, "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"); 373 game_init(&g, &sp, &tc2, true); 374 CHECK(play(&g, "e1g1 e8c8 f1f7 d8d1")); 375 Act extra = { ACT_DRAW, WHITE, 0, 0 }; 376 game_apply(&g, &extra); 377 char hdr[160], line[64]; 378 game_hdr_fmt(&g, hdr, sizeof hdr); 379 Game h; 380 CHECK(game_hdr_parse(hdr, &h)); 381 CHECK(h.tc.base == 180000 && h.tc.inc == 2000 && h.allow_undo && h.start.hash == g.start.hash); 382 Pos q = g.start; 383 for (int i = 0; i < g.nply; i++) { 384 Act x = { ACT_MOVE, (int8_t)q.side, g.mv[i], 1234 + i }; 385 act_fmt(&x, line, sizeof line); 386 Act y; 387 CHECK(act_parse(line, &y)); 388 CHECK(act_resolve(&h, &y)); 389 CHECK(y.m == g.mv[i] && y.ms == 1234 + i && y.c == x.c); 390 CHECK(game_check(&h, &y) == E_OK); 391 game_apply(&h, &y); 392 Undo uu; 393 pos_make(&q, g.mv[i], &uu); 394 } 395 CHECK(h.pos.hash == g.pos.hash && !memcmp(h.pos.sq, g.pos.sq, 64)); 396 Act z; 397 CHECK(act_parse("1 DRAWNO", &z) && z.type == ACT_DRAWNO && z.c == 1); 398 CHECK(act_parse("0 FLAG 61000", &z) && z.type == ACT_FLAG && z.ms == 61000); 399 CHECK(!act_parse("2 RESIGN", &z)); 400 CHECK(!act_parse("0 MOVE e9e4 0", &z)); 401 CHECK(!act_parse("0 BAILAR", &z)); 402 CHECK(act_parse("0 MOVE e7e8q 5", &z) && MIS_PROMO(z.m)); 403 } 404 405 /* ------------------------------------------------------------------ PGN */ 406 407 static void test_pgn(void) 408 { 409 Game g, h; 410 char buf[4096], err[80]; 411 game_init(&g, 0, 0, true); 412 CHECK(play(&g, "e4 e5 Nf3 Nc6 Bb5 a6 Bxc6 dxc6")); 413 CHECK(g.nply == 8); 414 Pos p = g.start; 415 for (int i = 0; i < 6; i++) { Undo u; pos_make(&p, g.mv[i], &u); } 416 char s[16]; 417 move_san(&p, g.mv[6], s, sizeof s); 418 CHECK(!strcmp(s, "Bxc6")); 419 PgnTags t = { "Ana", "Beto", "", "", "", "" }; 420 CHECK(game_pgn(&g, &t, buf, sizeof buf) > 0); 421 CHECK(strstr(buf, "[White \"Ana\"]") && strstr(buf, "4. Bxc6 dxc6 *")); 422 PgnTags t2; 423 CHECK(game_from_pgn(&h, buf, &t2, err, sizeof err)); 424 CHECK(h.nply == 8 && h.pos.hash == g.pos.hash && !strcmp(t2.white, "Ana")); 425 /* comentarios, variantes, NAGs, numeros pegados, resultado */ 426 const char *messy = "[Event \"x\"]\n[Black \"Z\"]\n\n1.e4 {buena} e5 (1...c5 2.Nf3 (2.c3)) 2.Nf3 $1 Nc6 ; comentario\n3.Bb5 a6 1-0\n"; 427 CHECK(game_from_pgn(&h, messy, &t2, err, sizeof err)); 428 CHECK(h.nply == 6 && !strcmp(t2.black, "Z")); 429 /* posicion inicial con FEN y negras primero */ 430 Pos sp; 431 pos_from_fen(&sp, "4k3/8/8/8/8/8/4P3/4K3 b - - 0 30"); 432 game_init(&g, &sp, 0, true); 433 CHECK(play(&g, "Kd7 e4 Ke6")); 434 game_pgn(&g, 0, buf, sizeof buf); 435 CHECK(strstr(buf, "[SetUp \"1\"]") && strstr(buf, "30... Kd7 31. e4 Ke6")); 436 CHECK(game_from_pgn(&h, buf, 0, err, sizeof err)); 437 CHECK(h.nply == 3 && h.pos.hash == g.pos.hash && h.start.fullmove == 30); 438 /* lista de jugadas suelta y jugada invalida */ 439 CHECK(game_from_pgn(&h, "e2e4 e7e5 g1f3", 0, err, sizeof err) && h.nply == 3); 440 CHECK(!game_from_pgn(&h, "1. e4 e5 2. Ke3", 0, err, sizeof err)); 441 CHECK(strstr(err, "Ke3") != 0); 442 } 443 444 /* ------------------------------------------------------------------ aperturas */ 445 446 static void test_openings(void) 447 { 448 Game g; 449 game_init(&g, 0, 0, true); 450 CHECK(play(&g, "e4 e5 Nf3 Nc6 Bb5")); 451 const char *eco = 0, *name = opening_name(g.mv, g.nply, &g.start, &eco); 452 CHECK(name && strstr(name, "Ruy Lopez")); 453 if (name) printf(" apertura: %s %s\n", eco, name); 454 Rng r; 455 rng_seed(&r, 1, 1); 456 game_init(&g, 0, 0, true); 457 for (int i = 0; i < 6; i++) { 458 Move m = book_move(g.mv, g.nply, &g.start, &r); 459 if (!m) break; 460 CHECK(pos_is_legal(&g.pos, m)); 461 Act a = { ACT_MOVE, (int8_t)g.pos.side, m, 0 }; 462 game_apply(&g, &a); 463 } 464 CHECK(g.nply >= 2); 465 } 466 467 /* ------------------------------------------------------------------ motor */ 468 469 static TTEntry tt[1 << 18]; 470 471 static void test_engine(bool deep) 472 { 473 Engine e; 474 engine_init(&e, tt, 1 << 18, mono_ms); 475 SearchLimits lim; 476 memset(&lim, 0, sizeof lim); 477 SearchResult r; 478 Game g; 479 /* mate en 1 y en 2 */ 480 start_fen(&g, "6k1/5ppp/8/8/8/8/8/R5K1 w - - 0 1"); 481 lim.max_depth = 4; 482 engine_search(&e, &g, &lim, &r); 483 CHECK(r.best == move_from_uci(&g.pos, "a1a8") && r.score >= MATE - 2); 484 start_fen(&g, "r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); 485 engine_search(&e, &g, &lim, &r); 486 CHECK(r.best == move_from_uci(&g.pos, "h5f7")); 487 /* no regala la dama */ 488 start_fen(&g, "4k3/8/8/3q4/8/8/3R4/4K3 b - - 0 1"); 489 lim.max_depth = 5; 490 engine_search(&e, &g, &lim, &r); 491 CHECK(r.best && r.best != move_from_uci(&g.pos, "d5d2")); /* Dxd2+ Rxd2 pierde la dama */ 492 /* puzzles: la primera jugada tiene que ser la de la solucion (o un mate igual de rapido), 493 * y la solucion entera tiene que ser legal */ 494 int solved = 0, total = 0; 495 for (int k = 0; k < NPUZZLES; k++) { 496 const Puzzle *pz = &PUZZLES[k]; 497 total++; 498 start_fen(&g, pz->fen); 499 CHECK(play(&g, pz->solution)); 500 start_fen(&g, pz->fen); 501 char first[8]; 502 sscanf(pz->solution, "%7s", first); 503 memset(&lim, 0, sizeof lim); 504 lim.movetime = deep ? 3000 : 1000; 505 engine_clear(&e); 506 engine_search(&e, &g, &lim, &r); 507 Move want = move_from_uci(&g.pos, first); 508 bool ok = r.best == want; 509 if (!ok && MATE_IN(r.score) && r.score > 0) { 510 SearchResult r2; 511 Game g2 = g; 512 Act a = { ACT_MOVE, (int8_t)g2.pos.side, want, 0 }; 513 game_apply(&g2, &a); 514 memset(&lim, 0, sizeof lim); 515 lim.movetime = 1000; 516 engine_search(&e, &g2, &lim, &r2); 517 ok = g2.result == R_WHITE || g2.result == R_BLACK || -r2.score <= r.score; 518 } 519 char u[8]; 520 move_uci(r.best, u, sizeof u); 521 if (!ok) printf(" puzzle '%s': jugo %s (score %d), solucion %s\n", pz->name[0], u, r.score, first); 522 solved += ok; 523 } 524 printf(" puzzles resueltos: %d/%d\n", solved, total); 525 CHECK(total > 0 && solved == total); 526 } 527 528 int app_selftest(bool deep) 529 { 530 chess_init(); 531 uint32_t t0 = mono_ms(); 532 test_perft(deep); 533 test_rules(); 534 test_fen(); 535 test_san(); 536 test_game(); 537 test_pgn(); 538 test_openings(); 539 test_engine(deep); 540 printf("%d chequeos, %d fallas (%u ms)\n", checks, fails, mono_ms() - t0); 541 return fails ? 1 : 0; 542 }