diff --git a/src/pages/Pacman.tsx b/src/pages/Pacman.tsx index 129b557..850e144 100644 --- a/src/pages/Pacman.tsx +++ b/src/pages/Pacman.tsx @@ -67,6 +67,8 @@ const Pacman = () => { const [player1Eliminated, setPlayer1Eliminated] = useState(false); const [player2Eliminated, setPlayer2Eliminated] = useState(false); const [showEliminationPrompt, setShowEliminationPrompt] = useState(false); + const [player1Invincible, setPlayer1Invincible] = useState(false); + const [player2Invincible, setPlayer2Invincible] = useState(false); 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 }, @@ -188,10 +190,44 @@ const Pacman = () => { setDots(newDots); setPowerPellets(newPowerPellets); setIsPowered(false); setScore(0); setScore2(0); setLevel(1); setGameOver(false); setGameComplete(false); setIsPaused(false); setGameStarted(true); setShowModeSelection(false); setPlayer1Eliminated(false); setPlayer2Eliminated(false); setShowEliminationPrompt(false); + setPlayer1Invincible(false); setPlayer2Invincible(false); playSound('success'); gameRef.current?.focus(); }; const continueIn1PMode = () => { + // Transfer the surviving player's state to Player 1 + if (player1Eliminated && !player2Eliminated) { + // Player 1 died, Player 2 survives - transfer P2 to P1 + setPacman(pacman2); + setDirection(direction2); + setNextDirection(nextDirection2); + setMouthOpen(mouthOpen2); + setLives(lives2); + setScore(score2); + setPlayer1Invincible(player2Invincible); + + // Reset Player 2 + setPacman2({ x: -1, y: -1 }); + setDirection2('right'); + setNextDirection2('right'); + setMouthOpen2(true); + setLives2(3); + setScore2(0); + setPlayer2Invincible(false); + } else if (player2Eliminated && !player1Eliminated) { + // Player 2 died, Player 1 survives - P1 already active, just reset P2 + setPacman2({ x: -1, y: -1 }); + setDirection2('right'); + setNextDirection2('right'); + setMouthOpen2(true); + setLives2(3); + setScore2(0); + setPlayer2Invincible(false); + } + + // Reset eliminated states and continue + setPlayer1Eliminated(false); + setPlayer2Eliminated(false); setGameMode('1p'); setShowEliminationPrompt(false); setIsPaused(false); @@ -321,14 +357,16 @@ const Pacman = () => { return ns; }); playSound('success'); - } else if (!ghost.eaten) { - // Player 1 dies - const newLives = lives - 1; - setLives(newLives); - if (newLives > 0) { - setPacman({ x: 10, y: 15 }); - playSound('error'); - } else { + } else if (!ghost.eaten && !player1Invincible) { + // Player 1 dies (only if not invincible) + const newLives = lives - 1; + setLives(newLives); + if (newLives > 0) { + setPacman({ x: 10, y: 15 }); + setPlayer1Invincible(true); + setTimeout(() => setPlayer1Invincible(false), 3000); // 3 seconds of invincibility + playSound('error'); + } else { setPlayer1Eliminated(true); setPacman({ x: -1, y: -1 }); playSound('error'); @@ -369,135 +407,54 @@ const Pacman = () => { return { ...ghost, pos: moveEntity(ghost.pos, dir), dir }; })); - // Now check for collisions after all movement is complete - // Check Player 1 collisions (from both player movement and ghost movement) - if (!player1Eliminated) { - setGhosts(currentGhosts => { - for (let i = 0; i < currentGhosts.length; i++) { - const ghost = currentGhosts[i]; - if (ghost.pos.x === pacman.x && ghost.pos.y === pacman.y) { - if (isPowered && !ghost.eaten) { - // Player eats ghost - setScore(prev => { - const ns = Math.min(prev + 200, MAX_SCORE); - if (ns >= MAX_SCORE) setShowGlitchCrash(true); - if (ns > highScore) { setHighScore(ns); localStorage.setItem(HIGHSCORE_KEY, ns.toString()); } - return ns; - }); - playSound('success'); - return currentGhosts.map((g, idx) => idx === i ? { ...g, eaten: true, pos: { x: 10, y: 9 } } : g); - } else if (!ghost.eaten) { - // Ghost kills player - const newLives = lives - 1; - setLives(newLives); - if (newLives > 0) { - setPacman({ x: 10, y: 15 }); - playSound('error'); - } else { - setPlayer1Eliminated(true); - setPacman({ x: -1, y: -1 }); - playSound('error'); - if (gameMode === '2p' && !player2Eliminated) { - setShowEliminationPrompt(true); - setIsPaused(true); - } else { - setGameOver(true); - } - } - } - } - } - return currentGhosts; - }); - } - - // Check Player 2 collisions - if (gameMode === '2p' && !player2Eliminated) { - setGhosts(currentGhosts => { - for (let i = 0; i < currentGhosts.length; i++) { - const ghost = currentGhosts[i]; - if (ghost.pos.x === pacman2.x && ghost.pos.y === pacman2.y) { - if (isPowered && !ghost.eaten) { - // Player eats ghost - setScore2(prev => { - const ns = Math.min(prev + 200, MAX_SCORE); - if (ns >= MAX_SCORE) setShowGlitchCrash(true); - if (ns > highScore) { setHighScore(ns); localStorage.setItem(HIGHSCORE_KEY, ns.toString()); } - return ns; - }); - playSound('success'); - return currentGhosts.map((g, idx) => idx === i ? { ...g, eaten: true, pos: { x: 10, y: 9 } } : g); - } else if (!ghost.eaten) { - // Ghost kills player - const newLives = lives2 - 1; - setLives2(newLives); - if (newLives > 0) { - setPacman2({ x: 10, y: 15 }); - playSound('error'); - } else { - setPlayer2Eliminated(true); - setPacman2({ x: -1, y: -1 }); - playSound('error'); - if (!player1Eliminated) { - setShowEliminationPrompt(true); - setIsPaused(true); - } else { - setGameOver(true); - } - } - } - } - } - return currentGhosts; - }); - } }, TICK_SPEED); return () => clearInterval(interval); }, [gameStarted, gameOver, gameComplete, isPaused, pacman, direction, nextDirection, pacman2, direction2, nextDirection2, dots, powerPellets, highScore, isPowered, playSound, gameMode, player1Eliminated, player2Eliminated]); - - // More responsive input - update direction immediately when pressed useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (!gameStarted || gameOver || gameComplete || isPaused) return; - // Player 1 controls (WASD and Arrow keys) - immediate response - switch (e.key) { - case 'ArrowUp': case 'w': case 'W': - e.preventDefault(); - if (!player1Eliminated) setNextDirection('up'); - break; - case 'ArrowDown': case 's': case 'S': - e.preventDefault(); - if (!player1Eliminated) setNextDirection('down'); - break; - case 'ArrowLeft': case 'a': case 'A': - e.preventDefault(); - if (!player1Eliminated) setNextDirection('left'); - break; - case 'ArrowRight': case 'd': case 'D': - e.preventDefault(); - if (!player1Eliminated) setNextDirection('right'); - break; + // Player 1 controls - WASD always, arrows only in 1P mode + if (e.key === 'w' || e.key === 'W' || e.key === 's' || e.key === 'S' || e.key === 'a' || e.key === 'A' || e.key === 'd' || e.key === 'D' || + (gameMode === '1p' && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight'))) { + switch (e.key) { + case 'ArrowUp': case 'w': case 'W': + e.preventDefault(); + if (!player1Eliminated) setNextDirection('up'); + break; + case 'ArrowDown': case 's': case 'S': + e.preventDefault(); + if (!player1Eliminated) setNextDirection('down'); + break; + case 'ArrowLeft': case 'a': case 'A': + e.preventDefault(); + if (!player1Eliminated) setNextDirection('left'); + break; + case 'ArrowRight': case 'd': case 'D': + e.preventDefault(); + if (!player1Eliminated) setNextDirection('right'); + break; + } } - // Player 2 controls (IJKL keys) - only in 2P mode + // Player 2 controls (Arrow keys) - only in 2P mode if (gameMode === '2p') { switch (e.key) { - case 'i': case 'I': + case 'ArrowUp': e.preventDefault(); if (!player2Eliminated) setNextDirection2('up'); break; - case 'k': case 'K': + case 'ArrowDown': e.preventDefault(); if (!player2Eliminated) setNextDirection2('down'); break; - case 'j': case 'J': + case 'ArrowLeft': e.preventDefault(); if (!player2Eliminated) setNextDirection2('left'); break; - case 'l': case 'L': + case 'ArrowRight': e.preventDefault(); if (!player2Eliminated) setNextDirection2('right'); break; @@ -570,8 +527,8 @@ const Pacman = () => { const isPowerPellet = powerPellets.has(`${x},${y}`); cells.push(
CONTROLS
{gameMode === '2p' ? ( <> -P1: WASD / ←→↑↓
-P2: I J K L
+P1: WASD
+P2: ← → ↑ ↓
P: Pause
> ) : (