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 }