session.c (12465B)
1 /* session.c - partida local (autoridad + motor en otro hilo + guardado) y vista remota. */ 2 #include <errno.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <time.h> 6 #include "session.h" 7 #include "net.h" 8 9 #define TT_SIZE (1u << 20) 10 11 bool latest_save(char *out, int n) { return latest_file(".ajedrez", out, n); } 12 13 static uint32_t mono32(void) { return (uint32_t)now_ms(); } 14 15 /* ------------------------------------------------------------ motor */ 16 17 static bool engine_poll(void *ctx) { return ((Session *)ctx)->stop != 0; } 18 19 static void *engine_thread(void *arg) 20 { 21 Session *s = arg; 22 if (s->job_sf) { 23 if (s->sf.pid <= 0 && !s->sf_failed && !uci_open(&s->sf, stockfish_path())) s->sf_failed = true; 24 if (!s->sf_failed && uci_search(&s->sf, &s->job_game, s->job_lim.movetime, &s->stop, &s->job_res)) goto done; 25 if (s->sf.pid <= 0) s->sf_failed = true; 26 } 27 engine_search(&s->eng, &s->job_game, &s->job_lim, &s->job_res); 28 done: 29 __atomic_store_n(&s->done, 1, __ATOMIC_RELEASE); 30 return 0; 31 } 32 33 static void engine_start(Session *s, bool move, bool sf, const SearchLimits *lim) 34 { 35 if (!s->tt) { 36 s->tt = calloc(TT_SIZE, sizeof(TTEntry)); 37 engine_init(&s->eng, s->tt, s->tt ? TT_SIZE : 0, mono32); 38 s->eng.poll = engine_poll; 39 s->eng.ctx = s; 40 } 41 s->job_game = s->g; 42 s->job_lim = *lim; 43 s->job_move = move; 44 s->job_sf = sf; 45 s->stop = 0; 46 s->done = 0; 47 if (pthread_create(&s->th, 0, engine_thread, s) == 0) s->busy = true; 48 } 49 50 static void engine_stop(Session *s) 51 { 52 if (!s->busy) return; 53 s->stop = 1; 54 pthread_join(s->th, 0); 55 s->busy = false; 56 } 57 58 bool sess_thinking(const Session *s) { return s->busy && s->job_move; } 59 60 /* ------------------------------------------------------------ guardado */ 61 62 const char *last_pgn_path(void) 63 { 64 static char p[600]; 65 snprintf(p, sizeof p, "%s/ultima.pgn", data_dir()); 66 return p; 67 } 68 69 /* PGN de la partida al lado del guardado, y una copia fija en ultima.pgn */ 70 static void write_pgn(Session *s) 71 { 72 if (!s->pgn_path[0]) return; 73 static char buf[1 << 16]; 74 PgnTags t; 75 memset(&t, 0, sizeof t); 76 snprintf(t.white, sizeof t.white, "%s", s->names[WHITE]); 77 snprintf(t.black, sizeof t.black, "%s", s->names[BLACK]); 78 snprintf(t.event, sizeof t.event, "%s", g_lang ? "Casual game" : "Partida amistosa"); 79 time_t now = time(0); 80 struct tm tm; 81 localtime_r(&now, &tm); 82 snprintf(t.date, sizeof t.date, "%04u.%02u.%02u", (unsigned)(tm.tm_year + 1900) % 10000u, (unsigned)(tm.tm_mon + 1) % 100u, (unsigned)tm.tm_mday % 100u); 83 if (game_pgn(&s->g, &t, buf, sizeof buf) < 0) return; 84 const char *paths[2] = { s->pgn_path, last_pgn_path() }; 85 for (int i = 0; i < 2; i++) { 86 FILE *f = fopen(paths[i], "w"); 87 if (!f) continue; 88 fputs(buf, f); 89 fclose(f); 90 } 91 } 92 93 static void pgn_path_for(Session *s) 94 { 95 snprintf(s->pgn_path, sizeof s->pgn_path, "%s", s->save_path); 96 char *dot = strrchr(s->pgn_path, '.'); 97 if (dot && !strcmp(dot, ".ajedrez")) strcpy(dot, ".pgn"); 98 else s->pgn_path[0] = 0; 99 } 100 101 static void save_act(Session *s, const Act *a) 102 { 103 write_pgn(s); 104 if (!s->save) return; 105 char line[64]; 106 act_fmt(a, line, sizeof line); 107 fprintf(s->save, "A %s\n", line); 108 fflush(s->save); 109 } 110 111 static void open_save(Session *s) 112 { 113 time_t t = time(0); 114 struct tm tm; 115 localtime_r(&t, &tm); 116 snprintf(s->save_path, sizeof s->save_path, "%s/saves/%04d%02d%02d-%02d%02d%02d.ajedrez", data_dir(), 117 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); 118 s->save = fopen(s->save_path, "w"); 119 pgn_path_for(s); 120 if (!s->save) return; 121 char hdr[160]; 122 game_hdr_fmt(&s->g, hdr, sizeof hdr); 123 fprintf(s->save, "AJEDREZ-SAVE 1\nHDR %s\n", hdr); 124 for (int c = 0; c < 2; c++) 125 fprintf(s->save, "PLAYER %d %c %d %s\n", c, s->kind[c] == PL_ENGINE ? 'E' : 'H', s->level[c], s->names[c]); 126 fflush(s->save); 127 } 128 129 /* ------------------------------------------------------------ partida local */ 130 131 static void apply(Session *s, const Act *a) 132 { 133 int before = s->g.nply; 134 game_apply(&s->g, a); 135 save_act(s, a); 136 if (s->g.nply != before) { 137 s->turn_start = now_ms(); 138 s->engine_at = s->turn_start + s->engine_delay; 139 } 140 } 141 142 void sess_new_local(Session *s, const Pos *start, const TimeCtl *tc, bool allow_undo, 143 const uint8_t kinds[2], const int levels[2], char names[2][24]) 144 { 145 memset(s, 0, sizeof *s); 146 s->local = true; 147 game_init(&s->g, start, tc, allow_undo); 148 for (int c = 0; c < 2; c++) { 149 s->kind[c] = kinds[c]; 150 s->level[c] = levels[c]; 151 snprintf(s->names[c], sizeof s->names[c], "%s", names[c]); 152 } 153 s->turn_start = now_ms(); 154 s->eval_ply = -1; 155 open_save(s); 156 write_pgn(s); 157 } 158 159 void sess_new_imported(Session *s, const Game *src, const uint8_t kinds[2], const int levels[2], char names[2][24]) 160 { 161 TimeCtl tc = { 0, 0, 0 }; 162 sess_new_local(s, &src->start, &tc, true, kinds, levels, names); 163 for (int i = 0; i < src->nply && s->g.result == R_NONE; i++) { 164 Act a = { ACT_MOVE, (int8_t)s->g.pos.side, src->mv[i], 0 }; 165 if (game_check(&s->g, &a) != E_OK) break; 166 apply(s, &a); 167 } 168 s->turn_start = now_ms(); 169 } 170 171 bool sess_resume(Session *s, const char *path, char *err, int errlen) 172 { 173 FILE *f = fopen(path, "r"); 174 if (!f) { snprintf(err, (size_t)errlen, "%s", strerror(errno)); return false; } 175 char line[256]; 176 Game g; 177 bool hdr = false, magic = false; 178 uint8_t kinds[2] = { PL_HUMAN, PL_HUMAN }; 179 int levels[2] = { 2, 2 }; 180 char names[2][24] = { "", "" }; 181 long acts_at = -1; 182 while (fgets(line, sizeof line, f)) { 183 line[strcspn(line, "\r\n")] = 0; 184 if (!strcmp(line, "AJEDREZ-SAVE 1")) magic = true; 185 else if (!strncmp(line, "HDR ", 4)) hdr = game_hdr_parse(line + 4, &g); 186 else if (!strncmp(line, "PLAYER ", 7)) { 187 int c, lv; 188 char k, nm[64] = ""; 189 if (sscanf(line + 7, "%d %c %d %63[^\n]", &c, &k, &lv, nm) >= 3 && (c == 0 || c == 1)) { 190 kinds[c] = k == 'E' ? PL_ENGINE : PL_HUMAN; 191 levels[c] = lv; 192 snprintf(names[c], sizeof names[c], "%s", nm); 193 } 194 } else if (line[0] == 'A') { acts_at = ftell(f) - (long)strlen(line) - 1; break; } 195 } 196 if (!magic || !hdr) { fclose(f); snprintf(err, (size_t)errlen, "%s", g_lang ? "not a saved game" : "no es una partida guardada"); return false; } 197 memset(s, 0, sizeof *s); 198 s->local = true; 199 s->g = g; 200 for (int c = 0; c < 2; c++) { s->kind[c] = kinds[c]; s->level[c] = levels[c]; snprintf(s->names[c], sizeof s->names[c], "%.23s", names[c]); } 201 if (acts_at >= 0) { 202 fseek(f, acts_at, SEEK_SET); 203 int n = 0; 204 while (fgets(line, sizeof line, f)) { 205 line[strcspn(line, "\r\n")] = 0; 206 Act a; 207 if (line[0] != 'A' || !act_parse(line + 2, &a) || !act_resolve(&s->g, &a)) continue; 208 if (game_check(&s->g, &a)) { snprintf(err, (size_t)errlen, "accion %d invalida: %s", n, line + 2); break; } 209 game_apply(&s->g, &a); 210 n++; 211 } 212 } 213 fclose(f); 214 snprintf(s->save_path, sizeof s->save_path, "%s", path); 215 s->save = fopen(path, "a"); 216 pgn_path_for(s); 217 write_pgn(s); 218 s->turn_start = now_ms(); 219 s->eval_ply = -1; 220 return true; 221 } 222 223 uint8_t sess_local_mask(const Session *s) 224 { 225 if (!s->local) return (uint8_t)(1 << s->my_color); 226 uint8_t m = 0; 227 for (int c = 0; c < 2; c++) if (s->kind[c] == PL_HUMAN) m |= (uint8_t)(1 << c); 228 return m; 229 } 230 231 int32_t sess_clock(const Session *s, int color) 232 { 233 int32_t t = game_clock(&s->g, color); 234 if (game_has_clock(&s->g) && s->g.result == R_NONE && s->g.pos.side == color) 235 t -= (int32_t)(now_ms() - s->turn_start); 236 return t; 237 } 238 239 /* el motor contesta ofertas: tablas si viene perdiendo, deshacer siempre */ 240 static void engine_answer(Session *s) 241 { 242 Game *g = &s->g; 243 if (g->draw_offer && g->result == R_NONE) { 244 int to = (g->draw_offer - 1) ^ 1; 245 if (s->kind[to] == PL_ENGINE) { 246 int ev = s->eval_valid ? (to == WHITE ? s->eval : -s->eval) : 0; 247 Act a = { (uint8_t)(ev < -150 || pos_insufficient(&g->pos) ? ACT_DRAWOK : ACT_DRAWNO), (int8_t)to, 0, 0 }; 248 apply(s, &a); 249 } 250 } 251 if (g->undo_req) { 252 int to = (g->undo_req - 1) ^ 1; 253 Act a = { ACT_UNDOOK, (int8_t)to, 0, 0 }; 254 if ((s->kind[to] == PL_ENGINE || s->kind[to] == PL_HUMAN) && game_check(g, &a) == E_OK) { 255 apply(s, &a); 256 s->eval_ply = -1; 257 } 258 } 259 } 260 261 int sess_submit(Session *s, Act *a) 262 { 263 if (!s->local) return s->net ? net_submit(s->net, a) : E_TURN; 264 if (a->type == ACT_MOVE) a->ms = (int32_t)(now_ms() - s->turn_start); 265 int e = game_check(&s->g, a); 266 if (e) return e; 267 engine_stop(s); 268 apply(s, a); 269 engine_answer(s); 270 return E_OK; 271 } 272 273 /* true si juega Stockfish */ 274 static bool engine_limits(Session *s, int color, SearchLimits *lim) 275 { 276 bool sf = s->level[color] == LEVEL_STOCKFISH && !s->sf_failed && stockfish_path(); 277 level_limits(s->level[color], lim); /* LEVEL_STOCKFISH sin Stockfish = Maximo */ 278 if (sf) lim->movetime = STOCKFISH_MS; 279 if (game_has_clock(&s->g)) { 280 /* con reloj: no gastar mas de una fraccion de lo que queda */ 281 int32_t left = sess_clock(s, color); 282 int32_t budget = s->g.tc.per_move > 0 ? left / 4 : left / 30 + s->g.tc.inc / 2; 283 if (budget < 50) budget = 50; 284 if (!lim->movetime || lim->movetime > budget) lim->movetime = budget; 285 } 286 return sf; 287 } 288 289 void sess_poll(Session *s) 290 { 291 if (s->net) net_poll(s->net); 292 long long now = now_ms(); 293 if (s->busy && __atomic_load_n(&s->done, __ATOMIC_ACQUIRE)) { 294 pthread_join(s->th, 0); 295 s->busy = false; 296 const SearchResult *r = &s->job_res; 297 bool same = s->job_game.nply == s->g.nply && s->job_game.pos.hash == s->g.pos.hash; 298 if (same && r->depth > 0) { 299 s->eval = s->g.pos.side == WHITE ? r->score : -r->score; 300 s->eval_valid = true; 301 s->eval_ply = s->g.nply; 302 } 303 if (same && s->job_move && s->local && s->g.result == R_NONE && r->best) { 304 Act a = { ACT_MOVE, (int8_t)s->g.pos.side, r->best, (int32_t)(now - s->turn_start) }; 305 if (game_check(&s->g, &a) == E_OK) { apply(s, &a); engine_answer(s); } 306 } 307 } 308 if (!s->local) { 309 if (s->want_eval && !s->busy && s->eval_ply != s->g.nply && s->g.result == R_NONE) { 310 SearchLimits lim; 311 memset(&lim, 0, sizeof lim); 312 lim.movetime = 400; 313 engine_start(s, false, false, &lim); 314 } 315 return; 316 } 317 Game *g = &s->g; 318 if (g->result != R_NONE) return; 319 /* bandera */ 320 if (game_has_clock(g) && sess_clock(s, g->pos.side) <= 0) { 321 engine_stop(s); 322 Act a = { ACT_FLAG, (int8_t)g->pos.side, 0, (int32_t)(now - s->turn_start) }; 323 apply(s, &a); 324 return; 325 } 326 if (s->busy) return; 327 if (s->kind[g->pos.side] == PL_ENGINE) { 328 if (now < s->engine_at) return; 329 SearchLimits lim; 330 bool sf = engine_limits(s, g->pos.side, &lim); 331 engine_start(s, true, sf, &lim); 332 } else if (s->want_eval && s->eval_ply != g->nply) { 333 SearchLimits lim; 334 memset(&lim, 0, sizeof lim); 335 lim.movetime = 400; 336 engine_start(s, false, false, &lim); 337 } 338 } 339 340 int sess_timeout(const Session *s) 341 { 342 if (s->busy) return 30; 343 if (s->local && s->g.result == R_NONE && s->kind[s->g.pos.side] == PL_ENGINE) { 344 long long d = s->engine_at - now_ms(); 345 return d < 0 ? 0 : d > 1000 ? 1000 : (int)d; 346 } 347 int t = game_has_clock(&s->g) && s->g.result == R_NONE ? 200 : 1000; 348 if (s->net) { int n = net_timeout(s->net); if (n < t) t = n; } 349 return t; 350 } 351 352 int sess_fds(const Session *s, int *fds, int max) { return s->net ? net_fds(s->net, fds, max) : 0; } 353 354 void sess_on_act(Session *s, const Act *a) 355 { 356 int before = s->g.nply; 357 game_apply(&s->g, a); 358 if (s->g.nply != before) s->turn_start = now_ms(); 359 } 360 361 void sess_close(Session *s) 362 { 363 engine_stop(s); 364 if (s->sf.pid > 0) uci_close(&s->sf); 365 if (s->net) { net_close(s->net); s->net = 0; } 366 if (s->save) { fclose(s->save); s->save = 0; } 367 free(s->tt); 368 s->tt = 0; 369 }