← Worms results

Worms

thinkingcap qwen3.6 27b · middle

Initial view of Worms

Browser check: Artifact runtime error. This is a technical check, not a quality review.

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 39,378 bytes · SHA-256 dbf7d3a886d8
<!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',system-ui,sans-serif;color:#fff;user-select:none}
canvas{display:block}
#ui-overlay{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10}
#ui-overlay>*{pointer-events:auto}
.screen{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(5,5,20,0.97);transition:opacity 0.4s;z-index:100}
.screen.hidden{opacity:0;pointer-events:none}
h1{font-size:3.5em;text-shadow:0 0 30px #ff6b35;margin-bottom:10px;background:linear-gradient(135deg,#ff6b35,#ffd700);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
h2{font-size:1.4em;color:#aaa;margin-bottom:12px;font-weight:normal}
.btn{padding:12px 32px;font-size:1.1em;border:2px solid rgba(255,255,255,0.2);border-radius:10px;background:rgba(255,255,255,0.06);color:#fff;cursor:pointer;margin:5px;transition:all 0.2s;font-family:inherit}
.btn:hover{background:rgba(255,255,255,0.18);border-color:rgba(255,255,255,0.5);transform:scale(1.04)}
.btn.selected{background:rgba(255,107,53,0.35);border-color:#ff6b35}
.team-select{display:flex;gap:8px;margin:8px 0;flex-wrap:wrap;justify-content:center}
#hud-top{position:absolute;top:0;left:0;right:0;display:none;justify-content:center;gap:10px;padding:8px 16px;background:linear-gradient(180deg,rgba(0,0,0,0.75),transparent);pointer-events:none}
.team-hud{display:flex;align-items:center;gap:5px;padding:5px 12px;border-radius:8px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08)}
.team-hud.active{border-color:#ff6b35;box-shadow:0 0 12px rgba(255,107,53,0.25)}
.unit-bar{width:32px;height:6px;border-radius:3px;background:rgba(255,255,255,0.1);overflow:hidden}
.unit-fill{height:100%;border-radius:3px;transition:width 0.3s}
#hud-bottom{position:absolute;bottom:0;left:0;right:0;display:none;align-items:center;justify-content:center;padding:10px;background:linear-gradient(0deg,rgba(0,0,0,0.85),transparent);gap:6px}
.weapon-btn{padding:6px 12px;border-radius:7px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.04);color:#fff;cursor:pointer;font-size:0.85em;display:flex;flex-direction:column;align-items:center;gap:1px;transition:all 0.15s}
.weapon-btn:hover{background:rgba(255,255,255,0.12)}
.weapon-btn.active{border-color:#ff6b35;background:rgba(255,107,53,0.18);box-shadow:0 0 8px rgba(255,107,53,0.25)}
.weapon-icon{font-size:1.3em}
#turn-timer-bar{position:absolute;top:56px;left:50%;transform:translateX(-50%);width:180px;height:5px;border-radius:3px;background:rgba(255,255,255,0.08);overflow:hidden;display:none}
#turn-timer-fill{height:100%;border-radius:3px;transition:background 0.3s}
#wind-indicator{position:absolute;top:70px;left:50%;transform:translateX(-50%);font-size:0.75em;color:#888;display:none;align-items:center;gap:5px}
#action-btns{display:flex;gap:5px;margin-left:10px}
.action-btn{padding:6px 12px;border-radius:7px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.04);color:#fff;cursor:pointer;font-size:0.8em;transition:all 0.15s}
.action-btn:hover{background:rgba(255,255,255,0.12)}
#power-meter{position:absolute;bottom:65px;left:50%;transform:translateX(-50%);width:280px;height:14px;border-radius:7px;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.15);overflow:hidden;display:none}
#power-fill{height:100%;border-radius:7px;transition:width 0.03s}
#turn-label{position:absolute;top:84px;left:50%;transform:translateX(-50%);font-size:0.9em;color:#ccc;display:none;text-align:center}
#game-over-screen h1{font-size:2.8em}
.score-row{display:flex;align-items:center;gap:8px;padding:6px 16px;margin:3px;border-radius:7px;background:rgba(255,255,255,0.04);min-width:220px}
.team-config{display:flex;flex-direction:column;align-items:center;gap:8px;margin:10px 0;width:90%;max-width:500px}
.config-row{display:flex;align-items:center;justify-content:center;gap:10px;padding:6px 12px;border-radius:8px;background:rgba(255,255,255,0.04);width:100%}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="ui-overlay">
  <div id="menu-screen" class="screen">
    <h1>🪱 WORM WARFARE</h1>
    <h2>Terrain Shape</h2>
    <div class="team-select" id="terrain-shape"></div>
    <h2 style="margin-top:15px">Teams (click to toggle CPU)</h2>
    <div class="team-config" id="team-config"></div>
    <button class="btn" onclick="startGame()" style="margin-top:20px;font-size:1.3em;padding:14px 50px">⚔️ START BATTLE</button>
  </div>
  <div id="hud-top"></div>
  <div id="turn-timer-bar"><div id="turn-timer-fill"></div></div>
  <div id="wind-indicator">💨 Wind: <span id="wind-val">0</span></div>
  <div id="turn-label"></div>
  <div id="hud-bottom">
    <div id="weapon-list"></div>
    <div id="action-btns">
      <button class="action-btn" onclick="doMove()">🏃 Move</button>
      <button class="action-btn" onclick="skipTurn()">⏭ Skip</button>
    </div>
  </div>
  <div id="power-meter"><div id="power-fill"></div></div>
  <div id="game-over-screen" class="screen hidden">
    <h1>BATTLE OVER!</h1>
    <div id="score-list"></div>
    <button class="btn" onclick="location.reload()" style="margin-top:18px;font-size:1.2em;padding:12px 40px">🔄 PLAY AGAIN</button>
  </div>
</div>

<script>
// ==================== AUDIO ENGINE ====================
const AudioCtx=window.AudioContext||window.webkitAudioContext;
let audioCtx;
function initAudio(){if(!audioCtx)try{audioCtx=new AudioCtx()}catch(e){}}
function playSound(type){
  if(!audioCtx)return;
  try{
    const t=audioCtx.currentTime;const g=audioCtx.createGain();g.connect(audioCtx.destination);
    switch(type){
      case'click':{const o=audioCtx.createOscillator();o.type='sine';o.frequency.setValueAtTime(800,t);o.frequency.exponentialRampToValueAtTime(400,t+0.1);g.gain.setValueAtTime(0.12,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.1);o.connect(g);o.start(t);o.stop(t+0.1);break}
      case'explosion':{const buf=audioCtx.createBuffer(1,audioCtx.sampleRate*0.5,audioCtx.sampleRate);const d=buf.getChannelData(0);for(let i=0;i<d.length;i++)d[i]=(Math.random()*2-1)*Math.pow(1-i/d.length,2);const n=audioCtx.createBufferSource();n.buffer=buf;g.gain.setValueAtTime(0.35,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.5);n.connect(g);n.start(t);break}
      case'smallExpl':{const buf=audioCtx.createBuffer(1,audioCtx.sampleRate*0.2,audioCtx.sampleRate);const d=buf.getChannelData(0);for(let i=0;i<d.length;i++)d[i]=(Math.random()*2-1)*Math.pow(1-i/d.length,3);const n=audioCtx.createBufferSource();n.buffer=buf;g.gain.setValueAtTime(0.18,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.2);n.connect(g);n.start(t);break}
      case'hit':{const o=audioCtx.createOscillator();o.type='square';o.frequency.setValueAtTime(300,t);o.frequency.exponentialRampToValueAtTime(80,t+0.15);g.gain.setValueAtTime(0.15,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.15);o.connect(g);o.start(t);o.stop(t+0.15);break}
      case'jump':{const o=audioCtx.createOscillator();o.type='sine';o.frequency.setValueAtTime(200,t);o.frequency.exponentialRampToValueAtTime(600,t+0.12);g.gain.setValueAtTime(0.08,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);o.connect(g);o.start(t);o.stop(t+0.12);break}
      case'laser':{const o=audioCtx.createOscillator();o.type='sawtooth';o.frequency.setValueAtTime(2000,t);o.frequency.exponentialRampToValueAtTime(200,t+0.3);g.gain.setValueAtTime(0.12,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.3);o.connect(g);o.start(t);o.stop(t+0.3);break}
      case'water':{const buf=audioCtx.createBuffer(1,audioCtx.sampleRate*0.3,audioCtx.sampleRate);const d=buf.getChannelData(0);for(let i=0;i<d.length;i++)d[i]=(Math.random()*2-1)*Math.pow(1-i/d.length,1.5)*0.3;const n=audioCtx.createBufferSource();n.buffer=buf;g.gain.setValueAtTime(0.1,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.3);n.connect(g);n.start(t);break}
      case'countdown':{const o=audioCtx.createOscillator();o.type='triangle';o.frequency.setValueAtTime(600,t);g.gain.setValueAtTime(0.1,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.08);o.connect(g);o.start(t);o.stop(t+0.08);break}
      case'death':{const o=audioCtx.createOscillator();o.type='sawtooth';o.frequency.setValueAtTime(400,t);o.frequency.exponentialRampToValueAtTime(50,t+0.6);g.gain.setValueAtTime(0.2,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.6);o.connect(g);o.start(t);o.stop(t+0.6);break}
      case'whoosh':{const o=audioCtx.createOscillator();o.type='sine';o.frequency.setValueAtTime(300,t);o.frequency.exponentialRampToValueAtTime(1200,t+0.08);g.gain.setValueAtTime(0.06,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.08);o.connect(g);o.start(t);o.stop(t+0.08);break}
    }
  }catch(e){}
}

// ==================== CONSTANTS ====================
const GRAVITY=0.35,WORM_RADIUS=7,MAX_HP=100,TURN_TIME=25;
const TEAM_COLORS=['#ff4444','#44aaff','#44dd44','#ffaa00'];
const TEAM_NAMES=['Red','Blue','Green','Gold'];

// ==================== WEAPONS ====================
const WEAPONS=[
  {id:'grenade',name:'Grenade',icon:'💣',cat:'indirect',power:35,aoe:40,speed:8,damage:25},
  {id:'rocket',name:'Rocket',icon:'🚀',cat:'indirect',power:50,aoe:60,speed:12,damage:40},
  {id:'missile',name:'Missile',icon:'☄️',cat:'indirect',power:60,aoe:80,speed:10,damage:55},
  {id:'shotgun',name:'Shotgun',icon:'🔫',cat:'direct',power:20,aoe:30,speed:25,damage:15,count:7,spread:0.4},
  {id:'laser',name:'Laser',icon:'⚡',cat:'direct',power:80,aoe:15,speed:999,damage:35},
  {id:'cluster',name:'Cluster',icon:'💥',cat:'indirect',power:45,aoe:70,speed:9,damage:20,subBombs:6},
  {id:'nuke',name:'Nuke',icon:'☢️',cat:'aoe',power:100,aoe:150,speed:8,damage:80}
];

// ==================== GAME STATE ====================
let canvas,ctx,W,H;
let terrainCanvas,terrainCtx,terrainData;
let gameState='menu';
let teams=[],activeTeam=0,activeUnit=0;
let projectiles=[],particles=[],floatingTexts=[];
let wind=0,camera={x:0,y:0,zoom:1,targetZoom:1};
let turnTimer=TURN_TIME,turnTimerMax=TURN_TIME;
let selectedWeapon=0,powerLevel=50,aimAngle=-45;
let isCharging=false,chargeDir=1;
let screenShake={x:0,y:0,intensity:0};
let impactFreeze=0;
let terrainShape='island',teamCount=2,isCPU=[];
let waterLevel=0,debris=[];
let countdownTimer=0;

// ==================== INIT ====================
function init(){
  canvas=document.getElementById('gameCanvas');ctx=canvas.getContext('2d');
  resize();window.addEventListener('resize',resize);setupMenu();requestAnimationFrame(gameLoop);
}
function resize(){W=window.innerWidth;H=window.innerHeight;canvas.width=W;canvas.height=H;}

// ==================== MENU ====================
function setupMenu(){
  const ts=document.getElementById('terrain-shape');ts.innerHTML='';
  ['island','twoIslands','cave'].forEach(s=>{
    const b=document.createElement('button');b.className='btn'+(s===terrainShape?' selected':'');
    b.textContent=s==='island'?'🏝️ Island':s==='twoIslands'?'🌊 Two Islands':'🕳️ Cave';
    b.onclick=()=>{terrainShape=s;setupMenu()};ts.appendChild(b);
  });
  const tc=document.getElementById('team-config');tc.innerHTML='';
  for(let i=0;i<4;i++){
    const row=document.createElement('div');row.className='config-row';
    const dot=document.createElement('span');dot.style.cssText=`display:inline-block;width:18px;height:18px;border-radius:50%;background:${TEAM_COLORS[i]};border:2px solid rgba(255,255,255,0.3)`;
    const name=document.createElement('b');name.textContent=TEAM_NAMES[i];
    const btn=document.createElement('button');btn.className='btn'+((i<teamCount&&isCPU[i])?' selected':'');
    btn.textContent=i<teamCount?(isCPU[i]?'🤖 CPU':'👤 Human'):'—';
    btn.style.fontSize='0.85em';btn.style.padding='6px 14px';
    if(i>=teamCount){btn.disabled=true;btn.style.opacity='0.3'}
    else{btn.onclick=()=>{isCPU[i]=!isCPU[i];setupMenu()}}
    row.appendChild(dot);row.appendChild(name);row.appendChild(btn);tc.appendChild(row);
  }
}

// ==================== TERRAIN GENERATION ====================
function generateTerrain(){
  const tw=Math.floor(W*2.5),th=Math.floor(H*1.8);
  terrainCanvas=document.createElement('canvas');terrainCanvas.width=tw;terrainCanvas.height=th;
  terrainCtx=terrainCanvas.getContext('2d');
  // Sky gradient
  const skyGrad=terrainCtx.createLinearGradient(0,0,0,th);
  skyGrad.addColorStop(0,'#0a0520');skyGrad.addColorStop(0.3,'#1a0a3e');
  skyGrad.addColorStop(0.6,'#4a1942');skyGrad.addColorStop(0.85,'#ff6b35');skyGrad.addColorStop(1,'#ffd700');
  terrainCtx.fillStyle=skyGrad;terrainCtx.fillRect(0,0,tw,th);
  // Stars
  for(let i=0;i<250;i++){const sx=Math.random()*tw,sy=Math.random()*th*0.45,r=Math.random()*1.5+0.3;
    terrainCtx.fillStyle=`rgba(255,255,255,${Math.random()*0.7+0.3})`;terrainCtx.beginPath();terrainCtx.arc(sx,sy,r,0,Math.PI*2);terrainCtx.fill()}
  // Moon
  const moonX=tw*0.8,moonY=th*0.1;
  const moonGrad=terrainCtx.createRadialGradient(moonX,moonY,0,moonX,moonY,40);
  moonGrad.addColorStop(0,'rgba(255,255,230,0.9)');moonGrad.addColorStop(0.5,'rgba(255,255,200,0.3)');moonGrad.addColorStop(1,'rgba(255,255,200,0)');
  terrainCtx.fillStyle=moonGrad;terrainCtx.beginPath();terrainCtx.arc(moonX,moonY,40,0,Math.PI*2);terrainCtx.fill();

  waterLevel=th*0.78;
  if(terrainShape==='island')generateIsland(tw,th,false);
  else if(terrainShape==='twoIslands')generateIsland(tw,th,true);
  else generateCave(tw,th);
  terrainData=terrainCtx.getImageData(0,0,tw,th);
}

function noise(x,s){return Math.sin(x*0.01+s)*0.5+Math.sin(x*0.023+s*2)*0.3+Math.sin(x*0.047+s*3)*0.2}

function generateIsland(tw,th,double){
  const seed=Math.random()*100;baseH=th*0.5;
  for(let x=0;x<tw;x++){
    let h=noise(x,seed)*90+noise(x*2,seed+50)*35+noise(x*4,seed+100)*12;
    if(double){const mid=tw/2,dist=Math.abs(x-mid);h-=Math.max(0,(mid-dist)/mid)*70}
    const topY=baseH-h;
    // Multi-layer terrain with gradient
    for(let y=topY;y<th;y++){
      const depth=y-topY;let r,g,b;
      if(depth<8){r=90+Math.random()*20;g=140+Math.random()*30;b=50} // grass
      else if(depth<30){r=139+Math.random()*15;g=105+Math.random()*10;b=20} // dirt
      else{r=80+Math.random()*20;g=60+Math.random()*15;b=30+Math.random()*10} // rock
      terrainCtx.fillStyle=`rgb(${r|0},${g|0},${b|0})`;terrainCtx.fillRect(x,y,1,1);
    }
  }
  drawWater(tw,th);
}

function generateCave(tw,th){
  const seed=Math.random()*100;
  // Fill with rock gradient
  for(let y=0;y<th;y++){for(let x=0;x<tw;x++){
    const d=y/th;r=50+d*30+Math.random()*10|0;g=40+d*25+Math.random()*8|0;b=30+d*20+Math.random()*6|0;
    terrainCtx.fillStyle=`rgb(${r},${g},${b})`;terrainCtx.fillRect(x,y,1,1);
  }}
  // Carve cave
  for(let y=th*0.15;y<th*0.85;y++){
    const leftEdge=noise(y,seed)*tw*0.12+tw*0.25;
    const rightEdge=noise(y,seed+50)*tw*0.12+tw*0.75;
    for(let x=leftEdge;x<rightEdge;x++){terrainCtx.clearRect(x,y,1,1)}
  }
  drawWater(tw,th);
}

function drawWater(tw,th){
  const wg=terrainCtx.createLinearGradient(0,waterLevel-30,0,th);
  wg.addColorStop(0,'rgba(25,70,140,0.6)');wg.addColorStop(0.5,'rgba(15,50,110,0.85)');wg.addColorStop(1,'rgba(8,30,80,0.95)');
  terrainCtx.fillStyle=wg;terrainCtx.fillRect(0,waterLevel,tw,th-waterLevel);
}

function isTerrain(px,py){
  if(px<0||px>=terrainCanvas.width||py<0||py>=terrainCanvas.height)return true;
  const d=terrainData.data,idx=(Math.floor(py)*terrainCanvas.width+Math.floor(px))*4;
  return d[idx+3]>128;
}

function destroyTerrain(cx,cy,aoe){
  const r=Math.ceil(aoe);const x0=Math.max(0,cx-r),x1=Math.min(terrainCanvas.width-1,cx+r);
  const y0=Math.max(0,cy-r),y1=Math.min(terrainCanvas.height-1,cy+r);
  for(let y=y0;y<=y1;y++){for(let x=x0;x<=x1;x++){
    if((x-cx)*(x-cx)+(y-cy)*(y-cy)<r*r)terrainCtx.clearRect(x,y,1,1)}}
  terrainData=terrainCtx.getImageData(0,0,terrainCanvas.width,terrainCanvas.height);
}

// ==================== TEAMS & UNITS ====================
function createTeams(){
  teams=[];
  for(let i=0;i<teamCount;i++){
    const team={color:TEAM_COLORS[i],name:TEAM_NAMES[i],units:[],alive:true,isCPU:isCPU[i]||false};
    const spacing=terrainCanvas.width/(teamCount+1);const sx=spacing*(i+1);
    let sy=0;for(let y=0;y<terrainCanvas.height;y++){if(isTerrain(sx,y)){sy=y-2;break}}
    for(let j=0;j<4;j++){
      const ux=sx+(j-1.5)*35;let uy=sy;
      for(let y=sy;y>0;y--){if(!isTerrain(ux,y))uy=y}
      team.units.push({x:ux,y:uy,hp:MAX_HP,alive:true,vy:0,vx:0,onGround:false,fallStartY:uy,inWater:false,drowning:0});
    }
    teams.push(team);
  }
}

function getTeamOfUnit(unit){for(let i=0;i<teams.length;i++)if(teams[i].units.includes(unit))return i;return -1;}

// ==================== PHYSICS ====================
function updateUnit(unit){
  unit.vy+=GRAVITY;unit.x+=unit.vx;unit.y+=unit.vy;unit.vx*=0.92;
  const feetY=Math.floor(unit.y+WORM_RADIUS);const lx=Math.floor(unit.x-WORM_RADIUS+1),rx=Math.floor(unit.x+WORM_RADIUS-1);
  if(isTerrain(lx,feetY)||isTerrain(rx,feetY)){
    let landY=unit.y;for(let y=Math.floor(unit.y-WORM_RADIUS);y<Math.floor(unit.y+WORM_RADIUS+5);y++){if(!isTerrain(Math.floor(unit.x),y))landY=y;else break}
    const fallDist=unit.y-unit.fallStartY;
    if(fallDist>100&&unit.onGround){const dmg=Math.min(40,Math.floor((fallDist-100)/5));if(dmg>0){unit.hp-=dmg;addFloatingText(unit.x,unit.y-20,'-'+dmg,'#ff6644');playSound('hit')}}
    unit.y=landY-WORM_RADIUS;unit.vy=0;if(!unit.onGround){unit.fallStartY=unit.y;spawnParticles(unit.x,unit.y+WORM_RADIUS,6,'#aa8855',2)}
    unit.onGround=true;
  }else{unit.onGround=false}
  if(unit.y+WORM_RADIUS>waterLevel){
    unit.inWater=true;unit.vy*=0.7;unit.vx*=0.85;
    if(gameState==='weaponSelect'||gameState==='turnSelect'){unit.drowning+=0.001;if(unit.drowning>2){unit.hp-=0.3}}else{unit.drowning=0}
  }else{unit.inWater=false;unit.drowning=0}
  if(unit.x<20){unit.x=20;unit.vx*=-0.5}if(unit.x>terrainCanvas.width-20){unit.x=terrainCanvas.width-20;unit.vx*=-0.5}
  if(unit.y>terrainCanvas.height+100)killUnit(unit);
  if(unit.hp<=0&&unit.alive)killUnit(unit);
}

function killUnit(unit){
  unit.alive=false;playSound('death');spawnParticles(unit.x,unit.y,35,'#ff6b35',6);addFloatingText(unit.x,unit.y-20,'💀','#fff');
  const ti=getTeamOfUnit(unit);if(ti>=0)teams[ti].alive=teams[ti].units.some(u=>u.alive);
}

// ==================== PROJECTILES ====================
function fireProjectile(teamIdx){
  const unit=teams[teamIdx].units[activeUnit];const w=WEAPONS[selectedWeapon];
  const power=w.power*(powerLevel||50)/50;const rad=(aimAngle||(-45))*Math.PI/180;
  if(w.cat==='direct'){
    if(w.id==='laser')fireLaser(unit,rad);
    else if(w.id==='shotgun')fireShotgun(unit,rad,w.count||5,w.spread||0.3,power);
  }else{
    const px=unit.x+Math.cos(rad)*15;const py=unit.y-12+Math.sin(rad)*15;
    projectiles.push({x:px,y:py,vx:Math.cos(rad)*power*0.3,vy:Math.sin(rad)*power*0.3,aoe:w.aoe,damage:w.damage,weaponId:w.id,subBombs:w.subBombs||0,trail:[],ownerTeam:teamIdx});
  }
  playSound(w.cat==='direct'?'laser':'whoosh');gameState='firing';
}

function fireLaser(unit,angle){
  const w=WEAPONS[selectedWeapon];let dx=Math.cos(angle),dy=-Math.abs(Math.sin(angle));
  let lx=unit.x,ly=unit.y-15;const steps=800,stepLen=3;
  for(let i=0;i<steps;i++){lx+=dx*stepLen;ly+=dy*stepLen;
    if(isTerrain(Math.floor(lx),Math.floor(ly))){destroyTerrain(lx,ly,w.aoe);damageUnitsInArea(lx,ly,w.aoe,w.damage,getTeamOfUnit(unit));spawnParticles(lx,ly,20,'#ff4444',4);screenShake.intensity=8;impactFreeze=5;playSound('explosion');break}}
  for(let i=0;i<steps;i+=3)particles.push({x:lx-dx*stepLen*i/3,y:ly-dy*stepLen*i/3,vx:0,vy:0,life:12,maxLife:12,size:2,color:'#ff4444'});
}

function fireShotgun(unit,angle,count,spread,power){
  const w=WEAPONS[selectedWeapon];const ti=getTeamOfUnit(unit);
  for(let i=0;i<count;i++){const a=angle+(Math.random()-0.5)*spread;
    projectiles.push({x:unit.x,y:unit.y-15,vx:Math.cos(a)*power*0.25,vy:Math.sin(a)*power*0.25,aoe:w.aoe,damage:w.damage/count,weaponId:'shotgun',trail:[],subBombs:0,ownerTeam:ti});
  }
}

function updateProjectiles(){
  for(let i=projectiles.length-1;i>=0;i--){const p=projectiles[i];
    p.vx+=wind*0.002;p.vy+=GRAVITY*0.5;p.x+=p.vx;p.y+=p.vy;
    p.trail.push({x:p.x,y:p.y,life:10});if(p.trail.length>30)p.trail.shift();p.trail.forEach(t=>t.life--);p.trail=p.trail.filter(t=>t.life>0);
    if(isTerrain(Math.floor(p.x),Math.floor(p.y))){explodeProjectile(p,i);continue}
    if(p.x<-100||p.x>terrainCanvas.width+100||p.y>terrainCanvas.height+100){projectiles.splice(i,1);continue}
  }
}

function explodeProjectile(p,idx){
  destroyTerrain(p.x,p.y,p.aoe);damageUnitsInArea(p.x,p.y,p.aoe,p.damage,p.ownerTeam!==undefined?p.ownerTeam:activeTeam);
  spawnParticles(p.x,p.y,25+Math.floor(p.aoe/3),'#ff8844',6);screenShake.intensity=Math.min(15,p.aoe*0.2);impactFreeze=Math.min(10,p.aoe*0.1);
  playSound(p.aoe>60?'explosion':'smallExpl');spawnDebris(p.x,p.y,p.aoe);
  if(p.subBombs){for(let i=0;i<p.subBombs;i++){const a=Math.random()*Math.PI*2,sp=Math.random()*4+2;
    projectiles.push({x:p.x,y:p.y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-3,aoe:p.aoe*0.4,damage:p.damage*0.5,weaponId:'cluster',trail:[],subBombs:0,ownerTeam:p.ownerTeam})}}
  projectiles.splice(idx,1);
}

function damageUnitsInArea(cx,cy,aoe,damage,excludeTeam){
  for(let i=0;i<teams.length;i++){if(i===excludeTeam)continue;
    teams[i].units.forEach(u=>{if(!u.alive)return;const dx=u.x-cx,dy=u.y-cy,dist=Math.sqrt(dx*dx+dy*dy);
      if(dist<aoe){const f=1-dist/aoe;u.hp-=Math.floor(damage*f);addFloatingText(u.x,u.y-20,'-'+Math.floor(damage*f),'#ff4444');if(dist>0){u.vx+=dx/dist*5;u.vy+=dy/dist*-3}}});
  }
}

// ==================== DEBRIS & PARTICLES ====================
function spawnDebris(cx,cy,aoe){const n=Math.floor(aoe/8);for(let i=0;i<n;i++){
  debris.push({x:cx+(Math.random()-0.5)*aoe,y:cy+(Math.random()-0.5)*aoe*0.5,vx:(Math.random()-0.5)*3,vy:-Math.random()*4-1,size:Math.random()*4+2,life:60+Math.random()*60,color:`hsl(${30+Math.random()*20},${40+Math.random()*30}%,${20+Math.random()*30}%)`});
}}

function updateDebris(){for(let i=debris.length-1;i>=0;i--){const d=debris[i];d.vy+=GRAVITY*0.5;d.x+=d.vx;d.y+=d.vy;
  if(isTerrain(Math.floor(d.x),Math.floor(d.y))){d.vy*=-0.3;d.vx*=0.5}d.life--;if(d.life<=0)debris.splice(i,1)}}

function spawnParticles(x,y,count,color,speed){for(let i=0;i<count;i++){const a=Math.random()*Math.PI*2,s=Math.random()*speed+1;
  particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-1,life:30+Math.random()*30,maxLife:60,size:Math.random()*4+1,color})}}

function updateParticles(){for(let i=particles.length-1;i>=0;i--){const p=particles[i];p.vy+=GRAVITY*0.3;p.x+=p.vx;p.y+=p.vy;p.life--;if(p.life<=0)particles.splice(i,1)}}

function addFloatingText(x,y,text,color){floatingTexts.push({x,y,text,color,life:60,vy:-2})}
function updateFloatingTexts(){for(let i=floatingTexts.length-1;i>=0;i--){const f=floatingTexts[i];f.y+=f.vy;f.life--;if(f.life<=0)floatingTexts.splice(i,1)}}

// ==================== CAMERA ====================
function updateCamera(){
  const unit=teams[activeTeam]?.units[activeUnit];
  if(unit&&unit.alive){camera.x+=(unit.x-camera.x)*0.05;camera.y+=(unit.y-H*0.6-camera.y)*0.05}
  camera.x=Math.max(0,Math.min(camera.x,terrainCanvas.width-W/camera.zoom));
  camera.y=Math.max(-100,Math.min(camera.y,terrainCanvas.height-H/camera.zoom));
  if(projectiles.length>0)camera.targetZoom=0.82;else camera.targetZoom=1;
  camera.zoom+=(camera.targetZoom-camera.zoom)*0.03;
  if(screenShake.intensity>0){screenShake.x=(Math.random()-0.5)*screenShake.intensity;screenShake.y=(Math.random()-0.5)*screenShake.intensity;screenShake.intensity*=0.85;if(screenShake.intensity<0.3)screenShake.intensity=0}
  else{screenShake.x=0;screenShake.y=0}
}

// ==================== RENDERING ====================
function render(){
  ctx.clearRect(0,0,W,H);ctx.save();ctx.translate(screenShake.x,screenShake.y);ctx.scale(camera.zoom,camera.zoom);ctx.translate(-camera.x,-camera.y);
  ctx.drawImage(terrainCanvas,0,0);
  // Water surface animation
  const t=Date.now()*0.002;ctx.fillStyle='rgba(30,80,160,0.35)';
  for(let x=camera.x-10;x<camera.x+W/camera.zoom+10;x+=4){const wy=waterLevel+Math.sin(x*0.05+t)*3;if(!isTerrain(Math.floor(x),Math.floor(wy)))ctx.fillRect(x,wy,4,2)}
  // Debris
  debris.forEach(d=>{ctx.globalAlpha=Math.min(1,d.life/20);ctx.fillStyle=d.color;ctx.fillRect(d.x-d.size/2,d.y-d.size/2,d.size,d.size)});ctx.globalAlpha=1;
  // Projectiles & trails
  projectiles.forEach(p=>{p.trail.forEach(t=>{ctx.fillStyle=`rgba(255,150,50,${t.life/10})`;ctx.beginPath();ctx.arc(t.x,t.y,2,0,Math.PI*2);ctx.fill()});
    const glow=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,8);glow.addColorStop(0,'#fff');glow.addColorStop(0.5,'#ffaa44');glow.addColorStop(1,'rgba(255,100,0,0)');
    ctx.fillStyle=glow;ctx.beginPath();ctx.arc(p.x,p.y,8,0,Math.PI*2);ctx.fill()});
  // Particles
  particles.forEach(p=>{const a=p.life/p.maxLife;ctx.globalAlpha=a;ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(p.x,p.y,p.size*a,0,Math.PI*2);ctx.fill()});ctx.globalAlpha=1;
  // Units
  teams.forEach((team,ti)=>team.units.forEach(u=>{if(!u.alive)return;drawWorm(u,team.color,ti===activeTeam&&teams[ti].units.indexOf(u)===activeUnit)}));
  // Floating texts
  floatingTexts.forEach(f=>{ctx.globalAlpha=Math.min(1,f.life/20);ctx.fillStyle=f.color;ctx.font='bold 14px sans-serif';ctx.textAlign='center';ctx.fillText(f.text,f.x,f.y)});ctx.globalAlpha=1;
  // Aiming line
  if(gameState==='aiming'){const unit=teams[activeTeam].units[activeUnit];if(unit)drawAimLine(unit)}
  ctx.restore();
}

function drawWorm(u,color,isActive){
  const t=Date.now()*0.003;
  // Shadow
  ctx.fillStyle='rgba(0,0,0,0.25)';ctx.beginPath();ctx.ellipse(u.x,u.y+WORM_RADIUS+2,WORM_RADIUS*1.4,2.5,0,0,Math.PI*2);ctx.fill();
  // Body
  ctx.strokeStyle=color;ctx.lineWidth=WORM_RADIUS*1.6;ctx.lineCap='round';ctx.beginPath();
  for(let i=-4;i<=4;i++){const wx=u.x+i*1.5,wy=u.y+Math.sin(i*0.8+t)*2;if(i===-4)ctx.moveTo(wx,wy);else ctx.lineTo(wx,wy)}ctx.stroke();
  ctx.strokeStyle='rgba(0,0,0,0.4)';ctx.lineWidth=1.5;ctx.beginPath();
  for(let i=-4;i<=4;i++){const wx=u.x+i*1.5,wy=u.y+Math.sin(i*0.8+t)*2;if(i===-4)ctx.moveTo(wx,wy);else ctx.lineTo(wx,wy)}ctx.stroke();
  // Eyes
  const eo=Math.sin(t)*0.3;ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(u.x-3,u.y-4+eo,3,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.arc(u.x+3,u.y-4+eo,3,0,Math.PI*2);ctx.fill();
  ctx.fillStyle='#111';ctx.beginPath();ctx.arc(u.x-2.5,u.y-3.5+eo,1.5,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.arc(u.x+3.5,u.y-3.5+eo,1.5,0,Math.PI*2);ctx.fill();
  // HP bar
  const hpPct=u.hp/MAX_HP;ctx.fillStyle='rgba(0,0,0,0.6)';ctx.fillRect(u.x-14,u.y-WORM_RADIUS-10,28,5);
  ctx.fillStyle=hpPct>0.5?'#44dd44':hpPct>0.25?'#ffaa00':'#ff4444';ctx.fillRect(u.x-14,u.y-WORM_RADIUS-10,Math.max(0,28*hpPct),5);
  // Active indicator
  if(isActive){const pulse=Math.sin(Date.now()*0.005)*0.3+0.7;ctx.strokeStyle=`rgba(255,255,255,${pulse*0.4})`;ctx.lineWidth=3;ctx.beginPath();ctx.arc(u.x,u.y,WORM_RADIUS+8,0,Math.PI*2);ctx.stroke()}
  if(u.inWater){ctx.fillStyle='rgba(100,150,255,0.25)';ctx.beginPath();ctx.arc(u.x,u.y,WORM_RADIUS+6,0,Math.PI*2);ctx.fill()}
}

function drawAimLine(unit){
  const w=WEAPONS[selectedWeapon];const power=w.power*(powerLevel||50)/50;const rad=(aimAngle||(-45))*Math.PI/180;
  let px=unit.x+Math.cos(rad)*15,py=unit.y-12+Math.sin(rad)*15,vx=Math.cos(rad)*power*0.3,vy=Math.sin(rad)*power*0.3;
  ctx.setLineDash([4,6]);ctx.strokeStyle='rgba(255,255,255,0.35)';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(px,py);
  for(let i=0;i<80;i++){vx+=wind*0.002;vy+=GRAVITY*0.5;px+=vx;py+=vy;if(isTerrain(Math.floor(px),Math.floor(py)))break;ctx.lineTo(px,py)}
  ctx.stroke();ctx.setLineDash([]);
  ctx.strokeStyle='rgba(255,100,50,0.25)';ctx.lineWidth=2;ctx.beginPath();ctx.arc(px,py,w.aoe,0,Math.PI*2);ctx.stroke();
}

// ==================== HUD ====================
function updateHUD(){
  const hudTop=document.getElementById('hud-top');hudTop.innerHTML='';
  teams.forEach((team,i)=>{if(!team.alive)return;
    const div=document.createElement('div');div.className='team-hud'+(i===activeTeam?' active':'');
    let html=`<span style="color:${team.color}">●</span><b>${team.name}</b>`+
      (team.isCPU?' <span style="font-size:0.7em;color:#888">🤖</span>':'') ;
    team.units.forEach(u=>{if(!u.alive)return;html+=`<div class="unit-bar"><div class="unit-fill" style="width:${Math.max(0,u.hp/MAX_HP*100)}%;background:${team.color}"></div></div>`});
    div.innerHTML=html;hudTop.appendChild(div);
  });
  const wl=document.getElementById('weapon-list');wl.innerHTML='';
  WEAPONS.forEach((w,i)=>{const btn=document.createElement('button');btn.className='weapon-btn'+(i===selectedWeapon?' active':'');
    btn.innerHTML=`<span class="weapon-icon">${w.icon}</span><span>${w.name}</span>`;
    btn.onclick=()=>{initAudio();playSound('click');selectedWeapon=i;updateHUD()};wl.appendChild(btn);
  });
  const tf=document.getElementById('turn-timer-fill');const pct=Math.max(0,turnTimer/turnTimerMax*100);tf.style.width=pct+'%';
  if(pct>50)tf.style.background='#44dd44';else if(pct>25)tf.style.background='#ffaa00';else tf.style.background='#ff4444';
  document.getElementById('wind-val').textContent=wind.toFixed(1);
  const pm=document.getElementById('power-meter'),pf=document.getElementById('power-fill');
  if(gameState==='aiming'){pm.style.display='block';pf.style.width=Math.max(0,Math.min(100,powerLevel))+'%';pf.style.background=`hsl(${120-powerLevel*1.2},80%,50%)`}else{pm.style.display='none'}
  // Turn label
  const tl=document.getElementById('turn-label');
  if(gameState==='weaponSelect'||gameState==='aiming'){
    const team=teams[activeTeam];tl.textContent=`${team.name}'s turn — Unit ${activeUnit+1}`;tl.style.color=team.color;tl.style.display='block';
  }else{tl.style.display='none'}
}

// ==================== AI SYSTEM ====================
let aiTimeout=null;
function runAI(){
  if(gameState!=='weaponSelect'||!teams[activeTeam]?.isCPU)return;
  const team=teams[activeTeam];const unit=team.units[activeUnit];if(!unit||!unit.alive){startTurn();return}

  // AI decision making with risk assessment
  aiTimeout=setTimeout(()=>{aiDecide(team,unit)},800+Math.random()*600);
}

function aiDecide(team,unit){
  if(gameState!=='weaponSelect')return;

  // Risk assessment: check environmental hazards
  const inDanger=unit.inWater||(!unit.onGround&&unit.y>waterLevel-50);
  const nearCliff=!isTerrain(Math.floor(unit.x+20),Math.floor(unit.y+WORM_RADIUS))&&!isTerrain(Math.floor(unit.x-20),Math.floor(unit.y+WORM_RADIUS));

  // If in danger, try to move to safety first
  if(inDanger&&unit.onGround){aiMoveToSafety(unit);return}

  // Find targets (enemy units)
  const enemies=[];
  teams.forEach((t,i)=>{if(i===activeTeam)return;t.units.forEach(u=>{if(!u.alive)return;enemies.push({team:i,unit:u,dist:Math.sqrt((u.x-unit.x)**2+(u.y-unit.y)**2)})})});

  if(enemies.length===0){skipTurn();return}

  // Sort enemies by distance (prioritize closest)
  enemies.sort((a,b)=>a.dist-b.dist);

  // Choose weapon based on situation
  let bestWeapon=0;
  const avgDist=enemies.reduce((s,e)=>s+e.dist,0)/enemies.length;
  if(avgDist<150){bestWeapon=3} // shotgun for close range
  else if(avgDist>400){bestWeapon=Math.random()>0.5?2:1} // missile or rocket for far
  else{bestWeapon=Math.floor(Math.random()*6)} // random otherwise

  selectedWeapon=bestWeapon;updateHUD();

  // Calculate aim angle toward target
  const target=enemies[0].unit;
  const dx=target.x-unit.x,dy=target.y-(unit.y-12);
  const dist=Math.sqrt(dx*dx+dy*dy);

  // Simple trajectory calculation for indirect weapons
  const w=WEAPONS[selectedWeapon];
  if(w.cat==='indirect'||w.cat==='aoe'){
    // Calculate angle to hit target with parabolic arc
    const power=w.power*0.6; // AI uses moderate power
    // Simplified: aim at a point above the target
    const aimX=target.x+(Math.random()-0.5)*40;
    const aimY=target.y-30-Math.random()*40;
    aimAngle=Math.atan2(aimY-(unit.y-12),aimX-unit.x)*180/Math.PI;
    powerLevel=40+Math.random()*40;

    // Risk assessment: avoid shooting near water or own units
    const shotLanding={x:aimX,y:aimY};
    if(shotLanding.y>waterLevel-20){powerLevel=Math.max(30,powerLevel-15)} // reduce power to avoid water splash

    setTimeout(()=>{fireProjectile(activeTeam)},400);
  }else{
    // Direct weapons - aim directly at target
    aimAngle=Math.atan2(dy,dx)*180/Math.PI;
    if(aimAngle>0)aimAngle=-aimAngle*0.5; // Adjust for upward aiming
    powerLevel=60+Math.random()*30;
    setTimeout(()=>{fireProjectile(activeTeam)},400);
  }
}

function aiMoveToSafety(unit){
  // Find safe ground nearby (not near water)
  const searchRange=150;const dir=Math.random()>0.5?1:-1;
  for(let d=30;d<searchRange;d+=20){
    const tx=unit.x+dir*d;
    let landY=-1;for(let y=Math.floor(unit.y);y<terrainCanvas.height;y++){if(isTerrain(Math.floor(tx),y)){landY=y;break}}
    if(landY>waterLevel-30&&landY>=0){
      // Move to safe position
      playSound('jump');unit.vx=dir*5;unit.vy=-7;unit.onGround=false;unit.fallStartY=unit.y;gameState='moving';
      setTimeout(()=>{if(gameState==='moving')startTurn()},1500);return;
    }
  }
  // If no safe ground found, skip turn
  startTurn();
}

// ==================== GAME FLOW ====================
function startGame(){
  initAudio();playSound('click');
  document.getElementById('menu-screen').classList.add('hidden');
  ['hud-top','turn-timer-bar','wind-indicator','hud-bottom'].forEach(id=>document.getElementById(id).style.display='flex');
  generateTerrain();createTeams();
  wind=(Math.random()-0.5)*10;activeTeam=0;activeUnit=0;startTurn();
}

function startTurn(){
  if(aiTimeout){clearTimeout(aiTimeout);aiTimeout=null}
  let found=false,attempts=0;
  while(!found&&attempts<teams.length*4){
    if(teams[activeTeam]?.alive&&teams[activeTeam].units[activeUnit]?.alive){found=true;break}
    activeUnit++;if(activeUnit>=teams[activeTeam]?.units?.length){activeUnit=0;activeTeam=(activeTeam+1)%teams.length}
    attempts++;
  }
  if(!found||!teams.some(t=>t.alive)){endGame();return}

  turnTimer=TURN_TIME;gameState='weaponSelect';selectedWeapon=0;powerLevel=50;aimAngle=-45;
  wind=(Math.random()-0.5)*12;updateHUD();

  // If CPU team, run AI after delay
  if(teams[activeTeam].isCPU){setTimeout(()=>runAI(),300)}
}

function skipTurn(){playSound('click');startTurn()}
function doMove(){if(gameState!=='weaponSelect')return;initAudio();playSound('click');gameState='moving'}

// ==================== INPUT ====================
let mouseX=0,mouseY=0,keys={};
canvas.addEventListener('mousemove',e=>{mouseX=e.clientX;mouseY=e.clientY});
canvas.addEventListener('mousedown',e=>handleClick(e));
document.addEventListener('keydown',e=>{keys[e.key]=true;handleKey(e)});
document.addEventListener('keyup',e=>{keys[e.key]=false});

function handleClick(e){
  initAudio();
  if(gameState==='weaponSelect'){
    const worldX=(e.clientX-screenShake.x)/camera.zoom+camera.x;const worldY=(e.clientY-screenShake.y)/camera.zoom+camera.y;
    const unit=teams[activeTeam].units[activeUnit];
    // Click on friendly unit to switch
    for(let i=0;i<teams[activeTeam].units.length;i++){const u=teams[activeTeam].units[i];if(!u.alive)continue;
      if((u.x-worldX)**2+(u.y-worldY)**2<400){activeUnit=i;playSound('click');updateHUD();return}}
    gameState='aiming';powerLevel=50;isCharging=false;chargeDir=1;
  }else if(gameState==='aiming'){fireProjectile(activeTeam)}
  else if(gameState==='moving'){const worldX=(e.clientX-screenShake.x)/camera.zoom+camera.x;const worldY=(e.clientY-screenShake.y)/camera.zoom+camera.y;moveUnit(worldX,worldY)}
}

function handleKey(e){
  if(gameState==='weaponSelect'){
    const num=parseInt(e.key);if(num>=1&&num<=WEAPONS.length){selectedWeapon=num-1;playSound('click');updateHUD()}
    if(e.key==='Enter'||e.key===' '){gameState='aiming';powerLevel=50;isCharging=false;chargeDir=1}
  }else if(gameState==='aiming'){if(e.key==='Escape'){gameState='weaponSelect';playSound('click')}}
}

function moveUnit(tx,ty){
  const unit=teams[activeTeam].units[activeUnit];if(!unit.alive)return;
  let landY=-1;for(let y=Math.floor(ty);y<terrainCanvas.height;y++){if(isTerrain(Math.floor(tx),y)){landY=y;break}}
  if(landY===-1){gameState='weaponSelect';return}
  const targetY=landY-WORM_RADIUS,dx=tx-unit.x,dy=targetY-unit.y,dist=Math.sqrt(dx*dx+dy*dy);
  if(dist<5){gameState='weaponSelect';return}
  playSound('jump');unit.vx=(dx/dist)*6;unit.vy=-8-Math.min(Math.abs(dy)*0.05,5);unit.onGround=false;unit.fallStartY=unit.y;
  setTimeout(()=>{if(gameState==='moving'){gameState='weaponSelect';updateHUD()}},1500);
}

// ==================== GAME LOOP ====================
let lastTime=0;
function gameLoop(time){
  const dt=Math.min(32,time-lastTime);lastTime=time;
  if(gameState!=='menu'&&gameState!=='gameOver'){
    if(impactFreeze>0){impactFreeze--;render();requestAnimationFrame(gameLoop);return}
    teams.forEach(t=>t.units.forEach(u=>{if(u.alive)updateUnit(u)}));
    updateProjectiles();updateParticles();updateDebris();updateFloatingTexts();updateCamera();
    if(gameState==='weaponSelect'||gameState==='turnSelect'){
      turnTimer-=dt/1000;
      if(turnTimer<=3&&turnTimer>0){countdownTimer+=dt;if(countdownTimer>500){playSound('countdown');countdownTimer=0}}
      if(turnTimer<=0)startTurn();
    }
    if(gameState==='firing'&&projectiles.length===0){gameState='weaponSelect';updateHUD()}
    if(gameState==='aiming'){
      const unit=teams[activeTeam].units[activeUnit];if(!unit)return;
      const worldMX=(mouseX-screenShake.x)/camera.zoom+camera.x,worldMY=(mouseY-screenShake.y)/camera.zoom+camera.y;
      aimAngle=Math.atan2(worldMY-(unit.y-12),worldMX-unit.x)*180/Math.PI;
      if(keys[' ']||keys['Shift']){powerLevel+=chargeDir*2.5;if(powerLevel>=100){powerLevel=100;chargeDir=-1}if(powerLevel<=10){powerLevel=10;chargeDir=1}}
    }
    updateHUD();
  }
  render();requestAnimationFrame(gameLoop);
}

// ==================== END GAME ====================
function endGame(){
  gameState='gameOver';const gs=document.getElementById('game-over-screen');gs.classList.remove('hidden');
  const sl=document.getElementById('score-list');sl.innerHTML='';
  const sorted=[...teams].sort((a,b)=>{const aS=a.units.filter(u=>u.alive).length*100+a.units.reduce((s,u)=>s+Math.max(0,u.hp),0);const bS=b.units.filter(u=>u.alive).length*100+b.units.reduce((s,u)=>s+Math.max(0,u.hp),0);return bS-aS});
  sorted.forEach((team,i)=>{const alive=team.units.filter(u=>u.alive).length,totalHP=team.units.reduce((s,u)=>s+Math.max(0,u.hp),0);
    const row=document.createElement('div');row.className='score-row';
    row.innerHTML=`<span style="color:${team.color};font-size:1.3em">${i===0?'🏆':'#'}</span><b>${team.name}</b><span style="margin-left:auto">${alive} alive, ${totalHP} HP</span>`;sl.appendChild(row)});
}

// ==================== START ====================
init();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"middle","sessionId":"d31a6d30-de7d-4ef8-bfe7-c7caea871ef2","tokensIn":66124,"tokensOut":28625,"tokensTotal":94749,"turns":4,"toolCalls":3,"failedToolCalls":0,"timestamp":"2026-07-18T23:21:04.044Z"} -->