🎮ArcadeLab

深空舰队模拟器 - 完整版

by EpicCoder88
593 lines20.6 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>深空舰队模拟器 - 完整版</title>
<style>
  body { margin: 0; overflow: hidden; background: #02040a; font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; touch-action: none; }
  canvas { display: block; width: 100vw; height: 100vh; }
  
  /* 战前配置UI */
  #ui-layer {
    position: absolute; top: 0; left: 0; width: 100%; height: 100%;
    background: rgba(2, 4, 10, 0.9); color: #fff; display: flex; flex-direction: column;
    align-items: center; justify-content: center; z-index: 10;
  }
  .panel { display: flex; gap: 20px; margin-bottom: 30px; }
  .faction { background: rgba(255,255,255,0.05); padding: 20px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); min-width: 250px; }
  .blue h2 { color: #66ccff; } .red h2 { color: #ff3366; }
  .ship-row { display: flex; justify-content: space-between; align-items: center; margin: 10px 0; font-size: 14px; }
  .btn-ctrl { background: #223; border: 1px solid #445; color: #fff; width: 30px; height: 30px; border-radius: 6px; font-size: 18px; cursor: pointer; }
  .btn-ctrl:active { background: #445; }
  .count { width: 40px; text-align: center; font-weight: bold; }
  #start-btn {
    padding: 15px 60px; font-size: 20px; font-weight: bold; color: #000;
    background: linear-gradient(90deg, #66ccff, #00ffcc); border: none; border-radius: 30px;
    box-shadow: 0 0 20px rgba(102, 204, 255, 0.5); cursor: pointer;
  }
  #start-btn:active { transform: scale(0.95); }
  
  /* 战斗UI */
  #hud { position: absolute; top: 10px; left: 10px; right: 10px; display: flex; justify-content: space-between; pointer-events: none; font-size: 12px; color: #a0b0d0; text-shadow: 1px 1px 2px #000;}
</style>
</head>
<body>

<div id="hud" style="display:none;">
  <div id="hud-left"></div>
  <div id="hud-right"></div>
</div>

<div id="ui-layer">
  <h1 style="letter-spacing: 2px;">深空舰队交战模拟</h1>
  <p style="color:#889; font-size:14px; margin-bottom:20px;">2060年 · 太阳系资源争夺战</p>
  
  <div class="panel">
    <div class="faction blue">
      <h2>蓝方舰队</h2>
      <div class="ship-row"><span>激光舰(线型)</span> <button class="btn-ctrl" onclick="changeCount('b_laser',-1)">-</button> <span class="count" id="b_laser">2</span> <button class="btn-ctrl" onclick="changeCount('b_laser',1)">+</button></div>
      <div class="ship-row"><span>突击舰(十字型)</span> <button class="btn-ctrl" onclick="changeCount('b_assault',-1)">-</button> <span class="count" id="b_assault">1</span> <button class="btn-ctrl" onclick="changeCount('b_assault',1)">+</button></div>
    </div>
    
    <div class="faction red">
      <h2>红方舰队</h2>
      <div class="ship-row"><span>导弹舰(多支型)</span> <button class="btn-ctrl" onclick="changeCount('r_missile',-1)">-</button> <span class="count" id="r_missile">2</span> <button class="btn-ctrl" onclick="changeCount('r_missile',1)">+</button></div>
      <div class="ship-row"><span>补给舰(接口型)</span> <button class="btn-ctrl" onclick="changeCount('r_supply',-1)">-</button> <span class="count" id="r_supply">1</span> <button class="btn-ctrl" onclick="changeCount('r_supply',1)">+</button></div>
    </div>
  </div>
  
  <button id="start-btn" onclick="startGame()">开 战</button>
</div>

<canvas id="gameCanvas"></canvas>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let W, H;
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
window.addEventListener('resize', resize); resize();

// ======= 核心数据与配置 =======
let entities = [], bullets = [], particles = [];
let gameState = 'setup'; // setup, playing, ended
let fleetConfig = { b_laser: 2, b_assault: 1, r_missile: 2, r_supply: 1 };

function changeCount(id, delta) {
  let val = parseInt(document.getElementById(id).innerText);
  val = Math.max(0, Math.min(8, val + delta));
  document.getElementById(id).innerText = val;
  fleetConfig[id] = val;
}

// 基础物理计算
function dist(a, b) { return Math.hypot(a.x - b.x, a.y - b.y); }
function angleTo(a, b) { return Math.atan2(b.y - a.y, b.x - a.x); }
function normalizeAngle(a) {
  while (a > Math.PI) a -= Math.PI * 2;
  while (a < -Math.PI) a += Math.PI * 2;
  return a;
}

// ======= 实体基类 =======
class Ship {
  constructor(x, y, color, isEnemy, config) {
    this.x = x; this.y = y;
    this.vx = 0; this.vy = 0;
    this.angle = isEnemy ? Math.PI : 0;
    this.color = color; this.isEnemy = isEnemy;
    this.dead = false;
    
    // 读取配置 (手稿中的设定)
    this.maxHp = 500; this.hp = this.maxHp;
    this.radius = 18;
    this.crew = config.crew;
    this.maxSpeed = config.speed / 200; // 适配屏幕比例
    this.turnSpeed = config.turnSpeed / 60; // 转换到每帧
    this.detectRange = config.detectRange / 3; // 适配屏幕
    
    this.fireCooldown = 0;
    this.pdCooldown = 0; // 近防炮冷却
    this.target = null;
    
    // 状态机
    this.state = 'patrol'; // patrol, combat, evade, dock
    this.wanderAngle = Math.random() * Math.PI * 2;
  }

  update() {
    if (this.dead) return;
    
    this.vx *= 0.96; this.vy *= 0.96; // 太空阻尼模拟
    this.x += this.vx; this.y += this.vy;
    
    // 边界反弹
    if (this.x < 50 || this.x > W - 50) this.vx *= -1.05;
    if (this.y < 50 || this.y > H - 50) this.vy *= -1.05;

    if (this.fireCooldown > 0) this.fireCooldown--;
    if (this.pdCooldown > 0) this.pdCooldown--;
    
    if (this.hp <= 0 && !this.dead) {
      this.dead = true;
      createExplosion(this.x, this.y, this.color, 30);
    }
  }

  // 驾驶逻辑
  driveToAngle(targetAngle, throttle = 1.0) {
    const diff = normalizeAngle(targetAngle - this.angle);
    // 转向
    this.angle += Math.sign(diff) * Math.min(Math.abs(diff), this.turnSpeed);
    // 推进 (对准一定程度才能加速)
    if (Math.abs(diff) < 0.8) {
      this.vx += Math.cos(this.angle) * 0.08 * throttle;
      this.vy += Math.sin(this.angle) * 0.08 * throttle;
      const sp = Math.hypot(this.vx, this.vy);
      if (sp > this.maxSpeed) {
        this.vx = (this.vx / sp) * this.maxSpeed;
        this.vy = (this.vy / sp) * this.maxSpeed;
      }
    }
  }

  drawHealthBar() {
    ctx.fillStyle = 'rgba(0,0,0,0.6)';
    ctx.fillRect(-22, 28, 44, 6);
    ctx.fillStyle = this.hp > this.maxHp*0.4 ? '#00ff88' : '#ff0044';
    ctx.fillRect(-22, 28, 44 * (this.hp / this.maxHp), 6);
    
    // 显示乘员数
    ctx.fillStyle = '#aaa'; ctx.font = '9px Arial'; ctx.textAlign = 'center';
    ctx.fillText(`👤${this.crew}`, 0, -25);
  }
}

// ======= 1. 激光舰 (线型) =======
class LaserShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, '#66ccff', isEnemy, { crew: 10, speed: 200, turnSpeed: 2, detectRange: 2000 });
    this.type = 'laser';
    this.laserTimer = 0; // 30秒主炮计时
  }
  update() {
    super.update();
    if (this.dead) return;
    
    // AI 索敌
    this.target = entities.find(e => !e.isEnemy && !e.dead && dist(this, e) < this.detectRange);
    
    if (this.target) {
      const d = dist(this, this.target);
      const angle = angleTo(this, this.target);
      
      // 保持距离
      if (d < 300) this.driveToAngle(angle + Math.PI, 1);
      else this.driveToAngle(angle, 0.5);
      
      // 舰艏固定大功率激光炮 (需要对准,连续开火)
      if (Math.abs(normalizeAngle(angle - this.angle)) < 0.1 && this.fireCooldown <= 0) {
        bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'laser_main'));
        this.fireCooldown = 5;
      }
      
      // 4门点射拦截 (简化为近防概率拦截)
      this.interceptMissiles();
    } else {
      this.patrol();
    }
  }
  patrol() {
    if (this.wanderAngle === undefined || Math.random() < 0.02) this.wanderAngle = Math.random() * Math.PI * 2;
    this.driveToAngle(this.wanderAngle, 0.5);
  }
  interceptMissiles() {
    if (this.pdCooldown > 0) return;
    for (let b of bullets) {
      if (b.isEnemy !== this.isEnemy && b.type === 'missile' && dist(this, b) < 150) {
        if (Math.random() < 0.15) { // 4门拦截概率
          b.dead = true;
          createExplosion(b.x, b.y, '#66ccff', 5);
          this.pdCooldown = 5;
          break;
        }
      }
    }
  }
  draw() {
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    // 线型结构: 动力舱-资源舱-核心舱-生活舱-武器舱
    ctx.fillStyle = this.color;
    ctx.fillRect(-25, -6, 50, 12); 
    // 太阳能板
    ctx.fillStyle = '#225588';
    ctx.fillRect(-10, -22, 20, 16); ctx.fillRect(-10, 6, 20, 16);
    // 舰艏激光炮
    ctx.fillStyle = '#fff'; ctx.fillRect(25, -2, 12, 4);
    ctx.restore();
    this.drawHealthBar();
  }
}

