const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); canvas.width = 800; canvas.height = 500; const player = { x: 100, y: 350, width: 32, height: 48, vx: 0, vy: 0, speed: 5, jumpForce: -14, grounded: false, hp: 100, maxHp: 100, facing: 1, weapon: 0, weaponLevel: 1, score: 0, invincible: 0, level: 1, wave: 1 }; const weapons = [ { name: 'Пистолет', damage: 25, fireRate: 300, color: '#ffff00', bulletSpeed: 10, bulletSize: 6, glow: '#ffff88' }, { name: 'Автомат', damage: 15, fireRate: 100, color: '#ff8800', bulletSpeed: 12, bulletSize: 5, glow: '#ffcc00' }, { name: 'Дробовик', damage: 12, fireRate: 600, color: '#ff0000', bulletSpeed: 9, bulletSize: 4, count: 5, glow: '#ff6666' }, { name: 'Лазер', damage: 45, fireRate: 350, color: '#00ffff', bulletSpeed: 20, bulletSize: 4, glow: '#88ffff', laser: true }, { name: 'Ракетница', damage: 80, fireRate: 800, color: '#aa00ff', bulletSpeed: 7, bulletSize: 10, glow: '#dd88ff', explosive: true }, { name: 'Огнемёт', damage: 8, fireRate: 50, color: '#ff4400', bulletSpeed: 6, bulletSize: 12, glow: '#ff8800', flame: true }, { name: 'Плазма', damage: 35, fireRate: 200, color: '#00ff00', bulletSpeed: 11, bulletSize: 14, glow: '#88ff88', plasma: true } ]; let bullets = []; let enemies = []; let powerups = []; let particles = []; let explosions = []; let keys = {}; let paused = false; let gameTime = 0; let waveTimer = 0; let waveActive = false; let enemiesInWave = 0; let enemiesToSpawn = 5; let bossActive = false; let boss = null; let backgroundOffset = 0; let shake = 0; const enemyTypes = [ { name: 'soldier', width: 32, height: 48, hp: 50, speed: 1.5, color: '#8b0000', score: 100, weaponDrop: 0.25 }, { name: 'tank', width: 60, height: 35, hp: 150, speed: 0.8, color: '#2d5016', score: 300, weaponDrop: 0.4 }, { name: 'helicopter', width: 55, height: 30, hp: 80, speed: 2.2, color: '#4a4a6a', score: 200, weaponDrop: 0.35, fly: true }, { name: 'commando', width: 34, height: 50, hp: 100, speed: 2.0, color: '#6a006a', score: 250, weaponDrop: 0.5 }, { name: 'mech', width: 70, height: 55, hp: 250, speed: 0.6, color: '#004a6a', score: 500, weaponDrop: 0.7 } ]; function startWave() { if (bossActive) return; waveActive = true; enemiesToSpawn = 5 + player.wave * 2; enemiesInWave = 0; waveTimer = 0; } function spawnEnemy() { const type = enemyTypes[Math.floor(Math.random() * Math.min(enemyTypes.length, 1 + player.wave))]; const isFly = type.fly; enemies.push({ x: canvas.width + 50, y: isFly ? 100 + Math.random() * 150 : 452 - type.height, ...type, maxHp: type.hp + player.wave * 15, hp: type.hp + player.wave * 15, shootTimer: 0, animFrame: 0, fly: isFly, isBoss: false }); } function spawnBoss() { bossActive = true; boss = { x: canvas.width - 150, y: 280, width: 120, height: 100, hp: 3000 + player.wave * 500, maxHp: 3000 + player.wave * 500, speed: 0.5, color: '#8b0000', score: 5000, animFrame: 0, shootTimer: 0, moveTimer: 0, isBoss: true, phase: 1 }; enemies = []; } const powerupTypes = [ { type: 'pistol', color: '#ffff00', label: 'P', weapon: 0 }, { type: 'machinegun', color: '#ff8800', label: 'M', weapon: 1 }, { type: 'shotgun', color: '#ff0000', label: 'S', weapon: 2 }, { type: 'laser', color: '#00ffff', label: 'L', weapon: 3 }, { type: 'rocket', color: '#aa00ff', label: 'R', weapon: 4 }, { type: 'flame', color: '#ff4400', label: 'F', weapon: 5 }, { type: 'plasma', color: '#00ff00', label: 'G', weapon: 6 }, { type: 'upgrade', color: '#ff00ff', label: '↑', upgrade: true }, { type: 'health', color: '#00ff00', label: '+', heal: 30 }, { type: 'maxhealth', color: '#ff0066', label: '♥', maxHeal: true }, { type: 'shield', color: '#0088ff', label: 'S', shield: true }, { type: 'bomb', color: '#ff6600', label: 'B', bomb: true } ]; function spawnPowerup(x, y) { const t = powerupTypes[Math.floor(Math.random() * powerupTypes.length)]; powerups.push({ x, y, width: 28, height: 28, ...t, vy: -6, grounded: false, bobOffset: Math.random() * Math.PI * 2 }); } function createExplosion(x, y, color, count = 15) { for (let i = 0; i < count; i++) { const angle = (Math.PI * 2 / count) * i; const speed = 2 + Math.random() * 4; particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 30 + Math.random() * 20, color: color, size: 3 + Math.random() * 4 }); } } function createBlood(x, y, count = 10) { for (let i = 0; i < count; i++) { particles.push({ x, y, vx: (Math.random() - 0.5) * 8, vy: (Math.random() - 0.5) * 8 - 3, life: 40 + Math.random() * 20, color: '#cc0000', size: 2 + Math.random() * 3, gravity: 0.4 }); } } function createSparks(x, y, count = 8) { for (let i = 0; i < count; i++) { particles.push({ x, y, vx: (Math.random() - 0.5) * 6, vy: (Math.random() - 0.5) * 6, life: 20 + Math.random() * 10, color: '#ffff00', size: 2 + Math.random() * 2, gravity: 0.2 }); } } document.addEventListener('keydown', e => { keys[e.code] = true; if (e.code === 'KeyX') { player.weapon = (player.weapon + 1) % weapons.length; } if (e.code === 'KeyP' || e.code === 'Escape') { togglePause(); } }); document.addEventListener('keyup', e => keys[e.code] = false); function togglePause() { paused = !paused; document.getElementById('pause-btn').textContent = paused ? '▶ Продолжить' : '⏸ Пауза'; } function update() { if (paused) return; gameTime++; waveTimer++; if (shake > 0) shake--; const wpn = weapons[player.weapon]; const damageMultiplier = 1 + (player.weaponLevel - 1) * 0.15; if (keys['ArrowLeft']) { player.vx = -player.speed; player.facing = -1; } else if (keys['ArrowRight']) { player.vx = player.speed; player.facing = 1; } else player.vx = 0; if (keys['ArrowUp'] && player.grounded) { player.vy = player.jumpForce; player.grounded = false; } player.vy += 0.7; player.x += player.vx; player.y += player.vy; if (player.y > 452 - player.height) { player.y = 452 - player.height; player.vy = 0; player.grounded = true; } if (player.x < 0) player.x = 0; if (player.x > canvas.width - player.width) player.x = canvas.width - player.width; if (keys['KeyZ'] && Date.now() - lastShot > wpn.fireRate) { const count = wpn.count || 1; for (let i = 0; i < count; i++) { const angle = (count > 1) ? (i - (count-1)/2) * 0.15 : 0; const spread = wpn.flame ? (Math.random() - 0.5) * 0.4 : 0; const shootUp = keys['ArrowUp'] && !player.grounded ? -1 : 0; bullets.push({ x: player.x + player.width/2, y: player.y + 14, vx: (Math.cos(angle + spread) * wpn.bulletSpeed * player.facing) + (wpn.flame ? (Math.random() - 0.5) * 2 : 0), vy: (shootUp !== 0 ? -wpn.bulletSpeed * 0.7 : 0) + Math.sin(angle + spread) * wpn.bulletSpeed + (wpn.flame ? (Math.random() - 0.5) * 2 : 0), damage: wpn.damage * damageMultiplier, color: wpn.color, size: wpn.bulletSize, glow: wpn.glow, explosive: wpn.explosive, flame: wpn.flame, plasma: wpn.plasma, laser: wpn.laser, life: wpn.flame ? 20 : 100 }); } lastShot = Date.now(); } bullets = bullets.filter(b => { b.x += b.vx; b.y += b.vy; if (b.laser) { b.life = 10; b.x += b.vx * 3; b.y += b.vy * 3; } if (b.flame) b.life--; return b.x > -50 && b.x < canvas.width + 50 && b.y > -50 && b.y < canvas.height + 50 && (!b.flame || b.life > 0); }); if (!bossActive && waveActive) { if (enemiesToSpawn > 0 && waveTimer % 40 === 0) { spawnEnemy(); enemiesToSpawn--; enemiesInWave++; } if (enemiesToSpawn <= 0 && enemies.length === 0) { waveActive = false; player.wave++; document.getElementById('wave').textContent = player.wave; if (player.score >= 10000 * player.wave && !bossActive) { spawnBoss(); } else { setTimeout(startWave, 2000); } } } if (bossActive && boss) { boss.moveTimer++; boss.shootTimer++; boss.animFrame++; boss.y += Math.sin(boss.moveTimer * 0.03) * 1.5; if (boss.x > 500) boss.x -= boss.speed; if (player.invincible <= 0 && player.x < boss.x + boss.width && player.x + player.width > boss.x && player.y < boss.y + boss.height && player.y + player.height > boss.y) { player.hp -= 30; player.invincible = 90; shake = 10; createBlood(player.x + player.width/2, player.y + player.height/2, 20); } if (boss.hp <= 0) { player.score += boss.score; createExplosion(boss.x + boss.width/2, boss.y + boss.height/2, '#ff0000', 100); createBlood(boss.x + boss.width/2, boss.y + boss.height/2, 50); for (let i = 0; i < 5; i++) { spawnPowerup(boss.x + Math.random() * boss.width, boss.y + Math.random() * boss.height); } boss = null; bossActive = false; player.wave++; document.getElementById('wave').textContent = player.wave; setTimeout(startWave, 3000); } } enemies = enemies.filter(e => { e.x -= e.speed; e.shootTimer++; e.animFrame++; if (e.fly) { e.y += Math.sin(e.animFrame * 0.05) * 2; } if (player.invincible <= 0 && player.x < e.x + e.width && player.x + player.width > e.x && player.y < e.y + e.height && player.y + player.height > e.y) { player.hp -= 20; player.invincible = 60; shake = 5; createBlood(player.x + player.width/2, player.y + player.height/2, 15); } return e.x > -100 && player.hp > 0; }); bullets = bullets.filter(b => { let hit = false; if (bossActive && boss && b.x > boss.x && b.x < boss.x + boss.width && b.y > boss.y && b.y < boss.y + boss.height) { boss.hp -= b.damage; hit = true; createExplosion(b.x, b.y, b.color, b.explosive ? 25 : b.laser ? 15 : 10); createSparks(b.x, b.y, 5); if (boss.hp <= boss.maxHp * 0.5 && boss.phase === 1) { boss.phase = 2; boss.speed = 1.0; } } enemies.forEach((e, ei) => { if (b.x > e.x && b.x < e.x + e.width && b.y > e.y && b.y < e.y + e.height) { e.hp -= b.damage; hit = true; createExplosion(b.x, b.y, b.color, b.explosive ? 25 : b.laser ? 15 : 10); if (e.hp <= 0) { player.score += e.score; createBlood(e.x + e.width/2, e.y + e.height/2, e.name === 'tank' || e.name === 'mech' ? 20 : 12); if (Math.random() < e.weaponDrop) spawnPowerup(e.x, e.y); enemies.splice(ei, 1); } if (b.explosive) { explosions.push({ x: b.x, y: b.y, radius: 0, maxRadius: 60, alpha: 1 }); shake = 3; } } }); return !hit; }); explosions = explosions.filter(e => { e.radius += 4; e.alpha -= 0.05; return e.alpha > 0; }); powerups = powerups.filter(p => { p.vy += 0.5; p.y += p.vy; if (p.y > 452 - p.height) { p.y = 452 - p.height; p.vy = 0; p.grounded = true; } p.bobOffset += 0.1; if (player.x < p.x + p.width && player.x + player.width > p.x && player.y < p.y + p.height && player.y + player.height > p.y) { if (p.weapon !== undefined) { if (player.weapon === p.weapon) { player.weaponLevel = Math.min(5, player.weaponLevel + 1); } else { player.weapon = p.weapon; player.weaponLevel = 1; } } if (p.upgrade) { player.weaponLevel = Math.min(5, player.weaponLevel + 1); createExplosion(p.x + p.width/2, p.y + p.height/2, '#ff00ff', 30); } if (p.heal) player.hp = Math.min(player.maxHp, player.hp + p.heal); if (p.maxHeal) { player.maxHp += 50; player.hp = player.maxHp; } if (p.shield) player.invincible = 300; if (p.bomb) { enemies.forEach(e => { e.hp -= 150; createExplosion(e.x + e.width/2, e.y + e.height/2, '#ff6600', 10); }); enemies = enemies.filter(e => { if (e.hp <= 0) { player.score += e.score; createBlood(e.x + e.width/2, e.y + e.height/2, 15); return false; } return true; }); if (bossActive && boss) { boss.hp -= 500; createExplosion(boss.x + boss.width/2, boss.y + boss.height/2, '#ff6600', 30); shake = 15; } } player.score += 50; createExplosion(p.x + p.width/2, p.y + p.height/2, p.color, 20); return false; } return true; }); particles = particles.filter(p => { p.x += p.vx; p.y += p.vy; p.vy += p.gravity || 0.3; p.life--; p.vx *= 0.95; return p.life > 0; }); if (player.invincible > 0) player.invincible--; backgroundOffset += player.vx * 0.3; document.getElementById('hp').textContent = player.hp; document.getElementById('maxhp').textContent = player.maxHp; document.getElementById('wpn').textContent = weapons[player.weapon].name + (player.weaponLevel > 1 ? ' +' + (player.weaponLevel - 1) : ''); document.getElementById('scr').textContent = player.score; document.getElementById('wave').textContent = player.wave; document.getElementById('wprogress').textContent = enemiesToSpawn + '/' + enemiesInWave; if (player.hp <= 0) { alert('GAME OVER! Счёт: ' + player.score + ', Волна: ' + player.wave); resetGame(); } } let lastShot = 0; function resetGame() { player.x = 100; player.y = 350; player.hp = 100; player.maxHp = 100; player.score = 0; player.weapon = 0; player.weaponLevel = 1; player.level = 1; player.wave = 1; bullets = []; enemies = []; powerups = []; particles = []; explosions = []; boss = null; bossActive = false; waveActive = false; document.getElementById('wave').textContent = '1'; startWave(); } function draw() { if (shake > 0) { ctx.save(); ctx.translate((Math.random() - 0.5) * shake * 2, (Math.random() - 0.5) * shake * 2); } const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, '#0a0a1a'); gradient.addColorStop(0.5, '#16213e'); gradient.addColorStop(1, '#1a1a2e'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#fff'; for (let i = 0; i < 80; i++) { const x = ((i * 73 + backgroundOffset * 0.1) % canvas.width + canvas.width) % canvas.width; const y = (i * 37) % (canvas.height - 100); const size = (i % 3) + 1; ctx.globalAlpha = 0.3 + Math.sin(gameTime * 0.05 + i) * 0.2; ctx.fillRect(x, y, size, size); } ctx.globalAlpha = 1; ctx.fillStyle = '#1a1a2e'; for (let i = 0; i < 8; i++) { const x = ((i * 120 - backgroundOffset * 0.2) % 1200) - 200; ctx.beginPath(); ctx.moveTo(x, 350); ctx.lineTo(x + 60, 180); ctx.lineTo(x + 120, 350); ctx.fill(); } ctx.fillStyle = '#2a2a3e'; for (let i = 0; i < canvas.width; i += 60) { const buildingH = 80 + Math.sin(i) * 30; ctx.fillRect(i, 452 - buildingH, 50, buildingH); const windows = Math.floor(buildingH / 20); for (let w = 0; w < windows; w++) { for (let col = 0; col < 3; col++) { if (Math.sin(gameTime * 0.02 + i + w + col) > 0.3) { ctx.fillStyle = '#ffff88'; ctx.fillRect(i + 8 + col * 14, 452 - buildingH + 10 + w * 20, 8, 12); ctx.fillStyle = '#2a2a3e'; } } } } ctx.fillStyle = '#3a2e1a'; ctx.fillRect(0, 452, canvas.width, 48); ctx.fillStyle = '#4a3a2a'; ctx.fillRect(0, 452, canvas.width, 8); ctx.fillStyle = '#2d4a1e'; for (let i = 0; i < canvas.width; i += 20) { const grassH = 4 + Math.sin(gameTime * 0.05 + i) * 2; ctx.fillRect(i, 452 - grassH, 8, grassH); } ctx.fillStyle = '#5a4a3a'; ctx.fillRect(120, 456, 16, 10); ctx.fillRect(340, 460, 20, 8); ctx.fillRect(580, 454, 14, 12); ctx.fillRect(720, 458, 18, 8); if (player.invincible % 4 < 2) { const legOffset = player.vx !== 0 ? Math.sin(Date.now() * 0.02) * 6 : 0; ctx.fillStyle = '#2a3a5a'; ctx.fillRect(player.x + 6, player.y + 36, 8, 12 + legOffset); ctx.fillRect(player.x + 18, player.y + 36, 8, 12 - legOffset); ctx.fillStyle = '#1a1a2e'; ctx.fillRect(player.x + 4, player.y + 44, 10, 4); ctx.fillRect(player.x + 18, player.y + 44, 10, 4); ctx.fillStyle = '#4a90d9'; ctx.fillRect(player.x + 4, player.y + 14, 24, 26); ctx.fillStyle = '#2a2a3a'; ctx.fillRect(player.x + 4, player.y + 28, 24, 6); ctx.fillStyle = '#5a7a9a'; ctx.fillRect(player.x + 8, player.y + 16, 16, 14); ctx.fillStyle = '#ffccaa'; ctx.fillRect(player.x + 6, player.y - 6, 20, 22); ctx.fillStyle = '#3a3a4a'; ctx.fillRect(player.x + 4, player.y - 10, 24, 10); ctx.fillStyle = '#2a3a4a'; ctx.fillRect(player.x + 8, player.y - 6, 14, 6); ctx.fillStyle = '#fff'; const eyeX = player.facing === 1 ? player.x + 18 : player.x + 10; ctx.fillRect(eyeX, player.y + 2, 6, 4); ctx.fillStyle = '#000'; ctx.fillRect(player.facing === 1 ? eyeX + 2 : eyeX, player.y + 3, 3, 2); ctx.fillStyle = '#ffccaa'; ctx.fillRect(player.x + 14, player.y + 16, 10, 10); ctx.fillStyle = '#4a90d9'; const wx = player.facing === 1 ? player.x + 20 : player.x - 8; ctx.fillRect(wx, player.y + 18, 28, 10); ctx.fillStyle = '#333'; ctx.fillRect(player.facing === 1 ? wx + 20 : wx, player.y + 20, 12, 6); if (player.invincible > 60) { ctx.strokeStyle = 'rgba(0, 136, 255, 0.5)'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(player.x + player.width/2, player.y + player.height/2, 40, 0, Math.PI * 2); ctx.stroke(); } } bullets.forEach(b => { if (b.laser) { ctx.strokeStyle = b.glow; ctx.lineWidth = b.size * 2; ctx.shadowBlur = 20; ctx.shadowColor = b.color; ctx.beginPath(); ctx.moveTo(b.x - b.vx * 5, b.y - b.vy * 5); ctx.lineTo(b.x, b.y); ctx.stroke(); ctx.shadowBlur = 0; ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(b.x, b.y, b.size, 0, Math.PI * 2); ctx.fill(); } else { const glowGradient = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.size * 3); glowGradient.addColorStop(0, b.glow); glowGradient.addColorStop(1, 'transparent'); ctx.fillStyle = glowGradient; ctx.beginPath(); ctx.arc(b.x, b.y, b.size * 3, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = b.color; ctx.beginPath(); ctx.arc(b.x, b.y, b.size, 0, Math.PI * 2); ctx.fill(); if (b.plasma) { ctx.strokeStyle = b.glow; ctx.lineWidth = b.size; ctx.beginPath(); ctx.moveTo(b.x, b.y); ctx.lineTo(b.x - b.vx * 2, b.y - b.vy * 2); ctx.stroke(); } } }); explosions.forEach(e => { ctx.fillStyle = `rgba(255, 100, 0, ${e.alpha})`; ctx.beginPath(); ctx.arc(e.x, e.y, e.radius, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = `rgba(255, 200, 50, ${e.alpha * 0.7})`; ctx.beginPath(); ctx.arc(e.x, e.y, e.radius * 0.7, 0, Math.PI * 2); ctx.fill(); }); particles.forEach(p => { ctx.globalAlpha = p.life / 50; ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); }); ctx.globalAlpha = 1; if (bossActive && boss) { boss.animFrame++; ctx.fillStyle = '#3a1a1a'; ctx.fillRect(boss.x + 10, boss.y + 50, 100, 50); ctx.fillStyle = boss.color; ctx.fillRect(boss.x + 20, boss.y + 20, 80, 40); ctx.fillRect(boss.x + 40, boss.y, 40, 30); ctx.fillStyle = '#ff4400'; ctx.beginPath(); ctx.arc(boss.x + 60, boss.y + 15, 12, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#ff0000'; ctx.beginPath(); ctx.arc(boss.x + 60, boss.y + 15, 6, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#5a2a2a'; ctx.fillRect(boss.x, boss.y + 60, 20, 40); ctx.fillRect(boss.x + 100, boss.y + 60, 20, 40); const hpPct = boss.hp / boss.maxHp; ctx.fillStyle = '#333'; ctx.fillRect(boss.x, boss.y - 25, boss.width, 12); ctx.fillStyle = hpPct > 0.5 ? '#0f0' : hpPct > 0.25 ? '#ff0' : '#f00'; ctx.fillRect(boss.x + 2, boss.y - 23, (boss.width - 4) * hpPct, 8); ctx.fillStyle = '#fff'; ctx.font = 'bold 14px monospace'; ctx.textAlign = 'center'; ctx.fillText('БОСС', boss.x + boss.width/2, boss.y - 28); ctx.textAlign = 'left'; } enemies.forEach(e => { e.animFrame++; if (e.name === 'soldier') { const legOffset = Math.sin(e.animFrame * 0.3) * 6; ctx.fillStyle = '#1a1a2e'; ctx.fillRect(e.x + 6, e.y + 36, 8, 12 + legOffset); ctx.fillRect(e.x + 18, e.y + 36, 8, 12 - legOffset); ctx.fillStyle = e.color; ctx.fillRect(e.x + 4, e.y + 14, 24, 24); ctx.fillStyle = '#ffccaa'; ctx.fillRect(e.x + 6, e.y, 20, 16); ctx.fillStyle = '#3a4a3a'; ctx.fillRect(e.x + 4, e.y - 4, 24, 8); ctx.fillStyle = '#4a2a2a'; ctx.fillRect(e.x + 16, e.y + 18, 14, 8); ctx.fillStyle = '#444'; ctx.fillRect(e.x + 28, e.y + 20, 12, 6); } else if (e.name === 'tank') { ctx.fillStyle = '#1a1a1a'; const trackOffset = Math.sin(e.animFrame * 0.1) * 2; ctx.fillRect(e.x, e.y + 24 + trackOffset, e.width, 11); ctx.fillStyle = e.color; ctx.fillRect(e.x + 4, e.y + 8, e.width - 8, 20); ctx.fillStyle = '#3d6016'; ctx.fillRect(e.x + 18, e.y, 24, 14); ctx.fillStyle = '#2d4010'; ctx.fillRect(e.x + 36, e.y + 4, 28, 8); ctx.fillStyle = '#1a1a1a'; ctx.beginPath(); ctx.arc(e.x + 30, e.y + 7, 5, 0, Math.PI * 2); ctx.fill(); } else if (e.name === 'helicopter') { ctx.fillStyle = '#333'; const rotorOffset = Math.sin(e.animFrame * 0.8) * 20; ctx.fillRect(e.x - 15, e.y - 8, 85, 4); ctx.fillRect(e.x + 25 + rotorOffset, e.y - 12, 6, 8); ctx.fillStyle = e.color; ctx.beginPath(); ctx.ellipse(e.x + 25, e.y + 8, 30, 15, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#6a8aaa'; ctx.beginPath(); ctx.arc(e.x + 40, e.y + 5, 12, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = e.color; ctx.fillRect(e.x - 20, e.y + 4, 20, 8); ctx.fillRect(e.x - 25, e.y, 8, 16); } else if (e.name === 'commando') { const legOffset = Math.sin(e.animFrame * 0.4) * 8; ctx.fillStyle = '#2a1a2a'; ctx.fillRect(e.x + 8, e.y + 38, 7, 12 + legOffset); ctx.fillRect(e.x + 19, e.y + 38, 7, 12 - legOffset); ctx.fillStyle = e.color; ctx.fillRect(e.x + 6, e.y + 16, 22, 24); ctx.fillStyle = '#ffccaa'; ctx.fillRect(e.x + 8, e.y + 2, 18, 16); ctx.fillStyle = '#4a1a4a'; ctx.fillRect(e.x + 6, e.y - 2, 22, 8); ctx.fillStyle = '#6a2a6a'; ctx.fillRect(e.x + 18, e.y + 20, 16, 8); ctx.fillStyle = '#333'; ctx.fillRect(e.x + 32, e.y + 22, 14, 6); } else if (e.name === 'mech') { const legOffset = Math.sin(e.animFrame * 0.15) * 4; ctx.fillStyle = '#1a2a3a'; ctx.fillRect(e.x + 10, e.y + 40, 14, 15 + legOffset); ctx.fillRect(e.x + 46, e.y + 40, 14, 15 - legOffset); ctx.fillStyle = e.color; ctx.fillRect(e.x + 8, e.y + 20, 54, 28); ctx.fillRect(e.x + 20, e.y + 8, 30, 20); ctx.fillStyle = '#006688'; ctx.beginPath(); ctx.arc(e.x + 35, e.y + 18, 10, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#003344'; ctx.fillRect(e.x + 50, e.y + 24, 24, 10); ctx.fillStyle = '#ff4400'; ctx.beginPath(); ctx.arc(e.x + 35, e.y + 18, 4, 0, Math.PI * 2); ctx.fill(); } const hpPct = e.hp / e.maxHp; ctx.fillStyle = '#333'; ctx.fillRect(e.x, e.y - 16, e.width, 8); ctx.fillStyle = hpPct > 0.5 ? '#0f0' : hpPct > 0.25 ? '#ff0' : '#f00'; ctx.fillRect(e.x + 1, e.y - 15, (e.width - 2) * hpPct, 6); }); powerups.forEach(p => { const bobY = p.grounded ? Math.sin(p.bobOffset) * 4 : 0; const glowGradient = ctx.createRadialGradient(p.x + p.width/2, p.y + p.height/2 + bobY, 0, p.x + p.width/2, p.y + p.height/2 + bobY, 20); glowGradient.addColorStop(0, p.color); glowGradient.addColorStop(1, 'transparent'); ctx.fillStyle = glowGradient; ctx.beginPath(); ctx.arc(p.x + p.width/2, p.y + p.height/2 + bobY, 20, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = p.color; ctx.fillRect(p.x + 2, p.y + 4 + bobY, p.width - 4, p.height - 4); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.strokeRect(p.x + 2, p.y + 4 + bobY, p.width - 4, p.height - 4); ctx.fillStyle = '#fff'; ctx.font = 'bold 18px monospace'; ctx.textAlign = 'center'; ctx.fillText(p.label, p.x + p.width/2, p.y + 20 + bobY); ctx.textAlign = 'left'; }); if (shake > 0) ctx.restore(); } function loop() { update(); draw(); requestAnimationFrame(loop); } startWave(); loop();