mirror of
https://github.com/tiennm99/nntv.git
synced 2026-06-05 00:15:41 +00:00
7233310662
- Fix ChaserGuard with BFS pathfinding and proper chase/return states
- Add undo/redo system (Z/Y keys) with full state snapshots
- Add procedural audio via Web Audio API (move, wait, detection, complete)
- Add mobile swipe controls with touch gesture detection
- Add detection feedback (cell flash, player shake animation)
- Add CSS transitions on grid cells for smooth lighting changes
- Add ARIA accessibility labels on game board and cells
- Add controls overlay ("?" button) showing all keyboard/touch shortcuts
- Add mute toggle in HUD
- Update Guide scene with chaser/mirror guard descriptions and tips
- Replace guard switch statement with factory registry pattern
- Extract princess mechanic and touch controls into separate modules
- Localize all UI strings (EN/VI) including new controls and tips
- Update README for Svelte 5 architecture with all current features
- Update project docs (architecture, code standards, codebase summary)
238 lines
7.9 KiB
Markdown
238 lines
7.9 KiB
Markdown
# Code Standards - Night Ninja: Twilight Voyage
|
|
|
|
## Naming Conventions
|
|
|
|
### Files
|
|
- **Svelte components**: PascalCase (`Game.svelte`, `GameBoard.svelte`, `PlayerSprite.svelte`)
|
|
- **JS modules**: kebab-case (`grid-system.js`, `turn-manager.js`, `level-manager.js`)
|
|
- **JSON/config**: kebab-case (`en.json`, `vi.json`, `theme.css`)
|
|
|
|
### Variables & Functions
|
|
- **Classes**: PascalCase (`GridSystem`, `Player`, `RotatingGuard`)
|
|
- **Methods & Functions**: camelCase (`updateLight()`, `nextTurn()`, `getText()`)
|
|
- **Constants**: UPPER_CASE for arrays (`LEVELS`), camelCase for others
|
|
- **Svelte state**: camelCase (`renderVersion`, `isPaused`, `detected`)
|
|
- **Props**: camelCase (`cellSize`, `oncellclick`, `onplayagain`)
|
|
|
|
### Grid Coordinates
|
|
- **row**: Vertical axis (0 = top, increases downward)
|
|
- **col**: Horizontal axis (0 = left, increases rightward)
|
|
- **Position objects**: `{ row: num, col: num }`
|
|
|
|
## Code Organization
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── main.js # Entry point, mounts App.svelte
|
|
├── App.svelte # Scene router ({#key} + fade)
|
|
│
|
|
├── scenes/ # Full-page scene components
|
|
│ ├── MainMenu.svelte
|
|
│ ├── StoryIntro.svelte
|
|
│ ├── LevelIntro.svelte
|
|
│ ├── LevelSelect.svelte
|
|
│ ├── Game.svelte # Main gameplay (state owner)
|
|
│ ├── GameOver.svelte
|
|
│ ├── Settings.svelte
|
|
│ └── Guide.svelte
|
|
│
|
|
├── components/ # Reusable UI components
|
|
│ ├── Button.svelte
|
|
│ ├── GameBoard.svelte # CSS grid rendering
|
|
│ ├── GameHud.svelte # Level/lives/turns display
|
|
│ ├── PlayerSprite.svelte # Positioned player div
|
|
│ ├── GuardSprite.svelte # Colored guard circle/diamond
|
|
│ ├── PauseMenu.svelte
|
|
│ └── DetectionPopup.svelte
|
|
│
|
|
├── lib/
|
|
│ ├── game/ # Pure JS game engine (no framework)
|
|
│ │ ├── grid-system.js
|
|
│ │ ├── player.js
|
|
│ │ ├── guards.js # Base + 6 guard subclasses (including ChaserGuard)
|
|
│ │ ├── turn-manager.js
|
|
│ │ ├── level-manager.js # GUARD_REGISTRY factory pattern
|
|
│ │ ├── game-history.js # Undo/redo system
|
|
│ │ ├── princess-mechanic.js # Level 12 escalating detection
|
|
│ │ └── touch-controls.js # Mobile swipe support
|
|
│ │
|
|
│ ├── levels/
|
|
│ │ └── levels.js # 12 level definitions
|
|
│ │
|
|
│ ├── locales/
|
|
│ │ ├── en.json
|
|
│ │ └── vi.json
|
|
│ ├── audio.js # Web Audio API sounds
|
|
│ ├── localization.js
|
|
│ └── progress.js
|
|
│
|
|
└── styles/
|
|
└── theme.css # CSS variables (colors, fonts)
|
|
```
|
|
|
|
### File Size Limits
|
|
- **Maximum 200 lines per file** before considering split
|
|
- Scenes with heavy logic → extract to `lib/game/`
|
|
- Shared UI → extract to `components/`
|
|
|
|
## Svelte 5 Patterns
|
|
|
|
### State Management
|
|
```javascript
|
|
// Primitive state
|
|
let isPaused = $state(false);
|
|
|
|
// Class instances (NOT auto-proxied — use renderVersion pattern)
|
|
let grid = $state(null);
|
|
let player = $state(null);
|
|
let renderVersion = $state(0);
|
|
|
|
// Derived values depend on renderVersion to pick up class mutations
|
|
let cells = $derived((renderVersion, grid ? grid.getAllCells() : []));
|
|
```
|
|
|
|
### Props
|
|
```javascript
|
|
let { navigate, level = 1, lives = 3 } = $props();
|
|
```
|
|
|
|
### Event Handlers
|
|
```svelte
|
|
<!-- Window-level events -->
|
|
<svelte:window onkeydown={onKeyDown} />
|
|
|
|
<!-- Component events via callback props -->
|
|
<Button text="Resume" onclick={() => isPaused = false} />
|
|
```
|
|
|
|
### Scene Navigation
|
|
```javascript
|
|
// Parent passes navigate function as prop
|
|
navigate('Game', { level: 3, lives: 2 });
|
|
|
|
// App.svelte routes via {#key currentScene}
|
|
```
|
|
|
|
## Class & Inheritance Patterns
|
|
|
|
### Guard Hierarchy
|
|
|
|
```
|
|
Guard (abstract base)
|
|
├── StaticGuard — fixed lit cells
|
|
├── RotatingGuard — rotating beam + mirror reflection
|
|
├── BlinkingGuard — toggle on/off
|
|
├── MirrorGuard — redirects beams
|
|
├── PatrollingGuard — path movement + directional light
|
|
└── ChaserGuard — BFS pathfinding + detection radius
|
|
```
|
|
|
|
**Base Guard contract:**
|
|
- Constructor: `grid, row, col, type`
|
|
- `updateLight(allGuards?)`: Set lit cells on grid
|
|
- `onTurnChange(allGuards?)`: Update state then call updateLight
|
|
|
|
**Level Manager Pattern:**
|
|
Use `GUARD_REGISTRY` factory pattern in `level-manager.js`:
|
|
```javascript
|
|
const GUARD_REGISTRY = {
|
|
static: (grid, g) => new StaticGuard(...),
|
|
chaser: (grid, g) => new ChaserGuard(...),
|
|
// ... etc
|
|
};
|
|
```
|
|
This eliminates switch statements and allows easy guard type registration.
|
|
|
|
**Key rule**: Game engine classes are pure JS with no Svelte dependency. They operate on raw object references, not proxied state.
|
|
|
|
## Grid & Coordinate System
|
|
|
|
- **Origin**: Top-left (0, 0)
|
|
- **Row**: 0 = top, increases downward
|
|
- **Col**: 0 = left, increases rightward
|
|
- **Cell Data**: `grid[row][col]` = `{ isWall, isGoal, isLight }`
|
|
- **Pixel Position**: `row * cellSize`, `col * cellSize`
|
|
|
|
## Theme & Styling
|
|
|
|
All visual constants centralized in `src/styles/theme.css` as CSS variables:
|
|
- `--bg-dark`, `--bg-panel` — backgrounds
|
|
- `--grid-empty`, `--grid-wall`, `--grid-goal`, `--grid-lit` — cell colors
|
|
- `--guard-static`, `--guard-rotating`, `--guard-blinking`, `--guard-patrolling`, `--guard-mirror` — guard colors
|
|
- `--font-title`, `--font-body`, `--font-button` — typography
|
|
- `--text-primary`, `--text-accent`, `--text-danger` — text colors
|
|
|
|
Component-scoped `<style>` blocks reference these variables. No inline color values.
|
|
|
|
## Error Handling
|
|
|
|
- **Try-catch**: localStorage operations (`progress.js`, `localization.js`)
|
|
- **Null checks**: `if (!player || !grid) return` in input handlers
|
|
- **Validation**: Grid bounds checked before all cell operations
|
|
- **Fallback**: Missing translations return key string itself
|
|
|
|
## Localization Pattern
|
|
|
|
```javascript
|
|
import { getText } from '../lib/localization.js';
|
|
|
|
const message = getText('level1Story'); // Returns EN or VI string
|
|
```
|
|
|
|
Key naming: camelCase matching JSON structure (`levelSelectTitle`, `enemyTypesContent`).
|
|
|
|
## Git & Commit Conventions
|
|
|
|
- **Format**: Conventional commits (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`)
|
|
- **Branch**: `main` for production
|
|
- **Messages**: Descriptive, explain "why" not just "what"
|
|
- **Example**: `fix: resolve Svelte 5 reactivity for class instances`
|
|
|
|
## New Patterns
|
|
|
|
### Undo/Redo System
|
|
```javascript
|
|
import { GameHistory } from '../lib/game/game-history.js';
|
|
const history = new GameHistory();
|
|
|
|
// Before player move
|
|
history.snapshot(player, guards, turnCount, princessAlerted, alertRadius);
|
|
|
|
// Z/Y key handlers
|
|
if (event.key === 'z') history.undo(player, guards);
|
|
if (event.key === 'y') history.redo(player, guards);
|
|
```
|
|
|
|
### Mobile Touch Controls
|
|
```javascript
|
|
import { TouchControls } from '../lib/game/touch-controls.js';
|
|
const touch = new TouchControls();
|
|
|
|
// In Game.svelte
|
|
<svelte:window ontouchstart={e => touch.onTouchStart(e)}
|
|
ontouchend={e => { const dir = touch.onTouchEnd(e); handleMove(dir); }} />
|
|
```
|
|
|
|
### Audio Feedback
|
|
```javascript
|
|
import * as audio from '../lib/audio.js';
|
|
audio.playMoveSound();
|
|
audio.playDetectionSound();
|
|
audio.toggleMute();
|
|
```
|
|
|
|
## Code Review Checklist
|
|
|
|
- [ ] Follows naming conventions (PascalCase components, kebab-case JS modules)
|
|
- [ ] No dead code or commented-out blocks
|
|
- [ ] Class mutations followed by `renderVersion++`
|
|
- [ ] No hardcoded colors (use CSS variables)
|
|
- [ ] Localization keys used for all user-facing strings
|
|
- [ ] File size under 200 lines
|
|
- [ ] Pure JS game logic has no Svelte imports
|
|
- [ ] Guard types registered in GUARD_REGISTRY (not switch statements)
|
|
- [ ] Touch input debounced/throttled if needed
|
|
- [ ] Audio context lazily initialized (autoplay policy compliance)
|