This tutorial picks up where Tic-tac-toe with gamekit leaves off. Same game, 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.
app, same cli — we add one optional field to the game and a sibling bots package, then wire them into the CLI so a human can play against the computer.
Reference code: examples/games/tic-tac-toe/bots and the bot-aware CLI at examples/games/tic-tac-toe/cli.
1. Add the legalActions hook to the game
The bot runtime needs to know which moves a seat may legally play. Add one field to defineGame in game/src/index.ts:
- The engine never reads this field. It is metadata for the bot runtime. Authors who don’t ship bots can omit it.
- We return
[]when the seat is not inderived.activePlayers, so the runner knows it is not this seat’s turn. payloadis{ row, col }— the same shape the existingplaceMarkmove accepts.
2. Create the bots package
Same workspace, sibling to game/, app/, and cli/.
bots/package.json
bots/tsconfig.json
3. Write the random bot
bots/src/random.ts:
rng is forked from the snapshot’s RNG and salted by bot name + seat + turn, so identical seeds produce identical bot games.
4. Write the minimax bot
Tic-tac-toe is small enough that depth-9 alpha-beta minimax solves the position exactly.bots/src/minimax.ts:
- We use
simulatefrom@openturn/botto dry-run candidates. It clones the snapshot, applies the move, and returns the resulting snapshot — the live session is never touched. - Alpha-beta pruning + depth-as-tiebreaker (
terminal - Math.sign(terminal) * depth) makes the bot prefer faster wins and slower losses. - The hosted-host fallback (
snapshot === null) returns a legal action — search-based bots run as in-process bots; over the network, switch to a heuristic strategy.
bots/src/index.ts
5. Wire bots into the CLI
The CLI already pullscreateLocalSession and the game definition. Patch it to accept a --bot <seat>=<name> flag:
- We call
applyEventon the returnedsession, not the raw one. The facade notifies the runner so the bot wakes up after each human move. isBot(playerID)andwhenIdle(playerID)come from the same return value — no per-bot bookkeeping.detachAll()runs infinallyso the runner is always cleaned up, even on errors.
6. Run it
random, you should win most games as long as you take the center first. Against minimax, the best you can do is force a draw — depth-9 alpha-beta solves tic-tac-toe.
7. Add tests
The pure-functional engine makes integration tests cheap. A 1000-match random-vs-random suite verifies the runner end-to-end in a few seconds:bun test runs it.
What you have built
- One additive field on the game (
legalActions) that the engine ignores and the bot runtime uses. - A sibling
botspackage with a 5-line random bot and a 60-line alpha-beta minimax bot. - A CLI that takes
--bot <seat>=<name>and drives the same loop for human-vs-human, human-vs-bot, and bot-vs-bot. - An integration test that proves the runner terminates 1000 matches legally.
attachHostedBot against a HostedClient, with the bot living in a separate sidecar process — see Concepts: bots.
Related
- Tutorial: tic-tac-toe with gamekit — the base game this builds on.
- Concepts: AI bots — design rationale.
- Reference:
@openturn/bot— full API. - How-to: add a bot — practical recipes for heuristic and search-based bots.