🎮ArcadeLab

援救狗星人

by BlazingDragon43
1238 lines33.0 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>援救狗星人</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; font-family: "Microsoft YaHei", "PingFang SC", sans-serif; }
  body {
    background: #0b1020;
    color: #fff;
    overflow: hidden;
    width: 100vw;
    height: 100vh;
    user-select: none;
  }
  #game {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
  }

  /* 屏幕震动 */
  .shake { animation: shake 0.25s; }
  @keyframes shake {
    0%,100% { transform: translate(0,0); }
    25% { transform: translate(-4px, 2px); }
    50% { transform: translate(4px, -2px); }
    75% { transform: translate(-2px, -3px); }
  }

  /* 通用按钮 */
  .btn {
    background: linear-gradient(180deg, #ffc857, #f28c28);
    color: #3a2410;
    border: 3px solid #fff;
    padding: 10px 16px;
    border-radius: 14px;
    font-weight: bold;
    box-shadow: 0 4px 0 #8a4b14, 0 6px 10px #0006;
  }
  .btn:active { transform: translateY(2px); box-shadow: 0 2px 0 #8a4b14; }

  /* 通用卡片 */
  .card {
    background: linear-gradient(180deg, #fff8e1, #ffe28a);
    border: 3px solid #fff;
    border-radius: 14px;
    box-shadow: 0 4px 10px #0006;
    color: #3a2410;
  }

  /* ========== 主界面 ========== */
  #home {
    position: absolute;
    inset: 0;
    background:
      radial-gradient(ellipse at 50% 20%, #ffd89b 0%, transparent 60%),
      linear-gradient(180deg, #3a7bd5 0%, #1e3c72 100%);
    z-index: 50;
    display: flex;
    flex-direction: column;
  }
  .home-top {
    padding: 16px;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .home-title {
    font-size: 28px;
    font-weight: 900;
    color: #fff;
    text-shadow: 3px 3px 0 #8a4b14, 6px 6px 10px #0008;
  }
  .resource-bar {
    display: flex;
    gap: 8px;
  }
  .resource {
    background: #0006;
    padding: 6px 10px;
    border-radius: 20px;
    font-size: 14px;
    border: 2px solid #ffd857;
  }
  .home-map {
    flex: 1;
    position: relative;
    margin: 12px;
    border-radius: 20px;
    background:
      linear-gradient(180deg, #a8e6cf 0%, #6bbf59 60%, #4a7c3a 100%);
    border: 4px solid #fff;
    overflow: hidden;
  }
  .map-mountain {
    position: absolute;
    bottom: 30%;
    width: 100%;
    height: 40%;
    background:
      radial-gradient(circle at 20% 100%, #3f6b4f 0%, transparent 40%),
      radial-gradient(circle at 60% 100%, #4a7c59 0%, transparent 45%),
      radial-gradient(circle at 85% 100%, #5a8f6a 0%, transparent 35%);
  }
  .map-road {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 35%;
    background: linear-gradient(180deg, #d4a574, #a67c52);
    border-top: 4px solid #7a5a2a;
  }
  .map-node {
    position: absolute;
    width: 54px;
    height: 54px;
    border-radius: 50%;
    background: radial-gradient(circle, #ffd857 0%, #f28c28 100%);
    border: 3px solid #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    color: #3a2410;
    box-shadow: 0 4px 10px #0008;
  }
  .home-menu {
    padding: 12px 16px;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
  }
  .menu-item {
    background: linear-gradient(180deg, #5b8def, #2f5bb7);
    border: 3px solid #fff;
    border-radius: 14px;
    padding: 10px 4px;
    text-align: center;
    font-size: 13px;
    color: #fff;
    font-weight: bold;
    box-shadow: 0 4px 8px #0006;
  }

  /* ========== 战斗界面 ========== */
  #battle {
    position: absolute;
    inset: 0;
    display: none;
    z-index: 40;
  }
  .battle-scene {
    position: absolute;
    inset: 0;
    overflow: hidden;
  }
  .sky {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 40%;
    background: linear-gradient(180deg, #ffd89b 0%, #87ceeb 100%);
  }
  .far-hills {
    position: absolute;
    top: 20%;
    left: 0;
    width: 100%;
    height: 30%;
    background:
      radial-gradient(circle at 20% 100%, #4a7c59 0%, transparent 40%),
      radial-gradient(circle at 60% 90%, #3f6b4f 0%, transparent 45%),
      radial-gradient(circle at 85% 70%, #5a8f6a 0%, transparent 35%);
    opacity: 0.85;
  }
  .ground {
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 50%;
    background: linear-gradient(180deg, #8fbc8f 0%, #6f9a6a 40%, #557a45 100%);
  }
  .battle-path {
    position: absolute;
    top: 58%;
    left: 0;
    width: 100%;
    height: 16%;
    background: linear-gradient(180deg, #d4a574, #a67c52);
    border-top: 4px solid #7a5a2a;
    border-bottom: 4px solid #7a5a2a;
  }
  .base {
    position: absolute;
    top: 46%;
    width: 110px;
    height: 70px;
    border: 4px solid #fff;
    border-radius: 14px;
    background: linear-gradient(180deg, #e8c38a, #c99a55);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    color: #3a2410;
    z-index: 6;
  }
  .base-left { left: 10px; }
  .base-right { right: 10px; background: linear-gradient(180deg, #ef9a9a, #c62828); color: #fff; }
  .base-hp {
    position: absolute;
    top: -14px;
    left: 0;
    width: 100%;
    height: 8px;
    background: #222;
    border: 2px solid #fff;
    border-radius: 4px;
    overflow: hidden;
  }
  .base-hp-inner {
    height: 100%;
    background: linear-gradient(90deg, #4caf50, #8bc34a);
    transition: width 0.3s;
  }
  .base-hp-inner.enemy { background: linear-gradient(90deg, #f44336, #ff7961); }

  /* 狗将单位 */
  .dog-unit {
    position: absolute;
    width: 72px;
    height: 88px;
    z-index: 10;
    transition: left 0.15s linear;
  }
  .dog-body {
    position: relative;
    width: 100%;
    height: 100%;
  }
  .dog-head {
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 42px;
    height: 42px;
    border-radius: 50%;
    border: 3px solid #fff;
    z-index: 4;
  }
  .dog-torso {
    position: absolute;
    top: 26px;
    left: 50%;
    transform: translateX(-50%);
    width: 48px;
    height: 38px;
    border-radius: 20px;
  }
  .dog-leg {
    position: absolute;
    top: 60px;
    width: 12px;
    height: 24px;
    border-radius: 6px;
  }
  .dog-leg.left { left: 16px; }
  .dog-leg.right { right: 16px; }
  .dog-tail {
    position: absolute;
    top: 30px;
    right: -8px;
    width: 24px;
    height: 8px;
    border-radius: 8px;
    transform-origin: left center;
  }
  .dog-weapon {
    position: absolute;
    top: 22px;
    right: -12px;
    width: 8px;
    height: 40px;
    background: linear-gradient(180deg, #eee, #888);
    border: 1px solid #333;
    z-index: 3;
  }
  .dog-shadow {
    position: absolute;
    bottom: -4px;
    left: 50%;
    width: 60%;
    height: 8px;
    background: rgba(0,0,0,0.35);
    border-radius: 50%;
    transform: translateX(-50%);
  }
  .unit-hp {
    position: absolute;
    top: -10px;
    left: 50%;
    transform: translateX(-50%);
    width: 60px;
    height: 6px;
    background: #222;
    border: 1px solid #fff;
    border-radius: 3px;
    overflow: hidden;
    z-index: 5;
  }
  .unit-hp-inner {
    height: 100%;
    background: linear-gradient(90deg, #4caf50, #8bc34a);
    transition: width 0.25s;
  }
  .unit-hp-inner.enemy { background: linear-gradient(90deg, #f44336, #ff7961); }
  .unit-energy {
    position: absolute;
    top: -18px;
    left: 50%;
    transform: translateX(-50%);
    width: 40px;
    height: 4px;
    background: #222;
    border: 1px solid #fff;
    border-radius: 2px;
    overflow: hidden;
    z-index: 5;
  }
  .unit-energy-inner {
    height: 100%;
    background: linear-gradient(90deg, #64b5f6, #1976d2);
    width: 0;
    transition: width 0.2s;
  }

  /* 单位动作 */
  @keyframes dRun {
    0%,100% { transform: translateY(0) rotate(-1deg); }
    50% { transform: translateY(-4px) rotate(1deg); }
  }
  @keyframes dAttack {
    0% { transform: translateX(0) rotate(0); }
    40% { transform: translateX(18px) rotate(6deg); }
    100% { transform: translateX(0) rotate(0); }
  }
  @keyframes dHit {
    0%,100% { filter: brightness(1); }
    50% { filter: brightness(2.2) saturate(0); }
  }
  @keyframes dDie {
    0% { transform: rotate(0); opacity: 1; }
    100% { transform: rotate(75deg) translateY(14px); opacity: 0.4; }
  }
  .dog-unit.run .dog-body { animation: dRun 0.32s infinite ease-in-out; }
  .dog-unit.atk .dog-body { animation: dAttack 0.42s ease-out; }
  .dog-unit.hit .dog-body { animation: dHit 0.22s; }
  .dog-unit.dead .dog-body { animation: dDie 0.5s forwards; }

  /* 技能特效 */
  .fx {
    position: absolute;
    pointer-events: none;
    z-index: 20;
  }
  .slash-fx {
    width: 120px;
    height: 80px;
    background: radial-gradient(circle, #fff 0%, #ffd700 40%, transparent 70%);
    border-radius: 50%;
    animation: slash 0.42s forwards;
  }
  @keyframes slash {
    0% { transform: scale(0.2) rotate(-30deg); opacity: 1; }
    100% { transform: scale(1.5) rotate(35deg); opacity: 0; }
  }
  .lightning-fx {
    width: 70px;
    height: 130px;
    background: linear-gradient(180deg, #fff, #64b5f6, #1976d2);
    clip-path: polygon(40% 0, 60% 0, 50% 40, 70% 40, 30% 100, 50% 60, 20% 60);
    animation: lightning 0.4s forwards;
  }
  @keyframes lightning {
    0%,100% { opacity: 0; }
    20%,80% { opacity: 1; }
  }
  .wave-fx {
    width: 170px;
    height: 44px;
    border: 5px solid #ffeb3b;
    border-radius: 50%;
    animation: wave 0.55s forwards;
  }
  @keyframes wave {
    0% { transform: scale(0.15); opacity: 1; }
    100% { transform: scale(1.7); opacity: 0; }
  }
  .combo-fx {
    width: 90px;
    height: 70px;
    background: radial-gradient(circle, #ff5722 0%, #ffd700 50%, transparent 70%);
    border-radius: 50%;
    animation: combo 0.28s forwards;
  }
  @keyframes combo {
    0% { transform: scale(0.3); opacity: 1; }
    100% { transform: scale(1.2); opacity: 0; }
  }

  /* 伤害数字 */
  .dmg {
    position: absolute;
    font-size: 22px;
    font-weight: 900;
    color: #ff4444;
    text-shadow: 2px 2px 0 #000;
    animation: floatUp 0.85s forwards;
    z-index: 25;
  }
  .dmg.crit {
    font-size: 30px;
    color: #ffd700;
  }
  @keyframes floatUp {
    0% { transform: translateY(0) scale(0.8); opacity: 1; }
    30% { transform: translateY(-14px) scale(1.15); }
    100% { transform: translateY(-60px) scale(1); opacity: 0; }
  }

  /* 战斗 HUD */
  .battle-hud {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    padding: 10px;
    display: flex;
    justify-content: space-between;
    z-index: 30;
  }
  .hud-box {
    background: #0008;
    padding: 8px 12px;
    border-radius: 12px;
    border: 2px solid #ffd857;
    font-size: 14px;
  }
  .energy-bar {
    position: absolute;
    left: 10px;
    right: 10px;
    top: 60px;
    height: 14px;
    background: #222;
    border: 2px solid #fff;
    border-radius: 8px;
    overflow: hidden;
    z-index: 20;
  }
  .energy-inner {
    height: 100%;
    background: linear-gradient(90deg, #64b5f6, #1976d2);
    width: 0;
    transition: width 0.15s;
  }
  .card-row {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 10px;
    padding: 0 10px;
    display: flex;
    gap: 10px;
    z-index: 30;
  }
  .hand-card {
    flex: 1;
    background: linear-gradient(180deg, #fff8e1, #ffe28a);
    border: 3px solid #fff;
    border-radius: 14px;
    padding: 6px;
    text-align: center;
    box-shadow: 0 4px 10px #0008;
    color: #3a2410;
    font-weight: bold;
  }
  .hand-card.disabled {
    filter: grayscale(1) brightness(0.7);
  }
  .hand-card.selected {
    border-color: #ff5722;
    transform: translateY(-8px);
  }
  .card-cost {
    background: #1976d2;
    color: #fff;
    border-radius: 50%;
    width: 24px;
    height: 24px;
    line-height: 24px;
    margin: 0 auto 2px;
    border: 2px solid #fff;
  }
  .card-img {
    width: 100%;
    height: 54px;
    border-radius: 8px;
    background: #eee;
    border: 2px solid #fff;
    margin-bottom: 2px;
  }
  .card-name {
    font-size: 12px;
  }

  /* 结算 */
  #result {
    position: absolute;
    top: 40%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: #000c;
    padding: 24px 36px;
    border-radius: 20px;
    text-align: center;
    display: none;
    z-index: 60;
  }
  #result.show { display: block; }
  .result-title {
    font-size: 28px;
    font-weight: 900;
    color: #ffd857;
    text-shadow: 3px 3px 0 #8a4b14;
    margin-bottom: 12px;
  }
  .result-reward {
    margin-bottom: 16px;
    font-size: 14px;
  }

  /* 弹窗 */
  .modal {
    position: absolute;
    inset: 0;
    background: #000c;
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 70;
    padding: 20px;
  }
  .modal.show { display: flex; }
  .modal-box {
    background: linear-gradient(180deg, #fff8e1, #ffe28a);
    border: 4px solid #fff;
    border-radius: 20px;
    padding: 20px;
    width: 100%;
    max-width: 340px;
    color: #3a2410;
  }
  .modal-title {
    font-size: 22px;
    font-weight: 900;
    text-align: center;
    margin-bottom: 12px;
  }
  .hero-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    margin-bottom: 14px;
  }
  .hero-tile {
    background: #fff;
    border: 3px solid #fff;
    border-radius: 12px;
    padding: 8px;
    text-align: center;
  }
  .hero-tile.selected {
    border-color: #ff5722;
  }
  .hero-tile-img {
    width: 100%;
    height: 60px;
    border-radius: 8px;
    background: #eee;
    margin-bottom: 4px;
  }
  .hero-tile-name {
    font-size: 12px;
    font-weight: bold;
  }
</style>
</head>
<body>
<div id="game">

  <!-- 主界面 -->
  <div id="home">
    <div class="home-top">
      <div class="home-title">援救狗星人</div>
      <div class="resource-bar">
        <div class="resource">💰 <span id="homeGold">5000</span></div>
        <div class="resource">💎 <span id="homeDiamond">100</span></div>
      </div>
    </div>
    <div class="home-map">
      <div class="map-mountain"></div>
      <div class="map-road"></div>
      <div class="map-node" style="left:10%;top:72%" onclick="goBattle()">1</div>
      <div class="map-node" style="left:30%;top:62%" onclick="alert('通关第1关解锁')">2</div>
      <div class="map-node" style="left:52%;top:52%" onclick="alert('通关第2关解锁')">3</div>
      <div class="map-node" style="left:72%;top:42%" onclick="alert('通关第3关解锁')">4</div>
      <div class="map-node" style="left:88%;top:32%" onclick="alert('通关第4关解锁')">5</div>
    </div>
    <div class="home-menu">
      <div class="menu-item" onclick="openHeroes()">🐶 狗将</div>
      <div class="menu-item" onclick="openGacha()">🎲 招募</div>
      <div class="menu-item" onclick="openShop()">🛒 商店</div>
      <div class="menu-item" onclick="openTask()">📜 任务</div>
    </div>
  </div>

  <!-- 战斗界面 -->
  <div id="battle">
    <div class="battle-scene" id="battleScene">
      <div class="sky"></div>
      <div class="far-hills"></div>
      <div class="ground"></div>
      <div class="battle-path"></div>
      <div class="base base-left">
        <div class="base-hp"><div class="base-hp-inner" id="baseHpInner"></div></div>
        我方基地
      </div>
      <div class="base base-right">
        <div class="base-hp"><div class="base-hp-inner enemy" id="enemyBaseHpInner"></div></div>
        敌方基地
      </div>
    </div>

    <div class="battle-hud">
      <div class="hud-box">第 <span id="stageNum">1</span> 关</div>
      <div class="hud-box">波次 <span id="waveNum">1</span>/<span id="waveTotal">3</span></div>
      <button class="btn" onclick="toggleAuto()">自动:<span id="autoText">开</span></button>
    </div>

    <div class="energy-bar">
      <div class="energy-inner" id="energyInner"></div>
    </div>

    <div class="card-row" id="cardRow"></div>

    <div id="result">
      <div class="result-title" id="resultTitle">胜利!</div>
      <div class="result-reward" id="resultReward">获得金币 +500</div>
      <button class="btn" onclick="nextStage()">下一关</button>
    </div>
  </div>

  <!-- 狗将弹窗 -->
  <div class="modal" id="heroModal">
    <div class="modal-box">
      <div class="modal-title">我的狗将</div>
      <div class="hero-grid" id="heroGrid"></div>
      <button class="btn" style="width:100%" onclick="closeModal('heroModal')">关闭</button>
    </div>
  </div>

  <!-- 招募弹窗 -->
  <div class="modal" id="gachaModal">
    <div class="modal-box">
      <div class="modal-title">招募狗将</div>
      <p style="text-align:center;margin-bottom:12px">消耗 100 钻石招募一只狗将</p>
      <button class="btn" style="width:100%" onclick="doGacha()">招募一次</button>
      <button class="btn" style="width:100%;margin-top:8px" onclick="closeModal('gachaModal')">关闭</button>
    </div>
  </div>

  <!-- 商店弹窗 -->
  <div class="modal" id="shopModal">
    <div class="modal-box">
      <div class="modal-title">商店</div>
      <p style="text-align:center;margin-bottom:12px">功能开发中</p>
      <button class="btn" style="width:100%" onclick="closeModal('shopModal')">关闭</button>
    </div>
  </div>

  <!-- 任务弹窗 -->
  <div class="modal" id="taskModal">
    <div class="modal-box">
      <div class="modal-title">任务</div>
      <p style="text-align:center;margin-bottom:12px">功能开发中</p>
      <button class="btn" style="width:100%" onclick="closeModal('taskModal')">关闭</button>
    </div>
  </div>

</div>

<script>
  // ========== 狗将数据:不同品种 + 武将特征 ==========
  const HERO_DATA = [
    {
      id: "guanyu", name: "关羽犬", breed: "藏獒", rarity: "SSR",
      headColor: "#8b4513", torsoColor: "#2e7d32", legColor: "#1b5e20",
      tailColor: "#8b4513", weaponColor: "#bdbdbd",
      hp: 1400, atk: 190, def: 40, cost: 4,
      skill: { name: "偃月斩", type: "slash", dmg: 3.0 }
    },
    {
      id: "lubu", name: "吕布拉多", breed: "杜宾", rarity: "SSR",
      headColor: "#2c2c2c", torsoColor: "#7b1fa2", legColor: "#4a148c",
      tailColor: "#2c2c2c", weaponColor: "#9e9e9e",
      hp: 1300, atk: 230, def: 38, cost: 5,
      skill: { name: "无双乱舞", type: "combo", dmg: 3.2 }
    },
    {
      id: "zhugeliang", name: "诸葛柴犬", breed: "柴犬", rarity: "SSR",
      headColor: "#f4c896", torsoColor: "#1565c0", legColor: "#0d47a1",
      tailColor: "#f4c896", weaponColor: "#64b5f6",
      hp: 1000, atk: 175, def: 30, cost: 3,
      skill: { name: "天雷", type: "lightning", dmg: 2.8 }
    },
    {
      id: "zhangfei", name: "张飞恶霸", breed: "恶霸犬", rarity: "SSR",
      headColor: "#d4a373", torsoColor: "#e64a19", legColor: "#bf360c",
      tailColor: "#d4a373", weaponColor: "#757575",
      hp: 1250, atk: 210, def: 35, cost: 4,
      skill: { name: "咆哮震地", type: "wave", dmg: 2.6 }
    },
    {
      id: "zhaoyun", name: "赵云萨摩耶", breed: "萨摩耶", rarity: "SSR",
      headColor: "#ffffff", torsoColor: "#455a64", legColor: "#263238",
      tailColor: "#ffffff", weaponColor: "#cfd8dc",
      hp: 1350, atk: 200, def: 42, cost: 4,
      skill: { name: "龙胆突击", type: "slash", dmg: 2.9 }
    },
    {
      id: "caocao", name: "曹操罗威纳", breed: "罗威纳", rarity: "SSR",
      headColor: "#212121", torsoColor: "#5d4037", legColor: "#3e2723",
      tailColor: "#212121", weaponColor: "#ffd700",
      hp: 1200, atk: 185, def: 44, cost: 4,
      skill: { name: "魏武号令", type: "wave", dmg: 2.7 }
    }
  ];

  // ========== 游戏状态 ==========
  let state = {
    gold: 5000,
    diamond: 100,
    stage: 1,
    maxStage: 1,
    heroes: [
      { ...HERO_DATA[0], level: 1 },
      { ...HERO_DATA[2], level: 1 },
      { ...HERO_DATA[4], level: 1 }
    ],
    team: [0, 2, 4],
    auto: true,
    energy: 0,
    maxEnergy: 10,
    hand: [0, 2, 4],
    battle: null
  };

  // ========== 工具函数 ==========
  function $(id) { return document.getElementById(id); }
  function save() { localStorage.setItem("dogStarSave", JSON.stringify(state)); }
  function load() {
    const s = localStorage.getItem("dogStarSave");
    if (s) state = { ...state, ...JSON.parse(s) };
  }

  // ========== 主界面 ==========
  function showHome() {
    $("home").classList.add("show");
    $("battle").style.display = "none";
    $("homeGold").innerText = state.gold;
    $("homeDiamond").innerText = state.diamond;
  }
  function hideHome() {
    $("home").classList.remove("show");
    $("battle").style.display = "block";
  }

  // ========== 弹窗 ==========
  function openHeroes() {
    const grid = $("heroGrid");
    grid.innerHTML = "";
    state.heroes.forEach((h, i) => {
      const div = document.createElement("div");
      div.className = "hero-tile";
      div.innerHTML = `
        <div class="hero-tile-img" style="background:${h.headColor}"></div>
        <div class="hero-tile-name">${h.name}</div>
        <div style="font-size:11px">Lv.${h.level}</div>
      `;
      grid.appendChild(div);
    });
    $("heroModal").classList.add("show");
  }
  function openGacha() { $("gachaModal").classList.add("show"); }
  function openShop() { $("shopModal").classList.add("show"); }
  function openTask() { $("taskModal").classList.add("show"); }
  function closeModal(id) { $(id).classList.remove("show"); }
  function doGacha() {
    if (state.diamond < 100) { alert("钻石不足"); return; }
    state.diamond -= 100;
    const newHero = { ...HERO_DATA[Math.floor(Math.random() * HERO_DATA.length)], level: 1 };
    state.heroes.push(newHero);
    alert("招募到:" + newHero.name);
    save();
    showHome();
  }

  // ========== 战斗系统 ==========
  function goBattle() {
    hideHome();
    startBattle();
  }

  function startBattle() {
    $("result").classList.remove("show");
    document.querySelectorAll(".dog-unit, .fx").forEach(el => el.remove());

    state.battle = {
      self: [],
      enemy: [],
      energy: 0,
      maxEnergy: 10,
      wave: 1,
      waveTotal: 3,
      enemySpawnQueue: buildWave(state.stage),
      spawnCd: 0,
      selfBaseHp: 1000,
      enemyBaseHp: 1000,
      selfBaseMax: 1000,
      enemyBaseMax: 1000,
      over: false
    };

    $("stageNum").innerText = state.stage;
    $("waveNum").innerText = state.battle.wave;
    $("waveTotal").innerText = state.battle.waveTotal;
    $("autoText").innerText = state.auto ? "开" : "关";

    renderHand();
    updateEnergy();
    updateBaseHp();
    battleLoop();
  }

  function buildWave(stage) {
    const q = [];
    const count = 3 + Math.min(stage, 4);
    for (let i = 0; i < count; i++) {
      q.push({
        id: "e" + i, name: "小兵", breed: "土狗",
        headColor: "#d7ccc8", torsoColor: "#6d4c41", legColor: "#4e342e",
        tailColor: "#d7ccc8", weaponColor: "#9e9e9e",
        hp: 400 + stage * 80, atk: 40 + stage * 6, def: 8,
        skill: { name: "攻击", type: "slash", dmg: 1.0 }
      });
    }
    if (stage % 5 === 0) {
      q.push({
        id: "boss", name: "BOSS", breed: "战犬",
        headColor: "#424242", torsoColor: "#b71c1c", legColor: "#7f0000",
        tailColor: "#424242", weaponColor: "#ffd700",
        hp: 1200 + stage * 150, atk: 80 + stage * 10, def: 14,
        skill: { name: "重击", type: "wave", dmg: 2.2 }
      });
    }
    return q;
  }

  function renderHand() {
    const row = $("cardRow");
    row.innerHTML = "";
    state.hand.forEach((idx, i) => {
      const h = state.heroes[idx];
      const card = document.createElement("div");
      card.className = "hand-card" + (state.energy < h.cost ? " disabled" : "");
      card.innerHTML = `
        <div class="card-cost">${h.cost}</div>
        <div class="card-img" style="background:${h.headColor}"></div>
        <div class="card-name">${h.name}</div>
      `;
      card.onclick = () => deployHero(i);
      row.appendChild(card);
    });
  }

  function deployHero(handIndex) {
    const idx = state.hand[handIndex];
    const h = state.heroes[idx];
    if (state.energy < h.cost) { alert("能量不足"); return; }

    state.energy -= h.cost;
    updateEnergy();

    const unit = {
      ...h,
      uid: "s" + Date.now() + Math.random(),
      x: 140,
      y: 50 + Math.random() * 20,
      nowHp: h.hp,
      maxHp: h.hp,
      target: null,
      cd: 0,
      energy: 0,
      state: "run"
    };
    state.battle.self.push(unit);
    createDogUnit(unit, false);

    state.hand.splice(handIndex, 1);
    state.hand.push(state.team[Math.floor(Math.random() * state.team.length)]);
    renderHand();
  }

  function createDogUnit(u, isEnemy) {
    const el = document.createElement("div");
    el.className = "dog-unit";
    el.id = u.uid;
    el.style.left = u.x + "px";
    el.style.bottom = u.y + "px";
    if (isEnemy) el.style.transform = "scaleX(-1)";

    el.innerHTML = `
      <div class="dog-body">
        <div class="unit-hp"><div class="unit-hp-inner ${isEnemy?'enemy':''}" style="width:100%"></div></div>
        <div class="unit-energy"><div class="unit-energy-inner" id="en-${u.uid}"></div></div>
        <div class="dog-head" style="background:${u.headColor}"></div>
        <div class="dog-torso" style="background:${u.torsoColor}"></div>
        <div class="dog-leg left" style="background:${u.legColor}"></div>
        <div class="dog-leg right" style="background:${u.legColor}"></div>
        <div class="dog-tail" style="background:${u.tailColor}"></div>
        <div class="dog-weapon" style="background:${u.weaponColor}"></div>
        <div class="dog-shadow"></div>
      </div>
    `;
    $("battleScene").appendChild(el);
  }

  function spawnEnemy() {
    if (state.battle.enemySpawnQueue.length === 0) return;
    state.battle.spawnCd--;
    if (state.battle.spawnCd > 0) return;
    state.battle.spawnCd = 90;

    const data = state.battle.enemySpawnQueue.shift();
    const unit = {
      ...data,
      uid: "e" + Date.now() + Math.random(),
      x: window.innerWidth - 160,
      y: 50 + Math.random() * 20,
      nowHp: data.hp,
      maxHp: data.hp,
      target: null,
      cd: 0,
      energy: 0,
      state: "run"
    };
    state.battle.enemy.push(unit);
    createDogUnit(unit, true);
  }

  function battleLoop() {
    if (state.battle.over) return;

    // 能量恢复
    state.battle.energy += 0.08;
    if (state.battle.energy > state.battle.maxEnergy) state.battle.energy = state.battle.maxEnergy;
    state.energy = state.battle.energy;
    updateEnergy();
    renderHand();

    // 生成敌人
    spawnEnemy();

    // 我方
    state.battle.self.forEach(u => {
      if (u.nowHp <= 0) return;
      const el = $(u.uid);
      if (!el) return;

      // 找目标
      if (!u.target || u.target.nowHp <= 0) {
        u.target = findTarget(u, state.battle.enemy);
      }

      if (u.target) {
        const dx = u.target.x - u.x;
        if (Math.abs(dx) > 90) {
          u.x += 1.4;
          el.style.left = u.x + "px";
          el.classList.remove("atk", "hit");
          el.classList.add("run");
        } else {
          el.classList.remove("run");
          u.cd--;
          if (u.cd <= 0) {
            u.cd = 55;
            dogAttack(u, u.target);
          }
        }
      } else {
        // 攻击敌方基地
        if (u.x < window.innerWidth - 180) {
          u.x += 1.2;
          el.style.left = u.x + "px";
          el.classList.remove("atk", "hit");
          el.classList.add("run");
        } else {
          el.classList.remove("run");
          u.cd--;
          if (u.cd <= 0) {
            u.cd = 55;
            state.battle.enemyBaseHp -= u.atk;
            updateBaseHp();
            shakeScreen();
            if (state.battle.enemyBaseHp <= 0) return endBattle(true);
          }
        }
      }

      // 技能能量
      u.energy = Math.min(100, u.energy + 0.4);
      const ei = document.getElementById("en-" + u.uid);
      if (ei) ei.style.width = u.energy + "%";
      if (u.energy >= 100) {
        u.energy = 0;
        useSkill(u, u.target || { x: u.x, y: u.y });
      }
    });

    // 敌方
    state.battle.enemy.forEach(u => {
      if (u.nowHp <= 0) return;
      const el = $(u.uid);
      if (!el) return;

      if (!u.target || u.target.nowHp <= 0) {
        u.target = findTarget(u, state.battle.self);
      }

      if (u.target) {
        const dx = u.target.x - u.x;
        if (Math.abs(dx) > 90) {
          u.x -= 1.1;
          el.style.left = u.x + "px";
          el.classList.remove("atk", "hit");
          el.classList.add("run");
        } else {
          el.classList.remove("run");
          u.cd--;
          if (u.cd <= 0) {
            u.cd = 65;
            dogAttack(u, u.target);
          }
        }
      } else {
        if (u.x > 140) {
          u.x -= 1.0;
          el.style.left = u.x + "px";
          el.classList.remove("atk", "hit");
          el.classList.add("run");
        } else {
          el.classList.remove("run");
          u.cd--;
          if (u.cd <= 0) {
            u.cd = 65;
            state.battle.selfBaseHp -= u.atk;
            updateBaseHp();
            shakeScreen();
            if (state.battle.selfBaseHp <= 0) return endBattle(false);
          }
        }
      }

      u.energy = Math.min(100, u.energy + 0.35);
      if (u.energy >= 100) {
        u.energy = 0;
        useSkill(u, u.target || { x: u.x, y: u.y });
      }
    });

    // 波次检查
    if (state.battle.enemySpawnQueue.length === 0 && state.battle.enemy.every(e => e.nowHp <= 0)) {
      if (state.battle.wave < state.battle.waveTotal) {
        state.battle.wave++;
        state.battle.enemySpawnQueue = buildWave(state.stage);
        $("waveNum").innerText = state.battle.wave;
      } else {
        return endBattle(true);
      }
    }

    requestAnimationFrame(battleLoop);
  }

  function findTarget(u, list) {
    let best = null, bestDist = Infinity;
    list.forEach(t => {
      if (t.nowHp <= 0) return;
      const d = Math.abs(t.x - u.x);
      if (d < bestDist) { bestDist = d; best = t; }
    });
    return best;
  }

  function dogAttack(attacker, target) {
    const aEl = $(attacker.uid);
    const tEl = $(target.uid);
    if (!aEl || !tEl) return;
    aEl.classList.add("atk");
    setTimeout(() => aEl.classList.remove("atk"), 420);

    setTimeout(() => {
      const dmg = Math.max(1, attacker.atk - target.def + Math.floor(Math.random() * 15));
      target.nowHp -= dmg;
      showDmg(target, dmg, false);
      tEl.classList.add("hit");
      setTimeout(() => tEl.classList.remove("hit"), 220);
      updateUnitHp(target);
      if (target.nowHp <= 0) {
        tEl.classList.add("dead");
        setTimeout(() => tEl.remove(), 500);
      }
    }, 260);
  }

  function useSkill(attacker, target) {
    const aEl = $(attacker.uid);
    if (!aEl) return;
    aEl.classList.add("atk");
    setTimeout(() => aEl.classList.remove("atk"), 420);

    setTimeout(() => {
      if (!target || target.nowHp <= 0) return;
      const dmg = Math.floor(attacker.atk * attacker.skill.dmg);
      target.nowHp -= dmg;
      showDmg(target, dmg, true);
      const tEl = $(target.uid);
      if (tEl) {
        tEl.classList.add("hit");
        setTimeout(() => tEl.classList.remove("hit"), 220);
        updateUnitHp(target);
        if (target.nowHp <= 0) {
          tEl.classList.add("dead");
          setTimeout(() => tEl.remove(), 500);
        }
      }
      createFx(target, attacker.skill.type);
      shakeScreen();
    }, 260);
  }

  function createFx(target, type) {
    const fx = document.createElement("div");
    fx.className = "fx " + type + "-fx";
    fx.style.left = target.x + 20 + "px";
    fx.style.bottom = target.y + 40 + "px";
    $("battleScene").appendChild(fx);
    setTimeout(() => fx.remove(), 600);
  }

  function showDmg(target, val, crit) {
    const d = document.createElement("div");
    d.className = "dmg" + (crit ? " crit" : "");
    d.innerText = "-" + val;
    d.style.left = target.x + 24 + "px";
    d.style.bottom = target.y + 80 + "px";
    $("battleScene").appendChild(d);
    setTimeout(() => d.remove(), 850);
  }

  function updateUnitHp(u) {
    const el = $(u.uid);
    if (!el) return;
    const bar = el.querySelector(".unit-hp-inner");
    if (bar) bar.style.width = Math.max(0, u.nowHp / u.maxHp * 100) + "%";
  }
  function updateEnergy() {
    $("energyInner").style.width = (state.energy / state.maxEnergy * 100) + "%";
  }
  function updateBaseHp() {
    $("baseHpInner").style.width = Math.max(0, state.battle.selfBaseHp / state.battle.selfBaseMax * 100) + "%";
    $("enemyBaseHpInner").style.width = Math.max(0, state.battle.enemyBaseHp / state.battle.enemyBaseMax * 100) + "%";
  }
  function shakeScreen() {
    const scene = $("battleScene");
    scene.classList.add("shake");
    setTimeout(() => scene.classList.remove("shake"), 250);
  }

  function endBattle(win) {
    state.battle.over = true;
    $("result").classList.add("show");
    if (win) {
      $("resultTitle").innerText = "胜利!";
      const reward = 500 + state.stage * 100;
      state.gold += reward;
      state.maxStage = Math.max(state.maxStage, state.stage);
      $("resultReward").innerText = "获得金币 +" + reward;
    } else {
      $("resultTitle").innerText = "战败";
      $("resultReward").innerText = "再接再厉";
    }
    save();
  }

  function nextStage() {
    state.stage++;
    startBattle();
  }

  function toggleAuto() {
    state.auto = !state.auto;
    $("autoText").innerText = state.auto ? "开" : "关";
  }

  // ========== 初始化 ==========
  load();
  showHome();
</script>
</body>
</html>

Game Source: 援救狗星人

Creator: BlazingDragon43

Libraries: none

Complexity: complex (1238 lines, 33.0 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-blazingdragon43-mw56a7be" to link back to the original. Then publish at arcadelab.ai/publish.