README.md (15649B)
1 # Catan en C — terminal, gráfico, online y PicoCalc 2 3 Catan (juego base completo + variante oficial para 2) escrito en C99 portable. 4 El mismo núcleo corre en: 5 6 - **la terminal** (TUI con ncurses, tablero dibujado con medios bloques de color), 7 - **una ventana gráfica** (SDL3, pixel-art), 8 - **un servidor** para tu VPS (partidas online en vivo o asincrónicas, que sobreviven reinicios), 9 - **la PicoCalc** (Raspberry Pi Pico 1), con **cable 1v1** entre dos PicoCalc. 10 11 Todos hablan el mismo protocolo: alguien en la terminal y alguien en la versión 12 gráfica juegan la misma partida sin notar diferencia. 13 14 ## Compilar 15 16 Dependencias: `gcc`/`clang`, `make`, `ncursesw`, `SDL3` (solo para la GUI y el simulador). 17 18 ```sh 19 make # catan, catan-server, catan-picosim 20 make test # pruebas del núcleo + 400 partidas bot contra bot + duelos búsqueda vs heurística 21 make pico # firmware PicoCalc (ver más abajo) 22 ``` 23 24 ## Jugar 25 26 ```sh 27 ./catan # menú (terminal) 28 ./catan-gui # lo mismo, en ventana gráfica (= ./catan --gui) 29 ./picocalc-sim # simulador de la PicoCalc en la PC 30 31 ./catan hotseat -n 4 --bots 3 # vos contra 3 bots, directo 32 ./catan hotseat -n 2 --2p # 1v1 en la misma compu con la variante para 2 33 ./catan resume # retomar la última partida 34 ./catan online mivps.com:7373 # mis partidas en el servidor 35 ./catan host 7373 # crear partida en red local 36 ./catan join 192.168.0.10 7373 # unirse a una partida en red local 37 ``` 38 39 Opciones: `--lang es|en`, `--gui`, `--layout wide|compact`, `--scale N`. 40 Las partidas locales se guardan solas en `~/.local/share/catan/saves/`. 41 42 ### Controles 43 44 | Tecla | Acción | 45 |---|---| 46 | Flechas / hjkl | mover el cursor por los lugares válidos | 47 | Enter / Espacio | confirmar | 48 | Esc | volver / cancelar | 49 | `r` | tirar los dados | 50 | `1` `2` `3` `4` | ruta, pueblo, ciudad, comprar carta | 51 | `d` | jugar carta de desarrollo | 52 | `b` | comerciar con banco / puerto | 53 | `t` | ofrecer trueque a los demás | 54 | `e` | terminar el turno | 55 | `f` `z` | (variante 2p) comercio forzado, ladrón al desierto | 56 | `Tab` | siguiente objetivo / panel de detalle | 57 | `?` `L` `q` | ayuda, idioma, salir | 58 59 Con mouse (terminal o GUI) se puede clickear en el tablero, las acciones y los menús. 60 61 En los diálogos de cartas (descartar, trueque, descubrimiento): ←→ elige recurso, 62 ↑↓ o `+`/`-` cambian la cantidad, `1`-`5` suman, Tab cambia entre "das" y "pedís". 63 64 ## Reglas 65 66 Catan base completo: colocación inicial en serpiente, producción (con la regla de 67 banco sin cartas), 7 con descarte y ladrón, rutas/pueblos/ciudades, cartas de 68 desarrollo (14 caballeros, 5 PV, 2 de cada progreso), camino más largo (cortado 69 por edificios ajenos), ejército más grande, comercio 4:1 / 3:1 / 2:1 y trueques 70 entre jugadores. Se gana con 10 PV en tu turno. 71 72 **Variante para 2** (inspirada en la de *Mercaderes y Bárbaros*): 73 74 - Hay 2 jugadores **neutrales** con 2 pueblos y 2 rutas iniciales al azar. 75 - Cada vez que construís una ruta tenés que construirle una ruta gratis a un 76 neutral; cada pueblo, un pueblo neutral (o una ruta si no hay lugar). 77 - **Dos tiradas por turno**; la segunda no puede sumar lo mismo que la primera. 78 - **Fichas de comercio**: arrancás con 2, ganás 1 por cada caballero jugado y por 79 cada pueblo al lado del desierto. Cuestan 1 (2 si vas ganando) y sirven para: 80 - **comercio forzado**: le sacás 2 cartas al azar al rival y le devolvés 2 que elijas; 81 - **ladrón al desierto**. 82 - **Ladrón amable**: no puede ir a un hexágono de alguien con 2 PV o menos. 83 84 Los valores están en `Opts` (`core/catan.h`) por si tu edición cambia algo. 85 86 ## Online: servidor en tu VPS 87 88 ```sh 89 make catan-server-static # binario estático, anda en cualquier Linux x86-64 90 scp catan-server ../comun/deploy/install-vps.sh root@mivps:/tmp/ 91 ssh root@mivps sh /tmp/install-vps.sh catan 7373 /tmp/catan-server "Servidor de Catan" 92 ``` 93 94 Eso crea el usuario `catan`, guarda las partidas en `/var/lib/catan` y lo deja 95 corriendo con systemd en el puerto **7373** (abrilo en el firewall). Logs: 96 `journalctl -u catan-server -f`. 97 98 Cómo se usa: 99 100 1. En `./catan` → *Jugar online*, poné `mivps.com:7373`, tu **nombre** y una **contraseña**. 101 No hay cuentas: nombre + contraseña son tu llave (el cliente manda solo un hash). 102 2. *Crear partida*: elegís jugadores, qué asientos son bots, el nivel de los bots (fáciles, 103 medios o difíciles: la misma búsqueda con más o menos iteraciones) y **"Bot juega si no 104 hay"** (nunca, 1, 4, 12, 24 o 48 horas). Quién empieza se sortea. Te da un **código** de 6 letras; pasáselo a tus amigos. 105 3. Tus amigos: *Jugar online* → su nombre y contraseña → el código → *Unirse con el código*. 106 4. Se juega igual estén todos o no: los conectados ven todo en vivo y la partida da la 107 vuelta hasta llegar a alguien que no está. Ahí espera; si pasa el tiempo elegido, el 108 bot juega ese turno por él y sigue. Cerrá cuando quieras: otro día, desde **cualquier 109 compu**, nombre + contraseña → *Mis partidas* muestra las tuyas y cuáles dicen **¡TE TOCA!**. 110 111 Dos instancias en la misma compu con distintos nombres son dos jugadores distintos. 112 Sin contraseña, *Mis partidas* usa las partidas guardadas en esta compu 113 (`~/.local/share/catan/online`, como antes). El tráfico va en texto plano; si querés 114 cifrarlo, usá un túnel SSH (`ssh -L 7373:localhost:7373 mivps`) o stunnel. 115 116 Al terminar una partida, **jugar otra** (`a`, o tocar la línea en el cuadro final) arma 117 una nueva con las mismas opciones: local, con otro reparto; online, el servidor la crea 118 con la misma gente en los mismos asientos y a los demás les aparece en *Mis partidas*. 119 120 **Si alguien se olvida la contraseña**: el servidor no la guarda (solo la llave que 121 deriva el cliente), pero el administrador puede ponerle una nueva a ese asiento: 122 123 ```sh 124 sudo catan-admin partidas # códigos y asientos 125 sudo catan-admin reset ECM8CA Félix nuevaclave # Félix entra con "nuevaclave" desde ahora 126 ``` 127 128 (`install-vps.sh` instala `catan-admin`; el secreto está en `/var/lib/catan/admin.key`.) 129 130 ## Web: la PWA para el teléfono 131 132 El mismo cliente compilado a WebAssembly (`make web`, ver abajo) se sirve desde una 133 página HTTPS. En el teléfono se entra a la dirección, aparece el menú de siempre y con 134 *Agregar a pantalla de inicio* queda como una app. En vertical el tablero va arriba y 135 el panel abajo; en apaisado es el layout ancho de la PC. Lo que en la PC es una tecla 136 (Esc, ayuda, idioma, salir) son botones; el chat y los campos de texto abren un cuadro 137 del navegador (SDL no muestra el teclado táctil). 138 139 - Las partidas locales y la configuración se guardan en el almacenamiento del navegador 140 (IndexedDB): sobreviven a cerrar y recargar, se pierden si se borran los datos del 141 sitio. En iPhone, instalada en la pantalla de inicio, queda exenta de la limpieza de 142 7 días de Safari. Con la app instalada, jugar solo anda sin internet. 143 - Online es idéntico a la PC: nombre + contraseña dan la misma llave, así que *Mis 144 partidas* muestra las mismas y se puede meter un turno desde el celular en la partida 145 que se juega en la compu. El campo "Servidor" no aparece: la web habla siempre con el 146 servidor de la página. 147 148 **Compilar** (`make web` → `web/dist/`): necesita emscripten (`pacman -S emscripten`, 149 `source /etc/profile.d/emscripten.sh`) y la fuente de SDL3 (la misma de `make windows`); 150 la primera vez compila SDL3 para wasm en `~/.local/opt/SDL3-em`. El cliente no cambia: 151 `-sASYNCIFY` hace que sus esperas (SDL, `poll()`) cedan al navegador, y los sockets van 152 por un WebSocket. Para probar en la PC: 153 154 ```sh 155 ./catan-server --port 7373 --data /tmp/catan-data & # acepta TCP y WebSocket en el mismo puerto 156 (cd web/dist && python3 -m http.server 8080) 157 chromium --window-size=390,844 'http://localhost:8080/?ws=ws://localhost:7373' 158 ``` 159 160 **Servir**: el navegador necesita `wss://` (TLS), que pone Caddy (o nginx) delante del 161 servidor; el `catan-server` entiende WebSocket solo (`comun/server/ws.c`). Bloque de 162 `/etc/caddy/Caddyfile`: 163 164 ``` 165 catan.lu3dhn.xyz { 166 encode zstd gzip 167 handle /ws { reverse_proxy 127.0.0.1:7373 } 168 handle { root * /var/www/catan; file_server } 169 } 170 ``` 171 172 `make deploy-web` sube `web/dist/` al VPS (host `vps` de ssh) a `/var/www/catan`. 173 174 Pendientes conocidos de la web: en iPhone el cuadro de texto no abre el teclado solo 175 (hay que tocar el campo); una partida local vive en el navegador de ese teléfono (si se 176 borran los datos del sitio se pierde: no hay exportar/importar todavía); una sola 177 instancia a la vez (la segunda pestaña avisa y no arranca). 178 179 ## PicoCalc 180 181 ### Compilar y cargar desde la tarjeta SD (sin cable USB) 182 183 Hace falta el compilador ARM y el pico-sdk: 184 185 ```sh 186 sudo pacman -S arm-none-eabi-gcc arm-none-eabi-newlib 187 git clone --depth 1 --recurse-submodules https://github.com/raspberrypi/pico-sdk ~/pico-sdk 188 make pico # arma la carpeta sd/ lista para copiar 189 ``` 190 191 Copiá a la **tarjeta SD** de la PicoCalc lo que corresponda a tu cargador: 192 193 - **Cargador de ClockworkPi "Bootloader v0.5"** (la SD tiene la carpeta `firmware/` con 194 archivos `.bin`, y al prender aparece la lista): copiá `sd/firmware/Catan.bin` a 195 `firmware/` y elegilo en la lista. Es el que está probado. 196 - **uf2loader** (SD v0.6 o más nueva, carpeta `pico1-apps/` con `.uf2`): copiá 197 `sd/pico1-apps/Catan.uf2` a `pico1-apps/` y elegilo en el menú (encendé con **Arriba**). 198 199 Además van en la raíz de la SD dos archivos que `make pico` deja en `sd/`: 200 201 - `CATAN.SAV` (80 KB): **la partida guardada**. Sin este archivo el juego no guarda. 202 - `CATAN.LOG` (256 KB de espacios): cada arranque agrega ahí qué pasó (pantalla, 203 teclado, cable, SD, errores). Si algo falla, ese archivo dice dónde. 204 205 Copialos **solo la primera vez**: si después los volvés a copiar encima, perdés la 206 partida guardada y el log. El firmware nunca crea archivos ni toca la FAT: solo 207 reescribe el contenido de estos dos, así que no puede romper la tarjeta. Si hace 208 falta crearlos a mano: 209 210 ```sh 211 head -c 81920 /dev/zero > /ruta/a/la/sd/CATAN.SAV 212 head -c 262144 /dev/zero | tr '\0' ' ' > /ruta/a/la/sd/CATAN.LOG 213 ``` 214 215 **Detalles del arranque desde la SD** (lo que costó hacerlo andar): 216 217 - El cargador salta al programa sin apagar nada: deja interrupciones armadas y **su 218 segundo núcleo (core 1) corriendo y dibujando en la pantalla**. Catan limpia las 219 interrupciones en su primera instrucción (`catan_early_reset`, que 220 `comun/tools/patch_reset.py` instala en el vector de reset del `.bin`) y resetea el core 1 221 antes de tocar la pantalla. 222 - La pantalla necesita cada comando con sus parámetros bajo un mismo *chip select*. 223 - La flash de la Pico no se escribe nunca: la partida va en `CATAN.SAV`, en dos 224 mitades que se alternan. Si se corta la energía mientras guarda, queda el guardado 225 anterior. 226 227 Antes de probar en el aparato, probalo en la PC: `./picocalc-sim` corre **el mismo código**. 228 229 En el menú principal está **"Prueba: 10 partidas de bots"**: juega 10 partidas completas 230 en la Pico verificando las reglas en cada movida y deja el resultado en la pantalla y en 231 `catan.log`. 232 233 ### El cable 1v1 234 235 La PicoCalc tiene dos headers de 8 pines. Usamos **J703** (el del lado de la Pico), 236 con el UART1 del RP2040: 237 238 | Pin J703 | Señal | Conectar con la otra PicoCalc | 239 |---|---|---| 240 | 4 | GP4 = TX | pin **5** | 241 | 5 | GP5 = RX | pin **4** | 242 | 8 | GND | pin **8** | 243 | 1 | 3V3 | **no conectar** | 244 245 ``` 246 PicoCalc A (J703) PicoCalc B (J703) 247 pin 4 (TX) ─────────────── pin 5 (RX) 248 pin 5 (RX) ─────────────── pin 4 (TX) 249 pin 8 (GND) ────────────── pin 8 (GND) 250 ``` 251 252 - Las dos son de 3,3 V: no hace falta conversor. Cable corto (< 1 m). 115200 baudios. 253 - GP2-GP5 y GP21 los comparte la PSRAM de la placa; el firmware deja su CS (GP20) 254 en alto, así que no molesta (el juego no usa la PSRAM). 255 - **No** usamos J702 (UART0): va al chip USB-serie del USB-C y chocarían las señales. 256 257 Uso: en una PicoCalc *Cable 1v1: crear (A)*, en la otra *Cable 1v1: unirse (B)*. 258 La A es la autoridad (dados, mazo, bots extra) y guarda la partida en su SD; 259 si se desenchufa el cable, la B vuelve a sincronizar sola al reconectar. 260 Cada línea lleva un checksum; si llega algo corrupto, la B pide todo de nuevo. 261 262 ## Cómo está hecho 263 264 ``` 265 core/ reglas, tablero, movidas, autoridad, bots, i18n, controlador de UI 266 (C99 sin malloc ni stdio: compila igual para Linux y el RP2040) 267 gfx/ dibujo de Catan en pixeles (tablero, pantallas compacta/ancha) 268 linux/ cliente: TUI (ncurses), GUI (SDL3), menús, red, guardado, pruebas 269 server/ Catan como módulo del servidor de partidas (también embebido en `catan host`) 270 pico/ firmware PicoCalc: app.c (portable, corre igual en el simulador) 271 app_config.h nombre de la app, archivos de la SD 272 ``` 273 274 Lo que no es de Catan vive en `../comun/` (ver `comun/README.md`): renderer de 275 pixeles y fuente, formularios, kits de TUI y SDL, red, el esqueleto del servidor, 276 la capa de hardware de la PicoCalc (pantalla, teclado, SD, cable) y su simulador, 277 y el instalador del VPS. El ajedrez usa lo mismo. 278 279 La idea central: un nodo **autoridad** valida cada movida y resuelve el azar 280 (dados, robos, mazo); todos los demás aplican las movidas ya resueltas con 281 `game_apply()`. Una partida es su **log de movidas**, así que guardar, retomar, 282 reconectar y jugar asincrónico salen de lo mismo. Las cartas de desarrollo 283 compradas se ocultan a los demás (`move_redact`). 284 285 ### Bots 286 287 Hay dos niveles (en *Nueva partida*: "Bots: Fáciles / Difíciles", se recuerda en la config): 288 289 - **Fáciles** (`core/bot.c`): heurística de una movida. Valora esquinas por pips, 290 diversidad y puertos, junta para un objetivo (pueblo, ciudad, ruta o carta), 291 cambia con el banco hacia lo que le falta. No propone cambios a otros. 292 - **Difíciles** (`core/search.c`): ISMCTS. En cada iteración sortea lo que el bot 293 no puede ver (cartas de desarrollo ajenas y orden del mazo), baja por un árbol 294 de movidas candidatas (podadas con la heurística) eligiendo con UCB, juega 8 295 turnos más con el bot fácil y evalúa (puntos, producción, cartas). Cada nodo 296 guarda el puntaje de quien movió (max-n). Corta por tiempo: 300 ms por decisión 297 en la PC, 150 ms en el servidor, 1,2 s en la PicoCalc. 298 299 Contra tres bots fáciles, uno difícil gana ~70-78 % de las partidas de 4 300 (base 25 %) y ~85 % de las de 2 (`./catan --duel`). 301 302 ### Protocolo 303 304 Líneas de texto terminadas en `\n` (general en `comun/server/srv.h`, lo de Catan en `server/server.h`): 305 306 ``` 307 -> HELLO 1 Ana <- WELCOME 1 308 -> CREATE 2 1 10 0 HH <- GAME K7QX2M 0 <token> 309 <- HDR G ... (tablero y opciones) 310 <- SEAT 0 P 1 Ana 311 <- EV 2 NEUTRAL 2 1 34 (historial...) 312 <- SYNC 313 -> MOVE 0 SETTLE 12 <- EV 0 SETTLE 12 314 -> MOVE 0 ROLL 0 0 0 0 <- EV 0 ROLL 3 4 2 5 (la autoridad pone los dados) 315 ``` 316 317 ### Pruebas 318 319 ```sh 320 ./catan --selftest # reglas, serialización, replay, redacción 321 ./catan --sim 2000 4 # 2000 partidas de bots: invariantes (recursos, piezas, mazo) 322 ./catan --sim 2000 2 2p # lo mismo con la variante para 2 323 ./catan --duel 100 4 2000 8 # 100 partidas: un bot difícil (2000 iteraciones, horizonte 8) 324 # contra fáciles, rotando el asiento; también 2p, semilla y c de UCB 325 make debug && make test # con AddressSanitizer / UBSan 326 ```