Building an unbeatable tic-tac-toe, the spectastic way
Tic-tac-toe is solved — so we over-engineered it. Six AI opponents, from a coin-flip to Monte Carlo Tree Search; two- and three-player modes; one self-contained HTML file. Here is how every decision was driven through the spectastic lifecycle — principles first, then spec, plan, tasks, a verification pass that had to prove the perfect players are unbeatable, a triage log for the one defect that slipped past it, and a change — proposed, risk-checked, and applied — that grew the board so three-player games aren't foregone draws.
A Wordle-flavoured board — green ✕ for you, amber ◯ and slate △ for the machines. Pick an opponent, pick a mode, and try to beat it. (You won't beat the perfect ones.) The engines follow the difficulty ladder in Over-Engineering Tic-Tac-Toe.
The lifecycle, step by step
Nothing here was vibe-coded. Each step produced one artifact, and each artifact grounded the next. Every filename below is a real page you can open.
Principles
principles.htmlBefore a line of code, five non-negotiables. The load-bearing one — "rules are pure and framework-free" — is why the board, legal-move check, and win detection ended up as plain functions with no DOM and no AI, the bedrock every engine and every test could stand on. Another — "correctness is demonstrated, not asserted" — is what later forced a self-play harness instead of a hopeful comment.
Spec
spec.htmlWhat to build, for whom, and how we'd know it worked — before deciding how. Ten requirements pinned it down: only legal moves (FR-001), immediate win/draw detection (FR-002), six engines behind one interface (FR-003), two modes (FR-004). The interesting part is the success criteria: SC-001 — "a perfect engine never loses" — a target written as a test, not a wish.
Plan
plan.htmlThe how — and where the one real decision lived. Three-player tic-tac-toe only makes MaxN and Paranoid interesting, but a bigger board would blow the move-time budget. Decision D-001: keep the 3×3 board for three players and accept that strong three-way play tends to draw. Every design fact was grounded against reality first — including the measured pruning win below.
Tasks
tasks.htmlThe plan, sliced into eighteen ordered, checkable tasks. The pure rules core lands first (everything depends on it); the engines stack up from Random to MCTS; the final phase is the verification harness. Tests-first where it counted — T-110 Minimax carried its own acceptance: never loses.
Implement & verify
tictactoe.htmlDrain the tasks, tick the boxes, and let the harness have the last word. The first run failed — a turn-alternation bug let a random player beat "perfect" Minimax once. The test caught what a demo never would; one line fixed it; the rerun went clean. That's the point of writing the target as a test.
Triage a defect
triage-log.htmlThen, after acceptance, a real one slipped through: the board's cells went rectangular as tiles landed. No silent hotfix — it went through triage. Reproduced with measurements (rows at 135 / 127 / 70 px), root-caused to content-sized grid rows, classified at the implementation layer, fixed by making each cell square, and locked in with a geometry assertion so it can't return. The whole trail lives in the triage-log.
Propose · apply · implement
proposal.htmlThe lifecycle isn't only greenfield. Three-player games kept ending in draws, so a change was proposed — larger boards, shorter win lines — with typed deltas and an adversarial risk pass (beyond 3×3 the search turns heuristic; a first-cut 2.1 s move was tuned back under budget). Approved, applied into the spec at v1.1.0, and implemented. A 5×5 board won on three in a row now drew 0 of 8 games.
The six opponents
The difficulty ladder from the article, mapped honestly onto the game: Minimax and Alpha-Beta assume a single opponent, so they appear only in two-player; MaxN and Paranoid need three seats, so they appear only in three-player; Random and MCTS span both.
Random2p · 3pPicks any empty square. The naive baseline — a losing strategy against anyone paying attention.
Minimax2pSearches the whole tree assuming perfect opposition; prefers faster wins. Unbeatable at 3×3.
Alpha-Beta2pMinimax that prunes branches it can't benefit from. Identical play, a fraction of the positions.
MaxN3pThe multiplayer generalisation: every node scores a vector, and each player maximises their own slot.
Paranoid3pAssumes the other two players coordinate against it — "me vs. the world." Cheaper, and often deeper.
MCTS2p · 3pKnows only the rules; plays 2,500 random games each turn and trusts what tends to win.
What the tests proved
The verification harness plays the game against itself — a random player versus each engine, hundreds of moves — and checks the success criteria hold.
Every strong three-player game drew. That isn't a defect — it's what three-way tic-tac-toe on a 3×3 board is under good play, exactly as decision D-001 predicted. The plan called it; the tests confirmed it.
The artifacts
Every step above is a real, self-contained page. Read them in order, or jump straight to the game.