电子斗蛐蛐 - 学科大战(爱因斯坦·兄弟内战版)
by EpicCoder881263 lines86.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: 10px; flex-wrap: wrap; justify-content: center; max-width: 90vw; }
.hero-card { padding: 10px 18px; background-color: #2a2a2a; border: 2px solid #444; border-radius: 8px; font-size: 14px; 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 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: '#FF4500', maxHp: 1100, speed: 115 },
{ id: 'hooke', name: '胡克', color: '#A0522D', maxHp: 900, speed: 112 },
{ id: 'curie', name: '居里', color: '#00FF00', maxHp: 1020, speed: 110 },
{ id: 'houdebang', name: '侯德榜', color: '#FF69B4', maxHp: 940, speed: 110 },
{ id: 'gauss', name: '高斯', color: '#FFD700', maxHp: 1000, speed: 80 },
{ id: 'einstein', name: '爱因斯坦',color: '#C0C0C0', maxHp: 1000, 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 missiles = [];
let bombardZones = [];
let springOscillators = [];
let blackHoles = [];
let radiumBlocks = [];
let floorItems = [];
let polygons = [];
// 新增爱因斯坦的实体
let einsteinPlates = [];
let electrons = [];
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');
}
heroList.querySelectorAll('.hero-card').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 = []; missiles = []; bombardZones = [];
springOscillators = []; blackHoles = []; radiumBlocks = []; floorItems = []; polygons = [];
einsteinPlates = []; electrons = [];
}
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, baseSpeed: 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,
permanentSlow: 1,
chargeStacks: 0, chargeTimer: 0,
lowHpTimer: 0, originalHp: 0,
lastBlastHp: data.maxHp // 记录上一次触发质能方程的血量
};
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.peaTimer = 8000; hero.geneSeed = 0;
} else if (id === 'taylor') {
hero.taylorMark = 0; hero.taylorMarkTimer = 0; hero.ultCooldown = 0;
} else if (id === 'starling') {
hero.hormoneStormCooldown = 15000; hero.overdriveTimer = 0;
} else if (id === 'faraday') {
hero.shieldCooldown = 8000; hero.shieldTimer = 0; hero.cageCooldown = 15000;
} else if (id === 'hawking') {
hero.wheelchairCooldown = 2000; hero.wheelchairTimer = 0;
hero.wheelchairDirX = 0; hero.wheelchairDirY = 0; hero.wheelchairDamageAccum = 0;
hero.wheelchairHitMap = {}; hero.wormholeTimer = 5000; hero.radiationCooldown = 18000;
hero.noDamageTimer = 0; hero.blackHoleCooldown = 10000;
} else if (id === 'kepler') {
hero.planetCooldown = 3000; hero.focusCooldown = 8000; hero.planetCount = 0;
} else if (id === 'zuchongzhi') {
hero.piCooldown = 8000; hero.piShieldTimer = 0;
} else if (id === 'schrodinger') {
hero.cloneCooldown = 6000; hero.cloneTimer = 0;
hero.collapseCooldown = 12000; hero.catBoxCooldown = 15000;
} else if (id === 'qianxuesen') {
hero.missileCooldown = 1000;
hero.rocketCooldown = 10000;
hero.isBombarding = false;
hero.bombardTimer = 0;
hero.bombardSpawnTimer = 0;
} else if (id === 'hooke') {
hero.springCooldown = 2000;
} else if (id === 'curie') {
hero.radiumCooldown = 6000;
} else if (id === 'houdebang') {
hero.attackTimer = 0;
} else if (id === 'gauss') {
hero.attackCooldown = 1750;
hero.ceres = null;
} else if (id === 'einstein') {
hero.einsteinSkillCD = 20000;
}
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 = 0.8 + Math.random() * 1.5;
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 * 2.0;
h.wanderY = Math.sin(h.wanderAngle + wobble) * h.speed * 2.0;
}
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 = 0, moveY = 0;
const minDist = hero.radius + target.radius + 25;
if (distPx < minDist) {
const tangent = (Math.sin(performance.now() / 800 + hero.wanderAngle) > 0) ? 1 : -1;
moveX = -dirY * tangent * hero.speed * 1.2 + hero.wanderX * 2.0;
moveY = dirX * tangent * hero.speed * 1.2 + hero.wanderY * 2.0;
} else {
moveX = dirX * hero.speed * 0.4 + hero.wanderX * 1.5;
moveY = dirY * hero.speed * 0.4 + hero.wanderY * 1.5;
}
hero.x += moveX * dt;
hero.y += moveY * 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 = h.baseSpeed * h.permanentSlow; }
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 = h.baseSpeed; }
if (h.id === 'starling' && h.overdriveTimer <= 0) {
h.speed = (h.hp < h.maxHp * 0.5) ? h.baseSpeed * 1.2 * h.permanentSlow : h.baseSpeed * h.permanentSlow;
}
if (h.lowHpTimer > 0) {
h.lowHpTimer -= dt * 1000;
if (h.lowHpTimer <= 0) {
h.lowHpTimer = 0;
h.hp = Math.max(h.hp, Math.min(h.originalHp, h.maxHp));
h.originalHp = 0;
addDamageText(h.x, h.y - 30, '血量恢复!', '#00ff00');
}
}
// 专属冷却
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.blackHoleCooldown > 0) h.blackHoleCooldown -= 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.missileCooldown > 0) h.missileCooldown -= dt * 1000;
if (h.rocketCooldown > 0) h.rocketCooldown -= dt * 1000;
if (h.isBombarding) {
h.bombardTimer -= dt;
h.bombardSpawnTimer += dt;
if (h.bombardSpawnTimer >= 0.3) {
h.bombardSpawnTimer = 0;
for (let i = 0; i < 3; i++) {
bombardZones.push({
x: ARENA_X + Math.random() * ARENA_WIDTH, y: ARENA_Y + Math.random() * ARENA_HEIGHT,
radius: 60, life: 0.5, maxLife: 0.5, ownerId: h.id, hitMap: {}
});
}
}
if (h.bombardTimer <= 0) h.isBombarding = false;
}
}
if (h.id === 'hooke') if (h.springCooldown > 0) h.springCooldown -= dt * 1000;
if (h.id === 'curie') if (h.radiumCooldown > 0) h.radiumCooldown -= dt * 1000;
if (h.id === 'houdebang') {
if (h.attackTimer <= 0) {
h.attackTimer = 1000;
const target = findNearestEnemy(h);
const r = Math.random();
if (r < 0.3) {
spawnProjectile({ x: h.x, y: h.y, startX: h.x, startY: h.y, target, speed: 500, damage: 45, type: 'NaCl', ownerId: h.id, homing: 0.1 });
addDamageText(h.x, h.y - 40, 'NaCl!', '#FF69B4');
} else if (r < 0.6) {
spawnProjectile({ x: h.x, y: h.y, startX: h.x, startY: h.y, target, speed: 500, damage: 5, type: 'H2O', ownerId: h.id, homing: 0.1 });
} else if (r < 0.8) {
spawnProjectile({ x: h.x, y: h.y, startX: h.x, startY: h.y, target, speed: 500, damage: 10, type: 'CO2', ownerId: h.id, homing: 0.1 });
} else if (r < 0.9) {
const comboId = 'hou_' + performance.now();
spawnProjectile({ x: h.x, y: h.y, startX: h.x, startY: h.y, target, speed: 500, damage: 45, type: 'NaCl', ownerId: h.id, homing: 0.1, comboId });
spawnProjectile({ x: h.x, y: h.y, startX: h.x, startY: h.y, target, speed: 500, damage: 5, type: 'H2O', ownerId: h.id, homing: 0.1, comboId });
spawnProjectile({ x: h.x, y: h.y, startX: h.x, startY: h.y, target, speed: 500, damage: 10, type: 'CO2', ownerId: h.id, homing: 0.1, comboId });
addDamageText(h.x, h.y - 40, '四药齐发!', '#FF0000');
}
}
}
if (h.id === 'gauss') {
if (h.attackCooldown > 0) h.attackCooldown -= dt * 1000;
if (h.hp < 800 && !h.ceres) {
h.ceres = { angle: 0, orbitRadius: 2 * CONFIG.GRID_SIZE, state: 'active', chargeTimer: 0, hitMap: {} };
addDamageText(h.x, h.y - 50, '🌍 谷神星!', '#FFD700');
}
}
if (h.id === 'einstein') {
if (h.einsteinSkillCD > 0) h.einsteinSkillCD -= dt * 1000;
}
if (h.chargeTimer > 0) { h.chargeTimer -= dt; if (h.chargeTimer <= 0) h.chargeStacks = 0; }
updateWander(h, dt);
}
// AI 调度
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);
else if (hero.id === 'faraday') updateFaraday(hero, dt);
else if (hero.id === 'hawking') updateHawking(hero, dt);
else if (hero.id === 'kepler') updateKepler(hero, dt);
else if (hero.id === 'zuchongzhi')updateZuchongzhi(hero, dt);
else if (hero.id === 'schrodinger') updateSchrodinger(hero, dt);
else if (hero.id === 'qianxuesen') updateQianXuesen(hero, dt);
else if (hero.id === 'hooke') updateHooke(hero, dt);
else if (hero.id === 'curie') updateCurie(hero, dt);
else if (hero.id === 'gauss') updateGauss(hero, dt);
else if (hero.id === 'einstein') updateEinstein(hero, dt);
}
updatePrisms(dt);
updateOxygenFields(dt);
updateMassParticles(dt);
updatePeaShooters(dt);
updateHormonePools(dt);
updateGeneSeeds(dt);
updateWormholes(dt);
updateFaradayCages(dt);
updateRadiationZones(dt);
updateKeplerPlanets(dt);
updateCutCircleBullets(dt);
updatePiShields(dt);
updateSchrodingerClones(dt);
updateCatBoxes(dt);
updateMissiles(dt);
updateBombardZones(dt);
updateSpringOscillators(dt);
updateBlackHoles(dt);
updateRadiumBlocks(dt);
updateFloorItems(dt);
updatePolygons(dt);
updateGaussCeres(dt);
updateEinsteinPlates(dt);
updateElectrons(dt);
resolveCollisions();
updateProjectiles(dt);
updateEffects(dt);
}
// ============ 爱因斯坦专属逻辑 ============
function updateEinstein(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
// 技能1:光电效应
if (hero.einsteinSkillCD <= 0) {
hero.einsteinSkillCD = 20000;
// 预判1秒前的位置(由于AI移动随机,简化为目标当前位置,因为电子本身覆盖率极大)
const px = target.x;
const py = target.y;
// 生成极板:A板和B板平行,相距10格(400像素)。为了视觉,采用垂直或水平随机。
const isVertical = Math.random() > 0.5;
let ax, ay, bx, by, dirX, dirY;
if (isVertical) {
ax = px - 200; ay = py;
bx = px + 200; by = py;
dirX = 0; dirY = Math.random() > 0.5 ? 1 : -1; // 电子上下射
} else {
ax = px; ay = py - 200;
bx = px; by = py + 200;
dirX = Math.random() > 0.5 ? 1 : -1; dirY = 0; // 电子左右射
}
einsteinPlates.push({
ownerId: hero.id,
ax, ay, bx, by,
hpA: 100, maxHpA: 100,
hpB: 100, maxHpB: 100,
life: 5,
emitTimer: 0,
isVertical,
dirX, dirY,
w: 20, h: 120 // 极板尺寸
});
addDamageText(hero.x, hero.y - 40, '⚡ 光电效应!', '#C0C0C0');
}
// 技能2:质能方程 (E=mc²)
const lostHp = hero.maxHp - hero.hp;
const threshold = Math.floor(lostHp / (hero.maxHp / 8)); // 每1/8血量
const lastThreshold = Math.floor((hero.maxHp - hero.lastBlastHp) / (hero.maxHp / 8));
if (threshold > lastThreshold) {
hero.lastBlastHp = hero.hp;
// 释放冲击波
addImpact(hero.x, hero.y, '#C0C0C0', 1.0);
addDamageText(hero.x, hero.y - 50, 'E=mc²!', '#ffffff');
for (const h of heroEntities) {
if (h.id === hero.id || h.hp <= 0) continue;
const dist = Math.hypot(h.x - hero.x, h.y - hero.y);
if (dist < 150) {
applyDamage(h, 20, hero, null, false, false);
const kdx = h.x - hero.x, kdy = h.y - hero.y, kd = dist || 1;
h.x += (kdx / kd) * 100; // 大幅击退
h.y += (kdy / kd) * 100;
constrainToArena(h);
addDamageText(h.x, h.y - 20, '击退!', '#C0C0C0');
}
}
}
}
function updateEinsteinPlates(dt) {
for (let i = einsteinPlates.length - 1; i >= 0; i--) {
const p = einsteinPlates[i];
p.life -= dt;
if (p.life <= 0) { einsteinPlates.splice(i, 1); continue; }
// 停止发射的条件:任意一块板被打爆
const canEmit = (p.hpA > 0 && p.hpB > 0);
if (canEmit) {
p.emitTimer += dt;
if (p.emitTimer >= 0.05) { // 每0.05秒生成一个电子
p.emitTimer = 0;
// A板发射向B板
const startX = p.ax + (p.isVertical ? (Math.random() - 0.5) * p.h : 0);
const startY = p.ay + (p.isVertical ? 0 : (Math.random() - 0.5) * p.w);
electrons.push({
ownerId: p.ownerId,
x: startX, y: startY,
vx: p.dirX * 960, // 24格/秒 = 960像素/秒
vy: p.dirY * 960,
life: 1.0, // 飞行1秒后自动消失
hitMap: {}
});
}
}
// 极板碰撞体积检测(把敌人卡住)
for (const h of heroEntities) {
if (h.hp <= 0) continue;
// 简单视为圆形障碍物,半径 20
const checkCollide = (px, py, hpRef) => {
if (hpRef <= 0) return;
const dist = Math.hypot(h.x - px, h.y - py);
if (dist < h.radius + 20) {
const overlap = h.radius + 20 - dist;
const nx = (h.x - px) / (dist || 1);
const ny = (h.y - py) / (dist || 1);
h.x += nx * overlap;
h.y += ny * overlap;
constrainToArena(h);
}
};
checkCollide(p.ax, p.ay, p.hpA);
checkCollide(p.bx, p.by, p.hpB);
}
}
}
function updateElectrons(dt) {
for (let i = electrons.length - 1; i >= 0; i--) {
const e = electrons[i];
e.life -= dt;
if (e.life <= 0) { electrons.splice(i, 1); continue; }
e.x += e.vx * dt;
e.y += e.vy * dt;
if (e.x < 0 || e.x > MAP_WIDTH || e.y < 0 || e.y > MAP_HEIGHT) {
electrons.splice(i, 1); continue;
}
let hit = false;
for (const h of heroEntities) {
if (h.hp <= 0 || h.id === e.ownerId) continue;
if (Math.hypot(h.x - e.x, h.y - e.y) < h.radius + 5) {
// 为了避免无限叠加2秒眩晕导致直接原地去世,我们加一个极短的命中免疫
if (!e.hitMap[h.id]) {
e.hitMap[h.id] = true;
applyDamage(h, 5, heroEntities.find(he => he.id === e.ownerId), null, false, false);
h.stunTimer = Math.max(h.stunTimer, 2000); // 2秒眩晕,无CD叠加,但在单个电子判定中只叠一次
hit = true;
break;
}
}
}
if (hit) electrons.splice(i, 1);
}
}
// ============ 其余英雄 AI ============
function updateGauss(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.attackCooldown <= 0) {
hero.attackCooldown = 1750;
spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 280, damage: 70, type: 'reciprocity', ownerId: hero.id, homing: 1.0 });
if (Math.random() < 0.3 && polygons.length < 6) {
polygons.push({ ownerId: hero.id, x: ARENA_X + Math.random() * ARENA_WIDTH, y: ARENA_Y + Math.random() * ARENA_HEIGHT, life: 12, maxLife: 12, hitMap: {} });
}
}
}
function updateGaussCeres(dt) {
for (const h of heroEntities) {
if (h.id !== 'gauss' || !h.ceres) continue;
const c = h.ceres;
if (c.state === 'charging') {
c.chargeTimer -= dt * 1000;
if (c.chargeTimer <= 0) { c.state = 'active'; c.chargeTimer = 0; }
continue;
}
c.angle += Math.PI * dt;
const cx = h.x + Math.cos(c.angle) * c.orbitRadius;
const cy = h.y + Math.sin(c.angle) * c.orbitRadius;
const now = performance.now();
for (const other of heroEntities) {
if (other === h || other.hp <= 0) continue;
if (Math.hypot(other.x - cx, other.y - cy) < other.radius + 12) {
if (!c.hitMap[other.id] || now - c.hitMap[other.id] > 500) {
c.hitMap[other.id] = now;
applyDamage(other, 50, h, null, false, false);
other.stunTimer = 2000;
c.state = 'charging'; c.chargeTimer = 3000;
break;
}
}
}
}
}
function updatePolygons(dt) {
for (let i = polygons.length - 1; i >= 0; i--) {
const p = polygons[i]; p.life -= dt;
if (p.life <= 0) { polygons.splice(i, 1); continue; }
const now = performance.now();
for (const h of heroEntities) {
if (h.hp <= 0 || h.id === p.ownerId) continue;
if (Math.hypot(h.x - p.x, h.y - p.y) < h.radius + 20) {
if (!p.hitMap[h.id] || now - p.hitMap[h.id] > 1000) {
p.hitMap[h.id] = now;
applyDamage(h, 120, heroEntities.find(he => he.id === p.ownerId), null, false, false);
h.stunTimer = 3000;
polygons.splice(i, 1); break;
}
}
}
}
}
function updateWheelchairCharge(hero, dt) {
hero.wheelchairTimer -= dt;
hero.x += hero.wheelchairDirX * 450 * dt; hero.y += hero.wheelchairDirY * 450 * dt;
constrainToArena(hero);
hero.wheelchairDamageAccum += dt;
while (hero.wheelchairDamageAccum >= 0.1) {
hero.wheelchairDamageAccum -= 0.1;
for (const h of heroEntities) {
if (h.id === hero.id || h.hp <= 0) continue;
if (Math.hypot(h.x - hero.x, h.y - hero.y) < h.radius + hero.radius + 8) {
if (hero.wheelchairHitMap[h.id] === undefined) {
hero.wheelchairHitMap[h.id] = true;
applyDamage(h, 97, hero, null, false, false);
h.stunTimer = 500;
}
}
}
}
if (hero.wheelchairTimer <= 0) hero.wheelchairTimer = 0;
}
function updateNewton(hero, dt) {
hero.prismCooldown -= dt * 1000; hero.appleTimer -= dt * 1000;
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.prismCooldown <= 0) {
hero.prismCooldown = 5000;
const px = hero.x + (target.x - hero.x) * 0.5; const py = hero.y + (target.y - hero.y) * 0.5;
newtonPrisms.push({ ownerId: hero.id, x: px, y: py, timer: 10000, baseAngle: Math.atan2(target.y - py, target.x - px), angle: 0, sweepDir: 1 });
}
if (hero.appleTimer <= 0) {
hero.appleTimer = 30000;
for (let i = 0; i < 10; i++) {
if (apples.length >= 20) break;
const ax = ARENA_X + Math.random() * ARENA_WIDTH; const ay = ARENA_Y + Math.random() * ARENA_HEIGHT;
let hitEnemy = false;
for (const h of heroEntities) {
if (h.id !== 'newton' && h.hp > 0 && Math.hypot(h.x - ax, h.y - ay) < h.radius + 10) { applyDamage(h, 40, hero, null); hitEnemy = true; break; }
}
if (!hitEnemy) apples.push({ x: ax, y: ay, radius: 8 });
}
}
for (let i = apples.length - 1; i >= 0; i--) {
const ap = apples[i];
for (const h of heroEntities) {
if (h.hp <= 0) continue;
if (Math.hypot(h.x - ap.x, h.y - ap.y) < h.radius + ap.radius) {
h.hp = Math.min(h.maxHp, h.hp + (h.id === 'newton' ? 50 : 10)); apples.splice(i, 1); break;
}
}
}
if (hero.satellites) {
hero.satelliteTimer -= dt * 1000;
if (hero.satelliteTimer <= 0) { hero.satelliteTimer = 5000; if (Math.random() < 0.3 && hero.satellites.length < 5) hero.satellites.push({ angle: Math.random() * Math.PI * 2 }); }
for (let i = hero.satellites.length - 1; i >= 0; i--) {
const sat = hero.satellites[i]; sat.angle += 2.5 * dt;
const satX = hero.x + Math.cos(sat.angle) * 80; const satY = hero.y + Math.sin(sat.angle) * 80;
for (const h of heroEntities) {
if (h.id === 'newton' || h.hp <= 0) continue;
if (Math.hypot(h.x - satX, h.y - satY) < h.radius + 10) {
applyDamage(h, 40, hero, null); h.stunTimer = 1000; hero.satellites.splice(i, 1); break;
}
}
}
}
}
function updateLavoisier(hero, dt) {
hero.oxygenCooldown -= dt * 1000; hero.ultCooldown -= dt * 1000;
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.oxygenCooldown <= 0) { hero.oxygenCooldown = 6000; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 500, damage: 20, type: 'oxygen', ownerId: hero.id, homing: 0.4 }); }
if (hero.preciseStacks >= 3 && hero.ultCooldown <= 0) {
hero.preciseStacks = 0; hero.ultCooldown = 12000;
applyDamage(target, 120, hero, null, false, false);
const kdx = target.x - hero.x, kdy = target.y - hero.y, kd = Math.hypot(kdx, kdy) || 1;
target.x += (kdx / kd) * CONFIG.GRID_SIZE; target.y += (kdy / kd) * CONFIG.GRID_SIZE;
constrainToArena(target);
}
}
function updateMendel(hero, dt) {
hero.peaTimer -= dt * 1000;
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.attackTimer <= 0) {
hero.attackTimer = 2000;
spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 700, damage: hero.geneSeed ? 50 : 15, type: hero.geneSeed ? 'superpea' : 'pea', ownerId: hero.id, homing: 0.2 });
hero.geneSeed = 0;
}
if (hero.peaTimer <= 0) {
hero.peaTimer = 8000;
if (peaShooters.filter(p => p.ownerId === hero.id).length < 4) peaShooters.push({ ownerId: hero.id, x: hero.x + (Math.random() - 0.5) * 100, y: hero.y + (Math.random() - 0.5) * 100, hp: 50, maxHp: 50, attackTimer: 0 });
}
}
function updateTaylor(hero, dt) {
hero.ultCooldown -= dt * 1000;
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.attackTimer <= 0) { hero.attackTimer = 1500; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 900, damage: 5, type: 'taylor', ownerId: hero.id, homing: 0 }); }
}
function updateStarling(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.attackTimer <= 0) { hero.attackTimer = 2000; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 800, damage: 15, type: 'hormone', ownerId: hero.id, homing: 0.25 }); }
}
function updateFaraday(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.attackTimer <= 0) { hero.attackTimer = 1800; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 850, damage: 12, type: 'spark', ownerId: hero.id, homing: 0.3 }); }
if (target.chargeStacks >= 4 && hero.cageCooldown <= 0) {
hero.cageCooldown = 15000;
faradayCages.push({ x: target.x, y: target.y, radius: 120, life: 5, ownerId: hero.id, tickAccum: 0, shockAccum: 0 });
}
}
function updateHawking(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
const dx = target.x - hero.x, dy = target.y - hero.y;
const distPx = Math.hypot(dx, dy) || 1; const distGrid = distPx / CONFIG.GRID_SIZE;
const dirX = dx / distPx, dirY = dy / distPx;
moveTowardTarget(hero, target, dt);
if (hero.wheelchairCooldown <= 0 && distGrid > 1.5) {
hero.wheelchairCooldown = 2000; hero.wheelchairTimer = 1.0;
hero.wheelchairDirX = dirX; hero.wheelchairDirY = dirY;
hero.wheelchairDamageAccum = 0; hero.wheelchairHitMap = {};
}
if (hero.blackHoleCooldown <= 0) {
hero.blackHoleCooldown = 10000;
let life = radiationZones.some(z => Math.hypot(z.x - hero.x, z.y - hero.y) < z.radius + 40) ? 3 : 10;
blackHoles.push({ ownerId: hero.id, x: hero.x + dirX * CONFIG.GRID_SIZE * 3, y: hero.y + dirY * CONFIG.GRID_SIZE * 3, radius: 200, life: life, maxLife: life });
}
if (hero.wormholeTimer <= 0) {
hero.wormholeTimer = 5000;
if (Math.random() < 0.3) {
const angle = Math.random() * Math.PI * 2;
const entryX = hero.x + Math.cos(angle) * 60, entryY = hero.y + Math.sin(angle) * 60;
let exitX, exitY; do { exitX = ARENA_X + Math.random() * ARENA_WIDTH; exitY = ARENA_Y + Math.random() * ARENA_HEIGHT; } while (Math.hypot(exitX - entryX, exitY - entryY) < 320);
wormholes.push({ entryX: clampX(entryX), entryY: clampY(entryY), exitX: clampX(exitX), exitY: clampY(exitY), life: 15, used: false });
}
}
}
function updateKepler(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.planetCooldown <= 0) {
hero.planetCooldown = 3000;
if (hero.planetCount < 5) { hero.planetCount++; keplerPlanets.push({ ownerId: hero.id, angle: Math.random() * Math.PI * 2, orbitRadius: 100, hitMap: {} }); }
}
if (hero.focusCooldown <= 0) { hero.focusCooldown = 8000; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 1200, damage: 50, type: 'focus', ownerId: hero.id, homing: 0.6 }); }
hero.speed = hero.baseSpeed * (1 + hero.planetCount * 0.08) * hero.permanentSlow;
}
function updateZuchongzhi(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.attackTimer <= 0) {
hero.attackTimer = 1600;
const d = Math.hypot(target.x - hero.x, target.y - hero.y) || 1;
cutCircleBullets.push({ ownerId: hero.id, x: hero.x, y: hero.y, dirX: (target.x - hero.x) / d, dirY: (target.y - hero.y) / d, speed: 600, damage: 12, sides: 3, splitsLeft: 2, life: 4, traveled: 0 });
}
if (hero.piCooldown <= 0 && !hero.piShieldTimer) { hero.piCooldown = 8000; hero.piShieldTimer = 6000; piShields.push({ ownerId: hero.id, angle: 0, orbitRadius: 80, life: 6 }); }
}
function updateSchrodinger(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.attackTimer <= 0) {
hero.attackTimer = 1700;
if (Math.random() < 0.5 && schrodingerClones.filter(c => c.ownerId === hero.id).length < 3) {
const angle = Math.random() * Math.PI * 2;
schrodingerClones.push({ ownerId: hero.id, x: hero.x + Math.cos(angle) * 100, y: hero.y + Math.sin(angle) * 100, life: 3 });
}
spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 850, damage: 14, type: 'quantum', ownerId: hero.id, homing: 0.1 });
}
if (hero.collapseCooldown <= 0 && schrodingerClones.filter(c => c.ownerId === hero.id).length > 0) {
hero.collapseCooldown = 12000;
const clones = schrodingerClones.filter(c => c.ownerId === hero.id);
const chosen = clones[Math.floor(Math.random() * clones.length)];
applyDamage(target, 60, hero, null, true, false);
hero.x = chosen.x; hero.y = chosen.y;
schrodingerClones = schrodingerClones.filter(c => c.ownerId !== hero.id);
constrainToArena(hero);
}
if (hero.catBoxCooldown <= 0) {
hero.catBoxCooldown = 15000;
if (Math.hypot(target.x - hero.x, target.y - hero.y) < 200) {
catBoxes.push({ x: target.x, y: target.y, life: 1.5, ownerId: hero.id });
target.stunTimer = 1500;
}
}
}
function updateQianXuesen(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.missileCooldown <= 0) {
hero.missileCooldown = 1000;
const angle = Math.random() * Math.PI * 2;
missiles.push({ ownerId: hero.id, x: hero.x, y: hero.y, vx: Math.cos(angle) * 800, vy: Math.sin(angle) * 800, hp: 100, maxHp: 100, radius: 10, life: 5 });
}
if (hero.rocketCooldown <= 0 && !hero.isBombarding) {
hero.rocketCooldown = 10000; hero.isBombarding = true; hero.bombardTimer = 10; hero.bombardSpawnTimer = 0;
}
}
function updateHooke(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.springCooldown <= 0) {
hero.springCooldown = 2000;
const d = Math.hypot(target.x - hero.x, target.y - hero.y) || 1;
const px = target.x + ((target.x - hero.x) / d) * 60, py = target.y + ((target.y - hero.y) / d) * 60;
springOscillators.push({ ownerId: hero.id, x: px, y: py, perpX: -(target.y - hero.y) / d, perpY: (target.x - hero.x) / d, oscAmount: 0, oscSpeed: 6, oscRange: 40, life: 8, hitMap: {}, visualFlash: 0, armLength: 25 });
}
if (hero.attackTimer <= 0) {
hero.attackTimer = 2500;
if (Math.random() < 0.4) spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 700, damage: 15, type: 'cell', ownerId: hero.id, homing: 0.3 });
}
}
function updateCurie(hero, dt) {
const target = findNearestEnemy(hero);
if (!target) return;
moveTowardTarget(hero, target, dt);
if (hero.radiumCooldown <= 0) { hero.radiumCooldown = 6000; radiumBlocks.push({ ownerId: hero.id, x: hero.x, y: hero.y, life: 3.14, angle: Math.random() * Math.PI * 2 }); }
if (hero.attackTimer <= 0) { hero.attackTimer = 2000; if (Math.random() < 0.4) spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 700, damage: 57, type: 'U238', ownerId: hero.id, homing: 0.2 }); }
}
// ============ 实体与渲染渲染逻辑(部分省略以节省长度,确保逻辑完整) ============
function updatePrisms(dt) {
for (let i = newtonPrisms.length - 1; i >= 0; i--) {
const p = newtonPrisms[i]; p.timer -= dt * 1000;
if (p.timer <= 0) { newtonPrisms.splice(i, 1); continue; }
p.angle += p.sweepDir * 2.5 * dt;
if (p.angle > Math.PI / 3) p.sweepDir = -1; if (p.angle < -Math.PI / 3) p.sweepDir = 1;
for (const h of heroEntities) {
if (h.id === 'newton' || h.hp <= 0) continue;
const dist = Math.hypot(h.x - p.x, h.y - p.y);
if (dist < 240) {
let diff = Math.abs(Math.atan2(h.y - p.y, h.x - p.x) - p.baseAngle);
while (diff > Math.PI) diff = Math.abs(diff - 2 * Math.PI);
if (diff < p.angle + Math.PI / 6) applyDamage(h, 1, null, null, false, true);
}
}
}
}
function updateOxygenFields(dt) { for (let i = oxygenFields.length - 1; i >= 0; i--) { const f = oxygenFields[i]; f.life -= dt; if (f.life <= 0) { oxygenFields.splice(i, 1); continue; } for (const h of heroEntities) { if (h.hp <= 0 || h.id === f.ownerId) continue; if (Math.hypot(h.x - f.x, h.y - f.y) < f.radius) { applyDamage(h, 15 * dt, null, null, false, true); h.slowTimer = 500; h.speed = h.baseSpeed * 0.7 * h.permanentSlow; } } } }
function updateMassParticles(dt) { for (let i = massParticles.length - 1; i >= 0; i--) { const p = massParticles[i]; p.life -= dt; if (p.life <= 0) { massParticles.splice(i, 1); continue; } const l = heroEntities.find(h => h.id === 'lavoisier' && h.hp > 0); if (l && Math.hypot(l.x - p.x, l.y - p.y) < l.radius + 15) { l.hp = Math.min(l.maxHp, l.hp + 30); l.preciseStacks = Math.min(3, l.preciseStacks + 1); massParticles.splice(i, 1); } } }
function updatePeaShooters(dt) { for (let i = peaShooters.length - 1; i >= 0; i--) { const p = peaShooters[i]; p.hp -= dt * 5; if (p.hp <= 0) { const m = heroEntities.find(h => h.id === 'mendel' && h.hp > 0); if (m) { if (Math.random() < 0.5) m.hp = Math.min(m.maxHp, m.hp + 100); else geneSeeds.push({ x: p.x, y: p.y, radius: 10 }); } peaShooters.splice(i, 1); continue; } p.attackTimer += dt * 1000; if (p.attackTimer >= 1500) { p.attackTimer = 0; let t = findNearestEnemy({x:p.x,y:p.y,id:p.ownerId}); if (t) spawnProjectile({ x: p.x, y: p.y, startX: p.x, startY: p.y, target: t, speed: 800, damage: 10, type: 'pea', ownerId: p.ownerId, homing: 0.15 }); } } }
function updateHormonePools(dt) { for (let i = hormonePools.length - 1; i >= 0; i--) { const p = hormonePools[i]; p.life -= dt; if (p.life <= 0) { hormonePools.splice(i, 1); continue; } const s = heroEntities.find(h => h.id === 'starling' && h.hp > 0); if (s && Math.hypot(s.x - p.x, s.y - p.y) < 40) s.hp = Math.min(s.maxHp, s.hp + 30 * dt); } }
function updateGeneSeeds(dt) { for (let i = geneSeeds.length - 1; i >= 0; i--) { const g = geneSeeds[i]; const m = heroEntities.find(h => h.id === 'mendel' && h.hp > 0); if (m && Math.hypot(m.x - g.x, m.y - g.y) < m.radius + 15) { m.geneSeed = 1; geneSeeds.splice(i, 1); } } }
function updateWormholes(dt) { for (let i = wormholes.length - 1; i >= 0; i--) { const w = wormholes[i]; w.life -= dt; if (w.life <= 0 || w.used) { wormholes.splice(i, 1); continue; } for (const h of heroEntities) { if (h.hp <= 0) continue; if (Math.hypot(h.x - w.entryX, h.y - w.entryY) < h.radius + 12) { h.x = w.exitX; h.y = w.exitY; w.used = true; break; } if (Math.hypot(h.x - w.exitX, h.y - w.exitY) < h.radius + 12) { h.x = w.entryX; h.y = w.entryY; w.used = true; break; } } } }
function updateFaradayCages(dt) { for (let i = faradayCages.length - 1; i >= 0; i--) { const cage = faradayCages[i]; cage.life -= dt; if (cage.life <= 0) { faradayCages.splice(i, 1); continue; } cage.tickAccum += dt; cage.shockAccum += dt; for (const h of heroEntities) { if (h.hp <= 0 || h.id === cage.ownerId) continue; const dist = Math.hypot(h.x - cage.x, h.y - cage.y); if (dist < cage.radius) { if (cage.tickAccum >= 0.5) applyDamage(h, 25 * dt, null, null, false, true); if (dist > cage.radius - h.radius) { h.x += ((cage.x - h.x) / (dist || 1)) * h.speed * dt * 0.8; h.y += ((cage.y - h.y) / (dist || 1)) * h.speed * dt * 0.8; } } } if (cage.shockAccum >= 1) { cage.shockAccum = 0; for (const h of heroEntities) if (h.hp > 0 && h.id !== cage.ownerId && Math.hypot(h.x - cage.x, h.y - cage.y) < cage.radius) applyDamage(h, 15, null, null, true, false); } } }
function updateRadiationZones(dt) { for (let i = radiationZones.length - 1; i >= 0; i--) { const zone = radiationZones[i]; zone.life -= dt; if (zone.life <= 0) { radiationZones.splice(i, 1); continue; } const o = heroEntities.find(h => h.id === zone.ownerId); if (!o || o.hp <= 0) { radiationZones.splice(i, 1); continue; } for (const h of heroEntities) { if (h.hp <= 0) continue; if (Math.hypot(h.x - zone.x, h.y - zone.y) < zone.radius) { if (h.id === zone.ownerId) o.hp = Math.min(o.maxHp, o.hp + 20 * dt); else applyDamage(h, 15 * dt, o, null, false, true); } } } }
function updateKeplerPlanets(dt) { for (let i = keplerPlanets.length - 1; i >= 0; i--) { const planet = keplerPlanets[i]; const o = heroEntities.find(h => h.id === planet.ownerId); if (!o || o.hp <= 0) { keplerPlanets.splice(i, 1); continue; } planet.angle += 1.2 * dt; const px = o.x + Math.cos(planet.angle) * planet.orbitRadius; const py = o.y + Math.sin(planet.angle) * planet.orbitRadius; for (const h of heroEntities) { if (h.id === planet.ownerId || h.hp <= 0) continue; if (Math.hypot(h.x - px, h.y - py) < h.radius + 10) { const now = performance.now(); if (!planet.hitMap[h.id] || now - planet.hitMap[h.id] > 500) { planet.hitMap[h.id] = now; applyDamage(h, 18, o, null, false, false); } } } } }
function updateCutCircleBullets(dt) { for (let i = cutCircleBullets.length - 1; i >= 0; i--) { const b = cutCircleBullets[i]; b.life -= dt; if (b.life <= 0) { cutCircleBullets.splice(i, 1); continue; } b.x += b.dirX * b.speed * dt; b.y += b.dirY * b.speed * dt; b.traveled += b.speed * dt; if (b.x < 0 || b.x > MAP_WIDTH || b.y < 0 || b.y > MAP_HEIGHT || b.traveled > 1500) { cutCircleBullets.splice(i, 1); continue; } let hit = null; for (const h of heroEntities) if (h.hp > 0 && h.id !== b.ownerId && Math.hypot(h.x - b.x, h.y - b.y) < h.radius + 10) { hit = h; break; } if (hit) { applyDamage(hit, b.damage, null, null, false, false); if (b.splitsLeft > 0) for (let k = 0; k < 2; k++) { const newA = Math.atan2(b.dirY, b.dirX) + (k === 0 ? 0.6 : -0.6); cutCircleBullets.push({ ownerId: b.ownerId, x: b.x, y: b.y, dirX: Math.cos(newA), dirY: Math.sin(newA), speed: b.speed * 1.2, damage: Math.max(4, Math.floor(b.damage * 0.7)), sides: b.sides + 1, splitsLeft: b.splitsLeft - 1, life: 3, traveled: 0 }); } cutCircleBullets.splice(i, 1); } } }
function updatePiShields(dt) { for (let i = piShields.length - 1; i >= 0; i--) { const s = piShields[i]; s.life -= dt; if (s.life <= 0) { piShields.splice(i, 1); continue; } const o = heroEntities.find(h => h.id === s.ownerId); if (!o || o.hp <= 0) { piShields.splice(i, 1); continue; } s.angle += 2.5 * dt; const sx = o.x + Math.cos(s.angle) * s.orbitRadius; const sy = o.y + Math.sin(s.angle) * s.orbitRadius; for (const h of heroEntities) if (h.hp > 0 && h.id !== s.ownerId && Math.hypot(h.x - sx, h.y - sy) < h.radius + 14) { applyDamage(h, 25, o, null, false, false); piShields.splice(i, 1); break; } } for (const h of heroEntities) if (h.id === 'zuchongzhi' && h.piShieldTimer > 0) { h.piShieldTimer -= dt * 1000; if (h.piShieldTimer <= 0) { h.piShieldTimer = 0; piShields = piShields.filter(s => s.ownerId !== h.id); } } }
function updateSchrodingerClones(dt) { for (let i = schrodingerClones.length - 1; i >= 0; i--) { const c = schrodingerClones[i]; c.life -= dt; if (c.life <= 0) { schrodingerClones.splice(i, 1); continue; } for (const h of heroEntities) if (h.hp > 0 && h.id !== c.ownerId && Math.hypot(h.x - c.x, h.y - c.y) < h.radius + 12) { applyDamage(h, 20, null, null, true, false); schrodingerClones.splice(i, 1); break; } } }
function updateCatBoxes(dt) { for (let i = catBoxes.length - 1; i >= 0; i--) { catBoxes[i].life -= dt; if (catBoxes[i].life <= 0) catBoxes.splice(i, 1); } }
function updateMissiles(dt) { for (let i = missiles.length - 1; i >= 0; i--) { const m = missiles[i]; m.life -= dt; if (m.life <= 0) { missiles.splice(i, 1); for (const h of heroEntities) if (h.hp > 0 && Math.hypot(h.x - m.x, h.y - m.y) < 100) applyDamage(h, 50, null, null, false, false); continue; } m.x += m.vx * dt; m.y += m.vy * dt; if (m.x < 0 || m.x > MAP_WIDTH || m.y < 0 || m.y > MAP_HEIGHT) { missiles.splice(i, 1); continue; } let hit = false; for (const h of heroEntities) if (h.hp > 0 && h.id !== m.ownerId && Math.hypot(h.x - m.x, h.y - m.y) < h.radius + m.radius) { applyDamage(h, 200, null, null, false, false); hit = true; break; } if (!hit) for (const p of projectiles) if (p.ownerId !== m.ownerId && Math.hypot(p.x - m.x, p.y - m.y) < p.hitRadius + m.radius) { m.hp -= p.damage; if (m.hp <= 0) { for (const h of heroEntities) if (h.hp > 0 && Math.hypot(h.x - m.x, h.y - m.y) < 100) applyDamage(h, 50, null, null, false, false); hit = true; } break; } if (hit) missiles.splice(i, 1); } }
function updateBombardZones(dt) { for (let i = bombardZones.length - 1; i >= 0; i--) { const z = bombardZones[i]; z.life -= dt; if (z.life <= 0) { bombardZones.splice(i, 1); continue; } for (const h of heroEntities) if (h.hp > 0 && Math.hypot(h.x - z.x, h.y - z.y) < z.radius && !z.hitMap[h.id]) { z.hitMap[h.id] = true; applyDamage(h, 50, null, null, false, false); } } }
function updateSpringOscillators(dt) { for (let i = springOscillators.length - 1; i >= 0; i--) { const s = springOscillators[i]; s.life -= dt; if (s.life <= 0) { springOscillators.splice(i, 1); continue; } s.oscAmount += s.oscSpeed * dt; s.currentX = s.x + s.perpX * Math.sin(s.oscAmount) * s.oscRange; s.currentY = s.y + s.perpY * Math.sin(s.oscAmount) * s.oscRange; const now = performance.now(); let flash = false; for (const h of heroEntities) if (h.hp > 0 && h.id !== s.ownerId && Math.hypot(h.x - s.currentX, h.y - s.currentY) < h.radius + 18 && (!s.hitMap[h.id] || now - s.hitMap[h.id] > 2000)) { s.hitMap[h.id] = now; h.stunTimer = 2000; applyDamage(h, 20, null, null, false, false); flash = true; } if (flash) s.visualFlash = 0.3; if (s.visualFlash > 0) s.visualFlash -= dt; } }
function updateBlackHoles(dt) { for (let i = blackHoles.length - 1; i >= 0; i--) { const bh = blackHoles[i]; bh.life -= dt; if (bh.life <= 0) { blackHoles.splice(i, 1); continue; } for (const h of heroEntities) { if (h.hp <= 0 || h.id === bh.ownerId) continue; const dx = bh.x - h.x, dy = bh.y - h.y, dist = Math.hypot(dx, dy) || 1; if (dist < bh.radius) { h.x += (dx / dist) * 150 * dt; h.y += (dy / dist) * 150 * dt; constrainToArena(h); applyDamage(h, 10 * dt, null, null, false, true); } } } }
function updateRadiumBlocks(dt) { for (let i = radiumBlocks.length - 1; i >= 0; i--) { const r = radiumBlocks[i]; r.life -= dt; if (r.life <= 0) { radiumBlocks.splice(i, 1); continue; } r.angle += 2.0 * dt; const rays = [r.angle, r.angle + Math.PI / 2, r.angle + Math.PI, r.angle + 3 * Math.PI / 2]; for (const h of heroEntities) if (h.hp > 0) for (const a of rays) { const proj = (h.x - r.x) * Math.cos(a) + (h.y - r.y) * Math.sin(a); if (proj > 0 && Math.abs((h.x - r.x) * Math.sin(a) - (h.y - r.y) * Math.cos(a)) < h.radius + 8) { applyDamage(h, 24 * dt, null, null, false, true); h.slowTimer = 200; h.speed = h.baseSpeed * 0.95 * h.permanentSlow; } } } }
function updateFloorItems(dt) { for (let i = floorItems.length - 1; i >= 0; i--) { const item = floorItems[i]; item.life -= dt; if (item.life <= 0) { floorItems.splice(i, 1); continue; } for (const h of heroEntities) if (h.hp > 0 && Math.hypot(h.x - item.x, h.y - item.y) < h.radius + 15) { if (item.type === 'NH4Cl') applyDamage(h, 100, null, null, false, false); else h.hp = Math.min(h.maxHp, h.hp + 100); floorItems.splice(i, 1); break; } } }
function updateProjectiles(dt) {
for (let i = projectiles.length - 1; i >= 0; i--) {
const p = projectiles[i];
if (p.homing > 0 && p.target && p.target.hp > 0) {
const tA = Math.atan2(p.target.y - p.y, p.target.x - p.x), cA = Math.atan2(p.dirY, p.dirX);
let diff = tA - cA;
while (diff > Math.PI) diff -= Math.PI * 2; while (diff < -Math.PI) diff += Math.PI * 2;
const rot = p.homing * 4 * dt, cl = Math.max(-rot, Math.min(rot, diff));
p.dirX = Math.cos(cA + cl); p.dirY = Math.sin(cA + cl);
}
p.x += p.dirX * p.speed * dt; p.y += p.dirY * p.speed * dt; p.traveled += p.speed * dt;
if (p.x < 0 || p.x > MAP_WIDTH || p.y < 0 || p.y > MAP_HEIGHT || p.traveled > p.maxDistance) {
if (p.type === 'oxygen') oxygenFields.push({ x: clampX(p.x), y: clampY(p.y), radius: 120, life: 5, ownerId: p.ownerId });
projectiles.splice(i, 1); continue;
}
let hitT = null;
for (const h of heroEntities) if (h.hp > 0 && h.id !== p.ownerId && Math.hypot(h.x - p.x, h.y - p.y) < h.radius + p.hitRadius) { hitT = h; break; }
if (p.type === 'oxygen' && p.target && p.target.hp > 0 && Math.hypot(p.target.x - p.x, p.target.y - p.y) < p.target.radius + 20) hitT = p.target;
if (hitT) {
const att = p.ownerId ? heroEntities.find(h => h.id === p.ownerId) : null;
if (p.comboId) {
hitT.hitByCombo = hitT.hitByCombo || {}; hitT.hitByCombo[p.comboId] = (hitT.hitByCombo[p.comboId] || 0) + 1;
if (hitT.hitByCombo[p.comboId] >= 3) {
applyDamage(hitT, 240, att, null, false, false);
hitT.x += ((hitT.x - p.x) / (Math.hypot(hitT.x - p.x, hitT.y - p.y) || 1)) * 200; hitT.y += ((hitT.y - p.y) / (Math.hypot(hitT.x - p.x, hitT.y - p.y) || 1)) * 200;
constrainToArena(hitT);
floorItems.push({ x: hitT.x + 50, y: hitT.y + 50, type: 'NH4Cl', life: 10 }, { x: hitT.x - 50, y: hitT.y - 50, type: 'NaHCO3', life: 10 });
hitT.hitByCombo[p.comboId] = 0;
}
}
if (p.type === 'oxygen') { applyDamage(hitT, p.damage, att, p); oxygenFields.push({ x: p.x, y: p.y, radius: 120, life: 5, ownerId: p.ownerId }); }
else if (p.type === 'U238') { applyDamage(hitT, p.damage, att, p); if (hitT.permanentSlow === 1) hitT.permanentSlow = 0.9; }
else if (p.type === 'cell') { applyDamage(hitT, p.damage, att, p); hitT.originalHp = hitT.hp; hitT.hp = Math.max(1, hitT.hp / 3); hitT.lowHpTimer = 3000; }
else if (p.type === 'reciprocity') { applyDamage(hitT, p.damage, att, p); hitT.stunTimer = 500; }
else applyDamage(hitT, p.damage, att, p);
projectiles.splice(i, 1);
}
}
}
function resolveCollisions() { for (let i = 0; i < heroEntities.length; i++) for (let j = i + 1; j < heroEntities.length; j++) { const h1 = heroEntities[i], h2 = heroEntities[j]; if (h1.hp <= 0 || h2.hp <= 0 || (h1.id === 'hawking' && h1.wheelchairTimer > 0) || (h2.id === 'hawking' && h2.wheelchairTimer > 0)) continue; let dx = h2.x - h1.x, dy = h2.y - h1.y, dist = Math.hypot(dx, dy); const minDist = h1.radius + h2.radius; if (dist < 0.01) { dx = 0.01; dy = 0; dist = 0.01; } if (dist < minDist) { const ov = minDist - dist, nx = dx / dist, ny = dy / dist; h1.x -= nx * ov * 0.5; h1.y -= ny * ov * 0.5; h2.x += nx * ov * 0.5; h2.y += ny * ov * 0.5; } } for (const h of heroEntities) constrainToArena(h); }
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; if (h.x < ARENA_X + h.radius) h.x = ARENA_X + h.radius; if (h.x > ARENA_X + ARENA_WIDTH - h.radius) h.x = ARENA_X + ARENA_WIDTH - h.radius; if (h.y < ARENA_Y + h.radius) h.y = ARENA_Y + h.radius; if (h.y > ARENA_Y + ARENA_HEIGHT - h.radius) h.y = ARENA_Y + ARENA_HEIGHT - h.radius; }
function clampX(x) { return Math.max(ARENA_X + 20, Math.min(ARENA_X + ARENA_WIDTH - 20, x)); }
function clampY(y) { return Math.max(ARENA_Y + 20, Math.min(ARENA_Y + ARENA_HEIGHT - 20, y)); }
// ============ 伤害结算:相对论闪避 ============
function applyDamage(target, amount, attacker, projectile, isTrueDamage = false, silent = false) {
if (!target || target.hp <= 0) return;
if (!isFinite(amount)) amount = 0; amount = Math.max(0, amount);
if (!isTrueDamage && attacker && attacker.attackPower !== undefined && attacker.attackPower < 1) amount *= attacker.attackPower;
if (target.id === 'schrodinger' && !isTrueDamage && Math.random() < 0.3) { if (!silent) addDamageText(target.x, target.y - 20, '量子闪避!', '#9370db'); return; }
// 爱因斯坦 相对论:残血闪避 + 减速
if (target.id === 'einstein' && target.hp < target.maxHp * 0.2 && !isTrueDamage && Math.random() < 0.5) {
if (!silent) addDamageText(target.x, target.y - 20, '相对论闪避!', '#C0C0C0');
if (attacker && attacker.hp > 0) {
attacker.slowTimer = 3000; // 3秒
// 这个减速可叠加,我们用速度乘算或额外计时,简化起见,每次触发往下扣一点速度
attacker.speed = Math.max(30, attacker.speed * 0.6);
if (!silent) addDamageText(attacker.x, attacker.y - 20, '时间膨胀 -40%!', '#9370db');
}
return; // 免疫本次伤害
}
if (target.id === 'faraday' && target.shieldTimer > 0 && !isTrueDamage) {
amount *= 0.7;
if (attacker && attacker.hp > 0 && attacker.id !== 'faraday' && !silent) {
const rDmg = Math.round(amount * 0.2);
if (rDmg > 0) { attacker.hp -= rDmg; attacker.chargeStacks = Math.min(4, (attacker.chargeStacks || 0) + 1); attacker.chargeTimer = 4; }
}
}
if (!isTrueDamage && target.dr > 0) amount *= (1 - target.dr / 100);
if (target.id === 'hawking') target.noDamageTimer = 0;
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 && massParticles.length < 15) massParticles.push({ x: target.x + (Math.random() - 0.5) * 30, y: target.y + (Math.random() - 0.5) * 30, life: 10 });
return { killed: target.hp <= 0 };
}
function addDamageText(x, y, amount, color) { if (damageTexts.length > 150) damageTexts.shift(); damageTexts.push({ x, y: y - 20, text: String(amount), color: color || '#fff', life: 1.0 }); }
function addImpact(x, y, color, maxLife) { impactEffects.push({ x, y, color, life: maxLife || 0.3, maxLife: maxLife || 0.3 }); }
function updateEffects(dt) { for (let i = impactEffects.length - 1; i >= 0; i--) { impactEffects[i].life -= dt; if (impactEffects[i].life <= 0) impactEffects.splice(i, 1); } for (let i = damageTexts.length - 1; i >= 0; i--) { damageTexts[i].y -= 30 * dt; damageTexts[i].life -= dt; if (damageTexts[i].life <= 0) damageTexts.splice(i, 1); } }
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(); drawPrisms(); drawSatellites();
drawMassParticles(); drawPeaShooters(); drawFaradayCages(); drawRadiationZones(); drawWormholes();
drawKeplerPlanets(); drawCutCircleBullets(); drawPiShields(); drawSchrodingerClones(); drawCatBoxes();
drawBombardZones(); drawMissiles(); drawSpringOscillators(); drawBlackHoles(); drawRadiumBlocks();
drawFloorItems(); drawPolygons(); drawGaussCeres();
drawEinsteinPlates(); drawElectrons();
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 (let i = 1; i < CONFIG.GRID_COLS; i++) { const x = ARENA_X + i * CONFIG.GRID_SIZE; ctx.moveTo(x, ARENA_Y); ctx.lineTo(x, ARENA_Y + ARENA_HEIGHT); }
for (let i = 1; i < CONFIG.GRID_ROWS; i++) { const y = ARENA_Y + i * CONFIG.GRID_SIZE; ctx.moveTo(ARENA_X, y); ctx.lineTo(ARENA_X + ARENA_WIDTH, y); }
ctx.stroke();
}
function drawEinsteinPlates() {
for (const p of einsteinPlates) {
const alpha = Math.min(1, p.life / 1);
// 画A板
if (p.hpA > 0) {
ctx.fillStyle = `rgba(192, 192, 192, ${0.8 * alpha})`;
ctx.fillRect(p.ax - p.w/2, p.ay - p.h/2, p.w, p.h);
ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`;
ctx.lineWidth = 2;
ctx.strokeRect(p.ax - p.w/2, p.ay - p.h/2, p.w, p.h);
// 血条
ctx.fillStyle = '#333'; ctx.fillRect(p.ax - p.w/2, p.ay - p.h/2 - 10, p.w, 4);
ctx.fillStyle = '#00ffcc'; ctx.fillRect(p.ax - p.w/2, p.ay - p.h/2 - 10, p.w * (p.hpA/100), 4);
ctx.fillStyle = '#C0C0C0'; ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.fillText('A', p.ax, p.ay + 5);
}
// 画B板
if (p.hpB > 0) {
ctx.fillStyle = `rgba(192, 192, 192, ${0.8 * alpha})`;
ctx.fillRect(p.bx - p.w/2, p.by - p.h/2, p.w, p.h);
ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`;
ctx.lineWidth = 2;
ctx.strokeRect(p.bx - p.w/2, p.by - p.h/2, p.w, p.h);
// 血条
ctx.fillStyle = '#333'; ctx.fillRect(p.bx - p.w/2, p.by - p.h/2 - 10, p.w, 4);
ctx.fillStyle = '#00ffcc'; ctx.fillRect(p.bx - p.w/2, p.by - p.h/2 - 10, p.w * (p.hpB/100), 4);
ctx.fillStyle = '#C0C0C0'; ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.fillText('B', p.bx, p.by + 5);
}
}
}
function drawElectrons() {
ctx.fillStyle = '#00ffff';
for (const e of electrons) {
ctx.beginPath(); ctx.arc(e.x, e.y, 3, 0, Math.PI * 2); ctx.fill();
}
}
function drawPolygons() { for (const p of polygons) { const alpha = Math.min(1, p.life / 2); ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(performance.now() / 2000); ctx.beginPath(); for (let i = 0; i <= 17; i++) { const a = (i / 17) * Math.PI * 2; const x = Math.cos(a) * 22, y = Math.sin(a) * 22; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.closePath(); ctx.fillStyle = `rgba(255, 215, 0, ${0.3 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(255, 215, 0, ${alpha})`; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); } }
function drawGaussCeres() { for (const h of heroEntities) { if (h.id !== 'gauss' || !h.ceres) continue; const c = h.ceres; const cx = h.x + Math.cos(c.angle) * c.orbitRadius; const cy = h.y + Math.sin(c.angle) * c.orbitRadius; if (c.state === 'charging') { ctx.beginPath(); ctx.arc(cx, cy, 16, 0, Math.PI * 2); ctx.fillStyle = 'rgba(255, 215, 0, 0.3)'; ctx.fill(); ctx.strokeStyle = 'rgba(255, 215, 0, 0.6)'; ctx.lineWidth = 2; ctx.setLineDash([4, 4]); ctx.stroke(); ctx.setLineDash([]); } else { ctx.beginPath(); ctx.arc(cx, cy, 16, 0, Math.PI * 2); ctx.fillStyle = '#FFD700'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#111'; ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('G', cx, cy); } } }
function drawBlackHoles() { for (const bh of blackHoles) { const alpha = Math.min(1, bh.life / 1); ctx.beginPath(); ctx.arc(bh.x, bh.y, bh.radius, 0, Math.PI * 2); ctx.fillStyle = `rgba(0, 0, 0, ${0.6 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(138, 43, 226, ${alpha})`; ctx.lineWidth = 3; ctx.stroke(); ctx.beginPath(); ctx.arc(bh.x, bh.y, bh.radius * 0.5, 0, Math.PI * 2); ctx.strokeStyle = `rgba(200, 100, 255, ${alpha})`; ctx.lineWidth = 2; ctx.setLineDash([10, 10]); ctx.stroke(); ctx.setLineDash([]); } }
function drawRadiumBlocks() { for (const r of radiumBlocks) { const alpha = Math.min(1, r.life / 1); ctx.save(); ctx.translate(r.x, r.y); ctx.beginPath(); ctx.arc(0, 0, 12, 0, Math.PI * 2); ctx.fillStyle = `rgba(0, 255, 0, ${0.8 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`; ctx.lineWidth = 2; ctx.stroke(); const angles = [r.angle, r.angle + Math.PI/2, r.angle + Math.PI, r.angle + 3*Math.PI/2]; for (const a of angles) { ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(Math.cos(a) * 400, Math.sin(a) * 400); ctx.strokeStyle = `rgba(0, 255, 0, ${0.4 * alpha})`; ctx.lineWidth = 4; ctx.stroke(); } ctx.restore(); } }
function drawFloorItems() { for (const item of floorItems) { const alpha = Math.min(1, item.life / 2); ctx.beginPath(); ctx.arc(item.x, item.y, 12, 0, Math.PI * 2); if (item.type === 'NH4Cl') { ctx.fillStyle = `rgba(0, 255, 0, ${0.6 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`; ctx.font = 'bold 10px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('NH4Cl', item.x, item.y); } else { ctx.fillStyle = `rgba(255, 105, 180, ${0.6 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`; ctx.font = 'bold 10px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('NaHCO3', item.x, item.y); } } }
function drawSpringOscillators() { for (const s of springOscillators) { const cx = s.currentX !== undefined ? s.currentX : s.x; const cy = s.currentY !== undefined ? s.currentY : s.y; const alpha = Math.min(1, s.life / 1); ctx.beginPath(); ctx.moveTo(s.x - s.perpX * s.oscRange, s.y - s.perpY * s.oscRange); ctx.lineTo(s.x + s.perpX * s.oscRange, s.y + s.perpY * s.oscRange); ctx.strokeStyle = `rgba(160, 82, 45, ${0.3 * alpha})`; ctx.lineWidth = 1; ctx.setLineDash([3, 3]); ctx.stroke(); ctx.setLineDash([]); ctx.save(); ctx.translate(cx, cy); ctx.rotate(Math.atan2(s.perpY, s.perpX)); ctx.beginPath(); for (let i = 0; i <= s.armLength; i++) { const x = i - s.armLength / 2; const y = Math.sin(i * 1.5) * 6; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.strokeStyle = `rgba(160, 82, 45, ${alpha})`; ctx.lineWidth = 2; ctx.stroke(); ctx.restore(); ctx.beginPath(); ctx.arc(cx, cy, 10, 0, Math.PI * 2); ctx.fillStyle = `rgba(160, 82, 45, ${0.8 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`; ctx.lineWidth = 2; ctx.stroke(); if (s.visualFlash > 0) { ctx.beginPath(); ctx.arc(cx, cy, 20, 0, Math.PI * 2); ctx.strokeStyle = `rgba(255, 255, 0, ${Math.min(1, s.visualFlash * 3)})`; ctx.lineWidth = 3; ctx.stroke(); } } }
function drawMissiles() { for (const m of missiles) { ctx.save(); ctx.translate(m.x, m.y); const angle = Math.atan2(m.vy, m.vx); ctx.rotate(angle); ctx.fillStyle = '#FF4500'; ctx.beginPath(); ctx.moveTo(12, 0); ctx.lineTo(-8, -6); ctx.lineTo(-8, 6); ctx.closePath(); ctx.fill(); ctx.fillStyle = '#ccc'; ctx.fillRect(-8, -6, 4, 12); ctx.restore(); const barW = 20, barH = 3; ctx.fillStyle = '#333'; ctx.fillRect(m.x - barW/2, m.y - 20, barW, barH); ctx.fillStyle = '#FF4500'; ctx.fillRect(m.x - barW/2, m.y - 20, barW * (m.hp / m.maxHp), barH); } }
function drawBombardZones() { for (const z of bombardZones) { const progress = z.life / z.maxLife; ctx.beginPath(); ctx.arc(z.x, z.y, z.radius, 0, Math.PI * 2); const alpha = progress > 0.5 ? 0.2 : 0.5; ctx.fillStyle = `rgba(255, 69, 0, ${alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(255, 69, 0, 0.8)`; ctx.lineWidth = 3; ctx.setLineDash([5, 5]); ctx.stroke(); ctx.setLineDash([]); } }
function drawOxygenFields() { for (const f of oxygenFields) { 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 (const p of hormonePools) { 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 (const g of geneSeeds) { 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 (const a of apples) { 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 drawPrisms() { for (const p of newtonPrisms) { ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.baseAngle); ctx.beginPath(); ctx.moveTo(0, 0); ctx.arc(0, 0, 240, -Math.PI / 6 + p.angle, Math.PI / 6 + p.angle); ctx.closePath(); ctx.fillStyle = 'rgba(255, 255, 255, 0.08)'; ctx.fill(); for (let i = 0; i < 7; i++) { const angle = -Math.PI / 6 + p.angle + (i / 6) * (Math.PI / 3); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(Math.cos(angle) * 240, Math.sin(angle) * 240); ctx.strokeStyle = `hsl(${i * 51}, 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 (const hero of heroEntities) { if (hero.id !== 'newton' || !hero.satellites) continue; for (const sat of hero.satellites) { const satX = hero.x + Math.cos(sat.angle) * 80; const satY = hero.y + Math.sin(sat.angle) * 80; ctx.beginPath(); ctx.arc(satX, satY, 8, 0, Math.PI * 2); ctx.fillStyle = '#00ffff'; ctx.fill(); ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.stroke(); ctx.beginPath(); ctx.moveTo(satX, satY - 8); ctx.lineTo(satX, satY - 14); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); } } }
function drawMassParticles() { for (const p of massParticles) { 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 (const p of peaShooters) { 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); const 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 drawFaradayCages() { for (const cage of faradayCages) { const alpha = Math.min(1, cage.life / 1); ctx.beginPath(); ctx.arc(cage.x, cage.y, cage.radius, 0, Math.PI * 2); ctx.fillStyle = `rgba(255, 215, 0, ${0.12 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(255, 215, 0, ${alpha})`; ctx.lineWidth = 3; ctx.stroke(); for (let i = 0; i < 8; i++) { const angle = (i / 8) * Math.PI * 2; ctx.beginPath(); ctx.moveTo(cage.x, cage.y); ctx.lineTo(cage.x + Math.cos(angle) * cage.radius, cage.y + Math.sin(angle) * cage.radius); ctx.strokeStyle = `rgba(255, 215, 0, ${0.4 * alpha})`; ctx.lineWidth = 1; ctx.stroke(); } } }
function drawRadiationZones() { for (const zone of radiationZones) { const alpha = Math.min(1, zone.life / 1); ctx.beginPath(); ctx.arc(zone.x, zone.y, zone.radius, 0, Math.PI * 2); ctx.fillStyle = `rgba(138, 43, 226, ${0.15 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(138, 43, 226, ${alpha})`; ctx.lineWidth = 2; ctx.stroke(); } }
function drawWormholes() { for (const w of wormholes) { if (w.used) continue; const alpha = Math.min(1, w.life / 2); ctx.beginPath(); ctx.arc(w.entryX, w.entryY, 18, 0, Math.PI * 2); ctx.fillStyle = `rgba(138, 43, 226, ${0.5 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(138, 43, 226, ${alpha})`; ctx.lineWidth = 3; ctx.stroke(); ctx.beginPath(); ctx.arc(w.entryX, w.entryY, 8, 0, Math.PI * 2); ctx.fillStyle = '#000'; ctx.fill(); ctx.beginPath(); ctx.arc(w.exitX, w.exitY, 18, 0, Math.PI * 2); ctx.fillStyle = `rgba(138, 43, 226, ${0.5 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(200, 100, 255, ${alpha})`; ctx.lineWidth = 3; ctx.stroke(); } }
function drawKeplerPlanets() { for (const planet of keplerPlanets) { const owner = heroEntities.find(h => h.id === planet.ownerId); if (!owner || owner.hp <= 0) continue; ctx.beginPath(); ctx.arc(owner.x, owner.y, planet.orbitRadius, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(65, 105, 225, 0.15)'; ctx.lineWidth = 1; ctx.stroke(); const px = owner.x + Math.cos(planet.angle) * planet.orbitRadius; const py = owner.y + Math.sin(planet.angle) * planet.orbitRadius; ctx.beginPath(); ctx.arc(px, py, 8, 0, Math.PI * 2); ctx.fillStyle = '#4169e1'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); } }
function drawCutCircleBullets() { for (const b of cutCircleBullets) { ctx.save(); ctx.translate(b.x, b.y); ctx.beginPath(); for (let i = 0; i <= b.sides; i++) { const angle = (i / b.sides) * Math.PI * 2; const x = Math.cos(angle) * 8, y = Math.sin(angle) * 8; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.closePath(); ctx.fillStyle = '#00ced1'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.stroke(); ctx.restore(); } }
function drawPiShields() { for (const s of piShields) { const owner = heroEntities.find(h => h.id === s.ownerId); if (!owner || owner.hp <= 0) continue; const sx = owner.x + Math.cos(s.angle) * s.orbitRadius; const sy = owner.y + Math.sin(s.angle) * s.orbitRadius; ctx.beginPath(); ctx.arc(sx, sy, 14, 0, Math.PI * 2); ctx.fillStyle = 'rgba(0, 206, 209, 0.8)'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#111'; ctx.font = 'bold 14px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('π', sx, sy); } }
function drawSchrodingerClones() { for (const c of schrodingerClones) { const alpha = Math.min(1, c.life / 1) * 0.5; ctx.beginPath(); ctx.arc(c.x, c.y, 20, 0, Math.PI * 2); ctx.fillStyle = `rgba(147, 112, 219, ${alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(147, 112, 219, ${alpha * 1.5})`; ctx.lineWidth = 2; ctx.setLineDash([4, 4]); ctx.stroke(); ctx.setLineDash([]); } }
function drawCatBoxes() { for (const box of catBoxes) { const alpha = Math.min(1, box.life / 0.5); ctx.save(); ctx.translate(box.x, box.y); ctx.strokeStyle = `rgba(147, 112, 219, ${alpha})`; ctx.lineWidth = 3; ctx.strokeRect(-25, -25, 50, 50); ctx.fillStyle = `rgba(147, 112, 219, ${alpha * 0.2})`; ctx.fillRect(-25, -25, 50, 50); ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`; ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('?', 0, 0); ctx.restore(); } }
function drawEffects() { for (const e of impactEffects) { const 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 (const p of projectiles) { ctx.beginPath(); ctx.moveTo(p.startX, p.startY); ctx.lineTo(p.x, p.y); ctx.strokeStyle = p.type === 'pea' ? '#33cc33' : p.type === 'superpea' ? '#ffff00' : p.type === 'taylor' ? '#3366ff' : p.type === 'hormone' ? '#ff44cc' : p.type === 'oxygen' ? '#ff8c00' : p.type === 'spark' ? '#ffd700' : p.type === 'focus' ? '#ffaa00' : p.type === 'quantum' ? '#9370db' : p.type === 'cell' ? '#A0522D' : p.type === 'NaCl' ? '#FF69B4' : p.type === 'H2O' ? '#00BFFF' : p.type === 'CO2' ? '#888888' : p.type === 'U238' ? '#00FF00' : p.type === 'reciprocity' ? '#FFD700' : '#fff'; 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 (const entity of heroEntities) {
ctx.globalAlpha = entity.hp > 0 ? 1.0 : 0.2;
ctx.beginPath(); ctx.arc(entity.x, entity.y, 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();
if (entity.id === 'hooke' && entity.lowHpTimer > 0) { ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 6, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(160, 82, 45, 0.9)'; ctx.lineWidth = 3; ctx.setLineDash([4, 4]); ctx.stroke(); ctx.setLineDash([]); }
if (entity.id === 'faraday' && entity.shieldTimer > 0) { ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 6, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(255, 215, 0, 0.8)'; ctx.lineWidth = 3; ctx.stroke(); }
if (entity.id === 'zuchongzhi' && entity.piShieldTimer > 0) { ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 8, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(0, 206, 209, 0.7)'; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.stroke(); ctx.setLineDash([]); }
if (entity.id === 'hawking' && entity.noDamageTimer >= 5 && entity.hp < entity.maxHp) { ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 10, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(0, 255, 0, 0.6)'; ctx.lineWidth = 3; ctx.setLineDash([4, 4]); ctx.stroke(); ctx.setLineDash([]); }
// 爱因斯坦的视觉:低血时光环
if (entity.id === 'einstein' && entity.hp < entity.maxHp * 0.2) {
ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 12, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(192, 192, 192, 0.8)'; ctx.lineWidth = 2; ctx.setLineDash([2, 6]); ctx.stroke(); ctx.setLineDash([]);
}
ctx.save(); ctx.translate(entity.x, entity.y);
if (entity.id === 'newton') { ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.moveTo(0, -entity.radius - 4); ctx.lineTo(6, -entity.radius + 2); ctx.lineTo(-6, -entity.radius + 2); ctx.closePath(); ctx.fill(); ctx.strokeStyle = '#aaa'; ctx.lineWidth = 1; ctx.stroke(); }
else if (entity.id === 'lavoisier') { ctx.strokeStyle = '#111'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(0, 6); ctx.lineTo(0, -6); ctx.stroke(); ctx.beginPath(); ctx.moveTo(-8, -4); ctx.lineTo(8, -4); ctx.stroke(); ctx.beginPath(); ctx.moveTo(-8, -4); ctx.lineTo(-10, 2); ctx.lineTo(-6, 2); ctx.closePath(); ctx.fillStyle = '#111'; ctx.fill(); ctx.beginPath(); ctx.moveTo(8, -4); ctx.lineTo(6, 2); ctx.lineTo(10, 2); ctx.closePath(); ctx.fill(); }
else if (entity.id === 'mendel') { ctx.beginPath(); ctx.arc(0, 2, 8, 0, Math.PI * 2); ctx.fillStyle = '#33cc33'; ctx.fill(); ctx.beginPath(); ctx.moveTo(0, 0); ctx.quadraticCurveTo(5, -10, 12, -8); ctx.strokeStyle = '#00ff00'; ctx.lineWidth = 3; ctx.stroke(); }
else if (entity.id === 'taylor') { ctx.fillStyle = '#111'; ctx.font = 'bold 18px Arial, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('∑', 0, 2); }
else if (entity.id === 'starling') { ctx.strokeStyle = '#ff44cc'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(-6, -8); ctx.bezierCurveTo(6, -4, -6, 0, 6, 4); ctx.stroke(); ctx.beginPath(); ctx.moveTo(6, -8); ctx.bezierCurveTo(-6, -4, 6, 0, -6, 4); ctx.stroke(); }
else if (entity.id === 'faraday') { ctx.fillStyle = '#111'; ctx.beginPath(); ctx.moveTo(-2, -10); ctx.lineTo(4, -2); ctx.lineTo(0, -2); ctx.lineTo(3, 8); ctx.lineTo(-4, -1); ctx.lineTo(-1, -1); ctx.closePath(); ctx.fill(); }
else if (entity.id === 'hawking') { ctx.strokeStyle = '#000'; ctx.lineWidth = 2; for (let i = 0; i < 3; i++) { ctx.beginPath(); ctx.arc(0, 0, 4 + i * 3, i * 1.5, i * 1.5 + Math.PI * 1.4); ctx.stroke(); } ctx.beginPath(); ctx.arc(-8, 12, 4, 0, Math.PI * 2); ctx.fillStyle = '#333'; ctx.fill(); ctx.beginPath(); ctx.arc(8, 12, 4, 0, Math.PI * 2); ctx.fill(); }
else if (entity.id === 'kepler') { ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.ellipse(0, 0, 14, 8, 0, 0, Math.PI * 2); ctx.stroke(); ctx.beginPath(); ctx.arc(14, 0, 3, 0, Math.PI * 2); ctx.fillStyle = '#fff'; ctx.fill(); }
else if (entity.id === 'zuchongzhi') { ctx.fillStyle = '#111'; ctx.font = 'bold 18px Arial, serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('π', 0, 2); }
else if (entity.id === 'schrodinger') { ctx.strokeStyle = '#111'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(0, 0, 10, 0, Math.PI * 2); ctx.stroke(); ctx.beginPath(); ctx.moveTo(-8, -8); ctx.lineTo(-4, -14); ctx.lineTo(0, -10); ctx.lineTo(4, -14); ctx.lineTo(8, -8); ctx.stroke(); ctx.beginPath(); ctx.arc(-3, -1, 1.5, 0, Math.PI * 2); ctx.fillStyle = '#111'; ctx.fill(); ctx.beginPath(); ctx.arc(3, -1, 1.5, 0, Math.PI * 2); ctx.fill(); }
else if (entity.id === 'qianxuesen') { ctx.fillStyle = '#111'; ctx.beginPath(); ctx.moveTo(0, -12); ctx.lineTo(6, -4); ctx.lineTo(2, 8); ctx.lineTo(-2, 8); ctx.lineTo(-6, -4); ctx.closePath(); ctx.fill(); ctx.fillStyle = '#FF4500'; ctx.fillRect(-3, -8, 6, 4); }
else if (entity.id === 'hooke') { ctx.strokeStyle = '#A0522D'; ctx.lineWidth = 2; ctx.beginPath(); for (let i = 0; i <= 12; i++) { const x = -8 + (i / 12) * 16; const y = Math.sin(i * 1.2) * 5; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); ctx.beginPath(); ctx.arc(0, -8, 4, 0, Math.PI * 2); ctx.fillStyle = '#8B4513'; ctx.fill(); }
else if (entity.id === 'curie') { ctx.fillStyle = '#00FF00'; ctx.beginPath(); ctx.arc(0, 0, 10, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#111'; ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('Ra', 0, 0); }
else if (entity.id === 'houdebang') { ctx.fillStyle = '#FF69B4'; ctx.font = 'bold 14px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('H', 0, 2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(0, 0, 12, 0, Math.PI * 2); ctx.stroke(); }
else if (entity.id === 'gauss') { ctx.fillStyle = '#FFD700'; ctx.beginPath(); for (let i = 0; i <= 17; i++) { const a = (i / 17) * Math.PI * 2; const x = Math.cos(a) * 12, y = Math.sin(a) * 12; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.closePath(); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.stroke(); }
else if (entity.id === 'einstein') {
// 质能方程视觉
ctx.fillStyle = '#C0C0C0';
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('E=mc²', 0, 0);
}
ctx.restore();
const barW = 50, barH = 5;
const barX = entity.x - barW / 2;
const barY = entity.y - entity.radius - 15;
const 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.textBaseline = 'alphabetic'; ctx.fillText(entity.name, entity.x, barY - 5);
ctx.globalAlpha = 1.0;
}
}
function drawDamageTexts() { ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; for (const txt of damageTexts) { ctx.fillStyle = txt.color; ctx.font = 'bold 12px Arial, sans-serif'; 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 (1263 lines, 86.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-mvvx6gvs" to link back to the original. Then publish at arcadelab.ai/publish.