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

catan_game.c (10322B)


      1 /* catan_game.c - Catan como GameModule del servidor de comun/. */
      2 #include <stdio.h>
      3 #include <string.h>
      4 #include <time.h>
      5 #include "server.h"
      6 #include "export.h"
      7 
      8 typedef struct {
      9     Host      host;
     10     SrvEmit   emit;
     11     void     *ctx;
     12     long long next_bot;
     13     int       away_min;         /* >0: si un humano no juega en tantos minutos, juega el bot por el */
     14     int       setup;            /* colocacion inicial: 0 por turnos, >0 plazo propio (min), -1 automatica */
     15     time_t    since[MAXP];      /* desde cuando le toca a cada asiento (0 = no le toca) */
     16 } CatanSt;
     17 
     18 /* Reloj de pared: tiene que valer entre reinicios del servidor (srv_now_ms no). */
     19 static void update_since(CatanSt *c)
     20 {
     21     const Game *g = &c->host.a.g;
     22     time_t now = time(0);
     23     for (int s = 0; s < MAXP; s++) {
     24         bool needs = s < g->o.np && c->host.kind[s] != SEAT_BOT && game_needs(g, s);
     25         if (!needs) c->since[s] = 0;
     26         else if (!c->since[s]) c->since[s] = now;
     27     }
     28 }
     29 
     30 /* Plazo en segundos para el que tiene que jugar ahora (-1: no hay plazo). En la
     31  * colocacion inicial manda la opcion propia: plazo corto, o 0 si es automatica. */
     32 static long limit_s(const CatanSt *c)
     33 {
     34     if (c->host.a.g.phase == PH_SETUP && c->setup) return c->setup < 0 ? 0 : (long)c->setup * 60;
     35     return c->away_min > 0 ? (long)c->away_min * 60 : -1;
     36 }
     37 
     38 /* asiento humano al que se le vencio el plazo (-1 ninguno) */
     39 static int overdue(const CatanSt *c, time_t now)
     40 {
     41     long lim = limit_s(c);
     42     if (lim < 0) return -1;
     43     for (int s = 0; s < MAXP; s++)
     44         if (c->since[s] && now - c->since[s] >= (time_t)lim) return s;
     45     return -1;
     46 }
     47 
     48 static void on_move(void *ctx, const Move *m)
     49 {
     50     CatanSt *c = ctx;
     51     update_since(c);
     52     char line[128];
     53     move_fmt(m, line, sizeof line);
     54     c->emit(c->ctx, line, move_is_auto(m));
     55 }
     56 
     57 static void fmt_opts(const Opts *o, int away_min, int setup, int hard, char *out, int n)
     58 {
     59     snprintf(out, (size_t)n, "%d %d %d %d %d %d %d %d %d %d %d %lu %d %d", o->np, o->variant2p, o->vp_target, o->friendly_robber,
     60              o->tokens_start, o->neutral_setup, o->beginner, away_min, setup, o->scenario, o->map, (unsigned long)o->bseed, hard,
     61              OPTS_FIRST_RANDOM);              /* quien empieza: se sortea con la semilla de la partida */
     62 }
     63 
     64 static uint32_t clock32(void) { return (uint32_t)srv_now_ms(); }
     65 
     66 /* AGAIN: las mismas opciones sin la semilla del tablero (0: sale de la semilla nueva de la
     67  * partida), asi el reparto es otro; el tablero de principiante queda como es. */
     68 static void cg_again(const char *opts, char *out, int n)
     69 {
     70     int v[11] = { 0 }, hard = 1;
     71     unsigned long bseed = 0;
     72     int k = sscanf(opts, "%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], &v[8], &v[9], &v[10], &bseed, &hard);
     73     if (k < 7) { snprintf(out, (size_t)n, "%s", opts); return; }
     74     snprintf(out, (size_t)n, "%d %d %d %d %d %d %d %d %d %d %d 0 %d %d", v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10], hard,
     75              OPTS_FIRST_RANDOM);
     76 }
     77 
     78 static int cg_create(const char *args, char *opts, int optsn, char *seats)
     79 {
     80     int np, v2p, vp, beg, away = 0, setup = 0, scen = 0, map = 0, hard = 1;   /* hard: bots dificiles (busqueda) o faciles */
     81     unsigned long bseed = 0;
     82     char st[8];
     83     if (sscanf(args, "%d %d %d %d %7s %d %d %d %d %lu %d", &np, &v2p, &vp, &beg, st, &away, &setup, &scen, &map, &bseed, &hard) < 5 ||
     84         np < 2 || np > MAXP || map < 0 || map >= NMAPS || bseed > 0xFFFFFFFFul ||
     85         scen < 0 || scen >= SCEN__COUNT || (scen && (np > 4 || v2p)) ||
     86         vp < 3 || vp > 20 || (int)strlen(st) != np || away < 0 || away > 30 * 24 * 60 || setup < -1 || setup > 24 * 60)
     87         return -1;
     88     Opts o;
     89     opts_default(&o, np, v2p != 0);
     90     o.vp_target = (uint8_t)vp;
     91     o.beginner = (uint8_t)(beg != 0);
     92     if (scen) opts_scenario(&o, scen);         /* Navegantes: su mapa y 13 puntos */
     93     o.map = (uint8_t)map;                      /* el mapa y el tablero que eligio quien crea */
     94     o.bseed = (uint32_t)bseed;
     95     if (o.map || o.bseed) o.beginner = 0;
     96     fmt_opts(&o, away, setup, hard != 0, opts, optsn);
     97     memcpy(seats, st, (size_t)np + 1);
     98     return np;
     99 }
    100 
    101 static bool cg_start(void *vst, const char *opts, uint64_t seed, char *kinds, char (*names)[20], SrvEmit emit, void *ctx)
    102 {
    103     CatanSt *c = vst;
    104     int v[11] = { 0 }, hard = 1, first = 0;    /* ausencia, colocacion, escenario, mapa, nivel y quien empieza no estan en partidas viejas */
    105     unsigned long bseed = 0;
    106     if (sscanf(opts, "%d %d %d %d %d %d %d %d %d %d %d %lu %d %d", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7], &v[8], &v[9],
    107                &v[10], &bseed, &hard, &first) < 7)
    108         return false;
    109     Opts o;
    110     memset(&o, 0, sizeof o);
    111     o.np = (uint8_t)v[0]; o.variant2p = (uint8_t)v[1]; o.vp_target = (uint8_t)v[2]; o.friendly_robber = (uint8_t)v[3];
    112     o.tokens_start = (uint8_t)v[4]; o.neutral_setup = (uint8_t)v[5]; o.beginner = (uint8_t)v[6];
    113     o.scenario = (uint8_t)(v[9] > 0 && v[9] < SCEN__COUNT ? v[9] : 0);
    114     o.map = (uint8_t)(v[10] > 0 && v[10] < NMAPS ? v[10] : 0);
    115     o.bseed = (uint32_t)bseed;
    116     o.first = (uint8_t)(first == OPTS_FIRST_RANDOM || (first >= 0 && first < MAXP) ? first : 0);
    117     if (o.np < 2 || o.np > MAXP) return false;
    118     c->emit = emit;
    119     c->ctx = ctx;
    120     c->away_min = v[7];
    121     c->setup = v[8];
    122     uint8_t k[MAXP];
    123     for (int i = 0; i < MAXP; i++) k[i] = kinds[i] == 'B' ? SEAT_BOT : i < o.np ? SEAT_REMOTE : SEAT_OPEN;
    124     host_init(&c->host, &o, seed, k, on_move, c);
    125     /* bots con busqueda, pero cortos: el servidor atiende todo en un solo hilo */
    126     /* nivel: 0 facil, 1 dificil (el valor de siempre), 2 medio; misma busqueda con mas o menos iteraciones */
    127     SearchCfg b = { .iters = hard == 0 ? 25 : hard == 2 ? 300 : 20000, .horizon = 8, .c_ucb = 0.5f, .now_ms = clock32, .max_ms = 150 };
    128     c->host.bot = b;
    129     for (int i = 0; i < o.np; i++)
    130         if (c->host.a.g.p[i].neutral) {
    131             kinds[i] = 'N';
    132             snprintf(names[i], 20, "Neutral %d", i - 1);
    133         }
    134     c->next_bot = srv_now_ms() + 500;
    135     update_since(c);
    136     return true;
    137 }
    138 
    139 static int cg_submit(void *vst, int seat, const char *line)
    140 {
    141     CatanSt *c = vst;
    142     Move m;
    143     if (!move_parse(line, &m)) return E_ARGS;
    144     if (m.p != seat) return E_TURN;
    145     int e = host_submit(&c->host, seat, &m);
    146     if (!e) c->next_bot = srv_now_ms() + 500;
    147     return e;
    148 }
    149 
    150 static int cg_replay(void *vst, const char *line)
    151 {
    152     CatanSt *c = vst;
    153     Move m;
    154     if (!move_parse(line, &m)) return 0;       /* lineas que no son movidas se ignoran */
    155     return host_replay(&c->host, &m);
    156 }
    157 
    158 static void cg_redact(const char *line, int viewer, char *out, int n)
    159 {
    160     Move m, r;
    161     if (!move_parse(line, &m)) { snprintf(out, (size_t)n, "%s", line); return; }
    162     move_redact(&m, viewer, &r);
    163     move_fmt(&r, out, n);
    164 }
    165 
    166 static void cg_header(const void *vst, char *out, int n)
    167 {
    168     const CatanSt *c = vst;
    169     hdr_fmt(&c->host.a.g, out, n);
    170 }
    171 
    172 static void cg_status(const void *vst, int seat, char *out, int n)
    173 {
    174     const Game *g = &((const CatanSt *)vst)->host.a.g;
    175     snprintf(out, (size_t)n, "%d %d %d %d", g->phase, g->turn, game_needs(g, seat) ? 1 : 0, g->winner);
    176 }
    177 
    178 static bool cg_is_over(const void *vst) { return ((const CatanSt *)vst)->host.a.g.phase == PH_OVER; }
    179 
    180 static long long cg_next_wake(const void *vst)
    181 {
    182     const CatanSt *c = vst;
    183     if (host_bot_pending(&c->host)) return c->next_bot;
    184     long lim = limit_s(c);
    185     if (lim < 0 || c->host.a.g.phase == PH_OVER) return -1;
    186     time_t now = time(0), first = 0;
    187     for (int s = 0; s < MAXP; s++) {
    188         if (!c->since[s]) continue;
    189         time_t due = c->since[s] + (time_t)lim;
    190         if (!first || due < first) first = due;
    191     }
    192     if (!first) return -1;
    193     long long t = srv_now_ms() + (long long)(first - now) * 1000;
    194     return t > c->next_bot ? t : c->next_bot;
    195 }
    196 
    197 static void cg_wake(void *vst, long long now)
    198 {
    199     CatanSt *c = vst;
    200     if (now < c->next_bot) return;
    201     if (host_bot_pending(&c->host)) {
    202         host_bot_step(&c->host);
    203         c->next_bot = now + 500;
    204         return;
    205     }
    206     /* ausente: el bot juega por el hasta que deje de tocarle (since vuelve a 0) */
    207     int s = overdue(c, time(0));
    208     if (s < 0) return;
    209     Game *g = &c->host.a.g;
    210     Move m;
    211     bool ok = search_decide(g, s, &c->host.brng, &c->host.bot, &m) && host_submit(&c->host, s, &m) == E_OK;
    212     if (!ok) {
    213         memset(&m, 0, sizeof m);
    214         m.p = (int8_t)s;
    215         m.type = g->offer.active && s != g->offer.from ? M_REJECT : M_END;
    216         if (host_submit(&c->host, s, &m) != E_OK) c->since[s] = 0;    /* no deberia pasar: no trabar */
    217     }
    218     c->next_bot = now + 500;
    219 }
    220 
    221 static void cg_summary(const void *vst, char *out, int n)
    222 {
    223     const Opts *o = &((const CatanSt *)vst)->host.a.g.o;
    224     int away = ((const CatanSt *)vst)->away_min;
    225     int setup = ((const CatanSt *)vst)->setup;
    226     char col[24];
    227     if (setup < 0) snprintf(col, sizeof col, "automatica");
    228     else if (setup) snprintf(col, sizeof col, "plazo %d min", setup);
    229     else snprintf(col, sizeof col, "por turnos");
    230     snprintf(out, (size_t)n, "%d jugadores%s, ausencia %d min, colocacion %s", o->np, o->variant2p ? ", variante 2p" : "", away, col);
    231 }
    232 
    233 static void cg_export(const void *vst, const char *code, const char *const *moves, int nmoves,
    234                       char (*names)[20], const char *kinds, void (*put)(void *, const char *), void *ctx)
    235 {
    236     const CatanSt *c = vst;
    237     char hdr[512];
    238     hdr_fmt(&c->host.a.g, hdr, sizeof hdr);     /* la cabecera no cambia durante la partida */
    239     const char *nm[MAXP];
    240     for (int i = 0; i < MAXP; i++) nm[i] = names[i][0] ? names[i] : "-";
    241     export_game(hdr, moves, nmoves, code, nm, kinds, ~0u, put, ctx);   /* el servidor lo ve todo */
    242 }
    243 
    244 const GameModule catan_module = {
    245     .magic = "CATAN-GAME 1",
    246     .state_size = sizeof(CatanSt),
    247     .err_args = E_ARGS,
    248     .err_turn = E_TURN,
    249     .create = cg_create,
    250     .start = cg_start,
    251     .submit = cg_submit,
    252     .replay = cg_replay,
    253     .redact = cg_redact,
    254     .header = cg_header,
    255     .status = cg_status,
    256     .is_over = cg_is_over,
    257     .next_wake = cg_next_wake,
    258     .wake = cg_wake,
    259     .summary = cg_summary,
    260     .export = cg_export,
    261     .again = cg_again,
    262 };