Overview

When I presented Kroz at the AWS Summit 2024 in Milan, it was a fascinating but simple prototype: a text adventure dynamically generated by AWS Bedrock, with procedural rooms and basic game logic. The demo worked, people were intrigued, but I knew it was just the beginning. The game had potential, but it lacked depth, coherence, and the kind of carefully crafted experience that makes players want to keep exploring.

Fast forward several months, and Kroz has evolved into something completely different. It’s now a complete adventure spanning 8 chapters, featuring a sophisticated combat system, complex puzzle mechanics, and a coherent storyline that takes players from an abandoned manor through dark forests and ancient ruins, all the way to Montezuma’s legendary treasure chamber.

This article chronicles the technical evolution of the game: how I transitioned from on-the-fly Bedrock-generated content to a structured JSON database, how I implemented intricate combat and status effect systems, and how every single modification was manually designed and then implemented with the help of Kiro Pro, AWS’s CLI assistant.


๐ŸŽฎ Phase 1: The Bedrock Prototype

The Original Architecture

In its first incarnation, Kroz was essentially an intelligent wrapper around AWS Bedrock. The architecture was straightforward but effectiveโ€”a serverless pipeline that transformed player commands into AI-generated narrative responses.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        AWS Cloud                                โ”‚
โ”‚                                                                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
โ”‚  โ”‚  CloudFront  โ”‚โ”€โ”€โ”€โ–ถโ”‚      S3      โ”‚    โ”‚  API Gateway โ”‚       โ”‚
โ”‚  โ”‚   + Static   โ”‚    โ”‚   Frontend   โ”‚โ—€โ”€โ”€โ”€โ”‚              โ”‚       โ”‚
โ”‚  โ”‚    Assets    โ”‚    โ”‚              โ”‚    โ”‚              โ”‚       โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
โ”‚                                                  โ”‚              โ”‚
โ”‚                                          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
โ”‚                                          โ”‚    Lambda    โ”‚       โ”‚
โ”‚                                          โ”‚ Game Handler โ”‚       โ”‚
โ”‚                                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
โ”‚                                                  โ”‚              โ”‚
โ”‚                                          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
โ”‚                                          โ”‚   Bedrock    โ”‚       โ”‚
โ”‚                                          โ”‚ Titan/Claude โ”‚       โ”‚
โ”‚                                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Every action the player took triggered a chain of events: the command would be sent to Lambda, packaged into a carefully crafted prompt, dispatched to Bedrock, and returned as a JSON structure describing the next room, its contents, and available exits.

The beauty of this approach was its simplicity. There was no database to maintain, no content to curate, no complex state management. The AI handled everything, generating unique experiences on the fly. But as I would soon discover, this simplicity came with significant trade-offs.

The Master Prompt

The heart of the system was a sophisticated prompt that instructed Bedrock on how to generate coherent content. It wasn’t just about asking for a room descriptionโ€”it was about maintaining context, ensuring consistency, and creating a sense of progression.

const prompt = `Generate a JSON structure for a text adventure game room.
Context: ${previousRooms}
Player action: ${command}

Return JSON with:
{
  "title": "Room name",
  "description": "Detailed description",
  "items": ["item1", "item2"],
  "exits": {"north": true, "south": false}
}`;

This approach had clear advantages. The content was theoretically infiniteโ€”every playthrough could be unique. There was no database to maintain, no content pipeline to manage. The AI adapted to player actions in real-time, creating a sense of dynamic storytelling that felt almost magical when it worked well.

But the limitations became apparent quickly:

  • Narrative coherence was difficult to maintain across multiple rooms. The AI might generate a dark forest in one room and a futuristic spaceship in the next, with no logical connection between them.
  • Each API call to Bedrock cost money and introduced latencyโ€”players had to wait 2-3 seconds for each action to resolve.
  • Complex puzzles were impossible to design. The AI simply couldn’t maintain that level of state consistency.

๐Ÿ”„ Phase 2: The Turning Point

Why Change the Approach

