Opening / Game Loop Overview
At startup the program builds all core objects (I/O, player, combat, maze, encounters, displays, etc.), asks the player for maze size, generates the maze and encounter grid, shows the intro, then loops until you reach the exit. In each loop iteration you move once and the game checks for an encounter; on exit it shows a success message and your story conclusion.

High-level loop ─────────────── User picks dimensions → Maze & encounters generated → Intro shown → while (player not at exit): • Read a move (N/E/S/W) • Update position • Check for encounter (combat or loot) → On exit: print success + show story conclusion/stats
Systems & Data Flow
The game is split into compact systems: Maze Generation, Traversal, Encounters, Combat, Monster Generation,
and Items & Stat Tracking. The Displays
class handles narrative / UI text and summaries.
+------------------+ +--------------------+ +------------------+ | Maze Generation | --> | Maze Traversal | --> | Encounter System | +------------------+ +--------------------+ +------------------+ | | v v +-----------------+ +------------------+ | Player / Stats | <----> | Combat System | +-----------------+ +------------------+ ^ | | v +-----------------+ +------------------+ | Items & Loot | <----> | Monster Gen/Data | +-----------------+ +------------------+
Maze Generation
After reading x & y dimensions from the user, the code computes the “with walls” grid
(x*2+1
by y*2+1
) and calls Maze.create(...)
to carve the labyrinth.
The carved wall matrix is stored in mazeData.walls
.
The creation logic uses a pilot-based carve: when moving the pilot one cell, it also “breaks” the wall between
current and next cell to create a passage (N/E/S/W). You can see how walls are removed while moving in
movePilotPosition
.
A direction is only considered if it’s inside bounds, there is a wall there, and the next cell hasn’t been visited yet.
That check is done in isDirectionPossible
. Completion is verified by scanning the grid for any unvisited cells.

Maze generation flow ──────────────────── Input (x, y) → compute (2x+1, 2y+1) → place pilot at start → while (unvisited cells exist): • collect valid directions (have wall & unvisited cell) • choose direction • break the wall between • move pilot → save walls to mazeData.walls
Traversal (Player Movement)
The game reads a direction (north/east/west/south), prints the narrative line, then updates the player’s
mazePos
by two grid steps (because walls occupy the tiles between cells). Movement is gated by walls
and boundaries.
Move loop (per turn) ──────────────────── • Read direction • Print narration line • Update player.mazePos by ±2 on axis • (Engine ensures walls/edges prevent illegal moves)
Encounter System
When the maze is created, an “encounter grid” of the same size is generated. Each open cell gets a chance (percentage) to be marked as an encounter; the exit is marked specially.
Every step, the game calls Encounter.check(...)
: if the player’s current cell is flagged,
the cell is cleared and a random scenario is triggered (loot or combat). A move counter increments as well.
Scenario selection: with a specified loot probability, the engine branches into the loot flow; otherwise, it spins up combat with monsters.
Encounter check ─────────────── On entering a cell: • If encounters[x][y] == 1 → clear it • RNG: - ≤ lootChance → Loot - else → Combat • Increment moves-taken metric
Combat System (Turn-Based)
Combat is turn-based. The Displays
class shows the player’s stats, then prompts for an action:
Attack, Cast Spell, Move, Consume Potion, or End Turn. After the player acts, monsters act according to speed
and status; combat repeats until one side is defeated.
The UI also prints monster stats (HP, damage dice, speed) before you make choices, and exposes helper displays for showing positions in grid-based combat.
Combat round ──────────── 1) Displays: player stats, learned spells, potions 2) Player chooses an action (attack/spell/move/potion/end) 3) Apply effects (HP/MP, positions, status) 4) Monsters take their actions 5) Repeat until HP ≤ 0 for a side → resolve (loot/xp/story)
Monster Generation / Casting
Enemies are stored in a large “cast” on GameData
, and the engine tracks which are “living”
in the current fight. This gives you a pool to pull randomized enemies from as encounters trigger.
Each monster has battle stats (HP, attack dice, speed, etc.) printed by the display helpers; the actual combat layer consumes these values to resolve damage and turns.
Monster pipeline ──────────────── Encounter triggers → pick template from GameData.enemiesCast → randomize/assign stats & positions → push into living[] → Combat loop consumes those stats each round
Items, Loot & Stat Tracking
Loot is controlled by a tunable chance (e.g., 60%). If a loot scenario is chosen, the
Loot
system grants items (potions, etc.) that mutate the Player
’s stats/state.
The loot chance and enemy/kill bookkeeping live on GameData
.
The Displays
class prints a final summary at the end (totals for movement, melee attacks,
spells, potions, and monsters killed).
Tracking / Summary ────────────────── • Player: HP, MP, learned spells, potions, combat counters • GameData: lootChance, enemiesCast[], living[], kill counts • Displays: per-turn & end-of-run summaries
Win Condition
The main loop continues until your player.mazePos
matches the maze exit coordinates.
When that happens, the game prints a success message and displays the concluding story screen.
while (player not at exit): move → check encounter → On exit → "You have successfully escaped the labyrinth" + story conclusion