Files
nntv/docs/codebase-summary.md
T
tiennm99 97a0a747a7 refactor: address code review — capture/apply contract, tests, docs
- Guards: add capture()/apply() dynamic-state snapshot contract; removes fragile per-type field lists in previewNextTurn and game-history
- TurnManager.previewNextTurn: use guard.capture/apply so new dynamic fields are picked up automatically
- PrincessMechanic: add capture()/apply() and include messageShown in undo snapshots
- Game.svelte: navigate to MainMenu when loadLevel returns null; track detection setTimeout for unmount cleanup
- LevelSelect: use getTotalLevels() instead of hardcoded 12
- audio.js: swallow resume() promise rejections
- art-scenes: norm() pads short rows with '.' transparent instead of last-char bleed
- Rename controls-overlay.svelte → ControlsOverlay.svelte (match PascalCase convention)
- Add vitest + tests for grid, player, guards, princess, turn-manager, game-history (46 tests)
- Update docs (codebase-summary, system-architecture, code-standards, README) for pixel pipeline and capture/apply contract
2026-04-20 22:22:03 +07:00

237 lines
8.4 KiB
Markdown
Raw 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.
# Codebase Summary - Night Ninja: Twilight Voyage
## Overview
NNTV is a turn-based stealth puzzle game built with Svelte 5 and Vite 6.x. The codebase separates pure JS game engine (classes in `lib/game/`) from Svelte rendering (scenes + components). State flows one-way: game classes mutate internally, then `renderVersion++` triggers Svelte re-derivation.
## Module Inventory
### Entry Point
- **src/main.js** — Mounts `App.svelte` into `#app`
### Scene Router
- **src/App.svelte** — `{#key currentScene}` with `transition:fade`, passes `navigate` function as prop to all scenes
### Scenes (src/scenes/)
| File | Purpose |
|------|---------|
| MainMenu.svelte | Start game, level select, settings, guide |
| StoryIntro.svelte | Scrolling narrative with skip button |
| LevelIntro.svelte | Level name + story text + continue |
| LevelSelect.svelte | 4x3 grid of level buttons (locked/unlocked/completed) |
| Game.svelte | Main gameplay: state owner, input, turn loop, rendering |
| GameOver.svelte | Loss/twist screen, retry/menu |
| Settings.svelte | Language toggle (EN/VI) |
| Guide.svelte | Rules, controls, enemy types |
### Components (src/components/)
| File | Purpose |
|------|---------|
| Button.svelte | Reusable styled button |
| GameBoard.svelte | CSS grid of cells, each rendering a pixel-art tile |
| GameHud.svelte | Level, lives (pixel hearts), turns, pixel-icon action buttons |
| PlayerSprite.svelte | Pixel-art ninja rabbit sprite |
| GuardSprite.svelte | Pixel-art veggie sprite dispatched by guard.type + direction indicator |
| DetectionPopup.svelte | "Detected!" overlay with retry |
| LevelCompletePopup.svelte | Star rating, best moves, next-level action |
| PauseMenu.svelte | Resume / restart / main menu |
| ControlsOverlay.svelte | Keyboard / tap / swipe reference overlay |
### Game Engine (src/lib/game/) — Pure JS, no Svelte
| File | Purpose |
|------|---------|
| grid-system.js | GridSystem class: cell state, walls, goals, lighting |
| player.js | Player class: position, movement validation |
| guards.js | Guard base + 6 subclasses (Static, Rotating, Blinking, Mirror, Patrolling, Chaser) |
| turn-manager.js | TurnManager: turn cycle, guard updates, detection |
| level-manager.js | loadLevel(): GUARD_REGISTRY factory pattern for guard instantiation |
| game-history.js | GameHistory class: undo/redo snapshots (Z/Y keys) |
| princess-mechanic.js | Princess detection logic: escalating light rings on level 12 |
| touch-controls.js | TouchControls class: swipe gesture detection for mobile |
### Level Data (src/lib/levels/)
| File | Purpose |
|------|---------|
| levels.js | LEVELS array: 12 level definitions (grid, guards, walls, goals) |
### Pixel-Art Pipeline (src/lib/pixel/)
| File | Purpose |
|------|---------|
| Pixel.svelte | SVG renderer: string-art + palette → run-length-merged `<rect>`s |
| palette.js | NNTV color constants (mirrors theme.css guard colors; extended atmosphere palette) |
| art-characters.js | 32×32 sprites: rabbit, princess, 6 guard veggies + GUARD_SPRITES map |
| art-tiles.js | 16×16 board tiles: empty, wall, goal, lit, mirror, preview |
| art-ui.js | Heart (full/empty), moon, logo, pixel icons (undo/redo/eye/pause/settings/lang/arrow) |
| art-scenes.js | 80×N act backdrops + SCENE_BY_LEVEL mapping (garden/walls/fortress/underground/palace/chamber) |
### Audio System (src/lib/)
| File | Purpose |
|------|---------|
| audio.js | Web Audio API procedural sound: playTone, playMoveSound, playDetectionSound, playCompleteSound, toggleMute |
### Utilities (src/lib/)
| File | Purpose |
|------|---------|
| localization.js | getText/setLanguage/getLanguage/initLanguage |
| progress.js | getProgress/completeLevel via localStorage |
### Localization (src/lib/locales/)
| File | Keys | Purpose |
|------|------|---------|
| en.json | ~55 | English translations |
| vi.json | ~55 | Vietnamese translations |
### Styles (src/styles/)
| File | Purpose |
|------|---------|
| theme.css | CSS variables: colors, fonts, guard colors, grid colors |
## Class Hierarchy
### Guard Inheritance
```
Guard (abstract base: grid, row, col, type, direction, isOn)
├── StaticGuard — lights fixed litCells array
├── RotatingGuard — rotates beam 90°/turn, castBeam with mirror bounce
├── BlinkingGuard — toggles isOn, lights litCells when on
├── MirrorGuard — lights own cell, stores reflectDirection (cw/ccw)
├── PatrollingGuard — follows path array, lights front + right cells
└── ChaserGuard — BFS pathfinding to player, detectionRadius range
```
## Key Data Structures
### Level Definition
```javascript
{
id: 1, name: "Garden Path", storyKey: "level1Story",
grid: { rows: 6, cols: 6 },
player: { row: 0, col: 0 },
goal: { row: 5, col: 5 },
walls: [{ row: 1, col: 1 }, ...],
guards: [
{ type: "static", position: { row: 2, col: 4 }, litCells: [...] },
{ type: "rotating", position: { row: 3, col: 3 }, startDirection: 0 },
{ type: "blinking", position: {...}, litCells: [...], startState: true },
{ type: "mirror", position: {...}, reflectDirection: "cw" },
{ type: "patrolling", startPosition: {...}, path: [...] },
],
isFinalLevel: false
}
```
### Cell State
```javascript
{ isWall: boolean, isGoal: boolean, isLight: boolean }
```
### Progress (localStorage)
```javascript
{ maxLevel: 1, completedLevels: [1, 2, 3] }
```
## Public API Summary
### GridSystem
```
new GridSystem(rows, cols, cellSize)
.isValidPosition(row, col), .isWall(row, col), .setWall(row, col, value)
.isGoal(row, col), .setGoal(row, col, value)
.isLight(row, col), .setLight(row, col, value)
.clearAllLight(), .getAllCells() → [{row, col, isWall, isGoal, isLight}]
```
### Player
```
new Player(grid, row, col)
.move(direction) → boolean, .moveTo(row, col) → boolean
.isInLitCell() → boolean, .isAtGoal() → boolean
```
### Guards
```
All: .updateLight(allGuards?), .onTurnChange(allGuards?)
All: .capture() → state, .apply(state) # dynamic-state snapshot for undo/preview
RotatingGuard: .castBeam(dir, fromRow, fromCol, range, allGuards, depth)
PatrollingGuard: .checkIfCircularPath(), path traversal with reversing
MirrorGuard: .reflectDirection ('cw' or 'ccw')
ChaserGuard: .bfsNextStep(targetRow, targetCol), hunting/returning state
```
### PrincessMechanic
```
new PrincessMechanic()
.update(grid, player, goalRow, goalCol) → { showMessage, detected }
.lightRing(grid, goalRow, goalCol, radius)
.capture() → { alerted, alertRadius, messageShown }, .apply(state)
.reset()
```
### GameHistory
```
new GameHistory()
.createSnapshot(player, guards, turnCount, princessState) → snapshot
.pushSnapshot(snap), .undo(...), .redo(...)
.canUndo(), .canRedo(), .reset()
```
### TurnManager
```
new TurnManager()
.nextTurn(grid, player, guards) → { detected, levelComplete }
.reset()
```
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| svelte | 5.x | UI framework (runes mode) |
| vite | 6.3.6 | Build tool, dev server |
| @sveltejs/vite-plugin-svelte | 6.x | Svelte compiler for Vite |
## File Dependency Map
```
main.js → App.svelte
App.svelte → all scenes
Game.svelte (central hub)
├── lib/game/level-manager.js → grid-system, player, guards, levels
├── lib/game/turn-manager.js
├── lib/game/game-history.js, princess-mechanic.js, touch-controls.js
├── lib/audio.js, lib/progress.js, lib/localization.js
├── lib/pixel/Pixel.svelte, lib/pixel/art-scenes.js (act backdrop)
├── components/GameBoard, PlayerSprite, GuardSprite, GameHud
├── components/DetectionPopup, LevelCompletePopup, PauseMenu, ControlsOverlay
└── renderVersion pattern for reactivity
LevelSelect.svelte → level-manager.getTotalLevels(), progress.js, localization.js
LevelIntro.svelte → levels.js, lib/pixel/art-scenes.js, localization.js
MainMenu.svelte → lib/pixel/{Pixel, art-ui, art-characters}
Pixel components (PlayerSprite/GuardSprite/GameBoard/GameHud) → lib/pixel/Pixel + art-*
All scenes → localization.js (for UI text)
```
## Statistics
| Metric | Value |
|--------|-------|
| Total Source Files | ~36 (8 scenes + 9 components + 8 engine + 6 pixel + audio + utils) |
| Number of Classes | 10 (GridSystem, Player, Guard + 6 subclasses, TurnManager, GameHistory, TouchControls) |
| Number of Levels | 12 (across 6 acts) |
| Guard Types | 6 (Static, Rotating, Blinking, Mirror, Patrolling, Chaser) |
| Localization Keys | ~67 per language |
| Max Grid Size | 10x10 |
| Max Guards/Level | 8 |