// ======= 2. 突击舰 (十字型) =======
class AssaultShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, '#00ffcc', isEnemy, { crew: 8, speed: 400, turnSpeed: 3, detectRange: 4000 });
    this.type = 'assault';
    this.railAmmo = 24;
    this.missileAmmo = 2;
  }
  update() {
    super.update();
    if (this.dead) return;
    
    // 没有弹药了就去规避
    if (this.railAmmo <= 0) {
      this.state = 'evade';
      this.driveToAngle(this.angle + Math.PI/2, 1.5); // 侧向机动
      return;
    }

    this.target = entities.find(e => !e.isEnemy && !e.dead && dist(this, e) < this.detectRange);
    if (this.target) {
      const angle = angleTo(this, this.target);
      this.driveToAngle(angle, 1);
      
      // 电磁炮
      if (this.fireCooldown <= 0 && Math.abs(normalizeAngle(angle - this.angle)) < 0.2) {
        bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'railgun'));
        this.railAmmo--;
        this.fireCooldown = 120; // 2秒装填
      }
    } else {
      if (this.wanderAngle === undefined || Math.random() < 0.02) this.wanderAngle = Math.random() * Math.PI * 2;
      this.driveToAngle(this.wanderAngle, 0.8);
    }
  }
  draw() {
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    ctx.fillStyle = this.color;
    // 十字型
    ctx.fillRect(-20, -6, 40, 12);
    ctx.fillRect(-6, -20, 12, 40);
    // 太阳能板x4
    ctx.fillStyle = '#226655';
    ctx.fillRect(-15, -25, 10, 10); ctx.fillRect(5, -25, 10, 10);
    ctx.fillRect(-15, 15, 10, 10); ctx.fillRect(5, 15, 10, 10);
    // 轨道炮管
    ctx.fillStyle = '#fff'; 
    ctx.fillRect(20, -8, 15, 3); ctx.fillRect(20, 5, 15, 3);
    ctx.restore();
    this.drawHealthBar();
    
    ctx.fillStyle = '#fff'; ctx.font = '9px Arial'; ctx.textAlign = 'center';
    ctx.fillText(`电磁:${this.railAmmo}`, 0, 40);
  }
}

