Add ghost piece preview showing where block will land
- 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
This commit is contained in:
+6
-6
@@ -7,7 +7,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="css/style.css?v=6">
|
<link rel="stylesheet" href="css/style.css?v=7">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
@@ -61,10 +61,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
<script src="js/network.js?v=3"></script>
|
<script src="js/network.js?v=4"></script>
|
||||||
<script src="js/game.js?v=3"></script>
|
<script src="js/game.js?v=4"></script>
|
||||||
<script src="js/renderer.js?v=3"></script>
|
<script src="js/renderer.js?v=4"></script>
|
||||||
<script src="js/ui.js?v=3"></script>
|
<script src="js/ui.js?v=4"></script>
|
||||||
<script src="js/app.js?v=3"></script>
|
<script src="js/app.js?v=4"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -280,6 +280,16 @@ class TetrisGame {
|
|||||||
this.dropInterval = Math.max(100, 1000 - (this.level - 1) * 100);
|
this.dropInterval = Math.max(100, 1000 - (this.level - 1) * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGhostY() {
|
||||||
|
if (!this.currentPiece) return null;
|
||||||
|
|
||||||
|
let ghostY = this.currentPiece.y;
|
||||||
|
while (this.isValidPosition(this.currentPiece.x, ghostY + 1, this.currentPiece.shape)) {
|
||||||
|
ghostY++;
|
||||||
|
}
|
||||||
|
return ghostY;
|
||||||
|
}
|
||||||
|
|
||||||
receiveGarbage(rows) {
|
receiveGarbage(rows) {
|
||||||
if (this.gameOver) return;
|
if (this.gameOver) return;
|
||||||
|
|
||||||
@@ -328,10 +338,12 @@ class TetrisGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getState() {
|
getState() {
|
||||||
|
const ghostY = this.currentPiece ? this.getGhostY() : null;
|
||||||
return {
|
return {
|
||||||
playerId: this.playerId,
|
playerId: this.playerId,
|
||||||
board: JSON.parse(JSON.stringify(this.board)),
|
board: JSON.parse(JSON.stringify(this.board)),
|
||||||
currentPiece: this.currentPiece ? JSON.parse(JSON.stringify(this.currentPiece)) : null,
|
currentPiece: this.currentPiece ? JSON.parse(JSON.stringify(this.currentPiece)) : null,
|
||||||
|
ghostY: ghostY,
|
||||||
nextPiece: this.nextPiece ? JSON.parse(JSON.stringify(this.nextPiece)) : null,
|
nextPiece: this.nextPiece ? JSON.parse(JSON.stringify(this.nextPiece)) : null,
|
||||||
holdPiece: this.holdPiece ? JSON.parse(JSON.stringify(this.holdPiece)) : null,
|
holdPiece: this.holdPiece ? JSON.parse(JSON.stringify(this.holdPiece)) : null,
|
||||||
canHold: this.canHold,
|
canHold: this.canHold,
|
||||||
|
|||||||
@@ -98,6 +98,11 @@ class TetrisRenderer {
|
|||||||
this.drawBoard(ctx, gameState.board);
|
this.drawBoard(ctx, gameState.board);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw ghost piece (landing preview)
|
||||||
|
if (gameState && gameState.currentPiece && !gameState.gameOver && gameState.ghostY !== null) {
|
||||||
|
this.drawGhostPiece(ctx, gameState.currentPiece, gameState.ghostY);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw current piece
|
// Draw current piece
|
||||||
if (gameState && gameState.currentPiece && !gameState.gameOver) {
|
if (gameState && gameState.currentPiece && !gameState.gameOver) {
|
||||||
this.drawPiece(ctx, gameState.currentPiece);
|
this.drawPiece(ctx, gameState.currentPiece);
|
||||||
@@ -210,6 +215,34 @@ class TetrisRenderer {
|
|||||||
ctx.strokeRect(px, py, CELL_SIZE, CELL_SIZE);
|
ctx.strokeRect(px, py, CELL_SIZE, CELL_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawGhostPiece(ctx, piece, ghostY) {
|
||||||
|
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 = piece.x + col;
|
||||||
|
const y = ghostY + row;
|
||||||
|
if (y >= 0) {
|
||||||
|
this.drawGhostCell(ctx, x, y, piece.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawGhostCell(ctx, x, y, color) {
|
||||||
|
const px = x * CELL_SIZE;
|
||||||
|
const py = y * CELL_SIZE;
|
||||||
|
|
||||||
|
// Semi-transparent fill with outlined border effect
|
||||||
|
ctx.strokeStyle = color + '80'; // 50% opacity border
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeRect(px + 2, py + 2, CELL_SIZE - 4, CELL_SIZE - 4);
|
||||||
|
|
||||||
|
// Light fill inside
|
||||||
|
ctx.fillStyle = color + '20'; // 12.5% opacity
|
||||||
|
ctx.fillRect(px + 3, py + 3, CELL_SIZE - 6, CELL_SIZE - 6);
|
||||||
|
}
|
||||||
|
|
||||||
drawNextPiece(ctx, piece) {
|
drawNextPiece(ctx, piece) {
|
||||||
// Clear
|
// Clear
|
||||||
ctx.fillStyle = '#000';
|
ctx.fillStyle = '#000';
|
||||||
|
|||||||
@@ -338,6 +338,16 @@ function addGarbageToPlayer(player) {
|
|||||||
// Elimination is handled by spawnPiece when a new piece can't fit
|
// Elimination is handled by spawnPiece when a new piece can't fit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getGhostY(piece, board) {
|
||||||
|
if (!piece) return null;
|
||||||
|
|
||||||
|
let ghostY = piece.y;
|
||||||
|
while (isValidPosition(piece, piece.x, ghostY + 1, board)) {
|
||||||
|
ghostY++;
|
||||||
|
}
|
||||||
|
return ghostY;
|
||||||
|
}
|
||||||
|
|
||||||
function startGame() {
|
function startGame() {
|
||||||
lobby.gameStarted = true;
|
lobby.gameStarted = true;
|
||||||
|
|
||||||
@@ -391,6 +401,7 @@ function getStates() {
|
|||||||
playerId: p.id,
|
playerId: p.id,
|
||||||
board: JSON.parse(JSON.stringify(p.board)),
|
board: JSON.parse(JSON.stringify(p.board)),
|
||||||
currentPiece: p.currentPiece ? JSON.parse(JSON.stringify(p.currentPiece)) : null,
|
currentPiece: p.currentPiece ? JSON.parse(JSON.stringify(p.currentPiece)) : null,
|
||||||
|
ghostY: p.currentPiece ? getGhostY(p.currentPiece, p.board) : null,
|
||||||
nextPiece: p.nextPiece ? JSON.parse(JSON.stringify(p.nextPiece)) : null,
|
nextPiece: p.nextPiece ? JSON.parse(JSON.stringify(p.nextPiece)) : null,
|
||||||
holdPiece: p.holdPiece ? JSON.parse(JSON.stringify(p.holdPiece)) : null,
|
holdPiece: p.holdPiece ? JSON.parse(JSON.stringify(p.holdPiece)) : null,
|
||||||
canHold: p.canHold,
|
canHold: p.canHold,
|
||||||
|
|||||||
Reference in New Issue
Block a user