<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0, shrink-to-fit=yes">
  <title>🏰 阳光防线 · 无尽塔防</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      background: linear-gradient(135deg, #87CEEB 0%, #98FB98 30%, #90EE90 100%);
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      font-family: 'Segoe UI', 'Microsoft YaHei', 'PingFang SC', sans-serif;
      padding: 10px;
      margin: 0;
      overflow-x: hidden;
    }
    .game-container {
      background: rgba(255, 255, 255, 0.25);
      border-radius: 24px;
      padding: 16px;
      box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2), 0 0 0 3px rgba(255, 255, 255, 0.5);
      backdrop-filter: blur(12px);
      width: 100%;
      max-width: 1200px;
      display: flex;
      flex-direction: column;
      align-items: center;
      position: relative;
      transition: all 0.3s ease;
    }
    .canvas-wrapper {
      width: 100%;
      position: relative;
      border-radius: 16px;
      overflow: hidden;
      box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
    }
    canvas {
      display: block;
      width: 100%;
      height: auto;
      cursor: crosshair;
      background: #7ec850;
    }
    .fullscreen-btn {
      position: absolute;
      top: 24px;
      right: 24px;
      z-index: 10;
      width: 44px;
      height: 44px;
      border-radius: 50%;
      border: 2px solid rgba(255, 255, 255, 0.9);
      background: rgba(255, 255, 255, 0.8);
      cursor: pointer;
      font-size: 22px;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.3s;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
      backdrop-filter: blur(5px);
      color: #2d5016;
      line-height: 1;
    }
    .fullscreen-btn:hover {
      background: #ffffff;
      transform: scale(1.1);
      box-shadow: 0 6px 18px rgba(0, 0, 0, 0.3);
    }
    .sound-btn {
      position: absolute;
      top: 24px;
      right: 78px;
      z-index: 10;
      width: 44px;
      height: 44px;
      border-radius: 50%;
      border: 2px solid rgba(255, 255, 255, 0.9);
      background: rgba(255, 255, 255, 0.8);
      cursor: pointer;
      font-size: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.3s;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
      backdrop-filter: blur(5px);
      color: #2d5016;
      line-height: 1;
    }
    .sound-btn:hover {
      background: #ffffff;
      transform: scale(1.1);
      box-shadow: 0 6px 18px rgba(0, 0, 0, 0.3);
    }
    .sound-btn.muted {
      background: #ffcccc;
      color: #cc0000;
    }
    .panel {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 14px;
      flex-wrap: wrap;
      gap: 10px;
      width: 100%;
    }
    .stats {
      display: flex;
      gap: 12px;
      font-size: clamp(14px, 2.5vw, 18px);
      font-weight: bold;
      color: #2d5016;
      flex-wrap: wrap;
    }
    .stat-item {
      background: rgba(255, 255, 255, 0.7);
      padding: 8px 14px;
      border-radius: 25px;
      border: 2px solid rgba(100, 180, 50, 0.6);
      box-shadow: 0 3px 8px rgba(0,0,0,0.1);
      display: flex;
      align-items: center;
      gap: 4px;
      white-space: nowrap;
    }
    .tower-buttons {
      display: flex;
      gap: 6px;
      flex-wrap: wrap;
    }
    .tower-btn {
      padding: 9px 13px;
      border-radius: 25px;
      border: 2px solid rgba(255, 255, 255, 0.8);
      background: rgba(255, 255, 255, 0.7);
      color: #2d5016;
      font-weight: bold;
      cursor: pointer;
      transition: all 0.3s;
      font-size: clamp(11px, 1.8vw, 14px);
      white-space: nowrap;
      box-shadow: 0 3px 8px rgba(0,0,0,0.1);
    }
    .tower-btn:hover {
      background: #c8e6a0;
      transform: translateY(-2px);
      box-shadow: 0 6px 18px rgba(0,0,0,0.2);
    }
    .tower-btn.active {
      background: #4CAF50;
      border-color: #ffffff;
      color: white;
      box-shadow: 0 0 20px rgba(76, 175, 80, 0.6);
      transform: scale(1.05);
    }
    .action-btns {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
      align-items: center;
    }
    .btn {
      padding: 9px 18px;
      border-radius: 25px;
      border: 2px solid rgba(255, 255, 255, 0.8);
      background: rgba(255, 255, 255, 0.8);
      color: #2d5016;
      font-weight: bold;
      cursor: pointer;
      transition: all 0.3s;
      font-size: clamp(12px, 2vw, 15px);
      box-shadow: 0 3px 8px rgba(0,0,0,0.1);
      white-space: nowrap;
    }
    .btn:hover {
      background: #d4edda;
      box-shadow: 0 5px 15px rgba(0,0,0,0.2);
    }
    .btn.start {
      background: #FF9800;
      border-color: #ffffff;
      color: white;
      font-weight: bold;
    }
    .btn.start:hover {
      background: #F57C00;
      box-shadow: 0 5px 20px rgba(255, 152, 0, 0.5);
    }
    .btn.continue {
      background: #4CAF50;
      border-color: #ffffff;
      color: white;
      font-weight: bold;
      display: none;
    }
    .btn.continue:hover {
      background: #388E3C;
      box-shadow: 0 5px 20px rgba(76, 175, 80, 0.5);
    }
    .btn.range-toggle {
      background: #9C27B0;
      border-color: #ffffff;
      color: white;
      font-weight: bold;
    }
    .btn.range-toggle:hover {
      background: #7B1FA2;
      box-shadow: 0 5px 20px rgba(156, 39, 176, 0.5);
    }
    .btn.range-toggle.active-range {
      background: #E040FB;
      box-shadow: 0 0 20px rgba(224, 64, 251, 0.7);
    }
    .info-text {
      color: #3e6b27;
      font-size: clamp(10px, 1.6vw, 13px);
      margin-top: 8px;
      text-align: center;
      background: rgba(255,255,255,0.5);
      padding: 6px 16px;
      border-radius: 15px;
    }

    @media (orientation: landscape) and (min-width: 800px) {
      .game-container {
        flex-direction: row;
        align-items: stretch;
        gap: 16px;
        padding: 16px;
      }
      .canvas-wrapper {
        flex: 1;
        min-width: 0;
      }
      .game-sidebar {
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: 12px;
        width: 260px;
        min-width: 240px;
      }
      .panel {
        flex-direction: column;
        align-items: stretch;
        margin-top: 0;
        gap: 10px;
      }
      .stats {
        flex-direction: column;
        gap: 8px;
      }
      .stat-item {
        justify-content: center;
        font-size: 16px;
      }
      .tower-buttons {
        flex-direction: column;
        gap: 6px;
      }
      .tower-btn {
        text-align: center;
        font-size: 14px;
        padding: 11px 16px;
      }
      .action-btns {
        flex-direction: column;
        gap: 6px;
      }
      .btn {
        text-align: center;
        font-size: 14px;
        padding: 11px 18px;
      }
      .info-text {
        margin-top: 0;
      }
      .fullscreen-btn {
        top: 24px;
        right: 284px;
      }
      .sound-btn {
        top: 24px;
        right: 338px;
      }
    }

    @media (max-width: 600px) {
      .game-container {
        padding: 10px;
        border-radius: 16px;
      }
      .panel {
        gap: 6px;
      }
      .tower-btn {
        padding: 7px 10px;
      }
      .fullscreen-btn {
        top: 16px;
        right: 16px;
        width: 36px;
        height: 36px;
        font-size: 18px;
      }
      .sound-btn {
        top: 16px;
        right: 60px;
        width: 36px;
        height: 36px;
        font-size: 16px;
      }
    }
  </style>
