Chapter 12: The Capstone (Building a Game)

Can Nanocode actually build something?

The “Zero Code Challenge”: build a classic Snake game using Python and Pygame. The rule—you are not allowed to write a single line of Python. You can only speak to the agent in English.

An icon indicating this blurb contains information

Aside: This demo involves many API calls. If you hit rate limits (HTTP 429), the agent will retry automatically. For long sessions, consider using a local model via Ollama to avoid limits entirely.

Step 1: Preparation

From your project root (the directory containing nanocode.py and .env), create a working directory for the game:

An icon indicating this blurb contains information

Aside: We work in a subdirectory to keep the game files together and start with a fresh memory. The copy of nanocode.py is still visible to the agent.

1 mkdir -p snake_game
2 cp nanocode.py snake_game/
3 cp .env snake_game/
4 cd snake_game

If you’re starting from the root of the reader code repository, copy from ch11 instead (create your local .env first):

1 mkdir -p snake_game
2 cp ch11/nanocode.py snake_game/
3 cp .env snake_game/
4 cd snake_game

Install pygame-ce, the maintained Pygame Community Edition used for this example. It supplies the same import pygame namespace.1 Use a fresh environment so the original pygame distribution and pygame-ce do not overwrite each other:

1 python -m venv venv
2 source venv/bin/activate
3 python -m pip install requests==2.34.2 python-dotenv==1.2.4 pytest==9.2.0 \
4   ddgs==9.16.1 pygame-ce==2.5.8

Keep import pygame in snake.py. pygame-ce is a capstone dependency, not part of Nanocode’s core requirements.

An icon indicating this blurb contains information

Aside: The shell commands above are for macOS/Linux. On Windows, use mkdir snake_game, copy nanocode.py snake_game\, and copy .env snake_game\, then activate the environment with venv\Scripts\activate. pygame-ce 2.5.8 provides Python 3.15 wheels for common platforms.2 If pip attempts a source build, check your Python version and platform against the available wheels before installing SDL development packages.

Step 2: The Architect (Plan Mode)

Set a longer command timeout before starting the agent. Pygame’s main loop runs until you close the window, so the default 30 seconds leaves little time to play. The value is in seconds and is inherited by the Nanocode process when it starts. We begin in plan mode because we want a blueprint before we lay bricks.

1 export NANOCODE_TIMEOUT=300
2 python nanocode.py

The Prompt:

1 Build a classic Snake game using Pygame.
2 Include a score counter and Game Over screen with a restart option.
3 Put ALL code in ONE file: snake.py. Write the plan in PLAN.md.

The agent will use write_plan to create PLAN.md. Read it. It should outline the Snake class, the Food class, and the game loop—all in a single file.

If the plan looks reasonable, move on to Step 3.

Step 3: The Builder (Act Mode)

Switch to act mode:

1 /mode act

The Prompt:

1 Implement the plan in snake.py. All code in one file.

Watch the terminal:

1   → Writing snake.py

The agent is generating code based on the context it stored in PLAN.md.

Step 4: The Reality Check

The agent already has the five-minute timeout set before startup. Close the game window when you finish testing so Nanocode can continue. Exporting a different value in another terminal would not change this running agent’s environment.

The Prompt:

1 Run the game with: python snake.py

The agent executes run_command. A window pops up. You play Snake.

An icon indicating this blurb contains a warning

Warning: If the command times out, the game process may still be running even though the tool returned an error. The model can then try another command before you regain the prompt. If it starts repeating launches, interrupt Nanocode, close surviving game windows or stop their processes, and inspect the files before restarting. The timeout does not provide process-tree cleanup.

If it crashes: LLMs are non-deterministic. Your agent might produce a bug on the first try. If the game crashes with an error like AttributeError: 'Snake' object has no attribute 'draw', don’t fix it yourself. Let the agent see the stderr.

The Prompt:

1 The game crashed. Read the error and fix it.

The agent will read the traceback, use read_file to find the bug, use edit_file to patch it, and run it again.

Step 5: The Pivot (Feature Creep)

The game works, but it’s ugly. The snake is just green squares. Let’s stress-test the agent’s ability to refactor.

The Prompt:

1 The game looks boring. Make the snake change color as it eats food, increase speed every 5 points, and search the web for 'cool retro game color palettes' to apply.

