SOURCE

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>�� 像素小鸟 · 闯关星光版</title>
  <style>
    * {
      box-sizing: border-box;
      user-select: none;
      margin: 0;
      padding: 0;
    }
    body {
      background: #1a2630;
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      font-family: 'Courier New', Courier, monospace;
      padding: 8px;
    }
    .game-wrapper {
      background: #2d4555;
      padding: 12px 12px 16px;
      border-radius: 24px;
      box-shadow: 0 10px 0 #16222b, 0 16px 30px rgba(0,0,0,0.7);
      border: 2px solid #5a7a8a;
      max-width: 520px;
      width: 100%;
    }
    canvas {
      display: block;
      margin: 0 auto;
      width: 100%;
      height: auto;
      aspect-ratio: 480 / 640;
      background: #80b9e0;
      border-radius: 16px;
      box-shadow: inset 0 0 0 3px #2a4355, 0 8px 0 #1a2f3a;
      cursor: pointer;
      touch-action: none;
      image-rendering: pixelated;
    }
    /* 状态栏 - 更紧凑 */
    .status-bar {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin: 8px 2px 0;
      color: #f0ebd0;
      text-shadow: 1px 1px 0 #1a2a33;
      font-size: 0.85rem;
      font-weight: bold;
      flex-wrap: wrap;
      gap: 4px 6px;
      padding: 4px 4px 0;
    }
    .status-bar span {
      background: #2a4355;
      padding: 3px 12px;
      border-radius: 30px;
      box-shadow: inset 0 -2px 0 #14222b;
      font-size: 0.8rem;
      letter-spacing: 0.3px;
      white-space: nowrap;
    }
    .status-bar .stars {
      background: #5a3f2e;
      box-shadow: inset 0 -2px 0 #3f2a1e;
    }
    .btn-reset {
      background: #d68d3a;
      border: none;
      color: #1e2a2f;
      font-weight: bold;
      font-size: 0.8rem;
      padding: 3px 14px;
      border-radius: 30px;
      box-shadow: 0 4px 0 #a86d2a;
      cursor: pointer;
      transition: 0.05s linear;
      font-family: inherit;
      white-space: nowrap;
    }
    .btn-reset:active {
      transform: translateY(3px);
      box-shadow: 0 1px 0 #a86d2a;
    }
    /* 状态栏内部数字保持可见 */
    .status-bar span span {
      background: none;
      box-shadow: none;
      padding: 0;
      font-size: inherit;
    }
    @media (max-width: 480px) {
      .game-wrapper { padding: 8px; border-radius: 16px; }
      .status-bar { font-size: 0.7rem; gap: 3px; }
      .status-bar span { padding: 2px 8px; font-size: 0.7rem; }
      .btn-reset { font-size: 0.7rem; padding: 2px 10px; }
    }
  </style>
</head>
<body>
<div class="game-wrapper">
  <canvas id="gameCanvas" width="480" height="640"></canvas>
  <div class="status-bar">
    <span>�� <span id="scoreDisplay">0</span></span>
    <span>�� 第 <span id="levelDisplay">1</span> 关</span>
    <span class="stars">⭐ <span id="starsDisplay">3</span></span>
    <span>�� <span id="coinDisplay">0</span></span>
    <button class="btn-reset" id="restartBtn">↺</button>
  </div>
</div>

