# Abstract
Gomoku is deceptively simple on the surface — place five stones in a row and you win. But with forbidden moves, forced captures, and a 19×19 board state space, building a competitive AI is a non-trivial systems problem. This post walks through the core implementation decisions: the board representation, the Minimax search with alpha-beta pruning, and the gnarly double-three forbidden move detection.
# Board representation
The board is stored as a flat [u8; 361] array. Two bits per cell encode three states: empty,
black, white. Using bitboards (u64 pairs for each player) speeds up threat detection
significantly — a five-in-a-row check becomes a bitwise scan rather than a loop over neighbors.
# The double-three problem
A "free three" is an unbroken sequence of three stones where both ends are open. Placing a stone that simultaneously creates two free threes is forbidden. The algorithm must enumerate all candidate moves, then for each candidate, scan four directions and count open-three formations — without counting the same sequence twice through different pivot points.
The hardest edge cases: overlapping sequences that share a stone, sequences blocked by the board edge vs.
an opponent stone, and sequences that would become five-in-a-row (which overrides the double-three rule).
After three rewrites, the get_forbidden_moves function finally passes all test vectors.
# Alpha-beta pruning
Minimax without pruning is impractical at depth ≥ 4 on a 19×19 board. Alpha-beta cuts the effective branching factor from ~50 legal moves to ~8-12 in practice. The move ordering heuristic sorts candidate moves by a fast static evaluation (proximity to existing stones + threat score) before recursing, which dramatically improves the pruning ratio.
With these optimizations, the AI reaches depth 5 consistently under 400ms on a modern CPU — comfortably within the 500ms budget.