Build a game with AI
Describe it. Paste it. Play it. Here's everything you and your AI agent need to know.
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
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 reference
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
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.