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:
@@ -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
@@ -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 {
|
||||
// Swap with held piece
|
||||
// Regenerate queue if exhausted
|
||||
lobby.pieceQueue = createPieceQueue(14);
|
||||
player.nextPiece = getPieceFromType(lobby.pieceQueue[0]);
|
||||
lobby.playerSequenceIndex.set(playerId, 1);
|
||||
}
|
||||
} else {
|
||||
// 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
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user