Have you ever wanted to make an AI beat an RPG? I'm one of those people. The game I picked was the Famicom version of Dragon Quest I (hereafter DQ1) — and yes, the Japanese version, just so we're clear. (In North America, this first game was originally released as Dragon Warrior.) In the end, I became one of the many people who have beaten an RPG with the help of an AI.
My goal was for the AI to beat it autonomously. I believed the crucial part was how to balance handing judgment over to the AI. It could make mistakes along the way, it could get lost — that was fine. What I wanted to watch was an AI that experiments and stumbles its way toward a goal, the way a human would.
This is the story of how it actually beat the game autonomously, told mainly for engineers.
What I Pictured at the Start
Puzzles you can't solve without reading their meaning
DQ1 has several signature puzzles. Let me give a few concrete examples.
| Objective | The puzzle | Where the hint comes from |
|---|---|---|
| Getting the Fairy Flute | You must recognize a single tile of water in a town as a hot spring | An old man at the inn in Rimuldar tells you to work out its position relative to the bath in Maira |
| Finding the entrance to Garai's grave | You must pass through an exit that's invisible in the darkness | A soldier east of Radatome Castle tells you that you have to push against the wall in the dark |
| Breaking into the Dragonlord's castle | You must examine the stairs hidden behind the throne to find them | An old man on an island west of Rimuldar tells you a hidden entrance exists — but not where! |
There are more, so if you've played it, try to recall them. Coming back to DQ1 for this project, I realized something: every single puzzle always has a hint prepared for it. I was struck all over again by how well-made this game is.
What these puzzles share is that you must understand the meaning of the hints. Back when I beat it as a kid, there is no way I truly understood those hints and solved them properly! Could an AI understand and crack these puzzles on its own? Just imagining it got me excited.
An AI that gets stuck needs a friend to turn to
I thought it would be fun to build a feature where the AI, when it was truly stuck, could "consult" a human. These days we look up game strategies online, but back when DQ1 came out, you got them by word of mouth. If you were playing games in that era, didn't you ask your siblings and friends all sorts of things about how to get through? It's a game from that time, so surely the AI deserves a friend to help it along too. That friend is me. Imagining the AI coming to me for advice made my heart race.
And what kind of answer would I give? One thing was certain: I would never hand over a direct answer. That would rob the AI of the joy of solving the game itself.
Combat as code — a step beyond the Gambit System
For combat, I planned to have it write dedicated logic in code. Do you know the Gambit System from Final Fantasy XII? Think of this as a step beyond that. I would have the AI write the combat code itself. I couldn't wait to see what kind of code it would come up with.
That was the rough shape of my thinking when I started. I expected it to be a long haul. After two years of trial and error, a new idea — one that grew out of all that experience — changed everything. I rebuilt from scratch around that idea, and in two months my daydream came true.
The Approach I Landed On
From here I'll talk about the method I ultimately used to beat DQ1 autonomously with an AI. I tried many approaches to get here, but those are a different story.
The target is a DQ1 simulator I built myself
First, I owe you a correction. When I said "DQ1," that wasn't quite accurate. To let the AI observe the world of DQ1 and run through its trial and error quickly, I built my own DQ1 simulator. I gathered information from the web and from running the actual game to check its behavior, and coded it up. Of course I leaned hard on coding agents, so I didn't build all of it by hand.
But it is deliberately not a perfect reproduction. For example, I made the following parts easier for the AI:
- I prioritized speed so it could be run over and over, making a full adventure finish in seconds to minutes.
- I added a position-swap mechanism so that a moving NPC blocking the path has no effect.
- I pinned the roaming NPCs behind shop counters to a fixed spot in front of the counter.
- I built a reproducible (seeded) random number system.
- I built save/load-anywhere.
Because I built it myself, I could adapt it flexibly.
Defining "beating it" — have the agent write strategy code
This DQ1 simulator exposes one set of public APIs, for observation and for action. With them, the AI could now "adventure through the world of DQ1 by writing code." So "having an AI beat DQ1 autonomously" came to mean exactly this: having a non-interactive coding agent develop the strategy code that carries the game from its opening all the way to the Dragonlord's defeat.
The coding agent here is, concretely, Claude Code. I call her Ai, our hero — yes, Ai, as in AI. I'll keep calling her that throughout this article.
The Code Ai Wrote
Let me show you a piece of the code Ai actually wrote.
The action API
First, an example of the action API in use.
The important thing here is not the TypeScript itself. Ai isn't merely writing a program — she is going on an adventure! So from here on, read the code less like code and more like you're watching a hero's journey.
01-throne-to-castle.ts
/**
* In the throne room, gather the treasures for the journey, then descend to Radatome Castle.
*
* Start at radatome-throne. Open the three chests beside King Lars XVI for starting supplies,
* then go through the south door (4,7), take the stairs down (8,8), and move to radatome-castle.
* The door blocks the single-tile south exit — the only route down to the castle.
*/
import type { GameClient } from '@agent/client/client.ts';
export const title = 'Throne Room → Radatome Castle';
export default function (client: GameClient): void {
client.section('Treasures for the journey');
client.intent('Open the three chests in the throne room');
client.walkAndOpenChest('chest-30ea8'); // (4,4)
client.walkAndOpenChest('chest-1c37c'); // (5,4)
client.walkAndOpenChest('chest-46e31'); // (6,1)
client.section('Descend to the castle');
client.intent('Open the south door, take the stairs down, to Radatome Castle');
client.walkAndOpenDoor('door-1e311'); // (4,7)
client.walkAndEnter('stairsDown-8aa4b'); // (8,8) → radatome-castle
}
GameClient is the accessor for the simulator's public API. walkAndOpenChest provides a compound action that walks to a chest and takes its contents. A defining trait is that, from the start, the API offers goal-oriented compound actions that make it easy for Ai to express her intent. section and intent are things I asked her to write purely so that I — the spectator and friend — could enjoy watching.
The observation API
Next, an example of the observation API in use.
/**
* This book's checkpoint ledger, in chapter order (the `Checkpoint` type in
* [../../client/checkpoints.ts](../../client/checkpoints.ts)). See "Checkpoints" in [CLAUDE.md](CLAUDE.md) for how to write these.
*
* Only for chapters where a new durable fact appears, add the milestone you reached and verified, in chapter order. Empty array if there are none yet.
*/
import type { Checkpoint } from '@agent/client/checkpoints.ts';
export default [
{
after: '01-throne-to-castle',
description: 'Descended into Radatome Castle',
holds: (c) => c.zonesVisited().includes('radatome-castle'),
},
// ... omitted ...
{
after: '04-buy-equipment',
description: 'Equipped a weapon (club) and armor (cloth armor) at the weapon shop',
holds: (c) => {
const inv = c.self().inventory;
return inv.weapon === 'club' && inv.armor === 'cloth-armor';
},
},
// ... omitted ...
] satisfies Checkpoint[];
zonesVisited is the list of zones she has ever visited. The holds for 01-throne-to-castle is code that declares she managed to move from the starting zone radatome-throne to the next zone radatome-castle.
Next, self is her current state. The holds for 04-buy-equipment is code that declares she equipped the club and the cloth armor.
The Touches I Added to the Public API
There are a few points worth noting about the observation and action APIs.
Show only the interface and its explanatory comments
I cleanly separated the interface-plus-explanatory-comments from the implementation into different directories, and in the prompt I granted access only to the interface-plus-comments directory. This was to shut off any route to picking up clues from the implementation.
If you tell an agent "you must NOT do this," it can be misread and flip into "do THIS!" — so I only spelled out the range it was allowed to look at, and avoided writing prohibitions as much as possible.
Hide the internal IDs; make the public IDs hashes
The internal IDs for NPCs, chests, and so on were implemented such that just looking at them reveals what they are. So I made it a rule to use hashed public IDs separate from the internal ones. Here are some examples of internal versus public IDs.
| Internal ID | Public ID | Description |
|---|---|---|
| soldier-equipment | soldier-90180 | A soldier who talks about equipment |
| throne-chest-gold | chest-30ea8 | A chest containing gold |
Whether checking a situation or taking an action, you need IDs — but a coding agent can read intent straight out of an ID. After someone pointed out that a test-run Ai could infer intent this way, I introduced a scheme that derives a hash from the internal ID and uses it as the public ID. There's no need to erase information you can see on screen in-game, so I kept prefixes like soldier- and chest-. The reason I didn't hash the internal ID itself: it was worth keeping for the benefit of the agent implementing the simulation — a conclusion I reached in discussion with Claude Code.
Save-anywhere, and encrypting it
I provided an API to save anytime, anywhere. Ai could freely load those saves to run experiments and scout ahead whenever she liked. Since save data contains internal IDs and information still unknown to Ai, I encrypted it so she couldn't stumble onto it. I certainly could have restricted this in the prompt instead. But my judgment was that what you can prevent structurally is better prevented structurally.
The Simulation and Memory Systems
Let me briefly explain the information-management systems I placed in the lower simulation layer and the upper agent layer.
The simulation layer — state and pure functions
I made the simulation layer simple and easy to unit-test. The simulation layer is a set of definitions:
- The game state
- Pure functions that define the game's state transitions
Let me show it in code. The game state is a type definition like this:
type SimState = {
zoneId: ZoneId;
heroName: string;
player: Position;
inventory: Inventory;
stats: Stats;
combat: CombatState | null;
rngSeed: number;
// ... omitted ...
};
The set of pure functions defining state transitions is a family of functions like this:
type ActionResult = {
state: SimState;
frame: Frame;
};
function move(state: SimState, direction: Direction): ActionResult;
function openChest(state: SimState): ActionResult;
function attack(state: SimState): ActionResult;
// ... omitted ...
Two memories — action history and scenery
The upper agent layer combines the simulation layer's primitive operations to offer observation and action APIs at an intent-sized granularity. On top of that, it also plays the role of providing a dedicated memory system. I've already given examples of the observation and action APIs, so here I'll explain the dedicated memory system.
The first memory is very simple: the full history of every action taken.
The second is a memory of scenery. The hero treats a 15×15 grid of tiles centered on herself as visible, and remembers, tile by tile, the floor types, NPCs, doors, chests, and the like that she has seen. In short, it's information about the scenery of the places Ai has visited.
Static and dynamic — two means
For Ai, I prepared two kinds of means to reference information: static and dynamic. The static one is a human-readable JSON file that dumps the action history and scenery at the moment of a save. The dynamic one is information access through the observation API. Ai may access this information at any time.
Here I'd like you to recall, once more, that this is a coding agent beating the game. What I prepared was something the coding agent could reach for on its own whenever it needed to. It's not something baked into the system prompt and always on display. The static JSON file could be read directly, or analyzed with Python. The dynamic access could be read via console output, or woven directly into her algorithms. I provide the information about the static JSON format and about the dynamic observation API — but the key point is that I left how to actually use them entirely up to the coding agent.
She seemed to use them for different purposes, roughly like this:
- Static
- Gathering information to decide the next goal
- Tuning the combat algorithm
- Dynamic
- Implementing the exploration algorithm
- e.g. patrol unknown areas of the world map, auto-explore caves
- Implementing the combat algorithm
- e.g. cast Heal when HP/MP drop below a threshold, or head to an inn
The Six Heroes Named Ai
By the time an Ai finally beat the game on its own, I had sent a total of six out on the adventure. The one who pulled it off was the sixth.
Hero #1 — first, prove it can be beaten
The first adventure was a baseline implementation whose goal was to prove that my DQ1 simulation could be beaten at all. Interactively, I laid out the rough route and had her implement it. It was a back-and-forth: go here next, grind around there, buy this equipment, then head over there — implement, run, repeat. Ai wrote code that threw exceptions on anything unexpected, so I basically only had to watch for the processing to succeed. Now and then I'd check the details of the adventure. I'll forgive the inefficient walking — this isn't a speedrun. On the whole, the playthrough progressed with simple instructions. A big factor: since the goal was just to prove it could be beaten, I allowed Ai access to everything, including the simulation's own source and strategy info from the web. From past experience I knew LLMs are weak with 2D maps, but surprisingly, exploration itself gave her no trouble. I only had to tell her where I wanted to go.
I've written about how LLMs behave with 2D maps in these articles, if you're bored:
- 2D Spatial Recognition with Local LLM: Comparing Prompt Strategies
- Not Magic, Just Diligent Thinking — Peeking into LLM Reasoning
Those articles are about local LLMs, but by my observation, even Claude is not strong with 2D maps, for the same reason. Still, it seems Claude Code worked around the weakness by leaning on script-based map analysis — converting the map into a graph structure and the like.
Where it struggled was Garai's grave and the combat in the Dragonlord's castle. In the grave the Knights of the Dead are strong, and in the castle the long trek drains your MP. Maybe because it once lost a damage race to the Starwyvern's ferocious healing, it kept a flee-first stance toward Starwyverns right to the end — which I found memorable.
And so the first adventure ran all the way through, proving what I'd set out to prove: my simulation could be beaten.
From then on, this baseline implementation served as an end-to-end regression test — a full run from the start to the Dragonlord's defeat — and it proved enormously useful.
The reality that 100,000 actions forced on me
The baseline implementation took roughly 100,000 actions to beat. By "actions" I mean the count of the simulation layer's primitive operations — moves, attacks, spells, and the like. That number taught me a hard truth. Earlier I said "having an AI beat DQ1 autonomously" meant "having non-interactive Claude Code develop the strategy code from the opening to the Dragonlord's defeat" — but at this point I held a completely different idea. I'd been imagining "building an MCP that exposes the world of DQ1 and having Claude Code beat it." The 100,000 figure made me realize that approach was unrealistic on both time and cost. On the other hand, I was left with code that could beat the game — built interactively, yes, but working. This was the moment the final direction crystallized.
Heroes #2–4 — lock down the information, go autonomous, and get stuck
The second adventure's main aim was to confirm whether it could beat the game autonomously with information locked down. Using the stretch from Radatome Castle to the town of Garai as material, I watched the broad behavior and tuned prompts and features.
The third adventure went fully autonomous. It reached the town of Garai, but got stuck there. To the west, it couldn't find the path to the mountain cave — visible from Radatome but reachable only by a detour — and to the east, it insisted the land beyond the swamp cave couldn't be explored. By "stuck" I mean a state where the adventure keeps going but makes no progress at all.
After analyzing the third run and strengthening prompts and features, the fourth reached Rimuldar but couldn't find the key shop and got stuck. Rimuldar's key shop hides under a roof. She couldn't take that one step out from under the roof to discover the shop.
Hero #5 — Fable 5, and off it went
Then a savior appeared: Fable 5, Claude's new model, which had just become available. I started an adventure with Fable 5 under the same conditions as the fourth. And what do you know — the adventure surged forward, smoother than I could have believed. Token usage felt like two to three times as much. But the adventure never stalled; it just kept advancing. In about half the time of the fourth run, it breezed through getting the key at Rimuldar's key shop, and then casually picked up the Sun Stone in Radatome and the Fairy Flute in Maira too. It might just beat the whole thing — but right then, through no choice of mine, Fable 5 abruptly became unavailable.
This article is an unofficial, personal project and is not affiliated with, authorized by, or endorsed by Square Enix. "Dragon Quest," "Final Fantasy," and related names are trademarks of Square Enix; they are referred to here only for commentary and description. The cover and illustrations were generated with AI (ChatGPT); the diagrams were drawn by the author.


Top comments (0)