diff --git a/public/js/game.js b/public/js/game.js index 3885664..6683d9e 100644 --- a/public/js/game.js +++ b/public/js/game.js @@ -366,6 +366,7 @@ class TetrisGame { this.level = state.level; this.gameOver = state.gameOver; this.eliminated = state.eliminated; + this.sequenceIndex = state.sequenceIndex || 0; this.dropInterval = Math.max(100, 1000 - (this.level - 1) * 100); } } diff --git a/server/index.js b/server/index.js index 7ca8a77..b59f2d5 100644 --- a/server/index.js +++ b/server/index.js @@ -15,7 +15,9 @@ const lobby = { players: new Map(), spectators: new Map(), gameStarted: false, - gameInterval: null + gameInterval: null, + pieceQueue: [], + playerSequenceIndex: new Map() }; const PORT = process.env.PORT || 3000; @@ -37,6 +39,36 @@ const BOARD_HEIGHT = 20; const GARBAGE_COLOR = '#666666'; const LOBBY_ROOM = 'global-lobby'; +// 7-bag piece generation system +function generatePieceBag() { + const bag = [...TETROMINO_KEYS]; + // Fisher-Yates shuffle + for (let i = bag.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [bag[i], bag[j]] = [bag[j], bag[i]]; + } + return bag; +} + +function createPieceQueue(bagCount = 14) { + let queue = []; + for (let i = 0; i < bagCount; i++) { + queue = queue.concat(generatePieceBag()); + } + return queue; +} + +function getPieceFromType(pieceType) { + const tetromino = TETROMINOS[pieceType]; + return { + type: pieceType, + shape: JSON.parse(JSON.stringify(tetromino.shape)), + color: tetromino.color, + x: Math.floor(BOARD_WIDTH / 2) - Math.floor(tetromino.shape[0].length / 2), + y: 0 + }; +} + io.on('connection', (socket) => { console.log('Player connected:', socket.id); @@ -206,16 +238,31 @@ io.on('connection', (socket) => { if (!player.canHold) return; if (player.holdPiece === null) { - // First hold - store current piece and spawn next + // First hold - store current piece and get next from queue player.holdPiece = { type: player.currentPiece.type, shape: JSON.parse(JSON.stringify(player.currentPiece.shape)), color: player.currentPiece.color }; player.currentPiece = player.nextPiece; - player.nextPiece = getRandomPiece(); + + // Advance sequence index since we consumed nextPiece + const idx = lobby.playerSequenceIndex.get(playerId); + lobby.playerSequenceIndex.set(playerId, idx + 1); + + // Get new next piece from queue + const nextType = lobby.pieceQueue[idx]; + if (nextType) { + player.nextPiece = getPieceFromType(nextType); + } else { + // Regenerate queue if exhausted + lobby.pieceQueue = createPieceQueue(14); + player.nextPiece = getPieceFromType(lobby.pieceQueue[0]); + lobby.playerSequenceIndex.set(playerId, 1); + } } else { - // Swap with held piece + // Swap with held piece - current piece goes to hold, held piece becomes current + // Don't advance sequence - the current piece is just going into hold const temp = { type: player.currentPiece.type, shape: JSON.parse(JSON.stringify(player.currentPiece.shape)), @@ -228,6 +275,15 @@ io.on('connection', (socket) => { y: 0 }; player.holdPiece = temp; + + // Update nextPiece to be the piece that was just held + player.nextPiece = { + type: temp.type, + shape: JSON.parse(JSON.stringify(temp.shape)), + color: temp.color, + x: Math.floor(BOARD_WIDTH / 2) - Math.floor(temp.shape[0].length / 2), + y: 0 + }; } player.canHold = false; @@ -325,8 +381,22 @@ function getRandomPiece() { } function spawnPiece(player) { + const idx = lobby.playerSequenceIndex.get(player.id); + const pieceType = lobby.pieceQueue[idx]; + + if (!pieceType) { + // Regenerate queue if exhausted + lobby.pieceQueue = createPieceQueue(14); + lobby.playerSequenceIndex.set(player.id, 0); + return spawnPiece(player); + } + + // Advance sequence index for next spawn + lobby.playerSequenceIndex.set(player.id, idx + 1); + player.currentPiece = player.nextPiece; - player.nextPiece = getRandomPiece(); + player.nextPiece = getPieceFromType(pieceType); + if (!player.currentPiece) return false; return isValidPosition(player.currentPiece, player.currentPiece.x, player.currentPiece.y, player.board); } @@ -419,6 +489,15 @@ function getGhostY(piece, board) { function startGame() { lobby.gameStarted = true; + // Initialize shared piece queue + lobby.pieceQueue = createPieceQueue(14); // 98 pieces (~14 bags) + + // Initialize sequence index for each player + lobby.playerSequenceIndex.clear(); + for (const player of lobby.players.values()) { + lobby.playerSequenceIndex.set(player.id, 0); + } + for (const player of lobby.players.values()) { player.board = createEmptyBoard(); player.score = 0; @@ -476,7 +555,8 @@ function getStates() { score: p.score, lines: p.lines, level: p.level, - eliminated: p.eliminated + eliminated: p.eliminated, + sequenceIndex: lobby.playerSequenceIndex.get(p.id) || 0 })); }