Skip to story
pogofish
skip to play →

Pogofish — the experiment that trained the human

Act I — the experiment·I

The experiment was meant to train an AI. It trained the human instead.

12 days · 1 sentence
II · Five moves to feel the rules

Try a few moves before we go on.

You play White. Red answers with a fixed rule: capture if it can, otherwise stack, otherwise pick the first legal move. After five of your plies, the article scrolls on.

WW
WW
WW
RR
RR
RR
ply 0 / 5
Your move.
III
Nine cells, a game I did not invent

Pogo arrived in my life by way of a friend who had the box and explained the rules across a dining table one evening. Three rows, three columns, twelve pieces, no dice and no hidden cards — both players see everything, like in chess. The rules took five minutes to learn. The game took rather longer to forget.

What I was actually after, that month, was a reinforcement-learningRLReinforcement learning — learning from rewards and punishments, not labels. experiment. RL — reinforcement learning — is the branch of machine learning where a program is taught to play a game by letting it play that game against itself, thousands or millions of times, until something resembling skill emerges from the noise. It is the technique behind the programs that beat the world champions at Go, at chess, at almost every game anyone has bothered to point it at — and it is famously expensive: the heavy-hitters consume small data centres for weeks at a stretch.

I did not have a small data centre. I had eight gigabytes of RAM. What I wanted was a game small enough that the experiment could fit on a laptop — train, evaluate, and play against the result, all on one machine, in time I could measure in days rather than months.

Pogo, three rows by three columns, looked very much like that game.

I did not write the first line of code that evening. I opened a chat with Claude — a general-purpose AI I had been using for months — to plan the experiment.

IV
“No more than a million.”

In the chat, I described what I wanted: an RL agent that learns Pogo by playing itself, on my laptop, in days. I asked Claude how to structure the project.

Claude came back with a five-phase plan. Phase one, a Python game engine — a sandbox for an RL agent to interact with. Phase two, a minimaxMinimaxA search that plays optimally by assuming the opponent does too. solver: a brute-force algorithm that reads every move from the current position, then every reply, then every reply to that, all the way to the end of the game, and propagates the verdicts back. Run it once, ahead of time, and you get a complete map of every reachable Pogo position with its true value attached — the ground truth against which to measure whatever the network later learns. Phases three and four, the RL itself: tabular Q-learning as a baseline, then deep RL on top of it. Phase five, an interactive page where any reader can play the trained network.

I had not known about minimax going in. Once Claude laid out the rationale — solve first, train against the solution — it landed cleanly. The plan made sense.

Only one practical question remained: was Pogo small enough for the brute-force phase to finish in a reasonable time on a laptop with eight gigabytes of RAM?

before you read on

Pogo is three by three, twelve pieces, no captures — pieces only restack. How many distinct positions can it reach? Commit to a number before the machine gives me its own.

your guess1.8M
100k1M10M100M
claude · session 0 · 2026-03-19
> how many reachable states?
no more than ≈ 1,000,000. easy solve — a few hours, single-threaded.

The estimate sounded fine. A million positions, brute-forced in an afternoon, was no obstacle to the project; it would get out of the way of the more interesting RL work. I agreed to the plan and asked Claude to write the engine.

The estimate would turn out to be wrong by a factor of fifty.

V
Thirty-six gigabytes at dawn

The engine came together in two days. Claude wrote it; I read each commit, asked questions, ran the test fixtures the engine shipped with. Phase one, done.