// ======= 3. 导弹舰 (多支型) =======
class MissileShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, '#ff3366', isEnemy, { crew: 8, speed: 1000, turnSpeed: 1, detectRange: 2008 });
    this.type = 'missile';
    this.missileAmmo = 80; // 8单元x10发
    this.maxAmmo = 80;
    this.needSupply = false;
  }
  update() {
    super.update();
    if (this.dead) return;
    
    if (this.missileAmmo <= 0) {
      this.needSupply = true;
      this.state = 'evade';
      this.driveToAngle(this.angle + Math.PI/2, 1); // 没弹药了规避
      return;
    }

    this.target = entities.find(e => !e.isEnemy && !e.dead && dist(this, e) < this.detectRange);
    if (this.target) {
      const angle = angleTo(this, this.target);
      const d = dist(this, this.target);
      
      if (d < 400) this.driveToAngle(angle + Math.PI, 0.8); // 拉开距离
      else this.driveToAngle(angle, 0.5);
      
      // 垂发导弹 (需要大致对准)
      if (this.fireCooldown <= 0 && Math.abs(normalizeAngle(angle - this.angle)) < 0.4) {
        bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'missile'));
        this.missileAmmo--;
        this.fireCooldown = 45; // 发射间隔
      }
      
      // 近防炮拦截
      if (this.pdCooldown <= 0) {
        for (let b of bullets) {
          if (b.isEnemy !== this.isEnemy && b.type === 'missile' && dist(this, b) < 120) {
            if (Math.random() < 0.1) {
              b.dead = true; createExplosion(b.x, b.y, '#ff3366', 3); this.pdCooldown = 8; break;
            }
          }
        }
      }
    } else {
      if (this.wanderAngle === undefined || Math.random() < 0.02) this.wanderAngle = Math.random() * Math.PI * 2;
      this.driveToAngle(this.wanderAngle, 0.6);
    }
  }
  draw() {
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    ctx.fillStyle = this.color;
    // 多支型
    ctx.fillRect(-18, -10, 36, 20);
    ctx.fillRect(-6, -18, 12, 36);
    // 垂发单元
    ctx.fillStyle = '#440000';
    for(let i=0; i<3; i++) { ctx.fillRect(5, -12 + i*10, 6, 6); }
    ctx.restore();
    this.drawHealthBar();
    
    ctx.fillStyle = this.needSupply ? '#ff0000' : '#fff'; 
    ctx.font = '9px Arial'; ctx.textAlign = 'center';
    ctx.fillText(`导弹:${this.missileAmmo}`, 0, 40);
  }
}

