← Worms results

Worms

Gemma 4 31b · 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 26,299 bytes · SHA-256 47863a283476
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WormWars: Chaos Artillery</title>
    <style>
        :root {
            --primary: #ffcc00;
            --accent: #ff4400;
            --bg: #1a1a2e;
            --panel: rgba(0, 0, 0, 0.7);
        }

        body, html {
            margin: 0; padding: 0; overflow: hidden;
            background: var(--bg);
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            user-select: none;
        }

        #game-container {
            position: relative;
            width: 100vw;
            height: 100vh;
        }

        canvas { display: block; }

        #hud {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            pointer-events: none;
            display: none;
        }

        .ui-panel {
            position: absolute;
            background: var(--panel);
            color: white;
            padding: 15px;
            border-radius: 12px;
            border: 2px solid var(--primary);
            pointer-events: auto;
            backdrop-filter: blur(5px);
            box-shadow: 0 0 20px rgba(0,0,0,0.5);
        }

        #turn-info {
            top: 20px; left: 50%;
            transform: translateX(-50%);
            text-align: center;
            min-width: 200px;
        }

        #timer-bar-container {
            width: 100%; height: 10px;
            background: #333;
            margin-top: 10px;
            border-radius: 5px;
            overflow: hidden;
        }

        #timer-bar {
            width: 100%; height: 100%;
            background: var(--primary);
            transition: width 0.1s linear;
        }

        #weapon-dock {
            bottom: 20px; left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 10px;
        }

        .weapon-btn {
            width: 60px; height: 60px;
            background: #444;
            border: 2px solid #888;
            color: white;
            border-radius: 8px;
            cursor: pointer;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            font-size: 10px;
            transition: all 0.2s;
        }

        .weapon-btn.active {
            border-color: var(--primary);
            background: #666;
            transform: scale(1.1);
            box-shadow: 0 0 15px var(--primary);
        }

        .weapon-btn:hover { background: #555; }

        #wind-indicator {
            top: 20px; right: 20px;
            width: 120px; text-align: center;
        }

        #wind-arrow {
            font-size: 24px;
            display: inline-block;
            transition: transform 0.3s;
        }

        #menu {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.85);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 100;
            color: white;
        }

        .menu-card {
            background: #2c3e50;
            padding: 30px;
            border-radius: 20px;
            border: 4px solid var(--primary);
            text-align: center;
            max-width: 500px;
        }

        .btn-main {
            padding: 15px 40px;
            font-size: 24px;
            background: var(--primary);
            border: none;
            border-radius: 50px;
            cursor: pointer;
            font-weight: bold;
            color: #000;
            margin-top: 20px;
            transition: transform 0.2s;
        }

        .btn-main:hover { transform: scale(1.1); }

        #combat-log {
            position: absolute;
            bottom: 100px;
            left: 20px;
            max-height: 200px;
            overflow-y: hidden;
            color: #aaa;
            font-size: 14px;
            pointer-events: none;
        }

        .floating-text {
            position: absolute;
            color: #ff4444;
            font-weight: bold;
            pointer-events: none;
            animation: floatUp 1s ease-out forwards;
        }

        @keyframes floatUp {
            0% { transform: translateY(0); opacity: 1; }
            100% { transform: translateY(-50px); opacity: 0; }
        }
    </style>
</head>
<body>

<div id="game-container">
    <canvas id="gameCanvas"></canvas>
    
    <div id="hud">
        <div id="turn-info" class="ui-panel">
            <div id="current-turn-name" style="font-size: 20px; font-weight: bold;">Team 1's Turn</div>
            <div id="timer-bar-container"><div id="timer-bar"></div></div>
        </div>

        <div id="wind-indicator" class="ui-panel">
            <div style="font-size: 12px;">WIND</div>
            <div id="wind-arrow">➔</div>
            <div id="wind-speed">0</div>
        </div>

        <div id="weapon-dock" class="ui-panel">
            <!-- Weapons injected via JS -->
        </div>

        <div id="combat-log"></div>
    </div>

    <div id="menu">
        <div class="menu-card">
            <h1 style="font-size: 48px; margin-top: 0; color: var(--primary);">WormWars</h1>
            <p>Destructible Terrain • Strategic Artillery • Absolute Chaos</p>
            <div style="margin: 20px 0; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; text-align: left;">
                <label>Teams (2-4): <input type="number" id="team-count" value="2" min="2" max="4" style="width: 40px;"></label>
                <label>Worms/Team: <input type="number" id="worm-count" value="3" min="1" max="4" style="width: 40px;"></label>
                <label>CPU Teams: <input type="number" id="cpu-count" value="1" min="0" max="4" style="width: 40px;"></label>
                <label>Map: 
                    <select id="map-type">
                        <option value="islands">Double Islands</option>
                        <option value="single">Single Island</option>
                        <option value="cave">The Cave</option>
                    </select>
                </label>
            </div>
            <button class="btn-main" onclick="startGame()">START BATTLE</button>
        </div>
    </div>