After the Summit, I spent time reflecting on what makes text adventures truly engaging. The classics of the genre weren’t procedurally generatedโ€”they were meticulously crafted experiences where every room, every object, every puzzle was deliberately placed to serve the overall narrative.

I realized that Bedrock’s strengthโ€”its ability to generate endless variationsโ€”was actually working against the core experience I wanted to create. What players needed wasn’t infinite content; they needed meaningful content. They needed puzzles that made sense, narratives that built to satisfying conclusions, and a world that felt coherent and intentional.

The decision crystallized: use Bedrock as a development tool to generate initial content drafts, but then curate every element manually. Build a structured JSON database that could be loaded instantly, with no API calls, no latency, and complete control over every aspect of the experience.

The New Architecture

The new architecture represented a complete philosophical shift. Instead of generating content on-demand, everything would be pre-defined in a comprehensive JSON database. The frontend would load this database once at startup, and all game logic would run entirely client-side.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Frontend (Vue.js)                            โ”‚
โ”‚                                                                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚                    GameView.vue                          โ”‚   โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚   โ”‚
โ”‚  โ”‚  โ”‚  Game State Management                             โ”‚  โ”‚   โ”‚
โ”‚  โ”‚  โ”‚  - Current Chapter/Room                            โ”‚  โ”‚   โ”‚
โ”‚  โ”‚  โ”‚  - Player HP/Status                                โ”‚  โ”‚   โ”‚
โ”‚  โ”‚  โ”‚  - Inventory                                       โ”‚  โ”‚   โ”‚
โ”‚  โ”‚  โ”‚  - Combat State                                    โ”‚  โ”‚   โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                              โ”‚                                  โ”‚
โ”‚                              โ–ผ                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚              Game Logic (logic.js)                       โ”‚   โ”‚
โ”‚  โ”‚  - Command Processing                                    โ”‚   โ”‚
โ”‚  โ”‚  - Combat System                                         โ”‚   โ”‚
โ”‚  โ”‚  - Status Effects                                        โ”‚   โ”‚
โ”‚  โ”‚  - Movement & Locks                                      โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                              โ”‚                                  โ”‚
โ”‚                              โ–ผ                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚            Utilities (utils.js)                          โ”‚   โ”‚
โ”‚  โ”‚  - JSON Database Loader                                  โ”‚   โ”‚
โ”‚  โ”‚  - Save/Load (localStorage)                              โ”‚   โ”‚
โ”‚  โ”‚  - Text Rendering (Typed.js)                             โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                              โ”‚                                  โ”‚
โ”‚                              โ–ผ                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚          Game Database (game.json)                       โ”‚   โ”‚
โ”‚  โ”‚  - 8 Chapters                                            โ”‚   โ”‚
โ”‚  โ”‚  - ~60 Rooms                                             โ”‚   โ”‚
โ”‚  โ”‚  - 20+ Enemies                                           โ”‚   โ”‚
โ”‚  โ”‚  - 30+ Items                                             โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

No cloud calls during gameplay, no latency, no costs per actionโ€”just pure, instant interaction. This architecture meant that once the page loaded, the entire game ran locally in the browser. The game could even work offline.


๐ŸŽฏ The Evolution of Game Systems

1. Movement System: From Simple to Complex

The original Bedrock version had a rudimentary movement systemโ€”just four cardinal directions with boolean flags. Real adventure games have vertical movement, diagonal directions, and most importantly, meaningful obstacles that require specific items or actions to overcome.

Before (Bedrock):

// Basic movement: 4 directions
const moves = {
  north: true,
  south: false,
  east: true,
  west: false
};

After (Structured):

// Complex movement with room IDs and multiple directions
"moves": {
  "north": "2",
  "up": "3",
  "northeast": "5",
  "down": "secret_passage"
},
"locks": {
  "north": {
    "type": "key",
    "required_items": ["brass_key"],
    "error": "The door is locked. You need a brass-colored key."
  },
  "down": {
    "type": "mechanism",
    "required_items": ["silver_lever"],
    "error": "There's a mechanism here but it's missing a lever."
  }
}

