🎮ArcadeLab

经典大战 · 角色选择

by ApexFalcon66
675 lines22.5 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>经典大战 · 角色选择</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body { height: 100%; overflow: hidden; }
  body {
    display: grid; place-items: center;
    background: radial-gradient(ellipse at 50% 45%, #0b1a24 0%, #05090d 55%, #010204 100%);
    font-family: -apple-system, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
    color: #e0f7ff;
  }

  /* ========== 通用界面 ========== */
  .screen {
    position: fixed; inset: 0;
    display: none; flex-direction: column;
    align-items: center; justify-content: center;
    z-index: 10;
  }
  .screen.active { display: flex; }

  /* ========== 界面一:开始界面 ========== */
  #screen-title h1 {
    font-size: clamp(28px, 6vw, 56px);
    letter-spacing: 6px;
    font-weight: 900;
    color: #00ffe0;
    text-shadow: 0 0 30px rgba(0,255,224,.6), 0 0 80px rgba(0,255,224,.25);
    margin-bottom: 10px;
  }
  #screen-title p {
    font-size: 13px; letter-spacing: 4px;
    color: rgba(0,255,224,.4); margin-bottom: 48px;
  }
  .btn-start {
    padding: 16px 64px;
    font-size: 20px; font-weight: 700; letter-spacing: 6px;
    color: #021016; background: linear-gradient(135deg, #00ffe0, #00b8a0);
    border: none; border-radius: 6px; cursor: pointer;
    box-shadow: 0 0 24px rgba(0,255,224,.5), 0 0 60px rgba(0,255,224,.2);
    transition: transform .15s, box-shadow .15s;
  }
  .btn-start:hover { transform: scale(1.04); box-shadow: 0 0 36px rgba(0,255,224,.8); }

  /* ========== 界面二:选人界面 ========== */
  #screen-select { padding: 24px; }
  #screen-select h2 {
    font-size: 18px; letter-spacing: 4px; color: rgba(0,255,224,.7);
    margin-bottom: 6px;
  }
  .hint { font-size: 12px; color: rgba(160,200,220,.45); margin-bottom: 28px; }
  .hint b { color: #00ffe0; }

  .char-row { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; }

  .char-card {
    width: 170px; padding: 18px 14px;
    border-radius: 12px; cursor: pointer;
    background: rgba(10, 30, 42, .85);
    border: 2px solid rgba(80, 200, 255, .15);
    transition: border-color .2s, box-shadow .2s, transform .15s;
    text-align: center; user-select: none;
  }
  .char-card:hover { transform: translateY(-4px); border-color: rgba(80,200,255,.35); }
  .char-card.selected {
    border-color: #00ffe0;
    box-shadow: 0 0 22px rgba(0,255,224,.45), inset 0 0 30px rgba(0,255,224,.06);
  }

  .char-orb {
    width: 64px; height: 64px; border-radius: 50%;
    margin: 0 auto 12px;
    display: flex; align-items: center; justify-content: center;
    font-size: 22px; font-weight: 900; color: #021016;
  }
  .char-name { font-size: 16px; font-weight: 700; margin-bottom: 4px; }
  .char-src { font-size: 11px; color: rgba(160,200,220,.45); margin-bottom: 10px; }
  .char-desc { font-size: 11px; line-height: 1.6; color: rgba(200,220,235,.6); text-align: left; }

  .btn-go {
    margin-top: 32px; padding: 14px 60px;
    font-size: 17px; font-weight: 700; letter-spacing: 4px;
    border: none; border-radius: 6px; cursor: pointer;
    background: linear-gradient(135deg, #00ffe0, #00b8a0); color: #021016;
    box-shadow: 0 0 20px rgba(0,255,224,.4);
    transition: opacity .2s, transform .15s;
  }
  .btn-go:disabled {
    opacity: .25; cursor: not-allowed; transform: none; box-shadow: none;
    background: #2a4a4a; color: #5a7a7a;
  }
  .btn-go:not(:disabled):hover { transform: scale(1.04); }

  /* ========== 地图画布 ========== */
  #arena-wrap { position: fixed; inset: 0; display: none; place-items: center; z-index: 5; }
  #arena-wrap.active { display: grid; }
  canvas { display: block; touch-action: none; }

  .label {
    position: fixed; left: 50%; bottom: 10px; transform: translateX(-50%);
    font-size: 11px; letter-spacing: 2px; color: rgba(0,255,224,.35);
    pointer-events: none; z-index: 6;
  }
</style>
</head>
<body>

<!-- ==================== 界面一:开始 ==================== -->
<div id="screen-title" class="screen active">
  <h1>经典大战</h1>
  <p>CLASSIC ARENA · 让经典角色自己打一架</p>
  <button class="btn-start" id="btnEnter">开 始 游 戏</button>
</div>

<!-- ==================== 界面二:选人 ==================== -->
<div id="screen-select" class="screen">
  <h2>选 择 参 战 角 色</h2>
  <p class="hint">点击角色图标选中,至少选中 <b>2 名</b> 角色后方可开始</p>
  <div class="char-row" id="charRow"></div>
  <button class="btn-go" id="btnFight" disabled>开 始 决 斗</button>
</div>

<!-- ==================== 地图 ==================== -->
<div id="arena-wrap">
  <canvas id="game"></canvas>
</div>
<div class="label" id="arenaLabel" style="display:none;">CLASSIC ARENA · 20 × 20 · 亮色边框带物理碰撞</div>

<script>
(function () {
  'use strict';

  /* ============================================================
     地图配置(沿用之前的物理框架)
     ============================================================ */
  const COLS = 20, ROWS = 20, CELL = 32, WALL = 16;
  const GRID_W = COLS * CELL, GRID_H = ROWS * CELL;
  const WORLD_W = GRID_W + WALL * 2, WORLD_H = GRID_H + WALL * 2;

  const WALLS = [
    { x: 0, y: 0, w: WORLD_W, h: WALL },
    { x: 0, y: WORLD_H - WALL, w: WORLD_W, h: WALL },
    { x: 0, y: 0, w: WALL, h: WORLD_H },
    { x: WORLD_W - WALL, y: 0, w: WALL, h: WORLD_H }
  ];

  function aabbHit(a, b) {
    return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
  }
  function hitsWall(rect) {
    for (let i = 0; i < WALLS.length; i++) if (aabbHit(rect, WALLS[i])) return true;
    return false;
  }
  function moveBody(body, dx, dy) {
    body.x += dx;
    for (let i = 0; i < WALLS.length; i++) {
      const w = WALLS[i];
      if (!aabbHit(body, w)) continue;
      if (dx > 0) body.x = w.x - body.w;
      else if (dx < 0) body.x = w.x + w.w;
    }
    body.y += dy;
    for (let i = 0; i < WALLS.length; i++) {
      const w = WALLS[i];
      if (!aabbHit(body, w)) continue;
      if (dy > 0) body.y = w.y - body.h;
      else if (dy < 0) body.y = w.y + w.h;
    }
    return body;
  }

  /* ============================================================
     角色数据(含原文依据的机制设计)
     ============================================================ */
  const ROSTER = [
    {
      id: 'xiangzi',
      name: '祥子',
      source: '《骆驼祥子》',
      orbColor: '#d4a24e',          // 土黄——黄包车、土地
      orbBorder: '#f0c878',
      maxHp: 120,
      defense: 4,                   // 中等防御,“铁做的”身体
      attack: 9,
      speed: 1.15,
      radius: 14,
      // 特殊条:洋车进度(攒钱买车)
      specialLabel: '洋车',
      specialMax: 100,
      specialValue: 0,
      // 被动:每 8 秒“攒够一笔钱”,洋车进度 +25;满 100 时触发“买了新车”:移速 ×1.6,攻击 +6,持续 6 秒,随后进度归零
      skill: {
        name: '三起三落',
        desc: '每 8 秒攒钱 +25 洋车进度。满 100 触发「买了新车」:移速 ×1.6,攻击 +6,持续 6 秒后进度归零。',
        interval: 8,
        gain: 25
      },
      // 战斗行为:近战冲撞
      behavior: 'melee'
    },
    {
      id: 'ruifeng',
      name: '瑞丰',
      source: '《四世同堂》',
      orbColor: '#c87a9a',          // 俗艳的粉紫——虚荣、浮浅
      orbBorder: '#e8a0c0',
      maxHp: 85,                    // 最低血量
      defense: 1,
      attack: 7,
      speed: 1.35,                  // 跑得快,钻营
      radius: 12,
      specialLabel: '官威',
      specialMax: 100,
      specialValue: 40,             // 起始有一定官威
      // 被动:受击时消耗 10 点官威,减免 50% 伤害(“我是科长!”);官威耗尽后变得脆弱。每 6 秒自动恢复 15 点。
      skill: {
        name: '科长身份',
        desc: '受击时消耗 10 点官威减免 50% 伤害。官威耗尽后防御降为 0。每 6 秒恢复 15 点。',
        costOnHit: 10,
        regenInterval: 6,
        regenAmount: 15
      },
      behavior: 'ranged'            // 远程:扔出“官腔”文字弹
    },
    {
      id: 'fushan',
      name: '富善先生',
      source: '《四世同堂》',
      orbColor: '#4a9e7a',          // 沉稳的墨绿——守旧、可靠
      orbBorder: '#7ed0aa',
      maxHp: 105,
      defense: 8,                   // 最高防御,守旧
      attack: 6,
      speed: 0.85,                  // 慢
      radius: 15,
      specialLabel: '守旧',
      specialMax: 100,
      specialValue: 60,             // 起始高守旧
      // 被动:守旧值越高,减伤越高(最高 40%)。每次受击守旧 -8(因为“新事物”冲击了他)。守旧低于 20 时减伤失效。
      skill: {
        name: '小琉璃厂',
        desc: '守旧值提供最高 40% 减伤。受击时守旧 -8。低于 20 时减伤失效。每 5 秒恢复 10 点。',
        maxReduction: 0.40,
        lossOnHit: 8,
        threshold: 20,
        regenInterval: 5,
        regenAmount: 10
      },
      behavior: 'tank'              // 坦克:缓慢靠近,高减伤
    }
  ];

  /* ============================================================
     界面控制
     ============================================================ */
  const screenTitle  = document.getElementById('screen-title');
  const screenSelect = document.getElementById('screen-select');
  const arenaWrap    = document.getElementById('arena-wrap');
  const arenaLabel   = document.getElementById('arenaLabel');
  const charRow      = document.getElementById('charRow');
  const btnFight     = document.getElementById('btnFight');

  const selected = new Set();

  function buildCharCards() {
    charRow.innerHTML = '';
    ROSTER.forEach(ch => {
      const card = document.createElement('div');
      card.className = 'char-card';
      card.dataset.id = ch.id;

      // 用 CSS 渐变模拟“小圆球”的立体感
      const orb = document.createElement('div');
      orb.className = 'char-orb';
      orb.style.background = `radial-gradient(circle at 35% 30%, ${ch.orbBorder}, ${ch.orbColor} 65%, #1a1a1a)`;
      orb.style.boxShadow = `0 0 14px ${ch.orbColor}55`;

      const name = document.createElement('div');
      name.className = 'char-name';
      name.textContent = ch.name;

      const src = document.createElement('div');
      src.className = 'char-src';
      src.textContent = ch.source;

      const desc = document.createElement('div');
      desc.className = 'char-desc';
      desc.innerHTML = `<b style="color:#00ffe0;">${ch.skill.name}</b><br>${ch.skill.desc}`;

      card.appendChild(orb);
      card.appendChild(name);
      card.appendChild(src);
      card.appendChild(desc);

      card.addEventListener('click', () => toggleSelect(ch.id, card));
      charRow.appendChild(card);
    });
  }

  function toggleSelect(id, card) {
    if (selected.has(id)) {
      selected.delete(id);
      card.classList.remove('selected');
    } else {
      selected.add(id);
      card.classList.add('selected');
    }
    btnFight.disabled = selected.size < 2;
  }

  document.getElementById('btnEnter').addEventListener('click', () => {
    screenTitle.classList.remove('active');
    screenSelect.classList.add('active');
  });

  btnFight.addEventListener('click', () => {
    if (selected.size < 2) return;
    screenSelect.classList.remove('active');
    arenaWrap.classList.add('active');
    arenaLabel.style.display = 'block';
    startBattle(Array.from(selected));
  });

  buildCharCards();

  /* ============================================================
     战斗系统
     ============================================================ */
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');

  let fighters = [];
  let running = false;
  let lastTime = 0;
  let battleOver = false;
  let winnerText = '';

  function resize() {
    const size = Math.min(window.innerWidth * 0.94, window.innerHeight * 0.94, 780);
    const dpr = window.devicePixelRatio || 1;
    canvas.style.width = size + 'px';
    canvas.style.height = size + 'px';
    canvas.width = Math.round(size * dpr);
    canvas.height = Math.round(size * dpr);
  }
  window.addEventListener('resize', resize);
  resize();

  function startBattle(ids) {
    fighters = [];
    battleOver = false;
    winnerText = '';

    // 均匀分布在场地内圈
    const margin = WALL + CELL * 3;
    const usableW = GRID_W - CELL * 6;
    const usableH = GRID_H - CELL * 6;

    ids.forEach((id, i) => {
      const proto = ROSTER.find(r => r.id === id);
      const f = {
        ...proto,
        hp: proto.maxHp,
        x: margin + (usableW / (ids.length - 1 || 1)) * i,
        y: margin + usableH * (i % 2 === 0 ? 0.15 : 0.7),
        vx: 0, vy: 0,
        specialValue: proto.specialValue,
        hitFlash: 0,
        alive: true,
        // 技能计时器
        skillTimer: 0,
        regenTimer: 0,
        buffTimer: 0,          // 祥子“新车”buff 剩余
        atkCooldown: 0
      };
      // 确保出生点在可行走区域
      f.x = Math.max(WALL + f.radius, Math.min(WORLD_W - WALL - f.radius, f.x));
      f.y = Math.max(WALL + f.radius, Math.min(WORLD_H - WALL - f.radius, f.y));
      fighters.push(f);
    });

    running = true;
    lastTime = performance.now();
    requestAnimationFrame(loop);
  }

  /* ---------- 行为逻辑 ---------- */
  function updateFighter(f, dt, now) {
    if (!f.alive) return;

    // 冷却
    f.atkCooldown = Math.max(0, f.atkCooldown - dt);

    // ---- 祥子:攒钱 / 新车 ----
    if (f.id === 'xiangzi') {
      f.skillTimer += dt;
      if (f.skillTimer >= f.skill.interval) {
        f.skillTimer -= f.skill.interval;
        f.specialValue = Math.min(f.specialMax, f.specialValue + f.skill.gain);
        if (f.specialValue >= f.specialMax) {
          f.specialValue = 0;
          f.buffTimer = 6;          // 买了新车,6 秒
        }
      }
      if (f.buffTimer > 0) f.buffTimer = Math.max(0, f.buffTimer - dt);
    }

    // ---- 瑞丰:官威恢复 ----
    if (f.id === 'ruifeng') {
      f.regenTimer += dt;
      if (f.regenTimer >= f.skill.regenInterval) {
        f.regenTimer -= f.skill.regenInterval;
        f.specialValue = Math.min(f.specialMax, f.specialValue + f.skill.regenAmount);
      }
    }

    // ---- 富善先生:守旧恢复 ----
    if (f.id === 'fushan') {
      f.regenTimer += dt;
      if (f.regenTimer >= f.skill.regenInterval) {
        f.regenTimer -= f.skill.regenInterval;
        f.specialValue = Math.min(f.specialMax, f.specialValue + f.skill.regenAmount);
      }
    }

    // 找最近敌人
    let target = null, minDist = Infinity;
    fighters.forEach(o => {
      if (o === f || !o.alive) return;
      const d = Math.hypot(o.x - f.x, o.y - f.y);
      if (d < minDist) { minDist = d; target = o; }
    });
    if (!target) return;

    // 移动 / 攻击
    const dx = target.x - f.x, dy = target.y - f.y;
    const dist = Math.hypot(dx, dy) || 1;
    const nx = dx / dist, ny = dy / dist;

    const speedMul = f.buffTimer > 0 ? 1.6 : 1;
    const spd = f.speed * 62 * speedMul;

    if (f.behavior === 'melee') {
      // 近战:冲到 30px 内攻击
      if (dist > 32) {
        moveBody(f, nx * spd * dt, ny * spd * dt);
      } else if (f.atkCooldown <= 0) {
        const atk = f.attack + (f.buffTimer > 0 ? 6 : 0);
        dealDamage(target, atk, f);
        f.atkCooldown = 0.85;
      }
    } else if (f.behavior === 'ranged') {
      // 远程:保持 120px 左右
      if (dist > 140) moveBody(f, nx * spd * dt, ny * spd * dt);
      else if (dist < 90) moveBody(f, -nx * spd * dt, -ny * spd * dt);
      else if (f.atkCooldown <= 0) {
        dealDamage(target, f.attack, f);
        f.atkCooldown = 1.1;
      }
    } else {
      // 坦克:缓慢靠近,贴脸攻击
      if (dist > 36) moveBody(f, nx * spd * dt, ny * spd * dt);
      else if (f.atkCooldown <= 0) {
        dealDamage(target, f.attack, f);
        f.atkCooldown = 1.3;
      }
    }
  }

  function dealDamage(target, rawAtk, attacker) {
    if (!target.alive) return;

    let dmg = rawAtk;

    // 祥子:目标防御
    dmg -= target.defense * 0.5;

    // ---- 瑞丰:官威减伤 ----
    if (target.id === 'ruifeng' && target.specialValue >= target.skill.costOnHit) {
      target.specialValue -= target.skill.costOnHit;
      dmg *= 0.5;
    } else if (target.id === 'ruifeng' && target.specialValue < target.skill.costOnHit) {
      // 官威耗尽,防御归 0(已在上面 -defense 中体现,这里额外不再减)
    }

    // ---- 富善先生:守旧减伤 ----
    if (target.id === 'fushan' && target.specialValue > target.skill.threshold) {
      const ratio = target.specialValue / target.specialMax;
      const reduction = ratio * target.skill.maxReduction;
      dmg *= (1 - reduction);
      target.specialValue = Math.max(0, target.specialValue - target.skill.lossOnHit);
    }

    // 最低伤害
    dmg = Math.max(1, Math.round(dmg));

    target.hp -= dmg;
    target.hitFlash = 0.15;

    if (target.hp <= 0) {
      target.hp = 0;
      target.alive = false;
    }
  }

  function checkBattleEnd() {
    const alive = fighters.filter(f => f.alive);
    if (alive.length <= 1 && !battleOver) {
      battleOver = true;
      winnerText = alive.length === 1 ? alive[0].name + ' 胜出' : '平局';
    }
  }

  /* ---------- 渲染 ---------- */
  function drawArena(time) {
    const scale = canvas.width / WORLD_W;
    ctx.setTransform(scale, 0, 0, scale, 0, 0);
    ctx.clearRect(0, 0, WORLD_W, WORLD_H);

    // 网格底色
    const bg = ctx.createLinearGradient(WALL, WALL, WALL + GRID_W, WALL + GRID_H);
    bg.addColorStop(0, '#0b1927'); bg.addColorStop(0.45, '#0d1e2e'); bg.addColorStop(1, '#061018');
    ctx.fillStyle = bg;
    ctx.fillRect(WALL, WALL, GRID_W, GRID_H);

    const glow = ctx.createRadialGradient(WORLD_W/2, WORLD_H/2, 8, WORLD_W/2, WORLD_H/2, WORLD_W*0.55);
    glow.addColorStop(0, 'rgba(0,210,255,0.07)'); glow.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = glow;
    ctx.fillRect(WALL, WALL, GRID_W, GRID_H);

    // 网格线
    ctx.lineWidth = 1; ctx.strokeStyle = 'rgba(80,200,255,0.10)';
    ctx.beginPath();
    for (let c = 0; c <= COLS; c++) { const x = WALL + c*CELL; ctx.moveTo(x, WALL); ctx.lineTo(x, WALL+GRID_H); }
    for (let r = 0; r <= ROWS; r++) { const y = WALL + r*CELL; ctx.moveTo(WALL, y); ctx.lineTo(WALL+GRID_W, y); }
    ctx.stroke();

    ctx.strokeStyle = 'rgba(80,210,255,0.22)';
    ctx.beginPath();
    for (let c = 0; c <= COLS; c += 5) { const x = WALL + c*CELL; ctx.moveTo(x, WALL); ctx.lineTo(x, WALL+GRID_H); }
    for (let r = 0; r <= ROWS; r += 5) { const y = WALL + r*CELL; ctx.moveTo(WALL, y); ctx.lineTo(WALL+GRID_W, y); }
    ctx.stroke();

    // 亮色边框
    const pulse = 0.5 + 0.5 * Math.sin(time * 0.0022);
    ctx.save();
    ctx.shadowColor = 'rgba(0,255,224,0.85)'; ctx.shadowBlur = 8 + pulse * 18;
    ctx.beginPath(); ctx.rect(0, 0, WORLD_W, WORLD_H); ctx.rect(WALL, WALL, GRID_W, GRID_H);
    ctx.fillStyle = '#00cdb2'; ctx.fill('evenodd');
    ctx.restore();

    ctx.strokeStyle = 'rgba(190,255,248,' + (0.45 + pulse * 0.45) + ')';
    ctx.lineWidth = 2; ctx.strokeRect(WALL+1, WALL+1, GRID_W-2, GRID_H-2);
    ctx.strokeStyle = 'rgba(0,255,224,0.5)'; ctx.lineWidth = 2; ctx.strokeRect(1, 1, WORLD_W-2, WORLD_H-2);
  }

  function drawFighters(time) {
    fighters.forEach(f => {
      if (!f.alive) return;

      const x = f.x, y = f.y, r = f.radius;

      // 受击闪白
      if (f.hitFlash > 0) {
        f.hitFlash -= 0.016;
      }

      // ---- 小圆球本体(立体渐变) ----
      const grad = ctx.createRadialGradient(x - r*0.35, y - r*0.35, r*0.1, x, y, r);
      grad.addColorStop(0, f.orbBorder);
      grad.addColorStop(0.7, f.orbColor);
      grad.addColorStop(1, '#111');
      ctx.beginPath();
      ctx.arc(x, y, r, 0, Math.PI * 2);
      ctx.fillStyle = grad;
      ctx.fill();

      // 描边
      ctx.lineWidth = 2.5;
      ctx.strokeStyle = f.hitFlash > 0 ? '#ffffff' : f.orbBorder;
      ctx.stroke();

      // 祥子“新车”buff 光环
      if (f.id === 'xiangzi' && f.buffTimer > 0) {
        ctx.beginPath();
        ctx.arc(x, y, r + 5 + Math.sin(time*0.01)*3, 0, Math.PI*2);
        ctx.strokeStyle = 'rgba(255, 220, 100, 0.8)';
        ctx.lineWidth = 2;
        ctx.stroke();
      }

      // ---- 名称 ----
      ctx.font = 'bold 11px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'bottom';
      ctx.fillStyle = 'rgba(255,255,255,0.9)';
      ctx.fillText(f.name, x, y - r - 22);

      // ---- 血条 ----
      const barW = 44, barH = 5;
      const barX = x - barW/2, barY = y - r - 16;

      ctx.fillStyle = 'rgba(0,0,0,0.65)';
      ctx.fillRect(barX, barY, barW, barH);

      const hpRatio = f.hp / f.maxHp;
      const hpColor = hpRatio > 0.5 ? '#3ee88a' : hpRatio > 0.25 ? '#e8c83e' : '#e84a4a';
      ctx.fillStyle = hpColor;
      ctx.fillRect(barX, barY, barW * hpRatio, barH);

      ctx.strokeStyle = 'rgba(255,255,255,0.25)';
      ctx.lineWidth = 1;
      ctx.strokeRect(barX, barY, barW, barH);

      // ---- 特殊条(如果有) ----
      if (f.specialMax > 0) {
        const spBarY = barY + barH + 3;
        const spRatio = f.specialValue / f.specialMax;
        const spColor = f.id === 'xiangzi' ? '#f0c878'
                      : f.id === 'ruifeng' ? '#e8a0c0'
                      : '#7ed0aa';

        ctx.fillStyle = 'rgba(0,0,0,0.55)';
        ctx.fillRect(barX, spBarY, barW, 3);

        ctx.fillStyle = spColor;
        ctx.fillRect(barX, spBarY, barW * spRatio, 3);
      }
    });
  }

  function drawWinner() {
    if (!battleOver) return;
    ctx.save();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.fillStyle = 'rgba(0,0,0,0.55)';
    ctx.fillRect(0, canvas.height * 0.38, canvas.width, canvas.height * 0.18);

    ctx.fillStyle = '#00ffe0';
    ctx.font = `900 ${Math.round(canvas.width * 0.055)}px -apple-system, "PingFang SC", sans-serif`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.shadowColor = 'rgba(0,255,224,0.7)';
    ctx.shadowBlur = 24;
    ctx.fillText(winnerText, canvas.width / 2, canvas.height * 0.47);
    ctx.restore();
  }

  /* ---------- 主循环 ---------- */
  function loop(now) {
    if (!running) return;

    const dt = Math.min(0.05, (now - lastTime) / 1000);
    lastTime = now;

    if (!battleOver) {
      fighters.forEach(f => updateFighter(f, dt, now));
      checkBattleEnd();
    }

    drawArena(now);
    drawFighters(now);
    drawWinner();

    requestAnimationFrame(loop);
  }

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

Game Source: 经典大战 · 角色选择

Creator: ApexFalcon66

Libraries: none

Complexity: complex (675 lines, 22.5 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-apexfalcon66-mvderb75" to link back to the original. Then publish at arcadelab.ai/publish.