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:
+69
-2
@@ -43,15 +43,30 @@ class TetrisRenderer {
|
||||
nextDiv.appendChild(nextCanvas);
|
||||
boardDiv.appendChild(nextDiv);
|
||||
|
||||
// Hold piece preview
|
||||
const holdDiv = document.createElement('div');
|
||||
holdDiv.className = 'board-info';
|
||||
const holdCanvas = document.createElement('canvas');
|
||||
holdCanvas.id = `hold-${playerId}`;
|
||||
holdCanvas.width = 80;
|
||||
holdCanvas.height = 80;
|
||||
holdCanvas.style.width = '80px';
|
||||
holdCanvas.style.height = '80px';
|
||||
holdDiv.innerHTML = '<span>HOLD:</span>';
|
||||
holdDiv.appendChild(holdCanvas);
|
||||
boardDiv.appendChild(holdDiv);
|
||||
|
||||
this.container.appendChild(boardDiv);
|
||||
|
||||
this.boards.set(playerId, {
|
||||
element: boardDiv,
|
||||
canvas: canvas,
|
||||
nextCanvas: nextCanvas,
|
||||
holdCanvas: holdCanvas,
|
||||
info: infoDiv,
|
||||
ctx: canvas.getContext('2d'),
|
||||
nextCtx: nextCanvas.getContext('2d')
|
||||
nextCtx: nextCanvas.getContext('2d'),
|
||||
holdCtx: holdCanvas.getContext('2d')
|
||||
});
|
||||
|
||||
return boardDiv;
|
||||
@@ -69,7 +84,7 @@ class TetrisRenderer {
|
||||
const boardData = this.boards.get(playerId);
|
||||
if (!boardData) return;
|
||||
|
||||
const { ctx, nextCtx, element, info } = boardData;
|
||||
const { ctx, nextCtx, holdCtx, element, info } = boardData;
|
||||
|
||||
// Clear canvas
|
||||
ctx.fillStyle = '#000';
|
||||
@@ -93,6 +108,15 @@ class TetrisRenderer {
|
||||
this.drawNextPiece(nextCtx, gameState.nextPiece);
|
||||
}
|
||||
|
||||
// Draw hold piece
|
||||
if (gameState && gameState.holdPiece) {
|
||||
this.drawHoldPiece(holdCtx, gameState.holdPiece, gameState.canHold);
|
||||
} else if (holdCtx) {
|
||||
// Clear hold canvas if no hold piece
|
||||
holdCtx.fillStyle = '#000';
|
||||
holdCtx.fillRect(0, 0, holdCtx.canvas.width, holdCtx.canvas.height);
|
||||
}
|
||||
|
||||
// Update stats
|
||||
if (gameState) {
|
||||
const linesSpan = info.querySelector('.lines');
|
||||
@@ -222,6 +246,49 @@ class TetrisRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
drawHoldPiece(ctx, piece, canHold) {
|
||||
// Clear
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
|
||||
if (!piece) return;
|
||||
|
||||
// Gray out if cannot hold again this turn
|
||||
const alpha = canHold ? 1.0 : 0.4;
|
||||
|
||||
// Center the piece
|
||||
const pieceWidth = piece.shape[0].length * 20;
|
||||
const pieceHeight = piece.shape.length * 20;
|
||||
const offsetX = (ctx.canvas.width - pieceWidth) / 2;
|
||||
const offsetY = (ctx.canvas.height - pieceHeight) / 2;
|
||||
|
||||
for (let row = 0; row < piece.shape.length; row++) {
|
||||
for (let col = 0; col < piece.shape[row].length; col++) {
|
||||
if (piece.shape[row][col]) {
|
||||
const x = offsetX + col * 20;
|
||||
const y = offsetY + row * 20;
|
||||
|
||||
ctx.fillStyle = piece.color + Math.floor(alpha * 255).toString(16).padStart(2, '0');
|
||||
ctx.fillRect(x, y, 20, 20);
|
||||
|
||||
if (canHold) {
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.3)';
|
||||
ctx.fillRect(x, y, 20, 3);
|
||||
ctx.fillRect(x, y, 3, 20);
|
||||
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.3)';
|
||||
ctx.fillRect(x, y + 17, 20, 3);
|
||||
ctx.fillRect(x + 17, y, 3, 20);
|
||||
|
||||
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(x, y, 20, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setActivePlayer(playerId) {
|
||||
// Remove main class from all boards
|
||||
this.boards.forEach((boardData, id) => {
|
||||
|
||||
Reference in New Issue
Block a user