</head>
<body>
  <div class="game-container" id="gameContainer">
    <button class="fullscreen-btn" id="fullscreenBtn" title="全屏模式">⛶</button>
    <button class="sound-btn" id="soundBtn" title="音效开关">🔊</button>
    
    <div class="canvas-wrapper">
      <canvas id="gameCanvas"></canvas>
    </div>
    
    <div class="game-sidebar" id="gameSidebar">
      <div class="panel">
        <div class="stats">
          <div class="stat-item">❤️ <span id="livesDisplay">20</span></div>
          <div class="stat-item">💰 <span id="moneyDisplay">400</span></div>
          <div class="stat-item">🌊 <span id="waveDisplay">1</span>/10</div>
        </div>
        <div class="tower-buttons">
          <button class="tower-btn active" data-type="arrow">🏹 箭塔 (80)</button>
          <button class="tower-btn" data-type="cannon">💣 炮塔 (150)</button>
          <button class="tower-btn" data-type="ice">❄️ 冰塔 (120)</button>
          <button class="tower-btn" data-type="lightning">⚡ 电塔 (200)</button>
        </div>
        <div class="action-btns">
          <button class="btn range-toggle active-range" id="rangeToggleBtn" title="显示/隐藏射击范围">🎯 范围</button>
          <button class="btn start" id="startWaveBtn">▶ 开始波次</button>
          <button class="btn continue" id="continueBtn">🔄 继续10关</button>
          <button class="btn" id="resetBtn">🔄 重新开始</button>
        </div>
      </div>
      <div class="info-text">
        💡 左键建造/升级 | 右键拆除炮塔 | 🎯切换范围 | 🔊音效
      </div>
    </div>
  </div>

  <script>
    (function() {
      const canvas = document.getElementById('gameCanvas');
      const ctx = canvas.getContext('2d');

      const livesDisplay = document.getElementById('livesDisplay');
      const moneyDisplay = document.getElementById('moneyDisplay');
      const waveDisplay = document.getElementById('waveDisplay');
      const rangeToggleBtn = document.getElementById('rangeToggleBtn');
      const fullscreenBtn = document.getElementById('fullscreenBtn');
      const soundBtn = document.getElementById('soundBtn');
      const gameContainer = document.getElementById('gameContainer');
      const startWaveBtn = document.getElementById('startWaveBtn');
      const continueBtn = document.getElementById('continueBtn');

      const GRID_SIZE = 64;
      const COLS = 10;
      const ROWS = 8;
      const MAP_W = COLS * GRID_SIZE;
      const MAP_H = ROWS * GRID_SIZE;

      canvas.width = MAP_W;
      canvas.height = MAP_H;

      let lives = 20;
      let money = 400;
      let wave = 1;
      let totalWavesCompleted = 0;
      let gameOver = false;
      let waveActive = false;
      let selectedTowerType = 'arrow';
      let showRange = true;
      let isFullscreen = false;
      let soundEnabled = true;
      let enemies = [];
      let towers = [];
      let bullets = [];
      let particles = [];
      let pathCells = [];
      let spawnPoint = {col: 0, row: 3};
      let endPoint = {col: 9, row: 4};
      let gridMap = [];
      
      // 敌人生成队列
      let spawnQueue = [];
      let spawnTimer = 0;
      let spawnInterval = 30; // 生成间隔(帧数)

      let audioCtx = null;
      
      function initAudio() {
        if (!audioCtx) {
          audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        }
        if (audioCtx.state === 'suspended') {
          audioCtx.resume();
        }
      }

      function playSound(frequency, type, duration, volume = 0.12, decay = true) {
        if (!soundEnabled || !audioCtx) return;
        try {
          const oscillator = audioCtx.createOscillator();
          const gainNode = audioCtx.createGain();
          oscillator.type = type;
          oscillator.frequency.setValueAtTime(frequency, audioCtx.currentTime);
          gainNode.gain.setValueAtTime(volume, audioCtx.currentTime);
          if (decay) {
            gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
          }
          oscillator.connect(gainNode);
          gainNode.connect(audioCtx.destination);
          oscillator.start(audioCtx.currentTime);
          oscillator.stop(audioCtx.currentTime + duration);
        } catch(e) {}
      }

      function sfxArrowHit() { playSound(800, 'square', 0.06, 0.06); }
      function sfxCannonHit() { playSound(80, 'sawtooth', 0.2, 0.15); }
      function sfxIceHit() { playSound(2000, 'sine', 0.08, 0.05); }
      function sfxLightningHit() { playSound(300, 'sawtooth', 0.1, 0.08); }
      function sfxBuild() { playSound(500, 'sine', 0.08, 0.08); }
      function sfxUpgrade() { playSound(400, 'sine', 0.06, 0.06); setTimeout(() => playSound(800, 'sine', 0.08, 0.08), 80); }
      function sfxRemove() { playSound(300, 'triangle', 0.12, 0.08); }
      function sfxEnemyDie() { playSound(200, 'square', 0.08, 0.06); }
      function sfxEnemyReachEnd() { playSound(150, 'sawtooth', 0.15, 0.12); }
      function sfxWaveStart() { playSound(300, 'sine', 0.1, 0.12); }
      function sfxWaveComplete() { playSound(500, 'sine', 0.08, 0.15); }

      soundBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        soundEnabled = !soundEnabled;
        if (soundEnabled) {
          soundBtn.classList.remove('muted');
          soundBtn.textContent = '🔊';
          initAudio();
        } else {
          soundBtn.classList.add('muted');
          soundBtn.textContent = '🔇';
        }
      });

      const towerDefs = {
        arrow: {
          name: '箭塔', cost: 80, range: 2.5, damage: 20, cooldown: 18,
          color: '#2196F3', bulletColor: '#64B5F6', bulletSpeed: 8,
          upgradeCost: 60, maxLevel: 5, shape: 'arrow', hitSound: sfxArrowHit
        },
        cannon: {
          name: '炮塔', cost: 150, range: 2.0, damage: 55, cooldown: 40,
          color: '#FF5722', bulletColor: '#FF8A65', bulletSpeed: 6,
          upgradeCost: 100, maxLevel: 4, splash: 1.2, shape: 'cannon', hitSound: sfxCannonHit
        },
        ice: {
          name: '冰塔', cost: 120, range: 2.3, damage: 12, cooldown: 22,
          color: '#00BCD4', bulletColor: '#80DEEA', bulletSpeed: 7,
          upgradeCost: 80, maxLevel: 5, slow: 0.5, shape: 'ice', hitSound: sfxIceHit
        },
        lightning: {
          name: '电塔', cost: 200, range: 2.8, damage: 35, cooldown: 25,
          color: '#FFC107', bulletColor: '#FFE082', bulletSpeed: 15,
          upgradeCost: 130, maxLevel: 4, chain: 2, shape: 'lightning', hitSound: sfxLightningHit
        }
      };

      const enemyTypes = {
        normal: {color: '#E53935', speed: 1.8, hp: 50, reward: 30, size: 14},
        fast: {color: '#FB8C00', speed: 3.2, hp: 30, reward: 25, size: 11},
        tank: {color: '#8E24AA', speed: 1.2, hp: 130, reward: 55, size: 18},
        boss: {color: '#D32F2F', speed: 1.0, hp: 400, reward: 150, size: 22}
      };

      function toggleFullscreen() {
        const fe = document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
        if (!fe) {
          if (gameContainer.requestFullscreen) gameContainer.requestFullscreen().catch(err => {});
          else if (gameContainer.webkitRequestFullscreen) gameContainer.webkitRequestFullscreen();
          else if (gameContainer.msRequestFullscreen) gameContainer.msRequestFullscreen();
        } else {
          if (document.exitFullscreen) document.exitFullscreen();
          else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
          else if (document.msExitFullscreen) document.msExitFullscreen();
        }
      }

      function handleFullscreenChange() {
        const fe = document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
        isFullscreen = !!fe;
        fullscreenBtn.textContent = isFullscreen ? '✕' : '⛶';
      }

      fullscreenBtn.addEventListener('click', (e) => { e.stopPropagation(); initAudio(); toggleFullscreen(); });
      document.addEventListener('fullscreenchange', handleFullscreenChange);
      document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
      document.addEventListener('msfullscreenchange', handleFullscreenChange);
      document.addEventListener('keydown', (e) => { if (e.key === 'F11') setTimeout(handleFullscreenChange, 300); });

      rangeToggleBtn.addEventListener('click', () => {
        showRange = !showRange;
        if (showRange) { rangeToggleBtn.classList.add('active-range'); rangeToggleBtn.textContent = '🎯 范围'; }
        else { rangeToggleBtn.classList.remove('active-range'); rangeToggleBtn.textContent = '🎯 隐藏'; }
      });

      function initGridMap() {
        gridMap = Array(ROWS).fill().map(() => Array(COLS).fill('empty'));
      }

      function generateRandomPath() {
        const startCol = 0;
        const startRow = Math.floor(Math.random() * (ROWS - 2)) + 1;
        const endCol = COLS - 1;
        const endRow = Math.floor(Math.random() * (ROWS - 2)) + 1;
        spawnPoint = {col: startCol, row: startRow};
        endPoint = {col: endCol, row: endRow};

        let visited = new Set();
        let parent = new Map();
        let queue = [{col: startCol, row: startRow}];
        visited.add(`${startCol},${startRow}`);
        
        let found = false;
        while (queue.length > 0 && !found) {
          let current = queue.shift();
          let directions = [{dc: 1, dr: 0}, {dc: 0, dr: 1}, {dc: 0, dr: -1}, {dc: -1, dr: 0}];
          for (let i = directions.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [directions[i], directions[j]] = [directions[j], directions[i]];
          }
          for (let dir of directions) {
            let nc = current.col + dir.dc;
            let nr = current.row + dir.dr;
            let key = `${nc},${nr}`;
            if (nc >= 0 && nc < COLS && nr >= 0 && nr < ROWS && !visited.has(key)) {
              visited.add(key);
              parent.set(key, `${current.col},${current.row}`);
              queue.push({col: nc, row: nr});
              if (nc === endCol && nr === endRow) { found = true; break; }
            }
          }
        }

        let path = [];
        let currentKey = `${endCol},${endRow}`;
        while (currentKey !== `${startCol},${startRow}`) {
          let [c, r] = currentKey.split(',').map(Number);
          path.push({col: c, row: r});
          currentKey = parent.get(currentKey);
          if (!currentKey) break;
        }
        path.push({col: startCol, row: startRow});
        path.reverse();

        let pathSet = new Set();
        pathCells = [];
        for (let p of path) {
          let key = `${p.col},${p.row}`;
          if (!pathSet.has(key)) {
            pathSet.add(key);
            pathCells.push(p);
          }
        }

        for (let r = 0; r < ROWS; r++) {
          for (let c = 0; c < COLS; c++) {
            gridMap[r][c] = pathSet.has(`${c},${r}`) ? 'path' : 'empty';
          }
        }
        
        towers = towers.filter(t => {
          if (pathSet.has(`${t.col},${t.row}`)) {
            let def = towerDefs[t.type];
            let refund = Math.floor(def.cost * 0.5);
            for (let lv = 2; lv <= t.level; lv++) refund += Math.floor(def.upgradeCost * (lv - 1) * 0.5);
            money += refund;
            return false;
          }
          return true;
        });
        
        for (let r = 0; r < ROWS; r++) {
          for (let c = 0; c < COLS; c++) {
            if (gridMap[r][c] !== 'path') gridMap[r][c] = 'empty';
          }
        }
        for (let t of towers) gridMap[t.row][t.col] = 'tower';
      }

      // 🔧 修复:正确的敌人生成数量和频率
      function spawnWave(waveNum, loopCount = 0) {
        let enemyList = [];
        let difficultyMult = 1 + loopCount * 0.5;
        
        // 修正敌人数量计算,使其更合理
        let baseCount = Math.max(3, Math.floor(3 + (waveNum - 1) * 1.2)); // 第1波3个,每波递增
        let count = Math.floor(baseCount * difficultyMult);
        
        console.log(`🌊 第${waveNum}波,生成${count}个敌人 (难度倍率: ${difficultyMult})`);
        
        for (let i = 0; i < count; i++) {
          let type = 'normal';
          let r = Math.random();
          if (waveNum >= 9 && i % 3 === 0) type = 'boss';
          else if (waveNum >= 6 && r < 0.15) type = 'tank';
          else if (waveNum >= 3 && r < 0.3) type = 'fast';
          
          let stats = enemyTypes[type];
          let hpMult = (1 + (waveNum - 1) * 0.25) * difficultyMult;
          
          enemyList.push({
            x: spawnPoint.col * GRID_SIZE + GRID_SIZE / 2,
            y: spawnPoint.row * GRID_SIZE + GRID_SIZE / 2,
            type: type,
            hp: Math.floor(stats.hp * hpMult),
            maxHp: Math.floor(stats.hp * hpMult),
            speed: stats.speed * 0.8,
            color: stats.color,
            size: stats.size,
            reward: Math.floor(stats.reward * difficultyMult),
            pathIndex: 0,
            slowTimer: 0,
            originalSpeed: stats.speed * 0.8
          });
        }
        return enemyList;
      }

      function spawnEnemyFromQueue() {
        if (spawnQueue.length === 0) return;
        let enemy = spawnQueue.shift();
        enemies.push(enemy);
        spawnTimer = spawnInterval; // 重置生成计时器
      }

      function moveEnemy(enemy) {
        if (enemy.pathIndex >= pathCells.length) {
          lives--;
          spawnParticles(enemy.x, enemy.y, '#ff3333', 8, 2);
          sfxEnemyReachEnd();
          return true;
        }
        let target = pathCells[enemy.pathIndex];
        let tx = target.col * GRID_SIZE + GRID_SIZE / 2;
        let ty = target.row * GRID_SIZE + GRID_SIZE / 2;
        let dx = tx - enemy.x;
        let dy = ty - enemy.y;
        let dist = Math.hypot(dx, dy);
        if (dist < 3) {
          enemy.pathIndex++;
          if (enemy.pathIndex >= pathCells.length) {
            lives--;
            spawnParticles(enemy.x, enemy.y, '#ff3333', 8, 2);
            sfxEnemyReachEnd();
            return true;
          }
          target = pathCells[enemy.pathIndex];
          tx = target.col * GRID_SIZE + GRID_SIZE / 2;
          ty = target.row * GRID_SIZE + GRID_SIZE / 2;
          dx = tx - enemy.x;
          dy = ty - enemy.y;
          dist = Math.hypot(dx, dy);
        }
        if (dist > 0) {
          enemy.x += (dx / dist) * enemy.speed;
          enemy.y += (dy / dist) * enemy.speed;
        }
        return false;
      }

      function spawnParticles(x, y, color, count = 6, speed = 2) {
        for (let i = 0; i < count; i++) {
          let angle = Math.random() * Math.PI * 2;
          let sp = Math.random() * speed + 0.5;
          particles.push({
            x, y, vx: Math.cos(angle) * sp, vy: Math.sin(angle) * sp,
            life: 0.4 + Math.random() * 0.4, maxLife: 0.4 + Math.random() * 0.4,
            color: color, size: 1.5 + Math.random() * 2
          });
        }
      }

      function updateTowers() {
        for (let tower of towers) {
          tower.cooldownCounter = (tower.cooldownCounter || 0) - 1;
          if (tower.cooldownCounter > 0) continue;
          
          let target = null;
          let bestProgress = -1;
          for (let enemy of enemies) {
            let dx = enemy.x - (tower.col * GRID_SIZE + GRID_SIZE / 2);
            let dy = enemy.y - (tower.row * GRID_SIZE + GRID_SIZE / 2);
            let dist = Math.hypot(dx, dy) / GRID_SIZE;
            if (dist <= tower.range && enemy.hp > 0 && enemy.pathIndex > bestProgress) {
              bestProgress = enemy.pathIndex;
              target = enemy;
            }
          }
          
          if (target) {
            bullets.push({
              x: tower.col * GRID_SIZE + GRID_SIZE / 2,
              y: tower.row * GRID_SIZE + GRID_SIZE / 2,
              target: target,
              damage: tower.damage,
              speed: towerDefs[tower.type].bulletSpeed,
              color: towerDefs[tower.type].bulletColor,
              type: tower.type,
              splash: tower.splash || 0,
              slow: tower.slow || 0,
              chain: tower.chain || 0
            });
            tower.cooldownCounter = tower.cooldown;
          }
        }
      }

      function updateBullets() {
        for (let i = bullets.length - 1; i >= 0; i--) {
          let b = bullets[i];
          if (!b.target || b.target.hp <= 0) { bullets.splice(i, 1); continue; }
          let dx = b.target.x - b.x;
          let dy = b.target.y - b.y;
          let dist = Math.hypot(dx, dy);
          if (dist < b.speed + 4) {
            applyDamage(b, b.target);
            bullets.splice(i, 1);
          } else {
            b.x += (dx / dist) * b.speed;
            b.y += (dy / dist) * b.speed;
          }
        }
      }

      function applyDamage(bullet, enemy) {
        enemy.hp -= bullet.damage;
        spawnParticles(enemy.x, enemy.y, bullet.color, 3, 1.5);
        
        let def = towerDefs[bullet.type];
        if (def && def.hitSound) def.hitSound();
        
        if (bullet.slow > 0 && enemy.slowTimer <= 0) {
          enemy.slowTimer = 35;
          enemy.speed = enemy.originalSpeed * (1 - bullet.slow);
        }
        
        if (bullet.splash > 0) {
          for (let e of enemies) {
            if (e === enemy || e.hp <= 0) continue;
            if (Math.hypot(e.x - enemy.x, e.y - enemy.y) / GRID_SIZE <= bullet.splash) {
              e.hp -= Math.floor(bullet.damage * 0.5);
            }
          }
        }
        
        if (bullet.chain > 0) {
          let lastTarget = enemy;
          let chained = new Set([enemy]);
          for (let c = 0; c < bullet.chain; c++) {
            let nearest = null, minDist = GRID_SIZE * 2;
            for (let e of enemies) {
              if (chained.has(e) || e.hp <= 0) continue;
              let d = Math.hypot(e.x - lastTarget.x, e.y - lastTarget.y);
              if (d < minDist) { minDist = d; nearest = e; }
            }
            if (nearest) {
              nearest.hp -= Math.floor(bullet.damage * 0.4);
              chained.add(nearest);
              lastTarget = nearest;
            } else break;
          }
        }
        
        if (enemy.hp <= 0) {
          money += enemy.reward;
          spawnParticles(enemy.x, enemy.y, '#ffffff', 6, 2);
          sfxEnemyDie();
        }
      }

      function upgradeTower(tower) {
        let def = towerDefs[tower.type];
        if (tower.level >= def.maxLevel) return false;
        let cost = Math.floor(def.upgradeCost * tower.level);
        if (money < cost) return false;
        money -= cost;
        tower.level++;
        tower.damage = Math.floor(def.damage * Math.pow(1.5, tower.level - 1));
        tower.range += 0.3;
        tower.cooldown = Math.max(10, def.cooldown - tower.level * 2);
        if (tower.splash) tower.splash += 0.2;
        if (tower.slow) tower.slow = Math.min(0.75, tower.slow + 0.08);
        if (tower.chain) tower.chain += (tower.level % 2 === 0 ? 1 : 0);
        spawnParticles(tower.col * GRID_SIZE + GRID_SIZE/2, tower.row * GRID_SIZE + GRID_SIZE/2, '#FFD700', 10, 2.5);
        sfxUpgrade();
        return true;
      }

      function removeTower(tower) {
        let def = towerDefs[tower.type];
        let refund = Math.floor(def.cost * 0.5);
        for (let lv = 2; lv <= tower.level; lv++) refund += Math.floor(def.upgradeCost * (lv - 1) * 0.5);
        money += refund;
        gridMap[tower.row][tower.col] = 'empty';
        towers = towers.filter(t => t !== tower);
        spawnParticles(tower.col * GRID_SIZE + GRID_SIZE/2, tower.row * GRID_SIZE + GRID_SIZE/2, '#ff6666', 8, 2);
        sfxRemove();
      }

      function startNextLoop() {
        wave = 1;
        waveActive = false;
        gameOver = false;
        enemies = [];
        bullets = [];
        spawnQueue = [];
        startWaveBtn.disabled = false;
        continueBtn.style.display = 'none';
        generateRandomPath();
        sfxWaveStart();
        updateUI();
      }

      function update() {
        if (gameOver) return;
        
        // 逐个生成队列中的敌人,确保有适当的间隔
        if (spawnQueue.length > 0) {
          spawnTimer--;
          if (spawnTimer <= 0) {
            spawnEnemyFromQueue();
          }
        }
        
        updateTowers();
        updateBullets();
        
        for (let i = enemies.length - 1; i >= 0; i--) {
          let enemy = enemies[i];
          if (enemy.slowTimer > 0) { 
            enemy.slowTimer--; 
            if (enemy.slowTimer <= 0) enemy.speed = enemy.originalSpeed; 
          }
          if (moveEnemy(enemy) || enemy.hp <= 0) enemies.splice(i, 1);
        }
        
        for (let i = particles.length - 1; i >= 0; i--) {
          let p = particles[i];
          p.life -= 0.02;
          p.x += p.vx; p.y += p.vy;
          p.vx *= 0.96; p.vy *= 0.96;
          if (p.life <= 0) particles.splice(i, 1);
        }
        
        if (waveActive && enemies.length === 0 && spawnQueue.length === 0) {
          waveActive = false;
          money += 100 + wave * 20;
          wave++;
          totalWavesCompleted++;
          sfxWaveComplete();
          console.log(`✅ 第${wave-1}波完成!剩余敌人: ${enemies.length}`);
          
          if (wave > 10) {
            continueBtn.style.display = 'inline-block';
            startWaveBtn.style.display = 'none';
            waveDisplay.textContent = '完成!';
          } else {
            startWaveBtn.disabled = false;
          }
        }
        if (lives <= 0) { gameOver = true; lives = 0; alert('💔 游戏结束!敌人突破了防线...'); }
        updateUI();
      }

      function updateUI() {
        livesDisplay.textContent = lives;
        moneyDisplay.textContent = money;
        if (wave <= 10) waveDisplay.textContent = wave + '/10';
        else waveDisplay.textContent = '完成!';
      }

      function drawGrid() {
        for (let r = 0; r < ROWS; r++) {
          for (let c = 0; c < COLS; c++) {
            let x = c * GRID_SIZE, y = r * GRID_SIZE;
            if (gridMap[r][c] === 'path') {
              let gradient = ctx.createLinearGradient(x, y, x + GRID_SIZE, y + GRID_SIZE);
              gradient.addColorStop(0, '#D4A854'); gradient.addColorStop(0.5, '#C49A3C'); gradient.addColorStop(1, '#B8892E');
              ctx.fillStyle = gradient; ctx.fillRect(x, y, GRID_SIZE, GRID_SIZE);
              ctx.fillStyle = 'rgba(139, 101, 38, 0.3)'; ctx.fillRect(x + 4, y + 4, GRID_SIZE - 8, GRID_SIZE - 8);
              ctx.strokeStyle = '#A07828'; ctx.lineWidth = 3; ctx.strokeRect(x + 1, y + 1, GRID_SIZE - 2, GRID_SIZE - 2);
            } else {
              let gradient = ctx.createLinearGradient(x, y, x + GRID_SIZE, y + GRID_SIZE);
              gradient.addColorStop(0, '#66BB6A'); gradient.addColorStop(0.5, '#4CAF50'); gradient.addColorStop(1, '#43A047');
              ctx.fillStyle = gradient; ctx.fillRect(x, y, GRID_SIZE, GRID_SIZE);
              ctx.fillStyle = 'rgba(129, 199, 132, 0.4)'; ctx.fillRect(x + 3, y + 3, GRID_SIZE - 6, GRID_SIZE - 6);
              if ((r + c) % 3 === 0) {
                ctx.fillStyle = '#FFEB3B'; ctx.beginPath();
                ctx.arc(x + GRID_SIZE * 0.7, y + GRID_SIZE * 0.25, 3, 0, Math.PI * 2); ctx.fill();
              }
            }
            ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; ctx.lineWidth = 1; ctx.strokeRect(x, y, GRID_SIZE, GRID_SIZE);
          }
        }
        let sx = spawnPoint.col * GRID_SIZE + GRID_SIZE/2;
        let sy = spawnPoint.row * GRID_SIZE + GRID_SIZE/2;
        ctx.fillStyle = 'rgba(0, 200, 83, 0.4)'; ctx.beginPath(); ctx.arc(sx, sy, 24, 0, Math.PI * 2); ctx.fill();
        ctx.fillStyle = '#00C853'; ctx.font = 'bold 24px Arial'; ctx.textAlign = 'center'; ctx.fillText('🚪', sx, sy + 8);
        
        let ex = endPoint.col * GRID_SIZE + GRID_SIZE/2;
        let ey = endPoint.row * GRID_SIZE + GRID_SIZE/2;
        ctx.fillStyle = 'rgba(255, 82, 82, 0.4)'; ctx.beginPath(); ctx.arc(ex, ey, 24, 0, Math.PI * 2); ctx.fill();
        ctx.fillText('🏠', ex, ey + 8);
      }

      function drawArrowTower(cx, cy, size, color) {
        ctx.fillStyle = '#5D4037'; ctx.beginPath(); ctx.arc(cx, cy, size * 0.7, 0, Math.PI * 2); ctx.fill();
        ctx.fillStyle = color; ctx.shadowColor = color; ctx.shadowBlur = 10;
        ctx.beginPath(); ctx.moveTo(cx, cy - size * 1.2); ctx.lineTo(cx - size * 0.7, cy + size * 0.8); ctx.lineTo(cx + size * 0.7, cy + size * 0.8); ctx.closePath(); ctx.fill();
        ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(cx, cy, size * 0.6, -0.8, 0.8); ctx.stroke();
        ctx.fillStyle = '#FFFFFF'; ctx.beginPath(); ctx.arc(cx, cy, size * 0.2, 0, Math.PI * 2); ctx.fill();
        ctx.shadowBlur = 0;
      }

      function drawCannonTower(cx, cy, size, color) {
        ctx.fillStyle = '#37474F'; ctx.beginPath(); ctx.arc(cx, cy, size * 0.8, 0, Math.PI * 2); ctx.fill();
        ctx.save(); ctx.translate(cx, cy); ctx.fillStyle = color; ctx.shadowColor = color; ctx.shadowBlur = 8;
        ctx.fillRect(-size * 0.3, -size * 1.1, size * 0.6, size * 1.3);
        ctx.fillStyle = '#212121'; ctx.fillRect(-size * 0.25, -size * 1.15, size * 0.5, size * 0.3); ctx.restore();
        for (let i = 0; i < 4; i++) {
          let angle = (i / 4) * Math.PI * 2; ctx.fillStyle = '#FFD54F';
          ctx.beginPath(); ctx.arc(cx + Math.cos(angle) * size * 0.6, cy + Math.sin(angle) * size * 0.6, 3, 0, Math.PI * 2); ctx.fill();
        }
        ctx.shadowBlur = 0;
      }

      function drawIceTower(cx, cy, size, color) {
        ctx.fillStyle = '#B3E5FC'; ctx.beginPath();
        for (let i = 0; i < 6; i++) {
          let angle = (i / 6) * Math.PI * 2 - Math.PI / 2;
          if (i === 0) ctx.moveTo(cx + Math.cos(angle) * size * 0.8, cy + Math.sin(angle) * size * 0.8);
          else ctx.lineTo(cx + Math.cos(angle) * size * 0.8, cy + Math.sin(angle) * size * 0.8);
        }
        ctx.closePath(); ctx.fill();
        ctx.fillStyle = color; ctx.shadowColor = color; ctx.shadowBlur = 12; ctx.beginPath();
        for (let i = 0; i < 6; i++) {
          let angle = (i / 6) * Math.PI * 2 - Math.PI / 2;
          if (i === 0) ctx.moveTo(cx + Math.cos(angle) * size * 0.7, cy + Math.sin(angle) * size * 0.7);
          else ctx.lineTo(cx + Math.cos(angle) * size * 0.7, cy + Math.sin(angle) * size * 0.7);
          let mid = angle + Math.PI / 6;
          ctx.lineTo(cx + Math.cos(mid) * size * 0.35, cy + Math.sin(mid) * size * 0.35);
        }
        ctx.closePath(); ctx.fill();
        ctx.fillStyle = '#FFFFFF'; ctx.beginPath(); ctx.arc(cx, cy, size * 0.2, 0, Math.PI * 2); ctx.fill();
        ctx.shadowBlur = 0;
      }

      function drawLightningTower(cx, cy, size, color) {
        ctx.fillStyle = '#FFF9C4'; ctx.beginPath();
        ctx.moveTo(cx, cy - size * 0.9); ctx.lineTo(cx + size * 0.7, cy);
        ctx.lineTo(cx, cy + size * 0.9); ctx.lineTo(cx - size * 0.7, cy); ctx.closePath(); ctx.fill();
        ctx.fillStyle = color; ctx.shadowColor = color; ctx.shadowBlur = 18; ctx.beginPath();
        ctx.moveTo(cx + size * 0.15, cy - size * 0.8); ctx.lineTo(cx - size * 0.4, cy + size * 0.1);
        ctx.lineTo(cx - size * 0.05, cy + size * 0.05); ctx.lineTo(cx - size * 0.25, cy + size * 0.8);
        ctx.lineTo(cx + size * 0.35, cy - size * 0.15); ctx.lineTo(cx, cy - size * 0.05);
        ctx.lineTo(cx + size * 0.25, cy - size * 0.8); ctx.closePath(); ctx.fill();
        ctx.shadowBlur = 0;
      }

      function drawTowerShape(tower) {
        let cx = tower.col * GRID_SIZE + GRID_SIZE/2;
        let cy = tower.row * GRID_SIZE + GRID_SIZE/2;
        let def = towerDefs[tower.type];
        let size = 16 + tower.level * 2;
        
        if (showRange) {
          ctx.beginPath(); ctx.arc(cx, cy, tower.range * GRID_SIZE, 0, Math.PI * 2);
          ctx.fillStyle = 'rgba(255, 255, 255, 0.12)'; ctx.fill();
          ctx.strokeStyle = def.color; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.stroke(); ctx.setLineDash([]);
        }
        
        switch(def.shape) {
          case 'arrow': drawArrowTower(cx, cy, size, def.color); break;
          case 'cannon': drawCannonTower(cx, cy, size, def.color); break;
          case 'ice': drawIceTower(cx, cy, size, def.color); break;
          case 'lightning': drawLightningTower(cx, cy, size, def.color); break;
        }
        
        ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 13px Arial'; ctx.textAlign = 'center';
        ctx.strokeStyle = 'rgba(0,0,0,0.6)'; ctx.lineWidth = 2;
        ctx.strokeText('Lv' + tower.level, cx, cy - size - 8);
        ctx.fillText('Lv' + tower.level, cx, cy - size - 8);
      }

      function drawAll() {
        ctx.clearRect(0, 0, MAP_W, MAP_H);
        drawGrid();
        for (let t of towers) drawTowerShape(t);
        
        for (let e of enemies) {
          ctx.save(); ctx.shadowColor = 'rgba(0,0,0,0.4)'; ctx.shadowBlur = 6;
          ctx.fillStyle = e.color; ctx.beginPath(); ctx.arc(e.x, e.y, e.size, 0, Math.PI * 2); ctx.fill();
          ctx.fillStyle = 'rgba(255,255,255,0.3)'; ctx.arc(e.x - 2, e.y - 2, e.size * 0.5, 0, Math.PI * 2); ctx.fill();
          let hpPercent = e.hp / e.maxHp; ctx.shadowBlur = 0;
          ctx.fillStyle = '#333'; ctx.fillRect(e.x - 14, e.y - e.size - 10, 28, 6);
          ctx.fillStyle = hpPercent > 0.5 ? '#4CAF50' : hpPercent > 0.25 ? '#FF9800' : '#f44336';
          ctx.fillRect(e.x - 14, e.y - e.size - 10, 28 * hpPercent, 6);
          ctx.restore();
        }
        
        for (let b of bullets) {
          ctx.shadowColor = b.color; ctx.shadowBlur = 8; ctx.fillStyle = b.color;
          ctx.beginPath(); ctx.arc(b.x, b.y, 4, 0, Math.PI * 2); ctx.fill();
          ctx.fillStyle = '#fff'; ctx.arc(b.x - 1, b.y - 1, 2, 0, Math.PI * 2); ctx.fill();
        }
        ctx.shadowBlur = 0;
        
        for (let p of particles) {
          ctx.globalAlpha = p.life * 0.9; ctx.fillStyle = p.color;
          ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill();
        }
        ctx.globalAlpha = 1;
        
        if (gameOver) {
          ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(0, 0, MAP_W, MAP_H);
          ctx.fillStyle = '#ffffff'; ctx.font = 'bold 42px Arial'; ctx.textAlign = 'center';
          ctx.fillText('游戏结束', MAP_W/2, MAP_H/2);
        }
      }

      canvas.addEventListener('click', (e) => {
        initAudio();
        if (gameOver) return;
        const rect = canvas.getBoundingClientRect();
        const scaleX = MAP_W / rect.width, scaleY = MAP_H / rect.height;
        const col = Math.floor((e.clientX - rect.left) * scaleX / GRID_SIZE);
        const row = Math.floor((e.clientY - rect.top) * scaleY / GRID_SIZE);
        if (col < 0 || col >= COLS || row < 0 || row >= ROWS) return;
        
        let clickedTower = towers.find(t => t.col === col && t.row === row);
        if (clickedTower) {
          let def = towerDefs[clickedTower.type];
          if (clickedTower.level >= def.maxLevel) { alert('已达到最高等级!右键拆除返还金币。'); return; }
          let cost = Math.floor(def.upgradeCost * clickedTower.level);
          if (confirm(`升级 ${def.name} 到 Lv${clickedTower.level + 1}?\n费用:${cost} 金币`)) {
            if (!upgradeTower(clickedTower)) alert('金币不足!');
          }
          return;
        }
        
        if (gridMap[row][col] !== 'empty') { alert('不能在这里建造!'); return; }
        let def = towerDefs[selectedTowerType];
        if (money < def.cost) { alert('金币不足!'); return; }
        money -= def.cost;
        towers.push({ col, row, type: selectedTowerType, level: 1, damage: def.damage, range: def.range, cooldown: def.cooldown, cooldownCounter: 0, splash: def.splash || 0, slow: def.slow || 0, chain: def.chain || 0 });
        gridMap[row][col] = 'tower';
        spawnParticles(col * GRID_SIZE + GRID_SIZE/2, row * GRID_SIZE + GRID_SIZE/2, '#aaddff', 6, 2);
        sfxBuild();
      });

      canvas.addEventListener('contextmenu', (e) => {
        e.preventDefault(); initAudio();
        if (gameOver) return;
        const rect = canvas.getBoundingClientRect();
        const scaleX = MAP_W / rect.width, scaleY = MAP_H / rect.height;
        const col = Math.floor((e.clientX - rect.left) * scaleX / GRID_SIZE);
        const row = Math.floor((e.clientY - rect.top) * scaleY / GRID_SIZE);
        if (col < 0 || col >= COLS || row < 0 || row >= ROWS) return;
        
        let clickedTower = towers.find(t => t.col === col && t.row === row);
        if (clickedTower) {
          let def = towerDefs[clickedTower.type];
          let refund = Math.floor(def.cost * 0.5);
          for (let lv = 2; lv <= clickedTower.level; lv++) refund += Math.floor(def.upgradeCost * (lv - 1) * 0.5);
          if (confirm(`拆除 ${def.name} Lv${clickedTower.level}?\n返还:${refund} 金币`)) removeTower(clickedTower);
        }
      });

      document.querySelectorAll('.tower-btn').forEach(btn => {
        btn.addEventListener('click', function() {
          initAudio();
          document.querySelectorAll('.tower-btn').forEach(b => b.classList.remove('active'));
          this.classList.add('active');
          selectedTowerType = this.dataset.type;
        });
      });

      startWaveBtn.addEventListener('click', () => {
        initAudio();
        if (gameOver || waveActive || wave > 10) return;
        waveActive = true;
        let newEnemies = spawnWave(wave, totalWavesCompleted);
        spawnQueue = newEnemies;
        spawnTimer = spawnInterval; // 初始化生成计时器
        startWaveBtn.disabled = true;
        sfxWaveStart();
        console.log(`🚀 开始第${wave}波!队列中有${spawnQueue.length}个敌人等待生成`);
      });

      continueBtn.addEventListener('click', () => {
        initAudio();
        totalWavesCompleted++;
        startNextLoop();
        continueBtn.style.display = 'none';
        startWaveBtn.style.display = 'inline-block';
        startWaveBtn.disabled = false;
        alert(`进入第 ${totalWavesCompleted + 1} 轮!敌人更强了!`);
      });

      document.getElementById('resetBtn').addEventListener('click', () => {
        initAudio();
        if (confirm('确定要重新开始吗?')) resetGame();
      });

      function resetGame() {
        lives = 20; money = 400; wave = 1; totalWavesCompleted = 0;
        gameOver = false; waveActive = false;
        enemies = []; towers = []; bullets = []; particles = []; spawnQueue = [];
        selectedTowerType = 'arrow'; showRange = true;
        rangeToggleBtn.classList.add('active-range'); rangeToggleBtn.textContent = '🎯 范围';
        continueBtn.style.display = 'none'; startWaveBtn.style.display = 'inline-block'; startWaveBtn.disabled = false;
        document.querySelectorAll('.tower-btn').forEach(b => b.classList.remove('active'));
        document.querySelector('.tower-btn[data-type="arrow"]').classList.add('active');
        initGridMap(); generateRandomPath(); updateUI();
      }

      function gameLoop() {
        update();
        drawAll();
        requestAnimationFrame(gameLoop);
      }

      initGridMap();
      generateRandomPath();
      updateUI();
      gameLoop();
    })();
  </script>
</body>
</html>