Построй самостоятельно размещённую игру
К концу этого руководства у тебя будет настоящая игра Caputchin: JavaScript-бандл, который работает в iframe виджета в песочнице, говорит с хостом через игровой SDK и отчитывается трассой, которую сервер может перезапустить. В отличие от ручного режима, самостоятельно размещённая игра может ставить ворота на ключе сайта, потому что её раунд воспроизводим. Это руководство покрывает построение и размещение игры; реплей и постановка ворот покрывает артефакт, который собственно включает ворота.
Прочитай запусти свою собственную игру о том, куда это вписывается. Тебе нужно место, чтобы разместить статический JavaScript-файл (любой CDN или статический хост), и виджет на твоей странице.
Форма самостоятельно размещённой игры
Самостоятельно размещённая игра это один самодостаточный JavaScript-бандл, который:
- Импортирует функцию
registerиз@caputchin/game-sdkи регистрирует единственную игровую фабрику. - Рендерится в контейнер, который виджет даёт фабрике, используя разрешённые язык, скин и конфигурацию, переданные рядом.
- Разыгрывается детерминированно под пер-раундовым сидом, записывая игру как трассу.
- При победе вызывает
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-sdk2. Прочитай пер-раундовый контекст
Третий аргумент фабрики, 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. Сделай её способной ставить ворота
Чтобы использовать свою самостоятельно размещённую игру как ворота проверки, нужны ещё две вещи, обе покрыты дальше:
- Зарегистрируй её как пользовательскую игру в панели (id, который ты выбираешь) и определи её схему и пресеты полей, чтобы контекст нёс настоящие locale/skin/config.
- Загрузи артефакт реплея: headless-сборку логики твоей игры, которую сервер запускает, чтобы перевывести вердикт из сида и трассы. Смотри реплей и постановка ворот.
Пока артефакт реплея не загружен и не прошёл свою самопроверку, пользовательская игра показывается как Не воспроизводима и не может ставить ворота.
См. также
- Реплей и постановка ворот: артефакт, который включает ворота.
- Справочник схемы панели: определение полей, которые разрешает контекст.
- Ручной режим: более лёгкая, не ставящая ворота альтернатива.
- Проверь на своём бэкенде: подтверждение токена, который производит pass.