4.7 KiB
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
# Install dependencies
cd server && npm install
# Start server
cd server && npm start
# or
cd server && node index.js
# Run tests
cd server && npm test
# Run tests with coverage
cd server && npm run test:coverage
# Server runs on http://localhost:3000 (or PORT env variable)
Architecture
Server-Client Model
-
Server (
server/): Express + Socket.io handles all game logic authoritativelyindex.js: Main server with socket.io event handlersgame-logic.js: Pure game logic functions (testable, no socket dependencies)__tests__/: Jest test suite with 57 tests- Manages single global lobby with
lobby.playersMap - Game tick runs at 50ms intervals via
gameTick() - Broadcasts state updates to all connected clients via
broadcastState()
-
Client (
public/js/): Vanilla JavaScript with module patternnetwork.js: Socket.io client wrapper, manages player/game state cachinggame.js:TetrisGameclass for local game state (mirrors server)renderer.js:TetrisRendererclass - Canvas rendering for all player boardsui.js:UIManagerclass - screen transitions, DOM manipulationapp.js: Entry point, ties modules together, game loop viarequestAnimationFrame
Game Flow
- Lobby: Players join global lobby via
join-lobbysocket event - Ready: All players click READY; game starts when all ready (2-8 players)
- Game: Server authoritative - client inputs sent via socket, server broadcasts state
- Elimination: Player eliminated when piece locks at top or garbage fills board
- Victory: Game ends when 1 active player remains
Key Constants
BOARD_WIDTH = 10,BOARD_HEIGHT = 20,CELL_SIZE = 24LOBBY_ROOM = 'global-lobby'- Socket.io room for all players- Garbage rules: 2 lines cleared -> 1 garbage row, 3 -> 2, 4 (Tetris) -> 4 rows
Piece Spawn & Negative Y Overflow
Pieces spawn at y = 0 (top of visible board). The coordinate system allows negative Y values as intentional overflow space:
- Negative Y allowed:
isValidPosition()only checksnewY >= BOARD_HEIGHT, notnewY < 0 - Purpose: When garbage is received, pieces are pushed up (y decreases) into negative territory
- Player survival: Player is NOT eliminated when piece goes negative — only when piece LOCKS with blocks in rows 0-1
- Recovery: Piece drops naturally back into visible area; player can continue playing
This design gives players a buffer zone to recover from garbage attacks before elimination.
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-hold |
Client->Server | { playerId } |
zone-activate |
Client->Server | { playerId } |
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 } |
Zone Mechanic (Tetris Effect)
Zone is a time-based attack mechanic imported from Tetris Effect. When players clear lines, their Zone meter fills:
- 1 line = +15 Zone meter
- 2 lines = +25 Zone meter
- 3 lines = +35 Zone meter
- 4 lines (Tetris) = +50 Zone meter
When Zone meter reaches 100:
- Press Z key or ZONE button to activate Zone
- Gravity pauses for 20 seconds
- Players control pieces manually but gravity is frozen
- Cleared lines cascade to bottom instead of disappearing
- After 20 seconds, Zone ends and sends garbage to opponents
Zone Attack Calculation:
- Lines cleared during Zone × 1.5 = garbage rows sent
- Example: 4 Zone lines = 6 garbage rows, 8 Zone lines = 12 garbage rows
- Garbage is distributed randomly among remaining opponents
Zone provides a high-risk, high-reward tactic for eliminating opponents with massive attacks.
Rendering
- Each player gets a dynamically created board with canvas + info divs
renderer.setActivePlayer()marks current player's board as.main, others as.spectator- Zone meter shown below each player board with fill bar and status indicator
- Battle grid layout classes:
.single-player,.two-players,.multi-player