|
|
||
|---|---|---|
| assets | ||
| js | ||
| server | ||
| CLAUDE.md | ||
| README.md | ||
| index.html | ||
| instructions.md | ||
| software.md | ||
| start_web.sh | ||
README.md
iPuzzle
A browser-based multiplayer jigsaw puzzle game built with Phaser 3 and vanilla ES6 JavaScript. Pick an image, choose a difficulty, and solve the puzzle solo or with friends in real time — no account required.
Features
- 55+ puzzle images — a curated library of illustrated and photographic artwork, from fantasy landscapes to wildlife
- 7 difficulty levels — 20, 40, 60, 100, 140, 180, or 250 pieces
- Procedurally generated pieces — interlocking tab/blank connectors generated from a grid so every edge fits its neighbor
- Real-time multiplayer — create a room and share the 4-character code (or a direct URL) for others to join instantly
- Player color labels — each player's held piece shows their name tag in a unique color
- Snap detection with glow — matching edges glow as you bring a piece close; release to lock
- Group movement — snapped pieces move as a unit
- Pan and zoom — middle-click drag or right-click drag to pan; scroll wheel to zoom
- Box selection — click-drag on empty space to grab multiple pieces at once
- Background themes — Dark Wood or Green Felt table surface
- Puzzle timer — tracks time from first interaction to completion
- Completion stats — snap rate chart, corner/edge/quadrant milestones, per-player snap counts
- Background music — 8 in-game tracks + main menu music with mute toggle
- Session persistence — progress is saved to
localStorage; resume an unfinished puzzle from the main menu - Fullscreen support
- Responsive canvas — scales to fit any window size via
Phaser.Scale.FIT
Running the Game
Because Phaser loads assets over XHR, the game must be served over HTTP (not opened as a file:// URL).
Option A — Node.js server (multiplayer)
The Node server handles both static file serving and WebSocket multiplayer.
cd server
npm install
node server.js
# open http://localhost:8080
Set a custom port with the PORT environment variable:
PORT=3000 node server.js
Option B — Simple HTTP server (single-player only)
Any static file server works if you only need single-player:
# Python
python3 -m http.server 8080
# Node (npx)
npx serve .
Then open http://localhost:8080.
Multiplayer
- One player starts a New Puzzle — a 4-character room code is generated (e.g.
X4KR). - Share the code or the full URL (e.g.
http://your-host:8080/?room=X4KR). - Other players click Join Puzzle and enter the room code, or open the share URL directly.
- All players see each other's pieces in real time. Piece claiming prevents two players from grabbing the same piece simultaneously.
Rooms persist on the server for 5 minutes after the last player disconnects, allowing brief reconnections.
Controls
| Action | Input |
|---|---|
| Pick up / release piece | Left-click |
| Pan the board | Middle-click drag or right-click drag |
| Zoom | Scroll wheel |
| Box-select multiple pieces | Left-click drag on empty space |
| Fullscreen | Button on main menu |
Project Structure
iPuzzle/
├── index.html # Entry point; loads all scripts via <script> tags
├── server/
│ ├── server.js # Node HTTP + WebSocket server
│ └── package.json
├── js/
│ ├── main.js # Phaser game config; registers all scenes
│ ├── scenes/
│ │ ├── MainMenuScene.js # Animated main menu, join/create room UI
│ │ ├── NewPuzzleScene.js # Image picker, difficulty selector
│ │ └── PuzzleScene.js # Core gameplay loop
│ ├── puzzle/
│ │ ├── ConnectorGeometry.js # Tab/blank connector math
│ │ ├── PuzzleGenerator.js # Divides image into piece grid
│ │ ├── PieceRenderer.js # Draws each piece to a canvas texture
│ │ ├── PieceObject.js # Phaser Image wrapper for a piece
│ │ ├── GroupManager.js # Tracks which pieces are locked together
│ │ ├── SnapDetector.js # Proximity checks and glow segment generation
│ │ └── RoomCode.js # 4-character room code generator
│ ├── state/
│ │ ├── PuzzleState.js # In-memory canonical puzzle state
│ │ └── StorageManager.js # localStorage persistence helpers
│ └── net/
│ └── NetworkManager.js # WebSocket client singleton
└── assets/
├── puzzles.json # Manifest of available puzzle images
├── thumbnails.json # Manifest of thumbnail paths
├── images/
│ ├── puzzles/ # Full-resolution puzzle images + thumbnails
│ └── ui/ # Backgrounds, logo, sub-logo
└── audio/
├── music/ # main_menu.mp3, track_01–08.mp3, tracks.json
└── fx/ # click.mp3, grab.mp3, stats.mp3
Architecture
Scenes
| Scene | Responsibility |
|---|---|
MainMenuScene |
Animated intro, Continue / New Puzzle / Join Puzzle / Fullscreen buttons, join dialog |
NewPuzzleScene |
Scrollable image gallery with thumbnails, difficulty picker, background picker |
PuzzleScene |
Piece rendering, interaction, snap/glow, pan/zoom, multiplayer relay, completion stats |
Puzzle piece generation
PuzzleGeneratordivides the chosen image into acols × rowsgrid.ConnectorGeometryassigns tab or blank connectors to each shared edge, ensuring that piece A's tab aligns with piece B's blank.PieceRendererdraws each piece — with its connectors — to an off-screen canvas, then registers it as a Phaser texture.PieceObjectwraps a PhaserImageand tracks grid position, group membership, and world coordinates.
Snap detection
Each frame while a piece is held, SnapDetector.check() compares every piece in the held group to its grid neighbors. If any neighbor is within snapRadius (35% of piece width), the pieces lock together via GroupManager. A larger glowRadius (140% of piece width) drives the edge-glow preview.
Multiplayer protocol (WebSocket)
| Message | Direction | Description |
|---|---|---|
create_room |
client → server | Host sends initial puzzle state |
room_created |
server → client | Confirms room code and player ID |
join_room |
client → server | Joiner requests to enter a room |
room_joined |
server → client | Returns full puzzle state, player list, and existing claims |
player_joined / player_left |
server → all | Presence events |
claim |
client → server | Request exclusive hold on a piece group |
claim_ok / claim_denied |
server → client | Claim result |
move |
client → server | Throttled (50 ms) position delta while dragging |
release |
client → server | Drop with updated positions and merged groups |
completed |
server → all | Broadcast when all pieces form one group |
State persistence
StorageManager serialises the current PuzzleState to localStorage on every significant event (snap, release). On "Continue Puzzle" the state is restored from storage. All state is keyed by room code to support future multi-room play.
Adding New Puzzles
- Add a 16:9 PNG or JPG to
assets/images/puzzles/. - Create a thumbnail (any size, same basename prefixed with
thumb_) in the same folder. - Add an entry to
assets/puzzles.jsonandassets/thumbnails.json.
A helper script for bulk thumbnail generation is provided at assets/images/puzzles/make_thumbnails.sh.
Tech Stack
| Concern | Technology |
|---|---|
| Game engine | Phaser 3.90 (CDN) |
| Language | Vanilla ES6 (no build step, no npm on the client) |
| Multiplayer | WebSocket via the ws library |
| Server | Node.js (built-in http + ws) |
| Persistence | Browser localStorage |