</div>

<script>
/**
 * WormWars Engine
 * Built as a single-file high-performance canvas application.
 * Features: Destructible pixel-terrain, custom physics, and hot-seat AI.
 */

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const terrainCanvas = document.createElement('canvas');
const tctx = terrainCanvas.getContext('2d', { willReadFrequently: true });

// Game Configuration
const CONFIG = {
    gravity: 0.25,
    waterLevel: 0,
    friction: 0.98,
    turnTime: 30, 
    colors: ['#ff4444', '#44ff44', '#4444ff', '#ffff44'],
    weaponry: [
        { id: 'bazooka', name: 'Bazooka', power: 15, radius: 40, type: 'indirect', color: '#aaa', icon: '🚀' },
        { id: 'cluster', name: 'Cluster', power: 10, radius: 30, type: 'indirect', color: '#ff0', icon: '💥' },
        { id: 'sniper', name: 'Sniper', power: 30, radius: 10, type: 'direct', color: '#fff', icon: '🎯' },
        { id: 'airstrike', name: 'Airstrike', power: 20, radius: 80, type: 'map', color: '#f0f', icon: '⚡' },
        { id: 'mine', name: 'Cluster Mine', power: 5, radius: 20, type: 'indirect', color: '#0f0', icon: '💣' },
        { id: 'teleport', name: 'Jump Pad', power: 0, radius: 0, type: 'utility', color: '#0ff', icon: '🌀' }
    ]
};

let state = {
    phase: 'MENU',
    teams: [],
    activeTeamIdx: 0,
    activeWormIdx: 0,
    wind: 0,
    timer: 0,
    camera: { x: 0, y: 0, zoom: 1 },
    input: { mouseX: 0, mouseY: 0, isMouseDown: false, power: 0 },
    selectedWeapon: 0,
    particles: [],
    projectiles: [],
    debris: [],
    shake: 0,
    freeze: 0
};

// Audio System (Procedural)
const audio = {
    ctx: new (window.AudioContext || window.webkitAudioContext)(),
    play(freq, type, duration, vol = 0.1) {
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = type;
        osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
        gain.gain.setValueAtTime(vol, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
        osc.connect(gain);
        gain.connect(this.ctx.destination);
        osc.start();
        osc.stop(this.ctx.currentTime + duration);
    },
    sound(effect) {
        switch(effect) {
            case 'shoot': this.play(150, 'square', 0.2); break;
            case 'explode': this.play(60, 'sawtooth', 0.5, 0.3); break;
            case 'hit': this.play(300, 'sine', 0.1); break;
            case 'click': this.play(800, 'sine', 0.05); break;
            case 'water': this.play(200, 'sine', 0.3); break;
        }
    }
};

class Particle {
    constructor(x, y, color) {
        this.x = x; this.y = y;
        this.vx = (Math.random() - 0.5) * 10;
        this.vy = (Math.random() - 0.5) * 10;
        this.life = 1.0;
        this.color = color;
        this.size = Math.random() * 4 + 2;
    }
    update() {
        this.x += this.vx; this.y += this.vy;
        this.vy += 0.1;
        this.life -= 0.02;
    }
    draw() {
        ctx.globalAlpha = this.life;
        ctx.fillStyle = this.color;
        ctx.fillRect(this.x - state.camera.x, this.y - state.camera.y, this.size, this.size);
        ctx.globalAlpha = 1.0;
    }
}

class Debris {
    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() - 0.5) * 4;
        this.rot = 0;
        this.vr = (Math.random() - 0.5) * 0.2;
    }
    update() {
        this.vx *= 0.99;
        this.vy += CONFIG.gravity;
        this.x += this.vx;
        this.y += this.vy;
        this.rot += this.vr;
    }
    draw() {
        ctx.save();
        ctx.translate(this.x - state.camera.x, this.y - state.camera.y);
        ctx.rotate(this.rot);
        ctx.fillStyle = '#654321';
        ctx.fillRect(-this.w/2, -this.h/2, this.w, this.h);
        ctx.restore();
    }
}

