diff --git a/src/pages/Pacman.tsx b/src/pages/Pacman.tsx index cc9ddda..44667bb 100644 --- a/src/pages/Pacman.tsx +++ b/src/pages/Pacman.tsx @@ -68,6 +68,10 @@ const Pacman = () => { const [player1Eliminated, setPlayer1Eliminated] = useState(false); const [player2Eliminated, setPlayer2Eliminated] = useState(false); const [showEliminationPrompt, setShowEliminationPrompt] = useState(false); + const [p1Invulnerable, setP1Invulnerable] = useState(false); + const [p2Invulnerable, setP2Invulnerable] = useState(false); + const invulnerableTimerRef = useRef(null); + const invulnerable2TimerRef = useRef(null); const [ghosts, setGhosts] = useState<{ pos: Position; dir: Direction; eaten: boolean }[]>([ { pos: { x: 9, y: 9 }, dir: 'left', eaten: false }, { pos: { x: 10, y: 9 }, dir: 'up', eaten: false }, @@ -211,10 +215,38 @@ const Pacman = () => { setGameMode('1p'); setShowEliminationPrompt(false); setIsPaused(false); + // If P1 was eliminated, remaining player continues as P1 + if (player1Eliminated && !player2Eliminated) { + // P2 becomes P1 + setPacman(pacman2); + setDirection(direction2); + setNextDirection(nextDirection2); + setScore(score2); + setLives(lives2); + setPlayer1Eliminated(false); + setPacman2({ x: -1, y: -1 }); + } + // If P2 was eliminated, P1 just continues normally playSound('success'); }; - useEffect(() => { return () => { if (powerTimerRef.current) clearTimeout(powerTimerRef.current); }; }, []); + const makeP1Invulnerable = () => { + if (invulnerableTimerRef.current) clearTimeout(invulnerableTimerRef.current); + setP1Invulnerable(true); + invulnerableTimerRef.current = setTimeout(() => setP1Invulnerable(false), 3000); + }; + + const makeP2Invulnerable = () => { + if (invulnerable2TimerRef.current) clearTimeout(invulnerable2TimerRef.current); + setP2Invulnerable(true); + invulnerable2TimerRef.current = setTimeout(() => setP2Invulnerable(false), 3000); + }; + + useEffect(() => { return () => { + if (powerTimerRef.current) clearTimeout(powerTimerRef.current); + if (invulnerableTimerRef.current) clearTimeout(invulnerableTimerRef.current); + if (invulnerable2TimerRef.current) clearTimeout(invulnerable2TimerRef.current); + }; }, []); useEffect(() => { if (!gameStarted || gameOver || gameComplete || isPaused) return; @@ -260,35 +292,45 @@ const Pacman = () => { }); playSound('success'); } else if (!ghostAtTarget.eaten) { - // Hit by ghost - lose life without moving - const isPlayer1 = player === pacman; - const currentLives = isPlayer1 ? lives : lives2; - const setLivesFunc = isPlayer1 ? setLives : setLives2; + // Check invulnerability + const isPlayer1 = setPlayer === setPacman; + const isInvulnerable = isPlayer1 ? p1Invulnerable : p2Invulnerable; + + if (!isInvulnerable) { + // Hit by ghost - lose life without moving + const currentLives = isPlayer1 ? lives : lives2; + const setLivesFunc = isPlayer1 ? setLives : setLives2; - const newLives = currentLives - 1; - setLivesFunc(newLives); + const newLives = currentLives - 1; + setLivesFunc(newLives); - if (newLives > 0) { - // Respawn at start - setPlayer({ x: 10, y: 15 }); - playSound('error'); + if (newLives > 0) { + // Respawn at start with invulnerability + setPlayer({ x: 10, y: 15 }); + if (isPlayer1) makeP1Invulnerable(); + else makeP2Invulnerable(); + playSound('error'); + } else { + // Player eliminated + if (isPlayer1) { + setPlayer1Eliminated(true); + setPacman({ x: -1, y: -1 }); + } else { + setPlayer2Eliminated(true); + setPacman2({ x: -1, y: -1 }); + } + playSound('error'); + + if (gameMode === '2p' && ((isPlayer1 && !player2Eliminated) || (!isPlayer1 && !player1Eliminated))) { + setShowEliminationPrompt(true); + setIsPaused(true); + } else { + setGameOver(true); + } + } } else { - // Player eliminated - if (isPlayer1) { - setPlayer1Eliminated(true); - setPacman({ x: -1, y: -1 }); - } else { - setPlayer2Eliminated(true); - setPacman2({ x: -1, y: -1 }); - } - playSound('error'); - - if (gameMode === '2p' && ((isPlayer1 && !player2Eliminated) || (!isPlayer1 && !player1Eliminated))) { - setShowEliminationPrompt(true); - setIsPaused(true); - } else { - setGameOver(true); - } + // Invulnerable - just move through + setPlayer(newPos); } } } else { @@ -337,12 +379,13 @@ const Pacman = () => { return ns; }); playSound('success'); - } else if (!ghost.eaten) { + } else if (!ghost.eaten && !p1Invulnerable) { // Player 1 dies const newLives = lives - 1; setLives(newLives); if (newLives > 0) { setPacman({ x: 10, y: 15 }); + makeP1Invulnerable(); playSound('error'); } else { setPlayer1Eliminated(true); @@ -402,12 +445,13 @@ const Pacman = () => { }); playSound('success'); return currentGhosts.map((g, idx) => idx === i ? { ...g, eaten: true, pos: { x: 10, y: 9 } } : g); - } else if (!ghost.eaten) { + } else if (!ghost.eaten && !p1Invulnerable) { // Ghost kills player const newLives = lives - 1; setLives(newLives); if (newLives > 0) { setPacman({ x: 10, y: 15 }); + makeP1Invulnerable(); playSound('error'); } else { setPlayer1Eliminated(true); @@ -443,12 +487,13 @@ const Pacman = () => { }); playSound('success'); return currentGhosts.map((g, idx) => idx === i ? { ...g, eaten: true, pos: { x: 10, y: 9 } } : g); - } else if (!ghost.eaten) { + } else if (!ghost.eaten && !p2Invulnerable) { // Ghost kills player const newLives = lives2 - 1; setLives2(newLives); if (newLives > 0) { setPacman2({ x: 10, y: 15 }); + makeP2Invulnerable(); playSound('error'); } else { setPlayer2Eliminated(true); @@ -469,7 +514,7 @@ const Pacman = () => { } }, TICK_SPEED); return () => clearInterval(interval); - }, [gameStarted, gameOver, gameComplete, isPaused, pacman, direction, nextDirection, pacman2, direction2, nextDirection2, dots, powerPellets, highScore, isPowered, playSound, gameMode, player1Eliminated, player2Eliminated]); + }, [gameStarted, gameOver, gameComplete, isPaused, pacman, direction, nextDirection, pacman2, direction2, nextDirection2, dots, powerPellets, highScore, isPowered, playSound, gameMode, player1Eliminated, player2Eliminated, p1Invulnerable, p2Invulnerable, lives, lives2]); @@ -604,8 +649,8 @@ const Pacman = () => { const isPowerPellet = powerPellets.has(`${x},${y}`); cells.push(
- {isPacmanHere &&
{renderPacman()}
} - {isPacman2Here &&
{renderPacman2()}
} + {isPacmanHere &&
{renderPacman()}
} + {isPacman2Here &&
{renderPacman2()}
} {ghostIndex !== -1 && !isPacmanHere && !isPacman2Here &&
{renderGhost(ghostIndex, ghosts[ghostIndex].eaten)}
} {isDot && !isPacmanHere && !isPacman2Here && ghostIndex === -1 &&
} {isPowerPellet && !isPacmanHere && !isPacman2Here && ghostIndex === -1 &&
}