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 };