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 }