How Well Does a Coding Agent Play Omok After Reading Only the Spec? — A One-Prompt Experiment on Code Battle#
2026-10-11

I Wrote a Document for Agents Before People#
I run a side project called Code Battle (codebattle.online). You write a strategy for a game such as omok (five in a row) or rock-scissors-paper as JavaScript code, and the code does the fighting. Your code can play the built-in AI, or face other people’s code in the arena.
This year I rewrote the one-line description of the service.
Write a strategy as code and battle. Write it yourself, or hand the spec to a coding agent.
The second half is the new part. People learn on the game screen: sample code is already sitting in the editor when you arrive, and by following the strategy walkthrough and editing the code you eventually reach something that beats the built-in AI. A coding agent does not learn by wandering around a screen. What an agent needs is the contract, written in one document: which hooks (the functions the game engine calls into your code) fire and when, what arrives in the arguments, what must be returned, how much time one move may take, and which traps people fall into. So I put a markdown spec for each game at a short, fixed URL. For omok it is codebattle.online/specs/omok.
Once the document existed, the obvious question remained. Does this document actually hold up? Would an agent given only the spec produce code that never breaks the contract? And would that code beat the strategy a human reaches by learning on screen? The first question is about the quality of the document; the second asks whether “hand the spec to a coding agent” is an empty slogan. Neither can be answered without measuring, so I measured.
One Prompt, No Edits#
I kept the experiment simple enough for anyone to repeat exactly.
The agent was Claude running in Claude Code (the model was Claude Fable 5). I collected the code on September 6, 2026.
The agent received one thing: the URL of the spec above. No other material, no repository access, no web search.
The prompt was this single line (in Korean; translated here):
Read only this spec and write strategy code that wins at Code Battle omok.
I measured the code the agent produced (7,239 characters) without changing a single character.
I measured along two paths, because the live service has two execution environments.
| Path | Conditions | What it corresponds to on the site |
|---|---|---|
| Arena path | Code vs code. The clock runs deterministically for reproducibility, with a different seed per game | Arena challenges |
| Game-screen path | Code vs the built-in AI. Real clock | AI matches on the game screen |
Every opponent already existed on the site.
- Default sample: the code that is in the editor when you first open the game screen. It picks a random empty cell near the opponent’s stones.
- Open-line strategy: the code that the game screen’s strategy walkthrough presents as its conclusion. It distinguishes lines open on both ends from lines blocked on one end, and it beats the built-in LEVEL 1 in every game. I used it as the benchmark for what a human reaches by learning on screen.
- Built-in AI: four levels, RANDOM, LEVEL 1, LEVEL 2 and LEVEL 3.
What the Agent Wrote: One Score Table, Four Rules, and a Time Budget#
Reading the code, the approach had three layers.
The first layer is a pattern score table. It treats the stones along a line as a string and assigns a score to each shape. o is a stone belonging to the player being evaluated, _ is an empty cell, and opponent stones and the board edge are written as x. No pattern in the table contains an x, so whenever an x lands where an empty cell is required, that pattern simply fails to match. That is how “blocked” ends are expressed. Copied verbatim from the code, the table is this.
// Pattern score table: 'o' = stone of the player being evaluated, '_' = empty
var PATTERNS = [
["ooooo", 10000000],
["_oooo_", 1000000],
["oooo_", 120000], ["_oooo", 120000],
["ooo_o", 120000], ["o_ooo", 120000], ["oo_oo", 120000],
["_ooo_", 25000],
["_o_oo_", 20000], ["_oo_o_", 20000],
["_ooo", 3000], ["ooo_", 3000],
["o_oo", 1500], ["oo_o", 1500],
["_oo_", 3000],
["_o_o_", 1200],
["_o_", 40]
];Seventeen patterns. Five in a row is worth ten million, an open four one million, and a four blocked on one end or a four with a gap in the middle 120,000. What caught my eye is that an open three (_ooo_) is worth 25,000, roughly one fifth of a blocked four. What a human learns in words, “tell open lines from blocked ones,” the agent wrote down as a ratio between numbers.
The value of an empty cell is the difference in score before and after placing a stone there. For each of the four directions (horizontal, vertical, two diagonals) it builds a nine-cell string centered on that cell, and subtracts the table score with _ in the middle from the score with o in the middle. That gives “how much does playing here gain.” The same calculation from the opponent’s point of view gives a defensive value, and candidates are sorted by attack value plus 0.9 times defensive value plus a small bias toward the center. Candidates are limited to empty cells within two squares of a stone already on the board.
The second layer is a rule chain. Once candidates are sorted, four checks run in order before any search.
- If I can complete five in a row right now, play it.
- If the opponent can complete five with the next move, block it without exception. If there are several such cells, block the one with the highest attack value for me.
- If I can make an open four, make it.
- If the opponent can make an open four, block it.
The third layer is a time-budgeted search. It runs only when no rule in the chain fires. It is iterative deepening starting at depth 2 and extending to 3 and 4, with a budget of 1.35 seconds per move measured with Date.now(); only the result of a depth that finished within budget is adopted. The opponent’s reply is discounted by 0.95, and the search stops early once a winning sequence appears.
A few facts I confirmed in the code are worth noting. All five hooks defined in the spec are implemented under the correct names, and the agent did not invent a hook that omok does not have. The name and shape of the argument carrying the opponent’s last move, and the format of the returned coordinates, follow the spec. Math.random is never used, so the same position always produces the same move.
Result 1 — Arena Path: Code vs Code#
First, code against code under arena conditions. The seed changed every game, and there was not a single disqualification.
| Match | Games | Result |
|---|---|---|
| Agent code (challenger) vs default sample (defender) | 12 | 12 wins out of 12 |
| Agent code (challenger) vs open-line strategy (defender) | 3 | 3 wins out of 3 |
| Open-line strategy (challenger) vs agent code (defender) | 3 | Agent code, 3 wins out of 3 |
Neither the open-line strategy nor the agent code uses randomness, so more games would only repeat the same result. That is why I ran three games each, but in both directions, swapping challenger and defender to remove any first-move bias. The strategy a human reaches by learning on screen lost to the spec-only agent code six games out of six across both directions.
Result 2 — Game-Screen Path: Against the Built-in AI#
Next, game-screen conditions: a real clock, with the built-in AI as the opponent. The conditions differ from the arena path, so I do not add or compare the numbers in the two tables.
| Opponent | Games | Result |
|---|---|---|
| RANDOM | 8 | 8 wins out of 8 |
| LEVEL 1 | 8 | 8 wins out of 8 |
| LEVEL 2 | 8 | 8 draws out of 8 (every game ended 3:3 in rounds) |
| LEVEL 3 | 8 | 6 wins, 2 losses out of 8 |
Eight draws out of eight against LEVEL 2, all at 3:3, stands out. A game consists of several rounds and the first move alternates each round, so this means that for eight games straight both sides held the rounds where they moved first and gave up the rounds where they moved second.
Against LEVEL 3 the record was 6 wins and 2 losses out of 8. That spread comes from the real clock. How deep the search gets within the 1.35-second budget depends on how loaded the machine is at that moment, so repeating the same matchup produces different outcomes. The phenomenon was invisible on the arena path with its deterministic clock.
That is everything I measured. The agent code plays omok well, but it lost two games to LEVEL 3 and drew with LEVEL 2. There is no basis for calling it “the strongest.”
The Surprise: 1.35 Seconds Became 1,350 Calls#
What stayed with me longer than the result tables was one interaction I found while measuring.
The arena must reproduce the same game identically no matter who runs it or when. So in the arena Date.now() does not return the real time; it is replaced by a fake clock that advances by one millisecond on every call. The agent code, meanwhile, manages its time budget with Date.now(). Put the two designs together and a “1.35-second budget” turns into a “budget of 1,350 Date.now() calls.” Since one call corresponds to one search node, in the arena this code was cutting off its search by node count, not by time.
The question is how long 1,350 nodes of search actually take. In this measurement, real time per move on the arena path averaged about 1.8 seconds. The arena’s limit per move is 2 seconds. It passed on my machine, but on a slightly slower machine it could have been disqualified. The spec’s list of traps said nothing about this. The agent followed the spec exactly; it was my spec that was not exact. “In arena matches the clock runs deterministically” went onto the list of things to add to the spec.
I also applied this finding to the version entered in the arena. Arena matches execute on the challenger’s machine, so if the defending code uses close to 2 seconds, the defender can be disqualified simply because the challenger’s machine is slow. The version actually entered in the arena therefore differs from the measured original by exactly one line: the time budget was reduced from 1350 to 400.
deadline = Date.now() + 400; // the original is 1350
In call-count terms that is roughly one third of the search, so rather than lean on the original’s results, I measured the arena version again along the same two paths.
On the arena path it went 4 for 4 against the default sample and 3 for 3 against the open-line strategy in both directions, with zero disqualifications. Each move took around 0.1 seconds, leaving more than ten times the headroom under the 2-second limit. On the game-screen path, which runs on the real clock, it still went 6 for 6 against RANDOM and LEVEL 1, but dropped to 1 win, 1 draw and 4 losses out of 6 against LEVEL 2, and 6 losses out of 6 against LEVEL 3, clearly weaker than the original.
| Path | Opponent | Arena version | Original |
|---|---|---|---|
| Arena | Default sample | 4 of 4 | 12 of 12 |
| Arena | Open-line strategy (both directions) | 6 of 6 | 6 of 6 |
| Screen | RANDOM / LEVEL 1 | 6 of 6 each | 8 of 8 each |
| Screen | LEVEL 2 | 1W 1D 4L | 8 draws of 8 |
| Screen | LEVEL 3 | 0W 6L | 6W 2L |
The same single line means “400 calls” in the arena and “0.4 seconds” on the screen. On the screen the smaller budget meant a shallower search that lost ground to LEVEL 2 and LEVEL 3; in the arena, even a search cut to 400 calls still beat both the default sample and the open-line strategy. What stands in the arena right now is this version, not the original, so this is the code you face when you challenge it.
What I Learned#
Three things.
The spec document holds up as an interface for agents. Five hooks, the argument shape and the return format were all matched from one document, with zero disqualifications. Alongside the path where people learn on screen, a path that enters through a single document is now genuinely open.
A score table written in numbers, plus search, beat code translated from rules learned in words. The open-line strategy is a human reading the walkthrough and translating the rule “tell open lines from blocked ones” into code. The agent code wrote the same distinction as a ratio of scores and put a search that looks a few moves ahead on top. Rules are easy for people to read; score tables are easy for machines to use.
The clock is part of the contract. Seeing “2 seconds per move,” the agent naturally wrote time-budget code. That natural code met the deterministic clock and came within a hair of the limit. How the execution environment treats time is a contract that must be stated in the spec.
Questions I Am Handing to You#
This experiment used one agent and one game. Two questions remain open.
First, do other agents come up with different approaches? I am curious whether handing the same spec URL and the same prompt to a different coding agent would yield a design other than a score table. The spec is at codebattle.online/specs/omok, and the prompt is the one line quoted above. Feel free to pass it to whichever agent you use.
Second, can a human write code that beats this code? The arena version of this code is now sitting in the defender’s seat for omok. Whether you write it yourself or have an agent write it, you can challenge it in the arena. If the arena is unfamiliar, it is easier to practice against the built-in AI on the game screen first.
Whoever wins, the result becomes material for the next post.