Worms
GPT 5.6 Sol · basic

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 36,514 bytes · SHA-256 c9467050f839
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Wobble Wars</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;700;800&display=swap');
:root{
--glass:rgba(15,24,50,.78);
--line:rgba(255,255,255,.15);
--cyan:#71e5ff;
--gold:#ffd85b;
}
*{box-sizing:border-box}
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#071225;color:#fff;font-family:"Baloo 2",system-ui,sans-serif}
canvas{position:fixed;inset:0;width:100%;height:100%;display:block;cursor:crosshair}
button,select,input{font:inherit}
.hidden{display:none!important}
.glass{
background:linear-gradient(145deg,rgba(27,43,79,.92),rgba(10,18,41,.88));
border:1px solid var(--line);
box-shadow:0 18px 55px rgba(0,0,0,.38),inset 0 1px rgba(255,255,255,.09);
backdrop-filter:blur(14px)
}
#menu{
position:fixed;inset:0;display:flex;align-items:center;justify-content:center;
background:radial-gradient(circle at 50% 20%,rgba(63,177,255,.18),transparent 36%),rgba(3,8,22,.38);
z-index:20;padding:18px
}
.menuCard{width:min(750px,96vw);border-radius:28px;padding:28px}
.logo{
margin:0;text-align:center;font-size:clamp(48px,9vw,92px);line-height:.86;font-weight:800;
letter-spacing:-4px;color:#fff;
text-shadow:0 5px 0 #2354a5,0 10px 22px rgba(0,0,0,.45)
}
.logo span{color:#ffe66a;text-shadow:0 5px 0 #e06945,0 10px 22px rgba(0,0,0,.45)}
.subtitle{text-align:center;color:#a9c9ec;font-weight:700;letter-spacing:2px;margin:9px 0 22px}
.configGrid{display:grid;grid-template-columns:repeat(3,1fr);gap:13px}
.field{padding:12px 14px;border-radius:15px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1)}
.field label{display:block;color:#9ebee2;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:1.4px;margin-bottom:5px}
.field select{width:100%;border:0;color:#fff;background:#1c315b;border-radius:9px;padding:7px 9px;outline:none}
.teamRows{margin-top:14px;display:grid;grid-template-columns:1fr 1fr;gap:9px}
.teamRow{
display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:13px;
background:rgba(255,255,255,.055);border:1px solid rgba(255,255,255,.08)
}
.dot{width:15px;height:15px;border-radius:50%;box-shadow:0 0 14px currentColor}
.teamRow select{margin-left:auto;background:#1b2c50;color:white;border:0;border-radius:8px;padding:5px 8px}
.playBtn{
display:block;width:100%;margin-top:17px;border:0;border-radius:16px;padding:13px;
color:#202434;font-size:22px;font-weight:800;background:linear-gradient(#fff278,#ffbd42);
box-shadow:0 8px 0 #c87031,0 15px 26px rgba(0,0,0,.3);transition:.12s;cursor:pointer
}
.playBtn:hover{transform:translateY(-2px);filter:brightness(1.06)}
.playBtn:active{transform:translateY(6px);box-shadow:0 2px 0 #c87031}
.help{text-align:center;color:#809fc4;font-size:13px;margin:17px 0 0}
#hud{position:fixed;inset:0;pointer-events:none;z-index:10}
#topbar{position:absolute;top:14px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:10px}
.pill{border-radius:16px;padding:8px 15px;min-width:135px;text-align:center}
#turnInfo{font-size:15px;font-weight:800}
#turnInfo small{display:block;color:#a9bfd9;font-size:10px;letter-spacing:1px}
#wind{font-weight:800;color:#dff8ff}
#windArrow{display:inline-block;color:#79e3ff;font-size:21px;margin:0 5px;transition:.3s}
#timer{
width:72px;height:72px;border-radius:50%;display:flex;align-items:center;justify-content:center;
font-size:28px;font-weight:800;border:3px solid #78e8ff;
background:radial-gradient(circle,#20375f,#0c1732);box-shadow:0 0 24px rgba(68,215,255,.22)
}
#timer.danger{color:#ffef68;border-color:#ff725f;animation:throb .52s infinite}
@keyframes throb{50%{transform:scale(1.13);box-shadow:0 0 30px rgba(255,70,55,.5)}}
#teams{position:absolute;top:14px;left:14px;display:flex;flex-direction:column;gap:7px}
.teamChip{width:172px;border-radius:13px;padding:7px 10px;opacity:.78;transition:.2s}
.teamChip.active{opacity:1;transform:translateX(5px);box-shadow:0 0 22px rgba(255,255,255,.12)}
.teamTitle{display:flex;justify-content:space-between;font-weight:800;font-size:13px}
.hpTrack{height:6px;background:rgba(0,0,0,.35);border-radius:9px;overflow:hidden;margin-top:3px}
.hpFill{height:100%;border-radius:9px;transition:.3s}
#weaponDock{
position:absolute;bottom:15px;left:50%;transform:translateX(-50%);
display:flex;gap:7px;padding:8px;border-radius:19px;pointer-events:auto;max-width:96vw
}
.weapon{
width:68px;height:65px;border-radius:13px;border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.06);color:white;cursor:pointer;position:relative;transition:.13s
}
.weapon:hover{transform:translateY(-4px);background:rgba(255,255,255,.13)}
.weapon.selected{background:linear-gradient(145deg,#328ac0,#394ca5);border-color:#a8edff;transform:translateY(-5px);box-shadow:0 8px 22px rgba(0,0,0,.28)}
.weapon .icon{display:block;font-size:25px;line-height:25px}
.weapon .name{display:block;font-size:9px;font-weight:800;white-space:nowrap}
.weapon .key{position:absolute;right:5px;top:3px;color:#9db9db;font-size:9px}
#powerBox{position:absolute;right:16px;bottom:95px;width:200px;padding:12px;border-radius:14px}
#powerLabel{display:flex;justify-content:space-between;font-weight:800;font-size:12px}
#powerTrack{height:11px;background:#101a31;border-radius:20px;overflow:hidden;margin-top:6px;border:1px solid rgba(255,255,255,.1)}
#powerFill{height:100%;width:55%;background:linear-gradient(90deg,#62e8ff,#ffe85e,#ff5e51);border-radius:20px}
#controls{position:absolute;left:15px;bottom:100px;padding:8px 12px;border-radius:13px;color:#b8cce4;font-size:11px;line-height:1.5}
kbd{background:#edf4ff;color:#18233d;padding:1px 5px;border-radius:4px;font-weight:800;box-shadow:0 2px #8798af}
#banner{
position:absolute;left:50%;top:28%;transform:translate(-50%,-50%) scale(.8);opacity:0;
font-size:clamp(34px,6vw,72px);font-weight:800;text-align:center;text-shadow:0 6px 18px #000;
transition:.25s;white-space:nowrap
}
#banner.show{opacity:1;transform:translate(-50%,-50%) scale(1)}
#banner small{display:block;font-size:18px;color:#d5e8ff}
#category{position:absolute;right:16px;bottom:180px;color:#a9c6e9;font-size:11px;text-align:right;font-weight:700}
#pauseBtn{position:absolute;right:16px;top:16px;pointer-events:auto;border:0;border-radius:12px;padding:8px 12px;color:white;background:rgba(10,20,44,.7);cursor:pointer}
#result{position:fixed;inset:0;z-index:30;display:flex;align-items:center;justify-content:center;background:rgba(3,8,22,.72)}
.resultCard{text-align:center;border-radius:25px;padding:30px;width:min(430px,90vw)}
.resultCard h2{font-size:45px;margin:0}
@media(max-width:760px){
.configGrid{grid-template-columns:1fr}.teamRows{grid-template-columns:1fr}
#controls,#powerBox,#category{display:none}
.weapon{width:51px;height:55px}.weapon .name{font-size:7px}.weapon .icon{font-size:21px}
#teams{transform:scale(.8);transform-origin:top left}
#topbar{top:8px}.pill{min-width:100px;padding:6px}.menuCard{padding:20px}
}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="menu">
<div class="menuCard glass">
<h1 class="logo">WOBBLE <span>WARS!</span></h1>
<div class="subtitle">POCKET-SIZED PANDEMONIUM</div>
<div class="configGrid">
<div class="field"><label>Battlefield</label><select id="mapType"><option value="island">Emerald Island</option><option value="two">Twin Trouble</option><option value="cave">Crystal Cave</option></select></div>
<div class="field"><label>Teams</label><select id="teamCount"><option>2</option><option>3</option><option>4</option></select></div>
<div class="field"><label>Turn Time</label><select id="turnLength"><option value="45">45 seconds</option><option value="35" selected>35 seconds</option><option value="25">25 seconds</option></select></div>
</div>
<div class="teamRows">
<div class="teamRow"><span class="dot" style="color:#ff5c70;background:#ff5c70"></span><b>Raspberry</b><select class="teamMode"><option value="human">Human</option><option value="cpu">CPU</option></select></div>
<div class="teamRow"><span class="dot" style="color:#58c8ff;background:#58c8ff"></span><b>Blueberry</b><select class="teamMode"><option value="human">Human</option><option value="cpu" selected>CPU</option></select></div>
<div class="teamRow"><span class="dot" style="color:#7cf178;background:#7cf178"></span><b>Lime</b><select class="teamMode"><option value="human">Human</option><option value="cpu" selected>CPU</option></select></div>
<div class="teamRow"><span class="dot" style="color:#ffca55;background:#ffca55"></span><b>Mango</b><select class="teamMode"><option value="human">Human</option><option value="cpu" selected>CPU</option></select></div>
</div>
<button class="playBtn" id="play">LAUNCH MAYHEM!</button>
<p class="help">Move with A/D · Aim with ↑/↓ · Jump with W · Hold SPACE to charge, release to fire · Mouse wheel changes weapons</p>
</div>
</div>
<div id="hud" class="hidden">
<div id="teams"></div>
<div id="topbar">
<div id="turnInfo" class="pill glass"></div>
<div id="timer">35</div>
<div id="wind" class="pill glass">WIND <span id="windArrow">➜</span><span id="windValue">0</span></div>
</div>
<div id="weaponDock" class="glass"></div>
<div id="powerBox" class="glass">
<div id="powerLabel"><span>LAUNCH POWER</span><span id="powerText">55%</span></div>
<div id="powerTrack"><div id="powerFill"></div></div>
</div>
<div id="controls" class="glass"><kbd>A</kbd><kbd>D</kbd> MOVE <kbd>W</kbd> JUMP<br><kbd>↑</kbd><kbd>↓</kbd> AIM <kbd>SPACE</kbd> CHARGE/FIRE</div>
<div id="category"></div>
<button id="pauseBtn">☰ Menu</button>
<div id="banner"></div>
</div>
<div id="result" class="hidden">
<div class="resultCard glass"><h2 id="winner"></h2><p id="stats"></p><button class="playBtn" onclick="location.reload()">REMATCH</button></div>
</div>
<script>
(() => {
"use strict";
const canvas=document.getElementById("game"),ctx=canvas.getContext("2d");
const terrainCanvas=document.createElement("canvas"),tctx=terrainCanvas.getContext("2d",{willReadFrequently:true});
const W=1900,H=950,WATER=790;
const colors=["#ff5c70","#58c8ff","#7cf178","#ffca55"];
const darkColors=["#a82747","#2376b4","#299f50","#c27b20"];
const names=["Raspberry","Blueberry","Lime","Mango"];
const wormNames=[
["Pip","Bongo","Noodle","Muffin"],["Fizz","Pebble","Skipper","Bubbles"],
["Pickle","Sprout","Wiggles","Bean"],["Nugget","Mango","Toast","Biscuit"]
];
const weapons=[
{name:"Bazooka",icon:"🚀",cat:"INDIRECT PROJECTILE",type:"rocket",speed:510,radius:72,damage:58},
{name:"Grenade",icon:"💣",cat:"INDIRECT · BOUNCING FUSE",type:"grenade",speed:420,radius:78,damage:62},
{name:"Shotgun",icon:"💥",cat:"DIRECT / INSTANT-HIT",type:"shotgun",damage:34},
{name:"Laser",icon:"⚡",cat:"DIRECT / TERRAIN-CUTTING",type:"laser",damage:46},
{name:"Cluster",icon:"🎇",cat:"HIGH-AOE · CLUSTER BOMB",type:"cluster",speed:390,radius:46,damage:28},
{name:"Airstrike",icon:"🛩️",cat:"HIGH-AOE · MAP-ALTERING",type:"airstrike",damage:35},
{name:"Teleport",icon:"🌀",cat:"UTILITY · TELEPORT BEACON",type:"teleport"},
{name:"Bubble",icon:"🫧",cat:"UTILITY · FLOAT SHIELD",type:"shield"}
];
let vw=innerWidth,vh=innerHeight,terrain=new Uint8Array(W*H),game=false;
let teams=[],units=[],turnTeam=0,unitCursor=[],active=null,selected=0,turnTime=35,timeLeft=35;
let wind=0,projectiles=[],particles=[],texts=[],debris=[],keys={},charging=false,power=.55,chargeDir=1;
let camera={x:W/2,y:H/2,zoom:1,tx:W/2,ty:H/2,tz:1},last=0,freezeUntil=0,shake=0;
let turnLocked=false,turnStarted=0,cpuDone=false,bannerTimer=0,waterPhase=0,shots=0,damageDone=0;
let stars=Array.from({length:90},()=>({x:Math.random()*W,y:Math.random()*500,s:Math.random()*2+.3,a:Math.random()}));
const $=s=>document.querySelector(s),$$=s=>[...document.querySelectorAll(s)];
function resize(){vw=canvas.width=innerWidth;vh=canvas.height=innerHeight}
addEventListener("resize",resize);resize();
const AC=window.AudioContext||window.webkitAudioContext;
let audio=null;
function tone(freq=440,dur=.08,type="sine",vol=.05,slide=0){
try{
audio ||= new AC(); if(audio.state==="suspended")audio.resume();
const o=audio.createOscillator(),g=audio.createGain(),t=audio.currentTime;
o.type=type;o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(Math.max(30,freq+slide),t+dur);
g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(.001,t+dur);
o.connect(g).connect(audio.destination);o.start(t);o.stop(t+dur);
}catch(e){}
}
function noise(dur=.18,vol=.07){
if(!audio)return;
const n=Math.floor(audio.sampleRate*dur),b=audio.createBuffer(1,n,audio.sampleRate),d=b.getChannelData(0);
for(let i=0;i<n;i++)d[i]=(Math.random()*2-1)*(1-i/n);
const s=audio.createBufferSource(),g=audio.createGain();s.buffer=b;g.gain.value=vol;s.connect(g).connect(audio.destination);s.start();
}
document.addEventListener("mouseover",e=>{if(e.target.matches("button,select"))tone(680,.025,"sine",.012)},true);
document.addEventListener("click",e=>{if(e.target.matches("button,select"))tone(360,.06,"triangle",.035,180)},true);
function solid(x,y){
x=x|0;y=y|0;
return x>=0&&x<W&&y>=0&&y<H&&terrain[y*W+x]>0;
}
function setSolid(x,y,v){if(x>=0&&x<W&&y>=0&&y<H)terrain[(y|0)*W+(x|0)]=v}
function smoothNoise(x,seed){
return Math.sin(x*.008+seed)*45+Math.sin(x*.019+seed*2.4)*20+Math.sin(x*.0031+seed*.4)*65;
}
function generateTerrain(type){
terrain.fill(0);const seed=Math.random()*99;
if(type==="island"){
for(let x=0;x<W;x++){
const edge=Math.min(x,W-x)/250,top=510+smoothNoise(x,seed)-Math.max(0,1-edge)*230;
const bottom=850+Math.sin(x*.005)*28;
if(x>45&&x<W-45)for(let y=Math.max(180,top|0);y<bottom;y++)setSolid(x,y,255);
}
}else if(type==="two"){
for(let x=0;x<W;x++){
const left=x<870,center=left?420:1460,rad=left?510:520,dx=Math.abs(x-center);
if(dx<rad){
const top=520+smoothNoise(x,seed)-(1-dx/rad)*65+Math.pow(dx/rad,3)*190;
for(let y=top|0;y<850;y++)setSolid(x,y,255);
}
}
}else{
for(let x=0;x<W;x++){
const floor=600+smoothNoise(x,seed)*.72;
const ceiling=130+Math.sin(x*.009+seed)*38+Math.sin(x*.025)*16;
for(let y=0;y<ceiling;y++)setSolid(x,y,255);
for(let y=floor|0;y<900;y++)setSolid(x,y,255);
if(x%360<75){
let cx=x-x%360+40;
for(let y=ceiling;y<floor;y++)if(Math.abs(x-cx)<18+Math.sin(y*.03)*8)setSolid(x,y,255);
}
}
}
rebuildTerrain();
}
function rebuildTerrain(){
terrainCanvas.width=W;terrainCanvas.height=H;
const im=tctx.createImageData(W,H),d=im.data;
for(let y=0;y<H;y++)for(let x=0;x<W;x++){
if(!terrain[y*W+x])continue;
const i=(y*W+x)*4,depth=Math.min(1,y/900),n=((x*13+y*7)%19)/19;
let grass=!solid(x,y-4);
if(grass){d[i]=62+n*24;d[i+1]=190+n*38;d[i+2]=100+n*20}
else{d[i]=90+n*28+depth*30;d[i+1]=64+n*22+depth*12;d[i+2]=66+n*18}
d[i+3]=255;
}
tctx.putImageData(im,0,0);
}
function surfaceAt(x){
x=Math.max(5,Math.min(W-6,x|0));
for(let y=90;y<WATER-5;y++)if(solid(x,y)&&!solid(x,y-3))return y;
return 680;
}
function findSpawn(x){
for(let n=0;n<200;n++){
const xx=Math.max(40,Math.min(W-40,x+(Math.random()-.5)*300)),y=surfaceAt(xx);
if(y<WATER-15&&!solid(xx,y-28))return {x:xx,y:y-17};
}
return{x:W/2,y:300};
}
function start(){
const count=+$(" #teamCount".trim()).value;
turnTime=+$("#turnLength").value;
generateTerrain($("#mapType").value);
teams=[];units=[];unitCursor=Array(count).fill(0);
for(let t=0;t<count;t++){
const team={id:t,name:names[t],color:colors[t],dark:darkColors[t],cpu:$$(".teamMode")[t].value==="cpu",kills:0};
teams.push(team);
for(let i=0;i<4;i++){
let base=(t+.5)*W/count+(i-1.5)*100;
if($("#mapType").value==="two")base=t%2?1250+i*120:220+i*120;
const p=findSpawn(base);
units.push({team:t,index:i,name:wormNames[t][i],x:p.x,y:p.y,vx:0,vy:0,r:15,hp:100,alive:true,grounded:false,
aim:t%2?Math.PI:0,shield:0,drown:0,moved:0,blink:Math.random()*4,face:t%2?-1:1});
}
}
$("#menu").classList.add("hidden");$("#hud").classList.remove("hidden");game=true;
buildWeapons();buildTeams();turnTeam=Math.floor(Math.random()*count);nextTurn(true);
last=performance.now();requestAnimationFrame(loop);
}
$("#play").onclick=start;
$("#pauseBtn").onclick=()=>location.reload();
function buildWeapons(){
const dock=$("#weaponDock");dock.innerHTML="";
weapons.forEach((w,i)=>{
const b=document.createElement("button");b.className="weapon";b.innerHTML=`<span class="key">${i+1}</span><span class="icon">${w.icon}</span><span class="name">${w.name}</span>`;
b.onclick=()=>{if(!turnLocked){selected=i;refreshWeapon();tone(500,.05,"square",.025,100)}};dock.appendChild(b);
});refreshWeapon();
}
function refreshWeapon(){
$$(".weapon").forEach((e,i)=>e.classList.toggle("selected",i===selected));
$("#category").innerHTML=`${weapons[selected].cat}<br><b style="color:white">${weapons[selected].name}</b>`;
}
function buildTeams(){
$("#teams").innerHTML=teams.map(t=>`<div class="teamChip glass" id="team${t.id}"><div class="teamTitle"><span style="color:${t.color}">● ${t.name}</span><span class="teamHp">400</span></div><div class="hpTrack"><div class="hpFill" style="background:${t.color};width:100%"></div></div></div>`).join("");
}
function updateHud(){
teams.forEach(t=>{
const hp=units.filter(u=>u.team===t.id&&u.alive).reduce((a,u)=>a+u.hp,0),el=$("#team"+t.id);
if(el){el.querySelector(".teamHp").textContent=Math.ceil(hp);el.querySelector(".hpFill").style.width=hp/4+"%";el.classList.toggle("active",t.id===turnTeam)}
});
if(active)$("#turnInfo").innerHTML=`<small>${teams[active.team].cpu?"CPU THINKING":"YOUR TURN"}</small><span style="color:${teams[active.team].color}">${active.name}</span>`;
$("#timer").textContent=Math.max(0,Math.ceil(timeLeft));
$("#timer").classList.toggle("danger",timeLeft<8);
$("#windValue").textContent=Math.abs(wind).toFixed(1);
$("#windArrow").style.transform=`scaleX(${wind<0?-1:1})`;
$("#powerFill").style.width=(power*100)+"%";$("#powerText").textContent=Math.round(power*100)+"%";
}
function banner(text,sub="",duration=1600){
const b=$("#banner");b.innerHTML=text+(sub?`<small>${sub}</small>`:"");b.classList.add("show");
clearTimeout(bannerTimer);bannerTimer=setTimeout(()=>b.classList.remove("show"),duration);
}
function nextTurn(first=false){
if(!game)return;
const livingTeams=teams.filter(t=>units.some(u=>u.team===t.id&&u.alive));
if(livingTeams.length<=1){endGame(livingTeams[0]);return}
if(!first){
let guard=0;do{turnTeam=(turnTeam+1)%teams.length;guard++}while(!units.some(u=>u.team===turnTeam&&u.alive)&&guard<10);
}
const list=units.filter(u=>u.team===turnTeam&&u.alive);
unitCursor[turnTeam]=(unitCursor[turnTeam]||0)%list.length;
active=list[unitCursor[turnTeam]++];active.moved=0;
timeLeft=turnTime;wind=(Math.random()*2-1)*48;turnLocked=false;cpuDone=false;charging=false;power=.55;
turnStarted=performance.now();selected=0;refreshWeapon();
camera.tx=active.x;camera.ty=active.y;camera.tz=1.12;
banner(`${teams[turnTeam].name}!`,teams[turnTeam].cpu?`${active.name} is plotting...`:`${active.name}, make it count!`);
tone(470,.09,"triangle",.04,230);updateHud();
}
function endTurn(delay=900){
if(turnLocked)return;turnLocked=true;charging=false;
setTimeout(()=>{if(game)nextTurn(false)},delay);
}
function endGame(t){
game=false;setTimeout(()=>{
$("#hud").classList.add("hidden");$("#result").classList.remove("hidden");
$("#winner").innerHTML=t?`<span style="color:${t.color}">${t.name}</span> wins!`:"Draw!";
$("#stats").textContent=`${shots} spectacular shots · ${Math.round(damageDone)} total damage`;
tone(392,.2,"triangle",.05,250);setTimeout(()=>tone(523,.3,"triangle",.05,300),180);
},1000);
}
addEventListener("keydown",e=>{
keys[e.code]=true;
if(!game||turnLocked||teams[turnTeam].cpu)return;
if(/^Digit[1-8]$/.test(e.code)){selected=+e.code.slice(5)-1;refreshWeapon()}
if(e.code==="Space"&&!charging){e.preventDefault();charging=true;chargeDir=1;tone(180,.05,"square",.025)}
});
addEventListener("keyup",e=>{
keys[e.code]=false;
if(e.code==="Space"&&charging&&!turnLocked&&!teams[turnTeam].cpu){charging=false;fire()}
});
addEventListener("wheel",e=>{
if(game&&!turnLocked){selected=(selected+(e.deltaY>0?1:-1)+weapons.length)%weapons.length;refreshWeapon()}
},{passive:true});
function fire(){
if(!active||!active.alive)return;
const w=weapons[selected],a=active.aim,dir=Math.cos(a)>=0?1:-1;active.face=dir;shots++;turnLocked=true;
if(w.type==="shield"){
active.shield=15;burst(active.x,active.y,25,"#9df4ff",2);tone(700,.3,"sine",.05,300);floating(active.x,active.y-35,"BUBBLE UP!","#bdfaff");endTurnAfter();
}else if(w.type==="teleport"){
let tx=active.x+Math.cos(a)*Math.max(170,power*620),sy=surfaceAt(tx);
tx=Math.max(35,Math.min(W-35,tx));
if(sy<WATER){burst(active.x,active.y,28,"#bc75ff",3);active.x=tx;active.y=sy-20;active.vx=active.vy=0;burst(tx,active.y,35,"#64e8ff",3);tone(280,.35,"sine",.06,900)}
endTurnAfter();
}else if(w.type==="shotgun")directShot(a,500,34,12,false);
else if(w.type==="laser")directShot(a,780,46,20,true);
else if(w.type==="airstrike"){
const target=Math.max(80,Math.min(W-80,active.x+Math.cos(a)*(220+power*650)));
for(let i=0;i<6;i++)setTimeout(()=>projectiles.push({type:"airbomb",x:target-125+i*50,y:30-Math.random()*120,vx:wind*.25,vy:80,r:7,owner:active,life:5,w}),i*120);
tone(120,.5,"sawtooth",.05,80);setTimeout(()=>endTurnAfter(),850);
}else{
const speed=w.speed*(.45+power*.75);
projectiles.push({type:w.type,x:active.x+Math.cos(a)*23,y:active.y+Math.sin(a)*23,vx:Math.cos(a)*speed,vy:Math.sin(a)*speed,r:w.type==="grenade"?8:6,owner:active,life:0,fuse:w.type==="grenade"?2.5:9,w,bounces:0});
active.vx-=Math.cos(a)*35;active.vy-=Math.sin(a)*12;tone(w.type==="rocket"?130:220,.13,"sawtooth",.06,180);noise(.08,.035);
}
camera.tz=.92;
}
function endTurnAfter(){setTimeout(()=>endTurn(900),400)}
function directShot(a,range,damage,width,cut){
const sx=active.x,sy=active.y,dx=Math.cos(a),dy=Math.sin(a);let hit=null,hx=sx+dx*range,hy=sy+dy*range;
for(let d=20;d<range;d+=4){
const x=sx+dx*d,y=sy+dy*d;
let u=units.find(q=>q.alive&&q!==active&&Math.hypot(q.x-x,q.y-y)<q.r+width*.2);
if(u){hit=u;hx=x;hy=y;break}
if(solid(x,y)){hx=x;hy=y;break}
}
particles.push({beam:true,x:sx,y:sy,x2:hx,y2:hy,life:.15,max:.15,color:cut?"#7efaff":"#ffd46b",width});
if(hit)hurt(hit,damage,dx*180,dy*130);
if(cut)carveLine(sx,sy,hx,hy,7);
else burst(hx,hy,16,"#ffd271",1.5);
tone(cut?920:180,.16,cut?"sine":"square",.07,cut?-400:300);noise(.12,.05);setTimeout(()=>endTurn(900),350);
}
function carveLine(x1,y1,x2,y2,r){
const len=Math.hypot(x2-x1,y2-y1);
for(let d=0;d<len;d+=r){let q=d/len;destroy(x1+(x2-x1)*q,y1+(y2-y1)*q,r,false)}
rebuildTerrain();
}
function explode(x,y,r,damage,heavy=true){
if(heavy){freezeUntil=performance.now()+55;shake=Math.max(shake,r*.13);noise(.34,Math.min(.15,r/600));tone(70,.28,"sawtooth",.07,80)}
destroy(x,y,r,true);burst(x,y,Math.floor(r*.75),"#ffb13b",4);burst(x,y,Math.floor(r*.35),"#ffe88a",2);
units.forEach(u=>{
if(!u.alive)return;const d=Math.hypot(u.x-x,u.y-y);
if(d<r*1.25){const f=Math.max(0,1-d/(r*1.25)),ang=Math.atan2(u.y-y,u.x-x);hurt(u,damage*f,Math.cos(ang)*f*390,Math.sin(ang)*f*350-100*f)}
});
camera.tx=x;camera.ty=y;camera.tz=.82;
}
function destroy(cx,cy,r,makeDebris){
const x0=Math.max(0,(cx-r)|0),x1=Math.min(W-1,(cx+r)|0),y0=Math.max(0,(cy-r)|0),y1=Math.min(H-1,(cy+r)|0);
for(let y=y0;y<=y1;y++)for(let x=x0;x<=x1;x++)if((x-cx)**2+(y-cy)**2<r*r)terrain[y*W+x]=0;
if(makeDebris){
for(let i=0;i<Math.min(30,r/2);i++)debris.push({x:cx+(Math.random()-.5)*r,y:cy+(Math.random()-.5)*r,vx:(Math.random()-.5)*260,vy:-Math.random()*300,size:3+Math.random()*8,rot:Math.random()*6,vr:(Math.random()-.5)*8,life:3+Math.random()*2,color:i%3?"#725249":"#55b764"});
rebuildTerrain();
}
}
function burst(x,y,n,color,speed){
for(let i=0;i<n;i++){const a=Math.random()*Math.PI*2,s=(20+Math.random()*100)*speed;particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:.35+Math.random()*.7,max:1,size:2+Math.random()*5,color,grav:220,drag:.97})}
}
function floating(x,y,text,color="#fff"){texts.push({x,y,text,color,life:1.3,max:1.3})}
function hurt(u,dmg,kx=0,ky=0){
if(u.shield>0)dmg*=.46;
dmg=Math.max(0,dmg);u.hp-=dmg;damageDone+=dmg;u.vx+=kx;u.vy+=ky;
if(dmg>1)floating(u.x,u.y-30,`-${Math.round(dmg)}`,dmg>35?"#ff735f":"#fff17b");
if(u.hp<=0&&u.alive){u.alive=false;u.hp=0;burst(u.x,u.y,30,teams[u.team].color,2);floating(u.x,u.y-35,"KER-POOF!","#fff");tone(95,.45,"sawtooth",.06,100)}
updateHud();
}
function updateProjectile(p,dt){
p.life+=dt;
if(p.type==="cluster"&&p.life>1.55&&!p.split){
p.split=true;
for(let i=0;i<6;i++)projectiles.push({type:"mini",x:p.x,y:p.y,vx:p.vx*.35+(i-2.5)*75,vy:-160-Math.random()*130,r:4,owner:p.owner,life:0,fuse:4,w:p.w});
burst(p.x,p.y,15,"#ff8de8",2);p.dead=true;return;
}
p.vx+=wind*(p.type==="airbomb"?1.1:.62)*dt;p.vy+=620*dt;p.x+=p.vx*dt;p.y+=p.vy*dt;
if(Math.random()<.7)particles.push({x:p.x,y:p.y,vx:-p.vx*.05,vy:-p.vy*.05,life:.32,max:.32,size:3,color:p.type==="rocket"?"#ffb84b":"#d7e1ec",grav:0,drag:.95});
let unit=units.find(u=>u.alive&&u!==p.owner&&Math.hypot(u.x-p.x,u.y-p.y)<u.r+p.r);
if(unit||solid(p.x,p.y)){
if(p.type==="grenade"&&!unit&&p.bounces<4){
p.bounces++;p.vy=-Math.abs(p.vy)*.55;p.vx*=.72;p.y-=5;tone(260+p.bounces*80,.035,"square",.025);
}else detonate(p);
}
if(p.type==="grenade"&&p.life>p.fuse)detonate(p);
if((p.type==="rocket"||p.type==="mini"||p.type==="airbomb")&&(p.life>p.fuse||p.y>H))detonate(p);
if(p.y>WATER&&!p.dead){burst(p.x,WATER,18,"#92e8ff",2);p.dead=true;tone(130,.12,"sine",.03,-60);finishProjectiles()}
}
function detonate(p){
if(p.dead)return;p.dead=true;
const w=p.w||weapons[4],r=p.type==="mini"?42:p.type==="airbomb"?55:w.radius;
const dmg=p.type==="mini"?23:p.type==="airbomb"?35:w.damage;
explode(p.x,p.y,r,dmg,r>50);finishProjectiles();
}
function finishProjectiles(){setTimeout(()=>{if(!projectiles.some(p=>!p.dead)&&turnLocked)endTurn(1100)},250)}
function updateUnit(u,dt){
if(!u.alive)return;
u.shield=Math.max(0,u.shield-dt);
const underwater=u.y+u.r>WATER;
if(underwater){
u.drown+=dt;u.vy-=u.shield>0?760*dt:390*dt;u.vx+=wind*.025*dt;u.vx*=.975;
if(Math.random()<.06)particles.push({x:u.x+(Math.random()-.5)*15,y:u.y-15,vx:(Math.random()-.5)*15,vy:-35,life:.8,max:.8,size:3,color:"#a9efff",grav:-10,drag:.99});
if(u.drown>4&&!u.shield){u.drown=2.8;hurt(u,18);floating(u.x,u.y-25,"GLUB!","#9eefff")}
}else u.drown=Math.max(0,u.drown-dt*2);
u.vy+=620*dt;u.vx+=wind*.002*dt;
u.x+=u.vx*dt;u.y+=u.vy*dt;u.grounded=false;
if(solid(u.x,u.y+u.r)){
const impact=u.vy;
let tries=0;while(solid(u.x,u.y+u.r)&&tries++<30)u.y--;
u.vy=0;u.grounded=true;u.vx*=Math.pow(.08,dt);
if(impact>470){const dmg=(impact-450)*.09;hurt(u,dmg);burst(u.x,u.y+14,12,"#c2a083",1)}
}
if(solid(u.x-u.r*.8,u.y)&&u.vx<0)u.vx*=-.15;
if(solid(u.x+u.r*.8,u.y)&&u.vx>0)u.vx*=-.15;
u.x=Math.max(15,Math.min(W-15,u.x));
if(u.y>H+80){hurt(u,999);u.alive=false}
}
function humanControl(dt){
if(!active||turnLocked||teams[turnTeam].cpu||!active.alive)return;
const left=keys.KeyA||keys.ArrowLeft,right=keys.KeyD||keys.ArrowRight;
if((left||right)&&active.moved<320){
const d=left?-1:1;active.face=d;active.vx+=d*(active.grounded?600:170)*dt;active.vx=Math.max(-105,Math.min(105,active.vx));active.moved+=Math.abs(active.vx*dt);
if(active.grounded&&Math.random()<.18)burst(active.x-d*10,active.y+14,1,"#c5aa8f",.35);
}
if((keys.KeyW||keys.ArrowUp&&keys.ShiftLeft)&&active.grounded&&!active.jumpLatch){active.vy=-285;active.jumpLatch=true;tone(320,.08,"sine",.03,180)}
if(!keys.KeyW)active.jumpLatch=false;
if(keys.ArrowUp&&!keys.ShiftLeft)active.aim-=1.35*dt;
if(keys.ArrowDown)active.aim+=1.35*dt;
active.aim=Math.max(-Math.PI+.08,Math.min(-.08,active.aim));
if(Math.cos(active.aim)>0)active.face=1;else active.face=-1;
if(charging){power+=chargeDir*dt*.72;if(power>=1){power=1;chargeDir=-1}if(power<=.18){power=.18;chargeDir=1}}
}
function cpuThink(dt){
if(!active||turnLocked||!teams[turnTeam].cpu||cpuDone)return;
const elapsed=(performance.now()-turnStarted)/1000;
if(elapsed<.9)return;
const enemies=units.filter(u=>u.alive&&u.team!==active.team);
if(!enemies.length)return;
const scored=enemies.map(u=>{
const d=Math.abs(u.x-active.x),nearWater=(WATER-u.y)<90,cluster=units.filter(v=>v.alive&&v.team!==active.team&&Math.hypot(v.x-u.x,v.y-u.y)<150).length;
return {u,score:(100-u.hp)*1.2+cluster*40-d*.035+(nearWater?25:0)}
}).sort((a,b)=>b.score-a.score);
const target=scored[0].u,dx=target.x-active.x;
const cliffAhead=surfaceAt(active.x+Math.sign(dx)*70)>active.y+120;
if(cliffAhead||active.y>WATER-90){selected=6;active.aim=dx>0?-.35:-Math.PI+.35;power=Math.min(.9,Math.abs(dx)/700)}
else{
const dist=Math.abs(dx);
selected=dist<260?2:dist>820?5:(Math.random()<.28?4:0);
if(selected===2){active.aim=Math.atan2(target.y-active.y,dx);power=.7}
else if(selected===5){active.aim=dx>0?-.25:-Math.PI+.25;power=Math.min(1,dist/800)}
else{
let best={err:1e9,a:-.7,p:.7};
for(let ai=0;ai<22;ai++)for(let pi=0;pi<12;pi++){
const a=dx>0?(-1.45+ai*.062):(-Math.PI+1.45-ai*.062),pw=.35+pi*.055,s=weapons[selected].speed*(.45+pw*.75);
let x=active.x,y=active.y,vx=Math.cos(a)*s,vy=Math.sin(a)*s;
for(let t=0;t<2.7;t+=.06){vx+=wind*.62*.06;vy+=620*.06;x+=vx*.06;y+=vy*.06;if(y>=target.y-5){const err=Math.abs(x-target.x)+Math.abs(y-target.y)*.35;if(err<best.err)best={err,a,p:pw};break}}
}
active.aim=best.a+(Math.random()-.5)*.045;power=best.p;
}
}
refreshWeapon();cpuDone=true;setTimeout(()=>{if(game&&!turnLocked)fire()},650);
}
function drawBackground(){
const g=ctx.createLinearGradient(0,0,0,vh);g.addColorStop(0,"#091631");g.addColorStop(.5,"#214873");g.addColorStop(1,"#79b7bd");ctx.fillStyle=g;ctx.fillRect(0,0,vw,vh);
ctx.save();ctx.translate(-camera.x*.025,-camera.y*.01);
stars.forEach(s=>{ctx.globalAlpha=.25+s.a*.55;ctx.fillStyle="#d8f4ff";ctx.beginPath();ctx.arc(s.x,s.y,s.s,0,7);ctx.fill()});
ctx.globalAlpha=.12;ctx.fillStyle="#77c9db";
for(let i=0;i<8;i++){ctx.beginPath();ctx.arc(i*310,620,260,Math.PI,0);ctx.fill()}
ctx.restore();ctx.globalAlpha=1;
}
function transform(){
const sx=(Math.random()-.5)*shake,sy=(Math.random()-.5)*shake;
ctx.translate(vw/2+sx,vh/2+sy);ctx.scale(camera.zoom,camera.zoom);ctx.translate(-camera.x,-camera.y);
}
function drawWorld(){
ctx.drawImage(terrainCanvas,0,0);
ctx.fillStyle="rgba(93,211,239,.62)";ctx.fillRect(0,WATER,W,H-WATER);
ctx.strokeStyle="rgba(210,251,255,.75)";ctx.lineWidth=3;
ctx.beginPath();for(let x=0;x<=W;x+=18){let y=WATER+Math.sin(x*.03+waterPhase)*4;ctx.lineTo(x,y)}ctx.stroke();
ctx.fillStyle="rgba(190,245,255,.13)";
for(let x=0;x<W;x+=80){ctx.beginPath();ctx.arc(x,WATER+18+Math.sin(x+waterPhase)*5,28,0,Math.PI);ctx.fill()}
debris.forEach(d=>{ctx.save();ctx.translate(d.x,d.y);ctx.rotate(d.rot);ctx.fillStyle=d.color;ctx.fillRect(-d.size/2,-d.size/2,d.size,d.size);ctx.restore()});
projectiles.forEach(p=>{if(p.dead)return;ctx.save();ctx.translate(p.x,p.y);ctx.rotate(Math.atan2(p.vy,p.vx));ctx.fillStyle=p.type==="grenade"?"#303844":p.type==="cluster"?"#f472da":"#f6e4a3";ctx.shadowColor="#ff9d43";ctx.shadowBlur=10;ctx.beginPath();ctx.arc(0,0,p.r,0,7);ctx.fill();ctx.restore()});
units.filter(u=>u.alive).forEach(drawUnit);
particles.forEach(drawParticle);texts.forEach(t=>{ctx.globalAlpha=t.life/t.max;ctx.fillStyle=t.color;ctx.font="800 18px 'Baloo 2'";ctx.textAlign="center";ctx.strokeStyle="rgba(0,0,0,.6)";ctx.lineWidth=4;ctx.strokeText(t.text,t.x,t.y);ctx.fillText(t.text,t.x,t.y);ctx.globalAlpha=1});
if(active&&active.alive&&!turnLocked)drawTrajectory();
}
function drawUnit(u){
ctx.save();ctx.translate(u.x,u.y);
if(u.shield>0){ctx.strokeStyle="rgba(155,240,255,.72)";ctx.lineWidth=3;ctx.fillStyle="rgba(100,225,255,.12)";ctx.beginPath();ctx.arc(0,0,24+Math.sin(waterPhase*3)*2,0,7);ctx.fill();ctx.stroke()}
ctx.fillStyle="rgba(0,0,0,.25)";ctx.beginPath();ctx.ellipse(0,17,17,5,0,0,7);ctx.fill();
ctx.fillStyle=teams[u.team].dark;ctx.beginPath();ctx.ellipse(0,5,15,17,0,0,7);ctx.fill();
ctx.fillStyle=teams[u.team].color;ctx.beginPath();ctx.arc(0,-5,15,0,7);ctx.fill();
ctx.fillStyle="#fff";ctx.beginPath();ctx.ellipse(u.face*5,-7,5,6,0,0,7);ctx.fill();
ctx.fillStyle="#17243b";ctx.beginPath();ctx.arc(u.face*6.5,-7,2,0,7);ctx.fill();
ctx.strokeStyle="#4a2530";ctx.lineWidth=2;ctx.beginPath();ctx.arc(u.face*4,-1,4,.2,2.2);ctx.stroke();
if(u===active){ctx.strokeStyle="#fff";ctx.lineWidth=2;ctx.globalAlpha=.8;ctx.beginPath();ctx.arc(0,0,23+Math.sin(waterPhase*4)*2,0,7);ctx.stroke()}
ctx.globalAlpha=1;ctx.fillStyle="rgba(8,13,29,.75)";ctx.fillRect(-19,-30,38,6);ctx.fillStyle=u.hp>50?"#63ed83":u.hp>25?"#ffcf52":"#ff5b55";ctx.fillRect(-19,-30,38*u.hp/100,6);
ctx.fillStyle="#fff";ctx.font="700 10px 'Baloo 2'";ctx.textAlign="center";ctx.fillText(u.name,0,-34);ctx.restore();
}
function drawTrajectory(){
const w=weapons[selected];if(!["rocket","grenade","cluster"].includes(w.type))return;
let x=active.x+Math.cos(active.aim)*23,y=active.y+Math.sin(active.aim)*23,s=w.speed*(.45+power*.75),vx=Math.cos(active.aim)*s,vy=Math.sin(active.aim)*s;
ctx.fillStyle="rgba(255,255,255,.65)";
for(let t=0;t<2.7;t+=.08){vx+=wind*.62*.08;vy+=620*.08;x+=vx*.08;y+=vy*.08;if(solid(x,y)||y>WATER)break;if((t/.08|0)%2===0){ctx.globalAlpha=.7-t*.18;ctx.beginPath();ctx.arc(x,y,2.4,0,7);ctx.fill()}}
ctx.globalAlpha=1;
}
function drawParticle(p){
if(p.beam){ctx.globalAlpha=p.life/p.max;ctx.strokeStyle=p.color;ctx.lineWidth=p.width*(p.life/p.max);ctx.shadowColor=p.color;ctx.shadowBlur=15;ctx.beginPath();ctx.moveTo(p.x,p.y);ctx.lineTo(p.x2,p.y2);ctx.stroke();ctx.shadowBlur=0;ctx.globalAlpha=1;return}
ctx.globalAlpha=Math.max(0,p.life/p.max);ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(p.x,p.y,p.size,0,7);ctx.fill();ctx.globalAlpha=1;
}
function updateEffects(dt){
particles.forEach(p=>{p.life-=dt;if(!p.beam){p.vy+=(p.grav||0)*dt;p.x+=p.vx*dt;p.y+=p.vy*dt;p.vx*=p.drag||1;p.vy*=p.drag||1}});
particles=particles.filter(p=>p.life>0);texts.forEach(t=>{t.life-=dt;t.y-=30*dt});texts=texts.filter(t=>t.life>0);
debris.forEach(d=>{d.life-=dt;d.vy+=520*dt;d.x+=d.vx*dt;d.y+=d.vy*dt;d.rot+=d.vr*dt;
if(solid(d.x,d.y+d.size/2)){d.vy*=-.28;d.vx*=.58;d.y-=4;if(Math.abs(d.vy)<35)d.life=Math.min(d.life,.4)}
if(d.y>WATER){d.vy*=.7;d.vx*=.7;d.life-=dt*2}
});debris=debris.filter(d=>d.life>0&&d.y<H);
}
function update(dt){
waterPhase+=dt*2;shake*=Math.pow(.025,dt);
if(!game)return;
humanControl(dt);cpuThink(dt);
units.forEach(u=>updateUnit(u,dt));projectiles.forEach(p=>{if(!p.dead)updateProjectile(p,dt)});projectiles=projectiles.filter(p=>!p.dead);
updateEffects(dt);
if(!turnLocked){timeLeft-=dt;if(timeLeft<=0){banner("TIME'S UP!","The clock has no chill.");tone(85,.35,"square",.07);endTurn(700)}
if(timeLeft<8&&Math.ceil(timeLeft)!==Math.ceil(timeLeft+dt))tone(880+(8-timeLeft)*35,.05,"square",.04);
}
if(active){camera.tx+=(active.x-camera.tx)*Math.min(1,dt*1.2);camera.ty+=(active.y-camera.ty)*Math.min(1,dt*1.2)}
camera.x+=(camera.tx-camera.x)*Math.min(1,dt*4);camera.y+=(camera.ty-camera.y)*Math.min(1,dt*4);camera.zoom+=(camera.tz-camera.zoom)*Math.min(1,dt*3);
camera.zoom=Math.max(.55,Math.min(1.25,Math.min(vw/900,vh/560)*camera.zoom));
camera.x=Math.max(vw/(2*camera.zoom),Math.min(W-vw/(2*camera.zoom),camera.x));
camera.y=Math.max(vh/(2*camera.zoom),Math.min(H-vh/(2*camera.zoom),camera.y));
updateHud();
}
function loop(now){
requestAnimationFrame(loop);
const dt=Math.min(.033,(now-last)/1000||.016);last=now;
if(now>=freezeUntil)update(dt);
drawBackground();ctx.save();transform();drawWorld();ctx.restore();
}
generateTerrain("island");
camera.zoom=Math.min(vw/W,vh/H)*1.28;
function previewLoop(){
if(game)return;
waterPhase+=.016;drawBackground();ctx.save();ctx.translate(vw/2,vh/2);ctx.scale(camera.zoom,camera.zoom);ctx.translate(-W/2,-H/2);drawWorld();ctx.restore();requestAnimationFrame(previewLoop);
}
previewLoop();
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-sol","provider":"openrouter","persona":"basic","sessionId":"51a11261-4bed-4447-a704-57747fb2a58f","tokensIn":529,"tokensOut":13238,"tokensTotal":13767,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-14T16:46:58.665Z"} -->