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

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 }