// ======= 4. 补给舰 (接口型) =======
class SupplyShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, '#ffaa00', isEnemy, { crew: 4, speed: 400, turnSpeed: 4, detectRange: 2000 });
    this.type = 'supply';
    this.state = 'seek';
    this.dockTarget = null;
  }
  update() {
    super.update();
    if (this.dead) return;

    // 寻找需要补给的友军
    if (this.state === 'seek') {
      this.dockTarget = entities.find(e => e.isEnemy === this.isEnemy && !e.dead && e.needSupply && dist(this, e) < this.detectRange);
      
      if (this.dockTarget) {
        this.state = 'approach';
      } else {
        // 无目标巡逻
        if (this.wanderAngle === undefined || Math.random() < 0.02) this.wanderAngle = Math.random() * Math.PI * 2;
        this.driveToAngle(this.wanderAngle, 0.5);
      }
    }

    // 靠近目标
    if (this.state === 'approach') {
      if (!this.dockTarget || this.dockTarget.dead) {
        this.state = 'seek'; return;
      }
      this.driveToAngle(angleTo(this, this.dockTarget), 1.2);
      
      // 距离足够近,开始对接
      if (dist(this, this.dockTarget) < 45) {
        this.state = 'docking';
        this.vx = 0; this.vy = 0;
      }
    }

    // 对接补给中
    if (this.state === 'docking') {
      if (!this.dockTarget || this.dockTarget.dead) {
        this.state = 'seek'; return;
      }
      this.vx = 0; this.vy = 0;
      // 调整姿态,对着友舰
      this.angle = angleTo(this, this.dockTarget);
      
      // 按照手稿速率:2发/秒 (每帧约 2/60)
      this.dockTarget.missileAmmo += 2 / 60;
      
      // 补给特效
      if (Math.random() < 0.3) createExplosion(this.x, this.y, '#00ff00', 1);
      
      if (this.dockTarget.missileAmmo >= this.dockTarget.maxAmmo) {
        this.dockTarget.missileAmmo = this.dockTarget.maxAmmo;
        this.dockTarget.needSupply = false;
        this.state = 'seek'; // 补给完毕,分离
      }
    }
  }
  draw() {
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    ctx.fillStyle = this.color;
    // 粗壮补给舱
    ctx.beginPath(); ctx.arc(0, 0, 16, 0, Math.PI*2); ctx.fill();
    // 接口
    ctx.fillStyle = '#fff'; ctx.fillRect(16, -6, 10, 12);
    // 状态灯
    ctx.fillStyle = this.state === 'docking' ? '#00ff00' : '#ff0000';
    ctx.beginPath(); ctx.arc(0, 0, 6, 0, Math.PI*2); ctx.fill();
    ctx.restore();
    this.drawHealthBar();
    
    if (this.state === 'docking') {
      ctx.strokeStyle = '#00ff00'; ctx.lineWidth = 2; ctx.setLineDash([5,5]);
      ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.lineTo(this.dockTarget.x, this.dockTarget.y); ctx.stroke();
      ctx.setLineDash([]);
    }
  }
}