The new system supports ten directions (including vertical and diagonal movement), multiple lock types (keys, mechanisms, combinations), and a sophisticated item identification system where each key has unique visual characteristicsโ€”shape, color, materialโ€”that match specific locks.

2. Combat System: From Nothing to Complete

The original prototype had no combat at all. It was purely exploration-based. But as the game evolved, it became clear that combat would add crucial tension and variety to the experience.

I drew inspiration from tabletop role-playing games, where combat is turn-based and strategic. Each enemy needed distinct characteristicsโ€”not just hit points and damage, but attack patterns, special abilities, and varying difficulty levels.

// Enemy structure
{
  "id": "ancient_guardian",
  "name": "Ancient Guardian",
  "hp": 120,
  "damage": 25,
  "attacks": ["crushing_blow", "stone_fist"],
  "status_effects": {
    "stunned": {
      "chance": 0.3,
      "duration": 2,
      "description": "You are stunned!"
    }
  },
  "flee_chance": 0.0  // Boss: cannot flee
}

Combat Flow:

                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚ Enemy Appears   โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  Player Turn    โ”‚
                    โ”‚ attack/defend/  โ”‚
                    โ”‚     flee        โ”‚
                    โ””โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜
                      โ”‚     โ”‚    โ”‚
         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
         โ”‚                  โ”‚               โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”
    โ”‚ Attack   โ”‚    โ”‚   Defend     โ”‚  โ”‚   Flee   โ”‚
    โ”‚ Calculateโ”‚    โ”‚ Reduce Dmg   โ”‚  โ”‚  Check   โ”‚
    โ”‚  Damage  โ”‚    โ”‚    50%       โ”‚  โ”‚          โ”‚
    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”ฌโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜
         โ”‚                  โ”‚            โ”‚    โ”‚
         โ”‚ enemy HP = 0     โ”‚            โ”‚    โ”‚ fail
         โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚            โ”‚    โ”‚
         โ”‚          โ”‚       โ”‚            โ”‚    โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”   โ”‚  โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚    โ”‚
    โ”‚ Victory  โ”‚   โ”‚  โ”‚ Enemy Turn  โ”‚โ—„โ”€โ”€โ”˜    โ”‚
    โ”‚          โ”‚   โ”‚  โ”‚ Attack      โ”‚        โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚  โ”‚  Player     โ”‚        โ”‚
                   โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚
                   โ”‚         โ”‚               โ”‚
                   โ”‚    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”‚
                   โ”‚    โ”‚Check Status โ”‚      โ”‚
                   โ”‚    โ”‚Poison/Para  โ”‚      โ”‚
                   โ”‚    โ””โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜      โ”‚
                   โ”‚       โ”‚      โ”‚          โ”‚
                   โ”‚  HP=0 โ”‚      โ”‚ continue โ”‚
                   โ”‚       โ”‚      โ”‚          โ”‚
              โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”   โ”‚  โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
              โ”‚Victory โ”‚   โ”‚  โ”‚Continue  โ”‚  โ”‚
              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚  โ”‚Playing   โ”‚  โ”‚
                           โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
                      โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”          โ”‚
                      โ”‚Game Over โ”‚          โ”‚
                      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ”‚
                                            โ”‚
                                       โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”
                                       โ”‚ Escaped  โ”‚
                                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Every mechanic was first designed on paper, then implemented with Kiro Pro’s assistance. The workflow was iterative: I would describe the desired behavior in natural language, Kiro would generate the code, and we’d refine it together until it worked perfectly.

// Manually designed, implemented with Kiro Pro
export function handleAttack(data, weapon) {
  const enemy = data.actualRoom.enemies[0];
  if (!enemy) return;
  
  const weaponData = utils.getItemData(data.chapter, weapon);
  if (!weaponData || weaponData.type !== 'weapon') {
    utils.updateTypedBox(data.errorBox, [
      utils.prefix("That's not a valid weapon!")
    ]);
    return;
  }
  
  // Damage calculation: base weapon damage + random variance
  const baseDamage = weaponData.damage;
  const variance = Math.floor(Math.random() * 5) - 2;
  const totalDamage = Math.max(1, baseDamage + variance);
  
  enemy.hp -= totalDamage;
  
  utils.updateTypedBox(data.infoBox, [
    utils.prefix(`You attack with ${weapon} for ${totalDamage} damage!`)
  ]);
  
  if (enemy.hp <= 0) {
    handleVictory(data, enemy);
  } else {
    enemyTurn(data, enemy);
  }
}