class Projectile {
    constructor(x, y, vx, vy, weapon) {
        this.x = x; this.y = y; this.vx = vx; this.vy = vy;
        this.weapon = weapon;
        this.radius = 4;
        this.dead = false;
    }
    update() {
        this.vx += state.wind * 0.01;
        this.vy += CONFIG.gravity;
        this.x += this.vx;
        this.y += this.vy;

        if (this.y > terrainCanvas.height || this.x < 0 || this.x > terrainCanvas.width) {
            this.explode();
        }

        if (checkCollision(this.x, this.y)) {
            this.explode();
        }
    }
    explode() {
        this.dead = true;
        audio.sound('explode');
        state.shake = this.weapon.radius / 2;
        state.freeze = 5;

        const radius = this.weapon.radius;
        digTerrain(this.x, this.y, radius);
        
        // Damage Worms
        state.teams.forEach(team => {
            team.worms.forEach(worm => {
                const dist = Math.hypot(worm.x - this.x, worm.y - this.y);
                if (dist < radius * 1.5) {
                    const dmg = Math.floor(this.weapon.power * (1 - dist/(radius*1.5)));
                    worm.hp -= dmg;
                    createFloatingText(worm.x, worm.y, `-${dmg}`);
                }
            });
        });

        // Debris creation
        for(let i=0; i<5; i++) {
            state.debris.push(new Debris(this.x, this.y, Math.random()*10+5, Math.random()*10+5));
        }

        // Particles
        for(let i=0; i<20; i++) {
            state.particles.push(new Particle(this.x, this.y, '#ffaa00'));
        }

        // Special Weapon Effects
        if (this.weapon.id === 'cluster') {
            for(let i=0; i<5; i++) {
                state.projectiles.push(new Projectile(this.x, this.y, (Math.random()-0.5)*10, -Math.random()*10, {id:'small', power:5, radius:15}));
            }
        }
    }
    draw() {
        ctx.fillStyle = this.weapon.color;
        ctx.beginPath();
        ctx.arc(this.x - state.camera.x, this.y - state.camera.y, this.radius, 0, Math.PI*2);
        ctx.fill();
    }
}

class Worm {
    constructor(x, y, teamIdx, wormIdx) {
        this.x = x; this.y = y;
        this.teamIdx = teamIdx;
        this.wormIdx = wormIdx;
        this.hp = 100;
        this.vx = 0;
        this.vy = 0;
        this.radius = 6;
        this.isGrounded = false;
        this.moveTimer = 0;
    }
    update() {
        this.vx *= CONFIG.friction;
        this.vy += CONFIG.gravity;
        
        let nextX = this.x + this.vx;
        let nextY = this.y + this.vy;

        // Collision logic
        if (checkCollision(nextX, nextY)) {
            if (this.vy > 5) {
                this.hp -= Math.floor(this.vy);
                createFloatingText(this.x, this.y, 'Ouch!', '#fff');
            }
            this.vy = 0;
            this.vx *= 0.5;
            this.isGrounded = true;
        } else {
            this.isGrounded = false;
        }

        this.x += this.vx;
        this.y += this.vy;

        // Map bounds
        if (this.x < 0) this.x = 0;
        if (this.x > terrainCanvas.width) this.x = terrainCanvas.width;
        
        // Water drowning
        if (this.y > CONFIG.waterLevel + 100) {
            this.hp -= 0.1;
            if (Math.random() < 0.05) audio.sound('water');
        }
    }
    draw() {
        ctx.fillStyle = CONFIG.colors[this.teamIdx];
        ctx.beginPath();
        ctx.arc(this.x - state.camera.x, this.y - state.camera.y, this.radius, 0, Math.PI*2);
        ctx.fill();
        
        // HP Bar
        ctx.fillStyle = '#000';
        ctx.fillRect(this.x - state.camera.x - 10, this.y - state.camera.y - 15, 20, 4);
        ctx.fillStyle = '#0f0';
        ctx.fillRect(this.x - state.camera.x - 10, this.y - state.camera.y - 15, (this.hp/100)*20, 4);
    }
}

