mirror of
https://github.com/tiennm99/nntv.git
synced 2026-06-04 00:16:07 +00:00
97a0a747a7
- 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
237 lines
8.4 KiB
Markdown
237 lines
8.4 KiB
Markdown
# 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 |
|