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
+43
View File
@@ -159,6 +159,42 @@ io.on('connection', (socket) => {
}
});
socket.on('player-hold', ({ playerId }) => {
if (!lobby.gameStarted) return;
const player = lobby.players.get(playerId);
if (!player || player.eliminated) return;
if (!player.canHold) return;
if (player.holdPiece === null) {
// First hold - store current piece and spawn next
player.holdPiece = {
type: player.currentPiece.type,
shape: JSON.parse(JSON.stringify(player.currentPiece.shape)),
color: player.currentPiece.color
};
player.currentPiece = player.nextPiece;
player.nextPiece = getRandomPiece();
} else {
// Swap with held piece
const temp = {
type: player.currentPiece.type,
shape: JSON.parse(JSON.stringify(player.currentPiece.shape)),
color: player.currentPiece.color
};
player.currentPiece = {
...player.holdPiece,
shape: JSON.parse(JSON.stringify(player.holdPiece.shape)),
x: Math.floor(BOARD_WIDTH / 2) - Math.floor(player.holdPiece.shape[0].length / 2),
y: 0
};
player.holdPiece = temp;
}
player.canHold = false;
broadcastState();
});
socket.on('disconnect', () => {
const player = lobby.players.get(socket.id);
if (player) {
@@ -254,6 +290,9 @@ function lockPiece(player) {
if (!spawnPiece(player)) player.eliminated = true;
// Reset canHold for the new piece
player.canHold = true;
if (rowsCleared > 0) sendGarbage(player, rowsCleared);
broadcastState();
@@ -309,6 +348,8 @@ function startGame() {
player.level = 1;
player.eliminated = false;
player.dropInterval = 1000;
player.holdPiece = null;
player.canHold = true;
player.currentPiece = getRandomPiece();
player.nextPiece = getRandomPiece();
}
@@ -351,6 +392,8 @@ function getStates() {
board: JSON.parse(JSON.stringify(p.board)),
currentPiece: p.currentPiece ? JSON.parse(JSON.stringify(p.currentPiece)) : null,
nextPiece: p.nextPiece ? JSON.parse(JSON.stringify(p.nextPiece)) : null,
holdPiece: p.holdPiece ? JSON.parse(JSON.stringify(p.holdPiece)) : null,
canHold: p.canHold,
score: p.score,
lines: p.lines,
level: p.level,