The damage calculation includes a random variance to keep combat unpredictable. A weapon with 15 base damage might deal anywhere from 13 to 17 damage on any given hit. This small randomness adds tensionโ€”you can’t always predict whether you’ll defeat an enemy before they get another attack in.

3. HP and Status Effects System

Health management adds another layer of strategy to the game. Players start with 100 HP and must carefully manage this resource throughout their adventure. Healing items are scattered throughout the world, but they’re limitedโ€”you can’t just spam potions to survive every encounter.

Status effects make combat more interesting by introducing conditions that persist beyond individual turns:

  • Poison deals damage over time, forcing players to find antidotes or risk slowly dying
  • Paralysis randomly prevents movement, creating tense moments where you might be stuck in place while an enemy attacks
  • Stun causes you to stumble in random directions, potentially leading you into more danger
export function processStatusEffects(data) {
  if (data.player.status !== 'normal' && data.player.status_duration > 0) {
    data.player.status_duration--;
    
    switch(data.player.status) {
      case 'poisoned':
        const damage = 3;
        data.player.hp = Math.max(0, data.player.hp - damage);
        utils.updateTypedBox(data.infoBox, [
          utils.prefix(`You take ${damage} poison damage! HP: ${data.player.hp}/${data.player.max_hp}`)
        ]);
        break;
        
      case 'paralyzed':
        if (Math.random() < 0.33) {
          utils.updateTypedBox(data.errorBox, [
            utils.prefix("You are paralyzed and can't move!")
          ]);
          data.actionAllowed = false;
          return;
        }
        break;
        
      case 'stunned':
        if (Math.random() < 0.5) {
          const randomDir = ['north', 'south', 'east', 'west'][Math.floor(Math.random() * 4)];
          data.command = randomDir;
          utils.updateTypedBox(data.infoBox, [
            utils.prefix("You stumble in a random direction!")
          ]);
        }
        break;
    }
    
    if (data.player.status_duration === 0) {
      data.player.status = 'normal';
      utils.updateTypedBox(data.infoBox, [
        utils.prefix("Status effect wore off.")
      ]);
    }
  }
}

4. Searchable Objects System

One of the most appreciated additions was the searchable objects system. Instead of having all items immediately visible in a room, some are hidden inside containers or behind objects that players must explicitly examine.

A room might mention a desk in the corner or a wardrobe against the wall. These aren’t just flavor textโ€”they’re interactive elements that can be searched for hidden items. This creates a more immersive experience where players feel like they’re actively investigating their environment.

// Room definition with searchable objects
"searchable": {
  "table": {
    "description": "An old wooden table with a drawer.",
    "items": ["bandage", "old_map_fragment"]
  },
  "wardrobe": {
    "description": "An old wardrobe with clothes.",
    "items": ["healing_potion"]
  }
}

๐Ÿ“– The Storyline: 8 Coherent Chapters

One of the biggest challenges was creating a coherent narrative across 8 chapters. Each chapter needed its own identity while contributing to the overall arc. The solution was to design each chapter around a distinct environmental theme and gradually increase the difficulty.

The narrative follows a treasure hunter’s quest for Montezuma’s legendary treasure. Each chapter represents a stage in this journey, with the player collecting map fragments that gradually reveal the treasure’s location.

Chapter Structure:

Chapter 1          Chapter 2         Chapter 3         Chapter 4
Abandoned    โ”€โ”€โ”€โ–ถ  Dark        โ”€โ”€โ”€โ–ถ  Mountain    โ”€โ”€โ”€โ–ถ  Ancient
Manor              Forest            Pass              Ruins
                                                          โ”‚
                                                          โ–ผ
