Caputchin
Desarrollo de juegos a medida

Construye un juego autoalojado

Al final de este tutorial tendrás un juego de Caputchin real: un bundle de JavaScript que corre en el iframe en sandbox del widget, habla con el anfitrión a través del game SDK, y reporta una traza que el servidor puede volver a correr. A diferencia del modo manual, un juego autoalojado puede ejercer de gate de una clave de sitio, porque su ronda es reproducible. Este tutorial cubre construir y alojar el juego; repetición y gate cubre el artefacto que de verdad activa el gate.

Lee corre tu propio juego para saber dónde encaja esto. Necesitas un sitio donde alojar un archivo de JavaScript estático (cualquier CDN o host estático) y el widget en tu página.

La forma de un juego autoalojado

Un juego autoalojado es un bundle de JavaScript autocontenido que:

  1. Importa la función register del @caputchin/game-sdk y registra una única factory de juego.
  2. Renderiza en el contenedor que el widget le da a la factory, usando el idioma, el skin y la configuración resueltos que se pasan junto a él.
  3. Se desarrolla deterministamente bajo la semilla por ronda, registrando el juego como una traza.
  4. Al ganar, llama a bridge.pass({ trace }) con ese registro.

El widget carga tu bundle en su iframe e invoca tu factory; que la factory sea llamada es la señal de inicio (no hay un evento de inicio separado que esperar).

1. Registra una factory de juego

Instala el SDK y llama a register una vez con tu factory. No pasas el manifiesto; el servidor resuelve los presets y se los entrega a la factory.

import { register } from "@caputchin/game-sdk";

register((container, bridge, ctx) => {
  // container — a DOM element inside the sandboxed iframe; render into it.
  // bridge    — push-only channel to the host (pass / error / setSize).
  // ctx       — the per-round context (seed + resolved locale/skin/config).

  const cleanup = startGame(container, bridge, ctx);

  // Return an optional cleanup function; the widget calls it on unmount.
  return cleanup;
});
npm install @caputchin/game-sdk

2. Lee el contexto por ronda

El tercer argumento de la factory, ctx, lleva todo lo que tu juego necesita para este visitante:

  • ctx.seed — la semilla de repetición por ronda. Deriva toda la aleatoriedad de esto. Null cuando el juego corre fuera de una sesión verificada.
  • ctx.locale — el objeto de idioma resuelto: ctx.locale._lang más tus claves de cadena traducidas.
  • ctx.skin — el objeto de skin resuelto: ctx.skin._theme (siempre light o dark) más tus claves de color y de asset.
  • ctx.config — la configuración de juego resuelta (o null).

Estos son los presets resueltos para este visitante, producidos por Caputchin a partir de tu esquema y presets definidos en el dashboard. Lees tus propias claves de ellos; no resuelves nada tú mismo.

Si tu layout necesita un tamaño explícito, díselo al anfitrión una vez tras tu primer pintado:

bridge.setSize(360, 480);

3. Haz el juego determinista

Esta es la regla que hace gateable un juego: el juego debe ser determinista dados la semilla y las entradas del jugador. No llames a Math.random() ni leas el reloj de pared para nada que afecte al desenlace; deriva cada elección aleatoria de ctx.seed. El servidor vuelve a correr tu lógica bajo la misma semilla y la misma traza registrada y debe llegar al mismo veredicto.

// A tiny seeded PRNG; feed it ctx.seed so the server reproduces the run.
function makeRng(seed) {
  let s = hashToInt(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}

Registra las entradas que conducen el desenlace (qué objetivos golpeó el jugador, en orden, con temporización si importa) mientras juegas. Serializa ese registro a una cadena: esa cadena es tu traza.

4. Pasa con la traza

Cuando el visitante gana, entrégale al anfitrión la traza. pass toma un objeto con una única cadena trace:

function onWin(traceString) {
  bridge.pass({ trace: traceString });
}

La traza es opaca para la plataforma: es cualquier cadena que solo tu juego define, tal que tu lógica, combinada con la semilla, reproduzca el resultado. El juego no reporta una puntuación aquí; la puntuación, si la hay, es asunto tuyo dentro del iframe.

Dos hechos más del bridge:

  • El primer pass canjea la ronda; una ronda fallida o abandonada se señala simplemente no llamando a pass (no hay método fail en este bridge).
  • bridge.error({ code, message }) es para un fallo interno del juego (un asset falló al cargar, una excepción), no una derrota del jugador. Hace aflorar un evento error al anfitrión.

Después de que el anfitrión canjea el pass, libera el token del widget, y tu backend verifica ese token como siempre.

5. Empaqueta para el sandbox

El widget carga exactamente una URL de script, así que todo debe estar en ese único archivo. Configura tu bundler (esbuild, rollup, vite, webpack; cualquiera de ellos) para una sola salida autocontenida:

  • inlinea los assets como data URLs (sprites, sonidos, fuentes);
  • desactiva el code splitting;
  • si usas WASM, embébelo como base64 e instáncialo desde bytes.

Patrones que no funcionan dentro del sandbox, porque el iframe es de origen opaco con una CSP estricta:

  • fetch('./sprite.png') relativo a la ruta — no hay ruta desde la que descargar;
  • import('./chunk.js') dinámico — la segunda URL está bloqueada;
  • new Worker('./worker.js') — lanza workers desde URLs Blob inline en su lugar;
  • descargas de CDN externo en tiempo de ejecución — connect-src las bloquea.

6. Aloja el bundle y apunta el widget a él

Sirve el archivo construido desde tu propio host estático sobre https (se permite http de loopback para dev local), luego apunta el widget a él:

<caputchin-game
  sitekey="cpt_pub_..."
  game-src="https://cdn.example.com/my-game/game.js"
></caputchin-game>

El widget carga tu bundle en su iframe en sandbox, invoca tu factory con el contexto, y espera tu pass. En este punto el juego corre y verifica, pero todavía no está permitido ejercer de gate de una clave.

7. Hazlo gateable

Para usar tu juego autoalojado como gate de verificación, hacen falta dos cosas más, ambas cubiertas a continuación:

  1. Regístralo como un juego a medida en el dashboard (un id que eliges) y define su esquema y presets de campos para que el contexto lleve idioma/skin/config reales.
  2. Sube un artefacto de repetición: un build headless de la lógica de tu juego que el servidor corre para volver a derivar el veredicto de la semilla y la traza. Mira repetición y gate.

Hasta que el artefacto de repetición se sube y pasa su autocomprobación, el juego a medida aparece como No reproducible y no puede ejercer de gate.

Véase también

En esta página