<script>
  (function() {
    // ---------- 画布 ----------
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    const W = 480, H = 640;

    // ---------- DOM ----------
    const scoreSpan = document.getElementById('scoreDisplay');
    const levelSpan = document.getElementById('levelDisplay');
    const starsSpan = document.getElementById('starsDisplay');
    const coinSpan = document.getElementById('coinDisplay');

    // ---------- 常量 ----------
    const GRAVITY = 0.28;
    const FLAP_FORCE = -5.0;
    const BIRD_X = 72;
    const PIPE_W = 38;
    const GROUND_H = 58;
    const MIN_PIPE = 48;

    // ---------- 游戏状态 ----------
    let bird = { y: 280, vy: 0, radius: 13 };
    let pipes = [];
    let score = 0;
    let level = 1;
    let stars = 3;
    let coins = 0;
    let highScore = parseInt(localStorage.getItem('flappyBirdHighScore')) || 0;
    let gameState = 'start';
    let frame = 0;
    let pipeSpawnCounter = 0;
    let baseSpeed = 2.0;
    let levelCompleteTimer = 0;
    let showLevelUp = false;
    let hitEffectTimer = 0;
    let starLossEffect = 0;
    let victoryParticles = [];
    let isVictoryCelebration = false;

    // ===== 皮肤系统 =====
    const SKIN_LIST = [
      { name: '经典黄', color: '#f5c842', eye: '#1e2e3a', wing: '#e5b832' },
      { name: '烈焰红', color: '#e65c4a', eye: '#2d1a1a', wing: '#c94a3a' },
      { name: '冰霜蓝', color: '#6fc1e0', eye: '#0f2a3a', wing: '#4fa3c4' },
      { name: '暗影紫', color: '#8f6db5', eye: '#1f1a2a', wing: '#7a54a0' },
      { name: '翡翠绿', color: '#5fb57a', eye: '#1a2a1a', wing: '#3f9a5a' },
      { name: '暗夜黑', color: '#4a4a4a', eye: '#1a1a1a', wing: '#3a3a3a' },
      { name: '樱花粉', color: '#f7a8b8', eye: '#2d1a1a', wing: '#e8889a' },
      { name: '阳光橙', color: '#f5a842', eye: '#1e2a1a', wing: '#d48a30' },
    ];
    let currentSkinIndex = 0;
    let skinUnlocked = [1, 0, 0, 0, 0, 0, 0, 0];
    const SKIN_COST = [0, 20, 30, 40, 50, 60, 35, 45];

    // 云朵
    let clouds = [];
    for (let i = 0; i < 8; i++) {
      clouds.push({
        x: Math.random() * W,
        y: Math.random() * 200 + 20,
        w: 45 + Math.random() * 70,
        speed: 0.15 + Math.random() * 0.3
      });
    }

    // 背景小元素
    let bgElements = [];
    for (let i = 0; i < 30; i++) {
      bgElements.push({
        x: Math.random() * W,
        y: Math.random() * (H - GROUND_H - 60) + 30,
        w: 3 + Math.random() * 6,
        h: 3 + Math.random() * 6,
        color: `hsla(${200 + Math.random() * 40}, 60%, ${60 + Math.random() * 30}%, 0.3)`
      });
    }

    // ---------- 辅助 ----------
    function updateUI() {
      scoreSpan.textContent = score;
      levelSpan.textContent = level;
      starsSpan.textContent = stars;
      coinSpan.textContent = coins;
    }

    function updateHighScore() {
      if (score > highScore) {
        highScore = score;
        localStorage.setItem('flappyBirdHighScore', String(highScore));
      }
    }

    function getLevelConfig(lvl) {
      switch (lvl) {
        case 1: return { speed: 2.0, gap: 175, spawnRate: 95, target: 20 };
        case 2: return { speed: 2.8, gap: 150, spawnRate: 78, target: 40 };
        case 3: return { speed: 3.7, gap: 128, spawnRate: 62, target: 70 };
        case 4: return { speed: 4.8, gap: 105, spawnRate: 44, target: 100 };
        default: return { speed: 2.0, gap: 175, spawnRate: 95, target: 20 };
      }
    }

    // ---------- 重置 ----------
    function fullReset() {
      level = 1;
      baseSpeed = 2.0;
      stars = 3;
      score = 0;
      bird.y = 280;
      bird.vy = 0;
      pipes = [];
      frame = 0;
      pipeSpawnCounter = 0;
      showLevelUp = false;
      levelCompleteTimer = 0;
      hitEffectTimer = 0;
      starLossEffect = 0;
      victoryParticles = [];
      isVictoryCelebration = false;
      gameState = 'start';
      updateUI();
    }

    function resetGame() {
      stars = 3;
      score = 0;
      bird.y = 280;
      bird.vy = 0;
      pipes = [];
      frame = 0;
      pipeSpawnCounter = 0;
      showLevelUp = false;
      levelCompleteTimer = 0;
      hitEffectTimer = 0;
      starLossEffect = 0;
      victoryParticles = [];
      isVictoryCelebration = false;
      const cfg = getLevelConfig(level);
      baseSpeed = cfg.speed;
      gameState = 'start';
      updateUI();
    }

    // ---------- 管道 ----------
    function spawnPipe() {
      const cfg = getLevelConfig(level);
      const gap = cfg.gap;
      const minH = 48;
      const maxH = H - GROUND_H - gap - 48;
      const top = Math.floor(Math.random() * (maxH - minH + 1)) + minH;
      pipes.push({
        x: W,
        topHeight: top,
        gap: gap,
        scored: false,
        width: PIPE_W,
      });
    }

    function checkCollision(pipe) {
      const bx = BIRD_X, by = bird.y, r = bird.radius - 2;
      const left = bx - r, right = bx + r, top = by - r, bottom = by + r;
      const pLeft = pipe.x, pRight = pipe.x + pipe.width;
      const topPipeBottom = pipe.topHeight;
      const bottomPipeTop = pipe.topHeight + pipe.gap;
      if (right > pLeft && left < pRight) {
        if (top < topPipeBottom || bottom > bottomPipeTop) return true;
      }
      return false;
    }

    function loseStar() {
      stars--;
      starLossEffect = 20;
      hitEffectTimer = 10;
      updateUI();
      if (stars <= 0) {
        gameState = 'gameover';
        updateHighScore();
        for (let i = 0; i < 25; i++) {
          victoryParticles.push({
            x: BIRD_X + (Math.random() - 0.5) * 30,
            y: bird.y + (Math.random() - 0.5) * 30,
            vx: (Math.random() - 0.5) * 10,
            vy: (Math.random() - 0.5) * 10 - 3,
            life: 30 + Math.random() * 25,
            color: `hsl(${Math.random() * 60 + 10}, 90%, 60%)`,
            size: 3 + Math.random() * 7
          });
        }
      }
    }

    function checkLevelComplete() {
      const cfg = getLevelConfig(level);
      if (score >= cfg.target) {
        if (level === 4) {
          gameState = 'victory';
          isVictoryCelebration = true;
          for (let i = 0; i < 100; i++) {
            victoryParticles.push({
              x: Math.random() * W,
              y: Math.random() * H * 0.7,
              vx: (Math.random() - 0.5) * 12,
              vy: (Math.random() - 0.5) * 12 - 5,
              life: 60 + Math.random() * 50,
              color: `hsl(${Math.random() * 360}, 90%, 65%)`,
              size: 4 + Math.random() * 9
            });
          }
          updateHighScore();
        } else {
          gameState = 'levelComplete';
          levelCompleteTimer = 70;
          showLevelUp = true;
          updateHighScore();
          for (let i = 0; i < 35; i++) {
            victoryParticles.push({
              x: W/2 + (Math.random() - 0.5) * 250,
              y: 180 + Math.random() * 120,
              vx: (Math.random() - 0.5) * 7,
              vy: (Math.random() - 0.5) * 7 - 4,
              life: 30 + Math.random() * 25,
              color: `hsl(${Math.random() * 80 + 30}, 90%, 60%)`,
              size: 3 + Math.random() * 6
            });
          }
        }
        return true;
      }
      return false;
    }

    function goToNextLevel() {
      if (level < 4) {
        level++;
        const cfg = getLevelConfig(level);
        baseSpeed = cfg.speed;
        pipes = [];
        bird.y = 280;
        bird.vy = 0;
        score = 0;
        gameState = 'playing';
        showLevelUp = false;
        updateUI();
      }
    }

    // ---------- 更新 ----------
    function update() {
      for (let c of clouds) {
        c.x += c.speed * 0.4;
        if (c.x > W + 60) { c.x = -c.w - 20; c.y = Math.random() * 200 + 20; }
      }

      if (gameState === 'start') {
        bird.y = 280 + Math.sin(frame * 0.05) * 8;
        frame++;
        return;
      }

      if (gameState === 'levelComplete') {
        levelCompleteTimer--;
        updateParticles();
        if (levelCompleteTimer <= 0) goToNextLevel();
        return;
      }

      if (gameState === 'victory') {
        updateParticles();
        return;
      }

      if (gameState === 'gameover') {
        updateParticles();
        if (starLossEffect > 0) starLossEffect--;
        return;
      }

      // playing
      bird.vy += GRAVITY;
      bird.y += bird.vy;

      if (bird.y + bird.radius > H - GROUND_H) {
        bird.y = H - GROUND_H - bird.radius;
        loseStar();
        if (gameState === 'gameover') return;
      }
      if (bird.y - bird.radius < 0) {
        bird.y = bird.radius;
        bird.vy = 0;
      }

      const cfg = getLevelConfig(level);
      pipeSpawnCounter++;
      if (pipeSpawnCounter >= cfg.spawnRate) {
        pipeSpawnCounter = 0;
        spawnPipe();
      }

      for (let i = pipes.length - 1; i >= 0; i--) {
        const p = pipes[i];
        p.x -= baseSpeed;

        if (checkCollision(p)) {
          loseStar();
          if (gameState === 'gameover') return;
          pipes.splice(i, 1);
          continue;
        }

        if (!p.scored && p.x + p.width < BIRD_X) {
          p.scored = true;
          score++;
          if (score % 5 === 0) coins++;
          updateUI();
          if (checkLevelComplete()) return;
        }

        if (p.x + p.width < 0) {
          pipes.splice(i, 1);
        }
      }

      if (hitEffectTimer > 0) hitEffectTimer--;
      if (starLossEffect > 0) starLossEffect--;
      frame++;
    }

    function updateParticles() {
      for (let i = victoryParticles.length - 1; i >= 0; i--) {
        const p = victoryParticles[i];
        p.x += p.vx;
        p.y += p.vy;
        p.vy += 0.08;
        p.life--;
        if (p.life <= 0) victoryParticles.splice(i, 1);
      }
    }

    // ---------- 绘制 ----------
    function draw() {
      ctx.clearRect(0, 0, W, H);

      // 天空 (像素色块)
      for (let y = 0; y < H * 0.65; y += 4) {
        let ratio = y / (H * 0.65);
        let r = 60 + 40 * ratio;
        let g = 130 + 60 * ratio;
        let b = 200 - 20 * ratio;
        ctx.fillStyle = `rgb(${r|0},${g|0},${b|0})`;
        ctx.fillRect(0, y, W, 4);
      }
      ctx.fillStyle = '#aad0ee';
      ctx.fillRect(0, H * 0.65, W, H * 0.35);

      for (let el of bgElements) {
        ctx.fillStyle = el.color;
        ctx.fillRect(el.x, el.y, el.w, el.h);
      }

      // 云
      for (let c of clouds) {
        ctx.fillStyle = 'rgba(255,255,248,0.85)';
        let cx = c.x, cy = c.y, cw = c.w;
        const blocks = [
          [0, 0, cw * 0.5, 14], [-cw * 0.2, -6, cw * 0.35, 12], [cw * 0.2, -4, cw * 0.3, 12],
          [-cw * 0.1, 4, cw * 0.4, 10], [cw * 0.15, 6, cw * 0.35, 10]
        ];
        for (let b of blocks) {
          ctx.fillRect(cx + b[0] - b[2]/2, cy + b[1] - b[3]/2, b[2], b[3]);
        }
      }

      // 城市剪影
      const cityColors = ['#3d5f72', '#4a6f82', '#355568'];
      for (let i = 0; i < 16; i++) {
        let bx = i * 34 - 8;
        let bh = 20 + Math.sin(i * 1.3 + 0.5) * 14 + 10;
        ctx.fillStyle = cityColors[i % 3];
        ctx.fillRect(bx, H - GROUND_H - bh, 24, bh);
        ctx.fillStyle = 'rgba(255,220,120,0.2)';
        for (let wy = 0; wy < bh - 8; wy += 8) {
          for (let wx = 0; wx < 16; wx += 8) {
            if (Math.random() > 0.35) continue;
            ctx.fillRect(bx + 4 + wx, H - GROUND_H - bh + 4 + wy, 4, 4);
          }
        }
      }

      // 管道
      for (let p of pipes) {
        ctx.fillStyle = '#3b8540';
        ctx.fillRect(p.x, 0, p.width, p.topHeight);
        ctx.fillStyle = '#2b6a2a';
        ctx.fillRect(p.x - 4, p.topHeight - 16, p.width + 8, 16);
        ctx.fillStyle = 'rgba(180,255,180,0.15)';
        ctx.fillRect(p.x + 4, 0, 6, p.topHeight - 16);
        ctx.fillRect(p.x + p.width - 10, 0, 6, p.topHeight - 16);

        const bottomY = p.topHeight + p.gap;
        ctx.fillStyle = '#3b8540';
        ctx.fillRect(p.x, bottomY, p.width, H - GROUND_H - bottomY);
        ctx.fillStyle = '#2b6a2a';
        ctx.fillRect(p.x - 4, bottomY, p.width + 8, 16);
        ctx.fillStyle = 'rgba(180,255,180,0.15)';
        ctx.fillRect(p.x + 4, bottomY + 16, 6, H - GROUND_H - bottomY - 20);
        ctx.fillRect(p.x + p.width - 10, bottomY + 16, 6, H - GROUND_H - bottomY - 20);
      }

      // 地面
      const gy = H - GROUND_H;
      ctx.fillStyle = '#7d9b6d';
      ctx.fillRect(0, gy, W, GROUND_H);
      for (let i = 0; i < 30; i++) {
        let gx = (i * 18 - (frame * 0.8) % 18 + 18) % W - 18;
        ctx.fillStyle = i % 2 === 0 ? '#5d7b4d' : '#6d8b5d';
        ctx.fillRect(gx, gy + 2, 8, 6);
        ctx.fillRect(gx + 10, gy + 14, 8, 6);
        ctx.fillRect(gx + 5, gy + 26, 8, 6);
      }
      ctx.fillStyle = '#9dbd8d';
      ctx.fillRect(0, gy, W, 3);

      // ---- 小鸟 ----
      const skin = SKIN_LIST[currentSkinIndex] || SKIN_LIST[0];
      ctx.shadowColor = 'rgba(0,0,0,0.25)';
      ctx.shadowBlur = 8;
      
      ctx.fillStyle = skin.color;
      ctx.fillRect(BIRD_X - 12, bird.y - 12, 24, 24);
      ctx.fillRect(BIRD_X - 6, bird.y - 4, 12, 14);
      
      const wingAngle = Math.sin(frame * 0.2) * 0.4 + 0.3;
      ctx.fillStyle = skin.wing;
      ctx.fillRect(BIRD_X - 16, bird.y - 4 + wingAngle * 4, 8, 8);
      ctx.fillRect(BIRD_X + 8, bird.y - 4 - wingAngle * 4, 8, 8);
      
      ctx.fillStyle = '#1e2e3a';
      ctx.fillRect(BIRD_X + 4, bird.y - 8, 6, 6);
      ctx.fillRect(BIRD_X + 12, bird.y - 8, 6, 6);
      ctx.fillStyle = 'white';
      ctx.fillRect(BIRD_X + 6, bird.y - 6, 3, 3);
      ctx.fillRect(BIRD_X + 14, bird.y - 6, 3, 3);
      ctx.fillStyle = '#1e2e3a';
      ctx.fillRect(BIRD_X + 8, bird.y - 4, 2, 2);
      ctx.fillRect(BIRD_X + 16, bird.y - 4, 2, 2);

      ctx.fillStyle = '#e8a030';
      ctx.fillRect(BIRD_X + 18, bird.y - 2, 8, 4);
      ctx.fillRect(BIRD_X + 22, bird.y + 2, 4, 2);
      
      ctx.shadowBlur = 0;

      if (hitEffectTimer > 0 && hitEffectTimer % 4 < 2) {
        ctx.fillStyle = 'rgba(255,255,255,0.4)';
        ctx.fillRect(BIRD_X - 14, bird.y - 14, 28, 28);
      }

      // 星星碎裂特效
      if (starLossEffect > 0) {
        ctx.font = '28px monospace';
        ctx.fillStyle = '#f5d742';
        ctx.shadowBlur = 16;
        ctx.shadowColor = '#ffaa33';
        ctx.fillText('��', BIRD_X - 18, bird.y - 28);
        ctx.shadowBlur = 0;
        for (let i = 0; i < 8; i++) {
          const angle = frame * 0.25 + i * 1.0;
          const dist = 14 + (22 - starLossEffect) * 1.5;
          ctx.fillStyle = `hsl(${40 + i * 20}, 90%, 60%)`;
          ctx.fillRect(BIRD_X + Math.cos(angle) * dist - 3, bird.y + Math.sin(angle) * dist - 10 - 3, 6, 6);
        }
      }

      // 粒子
      for (let p of victoryParticles) {
        ctx.globalAlpha = Math.min(1, p.life / 20);
        ctx.fillStyle = p.color;
        ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
      }
      ctx.globalAlpha = 1;

      // ---- UI 文字 ----
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.shadowColor = 'rgba(0,0,0,0.6)';
      ctx.shadowBlur = 10;

      if (gameState === 'start') {
        ctx.font = 'bold 40px "Courier New", monospace';
        ctx.fillStyle = '#f7f2d6';
        ctx.fillText('�� 像素小鸟', W/2, 100);
        
        ctx.font = '20px "Courier New", monospace';
        ctx.fillStyle = '#dfdbb0';
        ctx.fillText('点击 / 空格 起飞', W/2, 160);
        
        ctx.font = '16px "Courier New", monospace';
        ctx.fillStyle = '#c0b88a';
        ctx.fillText(`�� ${coins}  |  皮肤: ${skin.name}`, W/2, 220);
        
        ctx.font = '20px "Courier New", monospace';
        ctx.fillStyle = '#d4c88a';
        ctx.fillText('◀  换皮肤  ▶', W/2, 290);
        ctx.font = '14px "Courier New", monospace';
        ctx.fillStyle = '#b0a87a';
        let statusText = '已拥有';
        if (currentSkinIndex !== 0 && skinUnlocked[currentSkinIndex] === 0) {
          statusText = `需要 ${SKIN_COST[currentSkinIndex]} ��`;
        }
        ctx.fillText(statusText, W/2, 325);
        
        ctx.font = '11px "Courier New", monospace';
        ctx.fillStyle = '#90886a';
        let skinNames = SKIN_LIST.map((s, i) => {
          return skinUnlocked[i] === 1 ? s.name : `${s.name}��`;
        }).join('  ');
        ctx.fillText(skinNames, W/2, 380);
      }

      if (gameState === 'levelComplete' && showLevelUp) {
        ctx.font = 'bold 38px "Courier New", monospace';
        ctx.fillStyle = '#f5e56b';
        ctx.shadowBlur = 20;
        ctx.fillText(`�� 第 ${level+1} 关`, W/2, 180);
        ctx.font = '22px "Courier New", monospace';
        ctx.fillStyle = '#f0e8c0';
        ctx.fillText('难度提升 !', W/2, 250);
        ctx.shadowBlur = 0;
      }

      if (gameState === 'victory') {
        ctx.font = 'bold 44px "Courier New", monospace';
        ctx.fillStyle = '#f7d44a';
        ctx.shadowBlur = 24;
        ctx.fillText('�� 终极胜利', W/2, 160);
        ctx.font = '26px "Courier New", monospace';
        ctx.fillStyle = '#f5eab0';
        ctx.fillText(`得分 ${score}`, W/2, 240);
        ctx.shadowBlur = 0;
      }

      if (gameState === 'gameover') {
        ctx.font = 'bold 48px "Courier New", monospace';
        ctx.fillStyle = '#d94f4f';
        ctx.shadowBlur = 18;
        ctx.fillText('�� 坠毁', W/2, 150);
        ctx.font = '22px "Courier New", monospace';
        ctx.fillStyle = '#f0e8c0';
        ctx.fillText(`第 ${level} 关 · 得分 ${score}`, W/2, 230);
        if (score >= highScore && score > 0) {
          ctx.fillStyle = '#f5d742';
          ctx.fillText('✨ 新纪录!', W/2, 290);
        }
        ctx.shadowBlur = 0;
      }

      ctx.shadowBlur = 0;
    }

    // ---------- 循环 ----------
    function loop() {
      update();
      draw();
      requestAnimationFrame(loop);
    }

    // ---------- 交互 ----------
    function handleAction() {
      if (gameState === 'start') {
        gameState = 'playing';
        bird.y = 280;
        bird.vy = 0;
        pipes = [];
        score = 0;
        stars = 3;
        updateUI();
        return;
      }
      if (gameState === 'playing') {
        bird.vy = FLAP_FORCE;
        return;
      }
      if (gameState === 'gameover' || gameState === 'victory') {
        fullReset();
        return;
      }
    }

    function changeSkin(direction) {
      if (gameState !== 'start') return;
      let next = (currentSkinIndex + direction + SKIN_LIST.length) % SKIN_LIST.length;
      if (skinUnlocked[next] === 0 && next !== 0) {
        if (coins >= SKIN_COST[next]) {
          coins -= SKIN_COST[next];
          skinUnlocked[next] = 1;
          currentSkinIndex = next;
          updateUI();
        }
        return;
      }
      if (skinUnlocked[next] === 1 || next === 0) {
        currentSkinIndex = next;
      }
    }

    // 事件绑定
    canvas.addEventListener('click', (e) => {
      e.preventDefault();
      const rect = canvas.getBoundingClientRect();
      const scaleX = canvas.width / rect.width;
      const scaleY = canvas.height / rect.height;
      const x = (e.clientX - rect.left) * scaleX;
      const y = (e.clientY - rect.top) * scaleY;

      if (gameState === 'start') {
        if (y > 265 && y < 320) {
          if (x < W/2 - 50) { changeSkin(-1); return; }
          else if (x > W/2 + 50) { changeSkin(1); return; }
        }
        handleAction();
        return;
      }
      handleAction();
    });

    canvas.addEventListener('touchstart', (e) => {
      e.preventDefault();
      const touch = e.touches[0];
      const rect = canvas.getBoundingClientRect();
      const scaleX = canvas.width / rect.width;
      const scaleY = canvas.height / rect.height;
      const x = (touch.clientX - rect.left) * scaleX;
      const y = (touch.clientY - rect.top) * scaleY;

      if (gameState === 'start') {
        if (y > 265 && y < 320) {
          if (x < W/2 - 50) { changeSkin(-1); return; }
          else if (x > W/2 + 50) { changeSkin(1); return; }
        }
        handleAction();
        return;
      }
      handleAction();
    });

    document.addEventListener('keydown', (e) => {
      if (e.key === ' ' || e.key === 'Space') {
        e.preventDefault();
        handleAction();
      }
      if (gameState === 'start') {
        if (e.key === 'ArrowLeft') { changeSkin(-1); e.preventDefault(); }
        if (e.key === 'ArrowRight') { changeSkin(1); e.preventDefault(); }
      }
    });

    document.getElementById('restartBtn').addEventListener('click', () => {
      fullReset();
    });

    fullReset();
    loop();
  })();
</script>
</body>
</html>
console 命令行工具 X clear

                    
>
console