Files
Binding-Fignyaac/docs/ARCHITECTURE.md
T
Volodia b947fbb7fb initial: dungeon crawler with Bun + TypeScript modular architecture
- 19 TypeScript modules under src/ (constants, entities, room, game, render)
- Canvas 2D rendering with dark fantasy palette
- Room-based navigation, random 7x7 map generation
- Two combat modes: ranged (pistol) and melee (knife)
- Wall collision with door opening support
- Minimap, HP bar, enemy AI
- Bun build pipeline: src/main.ts -> dist/main.js + dist/index.html
2026-06-18 12:55:17 +03:00

5.1 KiB
Raw Blame History

Architecture

Overview

The game is a single-page Canvas 2D application. The source is written in TypeScript and split into ~19 modules under src/, bundled by Bun into a single dist/main.js. The HTML shell in dist/index.html loads the bundle.

Module Dependency Graph

main.ts
  ├── input.ts           (global KEYS, setupInput)
  └── Game.ts
        ├── constants.ts (all shared numeric constants)
        ├── types.ts     (Dir, RoomType, CombatMode, Box, Doors)
        ├── math.ts      (shuffle, rand, ri, dist, overlap)
        ├── RoomMap.ts   → Room.ts, tiles.ts, doors.ts
        ├── Player.ts
        ├── Enemy.ts
        ├── Tear.ts
        ├── MeleeSwing.ts
        ├── collision.ts → Room.ts, tiles.ts
        ├── transitions.ts → Game.ts (type only), input.ts
        ├── spawner.ts   → Room.ts, Enemy.ts
        └── render/*
              → roomRenderer.ts, entityRenderer.ts,
                hudRenderer.ts, minimapRenderer.ts

Game Loop

Every frame (requestAnimationFrame):

  1. Timers — decrement invTimer, atkCD, transCD
  2. Movement — read WASD, apply velocity, resolve wall collisions
  3. Attack — read arrow keys / Space, fire tear or create melee swing
  4. Melee Update — decrement swing life, apply damage + knockback
  5. Tear Update — move projectiles, check wall/enemy collisions
  6. Enemy AI — chase player, contact damage with cooldown
  7. Room Clear — if all enemies dead → room.cleared = true, rebuild tiles with doors
  8. Transition Check — if player on door tile + key pressed → load adjacent room
  9. Win Check — if boss room cleared → won = true
  10. Render — draw room tiles → entities → HUD → minimap → overlays

Transition System

Room transitions are the most complex subsystem. Key design:

  • Door geometry: DOOR constant defines 3-tile-wide openings at each cardinal edge (top: cols 6-8, row 0; bottom: cols 6-8, row 10; left: rows 4-6, col 0; right: rows 4-6, col 14).
  • Detection: checkTransition() computes the player's tile position (col, row). If they're on a door tile AND pressing the matching movement key, the transition fires.
  • Cooldown: transCD = 15 prevents re-entry within 15 frames.
  • Movement lock: Transition only works in cleared rooms (room.cleared === true).
  • Collision bypass: isBlocked() returns false for out-of-bounds tiles at door openings, letting the player's bounding box extend beyond the room boundary.

Collision System

  • isBlocked(room, col, row) — per-tile check. Returns true for wall tiles and out-of-bounds positions, except at door openings.
  • collidesWall(box, room, ox, oy) — iterates all tiles covered by the entity's bounding box. If any tile is blocked, returns true.
  • Movement resolves axis-independently: apply X, revert if collision; apply Y, revert if collision.

Room Generation

RoomMap.generate() uses a random walk:

  1. Start at (0,0) with a spawn room.
  2. Maintain a frontier list of rooms that have room to expand.
  3. Each step, pick a random frontier room, shuffle directions, and attempt to add a new room in an unoccupied adjacent cell within the 7×7 grid.
  4. Room types are assigned probabilistically (boss at ~20%, treasure at ~12%, rest normal).
  5. If no boss room was generated, one normal room is promoted.

Rendering

Rendering is split into 4 stateless functions, each taking CanvasRenderingContext2D as the first argument:

  • roomRenderer — tile loop with wall/door/floor styles
  • entityRenderer — enemies (3 types), player (body + weapon), tears, melee swing arc
  • hudRenderer — HP bar, mode indicator, enemy count, room label
  • minimapRenderer — 7×7 grid with visited/current room highlights

Weapon Visuals

  • Pistol (ranged): Barrel line + body rect, rotated toward facing direction. Muzzle flash circle at atkCD > 8.
  • Knife (melee): Triangular blade + handle + guard rects, offset in facing direction.

Enemy Types

Type Size HP Speed Damage Visual
Normal 32px 3 1.15 1 Brown block, yellow eyes
Fast 26px 2 1.9 1 Red circle, small eyes
Boss 46px 10 0.9 2 Large red circle, horns, HP bar

State Management

  • GAME_OVER / WON — boolean flags checked in loop and render.
  • KEYS — global mutable map, reset on window blur.
  • Room state (cleared, visited, enemies[], tears[]) — per-room.
  • Player state (hp, mode, facing, transCD, etc.) — single Player instance.
  • Cooldowns (invTimer, atkCD, transCD) decrement each frame.

Build Pipeline

bun build src/main.ts --outdir dist --target browser --minify
cp src/index.html dist/
  • Bundler: Bun's native bundler (esbuild under the hood).
  • Target: browser (ES module → IIFE/global wrapper).
  • Result: single dist/main.js (~30 KB) + dist/index.html.
  • Open dist/index.html directly in any modern browser.