🎮ArcadeLab

电子斗蛐蛐 - 学科大战(修复版)

by EpicCoder88
1138 lines44.4 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>电子斗蛐蛐 - 学科大战(修复版)</title>
<style>
    body { margin: 0; padding: 0; background-color: #1a1a1a; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; font-family: sans-serif; color: white; }
    .hidden { display: none !important; }
    #menu { text-align: center; }
    #startBtn { padding: 15px 40px; font-size: 24px; background-color: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer; transition: background 0.3s; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
    #startBtn:hover { background-color: #45a049; }
    #selectScreen { display: flex; flex-direction: column; align-items: center; gap: 20px; }
    #selectScreen h2 { font-weight: normal; color: #aaa; font-size: 18px; letter-spacing: 2px; }
    #heroList { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; max-width: 90vw; }
    .hero-card { padding: 15px 30px; background-color: #2a2a2a; border: 2px solid #444; border-radius: 8px; font-size: 18px; cursor: pointer; transition: all 0.2s ease; user-select: none; }
    .hero-card:hover { background-color: #3a3a3a; }
    .hero-card.selected { border-color: #00ffcc; background-color: rgba(0, 255, 204, 0.1); color: #00ffcc; box-shadow: 0 0 15px rgba(0, 255, 204, 0.4); }
    #gameCanvas { background-color: #2a2a2a; box-shadow: 0 0 20px rgba(0,0,0,0.5); max-width: 100vw; max-height: 100vh; }
    #gameOverScreen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.8); display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 30px; z-index: 10; backdrop-filter: blur(4px); }
    #gameOverText { font-size: 36px; font-weight: bold; text-shadow: 0 0 20px rgba(0, 255, 204, 0.5); }
    #restartBtn { padding: 15px 40px; font-size: 20px; background-color: #00ffcc; color: #111; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; transition: transform 0.2s, background 0.3s; }
    #restartBtn:hover { background-color: #00ccaa; transform: scale(1.05); }
    #errorBox { position: fixed; top: 0; left: 0; width: 100%; background: #b00020; color: #fff; padding: 10px; font-size: 12px; font-family: monospace; z-index: 9999; word-break: break-all; }
</style>
</head>
<body>

<div id="errorBox" class="hidden"></div>

<div id="menu"><button id="startBtn">开始游戏</button></div>
<div id="selectScreen" class="hidden"><h2>选择两名英雄进行对决</h2><div id="heroList"></div></div>
<canvas id="gameCanvas" class="hidden" width="800" height="600"></canvas>
<div id="gameOverScreen" class="hidden">
    <div id="gameOverText">胜利!</div>
    <button id="restartBtn">重新选择英雄</button>
</div>

<script>
window.onerror = function (msg, url, line, col, err) {
    var box = document.getElementById('errorBox');
    if (box) {
        box.classList.remove('hidden');
        box.textContent = 'JS错误:' + msg + ' (行 ' + line + ')';
    }
    return false;
};

(function () {
'use strict';

// ==========================================
// ⚙️ 常量
// ==========================================
var CONFIG = { GRID_SIZE: 40, GRID_COLS: 24, GRID_ROWS: 18, PADDING: 60 };
var ARENA_WIDTH  = CONFIG.GRID_COLS * CONFIG.GRID_SIZE;
var ARENA_HEIGHT = CONFIG.GRID_ROWS * CONFIG.GRID_SIZE;
var MAP_WIDTH    = ARENA_WIDTH  + CONFIG.PADDING * 2;
var MAP_HEIGHT   = ARENA_HEIGHT + CONFIG.PADDING * 2;
var ARENA_X = CONFIG.PADDING;
var ARENA_Y = CONFIG.PADDING;
var MAX_DT = 0.05;

// ==========================================
// 📖 英雄图鉴
// ==========================================
var HERO_LIST = [
    { id: 'newton',    name: '牛顿',   color: '#dddddd', symbol: '牛', desc: '阵地法师,三棱镜真伤,苹果雨续航' },
    { id: 'lavoisier', name: '拉瓦锡', color: '#ff8c00', symbol: '化', desc: '化学消耗,质量守恒,炼金炸药' },
    { id: 'mendel',    name: '孟德尔', color: '#33cc33', symbol: '豆', desc: '召唤豌豆,概率遗传,铺场压制' },
    { id: 'taylor',    name: '泰勒',   color: '#3366ff', symbol: '数', desc: '数学狙击,余项标记,高阶逼近' },
    { id: 'starling',  name: '斯他林', color: '#ff44cc', symbol: '激', desc: '内分泌续航,稳态调节,激素风暴' }
];
var HERO_MAP = {};
HERO_LIST.forEach(function (h) { HERO_MAP[h.id] = h; });

// ==========================================
// 🎮 全局状态
// ==========================================
var gameState = 'menu';
var selectedHeroes = [];
var heroEntities  = [];
var projectiles   = [];
var damageTexts   = [];
var impactEffects = [];
var muzzleFlashes = [];
var newtonPrisms  = [];
var apples        = [];
var satellites    = [];
var oxygenFields  = [];
var massParticles = [];
var peaShooters   = [];
var hormonePools  = [];
var geneSeeds     = [];
var lastTime = 0;
var rafId = 0;
var camera = { x: 0, y: 0 };
var globalAppleTimer = 0;

// ==========================================
// 🖱️ DOM 引用
// ==========================================
var menu           = document.getElementById('menu');
var startBtn       = document.getElementById('startBtn');
var selectScreen   = document.getElementById('selectScreen');
var heroList       = document.getElementById('heroList');
var canvas         = document.getElementById('gameCanvas');
var gameOverScreen = document.getElementById('gameOverScreen');
var gameOverText   = document.getElementById('gameOverText');
var restartBtn     = document.getElementById('restartBtn');
var ctx            = canvas.getContext('2d');

// ==========================================
// 🎬 事件绑定
// ==========================================
startBtn.addEventListener('click', function () {
    if (gameState !== 'menu') return;
    menu.classList.add('hidden');
    selectScreen.classList.remove('hidden');
    gameState = 'select';
    renderHeroSelection();
});

restartBtn.addEventListener('click', function () {
    cancelAnimationFrame(rafId);
    gameOverScreen.classList.add('hidden');
    canvas.classList.add('hidden');
    selectScreen.classList.remove('hidden');
    gameState = 'select';
    resetBattleState();
    renderHeroSelection();
});

// ==========================================
// 🎴 英雄选择
// ==========================================
function renderHeroSelection() {
    heroList.innerHTML = '';
    selectedHeroes = [];
    HERO_LIST.forEach(function (hero) {
        var card = document.createElement('div');
        card.className = 'hero-card';
        card.textContent = hero.name;
        card.title = hero.desc;
        card.dataset.id = hero.id;
        card.addEventListener('click', function () { toggleHeroSelection(hero.id, card); });
        heroList.appendChild(card);
    });
}

function toggleHeroSelection(id, card) {
    if (gameState !== 'select') return;
    if (selectedHeroes.indexOf(id) >= 0) {
        selectedHeroes = selectedHeroes.filter(function (x) { return x !== id; });
        card.classList.remove('selected');
        return;
    }
    if (selectedHeroes.length >= 2) return;
    selectedHeroes.push(id);
    card.classList.add('selected');
    if (selectedHeroes.length === 2) {
        gameState = 'starting';
        setTimeout(function () {
            if (gameState === 'starting') startBattle();
        }, 400);
    }
}

// ==========================================
// 🧹 重置
// ==========================================
function resetEffects() {
    projectiles = []; damageTexts = []; impactEffects = []; muzzleFlashes = [];
    newtonPrisms = []; apples = []; satellites = []; oxygenFields = [];
    massParticles = []; peaShooters = []; hormonePools = []; geneSeeds = [];
    globalAppleTimer = 0;
}
function resetBattleState() {
    heroEntities = [];
    resetEffects();
}

// ==========================================
// ⚔️ 开始战斗
// ==========================================
function startBattle() {
    gameState = 'playing';
    selectScreen.classList.add('hidden');
    canvas.classList.remove('hidden');

    camera.x = MAP_WIDTH / 2 - canvas.width / 2;
    camera.y = MAP_HEIGHT / 2 - canvas.height / 2;

    heroEntities = selectedHeroes.map(function (id, index) { return createHero(id, index); });
    resetEffects();
    lastTime = performance.now();

    cancelAnimationFrame(rafId);
    rafId = requestAnimationFrame(gameLoop);
}

// ==========================================
// 🏭 英雄工厂
// ==========================================
function createHero(id, index) {
    var heroData = HERO_MAP[id] || { name: '未知', color: '#fff', symbol: '?' };
    var base = {
        id: id, name: heroData.name, color: heroData.color, symbol: heroData.symbol,
        hp: 1000, maxHp: 1000, radius: 20, speed: 120, baseSpeed: 120,
        attackTimer: 0, attackAnimTimer: 0, stunTimer: 0,
        wanderTimer: 0, wanderAngle: Math.random() * Math.PI * 2,
        facingRight: index === 0,
        x: index === 0 ? ARENA_X + 200 : ARENA_X + ARENA_WIDTH - 200,
        y: ARENA_Y + ARENA_HEIGHT / 2,
        dr: 0,
        prismCooldown: 0, satelliteTimer: 0, peaTimer: 0,
        attackCooldown: 0, ultCooldown: 0,
        taylorUltCooldown: 0, hormoneStormCooldown: 0,
        preciseStacks: 0, geneSeed: 0,
        taylorMark: 0, taylorMarkTimer: 0,
        hormoneStacks: 0, hormoneTimer: 0,
        slowTimer: 0,
        wallPushTimer: 0, wallPushX: 0, wallPushY: 0,
        prismDamageAccum: 0, burnDamageAccum: 0, healAccum: 0,
        overdriveTimer: 0,
        attackPower: 1
    };

    if (id === 'newton') {
        base.hp = 1100; base.maxHp = 1100; base.speed = 120; base.baseSpeed = 120;
    } else if (id === 'lavoisier') {
        base.hp = 950; base.maxHp = 950; base.speed = 115; base.baseSpeed = 115;
        base.attackCooldown = 6000; base.ultCooldown = 12000;
    } else if (id === 'mendel') {
        base.hp = 850; base.maxHp = 850; base.speed = 105; base.baseSpeed = 105;
        base.attackCooldown = 2000;
    } else if (id === 'taylor') {
        base.hp = 800; base.maxHp = 800; base.speed = 110; base.baseSpeed = 110;
        base.attackCooldown = 1500; base.ultCooldown = 15000;
    } else if (id === 'starling') {
        base.hp = 900; base.maxHp = 900; base.speed = 115; base.baseSpeed = 115;
        base.attackCooldown = 2000; base.hormoneStormCooldown = 15000;
    }
    return base;
}

// ==========================================
// 🔄 主循环
// ==========================================
function gameLoop(timestamp) {
    if (gameState !== 'playing') return;
    var dt = Math.min((timestamp - lastTime) / 1000, MAX_DT);
    lastTime = timestamp;

    update(dt);
    if (gameState === 'playing') {
        updateCamera(dt);
        render();
        rafId = requestAnimationFrame(gameLoop);
    }
}

// ==========================================
// 📷 摄像机
// ==========================================
function updateCamera(dt) {
    if (heroEntities.length < 2) return;
    var a = heroEntities[0], b = heroEntities[1];
    var cx = (a.x + b.x) / 2, cy = (a.y + b.y) / 2;
    if (!isFinite(cx)) cx = MAP_WIDTH / 2;
    if (!isFinite(cy)) cy = MAP_HEIGHT / 2;
    var targetX = Math.max(0, Math.min(MAP_WIDTH - canvas.width, cx - canvas.width / 2));
    var targetY = Math.max(0, Math.min(MAP_HEIGHT - canvas.height, cy - canvas.height / 2));
    var k = Math.min(1, 8 * dt);
    camera.x += (targetX - camera.x) * k;
    camera.y += (targetY - camera.y) * k;
}

// ==========================================
// 🏃 逻辑更新
// ==========================================
function update(dt) {
    var alive = heroEntities.filter(function (h) { return h.hp > 0; });
    if (alive.length <= 1 && heroEntities.length >= 2) {
        gameState = 'gameover';
        showGameOver(alive);
        return;
    }

    for (var i = 0; i < heroEntities.length; i++) {
        var h = heroEntities[i];
        if (h.hp <= 0) continue;
        if (h.attackTimer > 0) h.attackTimer -= dt * 1000;
        if (h.attackAnimTimer > 0) h.attackAnimTimer -= dt * 1000;
        if (h.stunTimer > 0) h.stunTimer -= dt * 1000;
        if (h.prismCooldown > 0) h.prismCooldown -= dt * 1000;
        if (h.satelliteTimer > 0) h.satelliteTimer -= dt * 1000;
        if (h.ultCooldown > 0) h.ultCooldown -= dt * 1000;
        if (h.taylorUltCooldown > 0) h.taylorUltCooldown -= dt * 1000;
        if (h.hormoneStormCooldown > 0) h.hormoneStormCooldown -= dt * 1000;

        if (h.taylorMarkTimer > 0) {
            h.taylorMarkTimer -= dt;
            if (h.taylorMarkTimer <= 0) h.taylorMark = 0;
        }
        if (h.hormoneTimer > 0) {
            h.hormoneTimer -= dt;
            if (h.hormoneTimer <= 0) {
                h.hormoneStacks = 0;
                h.attackPower = 1;
            }
        }
        if (h.slowTimer > 0) {
            h.slowTimer -= dt * 1000;
            if (h.slowTimer <= 0) h.speed = h.baseSpeed;
        }
        if (h.wallPushTimer > 0) {
            h.wallPushTimer -= dt;
            h.x += h.wallPushX * h.speed * dt;
            h.y += h.wallPushY * h.speed * dt;
        }
        if (h.overdriveTimer > 0) {
            h.overdriveTimer -= dt * 1000;
            if (h.overdriveTimer <= 0 && h.id === 'starling') h.speed = h.baseSpeed;
        }
        if (h.id === 'starling' && h.overdriveTimer <= 0) {
            if (h.hp < h.maxHp * 0.5) h.speed = h.baseSpeed * 1.2;
            else h.speed = h.baseSpeed;
        }
    }

    // 苹果雨
    globalAppleTimer += dt * 1000;
    if (globalAppleTimer >= 30000) {
        globalAppleTimer = 0;
        if (apples.length < 20) {
            for (var k = 0; k < 10; k++) {
                var tx = ARENA_X + Math.random() * ARENA_WIDTH;
                var ty = ARENA_Y + Math.random() * ARENA_HEIGHT;
                var hit = false;
                for (var m = 0; m < heroEntities.length; m++) {
                    var hero = heroEntities[m];
                    if (hero.hp > 0 && Math.hypot(hero.x - tx, hero.y - ty) < hero.radius + 8) {
                        applyDamage(hero, 40, null, null, false, false);
                        hit = true;
                    }
                }
                if (!hit) apples.push({ x: tx, y: ty, radius: 8 });
            }
        }
    }

    // 苹果拾取
    for (var ai = apples.length - 1; ai >= 0; ai--) {
        var ap = apples[ai];
        for (var hi = 0; hi < heroEntities.length; hi++) {
            var hh = heroEntities[hi];
            if (hh.hp <= 0) continue;
            if (Math.hypot(hh.x - ap.x, hh.y - ap.y) < hh.radius + ap.radius) {
                var heal = hh.id === 'newton' ? 50 : 10;
                hh.hp = Math.min(hh.maxHp, hh.hp + heal);
                addDamageText(hh.x, hh.y - 20, '+' + heal, '#00ff00');
                apples.splice(ai, 1);
                break;
            }
        }
    }

    // 牛顿卫星
    for (var ni = 0; ni < heroEntities.length; ni++) {
        var nh = heroEntities[ni];
        if (nh.id !== 'newton' || nh.hp <= 0) continue;
        nh.satelliteTimer += dt * 1000;
        if (nh.satelliteTimer >= 5000) {
            nh.satelliteTimer = 0;
            var mySats = satellites.filter(function (s) { return s.ownerId === nh.id; });
            if (mySats.length < 5 && Math.random() < 0.4) {
                satellites.push({
                    ownerId: nh.id,
                    angle: Math.random() * Math.PI * 2,
                    distance: CONFIG.GRID_SIZE * 2,
                    x: nh.x, y: nh.y
                });
            }
        }
    }

    // 孟德尔豌豆
    for (var mi = 0; mi < heroEntities.length; mi++) {
        var mh = heroEntities[mi];
        if (mh.id !== 'mendel' || mh.hp <= 0) continue;
        mh.peaTimer = (mh.peaTimer || 0) + dt * 1000;
        if (mh.peaTimer >= 8000) {
            mh.peaTimer = 0;
            var myPeas = peaShooters.filter(function (p) { return p.ownerId === mh.id; });
            if (myPeas.length < 4) {
                peaShooters.push({
                    ownerId: mh.id,
                    x: mh.x + (Math.random() - 0.5) * 120,
                    y: mh.y + (Math.random() - 0.5) * 120,
                    hp: 50, maxHp: 50, attackTimer: 0
                });
            }
        }
    }

    // 英雄 AI
    for (var hi2 = 0; hi2 < heroEntities.length; hi2++) {
        var h2 = heroEntities[hi2];
        if (h2.hp <= 0 || h2.stunTimer > 0) continue;
        updateHeroAI(h2, dt);
    }

    updateNewtonPrisms(dt);
    updateSatellites(dt);
    updateOxygenFields(dt);
    updateMassParticles(dt);
    updatePeaShooters(dt);
    updateHormonePools(dt);
    updateGeneSeeds(dt);

    resolveCollisions();
    updateProjectiles(dt);
    updateEffects(dt);
}

// ==========================================
// 🧠 英雄 AI
// ==========================================
function updateHeroAI(hero, dt) {
    var target = null, minDist = Infinity;
    for (var i = 0; i < heroEntities.length; i++) {
        var other = heroEntities[i];
        if (other === hero || other.hp <= 0) continue;
        var d = Math.hypot(other.x - hero.x, other.y - hero.y);
        if (d < minDist) { minDist = d; target = other; }
    }
    if (!target) return;

    hero.facingRight = target.x > hero.x;

    if (hero.wallPushTimer > 0) return;

    var idealMin = 3 * CONFIG.GRID_SIZE, idealMax = 6 * CONFIG.GRID_SIZE;
    if (hero.id === 'taylor')  { idealMin = 5 * CONFIG.GRID_SIZE; idealMax = 7 * CONFIG.GRID_SIZE; }
    if (hero.id === 'mendel')  { idealMin = 2 * CONFIG.GRID_SIZE; idealMax = 4 * CONFIG.GRID_SIZE; }
    if (hero.id === 'starling') { idealMin = 4 * CONFIG.GRID_SIZE; idealMax = 6 * CONFIG.GRID_SIZE; }

    if (minDist > idealMax) {
        var dx = target.x - hero.x, dy = target.y - hero.y, dist = Math.hypot(dx, dy) || 1;
        hero.x += (dx / dist) * hero.speed * dt;
        hero.y += (dy / dist) * hero.speed * dt;
    } else if (minDist < idealMin) {
        var dx2 = hero.x - target.x, dy2 = hero.y - target.y, dist2 = Math.hypot(dx2, dy2) || 1;
        hero.x += (dx2 / dist2) * hero.speed * dt;
        hero.y += (dy2 / dist2) * hero.speed * dt;
    } else {
        var dx3 = target.x - hero.x, dy3 = target.y - hero.y, dist3 = Math.hypot(dx3, dy3) || 1;
        hero.x += (-dy3 / dist3) * hero.speed * 0.5 * dt;
        hero.y += (dx3 / dist3) * hero.speed * 0.5 * dt;
    }
    constrainToArena(hero);

    if (hero.id === 'newton') {
        if (hero.prismCooldown <= 0) {
            var angle = Math.atan2(target.y - hero.y, target.x - hero.x);
            newtonPrisms.push({
                ownerId: hero.id,
                x: hero.x + Math.cos(angle) * CONFIG.GRID_SIZE * 3,
                y: hero.y + Math.sin(angle) * CONFIG.GRID_SIZE * 3,
                angle: angle,
                sweep: 0,
                life: 10
            });
            hero.prismCooldown = 5000;
        }
        return;
    }

    if (hero.attackTimer <= 0) {
        var cd = 2000;
        if (hero.id === 'lavoisier') cd = 6000;
        else if (hero.id === 'mendel') cd = 2000;
        else if (hero.id === 'taylor') cd = 1500;
        else if (hero.id === 'starling') cd = 2000;

        doAttack(hero, target);
        hero.attackTimer = cd;
    }
}

// ==========================================
// 💥 攻击分发
// ==========================================
function doAttack(hero, target) {
    if (!target) return;
    hero.attackAnimTimer = 300;

    if (hero.id === 'lavoisier') {
        spawnProjectile({
            x: hero.x, y: hero.y, startX: hero.x, startY: hero.y,
            target: target, speed: 500, damage: 20, type: 'oxygen', ownerId: hero.id
        });
    } else if (hero.id === 'mendel') {
        spawnProjectile({
            x: hero.x, y: hero.y, startX: hero.x, startY: hero.y,
            target: target, speed: 700, damage: 15, type: 'pea', ownerId: hero.id
        });
    } else if (hero.id === 'taylor') {
        spawnProjectile({
            x: hero.x, y: hero.y, startX: hero.x, startY: hero.y,
            target: target, speed: 900, damage: 5, type: 'taylor', ownerId: hero.id
        });
    } else if (hero.id === 'starling') {
        spawnProjectile({
            x: hero.x, y: hero.y, startX: hero.x, startY: hero.y,
            target: target, speed: 800, damage: 15, type: 'hormone', ownerId: hero.id
        });
    }
}

// ==========================================
// 🔺 三棱镜
// ==========================================
function updateNewtonPrisms(dt) {
    for (var i = newtonPrisms.length - 1; i >= 0; i--) {
        var p = newtonPrisms[i];
        p.life -= dt;
        if (p.life <= 0) { newtonPrisms.splice(i, 1); continue; }

        p.sweep += 0.8 * dt;
        if (p.sweep > Math.PI / 3) p.sweep = -Math.PI / 3;

        for (var j = 0; j < heroEntities.length; j++) {
            var h = heroEntities[j];
            if (h.hp <= 0 || h.id === p.ownerId) continue;
            var dx = h.x - p.x, dy = h.y - p.y;
            var dist = Math.hypot(dx, dy);
            if (dist > 240) continue;
            var angleToHero = Math.atan2(dy, dx);
            var diff = Math.abs(angleToHero - p.angle - p.sweep);
            diff = Math.min(diff, Math.PI * 2 - diff);
            if (diff < Math.PI / 6) {
                h.prismDamageAccum = (h.prismDamageAccum || 0) + 60 * dt;
                if (h.prismDamageAccum >= 1) {
                    var dmg = Math.floor(h.prismDamageAccum);
                    h.prismDamageAccum -= dmg;
                    applyDamage(h, dmg, null, null, true, false);
                }
            }
        }
    }
}

// ==========================================
// 🛰️ 卫星
// ==========================================
function updateSatellites(dt) {
    for (var i = satellites.length - 1; i >= 0; i--) {
        var s = satellites[i];
        var owner = heroEntities.find(function (h) { return h.id === s.ownerId; });
        if (!owner || owner.hp <= 0) { satellites.splice(i, 1); continue; }
        s.angle += 2.5 * dt;
        s.x = owner.x + Math.cos(s.angle) * s.distance;
        s.y = owner.y + Math.sin(s.angle) * s.distance;

        for (var j = 0; j < heroEntities.length; j++) {
            var h = heroEntities[j];
            if (h.hp <= 0 || h.id === s.ownerId) continue;
            if (Math.hypot(h.x - s.x, h.y - s.y) < h.radius + 10) {
                applyDamage(h, 40, owner, null);
                h.stunTimer = 1000;
                addImpact(s.x, s.y, '#00ffff', 0.5);
                satellites.splice(i, 1);
                break;
            }
        }
    }
}

// ==========================================
// 🔥 氧气燃烧领域
// ==========================================
function updateOxygenFields(dt) {
    for (var i = oxygenFields.length - 1; i >= 0; i--) {
        var f = oxygenFields[i];
        f.life -= dt;
        if (f.life <= 0) { oxygenFields.splice(i, 1); continue; }

        for (var j = 0; j < heroEntities.length; j++) {
            var h = heroEntities[j];
            if (h.hp <= 0 || h.id === f.ownerId) continue;
            if (Math.hypot(h.x - f.x, h.y - f.y) < f.radius) {
                h.burnDamageAccum = (h.burnDamageAccum || 0) + 15 * dt;
                if (h.burnDamageAccum >= 1) {
                    var dmg = Math.floor(h.burnDamageAccum);
                    h.burnDamageAccum -= dmg;
                    var attacker = null;
                    for (var k = 0; k < heroEntities.length; k++) {
                        if (heroEntities[k].id === f.ownerId) { attacker = heroEntities[k]; break; }
                    }
                    applyDamage(h, dmg, attacker, null, false, false);
                }
                h.slowTimer = 500;
                h.speed = h.baseSpeed * 0.7;
            }
        }
    }
}

// ==========================================
// ⚖️ 质量微粒
// ==========================================
function updateMassParticles(dt) {
    for (var i = massParticles.length - 1; i >= 0; i--) {
        var p = massParticles[i];
        p.life -= dt;
        if (p.life <= 0) { massParticles.splice(i, 1); continue; }

        var lavoisier = null;
        for (var j = 0; j < heroEntities.length; j++) {
            if (heroEntities[j].id === 'lavoisier' && heroEntities[j].hp > 0) { lavoisier = heroEntities[j]; break; }
        }
        if (lavoisier && Math.hypot(lavoisier.x - p.x, lavoisier.y - p.y) < lavoisier.radius + 15) {
            lavoisier.hp = Math.min(lavoisier.maxHp, lavoisier.hp + 30);
            lavoisier.preciseStacks = Math.min(3, lavoisier.preciseStacks + 1);
            massParticles.splice(i, 1);
            addDamageText(lavoisier.x, lavoisier.y - 20, '+30', '#00ff00');
        }
    }
}

// ==========================================
// 🌱 豌豆射手
// ==========================================
function updatePeaShooters(dt) {
    for (var i = peaShooters.length - 1; i >= 0; i--) {
        var p = peaShooters[i];
        p.hp -= dt * 5;
        if (p.hp <= 0) {
            peaShooters.splice(i, 1);
            var mendel = null;
            for (var j = 0; j < heroEntities.length; j++) {
                if (heroEntities[j].id === 'mendel' && heroEntities[j].hp > 0) { mendel = heroEntities[j]; break; }
            }
            if (mendel) {
                if (Math.random() < 0.5) {
                    mendel.hp = Math.min(mendel.maxHp, mendel.hp + 100);
                    addDamageText(mendel.x, mendel.y - 20, '显性纯合 +100', '#00ff00');
                } else {
                    geneSeeds.push({ x: p.x, y: p.y, radius: 10 });
                }
            }
            continue;
        }
        p.attackTimer += dt * 1000;
        if (p.attackTimer >= 1500) {
            p.attackTimer = 0;
            var target = null, minDist = Infinity;
            for (var k = 0; k < heroEntities.length; k++) {
                var h = heroEntities[k];
                if (h.hp <= 0 || h.id === p.ownerId) continue;
                var d = Math.hypot(h.x - p.x, h.y - p.y);
                if (d < minDist) { minDist = d; target = h; }
            }
            if (target) {
                spawnProjectile({
                    x: p.x, y: p.y, startX: p.x, startY: p.y,
                    target: target, speed: 800, damage: 10, type: 'pea', ownerId: p.ownerId
                });
            }
        }
    }
}

// ==========================================
// 💧 激素池
// ==========================================
function updateHormonePools(dt) {
    for (var i = hormonePools.length - 1; i >= 0; i--) {
        var p = hormonePools[i];
        p.life -= dt;
        if (p.life <= 0) { hormonePools.splice(i, 1); continue; }

        var starling = null;
        for (var j = 0; j < heroEntities.length; j++) {
            if (heroEntities[j].id === 'starling' && heroEntities[j].hp > 0) { starling = heroEntities[j]; break; }
        }
        if (starling && Math.hypot(starling.x - p.x, starling.y - p.y) < 40) {
            var healPerSec = starling.hp > starling.maxHp * 0.8 ? 60 : 30;
            starling.healAccum = (starling.healAccum || 0) + healPerSec * dt;
            if (starling.healAccum >= 1) {
                var heal = Math.floor(starling.healAccum);
                starling.healAccum -= heal;
                starling.hp = Math.min(starling.maxHp, starling.hp + heal);
                addDamageText(starling.x, starling.y - 20, '+' + heal, '#00ff00');
            }
        }
    }
}

// ==========================================
// 🧬 基因种子
// ==========================================
function updateGeneSeeds(dt) {
    for (var i = geneSeeds.length - 1; i >= 0; i--) {
        var g = geneSeeds[i];
        var mendel = null;
        for (var j = 0; j < heroEntities.length; j++) {
            if (heroEntities[j].id === 'mendel' && heroEntities[j].hp > 0) { mendel = heroEntities[j]; break; }
        }
        if (mendel && Math.hypot(mendel.x - g.x, mendel.y - g.y) < mendel.radius + 15) {
            mendel.geneSeed = 1;
            geneSeeds.splice(i, 1);
            addDamageText(mendel.x, mendel.y - 20, '超级豌豆就绪!', '#ffff00');
        }
    }
}

// ==========================================
// 🚀 弹道更新
// ==========================================
function updateProjectiles(dt) {
    for (var i = projectiles.length - 1; i >= 0; i--) {
        var p = projectiles[i];

        if (p.target && p.target.hp <= 0) {
            if (p.type === 'oxygen') {
                oxygenFields.push({ x: p.target.x, y: p.target.y, radius: 120, life: 5, ownerId: p.ownerId });
            }
            projectiles.splice(i, 1);
            continue;
        }

        var hitTarget = null;
        if (p.target) {
            var t = p.target;
            var pdx = t.x - p.x, pdy = t.y - p.y;
            var pDist = Math.hypot(pdx, pdy) || 1;
            if (pDist < t.radius + 10) {
                hitTarget = t;
            } else {
                p.x += (pdx / pDist) * p.speed * dt;
                p.y += (pdy / pDist) * p.speed * dt;
            }
        }

        if (hitTarget) {
            var attacker = null;
            if (p.ownerId) {
                for (var k = 0; k < heroEntities.length; k++) {
                    if (heroEntities[k].id === p.ownerId) { attacker = heroEntities[k]; break; }
                }
            }

            if (p.type === 'oxygen') {
                applyDamage(hitTarget, p.damage, attacker, p);
                oxygenFields.push({ x: p.x, y: p.y, radius: 120, life: 5, ownerId: p.ownerId });
                addImpact(p.x, p.y, '#ff8c00', 0.5);
            } else if (p.type === 'pea') {
                applyDamage(hitTarget, p.damage, attacker, p);
                if (Math.random() < 0.75) {
                    addDamageText(hitTarget.x, hitTarget.y, '显性!', '#33cc33');
                } else {
                    hitTarget.slowTimer = 2000;
                    hitTarget.speed = hitTarget.baseSpeed * 0.7;
                    addDamageText(hitTarget.x, hitTarget.y, '隐性!', '#33cc33');
                }
            } else if (p.type === 'taylor') {
                applyDamage(hitTarget, p.damage, attacker, p);
                var kdx = hitTarget.x - p.x, kdy = hitTarget.y - p.y;
                var kd = Math.hypot(kdx, kdy) || 1;
                hitTarget.x += (kdx / kd) * 8;
                hitTarget.y += (kdy / kd) * 8;
                constrainToArena(hitTarget);
                hitTarget.taylorMark = Math.min(5, (hitTarget.taylorMark || 0) + 1);
                hitTarget.taylorMarkTimer = 3;
                addDamageText(hitTarget.x, hitTarget.y, '余项+' + hitTarget.taylorMark, '#3366ff');
            } else if (p.type === 'hormone') {
                applyDamage(hitTarget, p.damage, attacker, p);
                hitTarget.hormoneStacks = Math.min(5, (hitTarget.hormoneStacks || 0) + 1);
                hitTarget.hormoneTimer = 3;
                hitTarget.attackPower = 1 - hitTarget.hormoneStacks * 0.1;
                addDamageText(hitTarget.x, hitTarget.y, '失调x' + hitTarget.hormoneStacks, '#ff44cc');
            } else {
                applyDamage(hitTarget, p.damage, attacker, p);
            }

            if (p.type === 'taylor' && hitTarget.taylorMark >= 5 && attacker && attacker.hp > 0 && attacker.taylorUltCooldown <= 0) {
                var dist = Math.hypot(hitTarget.x - attacker.x, hitTarget.y - attacker.y);
                var dmg = 100 + (dist / CONFIG.GRID_SIZE) * 15;
                dmg = Math.min(250, dmg);
                applyDamage(hitTarget, dmg, attacker, null, true, false);
                hitTarget.taylorMark = 0;
                hitTarget.taylorMarkTimer = 0;
                attacker.taylorUltCooldown = 15000;
                addImpact(hitTarget.x, hitTarget.y, '#3366ff', 1.0);
                addDamageText(hitTarget.x, hitTarget.y - 30, '高阶逼近 ' + Math.round(dmg), '#3366ff');
            }

            projectiles.splice(i, 1);
        }
    }
}

// ==========================================
// 🧱 碰撞与边界
// ==========================================
function resolveCollisions() {
    for (var i = 0; i < heroEntities.length; i++) {
        for (var j = i + 1; j < heroEntities.length; j++) {
            var h1 = heroEntities[i], h2 = heroEntities[j];
            if (h1.hp <= 0 || h2.hp <= 0) continue;
            var dx = h2.x - h1.x, dy = h2.y - h1.y;
            var dist = Math.hypot(dx, dy);
            var minDist = h1.radius + h2.radius;
            if (dist < 0.01) { dx = 0.01; dy = 0; dist = 0.01; }
            if (dist < minDist) {
                var overlap = minDist - dist;
                var nx = dx / dist, ny = dy / dist;
                h1.x -= nx * overlap * 0.5; h1.y -= ny * overlap * 0.5;
                h2.x += nx * overlap * 0.5; h2.y += ny * overlap * 0.5;
            }
        }
    }
    for (var k = 0; k < heroEntities.length; k++) constrainToArena(heroEntities[k]);
}

function constrainToArena(h) {
    if (!isFinite(h.x)) h.x = ARENA_X + ARENA_WIDTH / 2;
    if (!isFinite(h.y)) h.y = ARENA_Y + ARENA_HEIGHT / 2;
    var pushed = false, pushX = 0, pushY = 0;
    if (h.x < ARENA_X + h.radius) { h.x = ARENA_X + h.radius; pushX = 1; pushed = true; }
    if (h.x > ARENA_X + ARENA_WIDTH  - h.radius) { h.x = ARENA_X + ARENA_WIDTH  - h.radius; pushX = -1; pushed = true; }
    if (h.y < ARENA_Y + h.radius) { h.y = ARENA_Y + h.radius; pushY = 1; pushed = true; }
    if (h.y > ARENA_Y + ARENA_HEIGHT - h.radius) { h.y = ARENA_Y + ARENA_HEIGHT - h.radius; pushY = -1; pushed = true; }
    if (pushed) {
        h.wallPushTimer = 0.25;
        h.wallPushX = pushX;
        h.wallPushY = pushY;
    }
}

// ==========================================
// 💥 伤害结算
// ==========================================
function applyDamage(target, amount, attacker, projectile, isTrueDamage, silent) {
    if (!target || target.hp <= 0) return { absorbed: false, killed: false };
    if (!isFinite(amount)) amount = 0;
    amount = Math.max(0, amount);

    if (!isTrueDamage && attacker && attacker.attackPower !== undefined && attacker.attackPower < 1) {
        amount *= attacker.attackPower;
    }
    if (!isTrueDamage && target.dr > 0) amount = amount * (1 - target.dr / 100);

    target.hp -= amount;

    if (!silent && amount >= 0.5) {
        addDamageText(target.x, target.y, Math.round(amount), isTrueDamage ? '#ff00ff' : '#ff6666');
    }

    if (attacker && attacker.id === 'lavoisier' && amount > 0 && Math.random() < 0.5) {
        if (massParticles.length < 15) {
            massParticles.push({
                x: target.x + (Math.random() - 0.5) * 30,
                y: target.y + (Math.random() - 0.5) * 30,
                life: 10
            });
        }
    }

    if (target.hormoneStacks >= 5 && target.id !== 'starling') {
        var starling = null;
        for (var i = 0; i < heroEntities.length; i++) {
            if (heroEntities[i].id === 'starling' && heroEntities[i].hp > 0) { starling = heroEntities[i]; break; }
        }
        if (starling && starling.hormoneStormCooldown <= 0) {
            starling.hormoneStormCooldown = 15000;
            for (var pi = 0; pi < hormonePools.length; pi++) {
                var pool = hormonePools[pi];
                for (var hi = 0; hi < heroEntities.length; hi++) {
                    var hh = heroEntities[hi];
                    if (hh.hp > 0 && hh.id !== 'starling' && Math.hypot(hh.x - pool.x, hh.y - pool.y) < 100) {
                        var extra = 80 + target.hormoneStacks * 10;
                        applyDamage(hh, extra, starling, null, true, false);
                    }
                }
            }
            target.hormoneStacks = 0;
            target.hormoneTimer = 0;
            target.attackPower = 1;
            starling.speed = starling.baseSpeed * 2;
            starling.overdriveTimer = 5000;
            addDamageText(starling.x, starling.y - 30, '内分泌风暴!', '#ff44cc');
        }
    }

    return { absorbed: false, killed: target.hp <= 0 };
}

// ==========================================
// 🚀 弹道工厂
// ==========================================
function spawnProjectile(opts) {
    var p = { x: 0, y: 0, startX: 0, startY: 0, target: null, speed: 600, damage: 0, type: 'bullet', ownerId: null };
    for (var k in opts) {
        if (opts.hasOwnProperty(k)) p[k] = opts[k];
    }
    projectiles.push(p);
}

// ==========================================
// ✨ 特效
// ==========================================
function updateEffects(dt) {
    for (var i = muzzleFlashes.length - 1; i >= 0; i--) { muzzleFlashes[i].life -= dt; if (muzzleFlashes[i].life <= 0) muzzleFlashes.splice(i, 1); }
    for (var j = impactEffects.length - 1; j >= 0; j--) { impactEffects[j].life -= dt; if (impactEffects[j].life <= 0) impactEffects.splice(j, 1); }
    for (var k = damageTexts.length - 1; k >= 0; k--) { damageTexts[k].y -= 30 * dt; damageTexts[k].life -= dt; if (damageTexts[k].life <= 0) damageTexts.splice(k, 1); }
}

function addDamageText(x, y, amount, color) {
    if (damageTexts.length > 100) damageTexts.shift();
    damageTexts.push({ x: x, y: y - 20, text: String(amount), color: color || '#fff', life: 1.0 });
}
function addImpact(x, y, color, maxLife) {
    maxLife = maxLife || 0.3;
    impactEffects.push({ x: x, y: y, color: color, life: maxLife, maxLife: maxLife });
}

function showGameOver(survivors) {
    gameOverScreen.classList.remove('hidden');
    canvas.classList.add('hidden');
    if (survivors.length === 1) {
        gameOverText.innerText = survivors[0].name + ' 胜利!';
        gameOverText.style.color = survivors[0].color;
    } else {
        gameOverText.innerText = '同归于尽!';
        gameOverText.style.color = '#fff';
    }
}

// ==========================================
// 🎨 渲染
// ==========================================
function render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.translate(-camera.x, -camera.y);
    drawArena();
    drawOxygenFields();
    drawHormonePools();
    drawGeneSeeds();
    drawApples();
    drawNewtonPrisms();
    drawSatellites();
    drawMassParticles();
    drawPeaShooters();
    drawEffects();
    drawProjectiles();
    drawEntities();
    drawDamageTexts();
    ctx.restore();
}

function drawArena() {
    ctx.fillStyle = '#2a2a2a'; ctx.fillRect(0, 0, MAP_WIDTH, MAP_HEIGHT);
    ctx.fillStyle = '#3a3a3a'; ctx.fillRect(ARENA_X, ARENA_Y, ARENA_WIDTH, ARENA_HEIGHT);
    ctx.strokeStyle = '#00ffcc'; ctx.lineWidth = 6; ctx.strokeRect(ARENA_X, ARENA_Y, ARENA_WIDTH, ARENA_HEIGHT);
    ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)'; ctx.lineWidth = 1; ctx.beginPath();
    for (var i = 1; i < CONFIG.GRID_COLS; i++) { var x = ARENA_X + i * CONFIG.GRID_SIZE; ctx.moveTo(x, ARENA_Y); ctx.lineTo(x, ARENA_Y + ARENA_HEIGHT); }
    for (var j = 1; j < CONFIG.GRID_ROWS; j++) { var y = ARENA_Y + j * CONFIG.GRID_SIZE; ctx.moveTo(ARENA_X, y); ctx.lineTo(ARENA_X + ARENA_WIDTH, y); }
    ctx.stroke();
}

function drawOxygenFields() {
    for (var i = 0; i < oxygenFields.length; i++) {
        var f = oxygenFields[i];
        ctx.beginPath(); ctx.arc(f.x, f.y, f.radius, 0, Math.PI * 2);
        ctx.fillStyle = 'rgba(255, 140, 0, 0.18)'; ctx.fill();
        ctx.strokeStyle = '#ff8c00'; ctx.lineWidth = 2; ctx.stroke();
    }
}
function drawHormonePools() {
    for (var i = 0; i < hormonePools.length; i++) {
        var p = hormonePools[i];
        ctx.beginPath(); ctx.arc(p.x, p.y, 40, 0, Math.PI * 2);
        ctx.fillStyle = 'rgba(255, 68, 204, 0.2)'; ctx.fill();
        ctx.strokeStyle = '#ff44cc'; ctx.lineWidth = 2; ctx.stroke();
    }
}
function drawGeneSeeds() {
    for (var i = 0; i < geneSeeds.length; i++) {
        var g = geneSeeds[i];
        ctx.beginPath(); ctx.arc(g.x, g.y, g.radius, 0, Math.PI * 2);
        ctx.fillStyle = '#ffff00'; ctx.fill();
        ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke();
    }
}
function drawApples() {
    for (var i = 0; i < apples.length; i++) {
        var a = apples[i];
        ctx.beginPath(); ctx.arc(a.x, a.y, a.radius, 0, Math.PI * 2);
        ctx.fillStyle = '#ff4444'; ctx.fill();
        ctx.strokeStyle = '#ff0000'; ctx.lineWidth = 2; ctx.stroke();
        ctx.fillStyle = '#00ff00';
        ctx.beginPath(); ctx.ellipse(a.x, a.y - a.radius - 2, 4, 2, Math.PI / 4, 0, Math.PI * 2);
        ctx.fill();
    }
}
function drawNewtonPrisms() {
    for (var i = 0; i < newtonPrisms.length; i++) {
        var p = newtonPrisms[i];
        ctx.save();
        ctx.translate(p.x, p.y);
        ctx.rotate(p.angle);
        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.arc(0, 0, 240, -Math.PI / 6 + p.sweep, Math.PI / 6 + p.sweep);
        ctx.closePath();
        ctx.fillStyle = 'rgba(255, 255, 255, 0.08)';
        ctx.fill();
        for (var s = 0; s < 7; s++) {
            var angle = -Math.PI / 6 + p.sweep + (s / 6) * (Math.PI / 3);
            ctx.beginPath();
            ctx.moveTo(0, 0);
            ctx.lineTo(Math.cos(angle) * 240, Math.sin(angle) * 240);
            var hue = s * 51;
            ctx.strokeStyle = 'hsl(' + hue + ', 100%, 50%)';
            ctx.lineWidth = 2;
            ctx.stroke();
        }
        ctx.beginPath();
        ctx.moveTo(0, -12); ctx.lineTo(10, 8); ctx.lineTo(-10, 8); ctx.closePath();
        ctx.fillStyle = '#ffffff'; ctx.fill();
        ctx.strokeStyle = '#aaa'; ctx.lineWidth = 1; ctx.stroke();
        ctx.restore();
    }
}
function drawSatellites() {
    for (var i = 0; i < satellites.length; i++) {
        var s = satellites[i];
        ctx.beginPath(); ctx.arc(s.x, s.y, 8, 0, Math.PI * 2);
        ctx.fillStyle = '#00ffff'; ctx.fill();
        ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke();
    }
}
function drawMassParticles() {
    for (var i = 0; i < massParticles.length; i++) {
        var p = massParticles[i];
        ctx.beginPath(); ctx.arc(p.x, p.y, 6, 0, Math.PI * 2);
        ctx.fillStyle = '#ffffff'; ctx.fill();
        ctx.strokeStyle = '#ff8c00'; ctx.lineWidth = 2; ctx.stroke();
    }
}
function drawPeaShooters() {
    for (var i = 0; i < peaShooters.length; i++) {
        var p = peaShooters[i];
        ctx.fillStyle = '#33cc33';
        ctx.fillRect(p.x - 6, p.y - 6, 12, 12);
        ctx.strokeStyle = '#fff'; ctx.lineWidth = 1;
        ctx.strokeRect(p.x - 6, p.y - 6, 12, 12);
        var hpP = p.hp / p.maxHp;
        ctx.fillStyle = '#333'; ctx.fillRect(p.x - 15, p.y - 15, 30, 3);
        ctx.fillStyle = '#00ff00'; ctx.fillRect(p.x - 15, p.y - 15, 30 * hpP, 3);
    }
}
function drawEffects() {
    for (var i = 0; i < impactEffects.length; i++) {
        var e = impactEffects[i];
        var t = 1 - Math.max(0, Math.min(1, e.life / (e.maxLife || 0.3)));
        ctx.beginPath(); ctx.arc(e.x, e.y, Math.max(0.5, t * 20), 0, Math.PI * 2);
        ctx.strokeStyle = e.color || '#fff'; ctx.lineWidth = 3; ctx.stroke();
    }
}
function drawProjectiles() {
    for (var i = 0; i < projectiles.length; i++) {
        var p = projectiles[i];
        ctx.beginPath();
        ctx.moveTo(p.startX, p.startY);
        ctx.lineTo(p.x, p.y);
        var color = '#fff';
        if (p.type === 'pea') color = '#33cc33';
        else if (p.type === 'taylor') color = '#3366ff';
        else if (p.type === 'hormone') color = '#ff44cc';
        else if (p.type === 'oxygen') color = '#ff8c00';
        ctx.strokeStyle = color;
        ctx.lineWidth = 4; ctx.stroke();
        ctx.beginPath(); ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
        ctx.fillStyle = '#fff'; ctx.fill();
    }
}
function drawEntities() {
    for (var i = 0; i < heroEntities.length; i++) {
        var entity = heroEntities[i];
        ctx.globalAlpha = entity.hp > 0 ? 1.0 : 0.2;
        ctx.save();
        ctx.translate(entity.x, entity.y);
        ctx.beginPath(); ctx.arc(0, 0, entity.radius, 0, Math.PI * 2);
        ctx.fillStyle = entity.stunTimer > 0 ? '#ffff00' : entity.color;
        ctx.fill();
        ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 2; ctx.stroke();
        ctx.fillStyle = '#111';
        ctx.font = 'bold 16px sans-serif';
        ctx.textAlign = 'center';
        ctx.fillText(entity.symbol || '?', 0, 6);
        ctx.restore();

        var barW = 50, barH = 5;
        var barX = entity.x - barW / 2;
        var barY = entity.y - entity.radius - 15;
        var hpP = Math.max(0, entity.hp) / entity.maxHp;
        ctx.fillStyle = '#333'; ctx.fillRect(barX, barY, barW, barH);
        ctx.fillStyle = hpP > 0.3 ? '#00ff00' : '#ff0000';
        ctx.fillRect(barX, barY, barW * hpP, barH);
        ctx.fillStyle = '#fff';
        ctx.font = '11px sans-serif';
        ctx.textAlign = 'center';
        ctx.fillText(entity.name, entity.x, barY - 5);
        ctx.globalAlpha = 1.0;
    }
}
function drawDamageTexts() {
    for (var i = 0; i < damageTexts.length; i++) {
        var txt = damageTexts[i];
        ctx.fillStyle = txt.color;
        ctx.font = 'bold 12px sans-serif';
        ctx.textAlign = 'center';
        ctx.globalAlpha = Math.max(0, Math.min(1, txt.life));
        ctx.fillText(txt.text, txt.x, txt.y);
    }
    ctx.globalAlpha = 1.0;
}

})();
</script>
</body>
</html>

Game Source: 电子斗蛐蛐 - 学科大战(修复版)

Creator: EpicCoder88

Libraries: none

Complexity: complex (1138 lines, 44.4 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-epiccoder88-muhveaix" to link back to the original. Then publish at arcadelab.ai/publish.