Implement shared 7-bag piece sequence

- Add generatePieceBag() and createPieceQueue() for 7-bag system
- Add pieceQueue and playerSequenceIndex to lobby state
- Modify spawnPiece() to consume from shared queue per player
- Update player-hold handler to manage sequence index on hold
- Add sequenceIndex to state updates for tracking
- Update client loadState() to receive sequenceIndex

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 01:37:55 +00:00
parent bdeb6c8849
commit 634c6e8eab
2 changed files with 87 additions and 6 deletions
+1
View File
@@ -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);
}
}
+86 -6
View File
@@ -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
}));
}