Chapter 8          Chapter 7         Chapter 6         Chapter 5
Treasure     โ—€โ”€โ”€โ”€  Ancient     โ—€โ”€โ”€โ”€  Cursed      โ—€โ”€โ”€โ”€  The
Chamber            Crypt             Cemetery          Pyramid

Each chapter was designed with:

  1. Environmental Theme: Distinct atmosphere and setting that feels different from previous chapters
  2. Difficulty Progression: Stronger enemies, more complex puzzles, higher stakes
  3. Narrative Arc: Story elements that connect to the overall quest while providing chapter-specific intrigue

๐Ÿ—บ๏ธ Visualization: Maps and Documentation

For each chapter, I generated visual maps using Graphviz. This proved essential for several reasons. First, it helped me design the room layouts before implementing themโ€”I could see at a glance whether the spatial relationships made sense. Second, it served as documentation, making it easy to verify that all connections were correct.

The DOT format is particularly well-suited for this because it’s text-based and version-controllable, yet can be rendered into professional-looking diagrams. Each room becomes a node, each connection an edge, and special attributes (locked doors, enemies, items) can be annotated directly in the graph.

Example DOT Graph:

digraph ExampleChapter {
  rankdir=TB;
  node [shape=box, style="rounded,filled", fillcolor=lightblue];
  edge [fontsize=10];
  
  // Rooms
  entrance [label="Entrance Hall\n[torch]"];
  corridor [label="Long Corridor\n[key]"];
  chamber [label="Main Chamber\n[sword]\nEnemy: Guard"];
  treasury [label="Treasury\n[END]", fillcolor=lightgreen];
  
  // Connections
  entrance -> corridor [label="north\n(no lock)"];
  corridor -> chamber [label="east\n(key required)"];
  chamber -> treasury [label="south\n(defeat guard)"];
  
  // Legend
  legend [shape=note, label="Example Chapter\n4 rooms | 1 enemy | 1 lock"];
}

This graph is then converted to PNG using:

dot -Tpng example.dot -o example.png

The resulting images serve as both design documents and player guides, showing the complete structure of each chapter at a glance.

Simplified ASCII Representation:

                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  Dusty Foyer    โ”‚
                    โ”‚ [bandage table] โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚      โ”‚
              north      โ”‚      โ”‚ up
           (brass_key)   โ”‚      โ”‚
                         โ”‚      โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚ Dark Hallway    โ”‚  Upper Landing
                    โ”‚ [rusty_dagger]  โ”‚  [brass_key]
                    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚
                    east โ”‚
                         โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚    Library      โ”‚
                    โ”‚ [chocolate_bar] โ”‚
                    โ”‚ Enemy: Rat      โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚ Master Bedroom  โ”‚
                    โ”‚ [silver_lever]  โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚
                    down โ”‚ (silver_lever)
                         โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚ Secret Passage  โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚
                    down โ”‚
                         โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚     Garden      โ”‚
                    โ”‚      [END]      โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The DOT format is particularly well-suited for this because it’s text-based and version-controllable, yet can be rendered into professional-looking diagrams.


๐Ÿค– The Role of Kiro Pro

Every feature was manually designed, but the implementation was dramatically accelerated by Kiro Pro. The typical workflow looked like this:

  1. Design on paper: Sketch out the system (e.g., combat mechanics)
  2. Pseudocode: Write the logic in pseudocode or detailed natural language
  3. Kiro Pro: Ask Kiro to implement the JavaScript code
  4. Testing: Verify the functionality works as intended
  5. Iteration: Refine with Kiro until it’s perfect

This hybrid approach proved incredibly effective. I maintained complete creative control over what the game should do, while Kiro handled the tedious work of translating those ideas into working code.

Example interaction:

> Kiro, implement a function that handles status effects. The player can be 
> poisoned (3 dmg/turn for 4-6 turns), paralyzed (33% chance of being unable 
> to move for 2-3 turns), or stunned (50% chance of moving in a random 
> direction for 2 turns). Duration should decrement each turn and when it 
> reaches 0, status returns to normal.

