From 11ad5cf6692ca8a5d545a68979676eea7c243f3d Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:11:35 +0000 Subject: [PATCH] Changes --- src/components/FullscreenPortal.tsx | 21 -- src/pages/AIChat.tsx | 3 +- src/pages/Tetris.tsx | 296 ++++++++++++++-------------- 3 files changed, 146 insertions(+), 174 deletions(-) delete mode 100644 src/components/FullscreenPortal.tsx diff --git a/src/components/FullscreenPortal.tsx b/src/components/FullscreenPortal.tsx deleted file mode 100644 index 84c06b4..0000000 --- a/src/components/FullscreenPortal.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ReactNode, useEffect, useState } from 'react'; -import { createPortal } from 'react-dom'; - -interface FullscreenPortalProps { - children: ReactNode; -} - -/** - * Portals children to document.body to escape any transformed ancestors. - * This is important because CSS transforms on parents can break `position: fixed`. - */ -export const FullscreenPortal = ({ children }: FullscreenPortalProps) => { - const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - }, []); - - if (!mounted || typeof document === 'undefined') return null; - return createPortal(children, document.body); -}; diff --git a/src/pages/AIChat.tsx b/src/pages/AIChat.tsx index 11bfb6c..4be3fe6 100644 --- a/src/pages/AIChat.tsx +++ b/src/pages/AIChat.tsx @@ -9,7 +9,6 @@ import { useAchievements, incrementAiMessageCount } from '@/contexts/Achievement import { useToast } from '@/hooks/use-toast'; import GlitchText from '@/components/GlitchText'; import MessageContent from '@/components/MessageContent'; -import { FullscreenPortal } from '@/components/FullscreenPortal'; import { AI_PROVIDERS, AIProvider, getProvider, CUSTOM_API_STORAGE_KEY } from '@/lib/aiProviders'; import { DropdownMenu, @@ -690,7 +689,7 @@ const AIChat = () => { ); - return isFullscreen ? {content} : content; + return content; }; export default AIChat; diff --git a/src/pages/Tetris.tsx b/src/pages/Tetris.tsx index 352b910..10f49bb 100644 --- a/src/pages/Tetris.tsx +++ b/src/pages/Tetris.tsx @@ -5,10 +5,8 @@ import { useAchievements } from '@/contexts/AchievementsContext'; import { Link } from 'react-router-dom'; import GlitchCrash from '@/components/GlitchCrash'; import { Maximize2, Minimize2, Users, User } from 'lucide-react'; -import { useBrowserFullscreen } from '@/hooks/useGameDimensions'; import GameTouchButton from '@/components/GameTouchButton'; import { MobileControlsDock, MobileControlsSpacer } from '@/components/MobileControlsDock'; -import { FullscreenPortal } from '@/components/FullscreenPortal'; const BOARD_WIDTH = 10; const BOARD_HEIGHT = 20; @@ -64,9 +62,7 @@ const randomTetromino = (isP2 = false, excludeShape?: number[][]): Piece => { 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]; @@ -93,10 +89,8 @@ const Tetris = () => { const { playSound } = useSettings(); const { checkGameScoreAchievements, unlockMaxScore } = useAchievements(); - // Mode selection const [gameMode, setGameMode] = useState(null); - // 1P state const [board, setBoard] = useState(createBoard); const [piece, setPiece] = useState(randomTetromino); const [nextPiece, setNextPiece] = useState(randomTetromino); @@ -104,34 +98,18 @@ const Tetris = () => { const [highScore, setHighScore] = useState(0); const [lines, setLines] = useState(0); - // 2P state const [player1, setPlayer1] = useState(() => { const p1Piece = randomTetromino(); const p1Next = randomTetromino(false, p1Piece.shape); - return { - board: createBoard(), - piece: p1Piece, - nextPiece: p1Next, - score: 0, - lines: 0, - gameOver: false, - }; + return { board: createBoard(), piece: p1Piece, nextPiece: p1Next, score: 0, lines: 0, gameOver: false }; }); const [player2, setPlayer2] = useState(() => { const p2Piece = randomTetromino(true); const p2Next = randomTetromino(true, p2Piece.shape); - return { - board: createBoard(), - piece: p2Piece, - nextPiece: p2Next, - score: 0, - lines: 0, - gameOver: false, - }; + return { board: createBoard(), piece: p2Piece, nextPiece: p2Next, score: 0, lines: 0, gameOver: false }; }); const [winner, setWinner] = useState(null); - // Common state const [gameOver, setGameOver] = useState(false); const [gameComplete, setGameComplete] = useState(false); const [isPaused, setIsPaused] = useState(false); @@ -139,7 +117,6 @@ const Tetris = () => { const [showGlitchCrash, setShowGlitchCrash] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const gameRef = useRef(null); - const { enterFullscreen, exitFullscreen } = useBrowserFullscreen(); const getCellSize = useCallback(() => { if (typeof window === 'undefined') return 28; @@ -149,9 +126,7 @@ const Tetris = () => { const maxHeight = window.innerHeight - 440; return Math.min(Math.floor(maxWidth / BOARD_WIDTH), Math.floor(maxHeight / BOARD_HEIGHT), 24); } - if (gameMode === '2p') { - return isFullscreen ? 26 : 22; - } + if (gameMode === '2p') return isFullscreen ? 26 : 22; return isFullscreen ? 34 : 28; }, [isFullscreen, gameMode]); @@ -164,30 +139,24 @@ const Tetris = () => { return () => window.removeEventListener('resize', handleResize); }, [getCellSize]); - useEffect(() => { - setCellSize(getCellSize()); - }, [isFullscreen, getCellSize, gameMode]); + useEffect(() => { setCellSize(getCellSize()); }, [isFullscreen, getCellSize, gameMode]); useEffect(() => { - if (gameStarted && isMobile && !isFullscreen) { - setIsFullscreen(true); - enterFullscreen(); - } - }, [gameStarted, isMobile, isFullscreen, enterFullscreen]); + if (gameStarted && isMobile && !isFullscreen) setIsFullscreen(true); + }, [gameStarted, isMobile, isFullscreen]); - const toggleFullscreen = async () => { - if (!isFullscreen) { setIsFullscreen(true); await enterFullscreen(); } - else { setIsFullscreen(false); await exitFullscreen(); } + const toggleFullscreen = () => { + setIsFullscreen(prev => !prev); playSound('click'); }; useEffect(() => { const handleEscape = (e: KeyboardEvent) => { - if (e.key === 'Escape' && isFullscreen) { setIsFullscreen(false); exitFullscreen(); } + if (e.key === 'Escape' && isFullscreen) setIsFullscreen(false); }; window.addEventListener('keydown', handleEscape); return () => window.removeEventListener('keydown', handleEscape); - }, [isFullscreen, exitFullscreen]); + }, [isFullscreen]); useEffect(() => { const saved = localStorage.getItem(HIGHSCORE_KEY); @@ -236,7 +205,6 @@ const Tetris = () => { return { board: newBoard, cleared }; }, []); - // 1P movement functions const moveDown = useCallback(() => { if (gameOver || gameComplete || isPaused || !gameStarted || gameMode !== '1p') return; const newPiece = { ...piece, y: piece.y + 1 }; @@ -292,7 +260,6 @@ const Tetris = () => { const startGame = (mode: GameMode) => { setGameMode(mode); - if (mode === '1p') { setBoard(createBoard()); setPiece(randomTetromino()); @@ -300,25 +267,10 @@ const Tetris = () => { setScore(0); setLines(0); } else { - setPlayer1({ - board: createBoard(), - piece: randomTetromino(), - nextPiece: randomTetromino(), - score: 0, - lines: 0, - gameOver: false, - }); - setPlayer2({ - board: createBoard(), - piece: randomTetromino(true), - nextPiece: randomTetromino(true), - score: 0, - lines: 0, - gameOver: false, - }); + setPlayer1({ board: createBoard(), piece: randomTetromino(), nextPiece: randomTetromino(), score: 0, lines: 0, gameOver: false }); + setPlayer2({ board: createBoard(), piece: randomTetromino(true), nextPiece: randomTetromino(true), score: 0, lines: 0, gameOver: false }); setWinner(null); } - setGameOver(false); setGameComplete(false); setIsPaused(false); @@ -333,82 +285,60 @@ const Tetris = () => { playSound('click'); }; - // 2P movement helpers const movePlayer = useCallback((playerNum: 1 | 2, action: 'left' | 'right' | 'down' | 'rotate' | 'drop') => { if (gameOver || isPaused || !gameStarted || gameMode !== '2p') return; - const setPlayer = playerNum === 1 ? setPlayer1 : setPlayer2; const player = playerNum === 1 ? player1 : player2; const isP2 = playerNum === 2; - if (player.gameOver) return; setPlayer(prev => { let newPiece = { ...prev.piece }; - switch (action) { case 'left': newPiece.x--; if (!isValidMove(newPiece, prev.board)) return prev; playSound('hover'); return { ...prev, piece: newPiece }; - case 'right': newPiece.x++; if (!isValidMove(newPiece, prev.board)) return prev; playSound('hover'); return { ...prev, piece: newPiece }; - case 'rotate': newPiece.shape = rotate(prev.piece.shape); if (!isValidMove(newPiece, prev.board)) return prev; playSound('hover'); return { ...prev, piece: newPiece }; - case 'drop': while (isValidMove({ ...newPiece, y: newPiece.y + 1 }, prev.board)) newPiece.y++; playSound('click'); return { ...prev, piece: newPiece }; - case 'down': newPiece.y++; - if (isValidMove(newPiece, prev.board)) { - return { ...prev, piece: newPiece }; - } else { + if (isValidMove(newPiece, prev.board)) return { ...prev, piece: newPiece }; + else { const mergedBoard = mergePiece(prev.board, prev.piece); const { board: clearedBoard, cleared } = clearLines(mergedBoard); const newScore = prev.score + (cleared > 0 ? cleared * 100 * cleared : 0); const newLines = prev.lines + cleared; - if (cleared > 0) playSound('success'); else playSound('click'); - const newTetromino = prev.nextPiece; const nextNext = randomTetromino(isP2, prev.nextPiece.shape); - if (!isValidMove(newTetromino, clearedBoard)) { playSound('error'); return { ...prev, board: clearedBoard, score: newScore, lines: newLines, gameOver: true }; } - - return { - ...prev, - board: clearedBoard, - piece: newTetromino, - nextPiece: nextNext, - score: newScore, - lines: newLines, - }; + return { ...prev, board: clearedBoard, piece: newTetromino, nextPiece: nextNext, score: newScore, lines: newLines }; } } return prev; }); }, [gameOver, isPaused, gameStarted, gameMode, player1, player2, isValidMove, mergePiece, clearLines, playSound]); - // Check 2P win condition useEffect(() => { if (gameMode !== '2p' || !gameStarted) return; - if (player1.gameOver && player2.gameOver) { setGameOver(true); setWinner(player1.score > player2.score ? 'P1 Wins!' : player2.score > player1.score ? 'P2 Wins!' : 'Draw!'); @@ -421,11 +351,9 @@ const Tetris = () => { } }, [player1.gameOver, player2.gameOver, player1.score, player2.score, gameMode, gameStarted]); - // Keyboard handler useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (!gameStarted) return; - if (gameMode === '1p') { switch (e.key) { case 'ArrowLeft': case 'a': e.preventDefault(); moveLeft(); break; @@ -436,20 +364,16 @@ const Tetris = () => { case 'p': e.preventDefault(); togglePause(); break; } } else { - // P1: WASD + Q for drop if (e.key === 'w' || e.key === 'W') { e.preventDefault(); movePlayer(1, 'rotate'); } if (e.key === 'a' || e.key === 'A') { e.preventDefault(); movePlayer(1, 'left'); } if (e.key === 's' || e.key === 'S') { e.preventDefault(); movePlayer(1, 'down'); } if (e.key === 'd' || e.key === 'D') { e.preventDefault(); movePlayer(1, 'right'); } if (e.key === 'q' || e.key === 'Q') { e.preventDefault(); movePlayer(1, 'drop'); } - - // P2: Arrows + / for drop if (e.key === 'ArrowUp') { e.preventDefault(); movePlayer(2, 'rotate'); } if (e.key === 'ArrowLeft') { e.preventDefault(); movePlayer(2, 'left'); } if (e.key === 'ArrowDown') { e.preventDefault(); movePlayer(2, 'down'); } if (e.key === 'ArrowRight') { e.preventDefault(); movePlayer(2, 'right'); } if (e.key === '/') { e.preventDefault(); movePlayer(2, 'drop'); } - if (e.key === 'p' || e.key === 'P') { e.preventDefault(); togglePause(); } } }; @@ -457,14 +381,12 @@ const Tetris = () => { return () => window.removeEventListener('keydown', handleKeyDown); }, [moveLeft, moveRight, moveDown, rotatePiece, hardDrop, gameStarted, gameMode, movePlayer]); - // 1P tick useEffect(() => { if (!gameStarted || gameOver || gameComplete || isPaused || gameMode !== '1p') return; const interval = setInterval(moveDown, TICK_SPEED); return () => clearInterval(interval); }, [moveDown, gameStarted, gameOver, gameComplete, isPaused, gameMode]); - // 2P tick useEffect(() => { if (!gameStarted || gameOver || isPaused || gameMode !== '2p') return; const interval = setInterval(() => { @@ -472,7 +394,7 @@ const Tetris = () => { if (!player2.gameOver) movePlayer(2, 'down'); }, TICK_SPEED); return () => clearInterval(interval); - }, [gameStarted, gameOver, isPaused, gameMode]); + }, [gameStarted, gameOver, isPaused, gameMode, player1.gameOver, player2.gameOver, movePlayer]); const renderBoard1P = () => { const displayBoard = board.map(row => [...row]); @@ -512,70 +434,51 @@ const Tetris = () => { return displayBoard.map((row, y) => (
{row.map((cell, x) => ( -
+
))}
)); }; + const renderNextPiece = (nextP: Piece) => ( +
+ {nextP.shape.map((row, y) => ( +
+ {row.map((val, x) => ( +
+ ))} +
+ ))} +
+ ); + if (showGlitchCrash) return window.location.reload()} />; - // Mode selection screen if (!gameMode) { return ( - +
{'<'} Back