The minimax solver came next. Claude wrote it in Python, alongside the engine. The two pieces of standard machinery from thirty years of game-tree research went in: alpha-beta pruningAlpha-beta pruningA shortcut that skips branches proven worse than one you've already seen. (skip a branch as soon as another has been proved better) and a transposition tableTransposition tableA hash map of positions already evaluated, so you don't redo the work. (cache the value of every position seen, so the same one isn't recomputed when reached by a different path of moves). Nothing exotic. On paper, the kind of solver that finishes a million positions over lunch.

I launched it, watched the first few hundred positions get scored, and went to do something else. I kept Claude posted. Day one, table at eight hundred thousand entries, growth nominal. Day two, slower, plausible. Day three, the curve was already shaped like nothing anyone recognised. Day five, it was climbing nearly vertical. On the morning of day six, the operating system killed the process — out of memory, thirty-six gigabytes resident, forty-nine million entries in the table, and not a single value had made it back to the root. Six days, no answer, no checkpoint on disk.

states visited0M·0 GB·DAY 6
d1d2d3d4d5d6
0peak 49M states · 36 GB RAM
solver.log · tail
  1. d1 03:14:07infoalpha-beta search depth=8 branching≈12
  2. d1 11:40:22infoTT entries: 412,008 hit-rate: 38.4%
  3. d2 07:12:55infoTT entries: 8,344,207 hit-rate: 41.1%
  4. d3 14:03:18warngrowth curve no longer log-shaped
  5. d4 09:45:01infoTT entries: 27,901,540 RAM: 22.4 GB
  6. d5 22:56:37warngrowth curve nearly linear
  7. d6 08:11:19infoTT entries: 49,000,000 RAM: 36.0 GB
  8. d6 08:11:21killkilled (oom-killer): pogofish-solver

I got lucky once. The solver had been running in a bash shell inside a Claude Code session, which meant the dead process's memory image was still reachable from inside the same session: between us, Claude and I pulled the value table out of RAM into a Pickle file before the session ended. On paper, the six days of compute were salvaged.

On the strength of that recovery, I asked Claude whether it was safe to relaunch — outside the session this time, on a fresh terminal, with a longer leash. Claude said yes.

It wasn't. The second run blew up the same way at day six, with no Claude Code memory underneath to fish out. This time there was nothing to recover. Twelve days of compute, gone. A near-empty log file, a fan that had been quiet for a week, and a diagnosis still owed.

VI
The tree had no leaves

After the second loss, Claude and I sat down with the source code and worked out what had actually been going on.

The cause was small. Pogo has no captures: pieces never leave the board, they only restack. A given position can therefore be reached not just by one sequence of moves but by many — including sequences that loop back through it. Two careful players can shuffle the same three pieces between the same three cells indefinitely. The same is true of a search algorithm: nothing in a textbook minimax stops it from going round the same circle thousands of times, filing each lap as a fresh path.

That is what had eaten thirty-six gigabytes. The forty-nine million entries the solver had stockpiled were not forty-nine million distinct Pogo positions — many were the same handful of positions filed under different routes. The transposition table couldn't catch it: it indexes positions, not paths, and there was nothing telling the route that led to a stored position that the route was itself going in circles.

The fix is well-known. When the search encounters a position it has already visited earlier in the current line of play, treat that branch as a draw and back out. Two extra lines of code, three with the comment. Claude wrote them.

While we were in the code, we noticed something else. Pogo's win condition — a player loses when no cell on the board has their colour on top, leaving them with no legal move — left room, in theory, for two careful players to settle into a stable, mutually inert configuration that neither side wanted to disturb. In casual play this almost never happens. Against a search that tried everything, it happened constantly. The game needed a sharper way to end, not just for the algorithm but for the game itself to feel like a game.

what I drew
finite · ends at leaves
what I was solving
artificial horizon
infinite · cycles below the line
Twelve days of compute. Two lines of code. Three with the comment.
VII
The rule becomes the variable

The rule rewrite was small. We added one rule: if the same position recurs three times in a game, the player to move loses. A repeat once or twice is just normal back-and-forth. Three times is a sign nobody wants to commit. That was enough to give the game an unambiguous way to end, even between two players who had silently agreed to do nothing.

With the search bug fixed and the rule added at the board level, Claude reran the solver. It finished in fifty-four minutes. Nine and a half million distinct positions seen, of which roughly nine hundred and seventy-five thousand turned out to be decisive — one side or the other had a forced win — and the rest were draws. From the opening position, with both sides playing the strongest move available, the game is a draw.

Somebody, eventually, has to lose.

Phase two of the original plan: done. But the result came with a footnote. To keep memory bounded, the solver had been capped at twenty half-moves of search depth, then declared anything still undecided a draw. Pogo, it turns out, is deeper than twenty half-moves. Some of those "draws" are positions whose true value can only be settled by looking further. Brute force can take Pogo to twenty. It cannot take it further.

Which is, on reflection, the right setup for the experiment I had wanted from the start. Everything brute force can prove has now been proved. Everything beyond that — the positions undecided at twenty half-moves — is exactly where reinforcement learning earns its place. If a network, after enough self-play, outplays the partial oracle on the deep positions, that is a real result.

The variants the project ended up testing are small variations on that one terminating rule: different repetition counts, different move budgets, the odd tie-breaker. The next chapter is a tour of them and a verdict on which ones are actually worth playing.

[VIII]
Five candidate endings

If the rules were the variable, which rule was the good one?

To pick the right rule, you have to actually play each variant. I ended up drawing up 5 small variations on the terminating rule and running 4 of them, and for each I built three opponents to set against one another.

The first opponent picks a legal move at random. It's a floor: if a trained network can't beat random, it has learned nothing.

The second is a network that learns by playing itself thousands of times, gradually preferring moves that ended in wins and steering away from those that ended in losses. The third is a stronger version of the same idea — the algorithm DeepMind used to beat the world champions at Go and chess: at every turn the network draws up a short list of plausible continuations and looks a few moves ahead before committing. The combination is meaningfully better than either piece alone.

For each variant, every level played every other level — random against the trained network, the network against its stronger version, and so on. 800 games per variant. Three things mattered. Was White's win rate between 45 and 55 % — is the game balanced? Did the stronger player win at least 75 % of the time against the weaker one — does skill matter? And when draws happened, were they between players of comparable strength (earned) or between unequal ones (suspicious)?

All 5 variants are below — 4 with tournament results, one that never got trained. The verdict follows.

LC1-2
Sudden Death

Repeat a position twice → you lose.

LC1-3↻↻
Triple Repeat

Three-fold repetition ends the game.

LC2-30
Hard Cap

30 moves. Parity picks the winner.

LC3-29
Classic

29 moves. Most towers wins. Ties draw.

LC3-40
Long Soft Cap

40 moves. Draws get earned.

[IX]
Three clear the bar, one never ran

Three clear all three axes. One fails. One never ran.

Balance, skill, and earned draws — pass or fail, per variant, per metric. The bars below are the tournament results compressed into three numbers. The result files are checked into the repository, and the story they tell is visible from across the room.

tournament.out · page 1 of 14 of 5 variants tested · 3,200 games
variantwhite win sharewin rate vs randomdraw shareverdict
shipped in the playable app
Sudden Death
LC1-2
52.0%
100.0%
0.0%
pass
shipped in the playable app
Classic · 29
LC3-29
50.0%
99.0%
5.5%
pass
Long Soft Cap
LC3-40
48.5%
100.0%
1.0%
pass
Hard Cap · 30
LC2-30
42.0%
99.5%
0.0%
fail
The cap's parity, not the play, picks the winning colour.
Triple Repeat
LC1-3
not run
Never trained. No tournament was run.

★ marks the variants whose models ship in the playable app — not a ranking.

White win share and draw share come from the model-against-itself tournament; win rate vs random is that model against a player picking legal moves at random. Both files are committed under src/data/tournaments/.

Verdict is computed, not written: pass needs a white win share between 45% and 55%, at least 75% against random, and at most 10% draws. A white win share outside that band fails outright.

Sudden Death wins on elegance. Repeat a position, you lose. Every move is consequential because the cost of stalling is built into the rule itself, not bolted on with a move counter. The network learned this quickly: the trained AlphaZero model beat the DQN baseline in every game they played, and not one game of the tournament ended in a draw. A game that either decides or continues.

LC1-2 · 52% white · 100% vs random · 0.0% draws

Classic · 29 wins on feel. A move budget you can hear ticking, a clean tiebreaker (most towers), and draws that exist but are earned — the trained network drew a modest share of its games against itself, and almost none against a random player. It is the rule I would pick if I were teaching a ten-year-old the game, which is the highest compliment a rule change can earn.

LC3-29 · 50% white · 99% vs random · 5.5% draws

Of the variants that didn't ship, the most instructive — and the only one the numbers actually reject — is the one with a hard move cap and no tie-breaker. Under that rule the game is secretly decided by parity: whoever has to move on the capping turn loses, so whichever colour's parity matches the cap wins regardless of play. The trained network took an afternoon to figure that out. After which it stopped playing the game and started playing with the counter — a useful reminder that a rule which lets a learner converge on a counting trick isn't really a rule about the board.

49,000,000 entries · 36 GB · killed
54 min · 975K positions · finished

The fix took an afternoon. The rerun took fifty-four minutes. The twelve lost days had only ever been mine to lose.

X · Now it is your move

Against an opponent that learned this rewritten game from a million games of itself.

Pick a rule. Pick a side. The network running in your browser is the same one that won the tournament — exported as ONNXONNXA neural network file format different frameworks can load. (a portable file format for trained networks) and loaded client-side, with no server in the loop. Every move it plays here is computed in your browser, on your device.

Rule
The losing condition the game is played under.
Opponent
Which network evaluates positions.
Play as
Your color on the board.
XI · What it decided about winning

After 500 games against itself, the network had opinions.

These charts come from one checkpoint of the soft-cap variant (LC3-29) playing itself, at 200 simulations per move, openings sampled at temperature 0.5 for six plies and greedy after. That is a different measurement from the verdict table above — a different checkpoint, more simulations, sampled openings, and no colour swap — which is why White's win share is higher here than there. The numbers are empirical, counted from games actually played; the diagrams are real positions from the sweep, and the whole payload is committed in the repository.

heatmap — opening moves

Where the network looks first.

The brighter the cell, the more often the network plays there in the very first ply. White almost never plays outside a tiny set of squares — the diagonal and the centre. Most- played first move: a3 → b3 [×1] in 72% of games.

rare → frequent
01

White's first move is nearly predetermined. a3 → b3 [×1] in 72% of games. (The bracket gives the stack size moved — Pogo lets you take one, two or three pieces from a cell where your colour is on top.)

Across 500 self-play games the network tried only 7 distinct first moves. It knows which square it wants.

initial position
02

Captures peak at ply 6, then taper as the position locks.

Plies 6–10 alone account for 1353 captures across the 500 games — a game can capture more than once in that window. After move 15, captures are rare: the network trades early, then plays for tempo.

captures by ply
01529
03

The network's confidence swings hard on small trades.

Same game, twelve plies apart. In the first position the value head says +0.55 (White wins). Twelve plies later, after a forced trade sequence, it says -0.47 (Red wins). Pogo has tactical cliffs, and AlphaZero sees them.

+0.55 — White wins
-0.47 — Red wins
04

First player wins by design: 65% White, 31% Red, 4% draws.

Against itself, the network produces a 2:1 first-player advantage. The Classic variant (LC3-29) softens this with draws but doesn't erase it — move order matters more than any strategic subtlety.

Glossary

Every term the story used, in plain English.

Most of these words meant nothing to William at the start of the project. The definitions below are the versions he wishes someone had handed him at the time — short, specific, and free of the assumption that you already know the surrounding ten terms.

Drag a term — the constellation follows. Hover to reveal the links.

Term constellation: three clusters — AI, Infrastructure, Pogo — linked by conceptual relationships.MinimaxAlpha-beta pruningTransposition tableRLQ-learningDQNAlphaZeroMCTSPUCTSelf-playGatekeeperPolicy / value networkONNXWASMRustManhattan distanceLazy equilibriumRound robinAI & LEARNINGINFRASTRUCTUREPOGO
  • Minimax: A classical search algorithm: build the game tree, assume each side picks the move that is best for them and worst for the other, back up the values from the leaves. It is optimal on finite games but needs every branch to eventually end — the thing Pogo, in its original form, does not do.
  • Alpha-beta pruning: An upgrade to minimax. As you search, you keep track of the best value each player has already guaranteed; when a branch can no longer beat that bound you stop exploring it. In a well-ordered tree it roughly halves the exponent of the search cost.
  • Transposition table: Game search algorithms revisit the same position through different move orders (“transpositions”). Caching the result keyed by the position itself turns an exponential blow-up into something manageable — at the cost of memory, which is exactly how six days ended in 36 GB and an OOM.
  • RL: Reinforcement learning. A branch of machine learning where an agent picks actions, observes a reward signal, and updates its strategy to get more reward over time. No teacher says “this move is correct”; the only signal is wins and losses accumulated over many games.
  • Q-learning: The entry-level RL algorithm. Keep a table of numbers Q(state, action) estimating the long-term reward of taking an action from a state, and update it using the Bellman equation after each transition. It is exact on small problems and utterly impractical on large ones — the table becomes bigger than RAM.
  • DQN: Deep Q-Network. DeepMind's 2013 result: replace the Q-table with a neural network that takes a position as input and outputs a value for each legal move. The network generalises, so you are no longer bounded by RAM. In this project DQN was the second opponent I trained — it beat random play handily and lost to AlphaZero.
  • AlphaZero: A training recipe. A single neural network outputs both a move policy and a position value. The agent plays against itself thousands of times, each move guided by Monte Carlo Tree Search that consults the network. Training uses only the outcomes of those games — no human examples required. It is the strongest opponent in this project.
  • MCTS: Monte Carlo Tree Search. Instead of exhaustively searching, repeatedly simulate playouts from the current position, grow the tree toward moves that did well, and return the most-visited action. Combined with a neural-network prior it becomes the core of AlphaZero.
  • PUCT: The formula MCTS uses to pick which node to expand next. It adds a bonus for rarely-visited moves that the policy network believes are good, so the tree grows toward promising-but-under-explored branches. The letters stand for Polynomial Upper Confidence Trees.
  • Self-play: Training data made by pitting the current network against itself. No humans required. Each game produces a trajectory of (state, policy, outcome) triples used to nudge the network toward better moves. The quality of the training data improves as the network does — a virtuous loop.
  • Gatekeeper: A gate that keeps training honest. When a new candidate network is produced, it plays a batch of games against the current champion. If it wins above a threshold (here: 55%), it becomes the new champion. Otherwise the candidate is discarded. Stops fluke training runs from being called progress.
  • Policy / value network: An AlphaZero-style network has two heads. The policy head outputs a probability distribution over legal moves; the value head outputs a single number estimating the outcome for the current player. Both are trained jointly from self-play data.
  • ONNX: Open Neural Network Exchange. A portable serialisation for trained models. Here a Python script exports the PyTorch checkpoint into ONNX, and the browser loads the same file through onnxruntime-web — the network trained on my iMac runs unchanged on your phone.
  • WASM: WebAssembly. A portable binary format for code running inside the browser. The Rust engine that drives training on the server is compiled to WASM and shipped to your browser, which is how your opponent uses exactly the same move generator the training loop did.
  • Rust: A programming language designed by Mozilla. Memory-safe without a garbage collector, fast enough for compilers and game engines, compiles to both native binaries and WebAssembly. In this project it replaces a Python+TypeScript combination that used to drift between training and production.
  • Manhattan distance: For two cells at (x1,y1) and (x2,y2), the value |x1−x2| + |y1−y2|. It is how a taxi would measure distance on a grid of streets. In Pogo, a 1-piece move travels distance 1, a 2-piece move distance 2, a 3-piece move distance 1 or 3.
  • Lazy equilibrium: Not a standard term — my shorthand for a Pogo state where the two players can keep shuffling pieces between the same three cells without ever forming a tower. Neither is losing, neither is winning, and the game, under natural rules, does not end.
  • Round robin: Each opponent plays every other opponent a fixed number of games, half as White and half as Red. The resulting win-rate matrix tells you not just who is strongest but how the rule variant behaves at every skill tier — important because a rule that works for strong players can fail for weak ones, and vice versa.
See the full list
AI & learning
Minimax
A classical search algorithm: build the game tree, assume each side picks the move that is best for them and worst for the other, back up the values from the leaves. It is optimal on finite games but needs every branch to eventually end — the thing Pogo, in its original form, does not do.
Alpha-beta pruning
An upgrade to minimax. As you search, you keep track of the best value each player has already guaranteed; when a branch can no longer beat that bound you stop exploring it. In a well-ordered tree it roughly halves the exponent of the search cost.
Transposition table
Game search algorithms revisit the same position through different move orders (“transpositions”). Caching the result keyed by the position itself turns an exponential blow-up into something manageable — at the cost of memory, which is exactly how six days ended in 36 GB and an OOM.
RL
Reinforcement learning. A branch of machine learning where an agent picks actions, observes a reward signal, and updates its strategy to get more reward over time. No teacher says “this move is correct”; the only signal is wins and losses accumulated over many games.
Q-learning
The entry-level RL algorithm. Keep a table of numbers Q(state, action) estimating the long-term reward of taking an action from a state, and update it using the Bellman equation after each transition. It is exact on small problems and utterly impractical on large ones — the table becomes bigger than RAM.
DQN
Deep Q-Network. DeepMind's 2013 result: replace the Q-table with a neural network that takes a position as input and outputs a value for each legal move. The network generalises, so you are no longer bounded by RAM. In this project DQN was the second opponent I trained — it beat random play handily and lost to AlphaZero.
AlphaZero
A training recipe. A single neural network outputs both a move policy and a position value. The agent plays against itself thousands of times, each move guided by Monte Carlo Tree Search that consults the network. Training uses only the outcomes of those games — no human examples required. It is the strongest opponent in this project.
MCTS
Monte Carlo Tree Search. Instead of exhaustively searching, repeatedly simulate playouts from the current position, grow the tree toward moves that did well, and return the most-visited action. Combined with a neural-network prior it becomes the core of AlphaZero.
PUCT
The formula MCTS uses to pick which node to expand next. It adds a bonus for rarely-visited moves that the policy network believes are good, so the tree grows toward promising-but-under-explored branches. The letters stand for Polynomial Upper Confidence Trees.
Self-play
Training data made by pitting the current network against itself. No humans required. Each game produces a trajectory of (state, policy, outcome) triples used to nudge the network toward better moves. The quality of the training data improves as the network does — a virtuous loop.
Gatekeeper
A gate that keeps training honest. When a new candidate network is produced, it plays a batch of games against the current champion. If it wins above a threshold (here: 55%), it becomes the new champion. Otherwise the candidate is discarded. Stops fluke training runs from being called progress.
Policy / value network
An AlphaZero-style network has two heads. The policy head outputs a probability distribution over legal moves; the value head outputs a single number estimating the outcome for the current player. Both are trained jointly from self-play data.
Round robin
Each opponent plays every other opponent a fixed number of games, half as White and half as Red. The resulting win-rate matrix tells you not just who is strongest but how the rule variant behaves at every skill tier — important because a rule that works for strong players can fail for weak ones, and vice versa.
Infrastructure
ONNX
Open Neural Network Exchange. A portable serialisation for trained models. Here a Python script exports the PyTorch checkpoint into ONNX, and the browser loads the same file through onnxruntime-web — the network trained on my iMac runs unchanged on your phone.
WASM
WebAssembly. A portable binary format for code running inside the browser. The Rust engine that drives training on the server is compiled to WASM and shipped to your browser, which is how your opponent uses exactly the same move generator the training loop did.
Rust
A programming language designed by Mozilla. Memory-safe without a garbage collector, fast enough for compilers and game engines, compiles to both native binaries and WebAssembly. In this project it replaces a Python+TypeScript combination that used to drift between training and production.
Pogo
Manhattan distance
For two cells at (x1,y1) and (x2,y2), the value |x1−x2| + |y1−y2|. It is how a taxi would measure distance on a grid of streets. In Pogo, a 1-piece move travels distance 1, a 2-piece move distance 2, a 3-piece move distance 1 or 3.
Lazy equilibrium
Not a standard term — my shorthand for a Pogo state where the two players can keep shuffling pieces between the same three cells without ever forming a tower. Neither is losing, neither is winning, and the game, under natural rules, does not end.
XII
Keep the problem in your head

Keep enough of the problem in your own head to know when something is wrong.

An AI told me, one day, that a small board game had fewer than a million reachable positions. It was wrong by a factor of fifty. The first run stockpiled forty-nine million entries before the kernel killed it. The second run blew up the same way and took everything with it. Twelve days of compute, total.

The answer was plausible. It matched what I was hoping to hear. Neither of us took the ten minutes it would have taken to check.

The lesson isn't that AIs are untrustworthy. It's that a mistake said fluently sounds exactly like a truth said fluently — and the only defence against that symmetry is to keep, in your own head, a version of the problem detailed enough to notice yourself, unaided, when an answer is off by an order of magnitude. It isn't a technical safeguard. It's a discipline.

You cannot outsource the part of the thinking that tells you whether the thinking is working.

The rewrite happened after the second loss. The Python solver was rewritten in RustRustA systems language that eliminates a whole class of memory bugs at compile time., with cycle detection in the search and checkpoints on every long job. The same engine now drives the network's training and the browser tab you're reading in, so there is no possible drift between what the network learned and what you can play against. The rule itself was treated as an experimental variable, not a given. What came out is a smaller, honest game: it ends, skill wins, draws are earned.

The code is open. The solver, the training pipeline, the variants and their tournament logs, the network and the bridge that runs it client-side — all of it lives in one Rust workspace you can clone and run. The best way to trust a computation is still to do it yourself.

The game, on your terminal

Pogofish ships a curses-style terminal game — the same Rust engine as the browser version above, keyboard controls, no GUI.

macOS and Linux. Keys: arrows to navigate, Enter to select, 1/2/3 for stack size, q to quit.

— William Revah, spring 2026

pogofish
a board game · a misplaced trust · a rewrite
Made with care by William.
▶ Play