mirror of
https://github.com/tiennm99/sokoban.git
synced 2026-10-11 03:13:52 +00:00
Bonus layouts live in their own module so the license boundary stays clean: Microban levels remain under Skinner's redistribution grant, while the hand-designed ones fall under the repository's main LICENSE. A new levels module concatenates both sets into one index space, so progress-store keys and level numbering continue unbroken. Level select tags bonus entries so they read as distinct from the stock set.
4.3 KiB
4.3 KiB
Codebase Summary
Layout
src/
├── main.js # Mounts App.svelte into #app
├── App.svelte # Root router: menu / levels / game
├── app.css # Nord palette (CSS variables) + resets
├── lib/
│ ├── 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)
│ │ └── haptics.js # navigator.vibrate wrapper (10ms on box push, 60ms on win)
│ └── data/
│ ├── microban-levels.js # 155 XSB level strings (Microban, D. W. Skinner)
│ ├── bonus-levels.js # Hand-designed XSB levels (project's own, main LICENSE)
│ └── levels.js # Microban + bonus concatenated; single index space
└── views/
├── MenuView.svelte # Title, play, progress, hints
├── LevelSelectView.svelte # Paginated 5×4 grid (20/page × 8 pages)
├── GameView.svelte # Board + HUD + win overlay + input + haptics
├── Board.svelte # Presentational DOM board (div-per-tile, touch-action: none)
├── MobileControls.svelte # On-screen D-pad + action stack (hidden on desktop)
└── AppButton.svelte # Shared themed button (wraps native <button>)
public/
├── style.css # Legacy file — theme now lives in src/app.css
├── favicon.png
└── assets/ # qr.jpg (donate VietQR)
Data flow
src/main.jsmountsApp.svelte.App.svelteholdsviewandlevelIndexstate and renders one of three view components.MenuView→ callsonPlay()→ App switches toLevelSelectView.LevelSelectViewreads completion + best-move data fromprogressStore, renders a paginated grid, callsonSelect(i)on click.GameViewis keyed onlevelIndex({#key levelIndex}in App) so every level change remounts it with fresh state.- Inside
GameView:parseLevel(xsb) → new BoardModel(level), keyboard input callsmodel.tryMove()/model.undo(), after each mutationsyncFromModel()reassigns the$statesnapshots thatBoardreads as props. - On win:
progressStore.recordCompletion()+ overlay with NEXT / LEVELS actions.
Key design choices
- Framework-agnostic core.
level-parser.js,board-model.js,progress-store.js,haptics.js,microban-levels.js,bonus-levels.js, andlevels.jscontain zero Svelte — they could be lifted into any other stack. - BoardModel as a non-reactive ref. Svelte reactivity is driven by plain
$statesnapshot fields (player,boxes,moves,won) thatsyncFromModel()reassigns after every mutation. Cleaner than making the class instance itself reactive. - Board is purely presentational. Takes plain props (
walls,targets,floors,player,boxes,tileSize) so Svelte reactivity is predictable. Desktop & mobile share the same component;touch-action: noneblocks browser gestures on all devices. - Mobile controls opt-in.
MobileControls.svelteis hidden on desktop via@media (pointer: coarse), so no UA sniffing or JS feature detection — CSS media queries handle the split. - Haptics are graceful.
haptics.jswrapsnavigator.vibratewith try/catch and no-op fallback. Callers can vibrate without checking support; unsupported devices silently ignore the call. - Animations via CSS. Box and player use
transform: translate()withtransition: transform 110ms ease. No JS animation loop. - Responsive tile sizing.
GameView.computeTileSize()picks a tile size that fits the level inside the viewport, capped at 56 px. - Safe-area insets. Bottom controls use
env(safe-area-inset-*)for notch/Home-indicator safety on iPhones and Android phones with nav bars. - Scoped CSS per component. Svelte SFCs keep markup, style, and logic co-located and isolate styles to the component that owns them.
File size
Every .js / .svelte file is under the 200-LOC budget, except microban-levels.js which is pure data (155 XSB strings) and exempt.