- Add test commands to CLAUDE.md and README.md
- Update file structure to include game-logic.js and __tests__/
- Document Jest test suite with 57 tests
- Extract pure game logic functions to game-logic.js for testability
- Add Jest testing framework with 57 tests covering:
* Unit tests for pure functions (createEmptyBoard, checkBoardOverflow, etc.)
* Integration tests for garbage system (addGarbageToPlayer, sendGarbage)
* Socket.io integration tests for multiplayer flow
- Refactor index.js to use extracted functions
- Tests verify garbage elimination bug fix: players only eliminated when board overflows
- Enable ready button in resetLobbyState() so players can ready up
- Remove redundant ready button state reset from endGame()
- Ensure ready button is enabled when returning to lobby
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add resetLobbyState() to UIManager to clear player lists and reset ready button
- Call resetLobbyState() when player-joined event has null player (game over reset)
- Reset ready button state in endGame() before showing game over screen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move elimination check from addGarbageToPlayer to lockPiece
- Check if top 2 rows have blocks after piece locks (board overflow)
- Re-compute opponents in sendGarbage loop to skip eliminated players
- Preserve game state after game over for viewing final results
- Reset all player variables at start of new game
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The client was checking activePlayers.length <= 1 and calling endGame()
prematurely, before the server's authoritative game-over event.
Now the client only ends the game when the server explicitly sends the
'game-over' event.
Added server-side logging to track active player count and winner.
Previously, players were eliminated when currentPiece.y <= 0, which was too
aggressive. Pieces like I, T, S, Z, J, L spawn at y=0 but have empty top rows,
so their actual blocks are at y >= 1 and visible on the board.
Now we check if any part of the locked piece is actually above y=0 (not
visible) before eliminating the player.
- Add garbageReceived array to player objects
- Track garbage in addGarbageToPlayer() with sender name
- Include garbageReceived in getStates() output
- Update endGame() to pass garbage stats to UI
- Update showGameOver() to display garbage count per player
- Add CSS styling for eliminated players and garbage column
- Bump CSS version for cache refresh
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Log elimination reason: board full, garbage collision, garbage overflow, disconnect
- Log garbage transfers: who cleared rows, how many, and recipients
- Log when no opponents available to send garbage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All players now receive the same first two pieces from the shared
piece queue, with sequence index starting at 2.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add generatePieceBag() and createPieceQueue() for 7-bag system
- Add pieceQueue and playerSequenceIndex to lobby state
- Modify spawnPiece() to consume from shared queue per player
- Update player-hold handler to manage sequence index on hold
- Add sequenceIndex to state updates for tracking
- Update client loadState() to receive sequenceIndex
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change y-- instead of y++ when pushing piece up on garbage
- Add y < 0 check to properly detect piece pushed off top
- Remove margin-top from battle grid to align with header
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When garbage rows are added to a player's board, the current falling
piece was not being adjusted, causing it to appear stuck while the
board shifted. This fix increments the piece's Y position when garbage
is received and eliminates the player if the piece is pushed above
the visible board.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Server: Handle unready event to set player ready state to false
- Client: Ready button toggles between READY and UNREADY
- Client: Unready sends unready socket event to server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Server: Late joiners are added as spectators instead of players
- Server: Send forced-spectator event only to joining spectator (not broadcast)
- Server: Track spectators separately and move them to players after game ends
- Client: Handle forced-spectator event to show all player boards
- Client: Spectators see all boards equally without main/spectator highlighting
- Client: Mobile view shows scrollable vertical list of all boards for spectators
- Fix: All cleared lines are sent as garbage to each opponent (not randomized)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add getGhostY() method to TetrisGame class (game.js)
- Add ghostY to getState() output
- Add getGhostY() helper to server (server/index.js)
- Include ghostY in getStates() broadcast
- Add drawGhostPiece() and drawGhostCell() methods (renderer.js)
- Ghost renders as semi-transparent outline at landing position
- 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>
- Main player board is large and centered with cyan glow
- Opponent boards are smaller (50% scale) and positioned around edges
- Layout adapts for 1, 2, or 3+ players
- Spectator boards have reduced opacity to reduce distraction
- Make gameover-screen a fixed overlay with semi-transparent background
- Removed 'screen' class from gameover-screen element
- Game scene remains visible behind game over overlay
- Remove premature elimination in addGarbageToPlayer (top rows check)
- Remove redundant y < 0 elimination check in lockPiece
- Players are only eliminated when spawnPiece fails (board full)
- Garbage rows now added to bottom of board, pushing blocks up
Changes:
- Added socket.join(LOBBY_ROOM) when player joins lobby
- Changed io.emit() to io.to(LOBBY_ROOM).emit() for all lobby events
- Players only see lobby events after they click JOIN LOBBY
- Fixed player list to update correctly when players join/ready
- Game now starts properly when all players are ready
Files modified:
- server/index.js: Use Socket.io rooms for lobby scoping
- public/js/app.js: Show lobby screen on player-joined event
- public/js/ui.js: Removed duplicate listener override
Changes:
- Removed room-based architecture, now using single global lobby
- Players only need to enter their name to join
- Game starts when all players in lobby are ready (min 2, max 8)
- Simplified UI - no room name field, shows "Global Lobby" header
- Updated all server events to use io.emit instead of io.to(roomName)
Files modified:
- server/index.js: Replaced rooms Map with single lobby object
- public/index.html: Removed room name input, updated button text
- public/js/network.js: Renamed joinRoom/leaveRoom to joinLobby/leaveLobby
- public/js/ui.js: Simplified join flow, removed room name validation
- public/js/app.js: Updated game header to show "GLOBAL LOBBY"
- PLAN.md: Marked all phases as complete
Features:
- 2-8 player multiplayer via Socket.io WebSocket
- Real-time board synchronization - all players see all boards
- Battle royale mechanic: clearing rows sends garbage to opponents
- Classic Tetris gameplay with all 7 tetrominoes
- Retro visual styling with CRT scanlines and pixel font
- Automatic level progression and speed increase
- Player elimination and winner announcement
Files:
- server/index.js: Node.js + Socket.io game server
- public/js/: Frontend game logic, rendering, network, and UI
- public/css/style.css: Retro Tetris styling
- README.md: Setup and usage instructions
- PLAN.md: Implementation plan with all phases completed