← Worms results

Worms

thinkingcap qwen3.6 27b · basic

Initial view of Worms

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 51,287 bytes · SHA-256 cfec4757c437
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worm Warfare</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0a1a; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; user-select: none; }
canvas { display: block; cursor: crosshair; }
#ui-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
#ui-overlay > * { pointer-events: auto; }
#menu-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #0a0a2e 0%, #1a0a3e 50%, #0a1a2e 100%); z-index: 100; }
#menu-screen h1 { font-size: 72px; color: #ff6b35; text-shadow: 0 0 30px rgba(255,107,53,0.5), 0 4px 8px rgba(0,0,0,0.8); margin-bottom: 10px; letter-spacing: 4px; }
#menu-screen .subtitle { color: #aaa; font-size: 18px; margin-bottom: 40px; letter-spacing: 2px; }
.menu-btn { background: linear-gradient(135deg, #ff6b35, #e74c3c); border: none; color: white; padding: 16px 48px; font-size: 22px; border-radius: 12px; cursor: pointer; margin: 8px; transition: all 0.2s; box-shadow: 0 4px 15px rgba(231,76,60,0.4); letter-spacing: 1px; }
.menu-btn:hover { transform: translateY(-2px) scale(1.05); box-shadow: 0 8px 25px rgba(231,76,60,0.6); }
.menu-btn:active { transform: translateY(0) scale(0.98); }
.menu-btn.secondary { background: linear-gradient(135deg, #3498db, #2980b9); box-shadow: 0 4px 15px rgba(52,152,219,0.4); }
.menu-btn.secondary:hover { box-shadow: 0 8px 25px rgba(52,152,219,0.6); }
#setup-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: none; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #0a0a2e 0%, #1a0a3e 50%, #0a1a2e 100%); z-index: 99; }
#setup-screen h2 { color: #ff6b35; font-size: 42px; margin-bottom: 30px; text-shadow: 0 0 20px rgba(255,107,53,0.4); }
.setup-row { display: flex; align-items: center; gap: 20px; margin: 8px 0; color: #ccc; font-size: 18px; }
.setup-row label { min-width: 160px; text-align: right; }
.setup-row select, .setup-row input { background: #1a1a3e; border: 2px solid #444; color: white; padding: 8px 16px; border-radius: 8px; font-size: 16px; }
.setup-row select:focus, .setup-row input:focus { outline: none; border-color: #ff6b35; }
#hud-top { position: absolute; top: 0; left: 0; width: 100%; padding: 12px 24px; display: flex; justify-content: space-between; align-items: flex-start; pointer-events: none; z-index: 50; }
.team-info { background: rgba(0,0,0,0.7); border-radius: 12px; padding: 8px 16px; backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); }
.team-info .team-name { font-weight: bold; font-size: 14px; margin-bottom: 4px; }
.team-info .unit-hp { display: flex; gap: 6px; align-items: center; }
.hp-bar-mini { width: 50px; height: 8px; background: #333; border-radius: 4px; overflow: hidden; }
.hp-bar-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
.unit-label { font-size: 11px; color: #aaa; min-width: 28px; }
#turn-timer-container { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); pointer-events: none; z-index: 50; text-align: center; display: none; }
#turn-timer-bar { width: 200px; height: 12px; background: rgba(0,0,0,0.6); border-radius: 6px; overflow: hidden; margin-top: 8px; }
#turn-timer-fill { height: 100%; background: linear-gradient(90deg, #e74c3c, #f39c12, #2ecc71); border-radius: 6px; transition: width 0.1s linear; }
#turn-timer-text { color: white; font-size: 28px; font-weight: bold; text-shadow: 0 2px 8px rgba(0,0,0,0.8); }
#weapon-panel { position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); display: flex; gap: 8px; padding: 12px 20px; background: rgba(0,0,0,0.75); border-radius: 16px; backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); z-index: 50; }
.weapon-slot { width: 64px; height: 64px; background: rgba(255,255,255,0.08); border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s; border: 2px solid transparent; position: relative; }
.weapon-slot:hover { background: rgba(255,255,255,0.15); transform: translateY(-4px); }
.weapon-slot.selected { border-color: #ff6b35; background: rgba(255,107,53,0.2); box-shadow: 0 0 20px rgba(255,107,53,0.3); }
.weapon-slot .w-icon { font-size: 24px; }
.weapon-slot .w-name { font-size: 9px; color: #ccc; margin-top: 2px; text-align: center; line-height: 1.1; }
.weapon-slot .w-count { position: absolute; top: -6px; right: -6px; background: #e74c3c; color: white; font-size: 10px; width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; }
#power-meter { position: absolute; right: 30px; bottom: 120px; width: 30px; height: 200px; background: rgba(0,0,0,0.6); border-radius: 15px; overflow: hidden; z-index: 50; display: none; }
#power-fill { position: absolute; bottom: 0; width: 100%; background: linear-gradient(to top, #2ecc71, #f39c12, #e74c3c); border-radius: 15px; transition: height 0.05s; }
#power-text { position: absolute; right: 68px; bottom: 120px; color: white; font-size: 14px; z-index: 50; display: none; text-shadow: 0 1px 4px rgba(0,0,0,0.8); }
#wind-indicator { position: absolute; top: 70px; left: 50%; transform: translateX(-50%); color: white; font-size: 13px; z-index: 50; text-align: center; background: rgba(0,0,0,0.5); padding: 6px 16px; border-radius: 20px; backdrop-filter: blur(5px); }
#wind-arrow { font-size: 20px; display: inline-block; transition: transform 0.3s; }
#action-buttons { position: absolute; bottom: 16px; right: 30px; display: flex; gap: 8px; z-index: 50; }
.action-btn { background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: white; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; transition: all 0.2s; }
.action-btn:hover { background: rgba(255,255,255,0.2); }
#game-over-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: none; flex-direction: column; align-items: center; justify-content: center; background: rgba(0,0,0,0.85); z-index: 200; }
#game-over-screen h2 { font-size: 64px; color: #ff6b35; text-shadow: 0 0 40px rgba(255,107,53,0.5); margin-bottom: 20px; }
#game-over-screen p { color: #ccc; font-size: 24px; margin-bottom: 30px; }
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="ui-overlay">
  <div id="menu-screen">
    <h1>🐛 WORM WARFARE</h1>
    <p class="subtitle">TACTICAL ARTILLERY COMBAT</p>
    <button class="menu-btn" onclick="showSetup()">⚔️ START GAME</button>
  </div>
  <div id="setup-screen">
    <h2>BATTLE SETUP</h2>
    <div class="setup-row"><label>Teams:</label><select id="num-teams"><option value="2">2 Teams</option><option value="3">3 Teams</option><option value="4" selected>4 Teams</option></select></div>
    <div class="setup-row"><label>Terrain:</label><select id="terrain-type"><option value="island">Single Island</option><option value="two-islands">Two Islands</option><option value="cave">Cave System</option></select></div>
    <div class="setup-row"><label>CPU Players:</label><select id="cpu-count"><option value="0">None (Hot-seat)</option><option value="1">1 CPU</option><option value="2" selected>2 CPUs</option><option value="3">3 CPUs</option></select></div>
    <button class="menu-btn" onclick="startGame()" style="margin-top:20px;">🚀 LAUNCH BATTLE</button>
    <button class="menu-btn secondary" onclick="backToMenu()">← BACK</button>
  </div>
  <div id="hud-top"></div>
  <div id="wind-indicator">Wind: <span id="wind-arrow">→</span> <span id="wind-val">0</span></div>
  <div id="turn-timer-container"><div id="turn-timer-text">25</div><div id="turn-timer-bar"><div id="turn-timer-fill"></div></div></div>
  <div id="weapon-panel"></div>
  <div id="power-meter"><div id="power-fill" style="height:0%"></div></div>
  <div id="power-text">Power</div>
  <div id="action-buttons">
    <button class="action-btn" onclick="skipTurn()">⏭ Skip Turn</button>
  </div>
  <div id="game-over-screen"><h2 id="winner-text"></h2><p>Battle Complete!</p><button class="menu-btn" onclick="backToMenu()">🔄 PLAY AGAIN</button></div>
</div>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let W, H;
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);

// Audio system
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playSound(freq, duration, type='sine', vol=0.3, slide=0) {
  const o = audioCtx.createOscillator();
  const g = audioCtx.createGain();
  o.type = type;
  o.frequency.setValueAtTime(freq, audioCtx.currentTime);
  if (slide) o.frequency.exponentialRampToValueAtTime(Math.max(slide,20), audioCtx.currentTime + duration);
  g.gain.setValueAtTime(vol, audioCtx.currentTime);
  g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
  o.connect(g); g.connect(audioCtx.destination);
  o.start(); o.stop(audioCtx.currentTime + duration);
}
function sfxClick() { playSound(800, 0.05, 'square', 0.15); }
function sfxExplosion(big) { 
  const dur = big ? 0.6 : 0.3;
  for(let i=0;i<3;i++) setTimeout(()=>playSound(80+i*20, dur, 'sawtooth', big?0.4:0.25, 20), i*30);
}
function sfxHit() { playSound(300, 0.15, 'square', 0.2, 100); }
function sfxJump() { playSound(400, 0.1, 'sine', 0.15, 600); }
function sfxLaser() { playSound(1200, 0.3, 'sawtooth', 0.2, 200); }
function sfxTick() { playSound(1000, 0.03, 'square', 0.08); }
function sfxTurnEnd() { for(let i=0;i<5;i++) setTimeout(()=>playSound(600-i*80,0.1,'sine',0.2),i*60); }

// Game state
let gameState = 'menu'; // menu, playing, gameover
let terrainData = null;
let terrainWidth = 3000;
let terrainHeight = 800;
let teams = [];
let currentTeamIdx = 0;
let currentUnitIdx = 0;
let turnTimeLeft = 25;
let turnTimerActive = false;
let selectedWeapon = null;
let isAiming = false;
let aimAngle = -Math.PI / 4;
let aimPower = 0;
let powerCharging = false;
let projectiles = [];
let particles = [];
let floatingTexts = [];
let debrisChunks = [];
let camera = { x: 0, y: 0, zoom: 1, targetX: 0, targetY: 0, targetZoom: 1 };
let wind = 0;
let screenShake = { x: 0, y: 0, intensity: 0 };
let impactFreeze = 0;
let waterLevel = -999;
let terrainImage = null;

const TEAM_COLORS = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12'];
const TEAM_NAMES = ['Red Fury', 'Blue Storm', 'Green Viper', 'Gold Eagle'];

const WEAPONS = [
  { id:'rocket', name:'Rocket', icon:'🚀', type:'indirect', damage:40, aoe:60, speed:8, count:3, color:'#ff6b35' },
  { id:'grenade', name:'Grenade', icon:'💣', type:'indirect', damage:25, aoe:45, speed:10, count:4, color:'#f39c12' },
  { id:'shotgun', name:'Shotgun', icon:'🔫', type:'direct', damage:35, range:300, count:2, color:'#e74c3c' },
  { id:'laser', name:'Laser', icon:'⚡', type:'instant', damage:30, range:600, count:2, color:'#9b59b6' },
  { id:'nuke', name:'Nuke', icon:'☢️', type:'aoe', damage:80, aoe:150, speed:4, count:1, color:'#ff0000' },
  { id:'dynamite', name:'Dynamite', icon:'🧨', type:'placeable', damage:50, aoe:70, count:2, color:'#e67e22' },
  { id:'minigun', name:'Minigun', icon:'💥', type:'direct', damage:8, range:350, count:1, color:'#c0392b' },
];

const UTILITIES = [
  { id:'parachute', name:'Parachute', icon:'🪂', count:1 },
  { id:'jetpack', name:'Jetpack', icon:'🎒', count:1 },
  { id:'extratime', name:'+Time', icon:'⏰', count:1 },
];

class Unit {
  constructor(teamIdx, x, y) {
    this.team = teamIdx;
    this.x = x; this.y = y;
    this.vx = 0; this.vy = 0;
    this.hp = 100;
    this.maxHp = 100;
    this.alive = true;
    this.onGround = false;
    this.facing = 1;
    this.hasParachute = false;
    this.hasJetpack = false;
    this.jetpackFuel = 0;
    this.inWater = false;
    this.swimTimer = 0;
    this.width = 24;
    this.height = 36;
    this.animFrame = 0;
    this.bobTimer = Math.random() * Math.PI * 2;
  }
}

class Projectile {
  constructor(x, y, vx, vy, weapon, ownerTeam) {
    this.x = x; this.y = y;
    this.vx = vx; this.vy = vy;
    this.weapon = weapon;
    this.ownerTeam = ownerTeam;
    this.alive = true;
    this.trail = [];
    this.bounces = 0;
    this.fuseTimer = 0;
    if (weapon.id === 'dynamite') {
      this.isDynamite = true;
      this.vx = 0; this.vy = 0;
      this.placed = false;
      this.timer = 180;
    }
  }
}

class Particle {
  constructor(x, y, vx, vy, color, life, size) {
    this.x = x; this.y = y;
    this.vx = vx; this.vy = vy;
    this.color = color;
    this.life = life;
    this.maxLife = life;
    this.size = size || 3;
  }
}

class FloatingText {
  constructor(x, y, text, color) {
    this.x = x; this.y = y;
    this.text = text;
    this.color = color;
    this.life = 60;
    this.maxLife = 60;
    this.vy = -2;
  }
}

class DebrisChunk {
  constructor(x, y, w, h) {
    this.x = x; this.y = y;
    this.w = w; this.h = h;
    this.vx = (Math.random()-0.5)*4;
    this.vy = -Math.random()*3-1;
    this.rotation = 0;
    this.rotSpeed = (Math.random()-0.5)*0.2;
    this.alive = true;
  }
}

// Terrain generation
function generateTerrain(type) {
  const data = new Uint8Array(terrainWidth * terrainHeight);
  
  if (type === 'island') {
    for (let x = 0; x < terrainWidth; x++) {
      let baseH = terrainHeight * 0.55;
      let h = baseH + Math.sin(x*0.008)*60 + Math.sin(x*0.02)*30 + Math.sin(x*0.05)*15 + Math.sin(x*0.003)*40;
      for (let y = 0; y < terrainHeight; y++) {
        if (y > h) data[y * terrainWidth + x] = 200;
      }
    }
    waterLevel = terrainHeight * 0.75;
  } else if (type === 'two-islands') {
    for (let x = 0; x < terrainWidth; x++) {
      let baseH = terrainHeight * 0.65;
      let island1 = Math.exp(-Math.pow((x - terrainWidth*0.3)/200, 2)) * 200;
      let island2 = Math.exp(-Math.pow((x - terrainWidth*0.7)/200, 2)) * 200;
      let h = baseH + island1 + island2 + Math.sin(x*0.03)*20 + Math.sin(x*0.07)*10;
      for (let y = 0; y < terrainHeight; y++) {
        if (y > h) data[y * terrainWidth + x] = 200;
      }
    }
    waterLevel = terrainHeight * 0.68;
  } else if (type === 'cave') {
    for (let x = 0; x < terrainWidth; x++) {
      let topH = terrainHeight * 0.2 + Math.sin(x*0.01)*30 + Math.sin(x*0.03)*15;
      let botH = terrainHeight * 0.7 + Math.sin(x*0.008+1)*40 + Math.sin(x*0.025)*20;
      for (let y = 0; y < terrainHeight; y++) {
        if (y < topH || y > botH) data[y * terrainWidth + x] = 200;
      }
    }
    waterLevel = -999;
  }
  
  return data;
}

function getTerrainPixel(x, y) {
  if (x < 0 || x >= terrainWidth || y < 0 || y >= terrainHeight) return 255;
  return terrainData[y * terrainWidth + x];
}

function setTerrainPixel(x, y, val) {
  if (x < 0 || x >= terrainWidth || y < 0 || y >= terrainHeight) return;
  terrainData[y * terrainWidth + x] = val;
}

function destroyTerrain(cx, cy, radius) {
  const r2 = radius * radius;
  let chunks = [];
  for (let dy = -radius; dy <= radius; dy++) {
    for (let dx = -radius; dx <= radius; dx++) {
      if (dx*dx + dy*dy > r2) continue;
      const tx = Math.floor(cx + dx);
      const ty = Math.floor(cy + dy);
      if (tx < 0 || tx >= terrainWidth || ty < 0 || ty >= terrainHeight) continue;
      if (terrainData[ty * terrainWidth + tx] > 0) {
        // Spawn debris chunks for larger explosions
        if (radius > 40 && Math.random() < 0.15) {
          const cw = 3 + Math.floor(Math.random()*8);
          const ch = 2 + Math.floor(Math.random()*6);
          chunks.push(new DebrisChunk(tx, ty, cw, ch));
        }
        setTerrainPixel(tx, ty, 0);
      }
    }
  }
  return chunks;
}

function spawnParticles(x, y, count, color, speed, life) {
  for (let i = 0; i < count; i++) {
    const angle = Math.random() * Math.PI * 2;
    const spd = Math.random() * speed;
    particles.push(new Particle(x, y, Math.cos(angle)*spd, Math.sin(angle)*spd, color, life + Math.random()*life*0.5, 1+Math.random()*3));
  }
}

function spawnExplosionParticles(x, y, radius) {
  const colors = ['#ff6b35','#e74c3c','#f39c12','#fff','#ffaa00'];
  for (let i = 0; i < radius * 2; i++) {
    const angle = Math.random() * Math.PI * 2;
    const spd = Math.random() * radius * 0.4 + 1;
    particles.push(new Particle(x, y, Math.cos(angle)*spd, Math.sin(angle)*spd, colors[Math.floor(Math.random()*colors.length)], 30+Math.random()*40, 2+Math.random()*5));
  }
}

function addFloatingText(x, y, text, color) {
  floatingTexts.push(new FloatingText(x, y, text, color || '#fff'));
}

// Terrain rendering (cached)
let terrainCanvas = null;
function renderTerrainToCanvas() {
  terrainCanvas = document.createElement('canvas');
  terrainCanvas.width = terrainWidth;
  terrainCanvas.height = terrainHeight;
  const tctx = terrainCanvas.getContext('2d');
  const imgData = tctx.createImageData(terrainWidth, terrainHeight);
  
  for (let i = 0; i < terrainData.length; i++) {
    const v = terrainData[i];
    if (v > 0) {
      const y = Math.floor(i / terrainWidth);
      // Gradient based on depth
      const depthFactor = Math.min(1, y / terrainHeight);
      const r = Math.floor(60 + depthFactor * 40);
      const g = Math.floor(120 - depthFactor * 30 + (y < waterLevel ? 20 : 0));
      const b = Math.floor(50 + depthFactor * 20);
      imgData.data[i*4] = r;
      imgData.data[i*4+1] = g;
      imgData.data[i*4+2] = b;
      imgData.data[i*4+3] = 255;
    } else {
      imgData.data[i*4+3] = 0;
    }
  }
  tctx.putImageData(imgData, 0, 0);
}

// Initialize game
function initGame() {
  const numTeams = parseInt(document.getElementById('num-teams').value);
  const terrainType = document.getElementById('terrain-type').value;
  const cpuCount = parseInt(document.getElementById('cpu-count').value);
  
  terrainData = generateTerrain(terrainType);
  renderTerrainToCanvas();
  
  teams = [];
  for (let t = 0; t < numTeams; t++) {
    teams.push({
      name: TEAM_NAMES[t],
      color: TEAM_COLORS[t],
      isCPU: t >= numTeams - cpuCount,
      units: [],
      weapons: WEAPONS.map(w => ({...w})),
      utilities: UTILITIES.map(u => ({...u}))
    });
  }
  
  // Place units on terrain
  for (let t = 0; t < numTeams.length; t++) {
    const baseX = terrainWidth * (t + 1) / (numTeams + 1);
    for (let u = 0; u < 4; u++) {
      let px = baseX + (u - 1.5) * 60;
      // Find ground level
      let py = 0;
      for (let y = 0; y < terrainHeight; y++) {
        if (getTerrainPixel(Math.floor(px), y) > 0) { py = y - 20; break; }
      }
      teams[t].units.push(new Unit(t, px, py));
    }
  }
  
  currentTeamIdx = 0;
  currentUnitIdx = 0;
  wind = (Math.random() - 0.5) * 4;
  projectiles = [];
  particles = [];
  floatingTexts = [];
  debrisChunks = [];
  selectedWeapon = null;
  isAiming = false;
  
  startTurn();
}

function getActiveUnit() {
  if (currentTeamIdx >= teams.length) return null;
  const team = teams[currentTeamIdx];
  for (let i = currentUnitIdx; i < team.units.length; i++) {
    if (team.units[i].alive) return team.units[i];
  }
  return null;
}

function startTurn() {
  // Find next alive unit
  let found = false;
  while (currentTeamIdx < teams.length) {
    const team = teams[currentTeamIdx];
    for (let i = currentUnitIdx; i < team.units.length; i++) {
      if (team.units[i].alive) {
        currentUnitIdx = i;
        found = true;
        break;
      }
    }
    if (!found) {
      currentTeamIdx++;
      currentUnitIdx = 0;
    } else break;
  }
  
  if (currentTeamIdx >= teams.length) {
    endGame();
    return;
  }
  
  const unit = getActiveUnit();
  if (!unit) { endGame(); return; }
  
  turnTimeLeft = 25;
  selectedWeapon = null;
  isAiming = false;
  aimAngle = -Math.PI / 4;
  aimPower = 0;
  
  // Change wind occasionally
  if (Math.random() < 0.3) {
    wind += (Math.random() - 0.5) * 2;
    wind = Math.max(-6, Math.min(6, wind));
  }
  
  updateHUD();
  updateWeaponPanel();
  
  document.getElementById('turn-timer-container').style.display = 'block';
  turnTimerActive = true;
  
  if (teams[currentTeamIdx].isCPU) {
    setTimeout(() => cpuTurn(), 1000);
  }
}

function endTurn() {
  turnTimerActive = false;
  sfxTurnEnd();
  
  // Advance to next unit/team
  currentUnitIdx++;
  if (currentUnitIdx >= teams[currentTeamIdx].units.length) {
    currentUnitIdx = 0;
    currentTeamIdx++;
  }
  
  startTurn();
}

function skipTurn() {
  sfxClick();
  endTurn();
}

// Weapon firing
function fireWeapon(weapon, angle, power) {
  const unit = getActiveUnit();
  if (!unit || !weapon) return;
  
  // Decrement weapon count
  const teamWeapons = teams[currentTeamIdx].weapons;
  const w = teamWeapons.find(w => w.id === weapon.id);
  if (w && w.count > 0) {
    w.count--;
  } else return;
  
  sfxClick();
  
  if (weapon.type === 'instant') {
    // Laser - instant hit
    const range = weapon.range || 500;
    const endX = unit.x + Math.cos(angle) * range;
    const endY = unit.y + Math.sin(angle) * range;
    
    sfxLaser();
    
    // Check collision with units along the line
    for (let t = 0; t < teams.length; t++) {
      if (t === currentTeamIdx && weapon.id !== 'nuke') continue;
      for (const u of teams[t].units) {
        if (!u.alive) continue;
        const dist = pointToSegmentDist(u.x, u.y, unit.x, unit.y, endX, endY);
        if (dist < 20) {
          damageUnit(u, weapon.damage, currentTeamIdx);
        }
      }
    }
    
    // Laser particles
    const steps = 30;
    for (let i = 0; i <= steps; i++) {
      const t = i / steps;
      const lx = unit.x + (endX - unit.x) * t;
      const ly = unit.y + (endY - unit.y) * t;
      spawnParticles(lx, ly, 2, '#9b59b6', 2, 15);
    }
    
    updateWeaponPanel();
    return;
  }
  
  if (weapon.type === 'direct') {
    // Shotgun or minigun - multiple pellets
    const count = weapon.id === 'shotgun' ? 8 : 15;
    const spread = weapon.id === 'shotgun' ? 0.3 : 0.2;
    for (let i = 0; i < count; i++) {
      const a = angle + (Math.random() - 0.5) * spread;
      const spd = power * 15 + weapon.speed * 5;
      projectiles.push(new Projectile(unit.x, unit.y - 20, Math.cos(a)*spd, Math.sin(a)*spd, weapon, currentTeamIdx));
    }
    updateWeaponPanel();
    return;
  }
  
  if (weapon.type === 'placeable') {
    // Dynamite - place at aim point
    const dist = power * 10 + 50;
    const dx = unit.x + Math.cos(angle) * dist;
    const dy = unit.y + Math.sin(angle) * dist;
    projectiles.push(new Projectile(dx, dy, 0, 0, weapon, currentTeamIdx));
    updateWeaponPanel();
    return;
  }
  
  // Indirect projectile (rocket, grenade, nuke)
  const spd = power * 12 + weapon.speed * 3;
  projectiles.push(new Projectile(unit.x, unit.y - 20, Math.cos(angle)*spd, Math.sin(angle)*spd, weapon, currentTeamIdx));
  
  updateWeaponPanel();
}

function pointToSegmentDist(px, py, x1, y1, x2, y2) {
  const dx = x2-x1, dy = y2-y1;
  const len2 = dx*dx+dy*dy;
  if (len2 === 0) return Math.hypot(px-x1, py-y1);
  let t = ((px-x1)*dx+(py-y1)*dy)/len2;
  t = Math.max(0, Math.min(1, t));
  return Math.hypot(px-(x1+t*dx), py-(y1+t*dy));
}

function damageUnit(unit, damage, attackerTeam) {
  if (!unit.alive) return;
  unit.hp -= damage;
  addFloatingText(unit.x, unit.y - 30, `-${damage}`, '#ff4444');
  sfxHit();
  
  spawnParticles(unit.x, unit.y, 15, TEAM_COLORS[unit.team], 4, 25);
  
  if (unit.hp <= 0) {
    unit.alive = false;
    addFloatingText(unit.x, unit.y - 40, '💀', '#ff0000');
    spawnParticles(unit.x, unit.y, 40, TEAM_COLORS[unit.team], 6, 50);
    
    // Check if team is eliminated
    const team = teams[attackerTeam === undefined ? unit.team : attackerTeam];
    if (teams[unit.team].units.every(u => !u.alive)) {
      addFloatingText(unit.x, unit.y - 60, `${TEAM_NAMES[unit.team]} ELIMINATED!`, '#ff0000');
    }
  }
}

function explodeProjectile(proj) {
  proj.alive = false;
  const w = proj.weapon;
  
  sfxExplosion(w.id === 'nuke' || w.aoe > 100);
  
  // Terrain destruction
  const newDebris = destroyTerrain(proj.x, proj.y, w.aoe);
  debrisChunks.push(...newDebris);
  
  spawnExplosionParticles(proj.x, proj.y, w.aoe);
  
  if (w.id === 'nuke') {
    screenShake.intensity = 15;
    impactFreeze = 20;
  } else if (w.aoe > 60) {
    screenShake.intensity = 8;
    impactFreeze = 8;
  } else {
    screenShake.intensity = 4;
    impactFreeze = 3;
  }
  
  // Damage units in AoE
  for (const team of teams) {
    for (const unit of team.units) {
      if (!unit.alive) continue;
      const dist = Math.hypot(unit.x - proj.x, unit.y - proj.y);
      if (dist < w.aoe) {
        const dmg = Math.floor(w.damage * (1 - dist / w.aoe));
        damageUnit(unit, Math.max(1, dmg), proj.ownerTeam);
        
        // Knockback
        const kb = 5 * (1 - dist / w.aoe);
        const angle = Math.atan2(unit.y - proj.y, unit.x - proj.x);
        unit.vx += Math.cos(angle) * kb;
        unit.vy += Math.sin(angle) * kb - 3;
      }
    }
  }
  
  // Check game over
  checkGameOver();
}

function checkGameOver() {
  const aliveTeams = teams.filter(t => t.units.some(u => u.alive));
  if (aliveTeams.length <= 1) {
    setTimeout(() => endGame(), 2000);
  }
}

function endGame() {
  gameState = 'gameover';
  const winner = teams.find(t => t.units.some(u => u.alive));
  document.getElementById('winner-text').textContent = winner ? `${winner.name} WINS!` : 'DRAW!';
  document.getElementById('game-over-screen').style.display = 'flex';
  document.getElementById('turn-timer-container').style.display = 'none';
}

// Physics update
function updatePhysics(dt) {
  const gravity = 0.4;
  
  // Update units
  for (const team of teams) {
    for (const unit of team.units) {
      if (!unit.alive) continue;
      
      unit.bobTimer += 0.05;
      
      // Gravity
      unit.vy += gravity;
      
      // Wind effect on airborne units
      if (!unit.onGround && !unit.hasParachute) {
        unit.vx += wind * 0.01;
      }
      
      // Friction
      if (unit.onGround) {
        unit.vx *= 0.85;
      } else {
        unit.vx *= 0.99;
      }
      
      // Jetpack
      if (unit.hasJetpack && unit.jetpackFuel > 0 && keys['KeyW']) {
        unit.vy -= 1.2;
        unit.jetpackFuel--;
        spawnParticles(unit.x, unit.y + 15, 2, '#ffaa00', 3, 15);
      }
      
      // Parachute
      if (unit.hasParachute && !unit.onGround) {
        unit.vy = Math.min(unit.vy, 2);
        spawnParticles(unit.x + (Math.random()-0.5)*20, unit.y - 10, 1, '#fff', 1, 20);
      }
      
      // Move
      unit.x += unit.vx;
      unit.y += unit.vy;
      
      // Terrain collision
      unit.onGround = false;
      const feetY = Math.floor(unit.y + unit.height/2);
      const leftX = Math.floor(unit.x - unit.width/3);
      const rightX = Math.floor(unit.x + unit.width/3);
      
      if (feetY < terrainHeight && getTerrainPixel(leftX, feetY) > 0 || getTerrainPixel(rightX, feetY) > 0) {
        // Find exact ground level
        for (let gy = feetY; gy >= 0; gy--) {
          if (getTerrainPixel(Math.floor(unit.x), gy) === 0) {
            unit.y = gy - unit.height/2;
            unit.vy = 0;
            unit.onGround = true;
            
            // Fall damage
            if (unit.vy > 8) {
              const fallDmg = Math.floor((unit.vy - 8) * 3);
              damageUnit(unit, fallDmg, -1);
            }
            break;
          }
        }
      }
      
      // Water physics
      if (waterLevel > 0 && unit.y + unit.height/2 > waterLevel) {
        unit.inWater = true;
        unit.swimTimer++;
        unit.vy *= 0.9;
        unit.vx *= 0.95;
        
        // Drowning
        if (unit.swimTimer > 300 && !unit.hasJetpack) {
          damageUnit(unit, 1, -1);
        }
      } else {
        unit.inWater = false;
        unit.swimTimer = Math.max(0, unit.swimTimer - 2);
      }
      
      // Keep in bounds
      unit.x = Math.max(20, Math.min(terrainWidth - 20, unit.x));
      if (unit.y > terrainHeight + 100) {
        damageUnit(unit, 999, -1);
      }
    }
  }
  
  // Update projectiles
  for (const proj of projectiles) {
    if (!proj.alive) continue;
    
    if (proj.isDynamite) {
      proj.timer--;
      if (proj.timer <= 0) {
        explodeProjectile(proj);
      }
      // Dynamite bobbing
      proj.y += Math.sin(Date.now() * 0.01) * 0.3;
      continue;
    }
    
    proj.vy += gravity;
    proj.vx += wind * 0.02;
    
    proj.x += proj.vx;
    proj.y += proj.vy;
    
    // Trail
    proj.trail.push({x: proj.x, y: proj.y});
    if (proj.trail.length > 20) proj.trail.shift();
    
    spawnParticles(proj.x, proj.y, 1, '#ffaa00', 1, 10);
    
    // Terrain collision
    if (getTerrainPixel(Math.floor(proj.x), Math.floor(proj.y)) > 0) {
      explodeProjectile(proj);
      continue;
    }
    
    // Out of bounds
    if (proj.x < -50 || proj.x > terrainWidth + 50 || proj.y > terrainHeight + 50) {
      proj.alive = false;
    }
    
    // Direct hit collision with units
    if (proj.weapon.type === 'direct') {
      for (const team of teams) {
        for (const unit of team.units) {
          if (!unit.alive) continue;
          const dist = Math.hypot(unit.x - proj.x, unit.y - proj.y);
          if (dist < 18) {
            damageUnit(unit, proj.weapon.damage, proj.ownerTeam);
            spawnParticles(proj.x, proj.y, 5, '#ff4444', 3, 15);
            proj.alive = false;
          }
        }
      }
    }
    
    // Fuse timer for rockets
    if (proj.weapon.id === 'rocket') {
      proj.fuseTimer++;
      if (proj.fuseTimer > 300) explodeProjectile(proj);
    }
  }
  
  projectiles = projectiles.filter(p => p.alive);
  
  // Update debris chunks
  for (const chunk of debrisChunks) {
    if (!chunk.alive) continue;
    chunk.vy += gravity * 0.8;
    chunk.x += chunk.vx;
    chunk.y += chunk.vy;
    chunk.rotation += chunk.rotSpeed;
    
    // Ground collision
    if (getTerrainPixel(Math.floor(chunk.x), Math.floor(chunk.y + chunk.h/2)) > 0) {
      chunk.alive = false;
      spawnParticles(chunk.x, chunk.y, 3, '#8B7355', 2, 15);
    }
    
    if (chunk.y > terrainHeight + 50) chunk.alive = false;
  }
  debrisChunks = debrisChunks.filter(d => d.alive);
  
  // Update particles
  for (const p of particles) {
    p.vy += 0.1;
    p.x += p.vx;
    p.y += p.vy;
    p.life--;
  }
  particles = particles.filter(p => p.life > 0);
  
  // Update floating texts
  for (const ft of floatingTexts) {
    ft.y += ft.vy;
    ft.life--;
  }
  floatingTexts = floatingTexts.filter(ft => ft.life > 0);
  
  // Screen shake decay
  if (screenShake.intensity > 0) {
    screenShake.x = (Math.random() - 0.5) * screenShake.intensity;
    screenShake.y = (Math.random() - 0.5) * screenShake.intensity;
    screenShake.intensity *= 0.9;
    if (screenShake.intensity < 0.5) screenShake.intensity = 0;
  }
}

// Camera
function updateCamera() {
  const unit = getActiveUnit();
  if (unit && unit.alive) {
    camera.targetX = unit.x;
    camera.targetY = unit.y - 100;
    camera.targetZoom = Math.max(0.5, Math.min(1.2, 800 / terrainWidth));
  }
  
  camera.x += (camera.targetX - camera.x) * 0.05;
  camera.y += (camera.targetY - camera.y) * 0.05;
  camera.zoom += (camera.targetZoom - camera.zoom) * 0.03;
}

// Rendering
function render() {
  ctx.clearRect(0, 0, W, H);
  
  // Sky gradient
  const skyGrad = ctx.createLinearGradient(0, 0, 0, H);
  skyGrad.addColorStop(0, '#1a0533');
  skyGrad.addColorStop(0.4, '#2d1b69');
  skyGrad.addColorStop(0.7, '#4a2c8a');
  skyGrad.addColorStop(1, '#1a3a5c');
  ctx.fillStyle = skyGrad;
  ctx.fillRect(0, 0, W, H);
  
  // Stars
  ctx.save();
  for (let i = 0; i < 80; i++) {
    const sx = ((i * 137.5 + 50) % terrainWidth - camera.x * 0.3) / camera.zoom + W/2;
    const sy = ((i * 97.3 + 30) % (terrainHeight * 0.4)) / camera.zoom + H * 0.1;
    if (sx > 0 && sx < W && sy > 0 && sy < H * 0.5) {
      const brightness = 0.3 + Math.sin(Date.now() * 0.002 + i) * 0.2;
      ctx.fillStyle = `rgba(255,255,255,${brightness})`;
      ctx.fillRect(sx, sy, 1.5, 1.5);
    }
  }
  ctx.restore();
  
  if (impactFreeze > 0) { impactFreeze--; return; }
  
  ctx.save();
  ctx.translate(screenShake.x, screenShake.y);
  ctx.translate(W/2, H/2);
  ctx.scale(camera.zoom, camera.zoom);
  ctx.translate(-camera.x, -camera.y);
  
  // Draw terrain
  if (terrainCanvas) {
    ctx.drawImage(terrainCanvas, 0, 0);
  }
  
  // Water
  if (waterLevel > 0) {
    const waterGrad = ctx.createLinearGradient(0, waterLevel, 0, terrainHeight);
    waterGrad.addColorStop(0, 'rgba(30,100,200,0.4)');
    waterGrad.addColorStop(1, 'rgba(10,50,120,0.7)');
    ctx.fillStyle = waterGrad;
    ctx.fillRect(0, waterLevel, terrainWidth, terrainHeight - waterLevel);
    
    // Water surface waves
    ctx.strokeStyle = 'rgba(100,180,255,0.3)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    for (let x = 0; x < terrainWidth; x += 5) {
      const wy = waterLevel + Math.sin(x * 0.05 + Date.now() * 0.003) * 3;
      if (x === 0) ctx.moveTo(x, wy);
      else ctx.lineTo(x, wy);
    }
    ctx.stroke();
  }
  
  // Draw debris chunks
  for (const chunk of debrisChunks) {
    ctx.save();
    ctx.translate(chunk.x, chunk.y);
    ctx.rotate(chunk.rotation);
    ctx.fillStyle = '#8B7355';
    ctx.fillRect(-chunk.w/2, -chunk.h/2, chunk.w, chunk.h);
    ctx.restore();
  }
  
  // Draw units
  for (const team of teams) {
    for (const unit of team.units) {
      if (!unit.alive) continue;
      drawUnit(unit, team.color);
    }
  }
  
  // Draw projectiles
  for (const proj of projectiles) {
    if (!proj.alive) continue;
    
    // Trail
    if (proj.trail.length > 1) {
      ctx.strokeStyle = 'rgba(255,170,0,0.4)';
      ctx.lineWidth = 2;
      ctx.beginPath();
      for (let i = 0; i < proj.trail.length; i++) {
        if (i === 0) ctx.moveTo(proj.trail[i].x, proj.trail[i].y);
        else ctx.lineTo(proj.trail[i].x, proj.trail[i].y);
      }
      ctx.stroke();
    }
    
    // Projectile body
    const w = proj.weapon;
    if (proj.isDynamite) {
      ctx.fillStyle = '#8B4513';
      ctx.fillRect(proj.x - 6, proj.y - 10, 12, 20);
      ctx.fillStyle = '#ff4444';
      ctx.beginPath();
      ctx.arc(proj.x, proj.y - 12, 3, 0, Math.PI * 2);
      ctx.fill();
      // Fuse spark
      if (proj.timer < 60) {
        spawnParticles(proj.x + (Math.random()-0.5)*4, proj.y - 14, 1, '#ffaa00', 2, 10);
      }
    } else {
      ctx.fillStyle = w.color;
      ctx.beginPath();
      ctx.arc(proj.x, proj.y, w.id === 'nuke' ? 8 : 5, 0, Math.PI * 2);
      ctx.fill();
      ctx.fillStyle = '#fff';
      ctx.beginPath();
      ctx.arc(proj.x, proj.y, w.id === 'nuke' ? 4 : 2, 0, Math.PI * 2);
      ctx.fill();
    }
  }
  
  // Draw particles
  for (const p of particles) {
    const alpha = p.life / p.maxLife;
    ctx.globalAlpha = alpha;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size * alpha, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
  
  // Draw floating texts
  for (const ft of floatingTexts) {
    const alpha = ft.life / ft.maxLife;
    ctx.globalAlpha = alpha;
    ctx.fillStyle = ft.color;
    ctx.font = 'bold 18px Arial';
    ctx.textAlign = 'center';
    ctx.fillText(ft.text, ft.x, ft.y);
  }
  ctx.globalAlpha = 1;
  
  // Draw aiming trajectory
  if (isAiming && selectedWeapon && getActiveUnit() && !teams[currentTeamIdx].isCPU) {
    drawTrajectory();
  }
  
  ctx.restore();
}

function drawUnit(unit, color) {
  const bob = Math.sin(unit.bobTimer) * 2;
  const x = unit.x;
  const y = unit.y + bob;
  
  // Shadow
  ctx.fillStyle = 'rgba(0,0,0,0.3)';
  ctx.beginPath();
  ctx.ellipse(x, unit.y + unit.height/2 + 2, 14, 5, 0, 0, Math.PI * 2);
  ctx.fill();
  
  // Body
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.ellipse(x, y, 12, 16, 0, 0, Math.PI * 2);
  ctx.fill();
  
  // Outline
  ctx.strokeStyle = 'rgba(0,0,0,0.5)';
  ctx.lineWidth = 2;
  ctx.stroke();
  
  // Eyes
  const eyeOffX = unit.facing * 3;
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.arc(x + eyeOffX - 4, y - 4, 4, 0, Math.PI * 2);
  ctx.arc(x + eyeOffX + 4, y - 4, 4, 0, Math.PI * 2);
  ctx.fill();
  
  ctx.fillStyle = '#111';
  ctx.beginPath();
  ctx.arc(x + eyeOffX - 3 + unit.facing, y - 4, 2, 0, Math.PI * 2);
  ctx.arc(x + eyeOffX + 5 + unit.facing, y - 4, 2, 0, Math.PI * 2);
  ctx.fill();
  
  // Mouth
  if (unit.hp < 30) {
    ctx.strokeStyle = '#111';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.arc(x + eyeOffX, y + 4, 3, Math.PI, 0);
    ctx.stroke();
  } else {
    ctx.fillStyle = '#111';
    ctx.fillRect(x + eyeOffX - 2, y + 3, 4, 2);
  }
  
  // Hat/helmet indicator for active unit
  const isActive = getActiveUnit() === unit;
  if (isActive) {
    ctx.fillStyle = '#ffdd00';
    ctx.beginPath();
    ctx.moveTo(x - 8, y - 16);
    ctx.lineTo(x, y - 28);
    ctx.lineTo(x + 8, y - 16);
    ctx.fill();
    
    // Pulsing indicator
    const pulse = Math.sin(Date.now() * 0.005) * 0.3 + 0.7;
    ctx.strokeStyle = `rgba(255,221,0,${pulse})`;
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(x, y, 22, 0, Math.PI * 2);
    ctx.stroke();
  }
  
  // HP bar
  if (unit.hp < unit.maxHp) {
    const barW = 30;
    const hpRatio = unit.hp / unit.maxHp;
    ctx.fillStyle = 'rgba(0,0,0,0.6)';
    ctx.fillRect(x - barW/2, y - 24, barW, 5);
    ctx.fillStyle = hpRatio > 0.5 ? '#2ecc71' : hpRatio > 0.25 ? '#f39c12' : '#e74c3c';
    ctx.fillRect(x - barW/2, y - 24, barW * hpRatio, 5);
  }
  
  // Parachute visual
  if (unit.hasParachute && !unit.onGround) {
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x, y - 30, 15, Math.PI, 0);
    ctx.fill();
    ctx.strokeStyle = '#ccc';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(x - 15, y - 30);
    ctx.lineTo(x, y - 16);
    ctx.moveTo(x + 15, y - 30);
    ctx.lineTo(x, y - 16);
    ctx.stroke();
  }
  
  // Jetpack visual
  if (unit.hasJetpack) {
    ctx.fillStyle = '#666';
    ctx.fillRect(x - 8, y + 2, 5, 10);
    ctx.fillRect(x + 3, y + 2, 5, 10);
  }
  
  // Water indicator
  if (unit.inWater) {
    ctx.fillStyle = 'rgba(100,180,255,0.3)';
    ctx.beginPath();
    ctx.arc(x, y, 18, 0, Math.PI * 2);
    ctx.fill();
  }
}

function drawTrajectory() {
  const unit = getActiveUnit();
  if (!unit || !selectedWeapon) return;
  
  const power = aimPower > 0 ? aimPower : 50;
  const spd = power * 12 + selectedWeapon.speed * 3;
  let px = unit.x, py = unit.y - 20;
  let vx = Math.cos(aimAngle) * spd;
  let vy = Math.sin(aimAngle) * spd;
  
  ctx.setLineDash([5, 8]);
  ctx.strokeStyle = 'rgba(255,255,255,0.4)';
  ctx.lineWidth = 1.5;
  ctx.beginPath();
  ctx.moveTo(px, py);
  
  for (let i = 0; i < 60; i++) {
    vy += 0.4;
    vx += wind * 0.02;
    px += vx;
    py += vy;
    
    if (getTerrainPixel(Math.floor(px), Math.floor(py)) > 0) break;
    if (px < 0 || px > terrainWidth || py > terrainHeight) break;
    
    ctx.lineTo(px, py);
  }
  ctx.stroke();
  ctx.setLineDash([]);
  
  // AoE preview
  if (selectedWeapon.aoe) {
    ctx.strokeStyle = 'rgba(255,100,50,0.3)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(px, py, selectedWeapon.aoe, 0, Math.PI * 2);
    ctx.stroke();
  }
}

// HUD updates
function updateHUD() {
  const hudTop = document.getElementById('hud-top');
  hudTop.innerHTML = '';
  
  for (let t = 0; t < teams.length; t++) {
    const team = teams[t];
    const aliveCount = team.units.filter(u => u.alive).length;
    if (aliveCount === 0) continue;
    
    const div = document.createElement('div');
    div.className = 'team-info';
    div.style.borderLeft = `3px solid ${team.color}`;
    
    let html = `<div class="team-name" style="color:${team.color}">${team.name} ${team.isCPU ? '(CPU)' : ''}</div>`;
    html += '<div class="unit-hp">';
    for (const unit of team.units) {
      if (!unit.alive) continue;
      const hpPct = Math.max(0, unit.hp / unit.maxHp * 100);
      const hpColor = hpPct > 50 ? '#2ecc71' : hpPct > 25 ? '#f39c12' : '#e74c3c';
      html += `<span class="unit-label">W${teams[t].units.indexOf(unit)+1}</span>`;
      html += `<div class="hp-bar-mini"><div class="hp-bar-fill" style="width:${hpPct}%;background:${hpColor}"></div></div>`;
    }
    html += '</div>';
    div.innerHTML = html;
    hudTop.appendChild(div);
  }
  
  // Wind indicator
  const windArrow = document.getElementById('wind-arrow');
  const windVal = document.getElementById('wind-val');
  windVal.textContent = Math.abs(wind).toFixed(1);
  if (Math.abs(wind) < 0.3) {
    windArrow.style.transform = 'rotate(-90deg)';
    windArrow.textContent = '•';
  } else {
    windArrow.style.transform = `rotate(${wind > 0 ? 0 : 180}deg)`;
    windArrow.textContent = '→';
  }
}

function updateWeaponPanel() {
  const panel = document.getElementById('weapon-panel');
  panel.innerHTML = '';
  
  if (currentTeamIdx >= teams.length) return;
  
  const teamWeapons = teams[currentTeamIdx].weapons;
  for (const w of teamWeapons) {
    if (w.count <= 0) continue;
    
    const slot = document.createElement('div');
    slot.className = 'weapon-slot' + (selectedWeapon && selectedWeapon.id === w.id ? ' selected' : '');
    slot.innerHTML = `<span class="w-icon">${w.icon}</span><span class="w-name">${w.name}</span><span class="w-count">${w.count}</span>`;
    slot.onclick = () => {
      sfxClick();
      if (selectedWeapon && selectedWeapon.id === w.id) {
        selectedWeapon = null;
        isAiming = false;
        document.getElementById('power-meter').style.display = 'none';
        document.getElementById('power-text').style.display = 'none';
      } else {
        selectedWeapon = w;
        isAiming = true;
        aimAngle = -Math.PI / 4;
        if (w.type === 'direct' || w.type === 'instant') {
          document.getElementById('power-meter').style.display = 'none';
          document.getElementById('power-text').style.display = 'none';
        } else {
          document.getElementById('power-meter').style.display = 'block';
          document.getElementById('power-text').style.display = 'block';
        }
      }
      updateWeaponPanel();
    };
    panel.appendChild(slot);
  }
  
  // Utility items
  const teamUtils = teams[currentTeamIdx].utilities;
  for (const u of teamUtils) {
    if (u.count <= 0) continue;
    
    const slot = document.createElement('div');
    slot.className = 'weapon-slot';
    slot.innerHTML = `<span class="w-icon">${u.icon}</span><span class="w-name">${u.name}</span><span class="w-count">${u.count}</span>`;
    slot.onclick = () => {
      sfxClick();
      useUtility(u);
      u.count--;
      updateWeaponPanel();
    };
    panel.appendChild(slot);
  }
}

function useUtility(util) {
  const unit = getActiveUnit();
  if (!unit || !unit.alive) return;
  
  switch (util.id) {
    case 'parachute':
      unit.hasParachute = true;
      addFloatingText(unit.x, unit.y - 40, '🪂 Parachute!', '#fff');
      break;
    case 'jetpack':
      unit.hasJetpack = true;
      unit.jetpackFuel = 120;
      addFloatingText(unit.x, unit.y - 40, '🎒 Jetpack!', '#ffaa00');
      break;
    case 'extratime':
      turnTimeLeft += 15;
      addFloatingText(unit.x, unit.y - 40, '+15s', '#2ecc71');
      break;
  }
}

// Input handling
const keys = {};
let mouseX = 0, mouseY = 0;
let mouseDown = false;

document.addEventListener('keydown', e => {
  keys[e.code] = true;
  
  if (gameState !== 'playing') return;
  if (teams[currentTeamIdx]?.isCPU) return;
  
  const unit = getActiveUnit();
  if (!unit || !unit.alive) return;
  
  // Movement
  if (e.code === 'KeyA' || e.code === 'ArrowLeft') {
    unit.vx -= 5;
    unit.facing = -1;
  }
  if (e.code === 'KeyD' || e.code === 'ArrowRight') {
    unit.vx += 5;
    unit.facing = 1;
  }
  if ((e.code === 'KeyW' || e.code === 'ArrowUp' || e.code === 'Space') && unit.onGround) {
    unit.vy = -10;
    sfxJump();
  }
  
  // Utility keys
  if (e.code === 'Digit1') selectWeaponByIndex(0);
  if (e.code === 'Digit2') selectWeaponByIndex(1);
  if (e.code === 'Digit3') selectWeaponByIndex(2);
  if (e.code === 'Digit4') selectWeaponByIndex(3);
  if (e.code === 'Digit5') selectWeaponByIndex(4);
  if (e.code === 'Digit6') selectWeaponByIndex(5);
  if (e.code === 'Digit7') selectWeaponByIndex(6);
});

document.addEventListener('keyup', e => { keys[e.code] = false; });

canvas.addEventListener('mousemove', e => {
  mouseX = e.clientX;
  mouseY = e.clientY;
  
  if (isAiming && getActiveUnit() && !teams[currentTeamIdx]?.isCPU) {
    const unit = getActiveUnit();
    const worldX = (e.clientX - W/2) / camera.zoom + camera.x;
    const worldY = (e.clientY - H/2) / camera.zoom + camera.y;
    aimAngle = Math.atan2(worldY - (unit.y - 20), worldX - unit.x);
    // Clamp angle to upward
    if (aimAngle > 0.1) aimAngle = 0.1;
    if (aimAngle < -Math.PI + 0.1) aimAngle = -Math.PI + 0.1;
  }
});

canvas.addEventListener('mousedown', e => {
  mouseDown = true;
  if (isAiming && selectedWeapon && !teams[currentTeamIdx]?.isCPU) {
    powerCharging = true;
    aimPower = 0;
  }
});

canvas.addEventListener('mouseup', e => {
  mouseDown = false;
  if (powerCharging && isAiming && selectedWeapon && getActiveUnit() && !teams[currentTeamIdx]?.isCPU) {
    powerCharging = false;
    fireWeapon(selectedWeapon, aimAngle, aimPower / 100);
    isAiming = false;
    selectedWeapon = null;
    document.getElementById('power-meter').style.display = 'none';
    document.getElementById('power-text').style.display = 'none';
    updateWeaponPanel();
  }
});

function selectWeaponByIndex(idx) {
  if (currentTeamIdx >= teams.length || teams[currentTeamIdx].isCPU) return;
  const teamWeapons = teams[currentTeamIdx].weapons;
  if (idx < teamWeapons.length && teamWeapons[idx].count > 0) {
    selectedWeapon = teamWeapons[idx];
    isAiming = true;
    sfxClick();
    updateWeaponPanel();
  }
}

// AI System
function cpuTurn() {
  const unit = getActiveUnit();
  if (!unit || !unit.alive) { endTurn(); return; }
  
  // Find nearest enemy
  let nearestEnemy = null;
  let nearestDist = Infinity;
  
  for (const team of teams) {
    if (team === teams[currentTeamIdx]) continue;
    for (const eu of team.units) {
      if (!eu.alive) continue;
      const d = Math.hypot(eu.x - unit.x, eu.y - unit.y);
      if (d < nearestDist) {
        nearestDist = d;
        nearestEnemy = eu;
      }
    }
  }
  
  if (!nearestEnemy) { endTurn(); return; }
  
  // Risk assessment: check for hazards
  const groundBelow = getTerrainPixel(Math.floor(unit.x), Math.floor(unit.y + unit.height/2 + 5));
  const nearWater = waterLevel > 0 && unit.y + unit.height/2 > waterLevel - 30;
  
  // Move away from danger if needed
  if (nearWater || groundBelow === 0) {
    const moveDir = unit.x < terrainWidth / 2 ? 1 : -1;
    unit.vx += moveDir * 4;
    setTimeout(() => cpuAttack(nearestEnemy), 500);
    return;
  }
  
  // Try to get closer if far away
  if (nearestDist > 400) {
    const dir = nearestEnemy.x > unit.x ? 1 : -1;
    unit.vx += dir * 3;
    setTimeout(() => cpuAttack(nearestEnemy), 500);
    return;
  }
  
  cpuAttack(nearestEnemy);
}

function cpuAttack(target) {
  const unit = getActiveUnit();
  if (!unit || !unit.alive) { endTurn(); return; }
  
  // Select weapon based on distance and situation
  const dist = Math.hypot(target.x - unit.x, target.y - unit.y);
  const teamWeapons = teams[currentTeamIdx].weapons.filter(w => w.count > 0);
  
  if (teamWeapons.length === 0) { endTurn(); return; }
  
  let weapon;
  if (dist < 200) {
    // Close range - use direct weapons
    const direct = teamWeapons.find(w => w.type === 'direct');
    weapon = direct || teamWeapons[0];
  } else if (dist > 600) {
    // Long range - use nuke or rocket
    const longRange = teamWeapons.find(w => w.id === 'nuke' || w.id === 'rocket');
    weapon = longRange || teamWeapons[0];
  } else {
    weapon = teamWeapons[Math.floor(Math.random() * teamWeapons.length)];
  }
  
  // Calculate aim angle and power
  const dx = target.x - unit.x;
  const dy = target.y - unit.y;
  let angle, power;
  
  if (weapon.type === 'direct' || weapon.type === 'instant') {
    angle = Math.atan2(dy, dx);
    power = 70 + Math.random() * 30;
  } else {
    // Parabolic trajectory
    const distAbs = Math.abs(dx);
    power = 40 + (distAbs / terrainWidth) * 50 + Math.random() * 15;
    
    if (dx > 0) {
      angle = -Math.PI/6 - Math.random() * Math.PI/4;
    } else {
      angle = -Math.PI*5/6 + Math.random() * Math.PI/4;
    }
  }
  
  // Add some inaccuracy for realism
  angle += (Math.random() - 0.5) * 0.1;
  
  fireWeapon(weapon, angle, power);
  
  setTimeout(() => endTurn(), 2000 + Math.random() * 1000);
}

// Turn timer
function updateTurnTimer() {
  if (!turnTimerActive || gameState !== 'playing') return;
  
  turnTimeLeft -= 1/60;
  
  const pct = Math.max(0, turnTimeLeft / 25 * 100);
  document.getElementById('turn-timer-fill').style.width = pct + '%';
  document.getElementById('turn-timer-text').textContent = Math.ceil(turnTimeLeft);
  
  // Escalating tension - timer scales up when low
  const container = document.getElementById('turn-timer-container');
  if (turnTimeLeft < 5) {
    const scale = 1 + (5 - turnTimeLeft) * 0.1;
    container.style.transform = `translate(-50%, -50%) scale(${scale})`;
    
    // Tick sound when low
    if (Math.floor(turnTimeLeft * 2) !== Math.floor((turnTimeLeft + 1/60) * 2)) {
      sfxTick();
    }
  } else {
    container.style.transform = 'translate(-50%, -50%) scale(1)';
  }
  
  if (turnTimeLeft <= 0) {
    endTurn();
  }
}

// Power meter update
function updatePowerMeter() {
  if (powerCharging && isAiming) {
    aimPower = Math.min(100, aimPower + 1.5);
    document.getElementById('power-fill').style.height = aimPower + '%';
  } else if (!mouseDown && !isAiming) {
    document.getElementById('power-fill').style.height = '0%';
  }
}

// Main game loop
function gameLoop() {
  requestAnimationFrame(gameLoop);
  
  if (gameState !== 'playing') return;
  
  updatePhysics(1/60);
  updateCamera();
  updateTurnTimer();
  updatePowerMeter();
  render();
}

// Menu functions
function showSetup() {
  sfxClick();
  document.getElementById('menu-screen').style.display = 'none';
  document.getElementById('setup-screen').style.display = 'flex';
}

function backToMenu() {
  sfxClick();
  gameState = 'menu';
  document.getElementById('game-over-screen').style.display = 'none';
  document.getElementById('setup-screen').style.display = 'none';
  document.getElementById('menu-screen').style.display = 'flex';
  document.getElementById('turn-timer-container').style.display = 'none';
}

function startGame() {
  sfxClick();
  gameState = 'playing';
  document.getElementById('setup-screen').style.display = 'none';
  initGame();
}

// Start the loop
gameLoop();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"basic","sessionId":"6542bf2b-d084-4311-b68e-dcdbf35ceb85","tokensIn":542,"tokensOut":18288,"tokensTotal":18830,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-19T02:11:54.072Z"} -->