juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

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 }