Reverted to commit a06d538170
This commit is contained in:
+121
-82
@@ -67,8 +67,6 @@ 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 },
|
||||
@@ -101,11 +99,7 @@ const Pacman = () => {
|
||||
const maxHeight = window.innerHeight - 420;
|
||||
return Math.min(Math.floor(maxWidth / GRID_WIDTH), Math.floor(maxHeight / GRID_HEIGHT), 16);
|
||||
}
|
||||
// Desktop: calculate based on available height (subtract header ~60px, margins ~40px)
|
||||
const availableHeight = window.innerHeight - 100;
|
||||
const maxCellFromHeight = Math.floor(availableHeight / GRID_HEIGHT);
|
||||
// Clamp to reasonable max
|
||||
return Math.min(maxCellFromHeight, 28);
|
||||
return isFullscreen ? 26 : 20;
|
||||
}, [isFullscreen]);
|
||||
|
||||
const [cellSize, setCellSize] = useState(getCellSize);
|
||||
@@ -194,44 +188,10 @@ 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);
|
||||
@@ -361,16 +321,14 @@ const Pacman = () => {
|
||||
return ns;
|
||||
});
|
||||
playSound('success');
|
||||
} 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 {
|
||||
} else if (!ghost.eaten) {
|
||||
// Player 1 dies
|
||||
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');
|
||||
@@ -411,54 +369,135 @@ 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 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 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 2 controls (Arrow keys) - only in 2P mode
|
||||
// Player 2 controls (IJKL keys) - only in 2P mode
|
||||
if (gameMode === '2p') {
|
||||
switch (e.key) {
|
||||
case 'ArrowUp':
|
||||
case 'i': case 'I':
|
||||
e.preventDefault();
|
||||
if (!player2Eliminated) setNextDirection2('up');
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
case 'k': case 'K':
|
||||
e.preventDefault();
|
||||
if (!player2Eliminated) setNextDirection2('down');
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
case 'j': case 'J':
|
||||
e.preventDefault();
|
||||
if (!player2Eliminated) setNextDirection2('left');
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
case 'l': case 'L':
|
||||
e.preventDefault();
|
||||
if (!player2Eliminated) setNextDirection2('right');
|
||||
break;
|
||||
@@ -531,8 +570,8 @@ const Pacman = () => {
|
||||
const isPowerPellet = powerPellets.has(`${x},${y}`);
|
||||
cells.push(
|
||||
<div key={`${x}-${y}`} className={`flex items-center justify-center transition-all duration-200 ease-out ${isWall ? 'bg-primary/20 border border-primary/40' : isTunnel ? 'bg-background/30' : 'bg-background/50 border border-primary/5'}`} style={{ width: cellSize, height: cellSize }}>
|
||||
{isPacmanHere && <div className={`drop-shadow-lg ${player1Invincible ? 'animate-pulse' : ''}`} style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out', filter: player1Invincible ? 'drop-shadow(0 0 8px hsl(var(--primary))) brightness(1.5)' : 'drop-shadow(0 0 4px hsl(var(--primary)))', opacity: player1Invincible ? 0.8 : 1 }}>{renderPacman()}</div>}
|
||||
{isPacman2Here && <div className={`drop-shadow-lg ${player2Invincible ? 'animate-pulse' : ''}`} style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out', filter: player2Invincible ? 'drop-shadow(0 0 8px hsl(120 70% 50%)) brightness(1.5)' : 'drop-shadow(0 0 4px hsl(120 70% 50%))', opacity: player2Invincible ? 0.8 : 1 }}>{renderPacman2()}</div>}
|
||||
{isPacmanHere && <div className="drop-shadow-lg" style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out', filter: 'drop-shadow(0 0 4px hsl(var(--primary)))' }}>{renderPacman()}</div>}
|
||||
{isPacman2Here && <div className="drop-shadow-lg" style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out', filter: 'drop-shadow(0 0 4px hsl(120 70% 50%))' }}>{renderPacman2()}</div>}
|
||||
{ghostIndex !== -1 && !isPacmanHere && !isPacman2Here && <div style={{ width: spriteSize, height: spriteSize, transition: 'all 0.2s ease-out' }}>{renderGhost(ghostIndex, ghosts[ghostIndex].eaten)}</div>}
|
||||
{isDot && !isPacmanHere && !isPacman2Here && ghostIndex === -1 && <div className="w-1.5 h-1.5 bg-primary/80 rounded-full transition-all duration-200 ease-out animate-pulse" style={{ animationDuration: '2s' }} />}
|
||||
{isPowerPellet && !isPacmanHere && !isPacman2Here && ghostIndex === -1 && <div className="w-3 h-3 bg-primary rounded-full animate-pulse box-glow transition-all duration-200 ease-out" />}
|
||||
@@ -558,7 +597,7 @@ const Pacman = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={`flex ${isMobile ? 'flex-col items-center' : 'flex-row gap-4 items-start justify-center'} flex-1`}>
|
||||
<div className={`flex ${isMobile ? 'flex-col items-center' : 'flex-row gap-4'} flex-1 ${isFullscreen ? 'justify-center items-center' : ''}`}>
|
||||
<div className="border-2 border-primary box-glow p-1 bg-background/80 relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 animate-pulse" style={{ animationDuration: '4s' }}></div>
|
||||
<div className="grid relative z-10" style={{ gridTemplateColumns: `repeat(${GRID_WIDTH}, ${cellSize}px)` }}>{renderGrid()}</div>
|
||||
@@ -595,8 +634,8 @@ const Pacman = () => {
|
||||
<p className="font-pixel text-[10px] text-foreground/60 mb-1">CONTROLS</p>
|
||||
{gameMode === '2p' ? (
|
||||
<>
|
||||
<p className="font-pixel text-[8px] text-foreground/80">P1: WASD</p>
|
||||
<p className="font-pixel text-[8px] text-foreground/80">P2: ← → ↑ ↓</p>
|
||||
<p className="font-pixel text-[8px] text-foreground/80">P1: WASD / ←→↑↓</p>
|
||||
<p className="font-pixel text-[8px] text-foreground/80">P2: I J K L</p>
|
||||
<p className="font-pixel text-[8px] text-foreground/80">P: Pause</p>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user