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

<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>