commit 8920f9890b79b7e70180d6827caf4c4f0bce9e78
parent 5815b3a21937a54aaa2aa532c9cb926ba1ed25ad
Author: Francisco Armando Rosmino <franciscorosmino@rosmino.com.ar>
Date: Sat, 3 Oct 2026 15:39:51 -0300
catan-duel: busqueda nueva contra la vieja
make catan-duel compila ademas la search.c de DUEL_BASE (un tag o commit) con
el simbolo renombrado; --duel ahi enfrenta las dos busquedas en la misma
partida rotando asientos. Es la medida para cualquier cambio de bots.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
4 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -28,3 +28,6 @@ __pycache__/
ajedrez/engine/stockfish
kit-picocalc.zip
catan/release/
+catan/catan-duel
+catan/core/search_old.c
+catan/core/search_old.o
diff --git a/CLAUDE.md b/CLAUDE.md
@@ -39,6 +39,7 @@ make debug && make test # recompila con ASan/UBSan
./catan --selftest # solo reglas/serialización/replay/redacción (linux/selftest.c)
./catan --sim N [jugadores] [2p] # N partidas bot vs bot verificando invariantes
./catan --duel N jugadores iter horizonte [2p|-] [semilla] [c] # bot ISMCTS vs heurísticos: % de victorias
+make catan-duel && ./catan-duel --duel 80 4 100 8 - 7 # búsqueda NUEVA vs la de DUEL_BASE (último tag): así se mide un cambio de bots
make catan-server-static # servidor estático para el VPS (instalar con comun/deploy/install-vps.sh)
make pico # firmware PicoCalc → sd/pico1-apps/Catan.uf2 y sd/firmware/Catan.bin
make web # PWA (emscripten + SDL3 estático) → web/dist/; make deploy-web la sube al VPS (/var/www/catan, Caddy)
@@ -65,7 +66,7 @@ Al correr pruebas o el binario, aislar `XDG_CONFIG_HOME`/`XDG_DATA_HOME` en un d
- `ctrl.c`: máquina de estados de interacción **compartida** por TUI, GUI y PicoCalc (teclas abstractas / clicks resueltos → `Move`).
- i18n: `strings.h` es una X-macro `X(id, es, en)`; todo texto visible va ahí y se lee con `tr()`.
- Reglas configurables en `Opts` (variante 2p, ladrón amable, fichas, etc.).
- - Bots: `bot.c` heurístico ("fáciles"); `search.c` ISMCTS ("difíciles", `search_decide` + `SearchCfg` en `Host.bot`; iters 0 = heurístico). Determiniza solo lo oculto para ese asiento, candidatas podadas con `bot_int.h`, playouts con `bot_decide`, horizonte 8 + evaluación. Árbol estático (`SEARCH_NODES`, 1024 en la Pico): una búsqueda a la vez. Corta por tiempo: 300 ms Linux (`sess_set_bots`), 150 ms servidor, 1200 ms Pico. Medir cambios con `--duel` (el ruido con 200 partidas es ±3-4 puntos).
+ - Bots: `bot.c` heurístico ("fáciles"); `search.c` ISMCTS ("difíciles", `search_decide` + `SearchCfg` en `Host.bot`; iters 0 = heurístico). Determiniza solo lo oculto para ese asiento, candidatas podadas con `bot_int.h`, playouts con `bot_decide`, horizonte 8 + evaluación. Árbol estático (`SEARCH_NODES`, 1024 en la Pico): una búsqueda a la vez. Corta por tiempo: 300 ms Linux (`sess_set_bots`), 150 ms servidor, 1200 ms Pico. **Todo cambio de bots se mide nueva contra vieja**: `make catan-duel` (lleva además la `search.c` de `DUEL_BASE`, por defecto el último tag, con el símbolo renombrado a `search_old_decide`) y `./catan-duel --duel 80 4 100 8 - <semilla>` enfrenta la búsqueda nueva contra la vieja en la misma partida rotando asientos (base 25 % con 4 jugadores; correr dos semillas, el ruido con 80 partidas es ±8 puntos). `--duel` en `./catan` sigue siendo contra los heurísticos y solo sirve de sanidad.
- **`gfx/`** (`catan_gfx.h`): tablero y pantallas de Catan en píxeles (layouts compact 320x320 y wide) sobre `comun/gfx`. `geom_fit(geo, game, ...)` encuadra cualquier tablero por los límites de sus vértices. Panel ancho: con más de 4 jugadores las filas son más bajas; acciones y costos van en dos columnas.
- **GUI ancha**: lienzo lógico de 640x360 (16:9) con escala **entera** (`gk_open_fluid` en `comun/linux/guikit.c`: 2x en 720p, 3x en 1080p; un factor no entero deja la letra despareja). Pensada para 720p/1080p (los amigos de Pancho juegan en Windows con `catan.exe`). Fuentes en `comun/gfx/font.c`, generadas por `comun/tools/mkfont.py` desde `comun/tools/fonts/`: Terminus 6x12 (normal, y engrosada a 7x12 como negrita) y Spleen 5x8 (chica, con los acentos del castellano agregados).
- **`linux/`**: cliente. `frontend.h` define la interfaz TUI (`tui.c`, sobre `comun/linux/tuikit`; colores opacos propios — el usuario no quiere depender de la transparencia de su terminal) / GUI (`gui_sdl.c`, sobre `guikit`). `app.c` menús y bucle, `session.c` guardado, `net.c`/`online.c` red (sobre `netline`).
diff --git a/catan/Makefile b/catan/Makefile
@@ -125,6 +125,18 @@ pico:
@echo "Listo: copiá el contenido de sd/ a la raíz de la tarjeta SD de la PicoCalc"
@echo "CATAN.LOG y CATAN.SAV van solo la primera vez: si los pisás, perdés el log y la partida guardada"
+# Duelo nueva vs vieja: catan-duel lleva ademas la search.c de DUEL_BASE (un tag o commit
+# del git) con el simbolo renombrado; --duel ahi enfrenta la busqueda nueva contra la vieja
+# en la misma partida, rotando asientos. Asi se ve si un cambio del bot es positivo.
+DUEL_BASE ?= v1.1.1
+core/search_old.c: FORCE
+ git show $(DUEL_BASE):catan/core/search.c > $@
+core/search_old.o: core/search_old.c $(HDRS)
+ $(CC) $(CPPFLAGS) $(CFLAGS) -Dsearch_decide=search_old_decide -c $< -o $@
+catan-duel: $(CLIENT_SRC) $(HDRS) core/search_old.o
+ $(CC) $(CPPFLAGS) $(CFLAGS) -DDUEL_OLD -o $@ $(CLIENT_SRC) core/search_old.o $(LDLIBS)
+FORCE:
+
debug:
$(MAKE) -B catan CFLAGS="-O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer"
@@ -143,4 +155,4 @@ clean:
rm -f catan catan-server catan-picosim catan.exe
rm -rf pico/build
-.PHONY: all debug test clean pico catan-server-static windows web deploy-web release publish
+.PHONY: all debug test clean pico catan-server-static windows web deploy-web release publish FORCE
diff --git a/catan/linux/selftest.c b/catan/linux/selftest.c
@@ -1500,7 +1500,13 @@ int sim_one(uint64_t seed, int players, bool v2p, bool verbose, Auth *a, EmitFn
return -1;
}
-/* Duelo: en la partida i el asiento i % jugadores usa la busqueda, el resto la heuristica. */
+#ifdef DUEL_OLD
+/* la search.c de la version publicada (core/search_old.c, la genera el Makefile) */
+bool search_old_decide(const Game *g, int seat, Rng *rng, const SearchCfg *cfg, Move *out);
+#endif
+
+/* Duelo: en la partida i el asiento i % jugadores usa la busqueda, el resto la heuristica
+ * (o, en catan-duel, la busqueda de la version anterior: nueva contra vieja). */
int app_duel(int n, uint64_t seed0, int players, bool v2p, const SearchCfg *cfg)
{
static Auth a;
@@ -1536,7 +1542,11 @@ int app_duel(int n, uint64_t seed0, int players, bool v2p, const SearchCfg *cfg)
clock_gettime(CLOCK_MONOTONIC, &t1);
if (d) { ms += (t1.tv_sec - t0.tv_sec) * 1e3 + (t1.tv_nsec - t0.tv_nsec) / 1e6; decisions++; }
} else {
+#ifdef DUEL_OLD
+ d = search_old_decide(g, s, &br, cfg, &m);
+#else
d = bot_decide(g, s, &br, &m);
+#endif
}
if (!d) continue;
int e = auth_submit(&a, &m);
@@ -1555,7 +1565,11 @@ int app_duel(int n, uint64_t seed0, int players, bool v2p, const SearchCfg *cfg)
}
if (ok) { done++; if (g->winner == strong) wins++; }
}
+#ifdef DUEL_OLD
+ printf("%d partidas (%d jugadores%s, %d iter, horizonte %d): la busqueda NUEVA gano %d de %d contra la VIEJA (%.1f%%, base %.1f%%), %d errores\n",
+#else
printf("%d partidas (%d jugadores%s, %d iter, horizonte %d): busqueda gano %d de %d (%.1f%%, base %.1f%%), %d errores\n",
+#endif
n, players, v2p ? ", 2p" : "", cfg->iters, cfg->horizon, wins, done,
done ? 100.0 * wins / done : 0.0, 100.0 / humans, bad);
printf("%.2f ms por decision (%ld decisiones)\n", decisions ? ms / (double)decisions : 0.0, decisions);