---
type: tutorial
title: Ein Marketplace-Spiel bauen
summary: "Bau ein Caputchin-Spiel aus einem leeren Ordner: installier das SDK, registrier eine Factory, render aus dem Pro-Runde-Kontext, zeichne einen Trace auf und bündel es für den gesandboxten Iframe."
---

Am Ende dieses Tutorials hast du ein vollständiges, spielbares Caputchin-Spiel in einem JavaScript-Bundle, bereit, abspielbar gemacht und veröffentlicht zu werden. Das ist die langweilige Jeden-Schritt-Version; lies zuerst [Ein Spiel an den Marketplace ausliefern](/docs/marketplace-game-development/overview) dafür, wo es hineinpasst.

Du brauchst Node und einen Bundler (esbuild, rollup, vite oder webpack, einer von ihnen). Du brauchst kein Caputchin-Konto zum Bauen, nur zum Veröffentlichen.

## 1. Das SDK installieren

```bash
npm install @caputchin/game-sdk
```

Das SDK ist winzig: ein `register`-Helfer plus TypeScript-Typen. Es bündelt die Widget-Runtime nicht, also bleibt dein Spiel klein.

## 2. Eine Spiel-Factory registrieren

Ein Spiel ist ein einzelner Aufruf von `register` mit einer **Factory-Funktion**. Das Widget ruft deine Factory im gesandboxten Iframe auf; dieser Aufruf ist selbst das Startsignal (es gibt kein separates Start-Event, auf das man warten müsste).

```js
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;
});
```

Du übergibst das Manifest **nicht** an `register`; der Server liest `caputchin.json` zur Index-Zeit und liefert die aufgelösten Presets als `ctx` an deine Factory hinunter. Sieh dir die [SDK-Referenz](/docs/marketplace-game-development/game-sdk-reference) für die vollständige Fläche an.

## 3. Aus dem Pro-Runde-Kontext rendern

Das dritte Argument der Factory, `ctx`, trägt alles, was sich pro Besucher ändert:

- `ctx.seed`: der Pro-Runde-Seed. **Leite jede Zufälligkeit hieraus ab** (siehe Schritt 5). `null` außerhalb einer verifizierten Session.
- `ctx.locale`: aufgelöste Sprach-Strings (`ctx.locale._lang` plus deine Schlüssel), oder `null`.
- `ctx.skin`: aufgelöste Farben und Asset-URLs (`ctx.skin._theme`, immer `light` oder `dark`, plus deine Schlüssel), oder `null`.
- `ctx.config`: aufgelöste Gameplay-Konfiguration, oder `null`.

Lies deine eigenen Schlüssel von diesen ab; du löst Presets nie selbst auf. Jedes ist `null`, wenn dein Manifest keinen passenden Block deklariert, also fall immer auf einen eingebauten Standard zurück:

```js
function startGame(container, bridge, ctx) {
  const title = ctx.locale?.title ?? "Tap the targets";
  const accent = ctx.skin?.accent_color ?? "#2da44e";
  const targetCount = ctx.config?.target_count ?? 5;
  // ...render with these...
}
```

Braucht dein Layout eine explizite Größe, ruf `bridge.setSize(width, height)` einmal nach deinem ersten Paint.

## 4. Gewinnen, verlieren und Fehler

Dein Spiel entscheidet, wann der Spieler gewinnt, und sagt es dem Host über die Brücke:

- Bei einem Sieg ruf `bridge.pass({ trace })` mit dem [Trace](#5-record-a-trace) der Runde (Schritt 5).
- Bei einem Verlust oder Abbruch ruf **nichts**; Stille ist das Fehler-Signal.
- Wenn das Spiel selbst kaputtgeht (ein Asset schlug fehl, eine Exception), ruf `bridge.error({ code, message })`. Das ist für ein spiel-internes Versagen, keinen verlierenden Spieler.

```js
function onWin(traceString) {
  bridge.pass({ trace: traceString });
}
function onAssetFailure(err) {
  bridge.error({ code: "asset-load-failed", message: String(err) });
}
```

## 5. Einen Trace aufzeichnen [#5-record-a-trace]

Das ist der Schritt, der das Spiel **abspielbar** macht. Während der Spieler agiert, zeichne die Eingaben auf, die das Ergebnis treiben (welches Ziel er traf, in Reihenfolge, mit Timing, wenn es zählt), und serialisier diesen Datensatz zu einem String oder Byte-Array: das ist dein **Trace**. Kombiniert mit dem Seed muss der Trace deine Logik das exakte Ergebnis reproduzieren lassen, weil der Server ihn erneut laufen lässt.

Zwei Regeln machen das möglich:

1. **Leite jede Zufallswahl aus `ctx.seed` ab.** Ruf nie `Math.random()` auf und lies nie die Wall-Clock für irgendetwas, das das Ergebnis beeinflusst.
2. **Zeichne genug zum Abspielen auf.** Der Trace plus der Seed ist die vollständige Eingabe für deine Spiel-Logik.

```js
function makeRng(seed) {
  // seed is ctx.seed; feed it so the server reproduces the same sequence.
  let s = hashSeed(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}
```

Die Form von `seed`, dem Trace und dem Urteil sind durch [den Replay-Vertrag](/docs/marketplace-game-development/replay-contract) definiert; diese Seite behandelt, wie man dieses aufgezeichnete Spiel in das Headless-`run`-Artefakt verwandelt, das der Server abspielt. Klingt deterministische Logik von Hand schreiben fummelig, das [Engine-Kit](/docs/marketplace-game-development/engine-kit) tut es für dich, optional.

## 6. 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 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 im Opaque-Origin-, Strict-CSP-Iframe **nicht** funktionieren:

- 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-src` blockiert sie.

## 7. Es lokal testen

Es gibt keine spezielle Harness. Bette das Widget in eine statische HTML-Seite ein, richte `game-src` auf deine lokale Bundle-Ausgabe und spiel es in einem Browser:

```html
<caputchin-game sitekey="cpt_pub_..." game-src="http://localhost:8080/game.js"></caputchin-game>
```

Das ist derselbe `game-src`-Weg, den ein [Custom-Spiel](/docs/custom-game-development/self-hosted-game) nutzt; für den Marketplace veröffentlichst du stattdessen das Repo, sodass die Plattform das Bundle für dich pinnt.

## Nächste Schritte

- [Der Replay-Vertrag](/docs/marketplace-game-development/replay-contract): verwandle dein aufgezeichnetes Spiel in das `run`-Artefakt, das das Spiel abspielbar macht.
- [Das caputchin.json-Manifest](/docs/marketplace-game-development/the-caputchin-json-manifest): das Spiel, seine Presets und sein Bundle beschreiben.
- [An den Marketplace veröffentlichen](/docs/marketplace-game-development/publish-to-the-marketplace): das Repo taggen und live gehen.

## Siehe auch

- [Spiel-SDK-Referenz](/docs/marketplace-game-development/game-sdk-reference): jeder Export, in voller Länge.
- [Das engine-kit](/docs/marketplace-game-development/engine-kit): der optionale Weg, Determinismus kostenlos zu bekommen.
