Skip to content
Back to home
Custom game studio

Build a game with AI

Describe it. Paste it. Play it. Here's everything you and your AI agent need to know.

2D canvasThree.js 3D120 KB maxNo networkNo music · SFX only

Step 1

Describe your game to an AI agent using the prompt below.

Step 2

Paste the returned JavaScript into Admin → Game templates → New custom game.

Step 3

Test it in the sandbox, publish, and host it with any question kit.

How custom games work

How custom games work

A custom game is a single JavaScript file that runs inside a sandboxed iframe. The Saha shell handles questions, scoring, timers and the leaderboard. Your code handles the world, the movement, and what happens after each answer.

The Saha shell owns questions, scoring and the timer

Your code owns the world, movement and reactions

Sandboxed: no network, cookies or local storage

AI prompt

Copy this into ChatGPT, Claude or any coding agent. Add your own game idea at the top.

Test it in the sandbox, publish, and host it with any question kit.

SDK

SDK reference

postMessage v1

canvas

The canvas element. Size it to the window or use api.width/api.height.

ctx

A 2D context for the canvas, or draw with Three.js when THREE is available.

THREE

The Three.js namespace in 3D sandboxes. Use it to create a renderer on api.canvas.

state

Live game state: score, streak, timeLeft, remaining, dimension, locale, settings and player.

setStats

Update the in-game stat chips: { lives, objective, extra }.

toast

Show a short message in the parent shell: toast(text, 'good' | 'bad').

sfx

Play a built-in sound effect: coin, correct, wrong, pop, whoosh, hit, win or lose.

finish

End the game early with a summary: finish({ title, detail }).

log

Write to the sandbox console panel.

input

Keyboard and pointer state: api.keys (Set), api.pointer {x,y,down}.

overlay

A DOM overlay above the canvas for HUD buttons. Clean up what you add.

Lifecycle

SahaGame.start receives an object with optional setup, frame, answer, state and end handlers. setup runs once. frame runs every animation frame with (api, dt). answer runs after each quiz answer. state runs when score, streak or time changes. end runs when the game finishes.

SahaGame.start({
  setup(api) { /* once */ },
  frame(api, dt) { /* every frame */ },
  answer(api, result, state) { /* quiz answer */ },
  resize(api) { /* canvas resized */ },
  end(api, reason) { /* game over */ }
});
Starter example

Starter example

Start from a template that actually runs, then ask your AI agent to remix it or build from scratch. Every template uses the same SDK contract as real games.

// Saha custom game starter — 2D
// A tiny star catcher. Answer correctly to spawn bonus stars.
SahaGame.start({
  setup(api) {
    api.game = {
      playerX: api.width / 2,
      targetX: api.width / 2,
      stars: [],
      caught: 0,
      bonus: 0
    };
    api.sfx("pop");
    updateHud(api);
  },

  frame(api, dt) {
    const ctx = api.ctx;
    const w = api.width;
    const h = api.height;
    const game = api.game;

    // background
    const bg = ctx.createLinearGradient(0, 0, 0, h);
    bg.addColorStop(0, "#141029");
    bg.addColorStop(1, "#28156E");
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, w, h);

    // stars
    for (let i = 0; i < 40; i++) {
      ctx.globalAlpha = 0.5;
      ctx.fillStyle = "#fff";
      ctx.fillRect((i * 97) % w, (i * 53 + performance.now() * 0.02) % h, 2, 2);
    }
    ctx.globalAlpha = 1;

    // player controls
    if (api.keys.has("a") || api.keys.has("arrowleft")) game.targetX -= 420 * dt;
    if (api.keys.has("d") || api.keys.has("arrowright")) game.targetX += 420 * dt;
    if (api.pointer.down) game.targetX = api.pointer.x;
    game.targetX = Math.max(45, Math.min(w - 45, game.targetX));
    game.playerX += (game.targetX - game.playerX) * Math.min(1, dt * 10);

    // spawn stars
    if (Math.random() < dt * 2.2) {
      game.stars.push({ x: 40 + Math.random() * (w - 80), y: -20, speed: 90 + Math.random() * 120, bonus: false });
    }

    // update + draw stars
    for (let i = game.stars.length - 1; i >= 0; i--) {
      const star = game.stars[i];
      star.y += star.speed * dt;
      ctx.font = star.bonus ? "28px sans-serif" : "22px sans-serif";
      ctx.textAlign = "center";
      ctx.fillText(star.bonus ? "🌟" : "⭐", star.x, star.y);
      if (star.y > h - 70 && Math.abs(star.x - game.playerX) < 70) {
        game.stars.splice(i, 1);
        game.caught += 1;
        api.sfx("coin");
        updateHud(api);
      } else if (star.y > h + 30) {
        game.stars.splice(i, 1);
      }
    }

    // player
    ctx.font = "54px sans-serif";
    ctx.textAlign = "center";
    ctx.fillText("🛸", game.playerX, h - 60);

    ctx.fillStyle = "rgba(255,255,255,.7)";
    ctx.font = "600 12px Cairo, system-ui, sans-serif";
    ctx.textAlign = "center";
    ctx.fillText("A / D or drag to move", w / 2, 28);
  },

  answer(api, result) {
    const game = api.game;
    if (result.correct) {
      for (let i = 0; i < 3; i++) {
        game.stars.push({ x: 50 + Math.random() * (api.width - 100), y: -20 - i * 30, speed: 120, bonus: true });
      }
      api.toast("+3 bonus stars", "good");
      api.sfx("streak");
    } else {
      api.toast("Keep going!", "info");
    }
  },

  resize(api) {
    if (!api.game) return;
    api.game.targetX = Math.min(api.game.targetX, api.width - 45);
  }
});

function updateHud(api) {
  api.setStats({
    objective: "Catch stars — answer correctly for bonus stars",
    extra: [{ label: "Stars", value: api.game.caught, tone: "gold" }]
  });
}

Safety model

Custom code is untrusted. It runs in an iframe with sandbox="allow-scripts", an opaque origin, a content security policy that blocks network requests, and no access to cookies, local storage or the Saha API. It can only talk to the shell through a small postMessage contract.

Limits

Code size: 120 KB. No external assets or CDN scripts. Use canvas drawing, emoji or Three.js primitives. Audio must use api.sfx — no music files.

Options schema

Add simple fields for the host setup screen. Supported types: number, text, boolean and select. Each field needs type, key, labelEn, labelAr and default.

New custom game

Paste a game generated by an AI agent. Test it safely, then publish it to your catalog.

New custom game
Custom game guide · Saha