Ein selbst gehostetes Spiel bauen
Am Ende dieses Tutorials hast du ein echtes Caputchin-Spiel: ein JavaScript-Bundle, das im gesandboxten Iframe des Widgets läuft, mit dem Host über das Spiel-SDK spricht und einen Trace meldet, den der Server erneut laufen lassen kann. Anders als der manuelle Modus kann ein selbst gehostetes Spiel einen Site-Key gaten, weil seine Runde abspielbar ist. Dieses Tutorial behandelt das Bauen und Hosten des Spiels; Replay und Gating behandelt das Artefakt, das das Gate tatsächlich einschaltet.
Lies dein eigenes Spiel betreiben dafür, wo das hineinpasst. Du brauchst einen Ort, um eine statische JavaScript-Datei zu hosten (jedes CDN oder jeder statische Host), und das Widget auf deiner Seite.
Die Form eines selbst gehosteten Spiels
Ein selbst gehostetes Spiel ist ein in sich geschlossenes JavaScript-Bundle, das:
- Die
register-Funktion von@caputchin/game-sdkimportiert und eine einzelne Spiel-Factory registriert. - In den Container rendert, den das Widget der Factory gibt, mit der aufgelösten Sprache, dem Skin und der Konfiguration, die daneben übergeben werden.
- Deterministisch unter dem Pro-Runde-Seed abläuft und das Spiel als Trace aufzeichnet.
- Bei einem Sieg
bridge.pass({ trace })mit diesem Datensatz aufruft.
Das Widget lädt dein Bundle in seinen Iframe und ruft deine Factory auf; dass die Factory aufgerufen wird, ist das Startsignal (es gibt kein separates Start-Event, auf das man warten müsste).
1. Eine Spiel-Factory registrieren
Installier das SDK und ruf register einmal mit deiner Factory. Du übergibst das Manifest nicht; der Server löst Presets auf und reicht sie der 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-sdk2. Den Pro-Runde-Kontext lesen
Das dritte Argument der Factory, ctx, trägt alles, was dein Spiel für diesen Besucher braucht:
ctx.seed: der Pro-Runde-Replay-Seed. Leite jede Zufälligkeit hieraus ab. Null, wenn das Spiel außerhalb einer verifizierten Session läuft.ctx.locale: das aufgelöste Sprach-Objekt,ctx.locale._langplus deine übersetzten String-Schlüssel.ctx.skin: das aufgelöste Skin-Objekt,ctx.skin._theme(immerlightoderdark) plus deine Farb- und Asset-Schlüssel.ctx.config: die aufgelöste Gameplay-Konfiguration (oder null).
Das sind die aufgelösten Presets für diesen Besucher, von Caputchin aus deinem dashboard-definierten Schema und Presets produziert. Du liest deine eigenen Schlüssel von ihnen ab; du löst nichts selbst auf.
Braucht dein Layout eine explizite Größe, sag es dem Host einmal nach deinem ersten Paint:
bridge.setSize(360, 480);3. Das Spiel deterministisch machen
Das ist die Regel, die ein Spiel gatebar macht: das Spiel muss deterministisch sein, gegeben den Seed und die Eingaben des Spielers. Ruf nicht Math.random() auf und lies nicht die Wall-Clock für irgendetwas, das das Ergebnis beeinflusst; leite jede Zufallswahl aus ctx.seed ab. Der Server lässt deine Logik unter demselben Seed und demselben aufgezeichneten Trace erneut laufen und muss zum selben Urteil kommen.
// 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;
}Zeichne die Eingaben auf, die das Ergebnis treiben (welche Ziele der Spieler traf, in Reihenfolge, mit Timing, wenn es zählt), während du spielst. Serialisier diesen Datensatz zu einem String: dieser String ist dein Trace.
4. Mit dem Trace bestehen
Wenn der Besucher gewinnt, reich dem Host den Trace. pass nimmt ein Objekt mit einem einzelnen trace-String:
function onWin(traceString) {
bridge.pass({ trace: traceString });
}Der Trace ist opak für die Plattform: er ist, was auch immer für ein String dein Spiel allein definiert, sodass deine Logik, kombiniert mit dem Seed, das Ergebnis reproduziert. Das Spiel meldet hier keine Punktzahl; Scoring, falls es welches gibt, ist deine eigene In-Iframe-Angelegenheit.
Zwei weitere Brücken-Fakten:
- Das erste
passlöst die Runde ein; eine fehlgeschlagene oder abgebrochene Runde wird signalisiert, indem manpasseinfach nicht aufruft (es gibt keinefail-Methode auf dieser Brücke). bridge.error({ code, message })ist für ein spiel-internes Versagen (ein Asset konnte nicht laden, eine Exception), keinen Spieler-Verlust. Es bringt einerror-Event zum Host.
Nachdem der Host das Pass eingelöst hat, gibt er das Widget-Token frei, und dein Backend verifiziert dieses Token wie üblich.
5. Für die Sandbox bündeln
Das Widget lädt genau eine Skript-URL, also muss alles in dieser einen Datei sein. Konfigurier deinen Bundler (esbuild, rollup, vite, webpack; einer von ihnen) für eine in sich geschlossene Ausgabe:
- Assets inline als Data-URLs (Sprites, Sounds, Schriften);
- Code-Splitting deaktivieren;
- nutzt du WASM, bette es als base64 ein und instanziiere aus Bytes.
Muster, die in der Sandbox nicht funktionieren, weil der Iframe Opaque-Origin mit einer strikten CSP ist:
- pfad-relatives
fetch('./sprite.png'), es gibt keinen Pfad, von dem abgerufen werden kann; - dynamisches
import('./chunk.js'), die zweite URL ist blockiert; new Worker('./worker.js'), starte Worker stattdessen aus Inline-Blob-URLs;- externe CDN-Abrufe zur Laufzeit,
connect-srcblockiert sie.
6. Das Bundle hosten und das Widget darauf richten
Serviere die gebaute Datei von deinem eigenen statischen Host über https (Loopback-http ist für lokale Entwicklung erlaubt), dann richte das Widget darauf:
<caputchin-game
sitekey="cpt_pub_..."
game-src="https://cdn.example.com/my-game/game.js"
></caputchin-game>Das Widget lädt dein Bundle in seinen gesandboxten Iframe, ruft deine Factory mit dem Kontext auf und wartet auf dein pass. An diesem Punkt läuft und verifiziert das Spiel, aber es darf einen Key noch nicht gaten.
7. Es gatebar machen
Um dein selbst gehostetes Spiel als Verifizierungs-Gate zu nutzen, sind zwei weitere Dinge nötig, beide als Nächstes behandelt:
- Registrier es als Custom-Spiel im Dashboard (eine id, die du wählst) und definier sein Feld-Schema und Presets, sodass der Kontext echtes Locale/Skin/Config trägt.
- Lade ein Replay-Artefakt hoch: einen Headless-Build deiner Spiel-Logik, den der Server ausführt, um das Urteil aus Seed und Trace neu abzuleiten. Sieh dir Replay und Gating an.
Bis das Replay-Artefakt hochgeladen ist und seine Selbstprüfung besteht, zeigt sich das Custom-Spiel als Nicht abspielbar und kann nicht gaten.
Siehe auch
- Replay und Gating: das Artefakt, das das Gate einschaltet.
- Dashboard-Schema-Referenz: die Felder definieren, die der Kontext auflöst.
- Manueller Modus: die leichtere, nicht-gatende Alternative.
- Auf deinem Backend verifizieren: das Token bestätigen, das ein Pass produziert.