spectastic Examples
Docs Play the game
Worked example · build-along

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.

▶  Play the game Read the build →
The product

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.

1

Principles

principles.html

Before 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.

2

What 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.

3

The 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.

# grounding & evidence · reply to a centre opening
Minimax   29,632 positions
Alpha-Beta  4,979 positions  — same move, ~6× less work
4

The 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.

5

Implement & verify

tictactoe.html

Drain 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.

6

Triage a defect

triage-log.html

Then, 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.

7

Propose · apply · implement

proposal.html

The 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 · 3p

Picks any empty square. The naive baseline — a losing strategy against anyone paying attention.

Minimax2p

Searches the whole tree assuming perfect opposition; prefers faster wins. Unbeatable at 3×3.

Alpha-Beta2p

Minimax that prunes branches it can't benefit from. Identical play, a fraction of the positions.

MaxN3p

The multiplayer generalisation: every node scores a vector, and each player maximises their own slot.

Paranoid3p

Assumes the other two players coordinate against it — "me vs. the world." Cheaper, and often deeper.

MCTS2p · 3p

Knows 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.

0
human wins in 40 games against Minimax and Alpha-Beta. SC-001 ✓
100%
of games ended in a clean win or draw — no illegal boards, in either mode. SC-002 ✓
~6×
fewer positions for Alpha-Beta than Minimax on the same opening. SC-003 ✓
A finding, not a bug

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.

principles.htmlFive non-negotiables spec.htmlWhat to build & how we'd know plan.htmlThe approach & the decisions tasks.htmlEighteen ordered, checkable tasks triage-log.htmlA defect, reproduced & closed proposal.htmlA change: propose → apply → implement ▶ tictactoe.htmlThe playable product
▶  Play the game ← All examples