Add hold piece feature

- Added holdPiece and canHold state to TetrisGame class
- Implemented hold() method to swap current piece with held piece
- Added player-hold socket event on server
- Added HOLD preview canvas showing held piece (grayed when unavailable)
- Added C key keyboard shortcut and touch button for hold
- Fixed canHold reset on piece spawn for proper swap functionality

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 08:50:52 -07:00
parent cde1643606
commit 4a49c76cdc
8 changed files with 410 additions and 8 deletions
+73
View File
@@ -0,0 +1,73 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Multiplayer Tetris Battle Royale game with 2-8 player real-time battles via WebSocket. Players clear rows to send garbage to opponents; last player standing wins.
## Commands
```bash
# Install dependencies
cd server && npm install
# Start server
cd server && npm start
# or
cd server && node index.js
# Server runs on http://localhost:3000 (or PORT env variable)
```
## Architecture
### Server-Client Model
- **Server** (`server/index.js`): Express + Socket.io handles all game logic authoritatively
- Manages single global lobby with `lobby.players` Map
- Game tick runs at 50ms intervals via `gameTick()`
- Broadcasts state updates to all connected clients via `broadcastState()`
- Tetromino definitions and board constants are duplicated on server
- **Client** (`public/js/`): Vanilla JavaScript with module pattern
- `network.js`: Socket.io client wrapper, manages player/game state caching
- `game.js`: `TetrisGame` class for local game state (mirrors server)
- `renderer.js`: `TetrisRenderer` class - Canvas rendering for all player boards
- `ui.js`: `UIManager` class - screen transitions, DOM manipulation
- `app.js`: Entry point, ties modules together, game loop via `requestAnimationFrame`
### Game Flow
1. **Lobby**: Players join global lobby via `join-lobby` socket event
2. **Ready**: All players click READY; game starts when all ready (2-8 players)
3. **Game**: Server authoritative - client inputs sent via socket, server broadcasts state
4. **Elimination**: Player eliminated when piece locks at top or garbage fills board
5. **Victory**: Game ends when 1 active player remains
### Key Constants
- `BOARD_WIDTH = 10`, `BOARD_HEIGHT = 20`, `CELL_SIZE = 24`
- `LOBBY_ROOM = 'global-lobby'` - Socket.io room for all players
- Garbage rules: 2 lines cleared -> 1 garbage row, 3 -> 2, 4 (Tetris) -> 4 rows
### Socket Events
| Event | Direction | Payload |
|-------|-----------|---------|
| `join-lobby` | Client->Server | `{ playerName }` |
| `ready` | Client->Server | - |
| `player-move` | Client->Server | `{ playerId, direction }` |
| `player-rotate` | Client->Server | `{ playerId }` |
| `player-drop` | Client->Server | `{ playerId, hard }` |
| `player-joined` | Server->Client | `{ player, players }` |
| `player-left` | Server->Client | `{ playerId, players }` |
| `game-started` | Server->Client | `{ players, states }` |
| `state-update` | Server->Client | `states[]` |
| `game-over` | Server->Client | `{ states }` |
### Rendering
- Each player gets a dynamically created board with canvas + info divs
- `renderer.setActivePlayer()` marks current player's board as `.main`, others as `.spectator`
- Battle grid layout classes: `.single-player`, `.two-players`, `.multi-player`