Fix garbage elimination to check board overflow after piece locks

- Move elimination check from addGarbageToPlayer to lockPiece
- Check if top 2 rows have blocks after piece locks (board overflow)
- Re-compute opponents in sendGarbage loop to skip eliminated players
- Preserve game state after game over for viewing final results
- Reset all player variables at start of new game

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 04:11:16 +00:00
parent 1b11a60acc
commit 656d5041b2
+51 -19
View File
@@ -405,6 +405,7 @@ function spawnPiece(player) {
function lockPiece(player) { function lockPiece(player) {
if (!player.currentPiece) return; if (!player.currentPiece) return;
// Lock piece into board
for (let row = 0; row < player.currentPiece.shape.length; row++) { for (let row = 0; row < player.currentPiece.shape.length; row++) {
for (let col = 0; col < player.currentPiece.shape[row].length; col++) { for (let col = 0; col < player.currentPiece.shape[row].length; col++) {
if (player.currentPiece.shape[row][col]) { if (player.currentPiece.shape[row][col]) {
@@ -419,10 +420,29 @@ function lockPiece(player) {
const rowsCleared = clearRows(player); const rowsCleared = clearRows(player);
// Check if board overflowed (top 2 rows have blocks) - this means player is eliminated
let boardOverflowed = false;
for (let row = 0; row < 2; row++) {
for (let col = 0; col < BOARD_WIDTH; col++) {
if (player.board[row][col] !== 0) {
boardOverflowed = true;
break;
}
}
if (boardOverflowed) break;
}
if (boardOverflowed) {
player.eliminated = true;
console.log(`[ELIMINATION] ${player.name} eliminated - board overflowed`);
broadcastState();
checkGameOver();
return;
}
const spawnResult = spawnPiece(player); const spawnResult = spawnPiece(player);
if (!spawnResult) { if (!spawnResult) {
player.eliminated = true; player.eliminated = true;
console.log(`[ELIMINATION] ${player.name} eliminated - piece could not spawn (board full at y=${player.currentPiece?.y})`); console.log(`[ELIMINATION] ${player.name} eliminated - piece could not spawn`);
} }
// Reset canHold for the new piece // Reset canHold for the new piece
@@ -456,20 +476,23 @@ function clearRows(player) {
function sendGarbage(sender, rowsCleared) { function sendGarbage(sender, rowsCleared) {
// Number of garbage rows equals number of lines cleared // Number of garbage rows equals number of lines cleared
const opponents = Array.from(lobby.players.values()).filter(p => p.id !== sender.id && !p.eliminated);
if (opponents.length === 0) {
console.log(`[GARBAGE] ${sender.name} cleared ${rowsCleared} row(s) but no opponents to send garbage to`);
return;
}
const garbageLog = []; const garbageLog = [];
for (let i = 0; i < rowsCleared; i++) { for (let i = 0; i < rowsCleared; i++) {
// Re-compute opponents each iteration to exclude players eliminated by previous garbage
const opponents = Array.from(lobby.players.values()).filter(p => p.id !== sender.id && !p.eliminated);
if (opponents.length === 0) {
break;
}
const target = opponents[Math.floor(Math.random() * opponents.length)]; const target = opponents[Math.floor(Math.random() * opponents.length)];
garbageLog.push(target.name); garbageLog.push(target.name);
addGarbageToPlayer(target, sender.name); addGarbageToPlayer(target, sender.name);
} }
if (garbageLog.length > 0) {
console.log(`[GARBAGE] ${sender.name} cleared ${rowsCleared} row(s) -> sent garbage to: ${garbageLog.join(', ')}`); console.log(`[GARBAGE] ${sender.name} cleared ${rowsCleared} row(s) -> sent garbage to: ${garbageLog.join(', ')}`);
} else {
console.log(`[GARBAGE] ${sender.name} cleared ${rowsCleared} row(s) but no opponents to send garbage to`);
}
} }
function addGarbageToPlayer(player, senderName) { function addGarbageToPlayer(player, senderName) {
@@ -484,17 +507,9 @@ function addGarbageToPlayer(player, senderName) {
player.garbageReceived.push({ rows: 1, sender: senderName }); player.garbageReceived.push({ rows: 1, sender: senderName });
// Push current piece up by 1 row if it exists (y decreases when moving up) // Push current piece up by 1 row if it exists (y decreases when moving up)
// Don't eliminate here - let the piece lock and check board overflow then
if (player.currentPiece) { if (player.currentPiece) {
const oldY = player.currentPiece.y;
player.currentPiece.y--; player.currentPiece.y--;
// Eliminate if the piece is pushed above the board or collides
if (player.currentPiece.y < 0) {
player.eliminated = true;
console.log(`[ELIMINATION] ${player.name} eliminated by ${senderName} - piece pushed above board (y=${oldY} -> ${player.currentPiece.y})`);
} else if (!isValidPosition(player.currentPiece, player.currentPiece.x, player.currentPiece.y, player.board)) {
player.eliminated = true;
console.log(`[ELIMINATION] ${player.name} eliminated by ${senderName} - piece collision after garbage (piece at x=${player.currentPiece.x}, y=${player.currentPiece.y})`);
}
} }
} }
@@ -514,6 +529,7 @@ function startGame() {
// Initialize shared piece queue // Initialize shared piece queue
lobby.pieceQueue = createPieceQueue(14); // 98 pieces (~14 bags) lobby.pieceQueue = createPieceQueue(14); // 98 pieces (~14 bags)
// Reset all player variables for the new game
for (const player of lobby.players.values()) { for (const player of lobby.players.values()) {
player.board = createEmptyBoard(); player.board = createEmptyBoard();
player.score = 0; player.score = 0;
@@ -521,9 +537,12 @@ function startGame() {
player.level = 1; player.level = 1;
player.eliminated = false; player.eliminated = false;
player.dropInterval = 1000; player.dropInterval = 1000;
player.dropCounter = 0;
player.holdPiece = null; player.holdPiece = null;
player.canHold = true; player.canHold = true;
player.garbageReceived = []; player.garbageReceived = [];
player.currentPiece = null;
player.nextPiece = null;
lobby.playerSequenceIndex.set(player.id, 0); lobby.playerSequenceIndex.set(player.id, 0);
} }
@@ -593,13 +612,19 @@ function checkGameOver() {
console.log(`[GAMEOVER CHECK] Active players: ${activePlayers.length} (${activePlayers.map(p => p.name).join(', ')})`); console.log(`[GAMEOVER CHECK] Active players: ${activePlayers.length} (${activePlayers.map(p => p.name).join(', ')})`);
if (activePlayers.length <= 1) { if (activePlayers.length <= 1) {
console.log(`[GAME OVER] ${activePlayers.length === 1 ? `Winner: ${activePlayers[0].name}` : 'No winner (all eliminated)'}`); console.log(`[GAME OVER] ${activePlayers.length === 1 ? `Winner: ${activePlayers[0].name}` : 'No winner (all eliminated)'}`);
io.to(LOBBY_ROOM).emit('game-over', { states: getStates() });
// Stop the game interval but keep game state visible for viewing
if (lobby.gameInterval) { if (lobby.gameInterval) {
clearInterval(lobby.gameInterval); clearInterval(lobby.gameInterval);
lobby.gameInterval = null; lobby.gameInterval = null;
} }
lobby.gameStarted = false; lobby.gameStarted = false;
// Set all players to not ready for next game (but keep their final state)
for (const player of lobby.players.values()) {
player.ready = false;
}
// Move spectators to players for next round // Move spectators to players for next round
for (const [id, spectator] of lobby.spectators.entries()) { for (const [id, spectator] of lobby.spectators.entries()) {
const player = { const player = {
@@ -611,12 +636,16 @@ function checkGameOver() {
board: createEmptyBoard(), board: createEmptyBoard(),
currentPiece: null, currentPiece: null,
nextPiece: null, nextPiece: null,
holdPiece: null,
canHold: true,
eliminated: false, eliminated: false,
ready: false, ready: false,
dropCounter: 0, dropCounter: 0,
dropInterval: 1000 dropInterval: 1000,
garbageReceived: []
}; };
lobby.players.set(id, player); lobby.players.set(id, player);
lobby.playerSequenceIndex.set(id, 0);
spectator.socket = io.sockets.sockets.get(id); spectator.socket = io.sockets.sockets.get(id);
if (spectator.socket) { if (spectator.socket) {
spectator.socket.data.playerName = spectator.name; spectator.socket.data.playerName = spectator.name;
@@ -624,13 +653,16 @@ function checkGameOver() {
} }
lobby.spectators.clear(); lobby.spectators.clear();
// Broadcast game over with final states
io.to(LOBBY_ROOM).emit('game-over', { states: getStates() });
// Broadcast updated player list // Broadcast updated player list
io.to(LOBBY_ROOM).emit('player-joined', { io.to(LOBBY_ROOM).emit('player-joined', {
player: null, player: null,
players: getPlayersList() players: getPlayersList()
}); });
console.log(`Game over. Moved ${activePlayers.length === 1 ? 0 : lobby.players.size} players to next round`); console.log(`Game over. Reset ${lobby.players.size} players for next round`);
} }
} }