// Terrain Systems
function generateTerrain(type) {
    tctx.fillStyle = '#87CEEB'; // Sky
    tctx.fillRect(0, 0, terrainCanvas.width, terrainCanvas.height);
    
    tctx.fillStyle = '#4a7a3a'; // Grass
    tctx.beginPath();

    if (type === 'single') {
        tctx.moveTo(0, terrainCanvas.height);
        tctx.lineTo(0, terrainCanvas.height);
        for(let x=0; x<=terrainCanvas.width; x+=20) {
            let y = terrainCanvas.height * 0.7 + Math.sin(x*0.01)*50 + Math.random()*20;
            if (x > 200 && x < terrainCanvas.width-200) y -= 100;
            tctx.lineTo(x, y);
        }
        tctx.lineTo(terrainCanvas.width, terrainCanvas.height);
    } else if (type === 'islands') {
        tctx.moveTo(0, terrainCanvas.height);
        for(let x=0; x<=terrainCanvas.width; x+=20) {
            let y = terrainCanvas.height * 0.8 + Math.sin(x*0.01)*40;
            if ((x > 200 && x < 600) || (x > 1400 && x < 1800)) y -= 150;
            tctx.lineTo(x, y);
        }
        tctx.lineTo(terrainCanvas.width, terrainCanvas.height);
    } else { // cave
        tctx.moveTo(0, terrainCanvas.height);
        tctx.lineTo(0, 200);
        for(let x=0; x<=terrainCanvas.width; x+=40) {
            tctx.lineTo(x, 200 + Math.random()*100);
        }
        tctx.lineTo(terrainCanvas.width, 200);
        tctx.lineTo(terrainCanvas.width, terrainCanvas.height);
    }
    tctx.fill();
    
    // Water
    CONFIG.waterLevel = terrainCanvas.height * 0.85;
    tctx.fillStyle = 'rgba(0, 0, 255, 0.4)';
    tctx.fillRect(0, CONFIG.waterLevel, terrainCanvas.width, terrainCanvas.height - CONFIG.waterLevel);
}

function digTerrain(cx, cy, radius) {
    tctx.globalCompositeOperation = 'destination-out';
    tctx.beginPath();
    tctx.arc(cx, cy, radius, 0, Math.PI*2);
    tctx.fill();
    tctx.globalCompositeOperation = 'source-over';
}

function checkCollision(x, y) {
    if (x < 0 || x >= terrainCanvas.width || y < 0 || y >= terrainCanvas.height) return false;
    const pixel = tctx.getImageData(x, y, 1, 1).data;
    return pixel[3] > 128; // Alpha channel check
}

// Core Game Loop
function update() {
    if (state.freeze > 0) {
        state.freeze--;
        return;
    }

    // Wind Shift
    if (Math.random() < 0.005) {
        state.wind = (Math.random() - 0.5) * 4;
        document.getElementById('wind-arrow').style.transform = `rotate(${state.wind > 0 ? 0 : 180}deg)`;
        document.getElementById('wind-speed').innerText = Math.abs(state.wind).toFixed(1);
    }

    // Projectiles
    state.projectiles.forEach((p, i) => {
        p.update();
        if (p.dead) state.projectiles.splice(i, 1);
    });

    // Worms
    state.teams.forEach(team => {
        team.worms.forEach(worm => {
            worm.update();
            if (worm.hp <= 0) worm.hp = 0;
        });
    });

    // Debris & Particles
    state.debris = state.debris.filter(d => {
        d.update();
        return d.y < terrainCanvas.height;
    });
    state.particles.forEach((p, i) => {
        p.update();
        if (p.life <= 0) state.particles.splice(i, 1);
    });

    // Turn Management
    if (state.phase === 'ACTION') {
        state.timer -= 1/60;
        const timerBar = document.getElementById('timer-bar');
        const percent = (state.timer / CONFIG.turnTime) * 100;
        timerBar.style.width = percent + '%';
        
        if (percent < 30) timerBar.style.background = 'red';
        else timerBar.style.background = 'var(--primary)';

        if (state.timer <= 0) nextTurn();
    }

    // Camera Smoothing
    const activeWorm = state.teams[state.activeTeamIdx].worms[state.activeWormIdx];
    if (activeWorm) {
        const targetX = activeWorm.x - canvas.width/2;
        const targetY = activeWorm.y - canvas.height/2;
        state.camera.x += (targetX - state.camera.x) * 0.1;
        state.camera.y += (targetY - state.camera.y) * 0.1;
    }

    if (state.shake > 0) {
        state.camera.x += (Math.random()-0.5) * state.shake;
        state.camera.y += (Math.random()-0.5) * state.shake;
        state.shake *= 0.9;
    }
}

