🎮ArcadeLab

电子斗蛐蛐 - 学科大战(钱学森版)

by EpicCoder88
464 lines19.9 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: 15px; flex-wrap: wrap; justify-content: center; max-width: 90vw; }
    .hero-card { padding: 12px 24px; background-color: #2a2a2a; border: 2px solid #444; border-radius: 8px; font-size: 16px; 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); }
    .hero-card.locked { opacity: 0.4; cursor: not-allowed; }
    #confirmBtn { padding: 14px 50px; font-size: 20px; background-color: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 6px rgba(0,0,0,0.3); font-weight: bold; }
    #confirmBtn:hover:not(:disabled) { background-color: #45a049; transform: scale(1.05); }
    #confirmBtn:disabled { background-color: #444; color: #888; cursor: not-allowed; transform: none; }
    #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); }
</style>
</head>
<body>

<div id="menu"><button id="startBtn">开始游戏</button></div>
<div id="selectScreen" class="hidden">
    <h2>选择 2~5 名英雄进行混战</h2>
    <div id="heroList"></div>
    <button id="confirmBtn" disabled>至少选 2 名英雄</button>
</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>
(function () {
'use strict';

const CONFIG = { GRID_SIZE: 40, GRID_COLS: 24, GRID_ROWS: 18, PADDING: 60 };
const ARENA_WIDTH  = CONFIG.GRID_COLS * CONFIG.GRID_SIZE;
const ARENA_HEIGHT = CONFIG.GRID_ROWS * CONFIG.GRID_SIZE;
const MAP_WIDTH    = ARENA_WIDTH  + CONFIG.PADDING * 2;
const MAP_HEIGHT   = ARENA_HEIGHT + CONFIG.PADDING * 2;
const ARENA_X = CONFIG.PADDING;
const ARENA_Y = CONFIG.PADDING;
const MAX_DT = 0.05;
const WANDER_SPEED  = 1.5;
const WANDER_CHANGE = 1.5;

const HERO_LIST = [
    { id: 'newton',     name: '牛顿',   color: '#dddddd', maxHp: 1100, speed: 120 },
    { id: 'lavoisier',  name: '拉瓦锡', color: '#ff8c00', maxHp: 950,  speed: 115 },
    { id: 'mendel',     name: '孟德尔', color: '#33cc33', maxHp: 850,  speed: 105 },
    { id: 'taylor',     name: '泰勒',   color: '#3366ff', maxHp: 800,  speed: 110 },
    { id: 'starling',   name: '斯他林', color: '#ff44cc', maxHp: 900,  speed: 115 },
    { id: 'faraday',    name: '法拉第', color: '#ffd700', maxHp: 1000, speed: 110 },
    { id: 'hawking',    name: '霍金',   color: '#8a2be2', maxHp: 900,  speed: 100 },
    { id: 'kepler',     name: '开普勒', color: '#4169e1', maxHp: 900,  speed: 110 },
    { id: 'zuchongzhi', name: '祖冲之', color: '#00ced1', maxHp: 850,  speed: 108 },
    { id: 'schrodinger',name: '薛定谔', color: '#9370db', maxHp: 800,  speed: 115 },
    { id: 'qianxuesen', name: '钱学森', color: '#ff3300', maxHp: 1100, speed: 110 }
];
const HERO_MAP = {};
HERO_LIST.forEach(h => { HERO_MAP[h.id] = h; });

let gameState = 'menu';
let selectedHeroes = [];
let heroEntities  = [];
let projectiles   = [];
let damageTexts   = [];
let impactEffects = [];
let newtonPrisms  = [];
let apples        = [];
let oxygenFields  = [];
let massParticles = [];
let peaShooters   = [];
let hormonePools  = [];
let geneSeeds     = [];
let wormholes     = [];
let faradayCages  = [];
let radiationZones = [];
let keplerPlanets = [];
let cutCircleBullets = [];
let piShields     = [];
let schrodingerClones = [];
let catBoxes      = [];
// 钱学森专属
let qianMissiles  = [];
let qianBombings  = [];
let lastTime = 0;
let rafId = 0;
const camera = { x: 0, y: 0 };

const menu           = document.getElementById('menu');
const startBtn       = document.getElementById('startBtn');
const selectScreen   = document.getElementById('selectScreen');
const heroList       = document.getElementById('heroList');
const canvas         = document.getElementById('gameCanvas');
const gameOverScreen = document.getElementById('gameOverScreen');
const gameOverText   = document.getElementById('gameOverText');
const restartBtn     = document.getElementById('restartBtn');
const confirmBtn     = document.getElementById('confirmBtn');
const ctx            = canvas.getContext('2d');

// ==========================================
// 🎬 事件
// ==========================================
startBtn.addEventListener('click', () => {
    menu.classList.add('hidden');
    selectScreen.classList.remove('hidden');
    gameState = 'select';
    renderHeroSelection();
});

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

confirmBtn.addEventListener('click', () => {
    if (gameState !== 'select' || selectedHeroes.length < 2) return;
    gameState = 'starting';
    startBattle();
});

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

function toggleHeroSelection(id, card) {
    if (gameState !== 'select') return;
    if (selectedHeroes.includes(id)) {
        selectedHeroes = selectedHeroes.filter(x => x !== id);
        card.classList.remove('selected');
    } else {
        if (selectedHeroes.length >= 5) return;
        selectedHeroes.push(id);
        card.classList.add('selected');
    }
    const allCards = heroList.querySelectorAll('.hero-card');
    allCards.forEach(c => {
        const cid = c.dataset.id;
        if (selectedHeroes.length >= 5 && !selectedHeroes.includes(cid)) {
            c.classList.add('locked');
        } else {
            c.classList.remove('locked');
        }
    });
    updateConfirmButton();
}

function updateConfirmButton() {
    if (selectedHeroes.length >= 2) {
        confirmBtn.disabled = false;
        confirmBtn.textContent = '开始对决 (' + selectedHeroes.length + ' 人混战)';
    } else {
        confirmBtn.disabled = true;
        confirmBtn.textContent = '至少选 2 名英雄';
    }
}

// ==========================================
// 🧹 重置
// ==========================================
function resetEffects() {
    projectiles = []; damageTexts = []; impactEffects = [];
    newtonPrisms = []; apples = []; oxygenFields = [];
    massParticles = []; peaShooters = []; hormonePools = []; geneSeeds = [];
    wormholes = []; faradayCages = []; radiationZones = [];
    keplerPlanets = []; cutCircleBullets = []; piShields = [];
    schrodingerClones = []; catBoxes = [];
    qianMissiles = []; qianBombings = [];
}
function resetBattleState() { heroEntities = []; resetEffects(); }

// ==========================================
// ⚔️ 开始战斗
// ==========================================
function startBattle() {
    gameState = 'playing';
    selectScreen.classList.add('hidden');
    canvas.classList.add('hidden');
    canvas.classList.remove('hidden');
    camera.x = MAP_WIDTH / 2 - canvas.width / 2;
    camera.y = MAP_HEIGHT / 2 - canvas.height / 2;

    const total = selectedHeroes.length;
    heroEntities = selectedHeroes.map((id, index) => createHero(id, index, total));
    resetEffects();
    lastTime = performance.now();

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

// ==========================================
// 🏭 英雄工厂
// ==========================================
function createHero(id, index, total) {
    const data = HERO_MAP[id];
    const hero = {
        id: id, name: data.name, color: data.color,
        hp: data.maxHp, maxHp: data.maxHp,
        radius: 20, speed: data.speed,
        x: 0, y: 0,
        facingRight: true,
        stunTimer: 0, wallPushTimer: 0, wallPushX: 0, wallPushY: 0,
        wanderAngle: Math.random() * Math.PI * 2,
        wanderTimer: 0, wanderX: 0, wanderY: 0,
        attackTimer: 0, attackAnimTimer: 0, slowTimer: 0,
        attackPower: 1, dr: 0,
        chargeStacks: 0, chargeTimer: 0,
        radiationStacks: 0, radiationTimer: 0
    };

    const startAngle = -Math.PI / 2;
    const angle = startAngle + (index / total) * Math.PI * 2;
    const radius = Math.min(ARENA_WIDTH, ARENA_HEIGHT) * 0.33;
    const cx = ARENA_X + ARENA_WIDTH / 2;
    const cy = ARENA_Y + ARENA_HEIGHT / 2;
    hero.x = cx + Math.cos(angle) * radius;
    hero.y = cy + Math.sin(angle) * radius;

    if (id === 'newton') {
        hero.prismCooldown = 5000; hero.appleTimer = 30000;
        hero.satellites = []; hero.satelliteTimer = 5000;
    } else if (id === 'lavoisier') {
        hero.oxygenCooldown = 6000; hero.preciseStacks = 0; hero.ultCooldown = 12000;
    } else if (id === 'mendel') {
        hero.attackCooldown = 2000; hero.peaTimer = 8000; hero.geneSeed = 0;
    } else if (id === 'taylor') {
        hero.attackCooldown = 1500; hero.taylorMark = 0; hero.taylorMarkTimer = 0; hero.ultCooldown = 0;
    } else if (id === 'starling') {
        hero.attackCooldown = 2000; hero.hormoneStormCooldown = 15000; hero.overdriveTimer = 0;
    } else if (id === 'faraday') {
        hero.attackCooldown = 1800; hero.shieldCooldown = 8000; hero.shieldTimer = 0; hero.cageCooldown = 15000;
    } else if (id === 'hawking') {
        hero.attackCooldown = 2000; hero.wheelchairCooldown = 8000; hero.wheelchairTimer = 0;
        hero.wheelchairDirX = 0; hero.wheelchairDirY = 0; hero.wheelchairDamageAccum = 0;
        hero.wheelchairHitMap = {}; hero.wormholeTimer = 5000; hero.radiationCooldown = 18000;
        hero.noDamageTimer = 0;
    } else if (id === 'kepler') {
        hero.planetCooldown = 3000; hero.focusCooldown = 8000; hero.planetCount = 0;
    } else if (id === 'zuchongzhi') {
        hero.attackCooldown = 1600; hero.piCooldown = 8000; hero.piShieldTimer = 0;
    } else if (id === 'schrodinger') {
        hero.attackCooldown = 1700; hero.cloneCooldown = 6000; hero.cloneTimer = 0;
        hero.collapseCooldown = 12000; hero.catBoxCooldown = 15000;
    } else if (id === 'qianxuesen') {
        hero.attackCooldown = 3000;  // 小导弹冷却3秒
        hero.rocketArmyCooldown = 10000; // 呼叫火箭军冷却10秒
        hero.rocketArmyActive = 0;       // 轰炸持续时间
        hero.bombingSpawnTimer = 0;
    }
    return hero;
}

function findNearestEnemy(hero) {
    let target = null, minDist = Infinity;
    for (const h of heroEntities) {
        if (h === hero || h.hp <= 0) continue;
        const d = Math.hypot(h.x - hero.x, h.y - hero.y);
        if (d < minDist) { minDist = d; target = h; }
    }
    return target;
}

// ==========================================
// 🔄 主循环
// ==========================================
function gameLoop(timestamp) {
    if (gameState !== 'playing') return;
    try {
        const dt = Math.min((timestamp - lastTime) / 1000, MAX_DT);
        if (!isFinite(dt) || dt <= 0) { rafId = requestAnimationFrame(gameLoop); return; }
        lastTime = timestamp;
        update(dt);
        if (gameState === 'playing') { updateCamera(dt); render(); }
    } catch (e) { console.error("Game Loop Error:", e); }
    rafId = requestAnimationFrame(gameLoop);
}

function updateCamera(dt) {
    let cx = 0, cy = 0, n = 0;
    for (const h of heroEntities) {
        if (h.hp <= 0) continue;
        cx += h.x; cy += h.y; n++;
    }
    if (n === 0) return;
    cx /= n; cy /= n;
    if (!isFinite(cx)) cx = MAP_WIDTH / 2;
    if (!isFinite(cy)) cy = MAP_HEIGHT / 2;
    const targetX = Math.max(0, Math.min(MAP_WIDTH - canvas.width, cx - canvas.width / 2));
    const targetY = Math.max(0, Math.min(MAP_HEIGHT - canvas.height, cy - canvas.height / 2));
    const k = Math.min(1, 8 * dt);
    camera.x += (targetX - camera.x) * k;
    camera.y += (targetY - camera.y) * k;
}

function updateWander(h, dt) {
    h.wanderTimer -= dt;
    if (h.wanderTimer <= 0) {
        h.wanderTimer = WANDER_CHANGE + Math.random() * 1.0;
        h.wanderAngle += (Math.random() - 0.5) * Math.PI * 3.0;
    }
    const wobble = Math.sin(performance.now() / 400 + h.wanderAngle) * 0.5;
    h.wanderX = Math.cos(h.wanderAngle + wobble) * h.speed * WANDER_SPEED;
    h.wanderY = Math.sin(h.wanderAngle + wobble) * h.speed * WANDER_SPEED;
}

function moveTowardTarget(hero, target, dt) {
    const dx = target.x - hero.x, dy = target.y - hero.y;
    const distPx = Math.hypot(dx, dy) || 1;
    const dirX = dx / distPx, dirY = dy / distPx;
    hero.facingRight = dirX > 0;

    let moveX = dirX * hero.speed;
    let moveY = dirY * hero.speed;

    const minDist = hero.radius + target.radius + 15;
    if (distPx < minDist) {
        moveX = -dirX * hero.speed * 0.4;
        moveY = -dirY * hero.speed * 0.4;
    }

    const tangent = Math.sin(performance.now() / 1400 + hero.wanderAngle * 2) * 0.9;
    moveX += -dirY * tangent * hero.speed;
    moveY +=  dirX * tangent * hero.speed;

    hero.x += (moveX + hero.wanderX) * dt;
    hero.y += (moveY + hero.wanderY) * dt;
    constrainToArena(hero);

    return { distPx, distGrid: distPx / CONFIG.GRID_SIZE, dirX, dirY };
}

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

    for (const h of heroEntities) {
        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.slowTimer > 0) { h.slowTimer -= dt * 1000; if (h.slowTimer <= 0) h.speed = HERO_MAP[h.id].speed; }
        if (h.wallPushTimer > 0) { h.wallPushTimer -= dt; h.x += h.wallPushX * h.speed * dt; h.y += h.wallPushY * h.speed * dt; }
        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.overdriveTimer > 0) { h.overdriveTimer -= dt * 1000; if (h.overdriveTimer <= 0 && h.id === 'starling') h.speed = HERO_MAP[h.id].speed; }
        if (h.id === 'starling' && h.overdriveTimer <= 0) {
            h.speed = (h.hp < h.maxHp * 0.5) ? HERO_MAP[h.id].speed * 1.2 : HERO_MAP[h.id].speed;
        }

        if (h.id === 'faraday') {
            if (h.shieldTimer > 0) h.shieldTimer -= dt * 1000;
            if (h.shieldCooldown > 0) h.shieldCooldown -= dt * 1000;
            if (h.shieldCooldown <= 0 && h.shieldTimer <= 0) {
                h.shieldCooldown = 8000; h.shieldTimer = 5000;
                addDamageText(h.x, h.y - 50, '⚡ 力场护盾!', '#ffd700');
            }
            if (h.cageCooldown > 0) h.cageCooldown -= dt * 1000;
        }

        if (h.id === 'hawking') {
            if (h.wheelchairCooldown > 0) h.wheelchairCooldown -= dt * 1000;
            if (h.wormholeTimer > 0) h.wormholeTimer -= dt * 1000;
            if (h.radiationCooldown > 0) h.radiationCooldown -= dt * 1000;
            if (h.hp < h.maxHp * 0.5 && h.radiationCooldown <= 0) {
                h.radiationCooldown = 18000;
                radiationZones.push({ x: h.x, y: h.y, radius: 120, life: 4, ownerId: h.id });
                addDamageText(h.x, h.y - 50, '🕳️ 霍金辐射!', '#8a2be2');
            }
            h.noDamageTimer += dt;
            if (h.noDamageTimer >= 5 && h.hp < h.maxHp) {
                h.hp = Math.min(h.maxHp, h.hp + 10 * dt);
            }
        }

        if (h.id === 'kepler') {
            if (h.planetCooldown > 0) h.planetCooldown -= dt * 1000;
            if (h.focusCooldown > 0) h.focusCooldown -= dt * 1000;
        }
        if (h.id === 'zuchongzhi') {
            if (h.piCooldown > 0) h.piCooldown -= dt * 1000;
            if (h.piShieldTimer > 0) h.piShieldTimer -= dt * 1000;
        }
        if (h.id === 'schrodinger') {
            if (h.cloneCooldown > 0) h.cloneCooldown -= dt * 1000;
            if (h.cloneTimer > 0) h.cloneTimer -= dt * 1000;
            if (h.collapseCooldown > 0) h.collapseCooldown -= dt * 1000;
            if (h.catBoxCooldown > 0) h.catBoxCooldown -= dt * 1000;
        }
        // 钱学森专属计时
        if (h.id === 'qianxuesen') {
            if (h.rocketArmyCooldown > 0) h.rocketArmyCooldown -= dt * 1000;
            if (h.rocketArmyActive > 0) {
                h.rocketArmyActive -= dt;
                // 轰炸期间,每0.3秒随机生成一个轰炸区
                h.bombingSpawnTimer += dt;
                if (h.bombingSpawnTimer >= 0.3) {
                    h.bombingSpawnTimer = 0;
                    qianBombings.push({
                        x: ARENA_X + Math.random() * ARENA_WIDTH,
                        y: ARENA_Y + Math.random() * ARENA_HEIGHT,
                        radius: 55,
                        life: 0.5,
                        maxLife: 0.5,
                        ownerId: h.id,
                        hitMap: {}
                    });
                }
            }
        }

        if (h.chargeTimer > 0) { h.chargeTimer -= dt; if (h.chargeTimer <= 0) h.chargeStacks = 0; }
        if (h.radiationTimer > 0) { h.radiationTimer -= dt; if (h.radiationTimer <= 0) h.radiationStacks = 0; }
        if (h.radiationStacks > 0) applyDamage(h, h.radiationStacks * 2 * dt, null, null, true, true);

        updateWander(h, dt);
    }

    for (const hero of heroEntities) {
        if (hero.hp <= 0 || hero.stunTimer > 0 || hero.wallPushTimer > 0) continue;
        if (hero.id === 'hawking' && hero.wheelchairTimer > 0) { updateWheelchairCharge(hero, dt); continue; }
        if (hero.id === 'newton')         updateNewton(hero, dt);
        else if (hero.id === 'lavoisier') updateLavoisier(hero, dt);
        else if (hero.id === 'mendel')    updateMendel(hero, dt);
        else if (hero.id === 'taylor')    updateTaylor(hero, dt);
        else if (hero.id === 'starling')  updateStarling(hero, dt

Game Source: 电子斗蛐蛐 - 学科大战(钱学森版)

Creator: EpicCoder88

Libraries: none

Complexity: complex (464 lines, 19.9 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-mujcesad" to link back to the original. Then publish at arcadelab.ai/publish.