Kiro would generate the code, I’d test it, and we’d iterate until it was perfect. This reduced development time from weeks to days, allowing me to focus on game design rather than syntax and debugging.


๐Ÿ’พ Save System and Persistence

The save/load system uses browser localStorage to maintain game state. This was a deliberate choiceโ€”it keeps the game entirely client-side with no server dependencies, while still allowing players to save their progress and return later.

export function saveGame(state) {
  try {
    localStorage.setItem('kroz_save', JSON.stringify({
      version: '2.0',
      timestamp: Date.now(),
      chapterNum: state.chapterNum,
      roomId: state.roomId,
      inventory: state.inventory,
      player: state.player,
      moveHistory: state.moveHistory
    }));
    return true;
  } catch (e) {
    console.error('Save failed:', e);
    return false;
  }
}

๐Ÿ“Š Performance and Optimizations

The transition from Bedrock to local JSON brought dramatic improvements across every metric that matters:

MetricBedrock (Before)JSON (After)
Command Latency2-3 seconds<50ms
Cost per Playthrough$0.10-0.50$0.00
Narrative CoherenceVariable100%
Design ControlLimitedTotal
Offline PlayNoYes

The performance improvement alone would have justified the change, but the real win was in design control and narrative coherence.


๐ŸŽฎ What We Built

Looking back at what started as a simple Bedrock prototype, the transformation is remarkable. The game now spans 8 complete chapters, each one a self-contained adventure that contributes to the larger narrative. We’re talking about roughly 60 meticulously designed roomsโ€”not procedurally generated, but hand-crafted with specific purposes and connections.

The combat system alone represents a massive leap forward. There are over 20 unique enemy types, each with their own attack patterns and behaviors. Weapons range from a rusty 8-damage dagger you find in the first chapter to a legendary 35-damage sword hidden in the final chamber. Four boss encounters punctuate the journey, each requiring different strategies and offering no option to fleeโ€”you either defeat them or reload your save.

The item system grew organically as the game evolved. Beyond the 12 weapons, there are healing items, keys with unique visual characteristics (brass, crystal, scarab-shaped), cure items for status effects, and searchable objects hidden throughout the world. Speaking of which, the searchable objects systemโ€”tables, chests, wardrobes, altarsโ€”adds about 15 different interactive elements that reward thorough exploration.

Movement evolved from simple four-direction navigation to a full ten-direction system including vertical movement (up/down) and diagonals. This opened up entirely new puzzle possibilitiesโ€”secret passages accessed by climbing, hidden chambers reached by descending through wells, treetop platforms that provide different perspectives on the world below.

The status effect system adds tactical depth with three distinct conditions: poison that slowly drains your health, paralysis that randomly prevents movement, and stun that causes you to stumble in random directions. Each one creates different challenges and requires different solutions.

All of this is packaged in a structured JSON database of 1005 linesโ€”every room, every enemy, every item, every connection documented and version-controlled. It’s the difference between a tech demo and a complete game, between procedural chaos and intentional design.


๐ŸŽฏ Conclusions

The evolution of Kroz demonstrates that generative AI like Bedrock is fantastic for rapid prototyping and exploring ideas, but creating a quality game experience requires intentional design and manual curation of details.

The hybrid workflow I adoptedโ€”human design combined with AI-assisted implementation through Kiro Proโ€”proved to be the best of both worlds:

  • Human creativity drives the narrative and game design decisions
  • AI speed handles the code implementation
  • Complete control over the final product

Bedrock remains in the project as a development tool for generating content drafts and exploring possibilities, but the final game is completely structured and local. This ensures:

  • Zero latency during gameplay
  • Zero costs for players
  • Consistent experience that’s reproducible
  • Offline play capability
  • Complete creative control over every detail

The game is available at kroz.madeddu.xyz and the source code is open source. Every line of code, every room, every enemy was designed to contribute to a memorable gaming experience.

From an AI-generated prototype to a hand-crafted complete adventure: this is Kroz’s journey. And the journey continues.


Kroz - Text Adventure Game Designed by hand, implemented with Kiro Pro