anycoder-59ebcd1c / index.html
kusatmer's picture
Upload folder using huggingface_hub
a4d00b7 verified
Raw
History Blame Contribute Delete
39.9 kB
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Pac-Man</title>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--pacman-yellow: #FFCC00;
--ghost-blinky: #FF0000;
--ghost-pinky: #FFB8FF;
--ghost-inky: #00FFFF;
--ghost-clyde: #FFB852;
--wall-blue: #2121DE;
--bg-dark: #0a0a0a;
--neon-glow: 0 0 20px currentColor, 0 0 40px currentColor, 0 0 60px currentColor;
}
body {
min-height: 100vh;
background: var(--bg-dark);
font-family: 'Rajdhani', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
/* Animated background */
.bg-grid {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(33, 33, 222, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(33, 33, 222, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
animation: gridMove 20s linear infinite;
pointer-events: none;
}
@keyframes gridMove {
0% { transform: perspective(500px) rotateX(60deg) translateY(0); }
100% { transform: perspective(500px) rotateX(60deg) translateY(50px); }
}
/* Header */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 15px 30px;
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(180deg, rgba(0,0,0,0.9) 0%, transparent 100%);
z-index: 100;
}
.logo {
font-family: 'Orbitron', sans-serif;
font-size: clamp(1.2rem, 3vw, 2rem);
font-weight: 900;
color: var(--pacman-yellow);
text-shadow: var(--neon-glow);
letter-spacing: 3px;
}
.built-with {
font-size: 0.85rem;
color: #888;
text-decoration: none;
transition: color 0.3s;
}
.built-with:hover {
color: var(--pacman-yellow);
}
/* Game Container */
.game-wrapper {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 20px;
z-index: 10;
}
.score-board {
display: flex;
gap: 40px;
font-family: 'Orbitron', sans-serif;
font-size: clamp(1rem, 2.5vw, 1.5rem);
color: #fff;
text-shadow: 0 0 10px currentColor;
}
.score-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.score-label {
font-size: 0.7rem;
color: #888;
text-transform: uppercase;
letter-spacing: 2px;
}
.score-value {
color: var(--pacman-yellow);
font-weight: 700;
min-width: 80px;
text-align: center;
}
.high-score {
color: #00FFFF;
}
/* Canvas Container */
.canvas-container {
position: relative;
border-radius: 20px;
padding: 4px;
background: linear-gradient(135deg, var(--wall-blue), #0000FF);
box-shadow:
0 0 60px rgba(33, 33, 222, 0.5),
inset 0 0 60px rgba(0,0,0,0.5);
}
canvas {
display: block;
border-radius: 16px;
background: #000;
max-width: 100%;
height: auto;
}
/* Controls */
.controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.btn {
font-family: 'Orbitron', sans-serif;
font-size: 1rem;
font-weight: 700;
padding: 15px 40px;
border: none;
border-radius: 50px;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 2px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn-primary {
background: linear-gradient(135deg, var(--pacman-yellow), #FF9500);
color: #000;
box-shadow: 0 0 30px rgba(255, 204, 0, 0.4);
}
.btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 10px 40px rgba(255, 204, 0, 0.6);
}
.btn-primary::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
transition: left 0.5s;
}
.btn-primary:hover::before {
left: 100%;
}
.instructions {
display: flex;
gap: 30px;
flex-wrap: wrap;
justify-content: center;
}
.key {
display: flex;
align-items: center;
gap: 10px;
color: #888;
font-size: 0.9rem;
}
.key span {
display: flex;
align-items: center;
justify-content: center;
width: 35px;
height: 35px;
background: rgba(255,255,255,0.1);
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.2);
color: #fff;
font-family: 'Orbitron', sans-serif;
font-size: 0.8rem;
}
/* Mobile Controls */
.mobile-controls {
display: none;
grid-template-columns: repeat(3, 70px);
gap: 10px;
margin-top: 10px;
}
.mobile-btn {
width: 70px;
height: 70px;
border-radius: 50%;
border: 2px solid var(--wall-blue);
background: rgba(33, 33, 222, 0.2);
color: #fff;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
user-select: none;
}
.mobile-btn:active {
background: var(--wall-blue);
transform: scale(0.95);
}
.mobile-btn:nth-child(2) { grid-column: 2; }
.mobile-btn:nth-child(3) { grid-column: 1; grid-row: 2; }
.mobile-btn:nth-child(4) { grid-column: 2; grid-row: 2; }
.mobile-btn:nth-child(5) { grid-column: 3; grid-row: 2; }
/* Overlay Screens */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.9);
border-radius: 16px;
z-index: 50;
transition: opacity 0.3s;
}
.overlay.hidden {
opacity: 0;
pointer-events: none;
}
.overlay h2 {
font-family: 'Orbitron', sans-serif;
font-size: clamp(2rem, 5vw, 3.5rem);
color: var(--pacman-yellow);
text-shadow: var(--neon-glow);
margin-bottom: 20px;
text-align: center;
}
.overlay p {
color: #888;
margin-bottom: 30px;
text-align: center;
max-width: 400px;
line-height: 1.6;
}
.game-over h2 {
color: #FF0000;
text-shadow: 0 0 20px #FF0000, 0 0 40px #FF0000;
}
.level-complete h2 {
color: #00FF00;
text-shadow: 0 0 20px #00FF00, 0 0 40px #00FF00;
}
/* Lives display */
.lives {
display: flex;
gap: 10px;
}
.life {
width: 25px;
height: 25px;
background: var(--pacman-yellow);
border-radius: 50%;
clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 85%, 0 100%);
animation: pulse 1s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
/* Responsive */
@media (max-width: 600px) {
.mobile-controls {
display: grid;
}
.instructions {
display: none;
}
.score-board {
gap: 20px;
}
}
/* Sound toggle */
.sound-toggle {
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
border-radius: 50%;
border: 2px solid var(--wall-blue);
background: rgba(0,0,0,0.8);
color: var(--wall-blue);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
z-index: 100;
}
.sound-toggle:hover, .sound-toggle.active {
background: var(--wall-blue);
color: #fff;
box-shadow: 0 0 20px var(--wall-blue);
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<header class="header">
<div class="logo">◉ PAC-MAN</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a>
</header>
<div class="game-wrapper">
<div class="score-board">
<div class="score-item">
<span class="score-label">Score</span>
<span class="score-value" id="score">0</span>
</div>
<div class="score-item">
<span class="score-label">High Score</span>
<span class="score-value high-score" id="highScore">0</span>
</div>
<div class="score-item">
<span class="score-label">Level</span>
<span class="score-value" id="level">1</span>
</div>
<div class="score-item">
<span class="score-label">Lives</span>
<div class="lives" id="lives"></div>
</div>
</div>
<div class="canvas-container">
<canvas id="gameCanvas" width="570" height="630"></canvas>
<div class="overlay" id="startScreen">
<h2>READY?</h2>
<p>Use arrow keys or WASD to move. Eat all dots and avoid ghosts! Power pellets let you eat ghosts.</p>
<button class="btn btn-primary" onclick="startGame()">Start Game</button>
</div>
<div class="overlay game-over hidden" id="gameOverScreen">
<h2>GAME OVER</h2>
<p id="finalScore">Final Score: 0</p>
<button class="btn btn-primary" onclick="restartGame()">Try Again</button>
</div>
<div class="overlay level-complete hidden" id="levelCompleteScreen">
<h2>LEVEL COMPLETE!</h2>
<p>Get ready for the next level...</p>
</div>
</div>
<div class="controls">
<div class="instructions">
<div class="key"><span></span> Up</div>
<div class="key"><span></span> Down</div>
<div class="key"><span></span> Left</div>
<div class="key"><span></span> Right</div>
<div class="key"><span>P</span> Pause</div>
</div>
<div class="mobile-controls">
<div class="mobile-btn" data-dir="up"></div>
<div class="mobile-btn" data-dir="left"></div>
<div class="mobile-btn" data-dir="down"></div>
<div class="mobile-btn" data-dir="right"></div>
</div>
</div>
</div>
<button class="sound-toggle" id="soundToggle" onclick="toggleSound()">
<span id="soundIcon">🔇</span>
</button>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Game constants
const CELL_SIZE = 30;
const COLS = 19;
const ROWS = 21;
const PACMAN_SPEED = 2;
const GHOST_SPEED = 1.8;
// Game state
let gameState = 'start'; // start, playing, paused, gameover, levelcomplete
let score = 0;
let highScore = localStorage.getItem('pacmanHighScore') || 0;
let level = 1;
let lives = 3;
let dots = [];
let powerPellets = [];
let particles = [];
// Map layout (0=empty, 1=wall, 2=dot, 3=power, 4=ghost house)
const mapLayout = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1],
[1,3,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,3,1],
[1,2,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,2,1],
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
[1,2,1,1,2,1,2,1,1,1,1,1,2,1,2,1,1,2,1],
[1,2,2,2,2,1,2,2,2,1,2,2,2,1,2,2,2,2,1],
[1,1,1,1,2,1,1,1,0,1,0,1,1,1,2,1,1,1,1],
[0,0,0,1,2,1,0,0,0,0,0,0,0,1,2,1,0,0,0],
[1,1,1,1,2,1,0,1,1,4,1,1,0,1,2,1,1,1,1],
[0,0,0,0,2,0,0,1,4,4,4,1,0,0,2,0,0,0,0],
[1,1,1,1,2,1,0,1,1,1,1,1,0,1,2,1,1,1,1],
[0,0,0,1,2,1,0,0,0,0,0,0,0,1,2,1,0,0,0],
[1,1,1,1,2,1,0,1,1,1,1,1,0,1,2,1,1,1,1],
[1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1],
[1,2,1,1,2,1,1,1,2,1,2,1,1,1,2,1,1,2,1],
[1,3,2,1,2,2,2,2,2,0,2,2,2,2,2,1,2,3,1],
[1,1,2,1,2,1,2,1,1,1,1,1,2,1,2,1,2,1,1],
[1,2,2,2,2,1,2,2,2,1,2,2,2,1,2,2,2,2,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
];
// Pac-Man
const pacman = {
x: 9 * CELL_SIZE + CELL_SIZE / 2,
y: 15 * CELL_SIZE + CELL_SIZE / 2,
dir: { x: 0, y: 0 },
nextDir: { x: 0, y: 0 },
mouthOpen: 0,
mouthSpeed: 0.15,
angle: 0,
powered: false,
poweredTime: 0
};
// Ghosts
const ghosts = [
{ name: 'blinky', color: '#FF0000', x: 9 * CELL_SIZE + CELL_SIZE / 2, y: 9 * CELL_SIZE + CELL_SIZE / 2, dir: { x: 0, y: -1 }, mode: 'chase', scared: false, eyes: false },
{ name: 'pinky', color: '#FFB8FF', x: 9 * CELL_SIZE + CELL_SIZE / 2, y: 10 * CELL_SIZE + CELL_SIZE / 2, dir: { x: 0, y: -1 }, mode: 'chase', scared: false, eyes: false },
{ name: 'inky', color: '#00FFFF', x: 8 * CELL_SIZE + CELL_SIZE / 2, y: 10 * CELL_SIZE + CELL_SIZE / 2, dir: { x: 0, y: -1 }, mode: 'chase', scared: false, eyes: false },
{ name: 'clyde', color: '#FFB852', x: 10 * CELL_SIZE + CELL_SIZE / 2, y: 10 * CELL_SIZE + CELL_SIZE / 2, dir: { x: 0, y: -1 }, mode: 'chase', scared: false, eyes: false }
];
// Sound synthesis
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
let soundEnabled = false;
function playSound(type) {
if (!soundEnabled) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
const now = audioCtx.currentTime;
switch(type) {
case 'waka':
osc.frequency.setValueAtTime(800, now);
osc.frequency.exponentialRampToValueAtTime(400, now + 0.1);
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
osc.start(now);
osc.stop(now + 0.1);
break;
case 'power':
osc.frequency.setValueAtTime(200, now);
osc.frequency.linearRampToValueAtTime(800, now + 0.3);
gain.gain.setValueAtTime(0.2, now);
gain.gain.linearRampToValueAtTime(0, now + 0.3);
osc.start(now);
osc.stop(now + 0.3);
break;
case 'eatGhost':
osc.frequency.setValueAtTime(400, now);
osc.frequency.exponentialRampToValueAtTime(800, now + 0.2);
gain.gain.setValueAtTime(0.3, now);
gain.gain.exponentialRampToValueAtTime(0, now + 0.2);
osc.start(now);
osc.stop(now + 0.2);
break;
case 'die':
osc.frequency.setValueAtTime(400, now);
osc.frequency.exponentialRampToValueAtTime(50, now + 0.5);
gain.gain.setValueAtTime(0.3, now);
gain.gain.exponentialRampToValueAtTime(0, now + 0.5);
osc.start(now);
osc.stop(now + 0.5);
break;
}
}
function toggleSound() {
soundEnabled = !soundEnabled;
document.getElementById('soundToggle').classList.toggle('active', soundEnabled);
document.getElementById('soundIcon').textContent = soundEnabled ? '🔊' : '🔇';
}
// Initialize game
function init() {
document.getElementById('highScore').textContent = highScore;
updateLives();
createMap();
requestAnimationFrame(gameLoop);
}
function createMap() {
dots = [];
powerPellets = [];
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
if (mapLayout[row] && mapLayout[row][col] === 2) {
dots.push({ x: col * CELL_SIZE + CELL_SIZE / 2, y: row * CELL_SIZE + CELL_SIZE / 2, eaten: false });
} else if (mapLayout[row] && mapLayout[row][col] === 3) {
powerPellets.push({ x: col * CELL_SIZE + CELL_SIZE / 2, y: row * CELL_SIZE + CELL_SIZE / 2, eaten: false, pulse: 0 });
}
}
}
}
function updateLives() {
const livesContainer = document.getElementById('lives');
livesContainer.innerHTML = '';
for (let i = 0; i < lives; i++) {
livesContainer.innerHTML += '<div class="life"></div>';
}
}
function startGame() {
document.getElementById('startScreen').classList.add('hidden');
gameState = 'playing';
resetPositions();
}
function restartGame() {
score = 0;
level = 1;
lives = 3;
document.getElementById('score').textContent = '0';
document.getElementById('level').textContent = '1';
updateLives();
createMap();
document.getElementById('gameOverScreen').classList.add('hidden');
gameState = 'playing';
resetPositions();
}
function resetPositions() {
pacman.x = 9 * CELL_SIZE + CELL_SIZE / 2;
pacman.y = 15 * CELL_SIZE + CELL_SIZE / 2;
pacman.dir = { x: 0, y: 0 };
pacman.nextDir = { x: 0, y: 0 };
pacman.powered = false;
ghosts[0].x = 9 * CELL_SIZE + CELL_SIZE / 2;
ghosts[0].y = 9 * CELL_SIZE + CELL_SIZE / 2;
ghosts[1].x = 9 * CELL_SIZE + CELL_SIZE / 2;
ghosts[1].y = 10 * CELL_SIZE + CELL_SIZE / 2;
ghosts[2].x = 8 * CELL_SIZE + CELL_SIZE / 2;
ghosts[2].y = 10 * CELL_SIZE + CELL_SIZE / 2;
ghosts[3].x = 10 * CELL_SIZE + CELL_SIZE / 2;
ghosts[3].y = 10 * CELL_SIZE + CELL_SIZE / 2;
ghosts.forEach(g => {
g.dir = { x: 0, y: -1 };
g.scared = false;
g.eyes = false;
});
}
// Input handling
const keys = {};
document.addEventListener('keydown', (e) => {
keys[e.key] = true;
if (gameState !== 'playing') return;
switch(e.key) {
case 'ArrowUp':
case 'w':
case 'W':
pacman.nextDir = { x: 0, y: -1 };
break;
case 'ArrowDown':
case 's':
case 'S':
pacman.nextDir = { x: 0, y: 1 };
break;
case 'ArrowLeft':
case 'a':
case 'A':
pacman.nextDir = { x: -1, y: 0 };
break;
case 'ArrowRight':
case 'd':
case 'D':
pacman.nextDir = { x: 1, y: 0 };
break;
case 'p':
case 'P':
gameState = gameState === 'paused' ? 'playing' : 'paused';
break;
}
});
// Mobile controls
document.querySelectorAll('.mobile-btn').forEach(btn => {
btn.addEventListener('touchstart', (e) => {
e.preventDefault();
const dir = btn.dataset.dir;
switch(dir) {
case 'up': pacman.nextDir = { x: 0, y: -1 }; break;
case 'down': pacman.nextDir = { x: 0, y: 1 }; break;
case 'left': pacman.nextDir = { x: -1, y: 0 }; break;
case 'right': pacman.nextDir = { x: 1, y: 0 }; break;
}
});
});
function canMove(x, y, dir) {
const nextX = x + dir.x * PACMAN_SPEED;
const nextY = y + dir.y * PACMAN_SPEED;
const col = Math.floor(nextX / CELL_SIZE);
const row = Math.floor(nextY / CELL_SIZE);
if (row < 0 || row >= ROWS || col < 0 || col >= COLS) {
// Tunnel wrap
if (col < 0) return { can: true, wrap: 'left' };
if (col >= COLS) return { can: true, wrap: 'right' };
return { can: false };
}
if (!mapLayout[row] || mapLayout[row][col] === 1) {
return { can: false };
}
// Check if in center of cell for turning
const centerX = col * CELL_SIZE + CELL_SIZE / 2;
const centerY = row * CELL_SIZE + CELL_SIZE / 2;
const distToCenter = Math.sqrt((nextX - centerX) ** 2 + (nextY - centerY) ** 2);
return { can: true, inCenter: distToCenter < 5 };
}
function update() {
if (gameState !== 'playing') return;
// Update power pellet pulse
powerPellets.forEach(p => p.pulse += 0.1);
// Update Pac-Man
const moveCheck = canMove(pacman.x, pacman.y, pacman.nextDir);
if (moveCheck.can && (pacman.dir.x === 0 && pacman.dir.y === 0 || moveCheck.inCenter)) {
pacman.dir = { ...pacman.nextDir };
}
const currentMove = canMove(pacman.x, pacman.y, pacman.dir);
if (currentMove.can) {
if (currentMove.wrap === 'left') {
pacman.x = COLS * CELL_SIZE - 1;
} else if (currentMove.wrap === 'right') {
pacman.x = 0;
} else {
pacman.x += pacman.dir.x * PACMAN_SPEED;
pacman.y += pacman.dir.y * PACMAN_SPEED;
}
// Animate mouth
pacman.mouthOpen += pacman.mouthSpeed;
if (pacman.mouthOpen > 1 || pacman.mouthOpen < 0) {
pacman.mouthSpeed = -pacman.mouthSpeed;
}
}
// Update angle based on direction
if (pacman.dir.x === 1) pacman.angle = 0;
else if (pacman.dir.x === -1) pacman.angle = Math.PI;
else if (pacman.dir.y === -1) pacman.angle = -Math.PI / 2;
else if (pacman.dir.y === 1) pacman.angle = Math.PI / 2;
// Eat dots
const pacCol = Math.floor(pacman.x / CELL_SIZE);
const pacRow = Math.floor(pacman.y / CELL_SIZE);
dots.forEach(dot => {
if (!dot.eaten) {
const dist = Math.sqrt((pacman.x - dot.x) ** 2 + (pacman.y - dot.y) ** 2);
if (dist < 15) {
dot.eaten = true;
score += 10;
document.getElementById('score').textContent = score;
playSound('waka');
createParticles(dot.x, dot.y, '#FFB897');
}
}
});
// Eat power pellets
powerPellets.forEach(pellet => {
if (!pellet.eaten) {
const dist = Math.sqrt((pacman.x - pellet.x) ** 2 + (pacman.y - pellet.y) ** 2);
if (dist < 15) {
pellet.eaten = true;
score += 50;
document.getElementById('score').textContent = score;
pacman.powered = true;
pacman.poweredTime = 600; // 10 seconds at 60fps
ghosts.forEach(g => { if (!g.eyes) g.scared = true; });
playSound('power');
createParticles(pellet.x, pellet.y, '#FFCC00', 10);
}
}
});
// Update power mode
if (pacman.powered) {
pacman.poweredTime--;
if (pacman.poweredTime <= 0) {
pacman.powered = false;
ghosts.forEach(g => g.scared = false);
} else if (pacman.poweredTime < 120) {
// Flash when about to end
ghosts.forEach(g => g.scared = Math.floor(pacman.poweredTime / 10) % 2 === 0);
}
}
// Update ghosts
ghosts.forEach((ghost, i) => {
if (ghost.eyes) {
// Return to ghost house
const targetX = 9 * CELL_SIZE + CELL_SIZE / 2;
const targetY = 9 * CELL_SIZE + CELL_SIZE / 2;
moveGhost(ghost, targetX, targetY, GHOST_SPEED * 2);
const distToHouse = Math.sqrt((ghost.x - targetX) ** 2 + (ghost.y - targetY) ** 2);
if (distToHouse < 5) {
ghost.eyes = false;
ghost.scared = false;
}
} else {
// AI targeting
let targetX, targetY;
if (ghost.scared) {
// Run away randomly
targetX = Math.random() * canvas.width;
targetY = Math.random() * canvas.height;
} else {
// Chase Pac-Man with different personalities
switch(ghost.name) {
case 'blinky':
targetX = pacman.x;
targetY = pacman.y;
break;
case 'pinky':
targetX = pacman.x + pacman.dir.x * 4 * CELL_SIZE;
targetY = pacman.y + pacman.dir.y * 4 * CELL_SIZE;
break;
case 'inky':
targetX = pacman.x + pacman.dir.x * 2 * CELL_SIZE;
targetY = pacman.y + pacman.dir.y * 2 * CELL_SIZE;
// Add some randomness
targetX += (Math.random() - 0.5) * 100;
break;
case 'clyde':
const dist = Math.sqrt((ghost.x - pacman.x) ** 2 + (ghost.y - pacman.y) ** 2);
if (dist > 8 * CELL_SIZE) {
targetX = pacman.x;
targetY = pacman.y;
} else {
targetX = 0;
targetY = canvas.height;
}
break;
}
}
moveGhost(ghost, targetX, targetY, ghost.scared ? GHOST_SPEED * 0.6 : GHOST_SPEED + level * 0.1);
}
// Collision with Pac-Man
const dist = Math.sqrt((ghost.x - pacman.x) ** 2 + (ghost.y - pacman.y) ** 2);
if (dist < 20) {
if (ghost.scared && !ghost.eyes) {
ghost.eyes = true;
ghost.scared = false;
score += 200 * (Math.pow(2, ghosts.filter(g => g.eyes).length - 1));
document.getElementById('score').textContent = score;
playSound('eatGhost');
createParticles(ghost.x, ghost.y, '#00FFFF', 15);
} else if (!ghost.eyes) {
// Pac-Man dies
lives--;
updateLives();
playSound('die');
createParticles(pacman.x, pacman.y, '#FFCC00', 20);
if (lives <= 0) {
gameOver();
} else {
resetPositions();
}
}
}
});
// Update particles
particles = particles.filter(p => {
p.x += p.vx;
p.y += p.vy;
p.vy += 0.1;
p.life--;
return p.life > 0;
});
// Check level complete
if (dots.every(d => d.eaten) && powerPellets.every(p => p.eaten)) {
levelComplete();
}
// Update high score
if (score > highScore) {
highScore = score;
localStorage.setItem('pacmanHighScore', highScore);
document.getElementById('highScore').textContent = highScore;
}
}
function moveGhost(ghost, targetX, targetY, speed) {
const dx = targetX - ghost.x;
const dy = targetY - ghost.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
const vx = (dx / dist) * speed;
const vy = (dy / dist) * speed;
// Try to move, with simple collision
const nextX = ghost.x + vx;
const nextY = ghost.y + vy;
const col = Math.floor(nextX / CELL_SIZE);
const row = Math.floor(nextY / CELL_SIZE);
if (row >= 0 && row < ROWS && col >= 0 && col < COLS &&
mapLayout[row] && mapLayout[row][col] !== 1) {
ghost.x = nextX;
ghost.y = nextY;
ghost.dir = { x: Math.sign(vx) || ghost.dir.x, y: Math.sign(vy) || ghost.dir.y };
} else {
// Try to slide along walls
const altMoves = [
{ x: speed, y: 0 },
{ x: -speed, y: 0 },
{ x: 0, y: speed },
{ x: 0, y: -speed }
];
for (let move of altMoves) {
const testCol = Math.floor((ghost.x + move.x) / CELL_SIZE);
const testRow = Math.floor((ghost.y + move.y) / CELL_SIZE);
if (testRow >= 0 && testRow < ROWS && testCol >= 0 && testCol < COLS &&
mapLayout[testRow] && mapLayout[testRow][testCol] !== 1) {
ghost.x += move.x;
ghost.y += move.y;
ghost.dir = { x: Math.sign(move.x), y: Math.sign(move.y) };
break;
}
}
}
}
}
function createParticles(x, y, color, count = 5) {
for (let i = 0; i < count; i++) {
particles.push({
x, y,
vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 8,
life: 30 + Math.random() * 20,
color,
size: 2 + Math.random() * 4
});
}
}
function gameOver() {
gameState = 'gameover';
document.getElementById('finalScore').textContent = `Final Score: ${score}`;
document.getElementById('gameOverScreen').classList.remove('hidden');
}
function levelComplete() {
gameState = 'levelcomplete';
document.getElementById('levelCompleteScreen').classList.remove('hidden');
setTimeout(() => {
level++;
document.getElementById('level').textContent = level;
createMap();
resetPositions();
document.getElementById('levelCompleteScreen').classList.add('hidden');
gameState = 'playing';
}, 2000);
}
function draw() {
// Clear canvas
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw maze walls with neon effect
ctx.shadowBlur = 20;
ctx.shadowColor = '#2121DE';
ctx.fillStyle = '#2121DE';
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
if (mapLayout[row] && mapLayout[row][col] === 1) {
// Rounded walls
const x = col * CELL_SIZE;
const y = row * CELL_SIZE;
const radius = 4;
ctx.beginPath();
ctx.roundRect(x + 1, y + 1, CELL_SIZE - 2, CELL_SIZE - 2, radius);
ctx.fill();
}
}
}
ctx.shadowBlur = 0;
// Draw dots
ctx.fillStyle = '#FFB897';
dots.forEach(dot => {
if (!dot.eaten) {
ctx.beginPath();
ctx.arc(dot.x, dot.y, 3, 0, Math.PI * 2);
ctx.fill();
}
});
// Draw power pellets with pulse
powerPellets.forEach(pellet => {
if (!pellet.eaten) {
const pulseSize = 6 + Math.sin(pellet.pulse) * 2;
ctx.shadowBlur = 15;
ctx.shadowColor = '#FFCC00';
ctx.fillStyle = '#FFCC00';
ctx.beginPath();
ctx.arc(pellet.x, pellet.y, pulseSize, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
});
// Draw Pac-Man
ctx.save();
ctx.translate(pacman.x, pacman.y);
ctx.rotate(pacman.angle);
const mouthAngle = 0.2 + pacman.mouthOpen * 0.3;
ctx.fillStyle = pacman.powered ? '#fff' : '#FFCC00';
ctx.shadowBlur = pacman.powered ? 30 : 20;
ctx.shadowColor = pacman.powered ? '#fff' : '#FFCC00';
ctx.beginPath();
ctx.arc(0, 0, 12, mouthAngle, Math.PI * 2 - mouthAngle);
ctx.lineTo(0, 0);
ctx.closePath();
ctx.fill();
ctx.restore();
ctx.shadowBlur = 0;
// Draw ghosts
ghosts.forEach(ghost => {
ctx.save();
ctx.translate(ghost.x, ghost.y);
if (ghost.eyes) {
// Draw eyes only
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(-4, -2, 4, 0, Math.PI * 2);
ctx.arc(4, -2, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#00f';
ctx.beginPath();
ctx.arc(-4, -2, 2, 0, Math.PI * 2);
ctx.arc(4, -2, 2, 0, Math.PI * 2);
ctx.fill();
} else {
// Ghost body with glow
const color = ghost.scared ? '#2121DE' : ghost.color;
ctx.shadowBlur = 15;
ctx.shadowColor = color;
ctx.fillStyle = color;
// Body shape
ctx.beginPath();
ctx.arc(0, -2, 12, Math.PI, 0);
ctx.lineTo(12, 10);
// Wavy bottom
for (let i = 0; i < 4; i++) {
ctx.arc(12 - i * 6 - 3, 10 - (i % 2) * 4, 3, 0, Math.PI, i % 2 === 0);
}
ctx.lineTo(-12, 10);
ctx.closePath();
ctx.fill();
// Eyes
ctx.fillStyle = '#fff';
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.arc(-4, -4, 4, 0, Math.PI * 2);
ctx.arc(4, -4, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = ghost.scared ? '#fff' : '#00f';
const lookX = ghost.dir.x * 2;
const lookY = ghost.dir.y * 2;
ctx.beginPath();
ctx.arc(-4 + lookX, -4 + lookY, 2, 0, Math.PI * 2);
ctx.arc(4 + lookX, -4 + lookY, 2, 0, Math.PI * 2);
ctx.fill();
// Scared mouth
if (ghost.scared