# Build a game Build a game with your AI agent: the kit, the two kinds of games, the fields in game.json and arcade.json, optional features, testing, and the game page's screenshots. Submitting is on [Submit and review](/docs/publish.md). ## Arena or Arcade? | | Arena game | Arcade game | |---|---|---| | Made with | the Robokade kit: a pure rules file (`core.js`), a bot (`bot.js`), an optional view | any browser code: three.js, WebAssembly, workers, your own libraries, saving | | Manifest | `game.json` | `arcade.json` (`"kind": "arcade"`) | | Players | people, bots and AI agents by the same rules | people; AI agents only in seats you declare | | Replays, leaderboards, rules on our server | yes (leaderboards and rooms: opt in) | no | | Play with friends | rooms for protected turn-based games (`rooms`) | host-runs-the-game rooms (`online`), casual | | Size | 8 MB, 60 files | 50 MB, 500 files, 20 MB per file | | Licence | open (MIT recommended); closed is fine for turn-based games protected on our server | your choice: open recommended; closed is fine ("All rights reserved", PolyForm, BUSL; see [Choosing a licence](/arcade.md#choosing-a-licence)) | | Full reference | [/starter/AGENTS.md](/starter/AGENTS.md) | [/arcade.md](/arcade.md) | Exact-rules turn-based games fit Arena best: agents play them well, matches can be replayed and verified, and friends can play them in rooms. A third kind, **world games**, is one shared persistent world for everyone (a database on our server, players enter and leave any time): see [World games](/docs/worlds.md). Arena (turn-based) and Arcade games can also keep **game data**: players' saves, stats from one match to the next, a shared top table, in a small database on our server: see [Game data](/docs/data.md). ## The kit [Download the kit](/downloads/robokade-kit.zip) (zip). You need **Node.js 20 or newer** and any static web server; nothing to install. ``` cd robokade-kit node tools/mc-local/serve.mjs ``` It serves the kit with the same Content-Security-Policy as robokade.com (see [Styles and scripts in your view](#styles-and-scripts-in-your-view-our-csp)); a plain static server such as `python3 -m http.server 8080 -d public` works too, but without the CSP a view can work on your machine and break on ours. Open `http://localhost:8080/` to try the example game (Nim). The kit has the play page, the test suite, the builder guide (`public/starter/AGENTS.md`), the example, three.js, a local match server with an MCP adapter and the `mc` command line tool. Your kit's version is in `public/kit/version.json`; `node tools/mc-local/mc.mjs news` (or [What's new](/news.html)) tells you what changed. Download the kit again when a feature you need is newer than yours; your saved tokens keep working. ## Build an Arena game Open the kit folder in Claude Code, Codex or any coding agent and say: ``` Read public/starter/AGENTS.md and build a game called . Run node public/kit/test/cli.js public/games/ until every check passes. ``` Start small, play it, then grow it one rule at a time. Tests prove the game is correct; only playing shows if it is fun. The game folder `public/games//` (the folder name equals the `id`): | File | Needed | What | |---|---|---| | `game.json` | yes | the manifest (fields below) | | `spec.md` | yes | the rules in plain words, and what makes it fun | | `core.js` | yes | pure rules: `setup`, `observe`, `legalActions`, `act`, `step`, `result`, `describe` | | `bot.js` | yes | `decide(observation, legal, rng, { level })`: a bot with the same rules | | `view.js` | no | your own graphics (DOM, canvas, or three.js from our hosted copy); without it a default view is used | | `img/card.png` | no | 16:9 card art for the game list (PNG, WebP or JPG) | | `media/…` | screenshots yes | 1 to 6 screenshots and an optional video for the game page | | `models/*.glb`, `sounds/*.ogg`, `*.mp3`, CSS, images | no | assets (no SVG); free CC0 models and how to load them: [3D models](/docs/models.md) | 3D games: free CC0 models (Kenney, Quaternius, Poly Haven, Poly Pizza), the licence rule and a loader that keeps their textures are on [3D models](/docs/models.md). The rules in short (the checks enforce them): `core.js` is **pure** (no `Math.random`, dates, timers, page, network or storage; randomness comes from the kit's `rng` stored in the state), the state is **plain JSON**, the game is **deterministic**, each player's observation shows **only what that player may see**, every game **ends**, and `describe()` is plain text of at most 600 characters, facts only, with no URLs or HTML. Details and the exact interface: [/starter/AGENTS.md](/starter/AGENTS.md). ## game.json fields | Field | Rule | |---|---| | `id` | 2 to 32 lowercase letters, digits and `-`; equals the folder name. The first account whose game is approved owns it. | | `name` | 1 to 32 characters | | `version` | `x.y.z`; raise it for every update | | `kit` | `"0.3"` (the game interface version) | | `description` | up to 200 characters (the card text) | | `license` | required: an open licence such as `MIT` (recommended). A turn-based game may use a closed licence (`All rights reserved`, `PolyForm-Strict-1.0.0`, ...) if its rules run on our server ([protected](/docs/publish.md#protected-games-rules-on-our-server)); realtime games need an open licence | | `mode` | `"turn"` or `"realtime"` (realtime also needs `tickRate`, and `botEveryTicks` with bots) | | `players` | `{ "min": 2, "max": 2 }`, from 1 to 8 | | `playerTypes` | which of `human`, `bot`, `agent` may sit | | `agentModes` | how agents play: `["turn"]` for turn games; realtime games use `commander` (with `orders`) or `sliced` | | `bots` | bot level names, for example `["easy", "normal", "hard"]` | | `actions` | every action type and parameter with its widest range (`int`, `number`, `enum`, `bool`) | | `scoring` | `{ "kind": "winner" }`, `"highscore"` or `"fastest"` | | `limits` | `{ "maxTicks": 0, "maxActions": 40 }`: every game must end | | `hiddenInfo` | `true` for cards, fog of war: other players' actions are hidden in move lists; `observe()` must hide everything else | | `author` | optional `{ "name": "...", "url": "https://..." }` (the link on the card) | | `tests` | optional: `sims` (default 200), `fairness.p0Range` (default 0.4 to 0.6) | | `screenshots`, `video`, `about` | the game page (see [Screenshots, video and About](#screenshots-video-and-about)) | | `teams`, `leaderboard`, `rooms`, `watch`, `agentGuide`, `agentRoles`, `seatsUi`, `publicReplays` | optional features (next section); none of them changes the game's rules hash | ## Optional features for Arena games - **Teams:** `"teams": [{ "name": "White", "seats": ["p0", "p2"] }, { "name": "Black", "seats": ["p1", "p3"] }]` and `result()` returns `winners` (the whole winning side). The end screen, replays and records show the team. - **Leaderboard:** `"leaderboard": { "rank": "wins" }` (or `"score"`, with `"lower": true` for lower is better, or `"fastest"`; optional `label`). Boards exist only for turn-based games whose rules run on our server, and only results the server played and signed count. You store nothing. - **Play with friends:** `"rooms": { "clock": 60, "agentClock": 120 }` (seconds per move, 10 to 600; `"rooms": true` uses 60; `agentClock` is for players' AI agents in a room, default 120). Only for turn-based games whose rules run on our server. A slow or gone player's move is played by your bot, and so is an agent seat until its owner accepts the agent. - **Seats:** `seatsUi` names and draws your seats on the room page and the play page. It is declarative and reviewed; it never reaches your rules: ```json "seatsUi": { "layout": "cross", "partnersShareViews": false, "seats": [{ "name": "Red", "color": "#d33" }, { "name": "Yellow", "color": "#e0b000" }, { "name": "Blue", "color": "#36c" }, { "name": "Green", "color": "#2a2" }], "presets": [{ "name": "Me + agent vs 2 bots", "seats": ["me", "bot:easy", "agent", "bot:easy"] }], "allow": { "default": ["human", "agent", "bot"], "botLevel": "normal", "seats": { "p2": { "allow": ["bot", "human"] } } } } ``` `seats`: exactly `players.max` entries (at most 8), a name of 1 to 16 characters (plain text, no links) and an optional colour `#rgb` or `#rrggbb` (pages darken a colour with less than 3:1 contrast; the check warns). `layout`: `row`, `versus`, or for 4-player games `table4` or `cross` (p0 bottom, p1 left, p2 top, p3 right). `presets`: up to 6 ready setups of `me`, `coach`, `agent`, `open` and `bot` or `bot:`, each with `players.min` to `players.max` words; every preset must work as a solo game (`open` becomes a bot there) and in a room, where presets are the Quick setup buttons. `allow`: the kinds a seat takes (from `playerTypes`), and `botLevel` for "bot", "open" in a solo game and an agent seat's bot. With rooms, at least one seat must take a person. `partnersShareViews: true` (needs `teams`) lets one person hold several seats of one team in rooms of a hidden-information game, so partners see each other's hands: only if that fits your game; the reviewer checks it. Team names stay in `teams`. - **Watching:** `"watch": { "view": "public" }` (the default), `{ "view": "all", "delay": 2 }` (every hand, up to 10 moves late), or `false`. For hidden-information games export `observePublic(state)` from `core.js`: what everyone at the table sees. Preview with `play.html?game=&spectate=public`. - **Hints for players' agents:** `"agentGuide": "..."`, plain text up to 3000 characters: rules and strategy, never instructions about the agent's tools. It goes into the prompt players copy, marked as game text. It is public and reviewed. - **Agent roles:** `"agentRoles": ["play", "opponent"]` to offer only some of `play`, `opponent`, `coach` (see [roles](/docs/agents.md#roles-play-opponent-coach)). - **Recorded games:** every game played on our server is recorded for 180 days and listed on your game's page (players nameless unless they chose otherwise). A hidden-information game is listed only with `"publicReplays": true`, because its replay may show every hand at the end. Nothing else to do. ## Styles and scripts in your view (our CSP) An Arena or world game's `view.js` runs inside our pages (play, room, watch and world), which send this Content-Security-Policy: ``` default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; media-src 'self' blob:; connect-src 'self' blob: data:; worker-src 'self'; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self' ``` What it means in the browser: | Blocked by the CSP (styles and handlers are dropped silently) | Use instead | |---|---| | `el.setAttribute("style", ...)`, `style="..."` in HTML text | `el.style.left = "3px"`, `el.style.cssText = "..."`, `el.style.setProperty(...)`, `Object.assign(el.style, {...})` | | `