The agent should:

  1. Use search_web to find color palettes
  2. Use read_file to understand the current rendering logic
  3. Use edit_file to inject the new features
  4. Run the game to verify

Check the Result

An open window is only the first check. Play through these cases and report each failure to the agent with what you did, what happened, and what you expected:

  • Arrow keys change direction; reversing directly into the snake’s own body is handled consistently.
  • Eating food grows the snake and increments the score once. New food appears on an empty cell.
  • Wall and self collisions end the round and display Game Over.
  • Restart resets the score, snake, food, and speed; repeated restarts still work.
  • After the feature change, food changes the snake’s color and each five-point boundary increases speed once.
  • Closing the window exits cleanly and returns control to Nanocode without a traceback.

The agent cannot see the window. A zero exit code and a passing unit test cannot establish these visual and interactive behaviors for you. Keep a short record of the checks you performed and inspect git diff before accepting the result.

What Goes Wrong

Your results will differ from mine—LLMs are non-deterministic. But here’s what typically happens, and what to watch for.

Common failures on the first run:

  • ModuleNotFoundError: No module named 'pygame' — the agent forgot you need to install it, or it ran the script in a different environment. Run python -m pip install pygame-ce==2.5.8 in the same environment used to launch the game. If both Pygame distributions were installed together, recreate the clean environment above.
  • AttributeError on a method the agent defined but misspelled in the call site. The agent fixes these quickly once it sees the traceback.
  • Off-by-one errors in collision detection. The snake walks through walls or dies one pixel too early. These take 2-3 iterations of edit-run-fix.

The typical session arc:

In my tests, the agent usually gets a working (but ugly) game in 2-4 iterations. The first write produces something that crashes. The second or third fix gets it running. The feature creep step (color changes, speed ramps) adds another 3-5 iterations as the agent reads its own code, makes surgical edits, and verifies each change.

Long sessions may trigger “(Compacting conversation…)” when the measured input-token count exceeds 75% of the brain’s configured context limit. The number of rounds depends on how much text and tool output each round contains; there is no fixed round count. After compaction, the agent loses some detail about early rounds but keeps working. For Ollama, the adapter reads the active served window before each request, as discussed in Chapter 10.

Where the agent struggles:

Pygame’s coordinate system and event loop are tricky. The agent sometimes writes code that renders correctly but doesn’t handle keyboard input properly, or that draws the snake in the wrong order so the head appears behind the body. These are the kind of bugs a human spots instantly but the agent can’t see—it has no visual feedback, only stdout and stderr. If the game runs without errors but looks wrong, you’ll need to describe the visual bug: “The snake renders backwards—the head should be at the front.”

The point isn’t perfection on the first try. It’s that the agent converges—write, run, read the error, fix, repeat—using every tool we built across eleven chapters.

Before you ship the final snake.py, read it. The agent writes code that runs, but a human should still audit for things the test loop can’t catch: hardcoded magic numbers, missing edge cases (what if the window is resized?), and whether the code is structured in a way you’d want to maintain. The agent is a fast drafter, not a final reviewer.

Wrapping Up

Plan, implement, crash, debug, fix, run again—that’s every chapter earning its keep.

So where does it go from here?

Epilogue

The whole thing is about 800 lines of Python. No framework. nanocode.py is yours. Do whatever you want with it:

  • Git integration that auto-commits after green tests
  • Screenshot-based debugging for frontend work (Claude can read images)
  • Voice input via Whisper so you can talk instead of type
  • MCP for connecting to external services your team already uses
  • Sub-agents that fork conversations and work on subtasks in parallel

Production agents like Claude Code, Cursor, and Copilot do more than this: streaming responses (see Appendix A), parallel tool execution, tree-sitter parsing, sandboxed execution environments, multi-file context windows spanning thousands of files. The gap between 800 lines and a large production system is real. But the architecture is the same: a brain, a loop, tools, memory, and a safety harness. Now you know what’s behind the curtain.

The models will keep getting better. The harness—the loop, the tools, the safety checks—that part is engineering. And that part isn’t going anywhere.


  1. https://pyga.me/docs/↩︎

  2. https://pypi.org/project/pygame-ce/2.5.8/#files↩︎