mirror of
https://github.com/tiennm99/sokoban.git
synced 2026-10-14 04:16:36 +00:00
feat: ship 100 Microban levels with overhauled UI and modular core
Replace the 3 hand-crafted JSON levels with the full Microban set (100 solvable puzzles by David W. Skinner) stored as XSB text and parsed at runtime. Rework the game into kebab-case modules under a 200-LOC budget: - core/level-parser: XSB parse with flood-fill interior detection - core/board-model: pure move/undo/win logic, Phaser-free - core/progress-store: localStorage persistence with graceful fallback - core/theme: Nord palette, fonts, responsive tile sizer - ui/button-factory: one rounded button impl with hover/press - ui/board-renderer: animated tile/wall/goal/box/player drawing - scenes: menu, paginated level select (5x4 grid, 5 pages), game Add WASD movement, U/Z undo, R restart, Esc to menu, live move counter, best-move tracking per level, win overlay with next/levels actions, and a radial-gradient CSS backdrop. Drop the dead Arcade Physics wiring, the broken manual shutdown/destroy code, the unused main.js self-import, and the hardcoded 3-level registry state. Add docs/ and refresh README.
This commit is contained in:
1 parent
a61d5a2410
commit
1d2fff6bf8
27 files changed
+1866
-881
No files matched your search
@@ -0,0 +1,36 @@
|
||||
# Code Standards
|
||||
|
||||
## Language & Toolchain
|
||||
- ES modules, modern JS (no TypeScript).
|
||||
- Phaser 3.88+.
|
||||
- Vite as build tool. Dev: `npm run dev`. Prod: `npm run build`.
|
||||
|
||||
## Naming
|
||||
- Files: **kebab-case** with descriptive names (`board-renderer.js`, `progress-store.js`).
|
||||
- Classes: PascalCase (`BoardModel`, `BoardRenderer`).
|
||||
- Functions and variables: camelCase.
|
||||
- Constants: UPPER_SNAKE for module-level tuning knobs (`KEY_REPEAT_MS`, `PER_PAGE`).
|
||||
|
||||
## File size
|
||||
- Code files must stay under 200 lines of code.
|
||||
- Pure-data files (levels, palettes) are exempt.
|
||||
|
||||
## Architecture rules
|
||||
- **Scenes** own lifecycle + layout; they delegate drawing to renderers and logic to models.
|
||||
- **Core** (`core/`) is pure JS — no Phaser imports. Anything that can be unit-tested without a canvas lives here.
|
||||
- **UI** (`ui/`) is Phaser-specific but scene-agnostic — reusable widgets and renderers.
|
||||
- No new dependencies without updating this doc.
|
||||
|
||||
## Style
|
||||
- Prefer composition over inheritance.
|
||||
- Fail loudly during development (console.error on unexpected state), fail gracefully at runtime (try/catch around level parsing, progress store).
|
||||
- Comments: short, explain *why*, not *what*. File headers give a one-sentence purpose.
|
||||
|
||||
## Git / commits
|
||||
- Conventional commits (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`).
|
||||
- Never commit dotenv, keys, or build artifacts.
|
||||
- Run `npm run build-nolog` before pushing to catch compile errors.
|
||||
|
||||
## Testing strategy (current)
|
||||
No automated tests yet. Manual smoke test: load menu → play level 1 → complete → verify progress saved in localStorage → reload page → verify completion persists.
|
||||
Future: unit tests for `level-parser.js` and `board-model.js` (both Phaser-free).
|
||||
@@ -0,0 +1,43 @@
|
||||
# Codebase Summary
|
||||
|
||||
## Layout
|
||||
```
|
||||
src/
|
||||
├── main.js # Entry: boots Phaser on #game-container
|
||||
└── game/
|
||||
├── main.js # Phaser Game config + scene registration
|
||||
├── data/
|
||||
│ └── microban-levels.js # 100 XSB level strings
|
||||
├── core/
|
||||
│ ├── level-parser.js # XSB text → {walls, targets, boxes, player, floors}
|
||||
│ ├── board-model.js # Pure game state + move/undo/win logic
|
||||
│ ├── progress-store.js # localStorage persistence (completed + best moves)
|
||||
│ └── theme.js # Nord color palette, fonts, tile-size helper
|
||||
├── ui/
|
||||
│ ├── button-factory.js # Reusable rounded button with hover/press
|
||||
│ └── board-renderer.js # Draws the board + animates moves
|
||||
└── scenes/
|
||||
├── menu-scene.js # Title, play, progress counter, keybinds
|
||||
├── level-scene.js # Paginated 5×4 level grid (5 pages × 20)
|
||||
└── game-scene.js # Gameplay: input, HUD, win overlay
|
||||
|
||||
public/
|
||||
├── style.css # Page background + container shadow
|
||||
├── favicon.png
|
||||
└── assets/ # bg.png, logo.png (reserved for future use)
|
||||
```
|
||||
|
||||
## Data flow
|
||||
1. `main.js` creates the Phaser Game with Menu / Level / Game scenes.
|
||||
2. `level-scene` writes `registry.currentLevel` on click, starts `GameScene`.
|
||||
3. `game-scene` reads the level index, calls `parseLevel()` on the XSB string, builds a `BoardModel`, hands it to `BoardRenderer`.
|
||||
4. Input → `BoardModel.tryMove()` → `BoardRenderer.animateMove()` → win check → `progressStore.recordCompletion()`.
|
||||
|
||||
## Key design choices
|
||||
- **XSB at runtime**: levels are stored as the standard Sokoban text format; the parser flood-fills interior floor tiles so the renderer only paints inside the puzzle shape.
|
||||
- **Pure `BoardModel`**: move/undo/win logic has zero Phaser dependencies — trivial to unit-test later.
|
||||
- **No Arcade physics**: the original version wired colliders but moved sprites by tween; the physics system did nothing. Removed.
|
||||
- **One button factory**: every scene goes through `createButton()` so hover/press feels identical everywhere.
|
||||
|
||||
## File size budget
|
||||
Every file under 200 LOC per development rules. Data file (`microban-levels.js`) exceeds that because it's pure data, not logic.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Development Roadmap
|
||||
|
||||
## Phase 1 — Core game (complete)
|
||||
- Phaser + Vite scaffolding.
|
||||
- Menu / Level / Game scenes.
|
||||
- Arrow-key movement, box pushing, target detection.
|
||||
|
||||
## Phase 2 — Overhaul (complete, 2026-04-11)
|
||||
- Replace 3 hand-crafted levels with 100 Microban levels.
|
||||
- Modularize: core / ui / scenes split, every file <200 LOC.
|
||||
- BoardModel with undo + move counter.
|
||||
- Paginated level select (5 pages × 20 levels).
|
||||
- Nord theme + rounded button factory + animated board renderer.
|
||||
- WASD support, U/Z undo, R restart, Esc to menu.
|
||||
- localStorage progress (completed + best move count).
|
||||
- Drop dead Arcade physics and broken shutdown code.
|
||||
- Docs folder + README refresh.
|
||||
|
||||
## Phase 3 — Polish (planned)
|
||||
- Sound effects (step, push, win).
|
||||
- Player facing direction indicator.
|
||||
- Level category tabs (Easy / Medium / Hard) derived from puzzle size or move count.
|
||||
- Touch controls (swipe) for mobile.
|
||||
- Unit tests for `level-parser` and `board-model`.
|
||||
|
||||
## Phase 4 — Stretch (ideas)
|
||||
- Additional level packs (Sasquatch, Sokogen).
|
||||
- Custom level importer (paste XSB text).
|
||||
- Replay / move playback.
|
||||
- Per-level leaderboards (local only).
|
||||
@@ -0,0 +1,32 @@
|
||||
# Project Changelog
|
||||
|
||||
## 2026-04-11 — Overhaul
|
||||
|
||||
### Added
|
||||
- **100 Microban levels** (David W. Skinner) as the new stock level set, replacing the 3 hand-crafted puzzles. Stored as XSB text in `src/game/data/microban-levels.js`.
|
||||
- **XSB level parser** (`core/level-parser.js`) with flood-fill floor detection.
|
||||
- **BoardModel** (`core/board-model.js`) — pure state, undo history, win detection.
|
||||
- **Progress store** (`core/progress-store.js`) — localStorage persistence of completion + best move count, fails gracefully if unavailable.
|
||||
- **BoardRenderer** (`ui/board-renderer.js`) — floor/wall/target/box/player drawing with animated moves.
|
||||
- **Button factory** (`ui/button-factory.js`) — one rounded-button implementation shared by every scene.
|
||||
- **Nord theme** (`core/theme.js`) — palette, fonts, and responsive tile-size helper.
|
||||
- **Paginated level select**: 5×4 grid × 5 pages, shows ✓ + best move count per completed level.
|
||||
- **WASD controls**, **U/Z undo**, **R restart**, **Esc to menu**, move counter, win overlay with NEXT / LEVELS buttons.
|
||||
- `docs/` — PDR, codebase summary, code standards, system architecture, roadmap, this changelog.
|
||||
|
||||
### Changed
|
||||
- Scenes renamed to kebab-case (`menu-scene.js`, `level-scene.js`, `game-scene.js`) and rebuilt against the new core/ui modules.
|
||||
- `main.js` now imports a shared theme and registers only the three new scenes.
|
||||
- `public/style.css` now uses a radial gradient background and rounds the game container.
|
||||
- `index.html` title updated.
|
||||
|
||||
### Removed
|
||||
- `MenuScene.js`, `LevelScene.js`, `MainScene.js` (old PascalCase versions).
|
||||
- `public/assets/levels/level1.json`, `level2.json`, `level3.json`.
|
||||
- Arcade Physics setup inside `MainScene` — it was configured but never actually drove movement (moves were all tween-based on a grid). Colliders, `physics.world.shutdown()` call, and the hand-rolled `shutdown/destroy` lifecycle overrides are gone.
|
||||
- Unused `import game from '../main.js'` in the old `MainScene`.
|
||||
- Hardcoded `totalLevels: 3` and 3-element `levelCompleted` array in the game registry — replaced with dynamic lookup via `progressStore`.
|
||||
|
||||
### Fixed
|
||||
- Scene restart no longer leaves dangling event listeners (Phaser handles this itself; the previous manual `removeAllListeners` was both redundant and fragile).
|
||||
- Level select now scales to 100 puzzles via pagination instead of a single-row layout.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Sokoban — Product Development Record
|
||||
|
||||
## What it is
|
||||
Browser-based Sokoban puzzle game. 100 solvable Microban levels (David W. Skinner, freely distributable), Phaser 3 + Vite, deployed to GitHub Pages.
|
||||
|
||||
## Goals
|
||||
- Clean, beginner-friendly Sokoban that runs in any modern browser with no install.
|
||||
- 100 curated puzzles, progressing from easy to moderately tricky.
|
||||
- Keyboard-first UX with undo, restart, move counter, persistent progress.
|
||||
- Small, well-organized codebase (<200 LOC per file) that's easy to extend.
|
||||
|
||||
## Non-goals
|
||||
- Custom level editor (puzzles are fixed, shipped with the build).
|
||||
- Networked play / leaderboards.
|
||||
- Sound / music (kept out to stay lightweight; may add later).
|
||||
|
||||
## Target audience
|
||||
Casual puzzle fans, Sokoban beginners. Microban set chosen specifically because it's designed for beginners and illustrates one concept per puzzle.
|
||||
|
||||
## Success criteria
|
||||
- All 100 levels playable end-to-end.
|
||||
- Progress (completion + best moves) persists across sessions.
|
||||
- Build output fits in the default Vite budget.
|
||||
- Runs smoothly on desktop browsers at 1024×768 canvas (scaled responsively).
|
||||
|
||||
## Credits
|
||||
- Puzzles: Microban by David W. Skinner (April 2000). Used per the collection's free-distribution terms with credit.
|
||||
- Engine: Phaser 3.
|
||||
@@ -0,0 +1,57 @@
|
||||
# System Architecture
|
||||
|
||||
## High-level
|
||||
Single-page static site. No backend. Phaser 3 runs the game loop inside a `<canvas>` element. Progress persists in `localStorage`.
|
||||
|
||||
```
|
||||
index.html ──▶ src/main.js ──▶ src/game/main.js (Phaser Game)
|
||||
│
|
||||
├── MenuScene
|
||||
├── LevelScene ── registry: currentLevel
|
||||
└── GameScene
|
||||
│
|
||||
├── parseLevel(XSB) ── core/level-parser.js
|
||||
├── BoardModel ── core/board-model.js
|
||||
├── BoardRenderer ── ui/board-renderer.js
|
||||
└── progressStore ── core/progress-store.js
|
||||
│
|
||||
└── localStorage
|
||||
```
|
||||
|
||||
## Scene lifecycle
|
||||
1. **MenuScene** — title, play, progress, hints.
|
||||
2. **LevelScene** — paginated 5×4 grid, reads completion/best-moves from `progressStore`. On click: writes `currentLevel` to the Phaser registry and starts `GameScene`.
|
||||
3. **GameScene** — `init` resets local state → `create` parses the level, builds `BoardModel`, instantiates `BoardRenderer`, wires input, builds HUD. `update()` handles key polling with a repeat gate (`KEY_REPEAT_MS = 130`).
|
||||
|
||||
## Input → state → render
|
||||
```
|
||||
key event ─▶ GameScene.update ─▶ BoardModel.tryMove(dx,dy)
|
||||
│
|
||||
└── returns true on legal move
|
||||
│
|
||||
├── renderer.animateMove()
|
||||
├── moveLabel.setText()
|
||||
└── if BoardModel.isSolved()
|
||||
└── onWin() ─▶ progressStore.recordCompletion()
|
||||
```
|
||||
|
||||
## Level data
|
||||
- Stored as XSB strings in `src/game/data/microban-levels.js`.
|
||||
- XSB symbols: `#` wall, ` ` floor, `.` target, `$` box, `*` box-on-target, `@` player, `+` player-on-target.
|
||||
- Parser flood-fills from the player position to compute the interior floor set. Everything outside the flood is treated as exterior (not rendered, not walkable).
|
||||
|
||||
## Persistence schema
|
||||
`localStorage['sokoban-progress-v1']`:
|
||||
```json
|
||||
{
|
||||
"completed": { "0": true, "3": true, ... },
|
||||
"bestMoves": { "0": 14, "3": 27, ... }
|
||||
}
|
||||
```
|
||||
Keys are level indices (0-based). `getCompletedCount()` returns the size of `completed`.
|
||||
|
||||
## Responsive rendering
|
||||
`computeTileSize(levelW, levelH, viewportW, viewportH)` picks the largest tile size (capped at 64px) that fits the level with margin, so small puzzles display large and the two huge Microban levels (#154, #155 — not shipped) would still fit.
|
||||
|
||||
## Deployment
|
||||
Static build via `vite build --config vite/config.prod.mjs`, output pushed to GitHub Pages via the repo's CI. No server-side components.
|
||||
Reference in new issue
Block a user