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
@@ -24,6 +24,8 @@ class TetrisGame {
this.board = this.createEmptyBoard();
this.currentPiece = null;
this.nextPiece = null;
this.holdPiece = null;
this.canHold = true;
this.score = 0;
this.lines = 0;
this.level = 1;
@@ -51,6 +53,8 @@ class TetrisGame {
this.gameOver = false;
this.eliminated = false;
this.dropInterval = 1000;
this.holdPiece = null;
this.canHold = true;
this.currentPiece = this.spawnPiece();
this.nextPiece = this.getRandomPiece();
@@ -172,6 +176,38 @@ class TetrisGame {
return dropped;
}
hold() {
if (this.gameOver || this.inputLocked || !this.canHold) return false;
if (this.holdPiece === null) {
// First hold - store current piece and spawn next
this.holdPiece = {
type: this.currentPiece.type,
shape: JSON.parse(JSON.stringify(this.currentPiece.shape)),
color: this.currentPiece.color
};
this.currentPiece = this.nextPiece;
this.nextPiece = this.getRandomPiece();
} else {
// Swap with held piece
const temp = {
type: this.currentPiece.type,
shape: JSON.parse(JSON.stringify(this.currentPiece.shape)),
color: this.currentPiece.color
};
this.currentPiece = {
...this.holdPiece,
shape: JSON.parse(JSON.stringify(this.holdPiece.shape)),
x: Math.floor(BOARD_WIDTH / 2) - Math.floor(this.holdPiece.shape[0].length / 2),
y: 0
};
this.holdPiece = temp;
}
this.canHold = false;
return true;
}
lockPiece() {
if (!this.currentPiece) return false;
@@ -205,6 +241,9 @@ class TetrisGame {
this.eliminated = true;
}
// Reset canHold for the new piece
this.canHold = true;
return cleared;
}
@@ -294,6 +333,8 @@ class TetrisGame {
board: JSON.parse(JSON.stringify(this.board)),
currentPiece: this.currentPiece ? JSON.parse(JSON.stringify(this.currentPiece)) : null,
nextPiece: this.nextPiece ? JSON.parse(JSON.stringify(this.nextPiece)) : null,
holdPiece: this.holdPiece ? JSON.parse(JSON.stringify(this.holdPiece)) : null,
canHold: this.canHold,
score: this.score,
lines: this.lines,
level: this.level,
@@ -306,6 +347,8 @@ class TetrisGame {
this.board = JSON.parse(JSON.stringify(state.board));
this.currentPiece = state.currentPiece ? JSON.parse(JSON.stringify(state.currentPiece)) : null;
this.nextPiece = state.nextPiece ? JSON.parse(JSON.stringify(state.nextPiece)) : null;
this.holdPiece = state.holdPiece ? JSON.parse(JSON.stringify(state.holdPiece)) : null;
this.canHold = state.canHold !== undefined ? state.canHold : true;
this.score = state.score;
this.lines = state.lines;
this.level = state.level;