// ======= 武器与子弹系统 =======
class Bullet {
  constructor(x, y, angle, isEnemy, type) {
    this.x = x; this.y = y; this.angle = angle;
    this.isEnemy = isEnemy; this.type = type;
    this.dead = false; this.life = 150;
    
    if (type === 'missile') { this.speed = 3.5; this.turnSpeed = 0.04; this.damage = 80; }
    else if (type === 'laser_main') { this.speed = 15; this.damage = 8; }
    else if (type === 'railgun') { this.speed = 25; this.damage = 150; }
    
    this.vx = Math.cos(angle) * this.speed;
    this.vy = Math.sin(angle) * this.speed;
  }
  update() {
    if (this.dead) return;
    
    // 导弹追踪
    if (this.type === 'missile') {
      let target = entities.find(e => e.isEnemy !== this.isEnemy && !e.dead);
      if (target) {
        let targetAngle = angleTo(this, target);
        let currentAngle = Math.atan2(this.vy, this.vx);
        let diff = normalizeAngle(targetAngle - currentAngle);
        currentAngle += Math.sign(diff) * this.turnSpeed;
        this.vx = Math.cos(currentAngle) * this.speed;
        this.vy = Math.sin(currentAngle) * this.speed;
        this.angle = currentAngle;
      }
    }

    this.x += this.vx; this.y += this.vy;
    this.life--;
    if (this.life <= 0 || this.x < 0 || this.x > W || this.y < 0 || this.y > H) this.dead = true;

    // 命中检测
    for (let e of entities) {
      if (!e.dead && e.isEnemy !== this.isEnemy && dist(this, e) < e.radius) {
        e.hp -= this.damage;
        this.dead = true;
        createExplosion(this.x, this.y, this.type==='missile'?'#ffaa00':'#fff', 5);
        if (e.hp <= 0) e.dead = true;
      }
    }
  }
  draw() {
    if (this.dead) return;
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    if (this.type === 'missile') {
      ctx.fillStyle = '#ffaa00'; ctx.fillRect(-6, -2, 12, 4); // 导弹
    } else if (this.type === 'laser_main') {
      ctx.fillStyle = '#66ccff'; ctx.fillRect(-15, -1, 30, 2); // 激光
    } else {
      ctx.fillStyle = '#00ffff'; ctx.fillRect(-15, -1.5, 30, 3); // 轨道炮
    }
    ctx.restore();
  }
}

