Caputchin
Разработка пользовательской игры

Построй самостоятельно размещённую игру

К концу этого руководства у тебя будет настоящая игра Caputchin: JavaScript-бандл, который работает в iframe виджета в песочнице, говорит с хостом через игровой SDK и отчитывается трассой, которую сервер может перезапустить. В отличие от ручного режима, самостоятельно размещённая игра может ставить ворота на ключе сайта, потому что её раунд воспроизводим. Это руководство покрывает построение и размещение игры; реплей и постановка ворот покрывает артефакт, который собственно включает ворота.

Прочитай запусти свою собственную игру о том, куда это вписывается. Тебе нужно место, чтобы разместить статический JavaScript-файл (любой CDN или статический хост), и виджет на твоей странице.

Форма самостоятельно размещённой игры

Самостоятельно размещённая игра это один самодостаточный JavaScript-бандл, который:

  1. Импортирует функцию register из @caputchin/game-sdk и регистрирует единственную игровую фабрику.
  2. Рендерится в контейнер, который виджет даёт фабрике, используя разрешённые язык, скин и конфигурацию, переданные рядом.
  3. Разыгрывается детерминированно под пер-раундовым сидом, записывая игру как трассу.
  4. При победе вызывает bridge.pass({ trace }) с этой записью.

Виджет загружает твой бандл в свой iframe и вызывает твою фабрику; вызванная фабрика и есть сигнал старта (нет отдельного события старта, которого надо ждать).

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

Установи SDK и вызови register один раз со своей фабрикой. Ты не передаёшь манифест; сервер разрешает пресеты и вручает их фабрике.

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. Прочитай пер-раундовый контекст

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

  • ctx.seed — пер-раундовый сид реплея. Выводи всю случайность из него. Null, когда игра работает вне проверенной сессии.
  • ctx.locale — разрешённый объект языка: ctx.locale._lang плюс твои переведённые ключи строк.
  • ctx.skin — разрешённый объект скина: ctx.skin._theme (всегда light или dark) плюс твои ключи цвета и ассетов.
  • ctx.config — разрешённая игровая конфигурация (или null).

Это разрешённые пресеты для этого посетителя, произведённые Caputchin из твоих определённых в панели схемы и пресетов. Ты читаешь свои собственные ключи с них; ты сам ничего не разрешаешь.

Если твоей раскладке нужен явный размер, скажи хосту один раз после первой отрисовки:

bridge.setSize(360, 480);

3. Сделай игру детерминированной

Это правило, которое делает игру способной ставить ворота: игра должна быть детерминированной при данном сиде и вводах игрока. Не вызывай Math.random() и не читай настенные часы для чего-либо, что влияет на исход; выводи каждый случайный выбор из ctx.seed. Сервер перезапускает твою логику под тем же сидом и той же записанной трассой и должен прийти к тому же вердикту.

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

Записывай вводы, которые ведут к исходу (по каким целям игрок попал, в порядке, с таймингом, если он важен), пока играешь. Сериализуй эту запись в строку: эта строка и есть твоя трасса.

4. Передай с трассой

Когда посетитель побеждает, вручи хосту трассу. pass принимает объект с единственной строкой trace:

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

Трасса непрозрачна для платформы: это какая угодно строка, которую определяет только твоя игра, такая, что твоя логика в сочетании с сидом воспроизводит результат. Игра не отчитывается о счёте здесь; подсчёт, если он есть, это твоя собственная забота внутри iframe.

Ещё два факта про мост:

  • Первый pass погашает раунд; о сбойном или брошенном раунде сигналят, просто не вызывая pass (на этом мосту нет метода fail).
  • bridge.error({ code, message }) для внутреннего сбоя игры (ассет не загрузился, исключение), а не для проигрыша игрока. Он всплывает событием error хосту.

После того как хост погашает pass, он отпускает токен виджета, и твой бэкенд проверяет этот токен как обычно.

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

Виджет загружает ровно один URL скрипта, так что всё должно быть в этом единственном файле. Настрой свой бандлер (esbuild, rollup, vite, webpack; любой из них) на один самодостаточный вывод:

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

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

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

6. Размести бандл и наведи на него виджет

Отдавай собранный файл со своего собственного статического хоста по https (петлевой http разрешён для локальной разработки), затем наведи виджет на него:

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

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

7. Сделай её способной ставить ворота

Чтобы использовать свою самостоятельно размещённую игру как ворота проверки, нужны ещё две вещи, обе покрыты дальше:

  1. Зарегистрируй её как пользовательскую игру в панели (id, который ты выбираешь) и определи её схему и пресеты полей, чтобы контекст нёс настоящие locale/skin/config.
  2. Загрузи артефакт реплея: headless-сборку логики твоей игры, которую сервер запускает, чтобы перевывести вердикт из сида и трассы. Смотри реплей и постановка ворот.

Пока артефакт реплея не загружен и не прошёл свою самопроверку, пользовательская игра показывается как Не воспроизводима и не может ставить ворота.

См. также

На этой странице