Files
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

132 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.