// ======= 特效系统 =======
function createExplosion(x, y, color, count) {
  for (let i = 0; i < count; i++) {
    let a = Math.random() * Math.PI * 2;
    let s = Math.random() * 4;
    particles.push({
      x, y, vx: Math.cos(a)*s, vy: Math.sin(a)*s,
      life: 20 + Math.random()*20, maxLife: 40, color, size: Math.random()*2.5 + 1
    });
  }
}

// ======= 游戏流程 =======
function startGame() {
  document.getElementById('ui-layer').style.display = 'none';
  document.getElementById('hud').style.display = 'flex';
  gameState = 'playing';
  entities = []; bullets = []; particles = [];
  
  // 生成蓝方 (左侧)
  let bY = H / 2;
  for(let i=0; i<fleetConfig.b_laser; i++) entities.push(new LaserShip(100, bY - 100 + i*100, false));
  for(let i=0; i<fleetConfig.b_assault; i++) entities.push(new AssaultShip(200, bY - 50 + i*100, false));
  
  // 生成红方 (右侧)
  let rY = H / 2;
  for(let i=0; i<fleetConfig.r_missile; i++) entities.push(new MissileShip(W - 100, rY - 100 + i*100, true));
  for(let i=0; i<fleetConfig.r_supply; i++) entities.push(new SupplyShip(W - 200, rY + (i-1)*80, true));
  
  loop();
}

function checkWin() {
  let blueAlive = entities.some(e => !e.isEnemy && !e.dead);
  let redAlive = entities.some(e => e.isEnemy && !e.dead);
  if (!blueAlive || !redAlive) {
    gameState = 'ended';
    setTimeout(() => {
      ctx.fillStyle = 'rgba(0,0,0,0.8)'; ctx.fillRect(0,0,W,H);
      ctx.fillStyle = '#fff'; ctx.font = 'bold 30px sans-serif'; ctx.textAlign = 'center';
      ctx.fillText(blueAlive ? '蓝方胜利' : (redAlive ? '红方胜利' : '同归于尽'), W/2, H/2);
      ctx.font = '16px sans-serif'; ctx.fillStyle = '#aaa';
      ctx.fillText('刷新页面重新开始', W/2, H/2 + 40);
    }, 1000);
    return true;
  }
  return false;
}

// 主循环
function loop() {
  if (gameState !== 'playing') return;
  
  ctx.fillStyle = 'rgba(2, 4, 10, 0.4)'; // 拖影
  ctx.fillRect(0, 0, W, H);

  // 画星空
  ctx.fillStyle = '#fff';
  for(let i=0; i<30; i++) { ctx.globalAlpha = Math.random()*0.6; ctx.fillRect(Math.random()*W, Math.random()*H, 1.5, 1.5); }
  ctx.globalAlpha = 1;

  // 更新实体
  for (let e of entities) {
    e.update();
    if (!e.dead) e.draw();
  }

  // 更新子弹
  for (let i = bullets.length - 1; i >= 0; i--) {
    bullets[i].update();
    if (!bullets[i].dead) bullets[i].draw();
    else bullets.splice(i, 1);
  }

  // 更新粒子
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i];
    p.x += p.vx; p.y += p.vy; p.life--;
    ctx.globalAlpha = Math.max(0, p.life / p.maxLife);
    ctx.fillStyle = p.color;
    ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); ctx.fill();
    if (p.life <= 0) particles.splice(i, 1);
  }
  ctx.globalAlpha = 1;

  // UI 更新
  document.getElementById('hud-left').innerHTML = `蓝方存活: ${entities.filter(e => !e.isEnemy && !e.dead).length}`;
  document.getElementById('hud-right').innerHTML = `红方存活: ${entities.filter(e => e.isEnemy && !e.dead).length}`;

  if (!checkWin()) {
    requestAnimationFrame(loop);
  }
}
</script>
</body>
</html>

Game Source: 深空舰队模拟器 - 完整版

Creator: EpicCoder88

Libraries: none

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