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

uci.c (13753B)


      1 /* uci.c - cliente UCI (Stockfish opcional) y el motor propio como servidor UCI. */
      2 #include <errno.h>
      3 #include <fcntl.h>
      4 #include <limits.h>
      5 #include <poll.h>
      6 #include <pthread.h>
      7 #include <signal.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <strings.h>
     12 #include <sys/wait.h>
     13 #include <unistd.h>
     14 #include "paths.h"
     15 #include "uci.h"
     16 
     17 /* ------------------------------------------------------------ buscar Stockfish */
     18 
     19 static bool exe_ok(const char *p) { return p && *p && access(p, X_OK) == 0; }
     20 
     21 const char *stockfish_path(void)
     22 {
     23     static char path[PATH_MAX];
     24     static int state;                  /* 0 sin buscar, 1 encontrado, 2 no hay */
     25     if (state) return state == 1 ? path : 0;
     26     state = 2;
     27     const char *env = getenv("CHESS_STOCKFISH");
     28     if (exe_ok(env)) { snprintf(path, sizeof path, "%s", env); state = 1; return path; }
     29     /* engine/stockfish al lado del ejecutable, o el que bajo get-stockfish.sh del bash */
     30     char dir[PATH_MAX - 64];
     31     ssize_t n = readlink("/proc/self/exe", dir, sizeof dir - 1);
     32     if (n > 0) {
     33         dir[n] = 0;
     34         char *slash = strrchr(dir, '/');
     35         if (slash) *slash = 0;
     36         static const char *const rel[] = { "engine/stockfish", "../ajedrez/engine/stockfish" };
     37         for (int i = 0; i < 2; i++) {
     38             snprintf(path, sizeof path, "%s/%s", dir, rel[i]);
     39             if (exe_ok(path)) { state = 1; return path; }
     40         }
     41     }
     42     const char *pe = getenv("PATH");
     43     while (pe && *pe) {
     44         size_t len = strcspn(pe, ":");
     45         snprintf(path, sizeof path, "%.*s/stockfish", (int)len, pe);
     46         if (len && exe_ok(path)) { state = 1; return path; }
     47         pe += len + (pe[len] == ':');
     48     }
     49     return 0;
     50 }
     51 
     52 /* ------------------------------------------------------------ cliente */
     53 
     54 static bool uci_write(Uci *u, const char *s)
     55 {
     56     size_t len = strlen(s);
     57     while (len) {
     58         ssize_t w = write(u->in, s, len);
     59         if (w < 0 && errno == EINTR) continue;
     60         if (w <= 0) return false;
     61         s += w;
     62         len -= (size_t)w;
     63     }
     64     return true;
     65 }
     66 
     67 /* una linea: 1 leida, 0 no llego nada en timeout ms, -1 se cerro */
     68 static int uci_line(Uci *u, char *out, int n, int timeout)
     69 {
     70     for (;;) {
     71         char *nl = memchr(u->buf, '\n', (size_t)u->blen);
     72         if (nl) {
     73             int len = (int)(nl - u->buf);
     74             snprintf(out, (size_t)n, "%.*s", len, u->buf);
     75             out[strcspn(out, "\r")] = 0;
     76             memmove(u->buf, nl + 1, (size_t)(u->blen - len - 1));
     77             u->blen -= len + 1;
     78             return 1;
     79         }
     80         if (u->blen == (int)sizeof u->buf) u->blen = 0;     /* linea monstruosa: se descarta */
     81         struct pollfd pf = { u->out, POLLIN, 0 };
     82         int pr = poll(&pf, 1, timeout);
     83         if (pr < 0 && errno == EINTR) continue;
     84         if (pr <= 0) return pr == 0 ? 0 : -1;
     85         ssize_t r = read(u->out, u->buf + u->blen, sizeof u->buf - (size_t)u->blen);
     86         if (r < 0 && errno == EINTR) continue;
     87         if (r <= 0) return -1;
     88         u->blen += (int)r;
     89     }
     90 }
     91 
     92 /* espera una linea que empiece con want */
     93 static bool uci_wait(Uci *u, const char *want, int timeout)
     94 {
     95     long long end = now_ms() + timeout;
     96     char line[1024];
     97     for (;;) {
     98         long long left = end - now_ms();
     99         if (left <= 0) return false;
    100         int r = uci_line(u, line, sizeof line, (int)left);
    101         if (r < 0) return false;
    102         if (r > 0 && !strncmp(line, want, strlen(want))) return true;
    103     }
    104 }
    105 
    106 bool uci_open(Uci *u, const char *path)
    107 {
    108     memset(u, 0, sizeof *u);
    109     u->pid = -1;
    110     u->in = u->out = -1;
    111     if (!path) return false;
    112     signal(SIGPIPE, SIG_IGN);
    113     int to[2], from[2];
    114     if (pipe(to)) return false;
    115     if (pipe(from)) { close(to[0]); close(to[1]); return false; }
    116     for (int i = 0; i < 2; i++) { fcntl(to[i], F_SETFD, FD_CLOEXEC); fcntl(from[i], F_SETFD, FD_CLOEXEC); }
    117     pid_t pid = fork();
    118     if (pid < 0) { close(to[0]); close(to[1]); close(from[0]); close(from[1]); return false; }
    119     if (pid == 0) {
    120         dup2(to[0], 0);
    121         dup2(from[1], 1);
    122         int nul = open("/dev/null", O_WRONLY);
    123         if (nul >= 0) dup2(nul, 2);
    124         execl(path, path, (char *)0);
    125         _exit(127);
    126     }
    127     close(to[0]);
    128     close(from[1]);
    129     u->pid = pid;
    130     u->in = to[1];
    131     u->out = from[0];
    132     if (!uci_write(u, "uci\n") || !uci_wait(u, "uciok", 5000) ||
    133         !uci_write(u, "setoption name Hash value 64\nisready\n") || !uci_wait(u, "readyok", 5000)) {
    134         uci_close(u);
    135         return false;
    136     }
    137     return true;
    138 }
    139 
    140 void uci_close(Uci *u)
    141 {
    142     if (u->pid > 0) {
    143         uci_write(u, "quit\n");
    144         int i;
    145         for (i = 0; i < 20 && waitpid(u->pid, 0, WNOHANG) == 0; i++) usleep(10000);
    146         if (i == 20) { kill(u->pid, SIGKILL); waitpid(u->pid, 0, 0); }
    147     }
    148     if (u->in >= 0) close(u->in);
    149     if (u->out >= 0) close(u->out);
    150     u->pid = -1;
    151     u->in = u->out = -1;
    152 }
    153 
    154 /* "score cp 35" / "score mate -3" -> puntaje del motor propio */
    155 static int parse_score(const char *kind, int v)
    156 {
    157     if (!strcmp(kind, "mate")) return v > 0 ? MATE - (2 * v - 1) : -MATE + 2 * -v;
    158     return v;
    159 }
    160 
    161 bool uci_search(Uci *u, const Game *g, int movetime, volatile int *stop, SearchResult *r)
    162 {
    163     memset(r, 0, sizeof *r);
    164     if (u->pid <= 0) return false;
    165     size_t cap = 128 + (size_t)g->nply * 6;
    166     char *cmd = malloc(cap);
    167     if (!cmd) return false;
    168     Pos std;
    169     pos_start(&std);
    170     int n;
    171     if (g->start.hash == std.hash) n = snprintf(cmd, cap, "position startpos");
    172     else {
    173         char fen[100];
    174         pos_fen(&g->start, fen, sizeof fen);
    175         n = snprintf(cmd, cap, "position fen %s", fen);
    176     }
    177     if (g->nply) n += snprintf(cmd + n, cap - (size_t)n, " moves");
    178     for (int i = 0; i < g->nply; i++) {
    179         char m[8];
    180         move_uci(g->mv[i], m, sizeof m);
    181         n += snprintf(cmd + n, cap - (size_t)n, " %s", m);
    182     }
    183     snprintf(cmd + n, cap - (size_t)n, "\ngo movetime %d\n", movetime);
    184     bool ok = uci_write(u, cmd);
    185     free(cmd);
    186     if (!ok) return false;
    187 
    188     long long t0 = now_ms();
    189     bool stopped = false;
    190     char line[4096];
    191     for (;;) {
    192         long long el = now_ms() - t0;
    193         if (!stopped && (*stop || el > movetime + 5000)) { stopped = true; if (!uci_write(u, "stop\n")) return false; }
    194         if (el > movetime + 10000) { uci_close(u); return false; }     /* colgado */
    195         int rl = uci_line(u, line, sizeof line, 50);
    196         if (rl < 0) { uci_close(u); return false; }
    197         if (!rl) continue;
    198         char *save = 0, *tok = strtok_r(line, " ", &save);
    199         if (!tok) continue;
    200         if (!strcmp(tok, "info")) {
    201             while ((tok = strtok_r(0, " ", &save))) {
    202                 if (!strcmp(tok, "depth")) { tok = strtok_r(0, " ", &save); if (tok) r->depth = atoi(tok); }
    203                 else if (!strcmp(tok, "nodes")) { tok = strtok_r(0, " ", &save); if (tok) r->nodes = (uint32_t)strtoul(tok, 0, 10); }
    204                 else if (!strcmp(tok, "multipv")) {
    205                     tok = strtok_r(0, " ", &save);
    206                     if (tok && atoi(tok) != 1) break;
    207                 } else if (!strcmp(tok, "score")) {
    208                     char *kind = strtok_r(0, " ", &save), *v = strtok_r(0, " ", &save);
    209                     if (kind && v) r->score = parse_score(kind, atoi(v));
    210                 } else if (!strcmp(tok, "pv")) {
    211                     /* la pv la armamos al final desde bestmove; con la primera alcanza */
    212                     break;
    213                 }
    214             }
    215         } else if (!strcmp(tok, "bestmove")) {
    216             tok = strtok_r(0, " ", &save);
    217             r->best = tok ? move_from_uci(&g->pos, tok) : MOVE_NONE;
    218             if (r->best) { r->pv[0] = r->best; r->npv = 1; }
    219             r->ms = (uint32_t)(now_ms() - t0);
    220             return r->best != MOVE_NONE;
    221         }
    222     }
    223 }
    224 
    225 /* ------------------------------------------------------------ servidor: ajedrez --uci */
    226 
    227 #define UCI_TT (1u << 20)
    228 
    229 static struct {
    230     Engine       e;
    231     TTEntry     *tt;
    232     Game         g;
    233     int          level;
    234     SearchLimits lim;
    235     SearchResult r;
    236     pthread_t    th;
    237     bool         busy;
    238     volatile int stop;
    239 } U;
    240 
    241 static uint32_t mono32(void) { return (uint32_t)now_ms(); }
    242 static bool uci_poll(void *ctx) { (void)ctx; return U.stop != 0; }
    243 
    244 static void *search_thread(void *arg)
    245 {
    246     (void)arg;
    247     SearchResult *r = &U.r;
    248     engine_search(&U.e, &U.g, &U.lim, r);
    249     char out[512], m[8];
    250     int n = 0;
    251     if (r->depth > 0) {
    252         int s = r->score;
    253         if (MATE_IN(s)) n = snprintf(out, sizeof out, "info depth %d score mate %d", r->depth, s > 0 ? (MATE - s + 1) / 2 : -(MATE + s) / 2);
    254         else n = snprintf(out, sizeof out, "info depth %d score cp %d", r->depth, s);
    255         n += snprintf(out + n, sizeof out - (size_t)n, " nodes %u time %u nps %u pv", r->nodes, r->ms,
    256                       r->ms ? (unsigned)((uint64_t)r->nodes * 1000 / r->ms) : 0);
    257         for (int i = 0; i < r->npv && n < (int)sizeof out - 8; i++) {
    258             move_uci(r->pv[i], m, sizeof m);
    259             n += snprintf(out + n, sizeof out - (size_t)n, " %s", m);
    260         }
    261         printf("%s\n", out);
    262     }
    263     if (r->best) move_uci(r->best, m, sizeof m);
    264     else strcpy(m, "0000");
    265     printf("bestmove %s\n", m);
    266     fflush(stdout);
    267     return 0;
    268 }
    269 
    270 static void stop_search(void)
    271 {
    272     if (!U.busy) return;
    273     U.stop = 1;
    274     pthread_join(U.th, 0);
    275     U.busy = false;
    276 }
    277 
    278 static void cmd_position(char *save)
    279 {
    280     Pos start;
    281     pos_start(&start);
    282     char *tok = strtok_r(0, " ", &save);
    283     if (tok && !strcmp(tok, "fen")) {
    284         char fen[128] = "";
    285         while ((tok = strtok_r(0, " ", &save)) && strcmp(tok, "moves")) {
    286             if (strlen(fen) + strlen(tok) + 2 >= sizeof fen) break;
    287             if (*fen) strcat(fen, " ");
    288             strcat(fen, tok);
    289         }
    290         if (!pos_from_fen(&start, fen)) return;
    291     } else if (tok) tok = strtok_r(0, " ", &save);     /* startpos [moves] */
    292     TimeCtl tc = { 0, 0, 0 };
    293     game_init(&U.g, &start, &tc, false);
    294     if (!tok || strcmp(tok, "moves")) return;
    295     while ((tok = strtok_r(0, " ", &save))) {
    296         Act a = { ACT_MOVE, (int8_t)U.g.pos.side, move_from_uci(&U.g.pos, tok), 0 };
    297         if (!a.m || game_check(&U.g, &a) != E_OK) break;
    298         game_apply(&U.g, &a);
    299     }
    300 }
    301 
    302 /* "go" solo usa los limites del nivel; movetime/wtime/depth/nodes/infinite los pisan */
    303 static void cmd_go(char *save)
    304 {
    305     int32_t t[2] = { -1, -1 }, inc[2] = { 0, 0 }, mtg = 0, movetime = 0, depth = 0;
    306     uint32_t nodes = 0;
    307     bool infinite = false;
    308     char *tok;
    309     while ((tok = strtok_r(0, " ", &save))) {
    310         char *v = !strcmp(tok, "infinite") ? 0 : strtok_r(0, " ", &save);
    311         long x = v ? atol(v) : 0;
    312         if (!strcmp(tok, "infinite")) infinite = true;
    313         else if (!strcmp(tok, "wtime")) t[WHITE] = (int32_t)x;
    314         else if (!strcmp(tok, "btime")) t[BLACK] = (int32_t)x;
    315         else if (!strcmp(tok, "winc")) inc[WHITE] = (int32_t)x;
    316         else if (!strcmp(tok, "binc")) inc[BLACK] = (int32_t)x;
    317         else if (!strcmp(tok, "movestogo")) mtg = (int32_t)x;
    318         else if (!strcmp(tok, "movetime")) movetime = (int32_t)x;
    319         else if (!strcmp(tok, "depth")) depth = (int32_t)x;
    320         else if (!strcmp(tok, "nodes")) nodes = (uint32_t)x;
    321     }
    322     SearchLimits *lim = &U.lim;
    323     level_limits(U.level, lim);
    324     int side = U.g.pos.side;
    325     if (infinite) { lim->movetime = 0; lim->max_depth = 0; lim->use_book = false; }
    326     else if (movetime > 0) lim->movetime = movetime;
    327     else if (t[side] >= 0) {
    328         int32_t left = t[side], budget = mtg > 0 ? left / (mtg + 1) : left / 30 + inc[side] / 2;
    329         if (budget > left - 50) budget = left - 50;
    330         if (budget < 10) budget = 10;
    331         if (budget < lim->movetime) lim->movetime = budget;
    332     } else if (depth || nodes) lim->movetime = 0;
    333     if (depth > 0) lim->max_depth = depth;
    334     if (nodes) lim->max_nodes = nodes;
    335     U.stop = 0;
    336     if (pthread_create(&U.th, 0, search_thread, 0) == 0) U.busy = true;
    337 }
    338 
    339 int app_uci(void)
    340 {
    341     setvbuf(stdout, 0, _IOLBF, 0);
    342     U.tt = calloc(UCI_TT, sizeof(TTEntry));
    343     engine_init(&U.e, U.tt, U.tt ? UCI_TT : 0, mono32);
    344     U.e.poll = uci_poll;
    345     U.level = LEVEL_COUNT - 1;
    346     Pos start;
    347     pos_start(&start);
    348     TimeCtl tc = { 0, 0, 0 };
    349     game_init(&U.g, &start, &tc, false);
    350     char line[1 << 16];
    351     while (fgets(line, sizeof line, stdin)) {
    352         line[strcspn(line, "\r\n")] = 0;
    353         char *save = 0, *tok = strtok_r(line, " ", &save);
    354         if (!tok) continue;
    355         if (!strcmp(tok, "uci")) {
    356             printf("id name Ajedrez-C\nid author Pancho\n"
    357                    "option name Level type spin default %d min 0 max %d\nuciok\n", LEVEL_COUNT - 1, LEVEL_COUNT - 1);
    358         } else if (!strcmp(tok, "isready")) printf("readyok\n");
    359         else if (!strcmp(tok, "setoption")) {
    360             /* setoption name Level value N */
    361             char *name = 0, *val = 0;
    362             while ((tok = strtok_r(0, " ", &save))) {
    363                 if (!strcmp(tok, "name")) name = strtok_r(0, " ", &save);
    364                 else if (!strcmp(tok, "value")) val = strtok_r(0, " ", &save);
    365             }
    366             if (name && val && !strcasecmp(name, "Level")) {
    367                 int lv = atoi(val);
    368                 U.level = lv < 0 ? 0 : lv >= LEVEL_COUNT ? LEVEL_COUNT - 1 : lv;
    369             }
    370         } else if (!strcmp(tok, "ucinewgame")) { stop_search(); engine_clear(&U.e); }
    371         else if (!strcmp(tok, "position")) { stop_search(); cmd_position(save); }
    372         else if (!strcmp(tok, "go")) { stop_search(); cmd_go(save); }
    373         else if (!strcmp(tok, "stop")) stop_search();
    374         else if (!strcmp(tok, "quit")) break;
    375     }
    376     stop_search();
    377     free(U.tt);
    378     return 0;
    379 }