function render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // Draw Terrain
    ctx.drawImage(terrainCanvas, -state.camera.x, -state.camera.y);
    
    // Draw Entities
    state.debris.forEach(d => d.draw());
    state.projectiles.forEach(p => p.draw());
    state.particles.forEach(p => p.draw());
    
    state.teams.forEach(team => {
        team.worms.forEach(worm => worm.draw());
    });

    // Aim Line
    if (state.phase === 'ACTION' && state.input.isMouseDown) {
        const worm = state.teams[state.activeTeamIdx].worms[state.activeWormIdx];
        ctx.beginPath();
        ctx.setLineDash([5, 5]);
        ctx.strokeStyle = 'rgba(255,255,255,0.5)';
        let lx = worm.x, ly = worm.y;
        let lvx = state.input.vx, lvy = state.input.vy;
        for(let i=0; i<60; i++) {
            ctx.lineTo(lx - state.camera.x, ly - state.camera.y);
            lvx += state.wind * 0.01;
            lvy += CONFIG.gravity;
            lx += lvx; ly += lvy;
            if (checkCollision(lx, ly)) break;
        }
        ctx.stroke();
        ctx.setLineDash([]);
    }
}

function loop() {
    update();
    render();
    requestAnimationFrame(loop);
}

// Game Logic
function startGame() {
    audio.sound('click');
    const tCount = parseInt(document.getElementById('team-count').value);
    const wCount = parseInt(document.getElementById('worm-count').value);
    const cpuCount = parseInt(document.getElementById('cpu-count').value);
    const mapType = document.getElementById('map-type').value;

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    terrainCanvas.width = 2000;
    terrainCanvas.height = 1000;

    generateTerrain(mapType);

    state.teams = [];
    for(let i=0; i<tCount; i++) {
        const team = {
            name: `Team ${i+1}`,
            isCPU: i < cpuCount,
            worms: []
        };
        for(let j=0; j<wCount; j++) {
            // Find safe spawn
            let sx, sy;
            do {
                sx = 100 + Math.random() * 1800;
                sy = 200 + Math.random() * 600;
            } while (checkCollision(sx, sy));
            team.worms.push(new Worm(sx, sy, i, j));
        }
        state.teams.push(team);
    }

    setupWeaponDock();
    document.getElementById('menu').style.display = 'none';
    document.getElementById('hud').style.display = 'block';
    state.phase = 'ACTION';
    startTurn();
    loop();
}

function setupWeaponDock() {
    const dock = document.getElementById('weapon-dock');
    dock.innerHTML = '';
    CONFIG.weaponry.forEach((wp, i) => {
        const btn = document.createElement('div');
        btn.className = `weapon-btn ${i === 0 ? 'active' : ''}`;
        btn.innerHTML = `<span>${wp.icon}</span><span>${wp.name}</span>`;
        btn.onclick = () => {
            audio.sound('click');
            state.selectedWeapon = i;
            document.querySelectorAll('.weapon-btn').forEach(b => b.classList.remove('active'));
            btn.classList.add('active');
        };
        dock.appendChild(btn);
    });
}

function startTurn() {
    const team = state.teams[state.activeTeamIdx];
    const worm = team.worms[state.activeWormIdx];
    
    document.getElementById('current-turn-name').innerText = `${team.name}'s Turn`;
    document.getElementById('current-turn-name').style.color = CONFIG.colors[state.activeTeamIdx];
    
    state.timer = CONFIG.turnTime;
    
    if (team.isCPU) {
        state.phase = 'CPU';
        setTimeout(runAI, 1000);
    } else {
        state.phase = 'ACTION';
    }
}

function nextTurn() {
    state.activeWormIdx++;
    if (state.activeWormIdx >= state.teams[state.activeTeamIdx].worms.length) {
        state.activeWormIdx = 0;
        state.activeTeamIdx = (state.activeTeamIdx + 1) % state.teams.length;
    }
    
    // Remove dead worms from turn rotation
    if (state.teams[state.activeTeamIdx].worms[state.activeWormIdx].hp <= 0) {
        nextTurn();
    } else {
        startTurn();
    }
}

