Files
nntv/docs/code-standards.md
T
tiennm99 7233310662 feat: add undo/redo, audio, mobile controls, BFS pathfinding, and accessibility
- 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)
2026-04-13 18:24:46 +07:00

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)