mirror of
https://github.com/tiennm99/nntv.git
synced 2026-05-20 20:24:55 +00:00
ba687ea130
Static guards now emit a Manhattan aura that shrinks by 1 per turn until harmless (clamped at -1 to keep solver state finite). Level data schema change: 'static' guards now take 'initialRadius' instead of 'litCells'. All 27 static-guard entries migrated in one pass. L1 and L2 redesigned with real forced-zigzag walls — solver path is now 20 moves (vs Manhattan 14), so players must move AWAY from the goal to reach it. L2 adds three wilting tomatoes parked on the zigzag path as a gentle intro to the mechanic. Tests: +3 new tests on StaticGuard wilting behavior; all 70 tests pass.
240 lines
8.8 KiB
Markdown
240 lines
8.8 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 |
|
||
| level-solver.js | BFS solvability checker (test-only); reuses runtime AI via capture()/apply() |
|
||
| 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: pointer gesture detection (legacy; no mobile support claim) |
|
||
|
||
### Level Data (src/lib/levels/)
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| levels.js | LEVELS array: 12 level definitions (grid, guards, walls, goals); L1-L11 solvable, L12 intentionally unsolvable |
|
||
| levels.solvability.test.js | CI-enforced invariants: each L1-L11 solvable, L12 unsolvable, no wall/light overlaps, exactly 12 levels |
|
||
|
||
### 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 — wilting tomato: Manhattan aura shrinks by 1 per turn (initialRadius → currentRadius)
|
||
├── 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 }, initialRadius: 2 },
|
||
{ type: "rotating", position: { row: 3, col: 3 }, startDirection: 0 },
|
||
{ type: "blinking", position: {...}, litCells: [...], startState: true },
|
||
{ type: "mirror", position: {...}, reflectDirection: "cw" },
|
||
{ type: "patrolling", startPosition: {...}, path: [...] },
|
||
{ type: "chaser", position: {...}, detectionRadius: 3 },
|
||
],
|
||
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 |
|