Fix Tetris 2P preview block synchronization issue
- Prevent duplicate piece shapes when generating next pieces - Enhanced randomTetromino() with excludeShape parameter - Update piece placement logic to avoid visual duplicates - Improve initial state generation for both players - Add P1 and P2 next piece previews in 2P mode
This commit is contained in:
+65
-18
@@ -58,10 +58,21 @@ interface PlayerState {
|
||||
|
||||
const createBoard = (): Board => Array.from({ length: BOARD_HEIGHT }, () => Array(BOARD_WIDTH).fill(null));
|
||||
|
||||
const randomTetromino = (isP2 = false): Piece => {
|
||||
const randomTetromino = (isP2 = false, excludeShape?: number[][]): Piece => {
|
||||
const keys = Object.keys(TETROMINOS) as TetrominoKey[];
|
||||
const key = keys[Math.floor(Math.random() * keys.length)];
|
||||
const tetromino = isP2 ? TETROMINOS_P2[key] : TETROMINOS[key];
|
||||
let key = keys[Math.floor(Math.random() * keys.length)];
|
||||
let tetromino = isP2 ? TETROMINOS_P2[key] : TETROMINOS[key];
|
||||
|
||||
// Ensure we don't generate the same shape twice in a row
|
||||
if (excludeShape && JSON.stringify(tetromino.shape) === JSON.stringify(excludeShape)) {
|
||||
// Try up to 7 times to get a different shape
|
||||
for (let attempts = 0; attempts < 7; attempts++) {
|
||||
key = keys[Math.floor(Math.random() * keys.length)];
|
||||
tetromino = isP2 ? TETROMINOS_P2[key] : TETROMINOS[key];
|
||||
if (JSON.stringify(tetromino.shape) !== JSON.stringify(excludeShape)) break;
|
||||
}
|
||||
}
|
||||
|
||||
return { shape: tetromino.shape, color: tetromino.color, x: Math.floor(BOARD_WIDTH / 2) - Math.floor(tetromino.shape[0].length / 2), y: 0 };
|
||||
};
|
||||
|
||||
@@ -93,21 +104,29 @@ const Tetris = () => {
|
||||
const [lines, setLines] = useState(0);
|
||||
|
||||
// 2P state
|
||||
const [player1, setPlayer1] = useState<PlayerState>({
|
||||
board: createBoard(),
|
||||
piece: randomTetromino(),
|
||||
nextPiece: randomTetromino(),
|
||||
score: 0,
|
||||
lines: 0,
|
||||
gameOver: false,
|
||||
const [player1, setPlayer1] = useState<PlayerState>(() => {
|
||||
const p1Piece = randomTetromino();
|
||||
const p1Next = randomTetromino(false, p1Piece.shape);
|
||||
return {
|
||||
board: createBoard(),
|
||||
piece: p1Piece,
|
||||
nextPiece: p1Next,
|
||||
score: 0,
|
||||
lines: 0,
|
||||
gameOver: false,
|
||||
};
|
||||
});
|
||||
const [player2, setPlayer2] = useState<PlayerState>({
|
||||
board: createBoard(),
|
||||
piece: randomTetromino(true),
|
||||
nextPiece: randomTetromino(true),
|
||||
score: 0,
|
||||
lines: 0,
|
||||
gameOver: false,
|
||||
const [player2, setPlayer2] = useState<PlayerState>(() => {
|
||||
const p2Piece = randomTetromino(true);
|
||||
const p2Next = randomTetromino(true, p2Piece.shape);
|
||||
return {
|
||||
board: createBoard(),
|
||||
piece: p2Piece,
|
||||
nextPiece: p2Next,
|
||||
score: 0,
|
||||
lines: 0,
|
||||
gameOver: false,
|
||||
};
|
||||
});
|
||||
const [winner, setWinner] = useState<string | null>(null);
|
||||
|
||||
@@ -364,7 +383,7 @@ const Tetris = () => {
|
||||
else playSound('click');
|
||||
|
||||
const newTetromino = prev.nextPiece;
|
||||
const nextNext = randomTetromino(isP2);
|
||||
const nextNext = randomTetromino(isP2, prev.nextPiece.shape);
|
||||
|
||||
if (!isValidMove(newTetromino, clearedBoard)) {
|
||||
playSound('error');
|
||||
@@ -625,6 +644,20 @@ const Tetris = () => {
|
||||
{player1.gameOver && <span className="font-pixel text-xs text-destructive">GAME OVER</span>}
|
||||
</div>
|
||||
|
||||
{/* P1 Next Piece */}
|
||||
<div className="border-2 border-primary/50 p-2 bg-background/50 flex flex-col items-center justify-center min-h-[60px]">
|
||||
<p className="font-pixel text-[10px] text-foreground/60">P1 NEXT</p>
|
||||
<div className="flex flex-col gap-1">
|
||||
{player1.nextPiece.shape.map((row, y) => (
|
||||
<div key={y} className="flex gap-1">
|
||||
{row.map((val, x) => (
|
||||
<div key={`${y}-${x}`} className={`w-2 h-2 ${val ? 'bg-primary box-glow' : 'bg-transparent'}`} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Center controls */}
|
||||
<div className="flex flex-col gap-2 min-w-[100px] items-center">
|
||||
<div className="border border-primary/50 p-2 bg-background/50 text-center">
|
||||
@@ -657,6 +690,20 @@ const Tetris = () => {
|
||||
</div>
|
||||
{player2.gameOver && <span className="font-pixel text-xs text-destructive">GAME OVER</span>}
|
||||
</div>
|
||||
|
||||
{/* P2 Next Piece */}
|
||||
<div className="border-2 border-purple-500/50 p-2 bg-background/50 flex flex-col items-center justify-center min-h-[60px]">
|
||||
<p className="font-pixel text-[10px] text-purple-400">P2 NEXT</p>
|
||||
<div className="flex flex-col gap-1">
|
||||
{player2.nextPiece.shape.map((row, y) => (
|
||||
<div key={y} className="flex gap-1">
|
||||
{row.map((val, x) => (
|
||||
<div key={`${y}-${x}`} className={`w-2 h-2 ${val ? 'bg-purple-400 box-glow' : 'bg-transparent'}`} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{/* P2 Next Piece */}
|
||||
<div className="border-2 border-purple-500/50 p-3 bg-background/50 flex flex-col items-center justify-center min-h-[60px]">
|
||||
<p className="font-pixel text-[10px] text-purple-400">P2 NEXT</p>
|
||||
<div className="flex flex-col gap-1">
|
||||
{player2.nextPiece.shape.map((row, y) => (
|
||||
<div key={y} className="flex gap-1">
|
||||
{row.map((val, x) => (
|
||||
<div key={`${y}-${x}`} className={`w-2 h-2 ${val ? 'bg-purple-400 box-glow' : 'bg-transparent'}`} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Center controls */}
|
||||
Reference in New Issue
Block a user