- Add interval management system to fix memory leaks and cleanup bugs - Implement win condition (survive 3 minutes) with victory screen - Add pause functionality (ESC/P key) with pause overlay menu - Add progressive difficulty (enemy speed/spawn rate increase over time) - Implement enemy variety: Normal, Fast (green), and Tank (larger, 2 HP) - Add boss fights every 60 seconds with health bar - Add power-ups: Extra Life, Triple Shot, Shield (10-15s spawn interval) - Implement Hardcore Mode (1 life, faster enemies, no power-ups) - Add high score system with localStorage persistence - Add background music support with volume control - Add visual feedback (player hit flash, enemy death animation, shield glow) - Add mobile touch controls (auto-detected on touch devices) - Improve collision detection using getBoundingClientRect - Add responsive styles for mobile devices
818 lines
24 KiB
JavaScript
818 lines
24 KiB
JavaScript
// DOM Elements
|
|
const gameIntro = document.getElementById('game-intro');
|
|
const gameArea = document.getElementById("gameArea");
|
|
const gameEnd = document.getElementById('game-end');
|
|
const gameVictory = document.getElementById('game-victory');
|
|
const pauseOverlay = document.getElementById('pause-overlay');
|
|
const startButton = document.getElementById('start-button');
|
|
const hardcoreButton = document.getElementById('hardcore-button');
|
|
const restartButton = document.getElementById('restart-button');
|
|
const victoryRestartButton = document.getElementById('victory-restart-button');
|
|
const scoreElement = document.getElementById('score');
|
|
const livesElement = document.getElementById('lives');
|
|
const timerElement = document.getElementById('timer');
|
|
const finalScoreElement = document.getElementById('final-score');
|
|
const finalTimeElement = document.getElementById('final-time');
|
|
const victoryScoreElement = document.getElementById('victory-score');
|
|
const victoryTimeElement = document.getElementById('victory-time');
|
|
const gameAreaHeight = window.innerHeight * 0.8;
|
|
const gameAreaWidth = window.innerWidth * 0.8;
|
|
const stats = document.getElementById('stats');
|
|
const volumeSlider = document.getElementById('volume');
|
|
const highScoreDisplay = document.getElementById('high-score-display');
|
|
const finalHighScore = document.getElementById('final-high-score');
|
|
const victoryHighScore = document.getElementById('victory-high-score');
|
|
const touchControls = document.getElementById('touch-controls');
|
|
|
|
// Game state variables
|
|
let player = null;
|
|
let activeIntervals = [];
|
|
let activeTimeouts = [];
|
|
let sec = 0;
|
|
let min = 0;
|
|
let lives = 3;
|
|
let score = 0;
|
|
let playerY = gameAreaHeight / 2 - 25;
|
|
let gameVolume = 0.4;
|
|
let isPaused = false;
|
|
let isGameRunning = false;
|
|
let isHardcoreMode = false;
|
|
let difficultyLevel = 1;
|
|
let hasShield = false;
|
|
let hasTripleShot = false;
|
|
let tripleShotTimeout = null;
|
|
let shieldTimeout = null;
|
|
let backgroundMusic = null;
|
|
let bossSpawned = false;
|
|
let lastBossTime = 0;
|
|
let keydownHandler = null;
|
|
|
|
// Interval management helpers
|
|
function addInterval(fn, ms, label = '') {
|
|
const id = setInterval(() => {
|
|
if (!isPaused) {
|
|
fn();
|
|
}
|
|
}, ms);
|
|
activeIntervals.push({ id, label });
|
|
return id;
|
|
}
|
|
|
|
function addTimeout(fn, ms, label = '') {
|
|
const id = setTimeout(() => {
|
|
if (!isPaused && isGameRunning) {
|
|
fn();
|
|
}
|
|
// Remove from tracking
|
|
activeTimeouts = activeTimeouts.filter(t => t.id !== id);
|
|
}, ms);
|
|
activeTimeouts.push({ id, label });
|
|
return id;
|
|
}
|
|
|
|
function clearAllIntervals() {
|
|
activeIntervals.forEach(interval => clearInterval(interval.id));
|
|
activeIntervals = [];
|
|
activeTimeouts.forEach(timeout => clearTimeout(timeout.id));
|
|
activeTimeouts = [];
|
|
}
|
|
|
|
function resetGame() {
|
|
clearAllIntervals();
|
|
|
|
// Clear power-up effects
|
|
if (tripleShotTimeout) clearTimeout(tripleShotTimeout);
|
|
if (shieldTimeout) clearTimeout(shieldTimeout);
|
|
hasShield = false;
|
|
hasTripleShot = false;
|
|
|
|
// Remove all game elements
|
|
document.querySelectorAll(".enemy").forEach(e => e.remove());
|
|
document.querySelectorAll(".bullet").forEach(b => b.remove());
|
|
document.querySelectorAll(".boss").forEach(b => b.remove());
|
|
document.querySelectorAll(".power-up").forEach(p => p.remove());
|
|
document.querySelectorAll(".boss-health-bar").forEach(h => h.remove());
|
|
|
|
// Remove player if exists
|
|
if (player && player.parentNode) {
|
|
player.remove();
|
|
}
|
|
player = null;
|
|
|
|
// Remove keydown listener
|
|
if (keydownHandler) {
|
|
document.removeEventListener("keydown", keydownHandler);
|
|
keydownHandler = null;
|
|
}
|
|
|
|
// Stop background music
|
|
stopBackgroundMusic();
|
|
|
|
// Reset state
|
|
sec = 0;
|
|
min = 0;
|
|
lives = isHardcoreMode ? 1 : 3;
|
|
score = 0;
|
|
playerY = gameAreaHeight / 2 - 25;
|
|
isPaused = false;
|
|
isGameRunning = false;
|
|
difficultyLevel = 1;
|
|
bossSpawned = false;
|
|
lastBossTime = 0;
|
|
}
|
|
|
|
// High Score functions
|
|
function getHighScore() {
|
|
const key = isHardcoreMode ? 'scriptocalypse_highscore_hardcore' : 'scriptocalypse_highscore';
|
|
return parseInt(localStorage.getItem(key)) || 0;
|
|
}
|
|
|
|
function updateHighScore() {
|
|
const key = isHardcoreMode ? 'scriptocalypse_highscore_hardcore' : 'scriptocalypse_highscore';
|
|
const currentHigh = getHighScore();
|
|
if (score > currentHigh) {
|
|
localStorage.setItem(key, score);
|
|
return true; // New record
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function displayHighScores() {
|
|
const normalHigh = parseInt(localStorage.getItem('scriptocalypse_highscore')) || 0;
|
|
const hardcoreHigh = parseInt(localStorage.getItem('scriptocalypse_highscore_hardcore')) || 0;
|
|
if (highScoreDisplay) {
|
|
highScoreDisplay.innerHTML = `High Score: ${normalHigh}<br>Hardcore High: ${hardcoreHigh}`;
|
|
}
|
|
}
|
|
|
|
// Background Music
|
|
function playBackgroundMusic() {
|
|
if (!backgroundMusic) {
|
|
backgroundMusic = new Audio('sounds/music.mp3');
|
|
backgroundMusic.loop = true;
|
|
}
|
|
backgroundMusic.volume = gameVolume;
|
|
backgroundMusic.play().catch(() => {
|
|
// Autoplay blocked, will play on first interaction
|
|
});
|
|
}
|
|
|
|
function stopBackgroundMusic() {
|
|
if (backgroundMusic) {
|
|
backgroundMusic.pause();
|
|
backgroundMusic.currentTime = 0;
|
|
}
|
|
}
|
|
|
|
// Volume control
|
|
volumeSlider.addEventListener('input', (event) => {
|
|
gameVolume = parseFloat(event.target.value);
|
|
if (backgroundMusic) {
|
|
backgroundMusic.volume = gameVolume;
|
|
}
|
|
});
|
|
|
|
function playSound(src) {
|
|
const sound = new Audio(src);
|
|
sound.volume = gameVolume;
|
|
sound.play();
|
|
}
|
|
|
|
// Touch device detection
|
|
function isTouchDevice() {
|
|
return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
|
|
}
|
|
|
|
function setupTouchControls() {
|
|
if (isTouchDevice() && touchControls) {
|
|
touchControls.style.display = 'flex';
|
|
|
|
const upBtn = document.getElementById('touch-up');
|
|
const downBtn = document.getElementById('touch-down');
|
|
const shootBtn = document.getElementById('touch-shoot');
|
|
|
|
if (upBtn) {
|
|
upBtn.addEventListener('touchstart', (e) => {
|
|
e.preventDefault();
|
|
if (playerY < gameAreaHeight - 60 && !isPaused) {
|
|
playerY += 40;
|
|
player.style.bottom = playerY + "px";
|
|
}
|
|
});
|
|
}
|
|
|
|
if (downBtn) {
|
|
downBtn.addEventListener('touchstart', (e) => {
|
|
e.preventDefault();
|
|
if (playerY > 0 && !isPaused) {
|
|
playerY -= 40;
|
|
player.style.bottom = playerY + "px";
|
|
}
|
|
});
|
|
}
|
|
|
|
if (shootBtn) {
|
|
shootBtn.addEventListener('touchstart', (e) => {
|
|
e.preventDefault();
|
|
if (!isPaused) {
|
|
shoot();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pause functionality
|
|
function togglePause() {
|
|
if (!isGameRunning) return;
|
|
|
|
isPaused = !isPaused;
|
|
|
|
if (pauseOverlay) {
|
|
pauseOverlay.style.display = isPaused ? 'flex' : 'none';
|
|
}
|
|
|
|
if (isPaused) {
|
|
if (backgroundMusic) backgroundMusic.pause();
|
|
} else {
|
|
if (backgroundMusic) backgroundMusic.play().catch(() => {});
|
|
}
|
|
}
|
|
|
|
// Visual feedback
|
|
function flashPlayer() {
|
|
if (!player) return;
|
|
player.classList.add('player-hit');
|
|
setTimeout(() => {
|
|
if (player) player.classList.remove('player-hit');
|
|
}, 300);
|
|
}
|
|
|
|
function enemyDeathEffect(enemy) {
|
|
enemy.classList.add('enemy-death');
|
|
setTimeout(() => {
|
|
enemy.remove();
|
|
}, 200);
|
|
}
|
|
|
|
// Setup Game Intro state
|
|
function introPage() {
|
|
gameIntro.style.display = 'block';
|
|
gameArea.style.display = 'none';
|
|
gameEnd.style.display = 'none';
|
|
if (gameVictory) gameVictory.style.display = 'none';
|
|
stats.style.display = 'none';
|
|
if (touchControls) touchControls.style.display = 'none';
|
|
displayHighScores();
|
|
}
|
|
|
|
// Initialize game
|
|
function initGame(hardcore = false) {
|
|
resetGame();
|
|
isHardcoreMode = hardcore;
|
|
lives = isHardcoreMode ? 1 : 3;
|
|
|
|
gameArea.style.width = gameAreaWidth + "px";
|
|
gameArea.style.height = gameAreaHeight + "px";
|
|
gameArea.style.position = "relative";
|
|
gameArea.style.overflow = "hidden";
|
|
gameIntro.style.display = 'none';
|
|
gameArea.style.display = 'block';
|
|
gameEnd.style.display = 'none';
|
|
if (gameVictory) gameVictory.style.display = 'none';
|
|
if (pauseOverlay) pauseOverlay.style.display = 'none';
|
|
stats.style.display = 'block';
|
|
|
|
scoreElement.innerText = score;
|
|
livesElement.innerText = lives;
|
|
timerElement.innerText = 'Time elapsed: 0:00';
|
|
|
|
isGameRunning = true;
|
|
|
|
// Start game systems
|
|
addInterval(timeGame, 1000, 'timer');
|
|
spawnPlayer();
|
|
spawnEnemy();
|
|
|
|
if (!isHardcoreMode) {
|
|
// Spawn power-ups every 15-20 seconds (not in hardcore)
|
|
schedulePowerUp();
|
|
}
|
|
|
|
playBackgroundMusic();
|
|
setupTouchControls();
|
|
}
|
|
|
|
// Start button handlers
|
|
function startGame() {
|
|
startButton.addEventListener('click', () => {
|
|
initGame(false);
|
|
});
|
|
|
|
if (hardcoreButton) {
|
|
hardcoreButton.addEventListener('click', () => {
|
|
initGame(true);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Game Time
|
|
function timeGame() {
|
|
if (!isGameRunning) return;
|
|
|
|
sec++;
|
|
if (sec >= 60) {
|
|
min++;
|
|
sec = 0;
|
|
}
|
|
|
|
const displaySec = sec < 10 ? "0" + sec : sec;
|
|
timerElement.innerText = 'Time elapsed: ' + min + ':' + displaySec;
|
|
|
|
// Update difficulty every 30 seconds
|
|
const totalSeconds = min * 60 + sec;
|
|
difficultyLevel = 1 + Math.floor(totalSeconds / 30) * 0.2;
|
|
|
|
// Check for boss spawn (every 60 seconds)
|
|
if (totalSeconds > 0 && totalSeconds % 60 === 0 && totalSeconds !== lastBossTime) {
|
|
lastBossTime = totalSeconds;
|
|
spawnBoss();
|
|
}
|
|
|
|
// Win condition: survive 3 minutes
|
|
if (min >= 3) {
|
|
victoryGame();
|
|
}
|
|
}
|
|
|
|
function spawnPlayer() {
|
|
player = document.createElement("img");
|
|
player.src = "images/shooter.gif";
|
|
player.classList.add("player");
|
|
gameArea.appendChild(player);
|
|
player.style.bottom = playerY + "px";
|
|
|
|
// Setup keyboard controls (only once)
|
|
keydownHandler = (e) => {
|
|
if (isPaused && e.key !== "Escape" && e.key !== "p" && e.key !== "P") return;
|
|
|
|
if (e.key === "Escape" || e.key === "p" || e.key === "P") {
|
|
togglePause();
|
|
return;
|
|
}
|
|
|
|
if (e.key === "ArrowUp" && playerY < gameAreaHeight - 60) {
|
|
playerY += 40;
|
|
} else if (e.key === "ArrowDown" && playerY > 0) {
|
|
playerY -= 40;
|
|
} else if (e.key === " ") {
|
|
e.preventDefault();
|
|
shoot();
|
|
}
|
|
player.style.bottom = playerY + "px";
|
|
};
|
|
document.addEventListener("keydown", keydownHandler);
|
|
|
|
// Collision check interval
|
|
addInterval(() => {
|
|
if (!player) return;
|
|
|
|
// Check enemy collisions
|
|
document.querySelectorAll(".enemy").forEach(enemy => {
|
|
checkCollisionEnemy(player, enemy);
|
|
});
|
|
|
|
// Check boss collisions
|
|
document.querySelectorAll(".boss").forEach(boss => {
|
|
checkCollisionEnemy(player, boss);
|
|
});
|
|
|
|
// Check power-up collisions
|
|
document.querySelectorAll(".power-up").forEach(powerUp => {
|
|
checkPowerUpCollision(player, powerUp);
|
|
});
|
|
}, 20, 'playerCollision');
|
|
}
|
|
|
|
function shoot() {
|
|
if (isPaused || !isGameRunning) return;
|
|
|
|
if (hasTripleShot) {
|
|
// Shoot 3 bullets
|
|
createBullet(playerY + 10);
|
|
createBullet(playerY + 20);
|
|
createBullet(playerY + 30);
|
|
} else {
|
|
createBullet(playerY + 20);
|
|
}
|
|
|
|
playSound("sounds/shoot.mp3");
|
|
}
|
|
|
|
function createBullet(yPosition) {
|
|
const bullet = document.createElement("div");
|
|
bullet.classList.add("bullet");
|
|
gameArea.appendChild(bullet);
|
|
bullet.style.left = "60px";
|
|
bullet.style.bottom = yPosition + "px";
|
|
|
|
const bulletInterval = addInterval(() => {
|
|
let bulletX = bullet.offsetLeft;
|
|
|
|
// Check collision with enemies
|
|
document.querySelectorAll(".enemy").forEach(enemy => {
|
|
checkCollision(bullet, enemy);
|
|
});
|
|
|
|
// Check collision with boss
|
|
document.querySelectorAll(".boss").forEach(boss => {
|
|
checkBossCollision(bullet, boss);
|
|
});
|
|
|
|
if (bulletX > gameAreaWidth || !bullet.parentNode) {
|
|
bullet.remove();
|
|
clearInterval(bulletInterval);
|
|
activeIntervals = activeIntervals.filter(i => i.id !== bulletInterval);
|
|
} else {
|
|
bullet.style.left = bulletX + 10 + "px";
|
|
}
|
|
}, 20, 'bullet');
|
|
}
|
|
|
|
// Calculate spawn delay based on difficulty
|
|
function getSpawnDelay() {
|
|
const baseDelay = isHardcoreMode ? 1500 : 2000;
|
|
const minDelay = isHardcoreMode ? 200 : 300;
|
|
const totalSeconds = min * 60 + sec;
|
|
return Math.max(minDelay, baseDelay - (totalSeconds * 5));
|
|
}
|
|
|
|
// Calculate enemy speed based on difficulty and type
|
|
function getEnemySpeed(baseSpeed) {
|
|
const multiplier = isHardcoreMode ? 1.5 : 1;
|
|
return Math.round(baseSpeed * difficultyLevel * multiplier);
|
|
}
|
|
|
|
// Enemy types
|
|
function getRandomEnemyType() {
|
|
const rand = Math.random();
|
|
if (rand < 0.6) {
|
|
return { type: 'normal', speed: 5, health: 1, class: 'enemy-normal' };
|
|
} else if (rand < 0.85) {
|
|
return { type: 'fast', speed: 8, health: 1, class: 'enemy-fast' };
|
|
} else {
|
|
return { type: 'tank', speed: 3, health: 2, class: 'enemy-tank' };
|
|
}
|
|
}
|
|
|
|
function spawnEnemy() {
|
|
if (!isGameRunning) return;
|
|
|
|
const enemyType = getRandomEnemyType();
|
|
const enemy = document.createElement("img");
|
|
enemy.src = "images/zombie.gif";
|
|
enemy.classList.add("enemy", enemyType.class);
|
|
enemy.dataset.health = enemyType.health;
|
|
enemy.dataset.type = enemyType.type;
|
|
gameArea.appendChild(enemy);
|
|
enemy.style.right = "0px";
|
|
enemy.style.bottom = Math.random() * (gameAreaHeight - 50) + "px";
|
|
|
|
const speed = getEnemySpeed(enemyType.speed);
|
|
|
|
const enemyInterval = addInterval(() => {
|
|
if (!enemy.parentNode) {
|
|
clearInterval(enemyInterval);
|
|
activeIntervals = activeIntervals.filter(i => i.id !== enemyInterval);
|
|
return;
|
|
}
|
|
|
|
let enemyX = enemy.offsetLeft;
|
|
if (enemyX < 0) {
|
|
enemy.remove();
|
|
clearInterval(enemyInterval);
|
|
activeIntervals = activeIntervals.filter(i => i.id !== enemyInterval);
|
|
} else {
|
|
enemy.style.right = parseInt(enemy.style.right) + speed + "px";
|
|
}
|
|
}, 50, 'enemy');
|
|
|
|
// Schedule next enemy spawn
|
|
addTimeout(spawnEnemy, Math.random() * getSpawnDelay() + 100, 'spawnEnemy');
|
|
}
|
|
|
|
// Boss functionality
|
|
function spawnBoss() {
|
|
if (!isGameRunning) return;
|
|
|
|
const boss = document.createElement("img");
|
|
boss.src = "images/zombie.gif";
|
|
boss.classList.add("boss");
|
|
boss.dataset.health = 10;
|
|
boss.dataset.maxHealth = 10;
|
|
gameArea.appendChild(boss);
|
|
boss.style.right = "0px";
|
|
boss.style.bottom = (gameAreaHeight / 2 - 75) + "px";
|
|
|
|
// Create health bar
|
|
const healthBar = document.createElement("div");
|
|
healthBar.classList.add("boss-health-bar");
|
|
healthBar.innerHTML = '<div class="boss-health-fill"></div>';
|
|
boss.healthBar = healthBar;
|
|
gameArea.appendChild(healthBar);
|
|
|
|
const bossInterval = addInterval(() => {
|
|
if (!boss.parentNode) {
|
|
if (healthBar.parentNode) healthBar.remove();
|
|
clearInterval(bossInterval);
|
|
activeIntervals = activeIntervals.filter(i => i.id !== bossInterval);
|
|
return;
|
|
}
|
|
|
|
let bossX = boss.offsetLeft;
|
|
|
|
// Update health bar position
|
|
healthBar.style.left = (bossX - 20) + "px";
|
|
healthBar.style.bottom = (parseInt(boss.style.bottom) + 160) + "px";
|
|
|
|
if (bossX < 0) {
|
|
boss.remove();
|
|
healthBar.remove();
|
|
clearInterval(bossInterval);
|
|
activeIntervals = activeIntervals.filter(i => i.id !== bossInterval);
|
|
} else {
|
|
boss.style.right = parseInt(boss.style.right) + 2 + "px";
|
|
}
|
|
}, 50, 'boss');
|
|
|
|
playSound("sounds/lostlife.mp3"); // Boss spawn alert
|
|
}
|
|
|
|
function checkBossCollision(bullet, boss) {
|
|
if (!bullet.parentNode || !boss.parentNode) return;
|
|
|
|
if (bullet.offsetLeft < boss.offsetLeft + boss.offsetWidth &&
|
|
bullet.offsetLeft + bullet.offsetWidth > boss.offsetLeft &&
|
|
bullet.offsetTop < boss.offsetTop + boss.offsetHeight &&
|
|
bullet.offsetTop + bullet.offsetHeight > boss.offsetTop) {
|
|
|
|
bullet.remove();
|
|
|
|
let health = parseInt(boss.dataset.health);
|
|
health--;
|
|
boss.dataset.health = health;
|
|
|
|
// Update health bar
|
|
const maxHealth = parseInt(boss.dataset.maxHealth);
|
|
const healthPercent = (health / maxHealth) * 100;
|
|
const healthFill = boss.healthBar.querySelector('.boss-health-fill');
|
|
if (healthFill) {
|
|
healthFill.style.width = healthPercent + '%';
|
|
}
|
|
|
|
if (health <= 0) {
|
|
score += 10; // Boss worth more points
|
|
scoreElement.innerText = score;
|
|
if (boss.healthBar) boss.healthBar.remove();
|
|
enemyDeathEffect(boss);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Power-ups
|
|
function schedulePowerUp() {
|
|
if (!isGameRunning || isHardcoreMode) return;
|
|
|
|
const delay = 10000 + Math.random() * 5000; // 10-15 seconds
|
|
addTimeout(() => {
|
|
spawnPowerUp();
|
|
schedulePowerUp();
|
|
}, delay, 'powerUpSchedule');
|
|
}
|
|
|
|
function spawnPowerUp() {
|
|
if (!isGameRunning || isPaused) return;
|
|
|
|
const types = ['extraLife', 'tripleShot', 'shield'];
|
|
const type = types[Math.floor(Math.random() * types.length)];
|
|
|
|
const powerUp = document.createElement("div");
|
|
powerUp.classList.add("power-up", `power-up-${type}`);
|
|
powerUp.dataset.type = type;
|
|
gameArea.appendChild(powerUp);
|
|
powerUp.style.right = "0px";
|
|
powerUp.style.bottom = Math.random() * (gameAreaHeight - 40) + "px";
|
|
|
|
// Set icon based on type
|
|
switch (type) {
|
|
case 'extraLife': powerUp.innerHTML = '+'; break;
|
|
case 'tripleShot': powerUp.innerHTML = '3'; break;
|
|
case 'shield': powerUp.innerHTML = 'S'; break;
|
|
}
|
|
|
|
const powerUpInterval = addInterval(() => {
|
|
if (!powerUp.parentNode) {
|
|
clearInterval(powerUpInterval);
|
|
activeIntervals = activeIntervals.filter(i => i.id !== powerUpInterval);
|
|
return;
|
|
}
|
|
|
|
// Move faster than enemies so players can catch them (7px/50ms)
|
|
powerUp.style.right = parseInt(powerUp.style.right) + 7 + "px";
|
|
|
|
// Remove when off-screen (left side)
|
|
if (parseInt(powerUp.style.right) > gameAreaWidth + 50) {
|
|
powerUp.remove();
|
|
clearInterval(powerUpInterval);
|
|
activeIntervals = activeIntervals.filter(i => i.id !== powerUpInterval);
|
|
}
|
|
}, 50, 'powerUp');
|
|
}
|
|
|
|
function checkPowerUpCollision(player, powerUp) {
|
|
if (!player || !powerUp.parentNode) return;
|
|
|
|
// Use getBoundingClientRect for accurate collision with right-positioned elements
|
|
const playerRect = player.getBoundingClientRect();
|
|
const powerUpRect = powerUp.getBoundingClientRect();
|
|
|
|
if (playerRect.left < powerUpRect.right &&
|
|
playerRect.right > powerUpRect.left &&
|
|
playerRect.top < powerUpRect.bottom &&
|
|
playerRect.bottom > powerUpRect.top) {
|
|
|
|
collectPowerUp(powerUp.dataset.type);
|
|
powerUp.remove();
|
|
playSound("sounds/shoot.mp3");
|
|
}
|
|
}
|
|
|
|
function collectPowerUp(type) {
|
|
switch (type) {
|
|
case 'extraLife':
|
|
lives++;
|
|
livesElement.innerText = lives;
|
|
break;
|
|
|
|
case 'tripleShot':
|
|
hasTripleShot = true;
|
|
if (tripleShotTimeout) clearTimeout(tripleShotTimeout);
|
|
tripleShotTimeout = setTimeout(() => {
|
|
hasTripleShot = false;
|
|
}, 10000);
|
|
break;
|
|
|
|
case 'shield':
|
|
hasShield = true;
|
|
player.classList.add('player-shield');
|
|
if (shieldTimeout) clearTimeout(shieldTimeout);
|
|
shieldTimeout = setTimeout(() => {
|
|
hasShield = false;
|
|
if (player) player.classList.remove('player-shield');
|
|
}, 5000);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function checkCollision(bullet, enemy) {
|
|
if (!bullet.parentNode || !enemy.parentNode) return;
|
|
|
|
if (bullet.offsetLeft < enemy.offsetLeft + enemy.offsetWidth &&
|
|
bullet.offsetLeft + bullet.offsetWidth > enemy.offsetLeft &&
|
|
bullet.offsetTop < enemy.offsetTop + enemy.offsetHeight &&
|
|
bullet.offsetTop + bullet.offsetHeight > enemy.offsetTop) {
|
|
|
|
bullet.remove();
|
|
|
|
let health = parseInt(enemy.dataset.health);
|
|
health--;
|
|
enemy.dataset.health = health;
|
|
|
|
if (health <= 0) {
|
|
score++;
|
|
scoreElement.innerText = score;
|
|
enemyDeathEffect(enemy);
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkCollisionEnemy(player, enemy) {
|
|
if (!player || !enemy.parentNode) return;
|
|
|
|
if (player.offsetLeft < enemy.offsetLeft + enemy.offsetWidth &&
|
|
player.offsetLeft + player.offsetWidth > enemy.offsetLeft &&
|
|
player.offsetTop < enemy.offsetTop + enemy.offsetHeight &&
|
|
player.offsetTop + player.offsetHeight > enemy.offsetTop) {
|
|
|
|
enemy.remove();
|
|
|
|
// Shield protects from damage
|
|
if (hasShield) {
|
|
return;
|
|
}
|
|
|
|
lives--;
|
|
livesElement.innerText = lives;
|
|
flashPlayer();
|
|
|
|
if (lives <= 0) {
|
|
endGame();
|
|
} else {
|
|
playSound("sounds/lostlife.mp3");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Victory screen
|
|
function victoryGame() {
|
|
isGameRunning = false;
|
|
clearAllIntervals();
|
|
stopBackgroundMusic();
|
|
|
|
gameArea.style.display = 'none';
|
|
if (gameVictory) {
|
|
gameVictory.style.display = 'block';
|
|
}
|
|
stats.style.display = 'block';
|
|
if (touchControls) touchControls.style.display = 'none';
|
|
|
|
const displaySec = sec < 10 ? "0" + sec : sec;
|
|
if (victoryScoreElement) victoryScoreElement.innerText = score;
|
|
if (victoryTimeElement) victoryTimeElement.innerText = 'Time survived: ' + min + ':' + displaySec;
|
|
|
|
const isNewRecord = updateHighScore();
|
|
if (victoryHighScore) {
|
|
victoryHighScore.innerText = isNewRecord ? 'NEW HIGH SCORE!' : 'High Score: ' + getHighScore();
|
|
}
|
|
|
|
playSound("sounds/shoot.mp3"); // Victory sound
|
|
|
|
// Cleanup
|
|
document.querySelectorAll(".enemy").forEach(e => e.remove());
|
|
document.querySelectorAll(".bullet").forEach(b => b.remove());
|
|
document.querySelectorAll(".boss").forEach(b => b.remove());
|
|
document.querySelectorAll(".power-up").forEach(p => p.remove());
|
|
document.querySelectorAll(".boss-health-bar").forEach(h => h.remove());
|
|
}
|
|
|
|
// Setup End game state
|
|
function endGame() {
|
|
isGameRunning = false;
|
|
clearAllIntervals();
|
|
stopBackgroundMusic();
|
|
|
|
gameArea.style.display = 'none';
|
|
gameEnd.style.display = 'block';
|
|
stats.style.display = 'block';
|
|
if (touchControls) touchControls.style.display = 'none';
|
|
|
|
const displaySec = sec < 10 ? "0" + sec : sec;
|
|
finalScoreElement.innerText = score;
|
|
finalTimeElement.innerText = 'Time elapsed: ' + min + ':' + displaySec;
|
|
|
|
const isNewRecord = updateHighScore();
|
|
if (finalHighScore) {
|
|
finalHighScore.innerText = isNewRecord ? 'NEW HIGH SCORE!' : 'High Score: ' + getHighScore();
|
|
}
|
|
|
|
playSound("sounds/gameover.mp3");
|
|
|
|
// Cleanup
|
|
document.querySelectorAll(".enemy").forEach(e => e.remove());
|
|
document.querySelectorAll(".bullet").forEach(b => b.remove());
|
|
document.querySelectorAll(".boss").forEach(b => b.remove());
|
|
document.querySelectorAll(".power-up").forEach(p => p.remove());
|
|
document.querySelectorAll(".boss-health-bar").forEach(h => h.remove());
|
|
}
|
|
|
|
// Restart game handlers
|
|
restartButton.addEventListener('click', () => {
|
|
initGame(isHardcoreMode);
|
|
});
|
|
|
|
if (victoryRestartButton) {
|
|
victoryRestartButton.addEventListener('click', () => {
|
|
initGame(isHardcoreMode);
|
|
});
|
|
}
|
|
|
|
// Resume button in pause menu
|
|
const resumeButton = document.getElementById('resume-button');
|
|
if (resumeButton) {
|
|
resumeButton.addEventListener('click', () => {
|
|
togglePause();
|
|
});
|
|
}
|
|
|
|
// Main menu button in pause
|
|
const mainMenuButton = document.getElementById('main-menu-button');
|
|
if (mainMenuButton) {
|
|
mainMenuButton.addEventListener('click', () => {
|
|
resetGame();
|
|
introPage();
|
|
});
|
|
}
|
|
|
|
// Start the initial function
|
|
introPage();
|
|
startGame();
|