Building over-engineered tic-tac-toe
A pure rules core, an engine registry, and a thin view — plus the one decision that actually mattered.
Three layers: pure rules, a registry of six interchangeable engines, and a thin DOM view. The engines come straight from the algorithmic interlude; the only genuine fork was whether three-player tic-tac-toe needs a bigger board. It doesn't.
§1 Principles check
| Principle | Gate | How met |
|---|---|---|
| P-1 | OK | Rules live in pure functions, no DOM. |
| P-2 | OK | One HTML file, no build. |
| P-3 | OK | All six engines behind chooseMove(). |
| P-4 | OK | A self-play harness proves SC-001–003. |
| P-5 | OK | Wordle-grade board, minimal chrome. |
§2 Technical context
- Language
- Vanilla JS (ES2019).
- Framework
- None.
- Build
- None.
- Storage
- None.
- Testing
- In-browser self-play harness.
- Performance
- < 500 ms per move.
- Constraints
- Single file, runs from file://.
§3 Grounding & evidence
| Claim | Source | Status | Finding |
|---|---|---|---|
| Engine set = Random → MCTS | Over-Engineering Tic-Tac-Toe (article) | verified | Six named engines adopted verbatim as the difficulty ladder. |
| 3×3 game tree ≤ 9! = 362,880 leaves | Combinatorial bound | verified | Exhaustive search is tractable in the browser. |
| Alpha-Beta explores far fewer nodes than Minimax | Self-play measurement | verified | Reply to a centre opening: 29,632 positions (Minimax) vs 4,979 (α-β); to a corner: 31,972 vs 3,886. |
| MCTS at 2,500 iterations plays soundly on 3×3 | Spike | spike | Sound within the 500 ms budget, though not provably optimal. |
| 3×3 Minimax moving second cannot lose | Classic result + self-play | verified | Zero losses across 40 random-opponent games. |
§4 Approach
Three layers, cleanly separated. A pure rules core holds the board, its legal moves, and win/draw detection — plain functions over plain data, with no reference to the DOM. On top sits an engine registry keyed by name: Random, Minimax, Alpha-Beta, MaxN, Paranoid, and MCTS, each reached through one chooseMove() contract, so adding an engine never touches the loop.
A thin DOM view renders the board, the controls, and the result overlay — and nothing more. A player click drives the game loop, which advances the turn and, for any CPU seat, calls chooseMove() against the current registry entry. The rules core is the single source of truth; the view only reflects it.
§5 Alternatives considered
| Option | MaxN cost | Winnable | Tractable < 500 ms |
|---|---|---|---|
| 3×3 (reuse) ✓ | Low | Yes (drawish) | Yes |
| 4×4, 3-in-a-row | High | Yes | Marginal |
| 5×5, 4-in-a-row | Very high | Yes | No |
Bigger boards make MaxN meaningful but blow the move-time budget. 3×3 keeps the search instant.
§6 Decisions
Keep three-player on the 3×3 board
- Context
- MaxN's cost climbs steeply with board size, and a larger board breaks the 500 ms budget.
- Decision
- Reuse the 3×3 board for three players.
- Consequences
- Strong three-player play tends to draw — accepted, and surfaced as a verify finding rather than treated as a defect.
Mode-scoped engine menu
- Context
- Minimax and Alpha-Beta assume exactly one opponent; MaxN and Paranoid need three or more players.
- Decision
- Offer Minimax/Alpha-Beta only in two-player, MaxN/Paranoid only in three-player; Random and MCTS span both.
- Consequences
- The menu maps honestly onto the article's taxonomy; no engine is offered where it degenerates.
MCTS budget = 2,500 iterations
- Context
- MCTS trades iterations for strength.
- Decision
- Fix 2,500 iterations.
- Consequences
- Sound 3×3 play inside the perf budget; revisit if the board ever grows.
§7 Project structure
tictactoe.html
├─ rules/ board · legalMoves · winner · isDraw (pure)
├─ engines/ random · minimax · alphabeta · maxn · paranoid · mcts
├─ view/ board render · controls · result overlay
└─ verify/ self-play harness (SC-001–003)
§8 Risks & mitigations
| Risk | Mitigation | Status |
|---|---|---|
| MaxN first move on the empty 3×3 explores ~362k nodes | Still resolves under 500 ms; bounded by the 9-cell board. | accepted |
| MCTS is non-deterministic (seedless) | Acceptable for a demo; results are stable in aggregate. | accepted |
§9 Change log
One file. Renders anywhere. Degrades to readable static HTML.