function runAI() {
    const team = state.teams[state.activeTeamIdx];
    const worm = team.worms[state.activeWormIdx];
    
    // Simple Risk-Assessment AI
    // Find nearest enemy
    let target = null;
    let minDist = Infinity;
    state.teams.forEach(t => {
        if (t === team) return;
        t.worms.forEach(w => {
            if (w.hp > 0) {
                const d = Math.hypot(w.x - worm.x, w.y - worm.y);
                if (d < minDist) { minDist = d; target = w; }
            }
        });
    });

    if (target) {
        const dx = target.x - worm.x;
        const dy = target.y - worm.y;
        const angle = Math.atan2(dy, dx);
        const dist = Math.hypot(dx, dy);
        
        // Calculate projectile force (approximate parabolic)
        const force = Math.sqrt(dist) * 0.8;
        const vx = Math.cos(angle) * force;
        const vy = Math.sin(angle) * force - 5;

        state.input.vx = vx;
        state.input.vy = vy;
        
        setTimeout(() => {
            fireWeapon();
            setTimeout(nextTurn, 2000);
        }, 1000);
    } else {
        nextTurn();
    }
}

function fireWeapon() {
    const worm = state.teams[state.activeTeamIdx].worms[state.activeWormIdx];
    const weapon = CONFIG.weaponry[state.selectedWeapon];
    
    audio.sound('shoot');
    
    if (weapon.type === 'utility' && weapon.id === 'teleport') {
        worm.vy = -10;
        worm.vx += (Math.random()-0.5)*5;
    } else if (weapon.type === 'map' && weapon.id === 'airstrike') {
        const tx = state.input.tx || worm.x + 200;
        const ty = state.input.ty || worm.y;
        for(let i=0; i<3; i++) {
            setTimeout(() => {
                state.projectiles.push(new Projectile(tx + (i-1)*100, 0, 0, 15, weapon));
            }, i * 200);
        }
    } else {
        state.projectiles.push(new Projectile(worm.x, worm.y, state.input.vx, state.input.vy, weapon));
    }
}

function createFloatingText(x, y, text, color = '#ff4444') {
    const div = document.createElement('div');
    div.className = 'floating-text';
    div.innerText = text;
    div.style.left = (x - state.camera.x) + 'px'; // Simplified, will flicker on cam move
    div.style.top = (y - state.camera.y) + 'px';
    div.style.color = color;
    document.body.appendChild(div);
    setTimeout(() => div.remove(), 1000);
}

// Controls
window.onmousedown = (e) => {
    if (state.phase !== 'ACTION') return;
    state.input.isMouseDown = true;
    state.input.startX = e.clientX;
    state.input.startY = e.clientY;
    audio.sound('click');
};

window.onmousemove = (e) => {
    if (!state.input.isMouseDown) return;
    const dx = e.clientX - state.input.startX;
    const dy = e.clientY - state.input.startY;
    state.input.vx = -dx * 0.1;
    state.input.vy = -dy * 0.1;
};

window.onmouseup = (e) => {
    if (!state.input.isMouseDown) return;
    state.input.isMouseDown = false;
    
    // Set airstrike target if that's the weapon
    if (CONFIG.weaponry[state.selectedWeapon].id === 'airstrike') {
        state.input.tx = e.clientX + state.camera.x;
        state.input.ty = e.clientY + state.camera.y;
    }
    
    fireWeapon();
};

window.onkeydown = (e) => {
    if (state.phase !== 'ACTION') return;
    const worm = state.teams[state.activeTeamIdx].worms[state.activeWormIdx];
    if (e.key === 'a' || e.key === 'ArrowLeft') worm.vx = -2;
    if (e.key === 'd' || e.key === 'ArrowRight') worm.vx = 2;
    if (e.key === 'w' || e.key === 'ArrowUp') {
        if (worm.isGrounded) {
            worm.vy = -6;
            audio.sound('click');
        }
    }
};

window.onresize = () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
};

</script>
</body>
</html>
<!-- agent-meta {"model":"gemma-4-31b","provider":"cerebras","persona":"basic","sessionId":"77f96d2b-952c-45e2-8ed9-c5a26b5995b3","tokensIn":559,"tokensOut":8580,"tokensTotal":9139,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-14T21:56:53.578Z"} -->