---
type: tutorial
title: Построй игру для маркетплейса
summary: "Построй игру Caputchin из пустой папки: установи SDK, зарегистрируй фабрику, отрисуй из пер-раундового контекста, запиши трассу и собери её для iframe в песочнице."
---

К концу этого руководства у тебя будет полная, играбельная игра Caputchin в одном JavaScript-бандле, готовая к тому, чтобы сделать её воспроизводимой и опубликовать. Это скучная версия «сделай-каждый-шаг»; сначала прочитай [Выпусти игру в маркетплейс](/docs/marketplace-game-development/overview) о том, куда это вписывается.

Тебе нужен Node и бандлер (esbuild, rollup, vite или webpack, любой из них). Тебе не нужен аккаунт Caputchin, чтобы строить, только чтобы публиковать.

## 1. Установи SDK

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

SDK крошечный: хелпер `register` плюс типы TypeScript. Он не встраивает рантайм виджета, так что твоя игра остаётся маленькой.

## 2. Зарегистрируй игровую фабрику

Игра это единственный вызов `register` с **фабричной функцией**. Виджет вызывает твою фабрику внутри iframe в песочнице; этот вызов сам и есть сигнал старта (нет отдельного события старта, которого надо ждать).

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

Ты **не** передаёшь манифест в `register`; сервер читает `caputchin.json` во время индексации и шлёт разрешённые пресеты вниз твоей фабрике как `ctx`. Смотри [справочник SDK](/docs/marketplace-game-development/game-sdk-reference) для полной поверхности.

## 3. Отрисуй из пер-раундового контекста

Третий аргумент фабрики, `ctx`, несёт всё, что меняется на каждого посетителя:

- `ctx.seed` - пер-раундовый сид. **Выводи всю случайность из него** (см. шаг 5). `null` вне проверенной сессии.
- `ctx.locale` - разрешённые языковые строки (`ctx.locale._lang` плюс твои ключи) или `null`.
- `ctx.skin` - разрешённые цвета и URL ассетов (`ctx.skin._theme`, всегда `light` или `dark`, плюс твои ключи) или `null`.
- `ctx.config` - разрешённая игровая конфигурация или `null`.

Читай свои собственные ключи с них; ты никогда сам не разрешаешь пресеты. Каждый `null`, когда твой манифест не объявляет соответствующего блока, так что всегда откатывайся на встроенное умолчание:

```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...
}
```

Если твоей раскладке нужен явный размер, вызови `bridge.setSize(width, height)` один раз после первой отрисовки.

## 4. Победа, поражение и ошибки

Твоя игра решает, когда игрок побеждает, и говорит хосту через мост:

- При победе вызови `bridge.pass({ trace })` с [трассой](#5-record-a-trace) раунда (шаг 5).
- При поражении или брошенности не вызывай **ничего**; молчание это сигнал сбоя.
- Если сама игра ломается (ассет не загрузился, исключение), вызови `bridge.error({ code, message })`. Это для внутреннего сбоя игры, а не для проигрыша игрока.

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

## 5. Запиши трассу [#5-record-a-trace]

Это шаг, который делает игру **воспроизводимой**. По мере того как игрок действует, записывай вводы, которые ведут к исходу (по какой цели он попал, в порядке, с таймингом, если он важен), и сериализуй эту запись в строку или массив байтов: это твоя **трасса**. В сочетании с сидом трасса должна давать твоей логике воспроизвести точный результат, потому что сервер перезапускает её.

Два правила делают это возможным:

1. **Выводи каждый случайный выбор из `ctx.seed`.** Никогда не вызывай `Math.random()` и не читай настенные часы для чего-либо, что влияет на исход.
2. **Записывай достаточно для реплея.** Трасса плюс сид это полный ввод для твоей игровой логики.

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

Форма `seed`, трассы и вердикта определены [контрактом реплея](/docs/marketplace-game-development/replay-contract); та страница покрывает превращение этой записанной игры в headless-артефакт `run`, который реплеит сервер. Если ручное написание детерминированной логики звучит муторно, [движковый кит](/docs/marketplace-game-development/engine-kit) делает это за тебя, опционально.

## 6. Собери для песочницы

Виджет загружает ровно один URL скрипта, так что всё должно быть в этом одном файле. Настрой свой бандлер на один самодостаточный вывод:

- встрой ассеты как data-URL (спрайты, звуки, шрифты);
- отключи разбиение кода;
- если используешь WASM, встрой его как base64 и инстанцируй из байтов.

Шаблоны, которые **не** работают внутри iframe с непрозрачным источником и строгим CSP:

- путь-относительный `fetch('./sprite.png')` - нет пути, откуда запрашивать;
- динамический `import('./chunk.js')` - второй URL заблокирован;
- `new Worker('./worker.js')` - порождай воркеры из инлайновых `Blob`-URL вместо этого;
- внешние запросы к CDN в рантайме - `connect-src` их блокирует.

## 7. Протестируй локально

Нет специальной обвязки. Встрой виджет в статическую HTML-страницу, наведи `game-src` на твой локальный вывод бандла и поиграй в браузере:

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

Это тот же путь `game-src`, что использует [пользовательская игра](/docs/custom-game-development/self-hosted-game); для маркетплейса ты вместо этого опубликуешь репозиторий, чтобы платформа закрепила бандл за тебя.

## Следующие шаги

- [Контракт реплея](/docs/marketplace-game-development/replay-contract): преврати свою записанную игру в артефакт `run`, который делает игру воспроизводимой.
- [Манифест caputchin.json](/docs/marketplace-game-development/the-caputchin-json-manifest): опиши игру, её пресеты и её бандл.
- [Опубликуй в маркетплейс](/docs/marketplace-game-development/publish-to-the-marketplace): пометь репозиторий тегом и выйди в эфир.

## См. также

- [Справочник игрового SDK](/docs/marketplace-game-development/game-sdk-reference): каждый экспорт, в полном виде.
- [engine-kit](/docs/marketplace-game-development/engine-kit): опциональный способ получить детерминизм даром.
