电子斗蛐蛐 - 学科大战(钱学森版)
by EpicCoder88464 lines19.9 KB
<!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, dtGame 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.