Worker-safe. The bot package lets a developer drop a traditional game AI into any seat of any openturn game without touching the engine. The sameDocumentation Index
Fetch the complete documentation index at: https://openturn.io/docs/llms.txt
Use this file to discover all available pages before exploring further.
defineBot({ decide }) shape powers a five-line random bot, a heuristic bot, and an alpha-beta minimax bot.
Install
@openturn/core and @openturn/client. The game itself is untouched — bots consume the public session API.
Authoring a bot
defineBot(bot)
Identity function that names and types your bot.
actionDelayMs is presentation pacing only — the runner waits at least this long between picking an action and dispatching it so human clients can see bot turns unfold. It is independent of thinkingBudgetMs (which bounds decide).
decide may be sync (random, heuristic) or async (MCTS with yields, network calls). The runner awaits whatever you return.
DecideContext<TGame>
viewis whatsession.getPlayerView(playerID)returns. Hidden information is already filtered out.snapshotis the full server-side snapshot. It isnullon hosted (network) hosts because clients only see views.legalActionsis enumerated for you — see Legal-action enumeration below.rngis reproducible: bots seeded from the same snapshot make the same choices.signalaborts when the snapshot moves on while you are thinking. Bots that make network calls should pass it tofetchso a stale request exits early.
LegalAction
payload is typed as unknown rather than JsonValue so authors can pass concrete move-arg types (e.g. PlaceMarkArgs) without adding index signatures. The engine validates JSON shape at dispatch time.
Legal-action enumeration
The runner needs to know which moves a seat may legally play. Enumeration resolves in this order:-
Game-level hook (preferred). Add
legalActionstodefineGame:The engine never reads this field — only the bot runtime does. -
Per-bot fallback. If the game has no hook, the bot supplies its own:
-
Empty. If neither exists,
legalActionsis[]and yourdecidemust usesimulateto explore the action space.
enumerateLegalActions(game, snapshot, view, playerID, bot)
Direct call, normally unnecessary because the runner does this for you. Useful inside MCTS-style searches that re-enumerate from a simulated snapshot.
Simulating moves
simulate(game, snapshot, playerID, action) => SimulateResult
Dry-run a candidate action against a clone of the snapshot. The original snapshot is never mutated.
skipValidation: true because the live session was already validated. RNG resumes from snapshot.meta.rng, so MCTS rollouts are deterministic given a forked seed.
SimulateFn is also handed to decide via context.simulate(action) — same function, with game, snapshot, and playerID already bound for you.
simulate is unavailable on hosted hosts (the bot doesn’t have the full snapshot or the game definition over the wire). The function returns { ok: false, reason: "simulate_unavailable_for_host" } in that case.
Attaching to a session
attachLocalBot(options) — single seat
session is a session-shaped facade. Use it instead of the raw session in your game loop — every applyEvent through it notifies the runner. Bot 1 watches for its turn and dispatches autonomously; the human side of the loop calls session.applyEvent("0", ...) exactly as before.
attachLocalBots (next), or pass the returned bus to subsequent attachLocalBot calls — every host on the same bus hears every dispatch.
attachLocalBots(options) — many seats at once
whenIdle(playerID) resolves once the bot at that seat has finished thinking and dispatched (or errored).
attachHostedBot(options) — over the network
For cloud play, a separate process connects to the room as a player and runs the bot:
client.dispatchEvent. simulate is unavailable; bots that need search must rely on a per-bot enumerate and pure-view heuristics.
The recommended cloud topology is documented in Concepts: AI bots.
BotRunner
detach removes the runner’s subscription and aborts any in-flight decide. Call it when the match ends or the player is replaced.
AttachOptions
Shared by every attach* call:
Lifecycle and cancellation
The session reducer is synchronous;decide may be slow. The runner separates them:
- On every snapshot change, if it is your seat’s turn and no decision is in flight,
decidefires inside a microtask. - While
decideis awaiting, every new snapshot abortssignaland queues a fresh decision once the in-flight one settles. - After
decideresolves, the runner re-checksposition.turn. If the turn has advanced (e.g. the opponent reconnected and the room re-synced), the decision is dropped before dispatch. host.dispatch(action)returns the engine’s outcome. Invalid moves never mutate state, soonErroris called and the runner waits for the next change to retry.
signal into their HTTP client (fetch(url, { signal })) so a superseded request is cancelled instead of left in flight.
Determinism and reproducibility
forkRng(base, botName, playerID, turn)
context.rng. Two bots on the same snapshot get different but reproducible streams; the same bot on the same snapshot always gets the same stream.
createDeadline(budgetMs, clock?) => DeadlineToken
decide. Tests can pass a custom clock ({ now(): number }) for deterministic deadline behaviour.
Hosts (low-level)
You will not normally construct hosts directly —attachLocalBot / attachHostedBot do it for you. They exist for advanced uses (custom transports, headless test harnesses).
BotHost<TGame>
createLocalSessionBus(rawSession) / createLocalSessionHost(busOrSession, playerID)
Build the shared notification bus for a local session, then wrap it as a BotHost. The bus exists because multiple bots on the same session must hear each other’s dispatches; constructing one explicitly lets you mix bots with custom drivers.
createHostedClientHost(client, playerID)
Wrap a HostedClient as a BotHost. Used internally by attachHostedBot.
Game-author hooks
@openturn/bot adds one optional field to GameDefinition (and through defineGame in gamekit):
Related
- Concepts: AI bots — design rationale and runtime model.
- How-to: add an AI bot to a game — practical recipe.
- Tutorial: tic-tac-toe with bots — end-to-end walkthrough.