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 (15528B)


      1 /* session.c - partida local (autoridad + bots + guardado) y vista remota. */
      2 #include <errno.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 #ifndef _WIN32
      6 #include <strings.h>
      7 #endif
      8 #include <sys/stat.h>
      9 #include <time.h>
     10 #include "plat.h"
     11 #include "session.h"
     12 #include "net.h"
     13 
     14 bool latest_save(char *out, int n) { return latest_file(".catan", out, n); }
     15 
     16 /* ------------------------------------------------------------------- log */
     17 
     18 void sess_add_log(Session *s, const char *line)
     19 {
     20     if (s->nlog == s->caplog) {             /* se guarda todo: son pocos KB por partida */
     21         int nc = s->caplog ? s->caplog * 2 : 256;
     22         char **l = realloc(s->log, sizeof *l * (size_t)nc);
     23         if (!l) return;
     24         s->log = l;
     25         s->caplog = nc;
     26     }
     27     char *copy = strdup(line);
     28     if (copy) s->log[s->nlog++] = copy;
     29 }
     30 
     31 /* ------------------------------------------------------------ chat */
     32 
     33 static int my_seat(const Session *s)
     34 {
     35     for (int i = 0; i < MAXP; i++) if ((s->local_mask >> i) & 1) return i;
     36     return -1;
     37 }
     38 
     39 void sess_chat_in(Session *s, int from, int to, const char *text)
     40 {
     41     char line[300];
     42     const char *who = from >= 0 && from < MAXP ? s->names[from] : "?";
     43     int me = my_seat(s);
     44     char seat = from >= 0 && from < MAXP ? (char)('0' + from) : '-';    /* para el color */
     45     if (to < 0) snprintf(line, sizeof line, "%c%c%s: %s", LOG_CHAT, seat, who, text);
     46     else if (from == me && to < MAXP)
     47         snprintf(line, sizeof line, "%c%c%s %s: %s", LOG_WHISPER, seat, g_lang ? "to" : "a", s->names[to], text);
     48     else snprintf(line, sizeof line, "%c%c%s %s: %s", LOG_WHISPER, seat, who, g_lang ? "(private)" : "(privado)", text);
     49     sess_add_log(s, line);
     50 }
     51 
     52 /* asiento cuyo nombre empieza con name (sin mayusculas); -1 ninguno, -2 varios */
     53 static int seat_by_name(const Session *s, const char *name, int len)
     54 {
     55     int found = -1, me = my_seat(s);
     56     for (int i = 0; i < s->g->o.np; i++) {
     57         if (i == me || s->g->p[i].neutral) continue;
     58         if (strncasecmp(s->names[i], name, (size_t)len)) continue;
     59         if ((int)strlen(s->names[i]) == len) return i;      /* exacto gana */
     60         found = found == -1 ? i : -2;
     61     }
     62     return found;
     63 }
     64 
     65 const char *sess_say(Session *s, const char *in)
     66 {
     67     while (*in == ' ') in++;
     68     if (!*in) return 0;
     69     int to = -1;
     70     if (*in == '/') {
     71         const char *cmd = in + 1, *sp = strchr(cmd, ' ');
     72         int cl = sp ? (int)(sp - cmd) : (int)strlen(cmd);
     73         bool w = (cl == 1 && *cmd == 'w') || (cl == 3 && !strncmp(cmd, "msg", 3)) || (cl == 7 && !strncmp(cmd, "whisper", 7))
     74               || (cl == 1 && *cmd == 'p');
     75         if (!w) return g_lang ? "Commands: /w name text (also /msg, /whisper)" : "Comandos: /w nombre texto (también /msg, /whisper)";
     76         if (!sp) return g_lang ? "Usage: /w name text" : "Uso: /w nombre texto";
     77         const char *name = sp + 1;
     78         while (*name == ' ') name++;
     79         /* primero un nombre completo (puede tener espacios: "Bot 2"), el mas largo */
     80         const char *end = 0;
     81         int me = my_seat(s), best = 0;
     82         for (int i = 0; i < s->g->o.np; i++) {
     83             int l = (int)strlen(s->names[i]);
     84             if (i == me || s->g->p[i].neutral || l <= best || strncasecmp(name, s->names[i], (size_t)l) || name[l] != ' ') continue;
     85             best = l; to = i; end = name + l;
     86         }
     87         if (!end) {                                   /* si no, el comienzo de un nombre */
     88             end = strchr(name, ' ');
     89             if (!end) return g_lang ? "Usage: /w name text" : "Uso: /w nombre texto";
     90             to = seat_by_name(s, name, (int)(end - name));
     91         }
     92         while (*end == ' ') end++;
     93         if (!*end) return g_lang ? "Usage: /w name text" : "Uso: /w nombre texto";
     94         end--;
     95         if (to == -1) return g_lang ? "No player with that name" : "No hay ningún jugador con ese nombre";
     96         if (to == -2) return g_lang ? "More than one player matches: write more of the name" : "Coincide con más de un jugador: escribí más del nombre";
     97         in = end + 1;
     98     }
     99     if (s->net) return net_send(s->net, "SAY %d %s", to, in) ? 0 : (g_lang ? "Not connected" : "Sin conexión");
    100     if (to >= 0) return g_lang ? "Private messages only online" : "Los privados son solo online";
    101     /* en la misma compu habla el que esta jugando */
    102     sess_chat_in(s, ((s->local_mask >> s->g->cur) & 1) ? s->g->cur : my_seat(s), -1, in);
    103     return 0;
    104 }
    105 
    106 static void describe(Session *s, const Game *g, const Move *m)
    107 {
    108     const char *names[MAXP];
    109     for (int i = 0; i < MAXP; i++) names[i] = s->names[i];
    110     /* movidas que no vale la pena listar */
    111     if (m->type == M_ACCEPT || m->type == M_REJECT) {
    112         if (!g->offer.active && m->type == M_REJECT) return;
    113     }
    114     /* la carta robada la ve solo quien mira esta pantalla, si es el ladron o la victima;
    115      * con varios humanos en la misma compu no se muestra (estan todos mirando) */
    116     unsigned lm = s->local_mask, viewers = lm && !(lm & (lm - 1)) ? lm : 0;
    117     bool victim = m->type == M_ROBBER && m->b >= 0 && ((viewers >> m->b) & 1) && !((viewers >> m->p) & 1);
    118     char buf[200];
    119     buf[0] = victim ? LOG_ALERT : LOG_MOVE;
    120     buf[1] = m->p >= 0 && m->p < MAXP ? (char)('0' + m->p) : '-';
    121     move_describe_for(g, m, names, viewers, buf + 2, sizeof buf - 2);
    122     sess_add_log(s, buf);
    123     if (m->type == M_END && g->phase == PH_PAIRED) {          /* 5-6: ahora juega la pareja */
    124         snprintf(buf, sizeof buf, "%c%c%s %s", LOG_MOVE, (char)('0' + g->cur), s->names[g->cur], tr(S_PLAYS_PAIRED));
    125         sess_add_log(s, buf);
    126     }
    127 }
    128 
    129 /* ------------------------------------------------------------ partida local */
    130 
    131 static const char *KIND_NAME[] = { "OPEN", "LOCAL", "REMOTE", "BOT", "NEUTRAL" };
    132 
    133 static void local_on_move(void *ctx, const Move *m)
    134 {
    135     Session *s = ctx;
    136     if (!s->replaying) {
    137         if (s->save) {
    138             char line[128];
    139             move_fmt(m, line, sizeof line);
    140             fprintf(s->save, "M %s\n", line);
    141             fflush(s->save);
    142             if (m->type == M_END) paths_flush();    /* la web: un sync por turno alcanza (autoPersist hace el resto) */
    143         }
    144         if (s->net) net_on_local_move(s->net, m);
    145     }
    146     describe(s, &s->host.a.g, m);
    147 }
    148 
    149 static void open_save(Session *s)
    150 {
    151     time_t t = time(0);
    152     struct tm tm;
    153     localtime_r(&t, &tm);
    154     snprintf(s->save_path, sizeof s->save_path, "%s/saves/%04d%02d%02d-%02d%02d%02d.catan", data_dir(),
    155              tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    156     s->save = fopen(s->save_path, "w");
    157 }
    158 
    159 static void write_header(Session *s, uint64_t seed)
    160 {
    161     if (!s->save) return;
    162     const Opts *o = &s->host.a.g.o;
    163     fprintf(s->save, "CATAN-SAVE 1\nSEED %llu\nOPTS %d %d %d %d %d %d %d %d %d %d %d %lu %d\n", (unsigned long long)seed,
    164             o->np, o->variant2p, o->vp_target, o->friendly_robber, o->tokens_start, o->neutral_setup, o->beginner,
    165             o->setup_first, o->freeplay, o->scenario, o->map, (unsigned long)o->bseed, o->first);
    166     for (int i = 0; i < o->np; i++) {
    167         int k = s->host.kind[i] == SEAT_REMOTE ? SEAT_LOCAL : s->host.kind[i];
    168         fprintf(s->save, "SEAT %d %s %s\n", i, KIND_NAME[k], s->names[i]);
    169     }
    170     fflush(s->save);
    171     paths_flush();
    172 }
    173 
    174 static void setup_names(Session *s)
    175 {
    176     for (int i = 0; i < MAXP; i++) {
    177         if (!s->names[i][0]) snprintf(s->names[i], sizeof s->names[i], "%s %d", tr(S_PLAYER), i + 1);
    178         snprintf(s->host.name[i], sizeof s->host.name[i], "%s", s->names[i]);
    179     }
    180 }
    181 
    182 void sess_new_local(Session *s, const Opts *o, uint64_t seed, const uint8_t kinds[MAXP], char names[MAXP][20])
    183 {
    184     memset(s, 0, sizeof *s);
    185     s->local = true;
    186     s->bot_delay_ms = 450;
    187     for (int i = 0; i < MAXP; i++) snprintf(s->names[i], sizeof s->names[i], "%s", names[i]);
    188     for (int i = 0; i < MAXP; i++)
    189         if (i < o->np && o->variant2p && i >= 2)
    190             snprintf(s->names[i], sizeof s->names[i], "%s %d", tr(S_NEUTRAL), i - 1);
    191     s->replaying = true;       /* las movidas automaticas del arranque se escriben despues */
    192     host_init(&s->host, o, seed, kinds, local_on_move, s);
    193     s->replaying = false;
    194     setup_names(s);
    195     for (int i = 0; i < o->np; i++)
    196         if (s->host.kind[i] == SEAT_LOCAL) s->local_mask |= (uint8_t)(1 << i);
    197     s->g = &s->host.a.g;
    198     open_save(s);
    199     write_header(s, seed);
    200 }
    201 
    202 bool sess_resume(Session *s, const char *path, char *err, int errlen)
    203 {
    204     FILE *f = fopen(path, "r");
    205     if (!f) { snprintf(err, (size_t)errlen, "%s", strerror(errno)); return false; }
    206     char line[256];
    207     unsigned long long seed = 0;
    208     Opts o;
    209     memset(&o, 0, sizeof o);
    210     uint8_t kinds[MAXP] = { 0 };
    211     char names[MAXP][20];
    212     memset(names, 0, sizeof names);
    213     bool hdr = false, opts = false;
    214     long moves_at = 0;
    215     while (fgets(line, sizeof line, f)) {
    216         line[strcspn(line, "\r\n")] = 0;
    217         if (!strncmp(line, "CATAN-SAVE", 10)) hdr = true;
    218         else if (!strncmp(line, "SEED ", 5)) seed = strtoull(line + 5, 0, 10);
    219         else if (!strncmp(line, "OPTS ", 5)) {
    220             int v[11] = { 0 }, first = 0;  /* setup_first, freeplay, escenario, mapa y quien empieza pueden faltar en partidas viejas */
    221             unsigned long bseed = 0;
    222             if (sscanf(line + 5, "%d %d %d %d %d %d %d %d %d %d %d %lu %d", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7],
    223                        &v[8], &v[9], &v[10], &bseed, &first) >= 7) {
    224                 o.np = (uint8_t)v[0]; o.variant2p = (uint8_t)v[1]; o.vp_target = (uint8_t)v[2];
    225                 o.friendly_robber = (uint8_t)v[3]; o.tokens_start = (uint8_t)v[4]; o.neutral_setup = (uint8_t)v[5];
    226                 o.beginner = (uint8_t)v[6];
    227                 o.setup_first = (uint8_t)(v[7] & ((1 << MAXP) - 1));
    228                 o.freeplay = v[8] != 0;
    229                 o.scenario = (uint8_t)(v[9] > 0 && v[9] < SCEN__COUNT ? v[9] : 0);
    230                 o.map = (uint8_t)(v[10] > 0 && v[10] < NMAPS ? v[10] : 0);
    231                 o.bseed = (uint32_t)bseed;
    232                 o.first = (uint8_t)(first == OPTS_FIRST_RANDOM || (first >= 0 && first < MAXP) ? first : 0);
    233                 opts = o.np >= 2 && o.np <= MAXP;
    234             }
    235         } else if (!strncmp(line, "SEAT ", 5)) {
    236             int i;
    237             char kind[16], name[64] = "";
    238             if (sscanf(line + 5, "%d %15s %63[^\n]", &i, kind, name) >= 2 && i >= 0 && i < MAXP) {
    239                 for (int k = 0; k < 5; k++) if (!strcmp(kind, KIND_NAME[k])) kinds[i] = (uint8_t)k;
    240                 snprintf(names[i], sizeof names[i], "%s", name);
    241             }
    242         } else if (line[0] == 'M') { moves_at = ftell(f) - (long)strlen(line) - 1; break; }
    243     }
    244     if (!hdr || !opts) { fclose(f); snprintf(err, (size_t)errlen, "archivo invalido"); return false; }
    245 
    246     memset(s, 0, sizeof *s);
    247     s->local = true;
    248     s->bot_delay_ms = 450;
    249     for (int i = 0; i < MAXP; i++) snprintf(s->names[i], sizeof s->names[i], "%s", names[i]);
    250     s->replaying = true;
    251     host_init(&s->host, &o, seed, kinds, local_on_move, s);
    252     setup_names(s);
    253     if (moves_at) {
    254         fseek(f, moves_at, SEEK_SET);
    255         int n = 0;
    256         while (fgets(line, sizeof line, f)) {
    257             line[strcspn(line, "\r\n")] = 0;
    258             Move m;
    259             if (line[0] != 'M' || !move_parse(line + 2, &m)) continue;
    260             int e = host_replay(&s->host, &m);
    261             if (e) { snprintf(err, (size_t)errlen, "movida %d invalida: %s", n, line + 2); break; }
    262             n++;
    263         }
    264     }
    265     fclose(f);
    266     s->replaying = false;
    267     for (int i = 0; i < o.np; i++)
    268         if (s->host.kind[i] == SEAT_LOCAL) s->local_mask |= (uint8_t)(1 << i);
    269     s->g = &s->host.a.g;
    270     snprintf(s->save_path, sizeof s->save_path, "%s", path);
    271     s->save = fopen(path, "a");
    272     sess_add_log(s, g_lang ? "— game resumed —" : "— partida retomada —");
    273     return true;
    274 }
    275 
    276 bool sess_go_free(Session *s, char *err, int errlen)
    277 {
    278     if (!s->local || !s->save_path[0]) { snprintf(err, (size_t)errlen, "no es una partida local"); return false; }
    279     if (s->save) { fclose(s->save); s->save = 0; }
    280     char path[512];
    281     snprintf(path, sizeof path, "%s", s->save_path);
    282     FILE *f = fopen(path, "r");
    283     if (!f) { snprintf(err, (size_t)errlen, "%s", strerror(errno)); return false; }
    284     fseek(f, 0, SEEK_END);
    285     long n = ftell(f);
    286     fseek(f, 0, SEEK_SET);
    287     char *buf = malloc((size_t)n + 1);
    288     if (!buf) { fclose(f); snprintf(err, (size_t)errlen, "sin memoria"); return false; }
    289     n = (long)fread(buf, 1, (size_t)n, f);
    290     buf[n] = 0;
    291     fclose(f);
    292     char *op = strstr(buf, "\nOPTS ");
    293     char *eol = op ? strchr(op + 1, '\n') : 0;
    294     if (!eol) { free(buf); snprintf(err, (size_t)errlen, "archivo invalido"); return false; }
    295     const Opts *o = &s->host.a.g.o;
    296     f = fopen(path, "w");
    297     if (!f) { free(buf); snprintf(err, (size_t)errlen, "%s", strerror(errno)); return false; }
    298     fwrite(buf, 1, (size_t)(op - buf), f);
    299     fprintf(f, "\nOPTS %d %d %d %d %d %d %d %d 1 %d %d %lu %d", o->np, o->variant2p, o->vp_target, o->friendly_robber,
    300             o->tokens_start, o->neutral_setup, o->beginner, o->setup_first, o->scenario, o->map, (unsigned long)o->bseed, o->first);
    301     fputs(eol, f);
    302     fclose(f);
    303     paths_flush();
    304     free(buf);
    305     bool handoff = s->handoff;
    306     int delay = s->bot_delay_ms;
    307     SearchCfg bot = s->host.bot;
    308     if (!sess_resume(s, path, err, errlen)) return false;
    309     s->handoff = handoff;
    310     s->bot_delay_ms = delay;
    311     s->host.bot = bot;
    312     return true;
    313 }
    314 
    315 static uint32_t clock32(void) { return (uint32_t)now_ms(); }
    316 
    317 /* Dificiles: ISMCTS con hasta 300 ms por decision (la ventana espera). */
    318 /* Los tres niveles son la misma busqueda con mas o menos iteraciones (calibrado contra el
    319  * bot de reglas con --duel: 25 iter ~ parejo, 300 ~ 45 %, el dificil muy por encima). */
    320 int bot_iters(int level) { return level <= 0 ? 25 : level == 1 ? 300 : 30000; }
    321 
    322 void sess_set_bots(Session *s, int level)
    323 {
    324     SearchCfg c = { .iters = bot_iters(level), .horizon = 8, .c_ucb = 0.5f, .now_ms = clock32, .max_ms = 300 };
    325     s->host.bot = c;
    326 }
    327 
    328 int sess_submit(Session *s, Move *m)
    329 {
    330     if (!s->local) return s->net ? net_submit(s->net, m) : E_TURN;
    331     if (m->p < 0 || m->p >= MAXP || !((s->local_mask >> m->p) & 1)) return E_TURN;
    332     int e = host_submit(&s->host, m->p, m);
    333     if (e == E_OK) s->next_bot_ms = now_ms() + s->bot_delay_ms;
    334     return e;
    335 }
    336 
    337 void sess_poll(Session *s)
    338 {
    339     if (s->net) net_poll(s->net);
    340     if (s->local && host_bot_pending(&s->host)) {
    341         long long t = now_ms();
    342         if (t >= s->next_bot_ms) {
    343             host_bot_step(&s->host);
    344             s->next_bot_ms = t + s->bot_delay_ms;
    345         }
    346     }
    347 }
    348 
    349 int sess_timeout(const Session *s)
    350 {
    351     if (s->local && host_bot_pending(&s->host)) {
    352         long long d = s->next_bot_ms - now_ms();
    353         return d < 0 ? 0 : (int)d;
    354     }
    355     return s->net ? net_timeout(s->net) : 1000;
    356 }
    357 
    358 int sess_fds(const Session *s, int *fds, int max)
    359 {
    360     return s->net ? net_fds(s->net, fds, max) : 0;
    361 }
    362 
    363 void sess_on_move(Session *s, const Move *m)
    364 {
    365     game_apply_u(&s->view, &s->view_undo, m);
    366     describe(s, &s->view, m);
    367 }
    368 
    369 void sess_log_clear(Session *s)
    370 {
    371     for (int i = 0; i < s->nlog; i++) free(s->log[i]);
    372     s->nlog = 0;
    373 }
    374 
    375 void sess_close(Session *s)
    376 {
    377     if (s->net) { net_close(s->net); s->net = 0; }
    378     if (s->save) { fclose(s->save); s->save = 0; }
    379     for (int i = 0; i < s->nlog; i++) free(s->log[i]);
    380     free(s->log);
    381     s->log = 0;
    382     s->nlog = s->caplog = 0;
    383 }