I knew the rules of Go. Writing them for a machine taught me I didn't understand them.
I’m rebuilding AlphaGo from scratch — 9×9 board, no shortcuts — to actually understand how it works instead of just running someone else’s repo. The spark was this podcast, which walks through building AlphaGo from scratch and makes the whole thing feel approachable enough to actually try. Stage 1 was the unglamorous part: the Go engine. Place a stone, capture dead groups, and score the board. I know the rules of Go, so I figured this would be a quick warm-up.
It was not. And the reason it wasn’t is the whole point of this post.
The machine takes nothing for granted
Knowing the rules of Go is enough to follow a game: “these stones are captured,” “that’s obviously territory,” “you can’t replay there, that’s ko.” But knowing a rule well enough to follow it lets you lean on intuition for the messy parts — you recognize the situations without ever pinning down the exact mechanics, because as a human, you never have to.
A computer has none of that slack. To write the engine, I had to turn every piece of “obvious” knowledge into a rule precise enough that a machine with zero common sense could follow it. That process is where I discovered how little I actually understood the rules I thought I knew.
Here are the moments that humbled me.
Pitfall 1: The bug hid exactly where I didn’t look
My first function just listed a point’s neighbors. I wrote it, tested the top-left corner, an edge, and the center — all correct. Moved on.
Then a test of the bottom-right corner returned neighbors that were off the board. The bug: I’d written r + 1 <= size instead of r + 1 < size — a classic off-by-one. My earlier tests had all exercised the top and left edges, where a different check fires. The bug lived in the one region my tests never touched.
Lesson, burned in permanently: a passing test suite doesn’t mean correct code — it means correct code in the cases you tested. Bugs hide in the gaps.
Pitfall 2: the rule I knew, in an order I never questioned
Anyone who knows Go “knows” you remove stones that have no liberties. But here’s a question I’d never had to answer: when you place a stone, what order do you resolve things in?
It turns out the order is load-bearing. You must remove the opponent’s dead stones before checking whether your own move was suicide. Why? Because a move that looks like suicide — your stone dropping into a spot surrounded by enemies — can actually be a capture. Removing the opponent first frees up the very liberty that saves your stone.
I knew the capture rule cold — but I’d never had to ask in what order captures and suicide resolve, because when you’re just following along, the board sorts itself out and you never notice the sequence. Writing it down for the machine forced the rule into focus.
Pitfall 3: the simple rule that wasn’t — ko
I knew ko: you can’t immediately recapture and put the board back the way it was. Simple.
Except the engine needs something stronger — positional superko: you can’t recreate any board position that has ever occurred in the game, not just the last one. And when I asked why, the answer reframed how I think about the whole project: self-play games must be guaranteed to terminate. AlphaGo trains by playing millions of games and scoring the result. A game that loops forever can never be scored. Simple ko only stops the 2-move loop; longer cycles (triple ko and friends) would still hang. Superko forbids all repetition, so every game ends.
I knew the rule. I’d never known what it was for.
(Bonus humbling: my first superko test “proved” the feature was broken. The board kept repeating. The bug wasn’t in the engine — it was in my test: I’d built the position by poking the board directly, which skipped the bookkeeping that records past positions. The engine was fine; I’d lied to it. Even your tests can be wrong.)
Pitfall 4: three functions, three jobs, and the danger of blur
The trickiest stretch was a refactor: I needed one function to mutate the board, another to check a move’s legality without changing anything, and a third to commit a real move. Three jobs.
I kept letting them bleed into each other. One version had the “check” function quietly committing moves, so generating the list of legal moves would have played 80 phantom moves. Another had the commitment silently, not placing the stone at all. Each bug came from the same root: a function doing more than its one job.
This is the kind of clean separation an AI would write correctly on the first try. And that’s precisely why I’m glad I wrote it myself — badly, then fixed it. Reasoning through why the board stopped updating taught me something about side effects and single-responsibility that a correct-on-the-first-try answer never would have.
The actual lesson: AI can write the code. It can’t transfer the understanding.
Through all of this, I had an AI tutor — but deliberately not as a code generator. It refused to hand me answers. It asked leading questions, reviewed what I wrote, pointed out the region my tests didn’t cover, and made me predict every output before running it. I found or fixed every bug above myself.
I could have typed “write me a 9×9 Go engine” and had a working one in seconds. I’d also have understood exactly nothing. The understanding doesn’t live in the code — it lives in the struggle of producing the code: choosing the data structures, predicting outputs, and especially debugging. Debugging is where the abstract rule collides with a concrete failure, and the lesson finally lands.
And the sharpest version of this, for me: implementing the rules of Go taught me the rules of Go — at a precision that simply knowing them never required. Knowing a rule well enough to follow it and understanding it well enough to build it are different kinds of knowledge, and only the second one transfers to the next problem.
That’s the whole reason I’m building this by hand. AlphaGo’s real magic — the search, the self-improving network — is still ahead of me (Stage 2 is Monte Carlo Tree Search). But if Stage 1 taught me anything, it’s that the value isn’t in having the engine. It’s in becoming the kind of person who could have written it.
Next up: teaching a computer to search — without a neural net yet.
A note on process: this post was drafted by AI and reviewed, corrected, and signed off by me, which, given the argument above, feels worth saying out loud. The code, the bugs, and the understanding are mine; the write-up is a collaboration. Using AI to help narrate the journey is fine. Using it to skip the journey would have defeated the entire point.
