Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Weapon-Pac: Battle Maze</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @keyframes ghost-pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| @keyframes powerup-glow { | |
| 0%, 100% { box-shadow: 0 0 5px 2px #ff0; } | |
| 50% { box-shadow: 0 0 15px 5px #ff0; } | |
| } | |
| .ghost { | |
| animation: ghost-pulse 2s infinite; | |
| } | |
| .powerup { | |
| animation: powerup-glow 1.5s infinite; | |
| } | |
| .bullet { | |
| transition: all 0.1s linear; | |
| } | |
| #game-container { | |
| background-color: #111; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .wall { | |
| background-color: #1a3d8a; | |
| box-shadow: inset 0 0 10px #000; | |
| } | |
| .path { | |
| background-color: #000; | |
| } | |
| #player { | |
| transition: transform 0.2s ease; | |
| z-index: 10; | |
| } | |
| .weapon-icon { | |
| transition: all 0.3s ease; | |
| } | |
| .weapon-icon:hover { | |
| transform: scale(1.2); | |
| filter: drop-shadow(0 0 5px #fff); | |
| } | |
| #game-overlay { | |
| background-color: rgba(0, 0, 0, 0.8); | |
| z-index: 100; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white font-mono flex flex-col items-center justify-center min-h-screen p-4"> | |
| <div class="max-w-4xl w-full"> | |
| <header class="flex justify-between items-center mb-4"> | |
| <h1 class="text-3xl font-bold text-yellow-400 flex items-center"> | |
| <i class="fas fa-ghost mr-2"></i> WEAPON-PAC | |
| </h1> | |
| <div class="flex items-center space-x-6"> | |
| <div class="bg-gray-800 px-4 py-2 rounded-lg flex items-center"> | |
| <span class="text-yellow-400 mr-2">SCORE:</span> | |
| <span id="score" class="text-xl font-bold">0</span> | |
| </div> | |
| <div class="bg-gray-800 px-4 py-2 rounded-lg flex items-center"> | |
| <span class="text-red-400 mr-2">LIVES:</span> | |
| <span id="lives" class="text-xl font-bold">3</span> | |
| </div> | |
| <div class="bg-gray-800 px-4 py-2 rounded-lg flex items-center"> | |
| <span class="text-blue-400 mr-2">WEAPON:</span> | |
| <span id="current-weapon" class="text-xl font-bold">PISTOL</span> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="relative"> | |
| <div id="game-container" class="w-full h-96 border-4 border-blue-800 rounded-lg relative"> | |
| <!-- Game elements will be generated by JavaScript --> | |
| </div> | |
| <div id="game-overlay" class="absolute inset-0 flex flex-col items-center justify-center hidden"> | |
| <div class="bg-gray-900 border-2 border-yellow-400 rounded-lg p-8 max-w-md text-center"> | |
| <h2 id="overlay-title" class="text-3xl font-bold mb-4 text-yellow-400">GAME OVER</h2> | |
| <p id="overlay-message" class="text-xl mb-6">You scored <span id="final-score" class="text-yellow-400">0</span> points!</p> | |
| <button id="restart-btn" class="bg-yellow-500 hover:bg-yellow-600 text-black font-bold py-3 px-6 rounded-lg text-lg transition"> | |
| PLAY AGAIN | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 bg-gray-800 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-bold text-yellow-400">WEAPON SELECTOR</h2> | |
| <div class="flex items-center"> | |
| <span class="mr-2">AMMO:</span> | |
| <span id="ammo-count" class="font-bold">∞</span> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-5 gap-4"> | |
| <div class="weapon-icon bg-gray-700 p-3 rounded-lg cursor-pointer border-2 border-yellow-500 flex flex-col items-center" data-weapon="pistol"> | |
| <i class="fas fa-gun text-2xl mb-1"></i> | |
| <span class="text-sm">PISTOL</span> | |
| </div> | |
| <div class="weapon-icon bg-gray-700 p-3 rounded-lg cursor-pointer border-2 border-gray-500 flex flex-col items-center" data-weapon="shotgun"> | |
| <i class="fas fa-gun text-2xl mb-1"></i> | |
| <span class="text-sm">SHOTGUN</span> | |
| </div> | |
| <div class="weapon-icon bg-gray-700 p-3 rounded-lg cursor-pointer border-2 border-gray-500 flex flex-col items-center" data-weapon="laser"> | |
| <i class="fas fa-bolt text-2xl mb-1"></i> | |
| <span class="text-sm">LASER</span> | |
| </div> | |
| <div class="weapon-icon bg-gray-700 p-3 rounded-lg cursor-pointer border-2 border-gray-500 flex flex-col items-center" data-weapon="rocket"> | |
| <i class="fas fa-rocket text-2xl mb-1"></i> | |
| <span class="text-sm">ROCKET</span> | |
| </div> | |
| <div class="weapon-icon bg-gray-700 p-3 rounded-lg cursor-pointer border-2 border-gray-500 flex flex-col items-center" data-weapon="mine"> | |
| <i class="fas fa-land-mine-on text-2xl mb-1"></i> | |
| <span class="text-sm">MINE</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 bg-gray-800 rounded-lg p-4"> | |
| <h2 class="text-xl font-bold text-yellow-400 mb-2">CONTROLS</h2> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div> | |
| <p class="mb-2"><span class="font-bold">WASD</span> or <span class="font-bold">Arrow Keys</span> to move</p> | |
| <p><span class="font-bold">SPACE</span> to shoot</p> | |
| </div> | |
| <div> | |
| <p class="mb-2"><span class="font-bold">1-5</span> to switch weapons</p> | |
| <p>Click weapon icons to select</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Game constants | |
| const GRID_SIZE = 19; | |
| const CELL_SIZE = 20; | |
| const PLAYER_SPEED = 150; | |
| const GHOST_SPEED = 180; | |
| const BULLET_SPEED = 8; | |
| // Game state | |
| let score = 0; | |
| let lives = 3; | |
| let gameRunning = true; | |
| let playerDirection = 'right'; | |
| let nextDirection = 'right'; | |
| let playerPosition = { x: 1, y: 1 }; | |
| let ghosts = []; | |
| let bullets = []; | |
| let powerups = []; | |
| let walls = []; | |
| let pellets = []; | |
| let mines = []; | |
| let currentWeapon = 'pistol'; | |
| let ammo = { | |
| pistol: Infinity, | |
| shotgun: 20, | |
| laser: 30, | |
| rocket: 5, | |
| mine: 3 | |
| }; | |
| // DOM elements | |
| const gameContainer = document.getElementById('game-container'); | |
| const scoreDisplay = document.getElementById('score'); | |
| const livesDisplay = document.getElementById('lives'); | |
| const weaponDisplay = document.getElementById('current-weapon'); | |
| const ammoDisplay = document.getElementById('ammo-count'); | |
| const gameOverlay = document.getElementById('game-overlay'); | |
| const overlayTitle = document.getElementById('overlay-title'); | |
| const overlayMessage = document.getElementById('overlay-message'); | |
| const finalScore = document.getElementById('final-score'); | |
| const restartBtn = document.getElementById('restart-btn'); | |
| const weaponIcons = document.querySelectorAll('.weapon-icon'); | |
| // Initialize game | |
| initGame(); | |
| // Event listeners | |
| document.addEventListener('keydown', handleKeyPress); | |
| restartBtn.addEventListener('click', resetGame); | |
| weaponIcons.forEach(icon => { | |
| icon.addEventListener('click', () => { | |
| const weapon = icon.dataset.weapon; | |
| if (ammo[weapon] > 0 || weapon === 'pistol') { | |
| selectWeapon(weapon); | |
| } | |
| }); | |
| }); | |
| // Game loop | |
| let lastTime = 0; | |
| function gameLoop(timestamp) { | |
| if (!gameRunning) return; | |
| const deltaTime = timestamp - lastTime; | |
| lastTime = timestamp; | |
| updatePlayer(deltaTime); | |
| updateGhosts(deltaTime); | |
| updateBullets(); | |
| updateMines(); | |
| checkCollisions(); | |
| checkWinCondition(); | |
| requestAnimationFrame(gameLoop); | |
| } | |
| requestAnimationFrame(gameLoop); | |
| // Game functions | |
| function initGame() { | |
| // Set up game container | |
| gameContainer.style.width = `${GRID_SIZE * CELL_SIZE}px`; | |
| gameContainer.style.height = `${GRID_SIZE * CELL_SIZE}px`; | |
| // Generate maze | |
| generateMaze(); | |
| // Create player | |
| createPlayer(); | |
| // Create initial ghosts | |
| createGhosts(3); | |
| // Create initial pellets | |
| createPellets(); | |
| // Create powerups | |
| createPowerups(); | |
| // Update displays | |
| updateDisplays(); | |
| } | |
| function generateMaze() { | |
| // Clear previous elements | |
| gameContainer.innerHTML = ''; | |
| walls = []; | |
| // Create border walls | |
| for (let y = 0; y < GRID_SIZE; y++) { | |
| for (let x = 0; x < GRID_SIZE; x++) { | |
| if (x === 0 || y === 0 || x === GRID_SIZE - 1 || y === GRID_SIZE - 1) { | |
| createWall(x, y); | |
| } else if (Math.random() < 0.15 && !(x < 3 && y < 3)) { | |
| createWall(x, y); | |
| } | |
| } | |
| } | |
| // Ensure some paths | |
| for (let x = 1; x < GRID_SIZE - 1; x++) { | |
| if (!walls.some(w => w.x === x && w.y === 1)) { | |
| createPath(x, 1); | |
| } | |
| if (!walls.some(w => w.x === x && w.y === GRID_SIZE - 2)) { | |
| createPath(x, GRID_SIZE - 2); | |
| } | |
| } | |
| for (let y = 1; y < GRID_SIZE - 1; y++) { | |
| if (!walls.some(w => w.x === 1 && w.y === y)) { | |
| createPath(1, y); | |
| } | |
| if (!walls.some(w => w.x === GRID_SIZE - 2 && w.y === y)) { | |
| createPath(GRID_SIZE - 2, y); | |
| } | |
| } | |
| } | |
| function createWall(x, y) { | |
| const wall = document.createElement('div'); | |
| wall.className = 'wall absolute'; | |
| wall.style.width = `${CELL_SIZE}px`; | |
| wall.style.height = `${CELL_SIZE}px`; | |
| wall.style.left = `${x * CELL_SIZE}px`; | |
| wall.style.top = `${y * CELL_SIZE}px`; | |
| gameContainer.appendChild(wall); | |
| walls.push({ x, y }); | |
| } | |
| function createPath(x, y) { | |
| const path = document.createElement('div'); | |
| path.className = 'path absolute'; | |
| path.style.width = `${CELL_SIZE}px`; | |
| path.style.height = `${CELL_SIZE}px`; | |
| path.style.left = `${x * CELL_SIZE}px`; | |
| path.style.top = `${y * CELL_SIZE}px`; | |
| gameContainer.appendChild(path); | |
| } | |
| function createPlayer() { | |
| const player = document.createElement('div'); | |
| player.id = 'player'; | |
| player.className = 'absolute bg-yellow-400 rounded-full'; | |
| player.style.width = `${CELL_SIZE - 4}px`; | |
| player.style.height = `${CELL_SIZE - 4}px`; | |
| player.style.left = `${playerPosition.x * CELL_SIZE + 2}px`; | |
| player.style.top = `${playerPosition.y * CELL_SIZE + 2}px`; | |
| gameContainer.appendChild(player); | |
| // Add mouth effect based on direction | |
| updatePlayerMouth(); | |
| } | |
| function updatePlayerMouth() { | |
| const player = document.getElementById('player'); | |
| player.innerHTML = ''; | |
| const mouth = document.createElement('div'); | |
| mouth.className = 'absolute bg-black rounded-full'; | |
| switch(playerDirection) { | |
| case 'right': | |
| mouth.style.width = `${CELL_SIZE / 3}px`; | |
| mouth.style.height = `${CELL_SIZE / 3}px`; | |
| mouth.style.right = '0'; | |
| mouth.style.top = '50%'; | |
| mouth.style.transform = 'translateY(-50%)'; | |
| break; | |
| case 'left': | |
| mouth.style.width = `${CELL_SIZE / 3}px`; | |
| mouth.style.height = `${CELL_SIZE / 3}px`; | |
| mouth.style.left = '0'; | |
| mouth.style.top = '50%'; | |
| mouth.style.transform = 'translateY(-50%)'; | |
| break; | |
| case 'up': | |
| mouth.style.width = `${CELL_SIZE / 3}px`; | |
| mouth.style.height = `${CELL_SIZE / 3}px`; | |
| mouth.style.top = '0'; | |
| mouth.style.left = '50%'; | |
| mouth.style.transform = 'translateX(-50%)'; | |
| break; | |
| case 'down': | |
| mouth.style.width = `${CELL_SIZE / 3}px`; | |
| mouth.style.height = `${CELL_SIZE / 3}px`; | |
| mouth.style.bottom = '0'; | |
| mouth.style.left = '50%'; | |
| mouth.style.transform = 'translateX(-50%)'; | |
| break; | |
| } | |
| player.appendChild(mouth); | |
| } | |
| function createGhosts(count) { | |
| ghosts = []; | |
| for (let i = 0; i < count; i++) { | |
| let x, y; | |
| do { | |
| x = Math.floor(Math.random() * (GRID_SIZE - 4)) + 2; | |
| y = Math.floor(Math.random() * (GRID_SIZE - 4)) + 2; | |
| } while (walls.some(w => w.x === x && w.y === y) || | |
| (x === playerPosition.x && y === playerPosition.y)); | |
| const ghost = { | |
| x, | |
| y, | |
| id: `ghost-${i}`, | |
| direction: ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)], | |
| speed: GHOST_SPEED + Math.random() * 50, | |
| color: ['red', 'pink', 'cyan', 'orange'][i % 4] | |
| }; | |
| ghosts.push(ghost); | |
| const ghostElement = document.createElement('div'); | |
| ghostElement.id = ghost.id; | |
| ghostElement.className = `ghost absolute rounded-full`; | |
| ghostElement.style.backgroundColor = ghost.color; | |
| ghostElement.style.width = `${CELL_SIZE - 4}px`; | |
| ghostElement.style.height = `${CELL_SIZE - 4}px`; | |
| ghostElement.style.left = `${ghost.x * CELL_SIZE + 2}px`; | |
| ghostElement.style.top = `${ghost.y * CELL_SIZE + 2}px`; | |
| // Add eyes | |
| const leftEye = document.createElement('div'); | |
| leftEye.className = 'absolute bg-white rounded-full'; | |
| leftEye.style.width = `${CELL_SIZE / 4}px`; | |
| leftEye.style.height = `${CELL_SIZE / 4}px`; | |
| leftEye.style.left = `${CELL_SIZE / 4}px`; | |
| leftEye.style.top = `${CELL_SIZE / 4}px`; | |
| const rightEye = leftEye.cloneNode(); | |
| rightEye.style.left = `${CELL_SIZE / 2}px`; | |
| ghostElement.appendChild(leftEye); | |
| ghostElement.appendChild(rightEye); | |
| gameContainer.appendChild(ghostElement); | |
| } | |
| } | |
| function createPellets() { | |
| pellets = []; | |
| for (let y = 1; y < GRID_SIZE - 1; y++) { | |
| for (let x = 1; x < GRID_SIZE - 1; x++) { | |
| if (!walls.some(w => w.x === x && w.y === y) && | |
| !(x === playerPosition.x && y === playerPosition.y) && | |
| !ghosts.some(g => g.x === x && g.y === y)) { | |
| // Skip some cells to not overcrowd | |
| if (Math.random() > 0.3) { | |
| pellets.push({ x, y }); | |
| const pellet = document.createElement('div'); | |
| pellet.className = 'absolute bg-white rounded-full'; | |
| pellet.style.width = `${CELL_SIZE / 4}px`; | |
| pellet.style.height = `${CELL_SIZE / 4}px`; | |
| pellet.style.left = `${x * CELL_SIZE + CELL_SIZE / 2 - CELL_SIZE / 8}px`; | |
| pellet.style.top = `${y * CELL_SIZE + CELL_SIZE / 2 - CELL_SIZE / 8}px`; | |
| pellet.dataset.x = x; | |
| pellet.dataset.y = y; | |
| gameContainer.appendChild(pellet); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| function createPowerups() { | |
| powerups = []; | |
| // Create 2 powerups in random positions | |
| for (let i = 0; i < 2; i++) { | |
| let x, y; | |
| do { | |
| x = Math.floor(Math.random() * (GRID_SIZE - 4)) + 2; | |
| y = Math.floor(Math.random() * (GRID_SIZE - 4)) + 2; | |
| } while (walls.some(w => w.x === x && w.y === y) || | |
| (x === playerPosition.x && y === playerPosition.y) || | |
| ghosts.some(g => g.x === x && g.y === y)); | |
| powerups.push({ x, y }); | |
| const powerup = document.createElement('div'); | |
| powerup.className = 'powerup absolute bg-yellow-400 rounded-full'; | |
| powerup.style.width = `${CELL_SIZE / 2}px`; | |
| powerup.style.height = `${CELL_SIZE / 2}px`; | |
| powerup.style.left = `${x * CELL_SIZE + CELL_SIZE / 4}px`; | |
| powerup.style.top = `${y * CELL_SIZE + CELL_SIZE / 4}px`; | |
| powerup.dataset.x = x; | |
| powerup.dataset.y = y; | |
| gameContainer.appendChild(powerup); | |
| } | |
| } | |
| function handleKeyPress(e) { | |
| if (!gameRunning) return; | |
| switch(e.key) { | |
| case 'ArrowUp': | |
| case 'w': | |
| case 'W': | |
| nextDirection = 'up'; | |
| break; | |
| case 'ArrowDown': | |
| case 's': | |
| case 'S': | |
| nextDirection = 'down'; | |
| break; | |
| case 'ArrowLeft': | |
| case 'a': | |
| case 'A': | |
| nextDirection = 'left'; | |
| break; | |
| case 'ArrowRight': | |
| case 'd': | |
| case 'D': | |
| nextDirection = 'right'; | |
| break; | |
| case ' ': | |
| shoot(); | |
| break; | |
| case '1': | |
| selectWeapon('pistol'); | |
| break; | |
| case '2': | |
| selectWeapon('shotgun'); | |
| break; | |
| case '3': | |
| selectWeapon('laser'); | |
| break; | |
| case '4': | |
| selectWeapon('rocket'); | |
| break; | |
| case '5': | |
| selectWeapon('mine'); | |
| break; | |
| } | |
| } | |
| function selectWeapon(weapon) { | |
| if (ammo[weapon] <= 0 && weapon !== 'pistol') return; | |
| currentWeapon = weapon; | |
| weaponDisplay.textContent = weapon.toUpperCase(); | |
| // Update weapon icons | |
| weaponIcons.forEach(icon => { | |
| if (icon.dataset.weapon === weapon) { | |
| icon.classList.add('border-yellow-500'); | |
| icon.classList.remove('border-gray-500'); | |
| } else { | |
| icon.classList.add('border-gray-500'); | |
| icon.classList.remove('border-yellow-500'); | |
| } | |
| }); | |
| updateDisplays(); | |
| } | |
| function shoot() { | |
| if (ammo[currentWeapon] <= 0 && currentWeapon !== 'pistol') return; | |
| if (currentWeapon !== 'pistol') { | |
| ammo[currentWeapon]--; | |
| updateDisplays(); | |
| } | |
| switch(currentWeapon) { | |
| case 'pistol': | |
| createBullet(playerPosition.x, playerPosition.y, playerDirection); | |
| break; | |
| case 'shotgun': | |
| // Create 3 bullets in a spread | |
| createBullet(playerPosition.x, playerPosition.y, playerDirection); | |
| createBullet(playerPosition.x, playerPosition.y, rotateDirection(playerDirection, -15)); | |
| createBullet(playerPosition.x, playerPosition.y, rotateDirection(playerDirection, 15)); | |
| break; | |
| case 'laser': | |
| createLaser(playerPosition.x, playerPosition.y, playerDirection); | |
| break; | |
| case 'rocket': | |
| createRocket(playerPosition.x, playerPosition.y, playerDirection); | |
| break; | |
| case 'mine': | |
| placeMine(playerPosition.x, playerPosition.y); | |
| break; | |
| } | |
| } | |
| function rotateDirection(direction, degrees) { | |
| const directions = ['up', 'right', 'down', 'left']; | |
| let index = directions.indexOf(direction); | |
| const steps = Math.round(degrees / 90); | |
| index = (index + steps + directions.length) % directions.length; | |
| return directions[index]; | |
| } | |
| function createBullet(x, y, direction) { | |
| const bullet = { | |
| x: x * CELL_SIZE + CELL_SIZE / 2, | |
| y: y * CELL_SIZE + CELL_SIZE / 2, | |
| direction, | |
| speed: BULLET_SPEED, | |
| id: `bullet-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, | |
| type: 'bullet' | |
| }; | |
| bullets.push(bullet); | |
| const bulletElement = document.createElement('div'); | |
| bulletElement.id = bullet.id; | |
| bulletElement.className = 'bullet absolute bg-yellow-400 rounded-full'; | |
| bulletElement.style.width = `${CELL_SIZE / 4}px`; | |
| bulletElement.style.height = `${CELL_SIZE / 4}px`; | |
| bulletElement.style.left = `${bullet.x - CELL_SIZE / 8}px`; | |
| bulletElement.style.top = `${bullet.y - CELL_SIZE / 8}px`; | |
| gameContainer.appendChild(bulletElement); | |
| } | |
| function createLaser(x, y, direction) { | |
| const laser = { | |
| x: x * CELL_SIZE + CELL_SIZE / 2, | |
| y: y * CELL_SIZE + CELL_SIZE / 2, | |
| direction, | |
| distance: 0, | |
| maxDistance: CELL_SIZE * 10, | |
| id: `laser-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, | |
| type: 'laser' | |
| }; | |
| bullets.push(laser); | |
| const laserElement = document.createElement('div'); | |
| laserElement.id = laser.id; | |
| laserElement.className = 'bullet absolute bg-red-500'; | |
| laserElement.style.width = direction === 'left' || direction === 'right' ? '2px' : `${CELL_SIZE / 2}px`; | |
| laserElement.style.height = direction === 'up' || direction === 'down' ? '2px' : `${CELL_SIZE / 2}px`; | |
| updateLaserPosition(laser, laserElement); | |
| gameContainer.appendChild(laserElement); | |
| // Laser only lasts for a short time | |
| setTimeout(() => { | |
| if (laserElement.parentNode) { | |
| gameContainer.removeChild(laserElement); | |
| bullets = bullets.filter(b => b.id !== laser.id); | |
| } | |
| }, 300); | |
| } | |
| function createRocket(x, y, direction) { | |
| const rocket = { | |
| x: x * CELL_SIZE + CELL_SIZE / 2, | |
| y: y * CELL_SIZE + CELL_SIZE / 2, | |
| direction, | |
| speed: BULLET_SPEED * 0.8, | |
| id: `rocket-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, | |
| type: 'rocket' | |
| }; | |
| bullets.push(rocket); | |
| const rocketElement = document.createElement('div'); | |
| rocketElement.id = rocket.id; | |
| rocketElement.className = 'bullet absolute'; | |
| rocketElement.innerHTML = '<i class="fas fa-rocket text-orange-500"></i>'; | |
| rocketElement.style.fontSize = `${CELL_SIZE / 2}px`; | |
| rocketElement.style.transform = `rotate(${direction === 'right' ? 0 : direction === 'left' ? 180 : direction === 'up' ? -90 : 90}deg)`; | |
| rocketElement.style.left = `${rocket.x - CELL_SIZE / 4}px`; | |
| rocketElement.style.top = `${rocket.y - CELL_SIZE / 4}px`; | |
| gameContainer.appendChild(rocketElement); | |
| } | |
| function placeMine(x, y) { | |
| // Check if there's already a mine here | |
| if (mines.some(m => m.x === x && m.y === y)) return; | |
| const mine = { | |
| x, | |
| y, | |
| id: `mine-${Date.now()}-${Math.random().toString(36).substr(2, 5)}` | |
| }; | |
| mines.push(mine); | |
| const mineElement = document.createElement('div'); | |
| mineElement.id = mine.id; | |
| mineElement.className = 'absolute'; | |
| mineElement.innerHTML = '<i class="fas fa-land-mine-on text-gray-400"></i>'; | |
| mineElement.style.fontSize = `${CELL_SIZE / 2}px`; | |
| mineElement.style.left = `${x * CELL_SIZE + CELL_SIZE / 4}px`; | |
| mineElement.style.top = `${y * CELL_SIZE + CELL_SIZE / 4}px`; | |
| gameContainer.appendChild(mineElement); | |
| } | |
| function updatePlayer(deltaTime) { | |
| // Check if we can change direction | |
| const nextX = playerPosition.x + (nextDirection === 'left' ? -1 : nextDirection === 'right' ? 1 : 0); | |
| const nextY = playerPosition.y + (nextDirection === 'up' ? -1 : nextDirection === 'down' ? 1 : 0); | |
| if (!walls.some(w => w.x === nextX && w.y === nextY)) { | |
| playerDirection = nextDirection; | |
| updatePlayerMouth(); | |
| } | |
| // Move in current direction | |
| const moveX = playerDirection === 'left' ? -1 : playerDirection === 'right' ? 1 : 0; | |
| const moveY = playerDirection === 'up' ? -1 : playerDirection === 'down' ? 1 : 0; | |
| const newX = playerPosition.x + moveX * (deltaTime / PLAYER_SPEED); | |
| const newY = playerPosition.y + moveY * (deltaTime / PLAYER_SPEED); | |
| // Check wall collisions | |
| const cellX = Math.round(newX); | |
| const cellY = Math.round(newY); | |
| if (!walls.some(w => w.x === cellX && w.y === cellY)) { | |
| playerPosition.x = newX; | |
| playerPosition.y = newY; | |
| // Wrap around edges | |
| if (playerPosition.x < 0) playerPosition.x = GRID_SIZE - 1; | |
| if (playerPosition.x >= GRID_SIZE) playerPosition.x = 0; | |
| if (playerPosition.y < 0) playerPosition.y = GRID_SIZE - 1; | |
| if (playerPosition.y >= GRID_SIZE) playerPosition.y = 0; | |
| // Update player element | |
| const player = document.getElementById('player'); | |
| player.style.left = `${playerPosition.x * CELL_SIZE + 2}px`; | |
| player.style.top = `${playerPosition.y * CELL_SIZE + 2}px`; | |
| } | |
| } | |
| function updateGhosts(deltaTime) { | |
| ghosts.forEach(ghost => { | |
| // Change direction randomly or when hitting a wall | |
| if (Math.random() < 0.02 || | |
| walls.some(w => | |
| w.x === ghost.x + (ghost.direction === 'left' ? -1 : ghost.direction === 'right' ? 1 : 0) && | |
| w.y === ghost.y + (ghost.direction === 'up' ? -1 : ghost.direction === 'down' ? 1 : 0))) { | |
| const directions = ['up', 'down', 'left', 'right']; | |
| ghost.direction = directions[Math.floor(Math.random() * directions.length)]; | |
| } | |
| // Move ghost | |
| const moveX = ghost.direction === 'left' ? -1 : ghost.direction === 'right' ? 1 : 0; | |
| const moveY = ghost.direction === 'up' ? -1 : ghost.direction === 'down' ? 1 : 0; | |
| const newX = ghost.x + moveX * (deltaTime / ghost.speed); | |
| const newY = ghost.y + moveY * (deltaTime / ghost.speed); | |
| // Check wall collisions | |
| const cellX = Math.round(newX); | |
| const cellY = Math.round(newY); | |
| if (!walls.some(w => w.x === cellX && w.y === cellY)) { | |
| ghost.x = newX; | |
| ghost.y = newY; | |
| // Update ghost element | |
| const ghostElement = document.getElementById(ghost.id); | |
| if (ghostElement) { | |
| ghostElement.style.left = `${ghost.x * CELL_SIZE + 2}px`; | |
| ghostElement.style.top = `${ghost.y * CELL_SIZE + 2}px`; | |
| // Update eyes direction | |
| const eyes = ghostElement.querySelectorAll('div'); | |
| eyes.forEach(eye => { | |
| eye.style.left = ghost.direction === 'left' ? `${CELL_SIZE / 4}px` : | |
| ghost.direction === 'right' ? `${CELL_SIZE / 2}px` : | |
| `${CELL_SIZE / 3}px`; | |
| eye.style.top = ghost.direction === 'up' ? `${CELL_SIZE / 4}px` : | |
| ghost.direction === 'down' ? `${CELL_SIZE / 2}px` : | |
| `${CELL_SIZE / 3}px`; | |
| }); | |
| } | |
| } | |
| }); | |
| } | |
| function updateBullets() { | |
| bullets.forEach(bullet => { | |
| if (bullet.type === 'laser') { | |
| // Laser grows until max distance | |
| bullet.distance += BULLET_SPEED * 2; | |
| if (bullet.distance > bullet.maxDistance) { | |
| bullet.distance = bullet.maxDistance; | |
| } | |
| const laserElement = document.getElementById(bullet.id); | |
| if (laserElement) { | |
| updateLaserPosition(bullet, laserElement); | |
| } | |
| } else { | |
| // Move bullet | |
| const moveX = bullet.direction === 'left' ? -1 : bullet.direction === 'right' ? 1 : 0; | |
| const moveY = bullet.direction === 'up' ? -1 : bullet.direction === 'down' ? 1 : 0; | |
| bullet.x += moveX * bullet.speed; | |
| bullet.y += moveY * bullet.speed; | |
| // Update bullet element | |
| const bulletElement = document.getElementById(bullet.id); | |
| if (bulletElement) { | |
| if (bullet.type === 'rocket') { | |
| bulletElement.style.left = `${bullet.x - CELL_SIZE / 4}px`; | |
| bulletElement.style.top = `${bullet.y - CELL_SIZE / 4}px`; | |
| } else { | |
| bulletElement.style.left = `${bullet.x - CELL_SIZE / 8}px`; | |
| bulletElement.style.top = `${bullet.y - CELL_SIZE / 8}px`; | |
| } | |
| } | |
| } | |
| // Check if bullet is out of bounds | |
| if (bullet.x < 0 || bullet.x > GRID_SIZE * CELL_SIZE || | |
| bullet.y < 0 || bullet.y > GRID_SIZE * CELL_SIZE) { | |
| removeBullet(bullet.id); | |
| } | |
| }); | |
| } | |
| function updateLaserPosition(laser, laserElement) { | |
| const centerX = laser.x; | |
| const centerY = laser.y; | |
| if (laser.direction === 'left' || laser.direction === 'right') { | |
| const length = laser.distance; | |
| const left = laser.direction === 'left' ? centerX - length : centerX; | |
| laserElement.style.left = `${left}px`; | |
| laserElement.style.top = `${centerY - 1}px`; | |
| laserElement.style.width = `${length}px`; | |
| } else { | |
| const length = laser.distance; | |
| const top = laser.direction === 'up' ? centerY - length : centerY; | |
| laserElement.style.left = `${centerX - 1}px`; | |
| laserElement.style.top = `${top}px`; | |
| laserElement.style.height = `${length}px`; | |
| } | |
| } | |
| function updateMines() { | |
| // Mines don't move, just check for explosions | |
| } | |
| function removeBullet(id) { | |
| const bulletElement = document.getElementById(id); | |
| if (bulletElement && bulletElement.parentNode) { | |
| gameContainer.removeChild(bulletElement); | |
| } | |
| bullets = bullets.filter(b => b.id !== id); | |
| } | |
| function checkCollisions() { | |
| // Check pellet collection | |
| const playerCellX = Math.round(playerPosition.x); | |
| const playerCellY = Math.round(playerPosition.y); | |
| // Check pellets | |
| const pelletIndex = pellets.findIndex(p => p.x === playerCellX && p.y === playerCellY); | |
| if (pelletIndex !== -1) { | |
| pellets.splice(pelletIndex, 1); | |
| score += 10; | |
| // Remove pellet element | |
| const pelletElements = document.querySelectorAll(`[data-x="${playerCellX}"][data-y="${playerCellY}"]`); | |
| pelletElements.forEach(el => { | |
| if (el.parentNode) gameContainer.removeChild(el); | |
| }); | |
| updateDisplays(); | |
| } | |
| // Check powerups | |
| const powerupIndex = powerups.findIndex(p => p.x === playerCellX && p.y === playerCellY); | |
| if (powerupIndex !== -1) { | |
| powerups.splice(powerupIndex, 1); | |
| score += 50; | |
| // Refill all ammo | |
| Object.keys(ammo).forEach(key => { | |
| if (key !== 'pistol') ammo[key] += 10; | |
| }); | |
| // Remove powerup element | |
| const powerupElements = document.querySelectorAll(`[data-x="${playerCellX}"][data-y="${playerCellY}"]`); | |
| powerupElements.forEach(el => { | |
| if (el.parentNode) gameContainer.removeChild(el); | |
| }); | |
| updateDisplays(); | |
| } | |
| // Check ghost collisions | |
| ghosts.forEach(ghost => { | |
| const ghostCellX = Math.round(ghost.x); | |
| const ghostCellY = Math.round(ghost.y); | |
| if (ghostCellX === playerCellX && ghostCellY === playerCellY) { | |
| loseLife(); | |
| } | |
| }); | |
| // Check bullet collisions with ghosts | |
| bullets.forEach(bullet => { | |
| const bulletCellX = Math.round(bullet.x / CELL_SIZE); | |
| const bulletCellY = Math.round(bullet.y / CELL_SIZE); | |
| if (bullet.type === 'laser') { | |
| // Laser hits everything in its path | |
| const hitGhosts = ghosts.filter(ghost => { | |
| const ghostCellX = Math.round(ghost.x); | |
| const ghostCellY = Math.round(ghost.y); | |
| if (bullet.direction === 'left' || bullet.direction === 'right') { | |
| return ghostCellY === bulletCellY && | |
| ((bullet.direction === 'left' && ghostCellX <= bulletCellX) || | |
| (bullet.direction === 'right' && ghostCellX >= bulletCellX)); | |
| } else { | |
| return ghostCellX === bulletCellX && | |
| ((bullet.direction === 'up' && ghostCellY <= bulletCellY) || | |
| (bullet.direction === 'down' && ghostCellY >= bulletCellY)); | |
| } | |
| }); | |
| hitGhosts.forEach(ghost => { | |
| killGhost(ghost); | |
| }); | |
| if (hitGhosts.length > 0) { | |
| removeBullet(bullet.id); | |
| } | |
| } else if (bullet.type === 'rocket') { | |
| // Rocket explodes when hitting a ghost or wall | |
| const hitGhost = ghosts.find(ghost => { | |
| const ghostCellX = Math.round(ghost.x); | |
| const ghostCellY = Math.round(ghost.y); | |
| return ghostCellX === bulletCellX && ghostCellY === bulletCellY; | |
| }); | |
| const hitWall = walls.some(w => w.x === bulletCellX && w.y === bulletCellY); | |
| if (hitGhost || hitWall) { | |
| // Explosion effect | |
| createExplosion(bulletCellX, bulletCellY); | |
| removeBullet(bullet.id); | |
| // Kill ghosts in 3x3 area | |
| for (let y = bulletCellY - 1; y <= bulletCellY + 1; y++) { | |
| for (let x = bulletCellX - 1; x <= bulletCellX + 1; x++) { | |
| const ghostIndex = ghosts.findIndex(g => | |
| Math.round(g.x) === x && Math.round(g.y) === y); | |
| if (ghostIndex !== -1) { | |
| killGhost(ghosts[ghostIndex]); | |
| } | |
| } | |
| } | |
| } | |
| } else { | |
| // Regular bullet hits single ghost | |
| const ghostIndex = ghosts.findIndex(ghost => { | |
| const ghostCellX = Math.round(ghost.x); | |
| const ghostCellY = Math.round(ghost.y); | |
| return ghostCellX === bulletCellX && ghostCellY === bulletCellY; | |
| }); | |
| if (ghostIndex !== -1) { | |
| killGhost(ghosts[ghostIndex]); | |
| removeBullet(bullet.id); | |
| } | |
| } | |
| // Check bullet collisions with walls | |
| if (walls.some(w => w.x === bulletCellX && w.y === bulletCellY)) { | |
| removeBullet(bullet.id); | |
| } | |
| }); | |
| // Check mine explosions | |
| mines.forEach(mine => { | |
| const mineCellX = mine.x; | |
| const mineCellY = mine.y; | |
| // Check if player stepped on mine | |
| if (playerCellX === mineCellX && playerCellY === mineCellY) { | |
| createExplosion(mineCellX, mineCellY); | |
| removeMine(mine.id); | |
| loseLife(); | |
| } | |
| // Check if ghost stepped on mine | |
| const ghostIndex = ghosts.findIndex(ghost => | |
| Math.round(ghost.x) === mineCellX && Math.round(ghost.y) === mineCellY); | |
| if (ghostIndex !== -1) { | |
| createExplosion(mineCellX, mineCellY); | |
| removeMine(mine.id); | |
| killGhost(ghosts[ghostIndex]); | |
| } | |
| }); | |
| } | |
| function createExplosion(x, y) { | |
| const explosion = document.createElement('div'); | |
| explosion.className = 'absolute bg-orange-500 rounded-full'; | |
| explosion.style.width = `${CELL_SIZE * 3}px`; | |
| explosion.style.height = `${CELL_SIZE * 3}px`; | |
| explosion.style.left = `${x * CELL_SIZE - CELL_SIZE}px`; | |
| explosion.style.top = `${y * CELL_SIZE - CELL_SIZE}px`; | |
| explosion.style.opacity = '0.7'; | |
| explosion.style.transform = 'scale(0)'; | |
| explosion.style.transition = 'transform 0.3s ease-out, opacity 0.3s ease-out'; | |
| gameContainer.appendChild(explosion); | |
| // Animate explosion | |
| setTimeout(() => { | |
| explosion.style.transform = 'scale(1)'; | |
| explosion.style.opacity = '0'; | |
| }, 10); | |
| // Remove after animation | |
| setTimeout(() => { | |
| if (explosion.parentNode) gameContainer.removeChild(explosion); | |
| }, 500); | |
| } | |
| function removeMine(id) { | |
| const mineElement = document.getElementById(id); | |
| if (mineElement && mineElement.parentNode) { | |
| gameContainer.removeChild(mineElement); | |
| } | |
| mines = mines.filter(m => m.id !== id); | |
| } | |
| function killGhost(ghost) { | |
| score += 100; | |
| // Remove ghost element | |
| const ghostElement = document.getElementById(ghost.id); | |
| if (ghostElement && ghostElement.parentNode) { | |
| // Death animation | |
| ghostElement.style.transform = 'scale(1.5)'; | |
| ghostElement.style.opacity = '0'; | |
| ghostElement.style.transition = 'all 0.3s ease-out'; | |
| setTimeout(() => { | |
| if (ghostElement.parentNode) gameContainer.removeChild(ghostElement); | |
| }, 300); | |
| } | |
| // Remove from array | |
| ghosts = ghosts.filter(g => g.id !== ghost.id); | |
| updateDisplays(); | |
| } | |
| function loseLife() { | |
| lives--; | |
| updateDisplays(); | |
| if (lives <= 0) { | |
| gameOver(false); | |
| } else { | |
| // Reset player position | |
| playerPosition = { x: 1, y: 1 }; | |
| const player = document.getElementById('player'); | |
| player.style.left = `${playerPosition.x * CELL_SIZE + 2}px`; | |
| player.style.top = `${playerPosition.y * CELL_SIZE + 2}px`; | |
| // Brief invulnerability | |
| player.style.opacity = '0.5'; | |
| setTimeout(() => { | |
| player.style.opacity = '1'; | |
| }, 1000); | |
| } | |
| } | |
| function checkWinCondition() { | |
| if (pellets.length === 0 && powerups.length === 0) { | |
| gameOver(true); | |
| } | |
| } | |
| function gameOver(won) { | |
| gameRunning = false; | |
| overlayTitle.textContent = won ? 'VICTORY!' : 'GAME OVER'; | |
| overlayTitle.className = won ? 'text-3xl font-bold mb-4 text-green-400' : 'text-3xl font-bold mb-4 text-red-400'; | |
| finalScore.textContent = score; | |
| if (won) { | |
| overlayMessage.innerHTML = `You cleared the maze with <span class="text-green-400">${lives}</span> lives remaining!`; | |
| } else { | |
| overlayMessage.innerHTML = 'You were defeated by the ghosts!'; | |
| } | |
| gameOverlay.classList.remove('hidden'); | |
| } | |
| function updateDisplays() { | |
| scoreDisplay.textContent = score; | |
| livesDisplay.textContent = lives; | |
| weaponDisplay.textContent = currentWeapon.toUpperCase(); | |
| ammoDisplay.textContent = ammo[currentWeapon] === Infinity ? '∞' : ammo[currentWeapon]; | |
| // Update weapon icons to show ammo | |
| weaponIcons.forEach(icon => { | |
| const weapon = icon.dataset.weapon; | |
| if (weapon !== 'pistol') { | |
| const ammoText = document.createElement('span'); | |
| ammoText.className = 'text-xs mt-1'; | |
| ammoText.textContent = ammo[weapon]; | |
| // Remove existing ammo display if any | |
| const existingAmmo = icon.querySelector('.text-xs'); | |
| if (existingAmmo) icon.removeChild(existingAmmo); | |
| icon.appendChild(ammoText); | |
| } | |
| }); | |
| } | |
| function resetGame() { | |
| // Reset game state | |
| score = 0; | |
| lives = 3; | |
| gameRunning = true; | |
| playerDirection = 'right'; | |
| nextDirection = 'right'; | |
| playerPosition = { x: 1, y: 1 }; | |
| ghosts = []; | |
| bullets = []; | |
| powerups = []; | |
| pellets = []; | |
| mines = []; | |
| currentWeapon = 'pistol'; | |
| ammo = { | |
| pistol: Infinity, | |
| shotgun: 20, | |
| laser: 30, | |
| rocket: 5, | |
| mine: 3 | |
| }; | |
| // Hide overlay | |
| gameOverlay.classList.add('hidden'); | |
| // Reinitialize game | |
| initGame(); | |
| // Restart game loop | |
| lastTime = 0; | |
| requestAnimationFrame(gameLoop); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ILLERRAPS/weapon-pac" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |