Caputchin
Développement de jeu personnalisé

Bâtir un jeu auto-hébergé

À la fin de ce tutoriel, tu auras un vrai jeu Caputchin : un bundle JavaScript qui tourne dans l'iframe en bac à sable du widget, parle à l'hôte via le SDK de jeu, et rapporte une trace que le serveur peut rejouer. Contrairement au mode manuel, un jeu auto-hébergé peut gater une clé de site, parce que sa manche est rejouable. Ce tutoriel couvre la construction et l'hébergement du jeu ; rejeu et gating couvre l'artefact qui active réellement le gate.

Lis exécute ton propre jeu pour savoir où ça s'insère. Il te faut un endroit où héberger un fichier JavaScript statique (n'importe quel CDN ou hôte statique) et le widget sur ta page.

La forme d'un jeu auto-hébergé

Un jeu auto-hébergé est un seul bundle JavaScript autonome qui :

  1. Importe la fonction register de @caputchin/game-sdk et enregistre une seule fabrique de jeu.
  2. Se rend dans le conteneur que le widget donne à la fabrique, en utilisant la langue, le skin et la configuration résolus passés à ses côtés.
  3. Se déroule de façon déterministe sous la graine par manche, enregistrant le jeu comme une trace.
  4. À la victoire, appelle bridge.pass({ trace }) avec cet enregistrement.

Le widget charge ton bundle dans son iframe et invoque ta fabrique ; le fait que la fabrique soit appelée est le signal de départ (il n'y a pas d'événement de départ distinct à attendre).

1. Enregistre une fabrique de jeu

Installe le SDK et appelle register une fois avec ta fabrique. Tu ne passes pas le manifeste ; le serveur résout les préréglages et les remet à la fabrique.

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. Lis le contexte par manche

Le troisième argument de la fabrique, ctx, porte tout ce dont ton jeu a besoin pour ce visiteur :

  • ctx.seed : la graine de rejeu par manche. Dérive tout l'aléatoire de celle-ci. Null quand le jeu tourne hors d'une session vérifiée.
  • ctx.locale : l'objet de langue résolu, ctx.locale._lang plus tes clés de chaînes traduites.
  • ctx.skin : l'objet de skin résolu, ctx.skin._theme (toujours light ou dark) plus tes clés de couleur et de ressource.
  • ctx.config : la configuration de gameplay résolue (ou null).

Ce sont les préréglages résolus pour ce visiteur, produits par Caputchin à partir de ton schéma et tes préréglages définis au tableau de bord. Tu lis tes propres clés dessus ; tu ne résous rien toi-même.

Si ta mise en page a besoin d'une taille explicite, dis-le à l'hôte une fois après ton premier rendu :

bridge.setSize(360, 480);

3. Rends le jeu déterministe

C'est la règle qui rend un jeu gatable : le jeu doit être déterministe étant donné la graine et les entrées du joueur. N'appelle pas Math.random() ni ne lis l'horloge murale pour quoi que ce soit qui affecte le résultat ; dérive chaque choix aléatoire de ctx.seed. Le serveur rejoue ta logique sous la même graine et la même trace enregistrée et doit atteindre le même verdict.

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

Enregistre les entrées qui pilotent le résultat (quelles cibles le joueur a touchées, dans l'ordre, avec le timing si ça compte) à mesure que tu joues. Sérialise cet enregistrement en une chaîne : cette chaîne est ta trace.

4. Passe avec la trace

Quand le visiteur gagne, remets la trace à l'hôte. pass prend un objet avec une seule chaîne trace :

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

La trace est opaque pour la plateforme : c'est n'importe quelle chaîne que ton jeu seul définit, telle que ta logique, combinée à la graine, reproduise le résultat. Le jeu ne rapporte pas de score ici ; le score, s'il y en a, est ta propre affaire dans l'iframe.

Deux faits de plus sur le pont :

  • Le premier pass échange la manche ; une manche échouée ou abandonnée est signalée en simplement n'appelant pas pass (il n'y a pas de méthode fail sur ce pont).
  • bridge.error({ code, message }) est pour une défaillance interne au jeu (une ressource a échoué à charger, une exception), pas une perte du joueur. Il fait surgir un événement error vers l'hôte.

Après que l'hôte a échangé le pass, il libère le token du widget, et ton backend vérifie ce token comme d'habitude.

5. Bundle pour le bac à sable

Le widget charge exactement une URL de script, donc tout doit être dans ce seul fichier. Configure ton bundler (esbuild, rollup, vite, webpack ; n'importe lequel) pour une seule sortie autonome :

  • intègre les ressources comme URL de données (sprites, sons, polices) ;
  • désactive le découpage de code ;
  • si tu utilises WASM, intègre-le en base64 et instancie à partir des octets.

Les schémas qui ne fonctionnent pas dans le bac à sable, parce que l'iframe est à origine opaque avec une CSP stricte :

  • un fetch('./sprite.png') relatif au chemin : il n'y a pas de chemin d'où récupérer ;
  • un import('./chunk.js') dynamique : la deuxième URL est bloquée ;
  • new Worker('./worker.js') : lance plutôt les workers à partir d'URL Blob en ligne ;
  • les récupérations de CDN externe à l'exécution : connect-src les bloque.

6. Héberge le bundle et pointe le widget vers lui

Sers le fichier construit depuis ton propre hôte statique en https (le http de bouclage est autorisé pour le dev local), puis pointe le widget vers lui :

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

Le widget charge ton bundle dans son iframe en bac à sable, invoque ta fabrique avec le contexte, et attend ton pass. À ce stade le jeu tourne et vérifie, mais il n'est pas encore autorisé à gater une clé.

7. Rends-le gatable

Pour utiliser ton jeu auto-hébergé comme gate de vérification, deux choses de plus sont nécessaires, toutes deux couvertes ensuite :

  1. Enregistre-le comme jeu personnalisé sur le tableau de bord (un id que tu choisis) et définis son schéma et ses préréglages de champs pour que le contexte porte de vrais locale/skin/config.
  2. Téléverse un artefact de rejeu : un build headless de ta logique de jeu que le serveur exécute pour re-dériver le verdict à partir de la graine et de la trace. Vois rejeu et gating.

Tant que l'artefact de rejeu n'est pas téléversé et ne réussit pas son auto-vérification, le jeu personnalisé s'affiche Non rejouable et ne peut pas gater.

Voir aussi

Sur cette page