Chicken Invaders
Welcome to Chicken Invaders, a space arcade shoot-em-up adventure. Protect the galaxy from waves of descending, egg-dropping cosmic poultry. Collect roast drumsticks and weapon power-up gifts to upgrade your arsenal and face the ultimate Giant Boss Chicken.
How to Play
- Move: Use your Mouse / Touch to drag the spaceship, or use Arrow Keys / WASD on your keyboard.
- Shoot: Hold Spacebar or Click/Hold your mouse button to shoot lasers.
- Collect: Grab Roast Chickens for score bonuses and Gifts to level up your weapon!
- Avoid: Avoid crashing into chickens and dropping Eggs.
<div style="font-family: 'Courier New', Courier, monospace; color: var(--color-text); background-color: #0b0c10; border-radius: 8px; padding: 12px; max-width: 600px; margin: 0 auto; box-shadow: 0 4px 15px rgba(0,0,0,0.5); user-select: none;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; border-bottom: 2px solid var(--color-border); padding-bottom: 8px;">
<div>WAVE: <span id="wave-val" style="color: var(--color-accent); font-weight: bold;">1</span></div>
<div>SCORE: <span id="score-val" style="color: var(--color-success); font-weight: bold;">0</span></div>
<div>HI-SCORE: <span id="hiscore-val" style="color: #f1c40f; font-weight: bold;">0</span></div>
<div style="display: flex; gap: 4px;" id="lives-container"></div>
</div>
<div style="position: relative; width: 100%; aspect-ratio: 1; background: #000; border-radius: 4px; overflow: hidden;">
<canvas id="gameCanvas" width="600" height="600" style="width: 100%; height: 100%; display: block; cursor: crosshair;"></canvas>
<!-- Overlay Screens -->
<div id="start-screen" style="position: absolute; inset: 0; background: rgba(0,0,0,0.85); display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 20px; z-index: 10;">
<h1 style="color: #e74c3c; margin: 0 0 10px 0; font-size: 28px; text-shadow: 0 0 8px rgba(231,76,60,0.6);">CHICKEN INVADERS</h1>
<p style="color: #bdc3c7; font-size: 14px; max-width: 80%; margin: 0 0 20px 0; line-height: 1.4;">The chickens are invading our galaxy. Defeat the flock, dodge their eggs, and upgrade your firepower!</p>
<button id="start-btn" style="background: #e74c3c; color: #fff; border: none; padding: 12px 28px; font-size: 18px; font-family: inherit; font-weight: bold; cursor: pointer; border-radius: 4px; box-shadow: 0 4px 10px rgba(231,76,60,0.4); transition: transform 0.1s;">START MISSION</button>
</div>
<div id="gameover-screen" style="position: absolute; inset: 0; background: rgba(0,0,0,0.95); display: none; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 20px; z-index: 10;">
<h1 style="color: #e74c3c; margin: 0 0 10px 0; font-size: 32px;">MISSION FAILED</h1>
<p style="color: #bdc3c7; font-size: 16px; margin: 0 0 10px 0;">Final Score: <span id="final-score" style="color: var(--color-success); font-weight: bold;">0</span></p>
<p id="new-hiscore-msg" style="color: #f1c40f; font-size: 14px; margin: 0 0 24px 0; display: none;">New High Score!</p>
<button id="restart-btn" style="background: #3498db; color: #fff; border: none; padding: 12px 28px; font-size: 18px; font-family: inherit; font-weight: bold; cursor: pointer; border-radius: 4px; box-shadow: 0 4px 10px rgba(52,152,219,0.4);">RETRY</button>
</div>
<div id="victory-screen" style="position: absolute; inset: 0; background: rgba(0,0,0,0.95); display: none; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 20px; z-index: 10;">
<h1 style="color: #f1c40f; margin: 0 0 10px 0; font-size: 32px; text-shadow: 0 0 10px #f1c40f;">VICTORY!</h1>
<p style="color: #bdc3c7; font-size: 16px; margin: 0 0 10px 0;">You saved the solar system from the galactic flock!</p>
<p style="color: #bdc3c7; font-size: 14px; margin: 0 0 24px 0;">Final Score: <span id="vic-score" style="color: var(--color-success); font-weight: bold;">0</span></p>
<button id="vic-btn" style="background: #2ecc71; color: #fff; border: none; padding: 12px 28px; font-size: 18px; font-family: inherit; font-weight: bold; cursor: pointer; border-radius: 4px; box-shadow: 0 4px 10px rgba(46,204,113,0.4);">PLAY AGAIN</button>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px; font-size: 11px; color: #7f8c8d;">
<div>Keys: [Arrow/WASD] to Move, [Space] to Fire</div>
<div>Click / Touch and drag to move and auto-fire</div>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const startScreen = document.getElementById('start-screen');
const gameoverScreen = document.getElementById('gameover-screen');
const victoryScreen = document.getElementById('victory-screen');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
const vicBtn = document.getElementById('vic-btn');
const newHiscoreMsg = document.getElementById('new-hiscore-msg');
const waveVal = document.getElementById('wave-val');
const scoreVal = document.getElementById('score-val');
const hiscoreVal = document.getElementById('hiscore-val');
const livesContainer = document.getElementById('lives-container');
const finalScoreSpan = document.getElementById('final-score');
const vicScoreSpan = document.getElementById('vic-score');
// Game States
let state = 'start'; // start, playing, gameover, victory
let score = 0;
let hiscore = 0; // Simple in-memory hi-score, completely removing local storage / store to avoid freezes
hiscoreVal.textContent = hiscore;
let wave = 1;
let lives = 3;
let weaponLevel = 1;
let invulnerableUntil = 0;
// Space Starfield Background
const stars = [];
for (let i = 0; i < 60; i++) {
stars.push({
x: Math.random() * 600,
y: Math.random() * 600,
size: Math.random() * 2 + 0.5,
speed: Math.random() * 1.5 + 0.5
});
}
// Entities
let player = {
x: 300,
y: 500,
w: 36,
h: 36,
speed: 5.5,
lastShot: 0,
shotCooldown: 220
};
let keys = {};
let lasers = [];
let chickens = [];
let eggs = [];
let drops = []; // drumsticks, gift boxes
let particles = [];
// Boss state
let boss = null;
// Audio Context (Synthesized sound effects with defensive fallback)
let audioCtx = null;
function playSound(type) {
try {
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (!AudioContextClass) return;
if (!audioCtx) {
audioCtx = new AudioContextClass();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
if (type === 'shoot') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(450, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.12);
gain.gain.setValueAtTime(0.08, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.12);
osc.start();
osc.stop(audioCtx.currentTime + 0.12);
} else if (type === 'hit') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(120, audioCtx.currentTime);
osc.frequency.linearRampToValueAtTime(30, audioCtx.currentTime + 0.18);
gain.gain.setValueAtTime(0.12, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(0.01, audioCtx.currentTime + 0.18);
osc.start();
osc.stop(audioCtx.currentTime + 0.18);
} else if (type === 'powerup') {
osc.type = 'sine';
osc.frequency.setValueAtTime(260, audioCtx.currentTime);
osc.frequency.setValueAtTime(390, audioCtx.currentTime + 0.08);
osc.frequency.setValueAtTime(520, audioCtx.currentTime + 0.16);
gain.gain.setValueAtTime(0.08, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.24);
osc.start();
osc.stop(audioCtx.currentTime + 0.24);
} else if (type === 'hurt') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(80, audioCtx.currentTime);
osc.frequency.linearRampToValueAtTime(10, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.start();
osc.stop(audioCtx.currentTime + 0.4);
}
} catch (e) {
// Quiet fail if AudioContext is blocked or unsupported
}
}
// Draw procedures
function drawSpaceship(x, y, w, h) {
ctx.save();
ctx.translate(x, y);
// Engine Flare
if (Math.random() > 0.3) {
ctx.fillStyle = '#e67e22';
ctx.beginPath();
ctx.moveTo(-6, h/2);
ctx.lineTo(6, h/2);
ctx.lineTo(0, h/2 + 12 + Math.random()*8);
ctx.closePath();
ctx.fill();
}
// Wings
ctx.fillStyle = '#7f8c8d';
ctx.beginPath();
ctx.moveTo(-w/2, h/4);
ctx.lineTo(w/2, h/4);
ctx.lineTo(0, -h/2);
ctx.closePath();
ctx.fill();
// Fuselage
ctx.fillStyle = '#ecf0f1';
ctx.beginPath();
ctx.ellipse(0, 0, w/4, h/2, 0, 0, Math.PI * 2);
ctx.fill();
// Shield/Energy wingtips
ctx.strokeStyle = '#3498db';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.moveTo(-w/2, h/4);
ctx.lineTo(-w/2 + 4, -h/6);
ctx.moveTo(w/2, h/4);
ctx.lineTo(w/2 - 4, -h/6);
ctx.stroke();
// Cockpit dome
ctx.fillStyle = '#2980b9';
ctx.beginPath();
ctx.arc(0, -h/8, w/6, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawChicken(x, y, size, flapState, isBoss = false, hpRatio = 1) {
ctx.save();
ctx.translate(x, y);
// Wings
let wingY = flapState ? -size/5 : size/5;
ctx.fillStyle = '#f39c12';
ctx.beginPath();
ctx.moveTo(-size/2, 0);
ctx.lineTo(-size/2 - (isBoss ? 25 : 12), wingY);
ctx.lineTo(-size/4, size/4);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(size/2, 0);
ctx.lineTo(size/2 + (isBoss ? 25 : 12), wingY);
ctx.lineTo(size/4, size/4);
ctx.closePath();
ctx.fill();
// Feet
ctx.strokeStyle = '#d35400';
ctx.lineWidth = isBoss ? 5 : 3;
ctx.beginPath();
ctx.moveTo(-size/6, size/2);
ctx.lineTo(-size/4, size/2 + (isBoss ? 15 : 6));
ctx.moveTo(size/6, size/2);
ctx.lineTo(size/4, size/2 + (isBoss ? 15 : 6));
ctx.stroke();
// Body
ctx.fillStyle = isBoss ? '#e74c3c' : '#ffffff';
ctx.beginPath();
ctx.arc(0, 0, size/2, 0, Math.PI*2);
ctx.fill();
// Comb
ctx.fillStyle = isBoss ? '#c0392b' : '#e74c3c';
ctx.beginPath();
ctx.arc(-size/6, -size/2, isBoss ? 12 : 5, 0, Math.PI*2);
ctx.arc(0, -size/2 - (isBoss ? 8 : 3), isBoss ? 15 : 7, 0, Math.PI*2);
ctx.arc(size/6, -size/2, isBoss ? 12 : 5, 0, Math.PI*2);
ctx.fill();
// Eyes
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(-size/5, -size/10, isBoss ? 5 : 2.5, 0, Math.PI*2);
ctx.arc(size/5, -size/10, isBoss ? 5 : 2.5, 0, Math.PI*2);
ctx.fill();
// Beak
ctx.fillStyle = '#f1c40f';
ctx.beginPath();
ctx.moveTo(-size/8, size/10);
ctx.lineTo(size/8, size/10);
ctx.lineTo(0, size/3);
ctx.closePath();
ctx.fill();
// Boss HP Bar overlay
if (isBoss) {
ctx.restore();
ctx.fillStyle = '#2c3e50';
ctx.fillRect(100, 30, 400, 10);
ctx.fillStyle = '#e74c3c';
ctx.fillRect(100, 30, Math.max(0, 400 * hpRatio), 10);
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.strokeRect(100, 30, 400, 10);
return;
}
ctx.restore();
}
function drawEgg(x, y, size) {
ctx.save();
ctx.translate(x, y);
ctx.fillStyle = '#f1f2f6';
ctx.beginPath();
ctx.ellipse(0, 0, size * 0.7, size, 0, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.ellipse(-size*0.2, -size*0.3, size * 0.2, size * 0.3, 0.2, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
function drawDrumstick(x, y, size) {
ctx.save();
ctx.translate(x, y);
ctx.fillStyle = '#d35400';
ctx.beginPath();
ctx.ellipse(-2, -2, size * 0.5, size * 0.6, 0.4, 0, Math.PI*2);
ctx.fill();
ctx.strokeStyle = '#f1f2f6';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(-1, -1);
ctx.lineTo(6, 6);
ctx.stroke();
ctx.fillStyle = '#f1f2f6';
ctx.beginPath();
ctx.arc(5, 8, 3, 0, Math.PI*2);
ctx.arc(8, 5, 3, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
function drawGift(x, y, size) {
ctx.save();
ctx.translate(x, y);
ctx.fillStyle = '#9b59b6';
ctx.fillRect(-size/2, -size/2, size, size);
ctx.fillStyle = '#f1c40f';
ctx.fillRect(-2, -size/2, 4, size);
ctx.fillRect(-size/2, -2, size, 4);
ctx.beginPath();
ctx.arc(-4, -size/2, 3, 0, Math.PI*2);
ctx.arc(4, -size/2, 3, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
function initWave() {
chickens = [];
eggs = [];
drops = [];
lasers = [];
boss = null;
waveVal.textContent = wave;
if (wave === 1) {
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 6; col++) {
chickens.push({
id: Math.random(),
x: 80 + col * 80,
y: 80 + row * 60,
originX: 80 + col * 80,
originY: 80 + row * 60,
size: 32,
hp: 1,
maxHp: 1,
moveDir: 1,
moveRange: 40,
swimOffset: Math.random() * Math.PI * 2
});
}
}
} else if (wave === 2) {
for (let i = 0; i < 14; i++) {
let angle = (i / 14) * Math.PI * 2;
chickens.push({
id: Math.random(),
x: 300 + Math.cos(angle) * 150,
y: 200 + Math.sin(angle) * 100,
originX: 300,
originY: 200,
radius: 140,
angle: angle,
size: 34,
hp: 2,
maxHp: 2,
type: 'circle'
});
}
} else if (wave === 3) {
boss = {
x: 300,
y: 150,
vx: 2,
size: 90,
hp: 30,
maxHp: 30,
lastAttack: 0,
attackCooldown: 1200
};
chickens.push(boss);
}
}
function updateLivesUI() {
livesContainer.innerHTML = '';
for (let i = 0; i < lives; i++) {
const heart = document.createElement('span');
heart.style.color = '#e74c3c';
heart.style.fontSize = '14px';
heart.innerHTML = '❤️';
livesContainer.appendChild(heart);
}
}
function fireWeapon() {
const now = Date.now();
if (now - player.lastShot >= player.shotCooldown) {
playSound('shoot');
player.lastShot = now;
if (weaponLevel === 1) {
lasers.push({ x: player.x, y: player.y - player.h/2, vy: -10, power: 1 });
} else if (weaponLevel === 2) {
lasers.push({ x: player.x - 10, y: player.y - player.h/2, vy: -10, power: 1 });
lasers.push({ x: player.x + 10, y: player.y - player.h/2, vy: -10, power: 1 });
} else if (weaponLevel === 3) {
lasers.push({ x: player.x, y: player.y - player.h/2, vy: -11, power: 1.5 });
lasers.push({ x: player.x - 16, y: player.y - player.h/4, vy: -9, vx: -2, power: 1 });
lasers.push({ x: player.x + 16, y: player.y - player.h/4, vy: -9, vx: 2, power: 1 });
} else {
lasers.push({ x: player.x, y: player.y - player.h/2, vy: -12, power: 2 });
lasers.push({ x: player.x - 12, y: player.y - player.h/2, vy: -11, power: 1 });
lasers.push({ x: player.x + 12, y: player.y - player.h/2, vy: -11, power: 1 });
lasers.push({ x: player.x - 24, y: player.y - player.h/4, vy: -9, vx: -3, power: 1 });
lasers.push({ x: player.x + 24, y: player.y - player.h/4, vy: -9, vx: 3, power: 1 });
}
}
}
// Pointer controls with strict NaN/Infinity prevention
let activePointer = false;
canvas.addEventListener('pointerdown', (e) => {
activePointer = true;
handlePointer(e);
});
canvas.addEventListener('pointermove', (e) => {
if (activePointer) handlePointer(e);
});
window.addEventListener('pointerup', () => activePointer = false);
function handlePointer(e) {
const rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) return; // Prevent division by zero / NaN
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const targetX = (e.clientX - rect.left) * scaleX;
const targetY = (e.clientY - rect.top) * scaleY;
if (isNaN(targetX) || isNaN(targetY) || !isFinite(targetX) || !isFinite(targetY)) {
return;
}
player.x = targetX;
player.y = targetY;
// Boundary constraints
if (player.x < player.w/2) player.x = player.w/2;
if (player.x > 600 - player.w/2) player.x = 600 - player.w/2;
if (player.y < 300) player.y = 300;
if (player.y > 600 - player.h/2) player.y = 600 - player.h/2;
}
// Keyboard controls
window.addEventListener('keydown', (e) => {
keys[e.key.toLowerCase()] = true;
if (e.key === ' ') e.preventDefault();
});
window.addEventListener('keyup', (e) => {
keys[e.key.toLowerCase()] = false;
});
function startMission() {
state = 'playing';
score = 0;
wave = 1;
lives = 3;
weaponLevel = 1;
invulnerableUntil = 0;
scoreVal.textContent = 0;
updateLivesUI();
initWave();
startScreen.style.display = 'none';
gameoverScreen.style.display = 'none';
victoryScreen.style.display = 'none';
newHiscoreMsg.style.display = 'none';
}
startBtn.onclick = startMission;
restartBtn.onclick = startMission;
vicBtn.onclick = startMission;
function createExplosion(x, y, colorCount = 12) {
const colors = ['#e74c3c', '#f1c40f', '#f39c12', '#ffffff'];
for (let i = 0; i < colorCount; i++) {
particles.push({
x: x,
y: y,
vx: (Math.random() - 0.5) * 6,
vy: (Math.random() - 0.5) * 6,
color: colors[Math.floor(Math.random() * colors.length)],
size: Math.random() * 4 + 2,
life: 1,
decay: Math.random() * 0.05 + 0.02
});
}
}
// Safe game loop wrapper to prevent freezing or unhandled errors
function gameLoop(timestamp) {
try {
const time = (typeof timestamp === 'number' && !isNaN(timestamp)) ? timestamp : performance.now();
ctx.clearRect(0, 0, 600, 600);
// Stars
ctx.fillStyle = '#ffffff';
stars.forEach(star => {
star.y += star.speed;
if (star.y > 600) {
star.y = 0;
star.x = Math.random() * 600;
}
ctx.fillRect(star.x, star.y, star.size, star.size);
});
if (state === 'playing') {
// Fallback checks to prevent NaN on coordinates
if (isNaN(player.x) || !isFinite(player.x)) player.x = 300;
if (isNaN(player.y) || !isFinite(player.y)) player.y = 500;
if (keys['arrowleft'] || keys['a']) player.x -= player.speed;
if (keys['arrowright'] || keys['d']) player.x += player.speed;
if (keys['arrowup'] || keys['w']) player.y -= player.speed;
if (keys['arrowdown'] || keys['s']) player.y += player.speed;
if (player.x < player.w/2) player.x = player.w/2;
if (player.x > 600 - player.w/2) player.x = 600 - player.w/2;
if (player.y < 300) player.y = 300;
if (player.y > 600 - player.h/2) player.y = 600 - player.h/2;
if (keys[' '] || activePointer) {
fireWeapon();
}
// Update Lasers
for (let i = lasers.length - 1; i >= 0; i--) {
let l = lasers[i];
l.y += l.vy;
if (l.vx) l.x += l.vx;
ctx.fillStyle = '#2ecc71';
ctx.fillRect(l.x - 2, l.y, 4, 12);
if (l.y < 0 || isNaN(l.y)) {
lasers.splice(i, 1);
}
}
// Chickens movement
let timeSec = time / 1000;
let isFlapping = Math.floor(time / 180) % 2 === 0;
chickens.forEach((ch) => {
if (ch === boss) {
ch.x += ch.vx;
if (ch.x - ch.size/2 < 50 || ch.x + ch.size/2 > 550) {
ch.vx *= -1;
}
if (time - ch.lastAttack > ch.attackCooldown) {
ch.lastAttack = time;
for (let offset of [-30, 0, 30]) {
eggs.push({ x: ch.x + offset, y: ch.y + ch.size/3, vy: 5 });
}
}
return;
}
if (ch.type === 'circle') {
ch.angle += 0.015;
ch.x = ch.originX + Math.cos(ch.angle) * ch.radius;
ch.y = ch.originY + Math.sin(ch.angle) * (ch.radius * 0.7);
} else {
let swing = Math.sin(timeSec * 2 + ch.swimOffset) * ch.moveRange;
ch.x = ch.originX + swing;
}
if (Math.random() < 0.0025) {
eggs.push({ x: ch.x, y: ch.y + ch.size/3, vy: 3.5 });
}
});
// Update Eggs
const isInvulnerable = Date.now() < invulnerableUntil;
for (let i = eggs.length - 1; i >= 0; i--) {
let egg = eggs[i];
egg.y += egg.vy;
drawEgg(egg.x, egg.y, 10);
let dist = Math.hypot(egg.x - player.x, egg.y - player.y);
if (dist < player.w/2 + 6) {
eggs.splice(i, 1);
if (!isInvulnerable) {
playerHit();
}
continue;
}
if (egg.y > 620 || isNaN(egg.y)) {
eggs.splice(i, 1);
}
}
// Chicken collisions
for (let cIdx = chickens.length - 1; cIdx >= 0; cIdx--) {
let ch = chickens[cIdx];
drawChicken(ch.x, ch.y, ch.size, isFlapping, ch === boss, ch.hp / ch.maxHp);
let crashDist = Math.hypot(ch.x - player.x, ch.y - player.y);
if (crashDist < (ch.size/2 + player.w/2 - 4)) {
if (!isInvulnerable) {
playerHit();
chickens.splice(cIdx, 1);
createExplosion(ch.x, ch.y, 25);
playSound('hit');
checkWaveSuccess();
}
continue;
}
for (let lIdx = lasers.length - 1; lIdx >= 0; lIdx--) {
let l = lasers[lIdx];
let hitDist = Math.hypot(l.x - ch.x, l.y - ch.y);
if (hitDist < ch.size/2 + 5) {
lasers.splice(lIdx, 1);
ch.hp -= l.power;
createExplosion(l.x, l.y, 4);
if (ch.hp <= 0) {
chickens.splice(cIdx, 1);
createExplosion(ch.x, ch.y, ch === boss ? 60 : 18);
playSound('hit');
if (ch === boss) {
score += 5000;
scoreVal.textContent = score;
checkWaveSuccess();
} else {
score += (wave === 2 ? 200 : 100);
scoreVal.textContent = score;
let rand = Math.random();
if (rand < 0.25) {
drops.push({ x: ch.x, y: ch.y, vy: 2, type: 'food' });
} else if (rand < 0.32) {
drops.push({ x: ch.x, y: ch.y, vy: 2.2, type: 'gift' });
}
checkWaveSuccess();
}
}
break;
}
}
}
// Upgrades / Drops updates
for (let i = drops.length - 1; i >= 0; i--) {
let d = drops[i];
d.y += d.vy;
if (d.type === 'food') {
drawDrumstick(d.x, d.y, 16);
} else {
drawGift(d.x, d.y, 18);
}
let dist = Math.hypot(d.x - player.x, d.y - player.y);
if (dist < player.w/2 + 8) {
if (d.type === 'food') {
score += 150;
scoreVal.textContent = score;
playSound('shoot');
} else {
weaponLevel = Math.min(4, weaponLevel + 1);
playSound('powerup');
}
drops.splice(i, 1);
continue;
}
if (d.y > 620 || isNaN(d.y)) {
drops.splice(i, 1);
}
}
if (!isInvulnerable || Math.floor(time / 100) % 2 === 0) {
drawSpaceship(player.x, player.y, player.w, player.h);
}
}
// Particles
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= p.decay;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, Math.max(0.1, p.size * p.life), 0, Math.PI*2);
ctx.fill();
if (p.life <= 0 || isNaN(p.x)) {
particles.splice(i, 1);
}
}
} catch (e) {
// Catch any unexpected loop errors silently to prevent complete page freezing
console.error(e);
}
requestAnimationFrame(gameLoop);
}
function playerHit() {
if (Date.now() < invulnerableUntil) return;
invulnerableUntil = Date.now() + 1500;
lives--;
updateLivesUI();
playSound('hurt');
createExplosion(player.x, player.y, 35);
weaponLevel = Math.max(1, weaponLevel - 1);
if (lives <= 0) {
state = 'gameover';
finalScoreSpan.textContent = score;
if (score > hiscore) {
hiscore = score;
hiscoreVal.textContent = hiscore;
newHiscoreMsg.style.display = 'block';
} else {
newHiscoreMsg.style.display = 'none';
}
gameoverScreen.style.display = 'flex';
}
}
function checkWaveSuccess() {
if (chickens.length === 0) {
if (wave === 3) {
state = 'victory';
vicScoreSpan.textContent = score;
if (score > hiscore) {
hiscore = score;
hiscoreVal.textContent = hiscore;
}
victoryScreen.style.display = 'flex';
} else {
wave++;
initWave();
}
}
}
requestAnimationFrame(gameLoop);
</script>