paddlebound / index.html
stroumphs's picture
Add 1 files
b63346e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Space Defender</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000;
font-family: 'Arial', sans-serif;
}
#gameCanvas {
display: block;
background: linear-gradient(to bottom, #000000 0%, #000033 100%);
cursor: crosshair;
}
#gameUI {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 18px;
text-shadow: 0 0 5px #00ffff;
}
#startScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
z-index: 10;
}
#gameOverScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
z-index: 10;
}
h1 {
font-size: 48px;
margin-bottom: 20px;
color: #00ffff;
text-shadow: 0 0 10px #00ffff;
}
button {
padding: 15px 30px;
font-size: 20px;
background: linear-gradient(to bottom, #00aaaa, #008888);
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin-top: 20px;
transition: all 0.3s;
}
button:hover {
background: linear-gradient(to bottom, #00cccc, #00aaaa);
transform: scale(1.05);
box-shadow: 0 0 15px #00ffff;
}
.instructions {
margin-top: 30px;
text-align: center;
max-width: 500px;
line-height: 1.6;
}
.highlight {
color: #00ffff;
font-weight: bold;
}
.powerBar {
margin-top: 10px;
height: 10px;
background: #333;
border-radius: 5px;
overflow: hidden;
}
.powerFill {
height: 100%;
background: linear-gradient(to right, #00aaff, #00ffff);
width: 0%;
transition: width 0.3s;
}
/* Ability Icons */
.abilities-container {
position: absolute;
bottom: 20px;
right: 20px;
display: flex;
gap: 15px;
z-index: 5;
}
.ability {
width: 60px;
height: 60px;
border-radius: 10px;
background: rgba(0, 0, 0, 0.5);
border: 2px solid rgba(255, 255, 255, 0.2);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
cursor: pointer;
transition: all 0.2s;
}
.ability:hover {
transform: scale(1.05);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.ability-icon {
font-size: 24px;
margin-bottom: 5px;
}
.ability-key {
position: absolute;
top: 5px;
right: 5px;
font-size: 12px;
background: rgba(0, 0, 0, 0.5);
border-radius: 3px;
padding: 2px 4px;
}
.ability-cooldown {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 0%;
background: rgba(0, 0, 0, 0.7);
border-radius: 0 0 8px 8px;
transition: height 0.1s;
}
.ability.active {
border-color: #00ffff;
box-shadow: 0 0 15px #00ffff;
}
.ability.on-cooldown {
opacity: 0.6;
}
.ability-timer {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
font-weight: bold;
color: white;
text-shadow: 0 0 3px black;
display: none;
}
.ability.active .ability-timer {
display: block;
}
/* Specific ability styles */
#bulletModeAbility {
border-color: #aa00aa;
}
#bulletModeAbility.active {
border-color: #ff00ff;
box-shadow: 0 0 15px #ff00ff;
}
#rocketAbility {
border-color: #ff5500;
}
#rocketAbility.active {
border-color: #ffaa00;
box-shadow: 0 0 15px #ffaa00;
}
#wingmanAbility {
border-color: #55aa00;
}
#wingmanAbility.active {
border-color: #55ff00;
box-shadow: 0 0 15px #55ff00;
}
#miniShipAbility {
border-color: #0055ff;
}
#miniShipAbility.active {
border-color: #00aaff;
box-shadow: 0 0 15px #00aaff;
}
.wingman-timer {
position: absolute;
bottom: 100px;
right: 20px;
color: #55ff00;
font-size: 14px;
text-shadow: 0 0 5px #55ff00;
z-index: 5;
display: none;
background: rgba(0, 0, 0, 0.5);
padding: 5px 10px;
border-radius: 5px;
}
.mini-ship-timer {
position: absolute;
bottom: 130px;
right: 20px;
color: #00aaff;
font-size: 14px;
text-shadow: 0 0 5px #00aaff;
z-index: 5;
display: none;
background: rgba(0, 0, 0, 0.5);
padding: 5px 10px;
border-radius: 5px;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="gameUI">
<div>Score: <span id="score">0</span></div>
<div>Lives: <span id="lives">3</span></div>
<div>Level: <span id="level">1</span></div>
<div>Bullet Power: <span id="bulletPower">1</span>/5</div>
<div class="powerBar">
<div class="powerFill" id="powerFill"></div>
</div>
</div>
<div id="startScreen">
<h1>SPACE DEFENDER</h1>
<p class="instructions">
Defend your sector from the alien invasion!<br>
Use <span class="highlight">mouse</span> to move your ship and <span class="highlight">click</span> to shoot.<br>
Press <span class="highlight">SPACE</span> or click the icon to switch bullet modes.<br>
Press <span class="highlight">R</span> or click the icon to fire a powerful rocket (cooldown: 5s).<br>
Press <span class="highlight">W</span> or click the icon to deploy wingman drones (cooldown: 10s).<br>
Press <span class="highlight">M</span> or click the icon to summon mini ships (cooldown: 15s).<br>
Destroy aliens to increase your bullet power!<br>
<span class="highlight">Every 5 kills</span> gives you an additional bullet (max 5)!
</p>
<button id="startButton">START MISSION</button>
</div>
<div id="gameOverScreen">
<h1>MISSION FAILED</h1>
<p>Your final score: <span id="finalScore">0</span></p>
<p>Max bullet power: <span id="finalPower">1</span>/5</p>
<button id="restartButton">TRY AGAIN</button>
</div>
<!-- Ability Icons -->
<div class="abilities-container">
<div class="ability" id="bulletModeAbility" title="Switch Bullet Mode (SPACE)">
<div class="ability-icon"><i class="fas fa-arrows-alt-h"></i></div>
<div class="ability-key">SPACE</div>
<div class="ability-cooldown"></div>
<div class="ability-timer"></div>
</div>
<div class="ability" id="rocketAbility" title="Fire Rocket (R)">
<div class="ability-icon"><i class="fas fa-rocket"></i></div>
<div class="ability-key">R</div>
<div class="ability-cooldown"></div>
<div class="ability-timer"></div>
</div>
<div class="ability" id="wingmanAbility" title="Deploy Wingmen (W)">
<div class="ability-icon"><i class="fas fa-fighter-jet"></i></div>
<div class="ability-key">W</div>
<div class="ability-cooldown"></div>
<div class="ability-timer"></div>
</div>
<div class="ability" id="miniShipAbility" title="Summon Mini Ships (M)">
<div class="ability-icon"><i class="fas fa-space-shuttle"></i></div>
<div class="ability-key">M</div>
<div class="ability-cooldown"></div>
<div class="ability-timer"></div>
</div>
</div>
<div class="wingman-timer" id="wingmanTimer">Wingmen: 10s</div>
<div class="mini-ship-timer" id="miniShipTimer">Mini Ships: 10s</div>
<script>
// Game canvas setup
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Game state
let gameRunning = false;
let score = 0;
let lives = 3;
let level = 1;
let aliens = [];
let bullets = [];
let rockets = [];
let wingmen = [];
let miniShips = [];
let particles = [];
let lastAlienSpawn = 0;
let alienSpawnInterval = 2000;
let alienSpeed = 1;
let alienHealth = 1;
let kills = 0;
let bulletPower = 1;
let maxBulletPower = 1;
let bulletMode = 'spread'; // 'spread' or 'parallel'
const MAX_BULLETS = 5;
// Ability cooldowns
const abilities = {
bulletMode: {
cooldown: 0,
maxCooldown: 0,
ready: true,
element: document.getElementById('bulletModeAbility')
},
rocket: {
cooldown: 0,
maxCooldown: 5000, // 5 seconds
ready: true,
element: document.getElementById('rocketAbility')
},
wingman: {
cooldown: 0,
maxCooldown: 10000, // 10 seconds
ready: true,
element: document.getElementById('wingmanAbility'),
active: false,
duration: 0,
maxDuration: 10000 // 10 seconds
},
miniShip: {
cooldown: 0,
maxCooldown: 15000, // 15 seconds
ready: true,
element: document.getElementById('miniShipAbility'),
active: false,
duration: 0,
maxDuration: 10000 // 10 seconds
}
};
// Player spaceship
const player = {
x: canvas.width / 2,
y: canvas.height - 50,
width: 40,
height: 60,
speed: 8,
color: '#00ffff',
lastShot: 0,
shootDelay: 300
};
// UI elements
const scoreElement = document.getElementById('score');
const livesElement = document.getElementById('lives');
const levelElement = document.getElementById('level');
const bulletPowerElement = document.getElementById('bulletPower');
const powerFillElement = document.getElementById('powerFill');
const finalScoreElement = document.getElementById('finalScore');
const finalPowerElement = document.getElementById('finalPower');
const startScreen = document.getElementById('startScreen');
const gameOverScreen = document.getElementById('gameOverScreen');
const startButton = document.getElementById('startButton');
const restartButton = document.getElementById('restartButton');
const wingmanTimerElement = document.getElementById('wingmanTimer');
const miniShipTimerElement = document.getElementById('miniShipTimer');
// Event listeners
startButton.addEventListener('click', startGame);
restartButton.addEventListener('click', startGame);
canvas.addEventListener('mousemove', movePlayer);
canvas.addEventListener('click', shoot);
// Ability click handlers
document.getElementById('bulletModeAbility').addEventListener('click', toggleBulletMode);
document.getElementById('rocketAbility').addEventListener('click', fireRocket);
document.getElementById('wingmanAbility').addEventListener('click', deployWingmen);
document.getElementById('miniShipAbility').addEventListener('click', summonMiniShips);
// Keyboard controls
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
e.preventDefault();
toggleBulletMode();
} else if (e.code === 'KeyR' && abilities.rocket.ready) {
e.preventDefault();
fireRocket();
} else if (e.code === 'KeyW' && abilities.wingman.ready) {
e.preventDefault();
deployWingmen();
} else if (e.code === 'KeyM' && abilities.miniShip.ready) {
e.preventDefault();
summonMiniShips();
}
});
// Resize handler
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
player.y = canvas.height - 50;
});
// Start game function
function startGame() {
gameRunning = true;
score = 0;
lives = 3;
level = 1;
aliens = [];
bullets = [];
rockets = [];
wingmen = [];
miniShips = [];
particles = [];
lastAlienSpawn = 0;
alienSpawnInterval = 2000;
alienSpeed = 1;
alienHealth = 1;
kills = 0;
bulletPower = 1;
maxBulletPower = 1;
bulletMode = 'spread';
// Reset abilities
for (const ability in abilities) {
abilities[ability].cooldown = 0;
abilities[ability].ready = true;
abilities[ability].active = false;
abilities[ability].duration = 0;
abilities[ability].element.classList.remove('active', 'on-cooldown');
abilities[ability].element.querySelector('.ability-cooldown').style.height = '0%';
abilities[ability].element.querySelector('.ability-timer').style.display = 'none';
}
scoreElement.textContent = score;
livesElement.textContent = lives;
levelElement.textContent = level;
bulletPowerElement.textContent = `${bulletPower}/${MAX_BULLETS}`;
powerFillElement.style.width = '0%';
wingmanTimerElement.style.display = 'none';
miniShipTimerElement.style.display = 'none';
startScreen.style.display = 'none';
gameOverScreen.style.display = 'none';
// Start game loop
requestAnimationFrame(gameLoop);
}
// Toggle bullet mode
function toggleBulletMode() {
if (!gameRunning) return;
bulletMode = bulletMode === 'spread' ? 'parallel' : 'spread';
// Visual feedback
abilities.bulletMode.element.classList.add('active');
setTimeout(() => {
abilities.bulletMode.element.classList.remove('active');
}, 300);
if (bulletMode === 'spread') {
abilities.bulletMode.element.style.borderColor = '#aa00aa';
} else {
abilities.bulletMode.element.style.borderColor = '#00aa00';
}
// Visual effect
for (let i = 0; i < 20; i++) {
particles.push({
x: player.x,
y: player.y,
radius: Math.random() * 8 + 2,
color: bulletMode === 'spread' ? '#ff00ff' : '#00ff00',
speedX: Math.random() * 6 - 3,
speedY: Math.random() * 6 - 3,
life: Math.random() * 30 + 20,
decay: Math.random() * 0.1 + 0.9
});
}
}
// Fire rocket
function fireRocket() {
if (!gameRunning || !abilities.rocket.ready) return;
// Create rocket
rockets.push({
x: player.x,
y: player.y - player.height / 2,
width: 15,
height: 30,
speedY: -15,
color: '#ff5500',
explosionRadius: 100,
damage: 5
});
// Rocket exhaust effect
for (let i = 0; i < 20; i++) {
particles.push({
x: player.x,
y: player.y - player.height / 2 + 15,
radius: Math.random() * 6 + 3,
color: '#ffaa00',
speedX: (Math.random() - 0.5) * 3,
speedY: Math.random() * 5,
life: 30,
decay: 0.9
});
}
// Start cooldown
abilities.rocket.ready = false;
abilities.rocket.cooldown = abilities.rocket.maxCooldown;
abilities.rocket.element.classList.add('on-cooldown');
// Visual feedback
abilities.rocket.element.classList.add('active');
setTimeout(() => {
abilities.rocket.element.classList.remove('active');
}, 300);
}
// Deploy wingmen drones
function deployWingmen() {
if (!gameRunning || !abilities.wingman.ready) return;
// Create two wingmen drones
wingmen.push({
x: player.x - 60,
y: player.y,
width: 25,
height: 40,
offsetX: -60,
offsetY: 0,
targetX: player.x - 60,
targetY: player.y,
color: '#55ff00',
lastShot: 0,
shootDelay: 800
});
wingmen.push({
x: player.x + 60,
y: player.y,
width: 25,
height: 40,
offsetX: 60,
offsetY: 0,
targetX: player.x + 60,
targetY: player.y,
color: '#55ff00',
lastShot: 0,
shootDelay: 800
});
// Wingman deployment effect
for (let i = 0; i < 30; i++) {
particles.push({
x: player.x - 60,
y: player.y,
radius: Math.random() * 5 + 2,
color: '#55ff00',
speedX: (Math.random() - 0.5) * 4,
speedY: (Math.random() - 0.5) * 4,
life: Math.random() * 30 + 20,
decay: 0.9
});
particles.push({
x: player.x + 60,
y: player.y,
radius: Math.random() * 5 + 2,
color: '#55ff00',
speedX: (Math.random() - 0.5) * 4,
speedY: (Math.random() - 0.5) * 4,
life: Math.random() * 30 + 20,
decay: 0.9
});
}
// Start wingman duration
abilities.wingman.active = true;
abilities.wingman.duration = abilities.wingman.maxDuration;
wingmanTimerElement.style.display = 'block';
wingmanTimerElement.textContent = `Wingmen: ${Math.ceil(abilities.wingman.duration/1000)}s`;
// Start cooldown
abilities.wingman.ready = false;
abilities.wingman.cooldown = abilities.wingman.maxCooldown;
abilities.wingman.element.classList.add('on-cooldown');
// Visual feedback
abilities.wingman.element.classList.add('active');
setTimeout(() => {
abilities.wingman.element.classList.remove('active');
}, 300);
}
// Summon mini ships
function summonMiniShips() {
if (!gameRunning || !abilities.miniShip.ready) return;
// Create three mini ships
for (let i = 0; i < 3; i++) {
miniShips.push({
x: player.x + (i - 1) * 40,
y: player.y,
width: 20,
height: 30,
color: '#00aaff',
lastShot: 0,
shootDelay: 500,
targetAlien: null
});
}
// Mini ship summon effect
for (let i = 0; i < 40; i++) {
particles.push({
x: player.x,
y: player.y,
radius: Math.random() * 6 + 3,
color: '#00aaff',
speedX: (Math.random() - 0.5) * 6,
speedY: (Math.random() - 0.5) * 6,
life: Math.random() * 30 + 20,
decay: 0.9
});
}
// Start mini ship duration
abilities.miniShip.active = true;
abilities.miniShip.duration = abilities.miniShip.maxDuration;
miniShipTimerElement.style.display = 'block';
miniShipTimerElement.textContent = `Mini Ships: ${Math.ceil(abilities.miniShip.duration/1000)}s`;
// Start cooldown
abilities.miniShip.ready = false;
abilities.miniShip.cooldown = abilities.miniShip.maxCooldown;
abilities.miniShip.element.classList.add('on-cooldown');
// Visual feedback
abilities.miniShip.element.classList.add('active');
setTimeout(() => {
abilities.miniShip.element.classList.remove('active');
}, 300);
}
// Update wingmen
function updateWingmen(deltaTime) {
if (!abilities.wingman.active) return;
// Update wingman duration
abilities.wingman.duration -= deltaTime;
wingmanTimerElement.textContent = `Wingmen: ${Math.ceil(abilities.wingman.duration/1000)}s`;
if (abilities.wingman.duration <= 0) {
abilities.wingman.active = false;
wingmen = [];
wingmanTimerElement.style.display = 'none';
}
// Update wingmen positions to follow player with slight delay
for (let i = 0; i < wingmen.length; i++) {
const wingman = wingmen[i];
// Update target position relative to player
wingman.targetX = player.x + wingman.offsetX;
wingman.targetY = player.y + wingman.offsetY;
// Move towards target with easing
wingman.x += (wingman.targetX - wingman.x) * 0.1;
wingman.y += (wingman.targetY - wingman.y) * 0.1;
// Draw wingman
ctx.fillStyle = wingman.color;
ctx.beginPath();
ctx.moveTo(wingman.x, wingman.y - wingman.height / 2);
ctx.lineTo(wingman.x - wingman.width / 2, wingman.y + wingman.height / 2);
ctx.lineTo(wingman.x + wingman.width / 2, wingman.y + wingman.height / 2);
ctx.closePath();
ctx.fill();
// Cockpit glow
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
ctx.beginPath();
ctx.arc(wingman.x, wingman.y - wingman.height / 4, wingman.width / 4, 0, Math.PI * 2);
ctx.fill();
// Engine glow
ctx.fillStyle = 'rgba(150, 255, 100, 0.7)';
ctx.beginPath();
ctx.moveTo(wingman.x - wingman.width / 3, wingman.y + wingman.height / 2);
ctx.lineTo(wingman.x, wingman.y + wingman.height / 2 + 10);
ctx.lineTo(wingman.x + wingman.width / 3, wingman.y + wingman.height / 2);
ctx.closePath();
ctx.fill();
// Wingmen shoot at aliens
const now = Date.now();
if (now - wingman.lastShot >= wingman.shootDelay && aliens.length > 0) {
wingman.lastShot = now;
// Find closest alien
let closestAlien = null;
let minDistance = Infinity;
for (const alien of aliens) {
const distance = Math.sqrt(
Math.pow(wingman.x - (alien.x + alien.width / 2), 2) +
Math.pow(wingman.y - (alien.y + alien.height / 2), 2)
);
if (distance < minDistance) {
minDistance = distance;
closestAlien = alien;
}
}
if (closestAlien) {
// Calculate direction to alien
const angle = Math.atan2(
closestAlien.y + closestAlien.height / 2 - wingman.y,
closestAlien.x + closestAlien.width / 2 - wingman.x
);
// Create bullet
bullets.push({
x: wingman.x,
y: wingman.y - wingman.height / 2,
width: 3,
height: 15,
speedX: Math.cos(angle) * 7,
speedY: Math.sin(angle) * 7,
color: '#55ff00'
});
// Muzzle flash effect
for (let j = 0; j < 5; j++) {
particles.push({
x: wingman.x,
y: wingman.y - wingman.height / 2,
radius: Math.random() * 3 + 1,
color: '#55ff00',
speedX: Math.cos(angle) * (Math.random() * 3 + 2),
speedY: Math.sin(angle) * (Math.random() * 3 + 2),
life: 15,
decay: 0.9
});
}
}
}
}
}
// Update mini ships
function updateMiniShips(deltaTime) {
if (!abilities.miniShip.active) return;
// Update mini ship duration
abilities.miniShip.duration -= deltaTime;
miniShipTimerElement.textContent = `Mini Ships: ${Math.ceil(abilities.miniShip.duration/1000)}s`;
if (abilities.miniShip.duration <= 0) {
abilities.miniShip.active = false;
miniShips = [];
miniShipTimerElement.style.display = 'none';
}
// Update mini ships
for (let i = miniShips.length - 1; i >= 0; i--) {
const miniShip = miniShips[i];
// Find the furthest alien if not already targeting one
if (!miniShip.targetAlien || miniShip.targetAlien.health <= 0) {
let furthestAlien = null;
let maxDistance = 0;
for (const alien of aliens) {
const distance = Math.sqrt(
Math.pow(miniShip.x - (alien.x + alien.width / 2), 2) +
Math.pow(miniShip.y - (alien.y + alien.height / 2), 2)
);
if (distance > maxDistance) {
maxDistance = distance;
furthestAlien = alien;
}
}
miniShip.targetAlien = furthestAlien;
}
// Move towards target alien if exists
if (miniShip.targetAlien) {
const targetX = miniShip.targetAlien.x + miniShip.targetAlien.width / 2;
const targetY = miniShip.targetAlien.y + miniShip.targetAlien.height / 2;
// Move with easing
miniShip.x += (targetX - miniShip.x) * 0.05;
miniShip.y += (targetY - miniShip.y) * 0.05;
// Shoot at target
const now = Date.now();
if (now - miniShip.lastShot >= miniShip.shootDelay) {
miniShip.lastShot = now;
// Calculate direction to target
const angle = Math.atan2(
targetY - miniShip.y,
targetX - miniShip.x
);
// Create bullet
bullets.push({
x: miniShip.x,
y: miniShip.y,
width: 3,
height: 10,
speedX: Math.cos(angle) * 8,
speedY: Math.sin(angle) * 8,
color: '#00aaff'
});
// Muzzle flash effect
for (let j = 0; j < 3; j++) {
particles.push({
x: miniShip.x,
y: miniShip.y,
radius: Math.random() * 2 + 1,
color: '#00aaff',
speedX: Math.cos(angle) * (Math.random() * 2 + 1),
speedY: Math.sin(angle) * (Math.random() * 2 + 1),
life: 10,
decay: 0.9
});
}
}
} else {
// No target, return to player
miniShip.x += (player.x - miniShip.x) * 0.05;
miniShip.y += (player.y - 50 - miniShip.y) * 0.05;
}
// Draw mini ship
ctx.fillStyle = miniShip.color;
ctx.beginPath();
ctx.moveTo(miniShip.x, miniShip.y - miniShip.height / 2);
ctx.lineTo(miniShip.x - miniShip.width / 2, miniShip.y + miniShip.height / 2);
ctx.lineTo(miniShip.x + miniShip.width / 2, miniShip.y + miniShip.height / 2);
ctx.closePath();
ctx.fill();
// Cockpit glow
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
ctx.beginPath();
ctx.arc(miniShip.x, miniShip.y - miniShip.height / 4, miniShip.width / 4, 0, Math.PI * 2);
ctx.fill();
// Engine glow
ctx.fillStyle = 'rgba(100, 200, 255, 0.7)';
ctx.beginPath();
ctx.moveTo(miniShip.x - miniShip.width / 3, miniShip.y + miniShip.height / 2);
ctx.lineTo(miniShip.x, miniShip.y + miniShip.height / 2 + 8);
ctx.lineTo(miniShip.x + miniShip.width / 3, miniShip.y + miniShip.height / 2);
ctx.closePath();
ctx.fill();
}
}
// Update cooldowns
function updateCooldowns(deltaTime) {
for (const ability in abilities) {
if (!abilities[ability].ready) {
abilities[ability].cooldown -= deltaTime;
// Update UI
const cooldownPercent = (1 - abilities[ability].cooldown / abilities[ability].maxCooldown) * 100;
abilities[ability].element.querySelector('.ability-cooldown').style.height = `${100 - cooldownPercent}%`;
// Update timer for active abilities
if (abilities[ability].active) {
const timer = abilities[ability].element.querySelector('.ability-timer');
timer.textContent = Math.ceil(abilities[ability].duration / 1000);
}
if (abilities[ability].cooldown <= 0) {
abilities[ability].ready = true;
abilities[ability].cooldown = 0;
abilities[ability].element.classList.remove('on-cooldown');
}
}
}
}
// Game loop
let lastTime = 0;
function gameLoop(timestamp) {
if (!gameRunning) return;
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
// Clear canvas
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw stars
drawStars();
// Spawn aliens
if (timestamp - lastAlienSpawn > alienSpawnInterval) {
spawnAlien();
lastAlienSpawn = timestamp;
}
// Update and draw player
drawPlayer();
// Update and draw wingmen
updateWingmen(deltaTime);
// Update and draw mini ships
updateMiniShips(deltaTime);
// Update and draw bullets
updateBullets();
// Update and draw rockets
updateRockets();
// Update and draw aliens
updateAliens();
// Update and draw particles
updateParticles();
// Update cooldowns
updateCooldowns(deltaTime);
// Check for level up
if (score >= level * 1000) {
levelUp();
}
// Continue game loop
requestAnimationFrame(gameLoop);
}
// Draw stars background
function drawStars() {
ctx.fillStyle = 'white';
for (let i = 0; i < 100; i++) {
const x = Math.sin(i * 100) * canvas.width / 2 + canvas.width / 2;
const y = Math.cos(i * 100) * canvas.height / 2 + canvas.height / 2;
const size = Math.random() * 2;
ctx.globalAlpha = Math.random();
ctx.fillRect(x, y, size, size);
}
ctx.globalAlpha = 1;
}
// Move player with mouse
function movePlayer(e) {
player.x = e.clientX;
if (player.x < player.width / 2) player.x = player.width / 2;
if (player.x > canvas.width - player.width / 2) player.x = canvas.width - player.width / 2;
}
// Draw player spaceship
function drawPlayer() {
// Ship body
ctx.fillStyle = player.color;
ctx.beginPath();
ctx.moveTo(player.x, player.y - player.height / 2);
ctx.lineTo(player.x - player.width / 2, player.y + player.height / 2);
ctx.lineTo(player.x + player.width / 2, player.y + player.height / 2);
ctx.closePath();
ctx.fill();
// Cockpit glow
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.beginPath();
ctx.arc(player.x, player.y - player.height / 4, player.width / 4, 0, Math.PI * 2);
ctx.fill();
// Engine glow
ctx.fillStyle = 'rgba(255, 100, 0, 0.7)';
ctx.beginPath();
ctx.moveTo(player.x - player.width / 3, player.y + player.height / 2);
ctx.lineTo(player.x, player.y + player.height / 2 + 15);
ctx.lineTo(player.x + player.width / 3, player.y + player.height / 2);
ctx.closePath();
ctx.fill();
}
// Player shoot function
function shoot(e) {
if (!gameRunning) return;
const now = Date.now();
if (now - player.lastShot < player.shootDelay) return;
player.lastShot = now;
// Create bullets based on current bullet power and mode
if (bulletMode === 'spread') {
// Spread mode - bullets fan out in an arc
for (let i = 0; i < bulletPower; i++) {
// Calculate angle offset for each bullet
const angleOffset = (i - (bulletPower - 1) / 2) * 0.3; // Spread angle
bullets.push({
x: player.x,
y: player.y - player.height / 2,
width: 3,
height: 15,
speedX: Math.sin(angleOffset) * 3, // Horizontal speed for spread
speedY: -10, // Base upward speed
color: '#00ffff'
});
}
} else {
// Parallel mode - bullets move straight up in parallel lines
for (let i = 0; i < bulletPower; i++) {
// Calculate horizontal offset for each bullet
const xOffset = (i - (bulletPower - 1) / 2) * 15;
bullets.push({
x: player.x + xOffset,
y: player.y - player.height / 2,
width: 3,
height: 15,
speedX: 0, // No horizontal movement
speedY: -10, // Upward speed
color: '#00ff00' // Different color for parallel mode
});
}
}
// Muzzle flash effect
for (let j = 0; j < 10; j++) {
particles.push({
x: player.x,
y: player.y - player.height / 2,
radius: Math.random() * 5 + 2,
color: bulletMode === 'spread' ? '#00ffff' : '#00ff00',
speedX: (Math.random() - 0.5) * 4,
speedY: Math.random() * -5,
life: 20,
decay: Math.random() * 0.2 + 0.8
});
}
}
// Update bullets
function updateBullets() {
for (let i = bullets.length - 1; i >= 0; i--) {
const bullet = bullets[i];
// Update position based on speed
bullet.x += bullet.speedX || 0;
bullet.y += bullet.speedY;
// Draw bullet
ctx.fillStyle = bullet.color;
ctx.fillRect(bullet.x - bullet.width / 2, bullet.y, bullet.width, bullet.height);
// Add bullet trail
particles.push({
x: bullet.x,
y: bullet.y + bullet.height,
radius: Math.random() * 2 + 1,
color: bullet.color,
speedX: 0,
speedY: 0,
life: 10,
decay: 0.9
});
// Remove bullets that are off screen
if (bullet.y + bullet.height < 0 ||
bullet.x < 0 ||
bullet.x > canvas.width) {
bullets.splice(i, 1);
}
}
}
// Update rockets
function updateRockets() {
for (let i = rockets.length - 1; i >= 0; i--) {
const rocket = rockets[i];
// Update position
rocket.y += rocket.speedY;
// Draw rocket
ctx.fillStyle = rocket.color;
ctx.beginPath();
ctx.moveTo(rocket.x, rocket.y);
ctx.lineTo(rocket.x - rocket.width / 2, rocket.y + rocket.height);
ctx.lineTo(rocket.x + rocket.width / 2, rocket.y + rocket.height);
ctx.closePath();
ctx.fill();
// Rocket flame effect
if (Math.random() > 0.3) {
particles.push({
x: rocket.x + (Math.random() - 0.5) * rocket.width / 2,
y: rocket.y + rocket.height,
radius: Math.random() * 5 + 2,
color: '#ffaa00',
speedX: (Math.random() - 0.5) * 2,
speedY: Math.random() * 5,
life: 15,
decay: 0.9
});
}
// Check for collisions with aliens
for (let j = aliens.length - 1; j >= 0; j--) {
const alien = aliens[j];
const distance = Math.sqrt(
Math.pow(rocket.x - (alien.x + alien.width / 2), 2) +
Math.pow(rocket.y - (alien.y + alien.height / 2), 2)
);
if (distance < rocket.explosionRadius) {
// Rocket hit alien
alien.health -= rocket.damage;
if (alien.health <= 0) {
// Alien destroyed
createExplosion(
alien.x + alien.width / 2,
alien.y + alien.height / 2,
alien.color,
25
);
score += alien.points;
kills++;
scoreElement.textContent = score;
aliens.splice(j, 1);
// Check for bullet power increase
updateBulletPower();
}
}
}
// Create explosion when rocket reaches top or hits alien
if (rocket.y + rocket.height < 0) {
// Rocket reached top of screen - explode
createExplosion(
rocket.x,
rocket.y,
rocket.color,
30
);
// Damage all aliens in explosion radius
for (let j = aliens.length - 1; j >= 0; j--) {
const alien = aliens[j];
const distance = Math.sqrt(
Math.pow(rocket.x - (alien.x + alien.width / 2), 2) +
Math.pow(rocket.y - (alien.y + alien.height / 2), 2)
);
if (distance < rocket.explosionRadius) {
alien.health -= rocket.damage;
if (alien.health <= 0) {
score += alien.points;
kills++;
scoreElement.textContent = score;
aliens.splice(j, 1);
updateBulletPower();
}
}
}
rockets.splice(i, 1);
}
}
}
// Spawn alien
function spawnAlien() {
const size = Math.random() * 30 + 20;
const health = Math.floor(Math.random() * level) + alienHealth;
aliens.push({
x: Math.random() * (canvas.width - size),
y: -size,
width: size,
height: size,
speed: Math.random() * alienSpeed + alienSpeed,
color: `hsl(${Math.random() * 60 + 300}, 70%, 50%)`,
health: health,
maxHealth: health,
points: health * 100
});
}
// Update aliens
function updateAliens() {
for (let i = aliens.length - 1; i >= 0; i--) {
const alien = aliens[i];
alien.y += alien.speed;
// Draw alien
ctx.fillStyle = alien.color;
ctx.beginPath();
ctx.arc(alien.x + alien.width / 2, alien.y + alien.height / 2, alien.width / 2, 0, Math.PI * 2);
ctx.fill();
// Draw alien details
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.beginPath();
ctx.arc(alien.x + alien.width / 2, alien.y + alien.height / 3, alien.width / 4, 0, Math.PI * 2);
ctx.fill();
// Draw health bar
const healthPercent = alien.health / alien.maxHealth;
ctx.fillStyle = 'red';
ctx.fillRect(alien.x, alien.y - 10, alien.width, 5);
ctx.fillStyle = 'lime';
ctx.fillRect(alien.x, alien.y - 10, alien.width * healthPercent, 5);
// Check for bullet collisions
for (let j = bullets.length - 1; j >= 0; j--) {
const bullet = bullets[j];
if (
bullet.x + bullet.width / 2 > alien.x &&
bullet.x - bullet.width / 2 < alien.x + alien.width &&
bullet.y < alien.y + alien.height &&
bullet.y + bullet.height > alien.y
) {
// Hit effect
createExplosion(
bullet.x,
bullet.y,
alien.color,
5
);
alien.health--;
bullets.splice(j, 1);
if (alien.health <= 0) {
// Alien destroyed
createExplosion(
alien.x + alien.width / 2,
alien.y + alien.height / 2,
alien.color,
15
);
score += alien.points;
kills++;
scoreElement.textContent = score;
aliens.splice(i, 1);
// Check for bullet power increase
updateBulletPower();
break;
}
}
}
// Check if alien reached bottom
if (alien.y > canvas.height) {
lives--;
livesElement.textContent = lives;
aliens.splice(i, 1);
if (lives <= 0) {
gameOver();
}
}
}
}
// Update bullet power based on kills
function updateBulletPower() {
// Every 5 kills increases bullet power (max 5)
const newPower = Math.min(Math.floor(kills / 5) + 1, MAX_BULLETS);
if (newPower > bulletPower) {
bulletPower = newPower;
maxBulletPower = Math.max(maxBulletPower, bulletPower);
bulletPowerElement.textContent = `${bulletPower}/${MAX_BULLETS}`;
// Power up effect
for (let i = 0; i < 30; i++) {
particles.push({
x: player.x,
y: player.y,
radius: Math.random() * 10 + 5,
color: bulletMode === 'spread' ? '#00ffff' : '#00ff00',
speedX: Math.random() * 6 - 3,
speedY: Math.random() * 6 - 3,
life: Math.random() * 30 + 20,
decay: Math.random() * 0.1 + 0.9
});
}
}
// Update power bar
const powerProgress = (kills % 5) / 5 * 100;
powerFillElement.style.width = powerProgress + '%';
}
// Create explosion effect
function createExplosion(x, y, color, particleCount) {
for (let i = 0; i < particleCount; i++) {
particles.push({
x: x,
y: y,
radius: Math.random() * 5 + 2,
color: color,
speedX: Math.random() * 6 - 3,
speedY: Math.random() * 6 - 3,
life: Math.random() * 30 + 20,
decay: Math.random() * 0.1 + 0.9
});
}
// Add shockwave effect for larger explosions
if (particleCount > 15) {
for (let i = 0; i < 10; i++) {
particles.push({
x: x,
y: y,
radius: Math.random() * 15 + 5,
color: '#ffffff',
speedX: (Math.random() - 0.5) * 10,
speedY: (Math.random() - 0.5) * 10,
life: 40,
decay: 0.85
});
}
}
}
// Update particles
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.speedX || 0;
p.y += p.speedY || 0;
p.life *= p.decay;
ctx.globalAlpha = p.life / 100;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
ctx.fill();
if (p.life < 1) {
particles.splice(i, 1);
}
}
ctx.globalAlpha = 1;
}
// Level up
function levelUp() {
level++;
levelElement.textContent = level;
// Increase difficulty
alienSpawnInterval *= 0.9;
alienSpeed += 0.2;
alienHealth += 0.5;
// Level up effect
ctx.fillStyle = 'rgba(0, 255, 255, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Level up message
ctx.fillStyle = 'white';
ctx.font = '48px Arial';
ctx.textAlign = 'center';
ctx.fillText(`LEVEL ${level}`, canvas.width / 2, canvas.height / 2);
ctx.textAlign = 'left';
}
// Game over
function gameOver() {
gameRunning = false;
finalScoreElement.textContent = score;
finalPowerElement.textContent = `${maxBulletPower}/${MAX_BULLETS}`;
gameOverScreen.style.display = 'flex';
}
</script>
</body>
</html>