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
+121
View File
@@ -289,6 +289,19 @@ button:active {
border: 1px solid #333;
}
/* Hold and Next piece previews side by side */
.board-info:has(canvas) {
justify-content: center;
gap: 20px;
padding: 8px 5px;
}
.board-info canvas {
border: 1px solid #333;
background: #000;
}
}
#game-header {
display: flex;
justify-content: space-between;
@@ -399,3 +412,111 @@ button:active {
.player-board.flash {
animation: flash 0.3s ease-in-out;
}
/* Touch Controls */
#touch-controls {
display: none;
width: 100%;
max-width: 400px;
margin-top: 20px;
gap: 20px;
justify-content: center;
align-items: flex-end;
padding-bottom: 20px;
}
#touch-dpad {
display: grid;
grid-template-columns: repeat(3, 70px);
grid-template-rows: repeat(2, 70px);
gap: 8px;
}
#btn-left {
grid-column: 1;
grid-row: 2;
}
#btn-down {
grid-column: 2;
grid-row: 2;
}
#btn-right {
grid-column: 3;
grid-row: 2;
}
#touch-actions {
display: grid;
grid-template-columns: repeat(2, 70px);
grid-template-rows: repeat(2, 70px);
gap: 8px;
}
#btn-rotate {
grid-column: 1;
grid-row: 1;
}
#btn-drop {
grid-column: 2;
grid-row: 1;
}
#btn-hold {
grid-column: 1;
grid-row: 2;
}
.touch-btn {
width: 70px;
height: 70px;
font-size: 1.5rem;
background: #333;
border: 3px solid #555;
border-radius: 10px;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
.touch-btn:active {
background: #0ff;
color: #000;
transform: scale(0.95);
}
.touch-btn.action-btn {
background: #444;
}
.touch-btn.drop-btn {
background: #ff00ff;
border-color: #ff00ff;
}
.touch-btn.hold-btn {
background: #ff8800;
border-color: #ff8800;
font-size: 0.9rem;
}
/* Show touch controls on mobile only */
@media (max-width: 768px) and (hover: none) {
#touch-controls {
display: flex;
}
#battle-grid {
height: 60vh;
}
.player-board.spectator {
display: none;
}
}