Tetris

-

Select game mode

-
- - {!isMobile && ( - )}
- - {isMobile && ( -

- 2 Player mode requires a keyboard and is not available on mobile devices -

- )} - + {isMobile &&

2 Player mode requires a keyboard and is not available on mobile devices

}

HIGH SCORE: {highScore.toLocaleString()}

@@ -584,7 +487,7 @@ const Tetris = () => { ); } - const content = ( + return (
@@ -605,37 +508,128 @@ const Tetris = () => {
{renderBoard1P()}
{!isMobile && (
- {/* Next Piece Preview */}

Next

-
-
- {nextPiece.shape.map((row, y) => ( -
- {row.map((val, x) => ( -
- ))} -
- ))} -
+
{renderNextPiece(nextPiece)}
+
+
+

Score

+

{score.toLocaleString()}

+
+
+

High Score

+

{highScore.toLocaleString()}

+

max: 4,294,967,296

+
+
+

Lines

+

{lines}

+
+
+

Controls

+
+

← → / A D: Move

+

↑ / W: Rotate

+

↓ / S: Soft drop

+

Space: Hard drop

+

P: Pause

- {/* ... keep existing code (rest of sidebar / stats / UI) */} + {!gameStarted || gameOver ? ( + + ) : ( + + )}
)} ) : ( - <> - {/* ... keep existing code (2P layout) */} - +
+
+

P1 (WASD + Q)

+
{renderBoard2P(player1, 'border-primary/20')}
+
+

Score

{player1.score}

+

Lines

{player1.lines}

+
+
+

Next

+ {renderNextPiece(player1.nextPiece)} +
+
+
+

P2 (Arrows + /)

+
{renderBoard2P(player2, 'border-purple-500/20')}
+
+

Score

{player2.score}

+

Lines

{player2.lines}

+
+
+

Next

+ {renderNextPiece(player2.nextPiece)} +
+
+
)}
- {/* ... keep existing code (mobile controls + overlays) */} + {isMobile && gameMode === '1p' && ( + <> + + +
+
+

SCORE

{score.toLocaleString()}

+

HIGH

{highScore.toLocaleString()}

+

LINES

{lines}

+
+ {gameStarted && !gameOver ? ( +
+ + + + + + +
+ ) : ( + + )} +
+
+ + )} + + {!isMobile && (gameOver || isPaused) && gameStarted && ( +
+
+

{gameOver ? (winner || 'GAME OVER') : 'PAUSED'}

+ {gameOver && gameMode === '1p' &&

Final Score: {score.toLocaleString()}

} + {gameOver && gameMode === '2p' && ( +
+

P1

{player1.score}

+

P2

{player2.score}

+
+ )} +
+ {gameOver ? ( + <> + + + + ) : ( + + )} +
+
+
+ )} ); - - return isFullscreen ? {content} : content; }; -export default Tetris; \ No newline at end of file +export default Tetris;