Files
nntv/docs/codebase-summary.md
T
tiennm99 05cc053cb6 docs: sync README and docs with redesigned levels
Reflect new grid sizes (8x8-13x13), chaser guard type, L12 easter egg,
and solvability CI guard. Remove lingering mobile-support claims.
Delete completed level-redesign plan directory (preserved in git history).
2026-04-21 21:41:33 +07:00

8.7 KiB
Raw Blame History

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     — 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

{
  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

{ isWall: boolean, isGoal: boolean, isLight: boolean }

Progress (localStorage)

{ 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