🎮ArcadeLab

便便蛇蛇 - Poop Snake

by RocketWolf27
2728 lines99.4 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>便便蛇蛇 - Poop Snake</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box;user-select:-webkit-user-select:none;-webkit-tap-highlight-color:transparent}
:root{--tile:#d4eef9;--tile2:#c0e3f2;--grout:#a8d4e6;--accent:#ff9eb5;--gold:#ffd700;--blue:#66b3ff;--green:#77dd77;--red:#ff6666}
html,body{width:100%;height:100%;overflow:hidden;font-family:'Fredoka',sans-serif;background:linear-gradient(160deg,#e8f6fc 0%,#d0eaf5 50%,#b8ddee 100%)}
body{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:8px;min-height:100vh}
#hud{width:100%;max-width:500px;display:flex;justify-content:space-between;align-items:center;background:rgba(255,255,255,0.85);backdrop-filter:blur(8px);border-radius:18px;padding:8px 14px;margin-bottom:6px;box-shadow:0 4px 20px rgba(0,0,0,0.08);flex-wrap:wrap;gap:6px}
.hud-item{display:flex;flex-direction:column;align-items:center;font-size:11px;color:#5a7a8a;font-weight:500}
.hud-item .val{font-size:18px;font-weight:700;color:#2d5a72;line-height:1.1}
.hud-item .val.gold{color:#d4a017}
.hud-item .val.pink{color:#d94a6e}
.hud-item .val.green{color:#3a9a3a}
.hud-item .val.blue{color:#3a7ab5}
#comboItem .val{animation:badgePulse 0.6s infinite alternate}
#pauseBtn{background:linear-gradient(135deg,#ff9eb5,#ff7a9e);color:#fff;border:none;border-radius:14px;padding:10px 18px;font-family:inherit;font-weight:600;font-size:14px;cursor:pointer;box-shadow:0 3px 10px rgba(255,122,158,0.4);transition:transform 0.15s}
#pauseBtn:active{transform:scale(0.92)}
#soundBtn{background:linear-gradient(135deg,#77dd77,#55bb55);color:#fff;border:none;border-radius:14px;padding:10px 12px;font-family:inherit;font-weight:600;font-size:14px;cursor:pointer;box-shadow:0 3px 10px rgba(85,187,85,0.4);transition:transform 0.15s}
#soundBtn.muted{background:linear-gradient(135deg,#bbb,#888);box-shadow:0 3px 10px rgba(100,100,100,0.3)}
#soundBtn:active{transform:scale(0.92)}
#shopBtn{background:linear-gradient(135deg,#ffd700,#daa520);color:#fff;border:none;border-radius:14px;padding:10px 12px;font-family:inherit;font-weight:600;font-size:14px;cursor:pointer;box-shadow:0 3px 10px rgba(218,165,32,0.4);transition:transform 0.15s}
#shopBtn:active{transform:scale(0.92)}
.shop-item{display:flex;align-items:center;justify-content:space-between;background:rgba(255,255,255,0.12);border-radius:12px;padding:10px 14px;margin-bottom:8px;gap:10px}
.shop-item-info{display:flex;align-items:center;gap:10px}
.shop-color-preview{width:36px;height:36px;border-radius:8px;border:2px solid rgba(255,255,255,0.4);display:flex;overflow:hidden}
.shop-color-preview span{flex:1}
.shop-item-name{color:#fff;font-weight:600;font-size:14px}
.shop-item-desc{color:#c0dde8;font-size:11px;margin-top:2px}
.shop-buy-btn{background:linear-gradient(135deg,#77dd77,#4caf50);color:#fff;border:none;border-radius:10px;padding:6px 12px;font-family:inherit;font-weight:600;font-size:12px;cursor:pointer;white-space:nowrap}
.shop-buy-btn:disabled{background:#888;cursor:not-allowed;opacity:0.6}
.shop-select-btn{background:linear-gradient(135deg,#66b3ff,#3a7ab5)}
.shop-owned-tag{color:#ffd700;font-size:11px;font-weight:600}
#gameWrap{position:relative;width:100%;max-width:500px;aspect-ratio:16/22;border-radius:20px;overflow:hidden;box-shadow:0 10px 40px rgba(0,0,0,0.15),inset 0 0 0 3px rgba(255,255,255,0.8);background:var(--tile)}
#gameCanvas{display:block;width:100%;height:100%}
#statusBadges{position:absolute;top:8px;left:8px;display:flex;flex-direction:column;gap:5px;z-index:10;pointer-events:none}
.badge{display:flex;align-items:center;gap:4px;background:rgba(255,255,255,0.92);border-radius:12px;padding:4px 10px;font-size:11px;font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,0.1);animation:badgePulse 1s infinite alternate}
.badge.invincible{background:linear-gradient(135deg,#ffe4a0,#ffd700);color:#7a5a00}
.badge.speed{background:linear-gradient(135deg,#a0e8a0,#77dd77);color:#2a6a2a}
.badge.combo{background:linear-gradient(135deg,#ffcce0,#ff9eb5);color:#7a3a5a}
@keyframes badgePulse{from{transform:scale(1)}to{transform:scale(1.05)}}
@keyframes floatUp{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-30px)}}
@keyframes shake{0%,100%{transform:translate(0,0)}25%{transform:translate(-3px,2px)}50%{transform:translate(2px,-3px)}75%{transform:translate(-2px,-2px)}}
@keyframes glow{0%,100%{filter:drop-shadow(0 0 4px rgba(255,215,0,0.6))}50%{filter:drop-shadow(0 0 12px rgba(255,215,0,1))}}
@keyframes tileShift{0%{background-position:0 0}100%{background-position:40px 40px}}
.badge.glow{animation:badgePulse 1s infinite alternate, glow 0.8s ease-in-out infinite}
#mobileControls{display:none}
@media(max-width:600px){
  #mobileControls{display:grid;grid-template-columns:repeat(3,64px);grid-template-rows:repeat(3,64px);gap:6px;margin-top:12px}
  #hud{padding:6px 10px}
  .hud-item .val{font-size:15px}
  .hud-item{font-size:10px}
}
.dpad-btn{display:flex;align-items:center;justify-content:center;background:linear-gradient(145deg,#fff,#e0eef5);border-radius:50%;box-shadow:0 5px 15px rgba(0,0,0,0.15),inset 0 -3px 8px rgba(0,0,0,0.08);font-size:26px;color:#3a6a82;font-weight:700;cursor:pointer;transition:all 0.1s;border:none;font-family:inherit}
.dpad-btn:active,.dpad-btn.active{transform:scale(0.9);box-shadow:0 2px 6px rgba(0,0,0,0.15),inset 0 2px 5px rgba(0,0,0,0.1)}
.dpad-up{grid-column:2;grid-row:1}
.dpad-left{grid-column:1;grid-row:2}
.dpad-right{grid-column:3;grid-row:2}
.dpad-down{grid-column:2;grid-row:3}
.dpad-center{grid-column:2;grid-row:2;background:linear-gradient(145deg,#ff9eb5,#ff7a9e);color:#fff;font-size:16px;cursor:default;box-shadow:0 4px 12px rgba(255,122,158,0.4)}
.dpad-center:active{transform:none}
.overlay{position:absolute;inset:0;background:rgba(30,60,80,0.78);backdrop-filter:blur(10px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:20;padding:20px;border-radius:20px;overflow-y:auto}
.overlay.hidden{display:none}
.mascot{font-size:80px;margin-bottom:10px;filter:drop-shadow(0 4px 8px rgba(0,0,0,0.3));animation:bounce 2s infinite}
@keyframes bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-10px)}}
.overlay h1{color:#fff;font-size:32px;font-weight:700;margin-bottom:6px;text-align:center;text-shadow:0 2px 10px rgba(0,0,0,0.3)}
.overlay h2{color:#fff;font-size:24px;font-weight:700;margin-bottom:12px;text-align:center}
.overlay p{color:#d0e8f2;font-size:14px;margin-bottom:12px;text-align:center;max-width:320px;line-height:1.5}
.name-input{background:rgba(255,255,255,0.95);border-radius:14px;padding:12px 18px;border:none;font-family:inherit;font-size:18px;font-weight:600;color:#2d5a72;width:240px;text-align:center;margin-bottom:12px;box-shadow:0 4px 15px rgba(0,0,0,0.15);outline:none}
.name-input:focus{box-shadow:0 4px 20px rgba(255,158,181,0.5)}
.diff-label{color:#fff;font-size:14px;font-weight:600;margin-bottom:8px}
.diff-options{display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin-bottom:14px;max-width:320px}
.diff-btn{background:rgba(255,255,255,0.85);border:none;border-radius:10px;padding:6px 12px;font-family:inherit;font-weight:600;font-size:13px;color:#3a6a82;cursor:pointer;transition:all 0.15s}
.diff-btn:hover{background:#fff}
.diff-btn.selected{background:linear-gradient(135deg,#ff9eb5,#ff7a9e);color:#fff;transform:scale(1.05)}
.start-btn{background:linear-gradient(135deg,#77dd77,#4caf50);color:#fff;border:none;border-radius:18px;padding:14px 40px;font-family:inherit;font-weight:700;font-size:20px;cursor:pointer;box-shadow:0 6px 20px rgba(76,175,80,0.4);transition:transform 0.15s;margin-top:8px}
.start-btn:active{transform:scale(0.95)}
.legend{display:grid;grid-template-columns:1fr 1fr;gap:6px 16px;background:rgba(255,255,255,0.15);border-radius:14px;padding:10px 16px;margin:10px 0;font-size:12px;color:#e0f0f8}
.legend-item{display:flex;align-items:center;gap:6px}
.legend-color{width:14px;height:14px;border-radius:50%;flex-shrink:0}
.instructions{color:#c0dde8;font-size:12px;margin-top:8px;text-align:center;line-height:1.6}
.score-summary{background:rgba(255,255,255,0.15);border-radius:14px;padding:16px 24px;margin:10px 0;text-align:center}
.score-summary .big{font-size:42px;font-weight:700;color:#fff}
.score-summary .label{font-size:13px;color:#c0dde8;margin-top:4px}
.new-record{color:#ffd700;font-size:18px;font-weight:700;margin-top:6px;text-shadow:0 0 10px rgba(255,215,0,0.6)}
.shake{animation:shake 0.45s}
@keyframes shake{0%,100%{transform:translate(0,0)}10%{transform:translate(-6px,4px)}20%{transform:translate(6px,-3px)}30%{transform:translate(-5px,-5px)}40%{transform:translate(5px,5px)}50%{transform:translate(-4px,3px)}60%{transform:translate(4px,-2px)}70%{transform:translate(-3px,4px)}80%{transform:translate(3px,-3px)}90%{transform:translate(-2px,2px)}}

/* 测试面板样式 */
#testPanel{position:fixed;top:10px;right:10px;z-index:9999;background:rgba(30,60,80,0.92);border-radius:12px;padding:12px;box-shadow:0 4px 20px rgba(0,0,0,0.3);display:none;min-width:180px}
#testPanel.visible{display:block}
#testPanel h3{color:#fff;font-size:13px;font-weight:700;margin:0 0 8px;text-align:center;border-bottom:1px solid rgba(255,255,255,0.2);padding-bottom:6px}
#testPanel .test-btn{display:block;width:100%;margin:4px 0;padding:8px 12px;background:linear-gradient(135deg,#ff9eb5,#ff7a9e);color:#fff;border:none;border-radius:8px;font-family:inherit;font-weight:600;font-size:12px;cursor:pointer;transition:transform 0.15s}
#testPanel .test-btn:hover{transform:scale(1.03)}
#testPanel .test-btn:active{transform:scale(0.97)}
#testPanel .test-btn.green{background:linear-gradient(135deg,#77dd77,#4caf50)}
#testPanel .test-btn.blue{background:linear-gradient(135deg,#66b3ff,#3a7ab5)}
#testPanel .test-btn.orange{background:linear-gradient(135deg,#ffb366,#ff8c1a)}
#testPanel .test-btn.red{background:linear-gradient(135deg,#ff6666,#cc3333)}
#testPanel .test-btn.purple{background:linear-gradient(135deg,#c77dff,#9d4edd)}
#testPanel .hint{color:rgba(255,255,255,0.5);font-size:10px;margin-top:8px;text-align:center;line-height:1.4}
#testToggle{position:fixed;top:10px;right:10px;z-index:9999;background:rgba(30,60,80,0.8);color:#fff;border:none;border-radius:8px;padding:6px 10px;font-size:11px;cursor:pointer;display:none}
#testToggle.visible{display:block}
</style>
</head>
<body>
<div id="hud">
  <div class="hud-item"><span>得分</span><span class="val" id="score">0</span></div>
  <div class="hud-item"><span>名字</span><span class="val" id="playerName">便便</span></div>
  <div class="hud-item"><span>长度</span><span class="val green" id="length">3</span></div>
  <div class="hud-item"><span>难度</span><span class="val pink" id="difficulty">低级</span></div>
  <div class="hud-item" id="waveItem" style="display:none"><span>波次</span><span class="val blue" id="waveLabel">0</span></div>
  <div class="hud-item" id="comboItem" style="display:none"><span>连击</span><span class="val pink" id="comboLabel">0</span></div>
  <div class="hud-item"><span>最高</span><span class="val gold" id="highScore">0</span></div>
  <div class="hud-item"><span>金币</span><span class="val gold" id="coinsDisplay">0</span></div>
  <div class="hud-item" id="shieldItem" style="display:none"><span>护盾</span><span class="val blue" id="shieldDisplay">0</span></div>
  <button id="pauseBtn">⏸</button>
  <button id="soundBtn" title="声音">🔊</button>
  <button id="shopBtn" title="商店">🛒</button>
</div>
<div id="gameWrap">
  <canvas id="gameCanvas" width="480" height="660"></canvas>
  <div id="statusBadges"></div>
  <div id="startOverlay" class="overlay">
    <div class="mascot">💩</div>
    <h1>便便蛇蛇</h1>
    <p>欢迎来到马桶浴室!吃掉便便,避开厕所和敌人蛇蛇!</p>
    <input type="text" class="name-input" id="nameInput" maxlength="8" value="便便" placeholder="输入你的名字">
        <div style="font-size:12px; font-weight:700; color:var(--blue); margin:8px 0 2px;">控制方式:</div>
        <div class="difficulty-select" id="ctrlSelect">
          <button class="diff-btn active" data-ctrl="dpad">点按</button>
          <button class="diff-btn" data-ctrl="swipe">滑动</button>
        </div>
    <div class="diff-label">选择难度</div>
    <div class="diff-options" id="diffOptions"></div>
    <div class="legend">
      <div class="legend-item"><span class="legend-color" style="background:#8b4513"></span>普通便便 +10分 +1金币</div>
      <div class="legend-item"><span class="legend-color" style="background:#9b59b6"></span>神秘道具 随机效果</div>
      <div class="legend-item"><span class="legend-color" style="background:#6b4226;border-radius:0;width:12px;height:14px"></span>马桶/敌蛇 致命</div>
    </div>
    <div class="instructions">⬅️⬆️⬇️➡️ 方向键 / WASD / 虚拟按键控制<br>空格键暂停 · 移动端使用下方按键</div>
    <button class="start-btn" id="startBtn">开始游戏</button>
    <button class="start-btn" id="shopBtnStart" style="background:linear-gradient(135deg,#ffd700,#daa520);margin-top:10px">🛒 商店</button>
  </div>
  <div id="pauseOverlay" class="overlay hidden">
    <div style="font-size:70px;margin-bottom:10px">⏸️</div>
    <h1>已暂停</h1>
    <p>按空格键或点击继续按钮恢复游戏</p>
    <button class="start-btn" id="resumeBtn">继续游戏</button>
    <button class="start-btn" id="restartBtn" style="background:linear-gradient(135deg,#ff9eb5,#ff7a9e);margin-top:10px">重新开始</button>
  </div>
  <div id="gameOverOverlay" class="overlay hidden">
    <div style="font-size:70px;margin-bottom:10px">💀</div>
    <h2>游戏结束</h2>
    <div class="score-summary">
      <div class="big" id="finalScore">0</div>
      <div class="label">最终得分</div>
      <div class="new-record hidden" id="newRecord">🎉 新纪录!</div>
    </div>
    <div style="color:#ffd700;font-size:16px;font-weight:600;margin-bottom:8px" id="coinsEarned">获得 0 金币</div>
    <p id="gameOverReason"></p>
    <div style="color:#c0dde8;font-size:12px;margin-bottom:12px" id="statsLine"></div>
    <button class="start-btn" id="playAgainBtn">再玩一局</button>
    <button class="start-btn" id="shopBtnGameOver" style="background:linear-gradient(135deg,#ffd700,#daa520);margin-top:10px">🛒 商店</button>
    <button class="start-btn" id="backToMenuBtn" style="background:linear-gradient(135deg,#7ab5d4,#5a8fae);margin-top:10px">返回主菜单</button>
  </div>
  <div id="shopOverlay" class="overlay hidden">
    <div style="font-size:60px;margin-bottom:6px">🛒</div>
    <h2>商店</h2>
    <div style="color:#ffd700;font-size:16px;font-weight:600;margin-bottom:10px" id="shopCoinsDisplay">💰 0</div>
    <div style="display:flex;gap:8px;margin-bottom:12px">
      <button class="diff-btn active" id="shopTabSkins">皮肤</button>
      <button class="diff-btn" id="shopTabUpgrades">升级</button>
    </div>
    <div id="shopSkinsPanel" style="width:100%;max-width:340px"></div>
    <div id="shopUpgradesPanel" style="width:100%;max-width:340px;display:none"></div>
    <button class="start-btn" id="closeShopBtn" style="margin-top:14px">关闭</button>
  </div>
</div>
<div id="mobileControls">
  <div class="dpad-btn dpad-up" data-dir="up">▲</div>
  <div class="dpad-btn dpad-left" data-dir="left">◀</div>
  <div class="dpad-center">💩</div>
  <div class="dpad-btn dpad-right" data-dir="right">▶</div>
  <div class="dpad-btn dpad-down" data-dir="down">▼</div>
</div>

<!-- 测试面板 -->
<button id="testToggle">🧪 测试</button>
<div id="testPanel">
  <h3>🧪 测试控制面板</h3>
  <button class="test-btn orange" onclick="testSpawnWarning()">测试警告 (W键)</button>
  <button class="test-btn red" onclick="testSpawnCountdown()">测试倒计时 (E键)</button>
  <button class="test-btn" onclick="testSpawnFalling()">测试马桶落下 (R键)</button>
  <button class="test-btn blue" onclick="testFullSequence()">完整流程 (T键)</button>
  <button class="test-btn green" onclick="testClearAll()">清空所有 (C键)</button>
  <button class="test-btn purple" onclick="testRandomWave()">随机一波 (Q键)</button>
  <hr style="border:none;border-top:1px solid rgba(255,255,255,0.2);margin:8px 0">
  <div style="color:#fff;font-size:11px;font-weight:600;margin-bottom:6px">难度测试</div>
  <button class="test-btn red" onclick="testDifficulty10()">难度10测试 (D键)</button>
  <button class="test-btn orange" onclick="testSetDifficulty(10)">设难度10</button>
  <button class="test-btn" onclick="testForceWave(10)">强制波(难度10)</button>
  <button class="test-btn blue" onclick="testCheckStatus()">检查状态(S键)</button>
  <div class="hint">
    快捷键:<br>
    W=警告 E=倒计时<br>
    R=落下 T=全流程<br>
    Q=随机波 C=清空<br>
    D=难度10 S=状态<br>
    H=显示/隐藏面板
  </div>
</div>
<script>
(function(){
'use strict';

var COLS = 16;
var ROWS = 22;
var canvas = document.getElementById('gameCanvas');
var ctx = canvas.getContext('2d');
var CELL = canvas.width / COLS;
var LS_KEY = 'neon_snake_best';
var STATE = { MENU:0, PLAYING:1, PAUSED:2, OVER:3 };
var game = null;
var audioCtx = null;

function initAudio(){
  if(!audioCtx){
    try{ audioCtx = new (window.AudioContext||window.webkitAudioContext)(); }catch(e){ audioCtx = null; }
  }
}

function playBeep(freq, dur, type, vol){
  if(!audioCtx || (game && game.soundMuted)) return;
  try{
    var osc = audioCtx.createOscillator();
    var gain = audioCtx.createGain();
    osc.type = type||'sine';
    osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
    gain.gain.setValueAtTime(vol||0.1, audioCtx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + dur);
    osc.connect(gain);
    gain.connect(audioCtx.destination);
    osc.start();
    osc.stop(audioCtx.currentTime + dur);
  }catch(e){}
}
function playEatSound(){ playBeep(600, 0.08, 'square', 0.08); setTimeout(function(){playBeep(800, 0.08, 'square', 0.06);}, 60); }
function playPowerUpSound(){ playBeep(500, 0.1, 'sine', 0.1); setTimeout(function(){playBeep(700, 0.1, 'sine', 0.1);}, 80); setTimeout(function(){playBeep(900, 0.15, 'sine', 0.1);}, 160); }
function playGameOverSound(){ playBeep(300, 0.2, 'sawtooth', 0.12); setTimeout(function(){playBeep(200, 0.3, 'sawtooth', 0.12);}, 150); setTimeout(function(){playBeep(100, 0.5, 'sawtooth', 0.12);}, 350); }

function makeGame(){
  return {
    state: STATE.MENU,
    snake: [],
    dir: {x:1, y:0},
    nextDir: {x:1, y:0},
    score: 0,
    coins: parseInt(localStorage.getItem('poop_snake_coins')||'0',10),
    coinsEarned: 0,
    length: 3,
    speed: 220,
    baseSpeed: 220,
    lastStep: 0,
    food: null,
    specialFoods: [],
    toilets: [],
    warnings: [],
    enemySnakes: [],
    particles: [],
    floatTexts: [],
    shakeTime: 0,
    shakeMag: 0,
    difficulty: 0,
    playerName: '便便',
    controlType: 'dpad',
    highScore: parseInt(localStorage.getItem(LS_KEY)||'0',10),
    invincibleUntil: 0,
    speedBoostUntil: 0,
    stepProgress: 0,
    lastTime: 0,
    specialSpawnTimers: { mystery:0 },
    toiletSpawnTimer: 0,
    toiletSpawnQueue: [],
    enemySpawnTimer: 0,
    enemySpawnInterval: 15000,
    toiletWaveInterval: 5000,
    currentWave: 0,
    tick: 0,
    comboCount: 0,
    lastEatTime: 0,
    frameCount: 0,
    pulseTime: 0,
    elapsedTime: 0,
    flashAlpha: 0,
    flashColor: '',
    flashDuration: 300,
    achievements: [],
    nextMilestone: 50,
    notifications: [],
    notifTimer: 0,
    soundMuted: false,
    upgradeLevel: parseInt(localStorage.getItem('poop_snake_upgrade')||'0',10),
    shieldCharges: 0,
    skinKey: localStorage.getItem('poop_snake_selected_skin') || 'default',
    tips: [
      '吃便便可以加分数哦!',
      '连续吃便便可以获得连击奖励!',
      '金便便是无敌的!',
      '蓝便便会缩短蛇身',
      '红便便可以加速',
      '绿便便会召唤敌蛇',
      '小心马桶!',
      '敌蛇5秒后会消失',
      '空格键可以暂停游戏',
      '最高难度每15秒生成敌蛇'
    ],
    tipTimer: 0
  };
}

var SKINS = {
  default: {name:'经典棕', colors:['#a0693a','#8b5a2b','#6b4226'], price:0},
  fire: {name:'火焰红', colors:['#ff6b35','#e63946','#9d0208'], price:100},
  ice: {name:'冰霜蓝', colors:['#a8dadc','#457b9d','#1d3557'], price:150},
  gold: {name:'黄金蛇', colors:['#ffd700','#daa520','#b8860b'], price:300}
};
var UPGRADE_COSTS = [50, 150, 400, 1000, 2500];

function saveCoins(){
  localStorage.setItem('poop_snake_coins', String(game.coins));
}

function getPurchasedSkins(){
  try {
    var arr = JSON.parse(localStorage.getItem('poop_snake_skins')||'[]');
    return Array.isArray(arr) ? arr : [];
  } catch(e) { return []; }
}

function savePurchasedSkins(arr){
  localStorage.setItem('poop_snake_skins', JSON.stringify(arr));
}

function setSelectedSkin(key){
  localStorage.setItem('poop_snake_selected_skin', key);
}

function saveUpgradeLevel(level){
  localStorage.setItem('poop_snake_upgrade', String(level));
}

function initGame(){
  initAudio();
  game = makeGame();
  game.state = STATE.PLAYING;
  game.playerName = document.getElementById('nameInput').value.trim() || '便便';
  game.controlType = document.getElementById('ctrlSelect').dataset.selected || 'dpad';
  game.shieldCharges = game.upgradeLevel;
  document.getElementById('playerName').textContent = game.playerName;
  game.highScore = parseInt(localStorage.getItem(LS_KEY)||'0',10);
  document.getElementById('highScore').textContent = game.highScore;
  game.snake = [];
  var cx = Math.floor(COLS/2), cy = Math.floor(ROWS/2);
  for(var i=0;i<3;i++){
    game.snake.push({x:cx-i, y:cy, prevX:cx-i, prevY:cy});
  }
  game.dir = {x:1,y:0};
  game.nextDir = {x:1,y:0};
  game.difficulty = parseInt(document.getElementById('diffOptions').dataset.selected||'0',10);
  updateDifficultyUI();
  game.food = spawnFood();
  game.lastTime = performance.now();
  requestAnimationFrame(gameLoop);
}

function updateDifficultyUI(){
  var d = game.difficulty;
  document.getElementById('difficulty').textContent = d;
}

function spawnFood(){
  var pos, tries=0;
  do{
    pos = { x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS) };
    tries++;
    if(tries>300) return pos;
  }while(isOccupied(pos.x,pos.y,false,false));
  return pos;
}

function spawnSpecialFood(type){
  var pos, tries=0;
  do{
    pos = { x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS) };
    tries++;
    if(tries>300) return null;
  }while(isOccupied(pos.x,pos.y,true,false));
  var durations = { mystery: 7000 };
  game.specialFoods.push({ type:type, x:pos.x, y:pos.y, spawnTime:performance.now(), duration:durations[type] });
}

function isOccupied(x,y,includeSpecials,includeEnemies){
  var i;
  for(i=0;i<game.snake.length;i++) if(game.snake[i].x===x && game.snake[i].y===y) return true;
  if(game.food && game.food.x===x && game.food.y===y) return true;
  for(i=0;i<game.toilets.length;i++) if(game.toilets[i].x===x && game.toilets[i].y===y) return true;
  if(includeSpecials){
    for(i=0;i<game.specialFoods.length;i++) if(game.specialFoods[i].x===x && game.specialFoods[i].y===y) return true;
  }
  if(includeEnemies){
    for(var e=0;e<game.enemySnakes.length;e++){
      var es = game.enemySnakes[e];
      for(var s=0;s<es.segments.length;s++){
        if(es.segments[s].x===x && es.segments[s].y===y) return true;
      }
    }
  }
  return false;
}

function spawnToilet(now){
  var d = game.difficulty;
  if(d<1) return;
  var perWave = d * 2;
  // 将这一波的马桶随机分散在接下来的5秒内生成
  for(var i=0;i<perWave;i++){
    var delay = Math.random() * game.toiletWaveInterval;
    game.toiletSpawnQueue.push({ spawnAt: now + delay });
  }
}

function isOnFood(x, y){
  if(game.food && game.food.x===x && game.food.y===y) return true;
  for(var i=0;i<game.specialFoods.length;i++){
    if(game.specialFoods[i].x===x && game.specialFoods[i].y===y) return true;
  }
  return false;
}

function spawnSingleToilet(){
  var pos = null;
  var tries = 0;
  while(tries < 200){
    pos = { x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS) };
    tries++;
    if(!isOccupied(pos.x,pos.y,true,true) && !isTooCloseToSnake(pos) && !isWarningAt(pos.x,pos.y) && !isOnFood(pos.x,pos.y)){
      break;
    }
    pos = null;
  }
  if(pos){
    game.warnings.push({
      x: pos.x,
      y: pos.y,
      state: 'warn',
      stateTime: 0,
      warnDuration: 1000,
      countDuration: 2000,
      fallDuration: 400,
      fallY: -1,
    });
  }
}

function isWarningAt(x,y){
  for(var i=0;i<game.warnings.length;i++){
    if(game.warnings[i].x===x && game.warnings[i].y===y) return true;
  }
  return false;
}

function isTooCloseToSnake(pos){
  // 检查蛇头周围4格(避免生成在蛇头前方导致来不及躲避)
  var head = game.snake[0];
  if(Math.abs(head.x-pos.x)<4 && Math.abs(head.y-pos.y)<4) return true;
  // 检查整条蛇身(避免马桶生成在蛇身上)
  for(var i=1;i<game.snake.length;i++){
    if(Math.abs(game.snake[i].x-pos.x)<2 && Math.abs(game.snake[i].y-pos.y)<2) return true;
  }
  return false;
}

function spawnEnemySnake(now){
  var len = 2 + Math.floor(Math.random()*3);
  var edge = Math.floor(Math.random()*4);
  var startX, startY;
  if(edge===0){ startX=Math.floor(Math.random()*COLS); startY=0; }
  else if(edge===1){ startX=COLS-1; startY=Math.floor(Math.random()*ROWS); }
  else if(edge===2){ startX=Math.floor(Math.random()*COLS); startY=ROWS-1; }
  else{ startX=0; startY=Math.floor(Math.random()*ROWS); }
  var dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
  var dir = dirs[Math.floor(Math.random()*4)];
  var segments = [];
  for(var i=0;i<len;i++){
    segments.push({ x:startX - dir.x*i, y:startY - dir.y*i });
  }
  game.enemySnakes.push({
    segments: segments,
    dir: dir,
    moveTimer: 0,
    lifeTime: 5000,
    spawnTime: now,
    moveInterval: 450,
    prevSegments: segments.map(function(s){return {x:s.x,y:s.y};})
  });
}

function spawnEnemySnakeAt(x,y,now){
  var len = 2 + Math.floor(Math.random()*3);
  var dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
  var dir = dirs[Math.floor(Math.random()*4)];
  var segments = [];
  for(var i=0;i<len;i++){
    var sx = Math.max(0, Math.min(COLS-1, x - dir.x*i));
    var sy = Math.max(0, Math.min(ROWS-1, y - dir.y*i));
    segments.push({ x:sx, y:sy });
  }
  game.enemySnakes.push({
    segments: segments,
    dir: dir,
    moveTimer: 0,
    lifeTime: 5000,
    spawnTime: now,
    moveInterval: 450,
    prevSegments: segments.map(function(s){return {x:s.x,y:s.y};})
  });
}

function gameLoop(now){
  if(!game || game.state===STATE.OVER) return;
  if(game.state===STATE.PAUSED){
    game.lastTime = now;
    requestAnimationFrame(gameLoop);
    return;
  }
  var dt = now - game.lastTime;
  game.lastTime = now;
  update(dt, now);
  render(now);
  requestAnimationFrame(gameLoop);
}

function update(dt, now){
  if(game.state!==STATE.PLAYING) return;
  game.tick++;
  game.frameCount++;
  game.elapsedTime += dt;

  game.specialSpawnTimers.mystery += dt;

  if(game.specialSpawnTimers.mystery >= 8000){
    game.specialSpawnTimers.mystery = 0;
    if(game.specialFoods.length < 3)
      spawnSpecialFood('mystery');
  }

  for(var i=game.specialFoods.length-1;i>=0;i--){
    if(now - game.specialFoods[i].spawnTime > game.specialFoods[i].duration){
      game.specialFoods.splice(i,1);
    }
  }

  if(game.difficulty>0){
    game.toiletSpawnTimer += dt;
    if(game.toiletSpawnTimer >= game.toiletWaveInterval){
      game.toiletSpawnTimer = 0;
      spawnToilet(now);
      game.currentWave++;
      flashScreen('rgba(100,150,200,0.15)', 300);
      spawnFloatText(8, 1, '第' + game.currentWave + '波来袭!', '#3a7ab5');
    }

    // 检查生成队列:到了时间就生成单个马桶(随机分散掉落)
    for(var qi=game.toiletSpawnQueue.length-1; qi>=0; qi--){
      if(now >= game.toiletSpawnQueue[qi].spawnAt){
        spawnSingleToilet();
        game.toiletSpawnQueue.splice(qi, 1);
      }
    }
    
    for(var wi = game.warnings.length-1; wi>=0; wi--){
      var w = game.warnings[wi];
      w.stateTime += dt;
      
      if(w.state === 'warn'){
        if(w.stateTime >= w.warnDuration){
          w.state = 'countdown';
          w.stateTime = 0;
        }
      } else if(w.state === 'countdown'){
        if(w.stateTime >= w.countDuration){
          w.state = 'falling';
          w.stateTime = 0;
        }
      } else if(w.state === 'falling'){
        // 落下动画:从屏幕顶部掉到目标位置
        var t = Math.min(w.stateTime / w.fallDuration, 1);
        var targetY = w.y * CELL + CELL/2;
        var startY = -CELL * 2;
        w.fallY = startY + (targetY - startY) * t;
        
        if(t >= 1){
          // 落下完成,检查蛇身是否在该位置(整条蛇,不只是头部)
          var hitSnake = false;
          for(var si=0; si<game.snake.length; si++){
            if(game.snake[si].x === w.x && game.snake[si].y === w.y){
              hitSnake = true;
              break;
            }
          }
          if(hitSnake){
            // 蛇身在该位置,马桶消失(避免不公平死亡)
            game.warnings.splice(wi, 1);
            spawnFloatText(w.x, w.y, '安全!', '#77dd77');
            spawnParticles(w.x, w.y, '#77dd77');
          } else {
            // 正式成为马桶障碍(10秒后自动消失)
            var expireTime = now + 10000;
            game.toilets.push({x: w.x, y: w.y, spawnTime: now, landTime: now, expireTime: expireTime});
            game.warnings.splice(wi, 1);
            spawnParticles(w.x, w.y, '#ff6b6b');
            flashScreen('rgba(255,100,100,0.15)', 200);
          }
        }
      }
    }
    
    game.enemySpawnTimer += dt;
    if(game.enemySpawnTimer >= game.enemySpawnInterval){
      game.enemySpawnTimer = 0;
      spawnEnemySnake(now);
    }
    
    // 马桶过期检查:10秒后自动消失
    for(var ti = game.toilets.length-1; ti >= 0; ti--){
      if(game.toilets[ti].expireTime && now >= game.toilets[ti].expireTime){
        var expired = game.toilets[ti];
        spawnParticles(expired.x, expired.y, '#a8b8c4');
        game.toilets.splice(ti, 1);
      }
    }
  }

  if(game.comboCount > 0 && now - game.lastEatTime > 3000){
    game.comboCount = 0;
  }

  if(game.tipTimer === undefined) game.tipTimer = 0;
  game.tipTimer += dt;
  if(game.tipTimer > 15000 && game.tips && game.tips.length > 0){
    game.tipTimer = 0;
    var tip = game.tips[Math.floor(Math.random()*game.tips.length)];
    spawnFloatText(8, 10, tip, '#5a7a8a');
  }

  var speedMul = now < game.speedBoostUntil ? 1.35 : 1;
  var currentSpeed = game.speed / speedMul;

  game.lastStep += dt;
  game.stepProgress = Math.min(game.lastStep / currentSpeed, 1);

  while(game.lastStep >= currentSpeed){
    game.lastStep -= currentSpeed;
    game.stepProgress = 0;
    stepSnake(now);
    if(game.state===STATE.OVER) return;
  }

  for(var i=game.enemySnakes.length-1;i>=0;i--){
    var es = game.enemySnakes[i];
    if(now - es.spawnTime > es.lifeTime){
      game.enemySnakes.splice(i,1);
      continue;
    }
    es.moveTimer += dt;
    if(es.moveTimer >= es.moveInterval){
      es.moveTimer = 0;
      moveEnemySnake(es, now);
      if(game.state===STATE.OVER) return;
    }
  }

  for(var i=game.particles.length-1;i>=0;i--){
    var p = game.particles[i];
    p.life -= dt;
    p.x += p.vx * dt/1000;
    p.y += p.vy * dt/1000;
    p.vy += 300 * dt/1000;
    p.vx *= 0.98;
    if(p.life <= 0) game.particles.splice(i,1);
  }

  for(var i=game.floatTexts.length-1;i>=0;i--){
    var f = game.floatTexts[i];
    f.life -= dt;
    f.y += f.vy * dt/1000;
    if(f.life <= 0) game.floatTexts.splice(i,1);
  }

  for(var i=game.notifications.length-1;i>=0;i--){
    game.notifications[i].life -= dt;
    if(game.notifications[i].life <= 0){
      game.notifications.splice(i,1);
    }
  }

  if(game.shakeTime > 0){
    game.shakeTime -= dt;
    if(game.shakeTime <= 0) game.shakeMag = 0;
  }

  if(game.flashAlpha > 0){
    game.flashAlpha -= dt / (game.flashDuration || 300);
    if(game.flashAlpha < 0) game.flashAlpha = 0;
  }

  if(game.pulseTime > 0){
    game.pulseTime -= dt;
  }

  if(game.elapsedTime > 60000 && game.achievements.indexOf('survivor') < 0){
    onAchievement('survivor');
  }
  if(now < game.invincibleUntil && game.achievements.indexOf('invincible') < 0){
    onAchievement('invincible');
  }
  if(now < game.speedBoostUntil && game.achievements.indexOf('speed') < 0){
    onAchievement('speed');
  }

  updateBadges(now);
}

function stepSnake(now){
  game.dir = game.nextDir;

  for(var i=0;i<game.snake.length;i++){
    game.snake[i].prevX = game.snake[i].x;
    game.snake[i].prevY = game.snake[i].y;
  }

  var head = game.snake[0];
  var newX = head.x + game.dir.x;
  var newY = head.y + game.dir.y;

  // 四周穿墙:从一边出去从对面进来
  if(newX < 0) newX = COLS - 1;
  else if(newX >= COLS) newX = 0;
  if(newY < 0) newY = ROWS - 1;
  else if(newY >= ROWS) newY = 0;

  var invincible = now < game.invincibleUntil;

  if(invincible){
    // 无敌时:碰马桶/敌蛇自动掉头
    for(var t=0;t<game.toilets.length;t++){
      if(game.toilets[t].x===newX && game.toilets[t].y===newY){
        game.dir = {x: -game.dir.x, y: -game.dir.y};
        game.nextDir = game.dir;
        spawnFloatText(newX, newY, '自动掉头!', '#ffd700');
        return;
      }
    }
    for(var e=0;e<game.enemySnakes.length;e++){
      var es = game.enemySnakes[e];
      for(var s=0;s<es.segments.length;s++){
        if(es.segments[s].x===newX && es.segments[s].y===newY){
          game.dir = {x: -game.dir.x, y: -game.dir.y};
          game.nextDir = game.dir;
          spawnFloatText(newX, newY, '自动掉头!', '#ffd700');
          return;
        }
      }
    }
  } else {
    // 普通模式:碰马桶/敌蛇判负(有护盾则掉头)
    for(var t=0;t<game.toilets.length;t++){
      if(game.toilets[t].x===newX && game.toilets[t].y===newY){
        if(game.shieldCharges > 0){
          game.shieldCharges--;
          game.dir = {x: -game.dir.x, y: -game.dir.y};
          game.nextDir = game.dir;
          spawnFloatText(newX, newY, '护盾抵挡!', '#66b3ff');
          return;
        }
        triggerGameOver('掉进马桶了!');
        return;
      }
    }
    for(var e=0;e<game.enemySnakes.length;e++){
      var es = game.enemySnakes[e];
      for(var s=0;s<es.segments.length;s++){
        if(es.segments[s].x===newX && es.segments[s].y===newY){
          if(game.shieldCharges > 0){
            game.shieldCharges--;
            game.dir = {x: -game.dir.x, y: -game.dir.y};
            game.nextDir = game.dir;
            spawnFloatText(newX, newY, '护盾抵挡!', '#66b3ff');
            return;
          }
          triggerGameOver('被敌人蛇蛇咬到了!');
          return;
        }
      }
    }
  }

  game.snake.unshift({x:newX, y:newY, prevX:head.x, prevY:head.y});

  var grew = false;

  if(game.food && game.food.x===newX && game.food.y===newY){
    game.comboCount++;
    game.lastEatTime = now;
    var comboBonus = Math.min(game.comboCount, 5);
    var points = 10 + (comboBonus-1)*2;
    game.score += points;
    game.coins += 1;
    game.coinsEarned += 1;
    game.length++;
    game.speed = Math.max(80, game.baseSpeed - Math.floor(game.length*0.8));
    game.baseSpeed = game.speed;
    spawnParticles(newX, newY, '#8b4513');
    spawnFloatText(newX, newY, '+' + points + (game.comboCount>1?' x'+game.comboCount:''), '#8b4513');
    game.food = spawnFood();
    grew = true;
    // 吃大便消除马桶(难度越高消除越多)
    var removeCount = 1 + Math.floor(game.difficulty / 2);
    for(var ri = 0; ri < removeCount && game.toilets.length > 0; ri++){
      var removed = game.toilets.shift();
      spawnParticles(removed.x, removed.y, '#c0392b');
    }
    if(removeCount > 1 && game.toilets.length >= removeCount - 1){
      spawnFloatText(newX, newY - CELL, '清除 ' + removeCount + ' 个马桶!', '#c0392b');
    }
    playEatSound();
    game.pulseTime = 200;
    if(game.score >= game.nextMilestone){
      onAchievement('score');
      game.nextMilestone += 50;
    }
    if(game.length === 10){ onAchievement('length10'); }
    if(game.length === 25){ onAchievement('length25'); }
    if(game.length === 50){ onAchievement('length50'); }
  }

  for(var i=game.specialFoods.length-1;i>=0;i--){
    var sf = game.specialFoods[i];
    if(sf.x===newX && sf.y===newY){
      applySpecial(sf.type, now);
      spawnParticles(newX, newY, specialColor(sf.type));
      spawnFloatText(newX, newY, specialLabel(sf.type), specialColor(sf.type));
      game.specialFoods.splice(i,1);
      // 注意:特殊道具不增加蛇长,所以不设置 grew
      playPowerUpSound();
      game.flashAlpha = 0.3;
      game.flashColor = specialColor(sf.type);
      break;
    }
  }

  if(!grew){
    game.snake.pop();
  }

  updateHUD();
}

function triggerGameOver(reason){
  if(game.state===STATE.OVER) return;
  game.state = STATE.OVER;
  saveCoins();
  game.shakeMag = 18;
  game.shakeTime = 450;
  game.flashAlpha = 0.5;
  game.flashColor = '#ff3333';
  playGameOverSound();
  var isNew = game.score > game.highScore;
  if(isNew){
    game.highScore = game.score;
    localStorage.setItem(LS_KEY, String(game.highScore));
  }
  document.getElementById('gameOverReason').textContent = reason || '';
  document.getElementById('finalScore').textContent = game.score;
  document.getElementById('coinsEarned').textContent = '获得 ' + game.coinsEarned + ' 金币';
  document.getElementById('newRecord').classList.toggle('hidden', !isNew);
  if(isNew){
    celebrateNewRecord();
  }
  document.getElementById('highScore').textContent = game.highScore;
  var statsParts = [];
  statsParts.push('长度: ' + game.length);
  statsParts.push('难度: ' + game.difficulty);
  statsParts.push('存活: ' + Math.floor(game.elapsedTime/1000) + '秒');
  var achText = '';
  if(game.achievements.length > 0){
    var icons = game.achievements.map(function(a){ return ACHIEVEMENTS[a] ? ACHIEVEMENTS[a].icon : ''; });
    achText = ' 成就: ' + icons.join(' ');
  }
  document.getElementById('statsLine').textContent = statsParts.join(' · ') + achText;
  setTimeout(function(){
    document.getElementById('gameOverOverlay').classList.remove('hidden');
  }, 600);
}

function applySpecial(type, now){
  if(type==='mystery'){
    var effects = ['speed', 'shorten', 'enemy', 'invincible'];
    var effect = effects[Math.floor(Math.random()*effects.length)];
    if(effect==='speed'){
      game.speedBoostUntil = now + 10000;
      game.lastEffect = 'speed';
    } else if(effect==='shorten'){
      for(var i=0;i<3 && game.snake.length>3;i++){
        game.snake.pop();
        game.length--;
      }
      game.lastEffect = 'shorten';
    } else if(effect==='enemy'){
      var head = game.snake[0];
      var offsetDirs = [{x:0,y:-3},{x:0,y:3},{x:3,y:0},{x:-3,y:0}];
      var od = offsetDirs[Math.floor(Math.random()*4)];
      spawnEnemySnakeAt(head.x + od.x, head.y + od.y, now);
      game.lastEffect = 'enemy';
    } else if(effect==='invincible'){
      game.invincibleUntil = now + 7000;
      game.lastEffect = 'invincible';
    }
  }
}

function flashScreen(color, duration){
  game.flashColor = color;
  game.flashAlpha = 1;
  game.flashDuration = duration || 300;
}

function celebrateNewRecord(){
  for(var i=0;i<40;i++){
    setTimeout(function(idx){
      var colors = ['#ffd700','#ff6666','#66b3ff','#77dd77','#ff9eb5'];
      var color = colors[idx % colors.length];
      spawnParticles(
        Math.floor(Math.random()*COLS),
        Math.floor(Math.random()*ROWS),
        color
      );
    }, i * 50, i);
  }
  for(var k=0;k<8;k++){
    setTimeout(function(c){
      playBeep(600 + c*200, 0.15, 'sine', 0.1);
    }, k * 120, k);
  }
}

function shakeScreen(mag, duration){
  game.shakeMag = mag || 8;
  game.shakeTime = duration || 300;
}

function specialColor(type){
  return { mystery:'#9b59b6' }[type] || '#9b59b6';
}

function specialLabel(type){
  return { mystery:'神秘!' }[type] || '神秘!';
}

var ACHIEVEMENTS = {
  score:       { icon:'🏆', title:'分数里程碑!', desc:'得分达到 ' },
  length10:    { icon:'📏', title:'小蛇成形!', desc:'蛇身长度达到 10' },
  length25:    { icon:'🐍', title:'蛇蛇蛇!', desc:'蛇身长度达到 25' },
  length50:    { icon:'🐲', title:'巨龙降临!', desc:'蛇身长度达到 50' },
  invincible:  { icon:'⭐', title:'无敌状态!', desc:'黄金便便加持中' },
  speed:       { icon:'⚡', title:'极速冲刺!', desc:'加速效果启动' },
  survivor:    { icon:'🛡️', title:'生存专家!', desc:'存活超过 60 秒' }
};

function onAchievement(type){
  if(game.achievements.indexOf(type) >= 0 && type!=='score') return;
  game.achievements.push(type);
  var ach = ACHIEVEMENTS[type];
  var msg = ach.desc;
  if(type==='score') msg += game.score + '!';
  game.notifications.push({ icon:ach.icon, title:ach.title, desc:msg, life:3000, maxLife:3000 });
  game.notifTimer = 3000;
  var head = game.snake[0];
  spawnFloatText(head.x, head.y, ach.icon + ' ' + ach.title, '#ffd700');
  playBeep(1200, 0.2, 'sine', 0.12);
  setTimeout(function(){ playBeep(1600, 0.15, 'sine', 0.1); }, 100);
}

function drawNotifications(){
  var startY = canvas.height * 0.15;
  for(var i=0;i<game.notifications.length;i++){
    var n = game.notifications[i];
    var alpha = Math.min(1, n.life / 500);
    var slideY = (1 - n.life/n.maxLife) * -30;
    ctx.globalAlpha = alpha;
    var boxW = 220;
    var boxH = 56;
    var boxX = (canvas.width - boxW)/2;
    var boxY = startY + i * (boxH + 8) + slideY;
    ctx.fillStyle = 'rgba(30,60,80,0.92)';
    roundRect(ctx, boxX, boxY, boxW, boxH, 12);
    ctx.fill();
    ctx.strokeStyle = '#ffd700';
    ctx.lineWidth = 2;
    ctx.stroke();
    ctx.fillStyle = '#fff';
    ctx.font = '24px sans-serif';
    ctx.textAlign = 'left';
    ctx.textBaseline = 'middle';
    ctx.fillText(n.icon, boxX + 14, boxY + boxH/2);
    ctx.font = 'bold 14px Fredoka, sans-serif';
    ctx.fillStyle = '#ffd700';
    ctx.fillText(n.title, boxX + 46, boxY + boxH/2 - 8);
    ctx.font = '11px Fredoka, sans-serif';
    ctx.fillStyle = '#d0e8f2';
    ctx.fillText(n.desc, boxX + 46, boxY + boxH/2 + 10);
  }
  ctx.globalAlpha = 1;
}

function moveEnemySnake(es, now){
  if(!es.segments || es.segments.length===0) return;

  for(var i=0;i<es.segments.length;i++){
    es.prevSegments[i] = { x: es.segments[i].x, y: es.segments[i].y };
  }

  var head = es.segments[0];
  var nx = head.x + es.dir.x;
  var ny = head.y + es.dir.y;

  var blocked = false;
  if(nx<0 || nx>=COLS || ny<0 || ny>=ROWS) blocked = true;
  if(!blocked){
    for(var t=0;t<game.toilets.length;t++){
      if(game.toilets[t].x===nx && game.toilets[t].y===ny){ blocked=true; break; }
    }
  }
  if(!blocked){
    for(var s=0;s<es.segments.length;s++){
      if(es.segments[s].x===nx && es.segments[s].y===ny){ blocked=true; break; }
    }
  }

  if(blocked){
    var dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
    var options = dirs.filter(function(d){return !(d.x===-es.dir.x && d.y===-es.dir.y);});
    options = options.filter(function(d){
      var tx = head.x+d.x, ty = head.y+d.y;
      if(tx<0||tx>=COLS||ty<0||ty>=ROWS) return false;
      for(var t=0;t<game.toilets.length;t++) if(game.toilets[t].x===tx&&game.toilets[t].y===ty) return false;
      return true;
    });
    if(options.length===0){
      es.segments = [];
      return;
    }
    es.dir = options[Math.floor(Math.random()*options.length)];
    nx = head.x + es.dir.x;
    ny = head.y + es.dir.y;
  }

  es.segments.unshift({x:nx, y:ny});
  es.segments.pop();

  if(game.snake.length>0){
    var playerHead = game.snake[0];
    for(var s=0;s<es.segments.length;s++){
      var seg = es.segments[s];
      if(seg.x===playerHead.x && seg.y===playerHead.y){
        es.segments = [];
        spawnParticles(playerHead.x, playerHead.y, '#8b5a2b');
        spawnFloatText(playerHead.x, playerHead.y, '敌蛇消失!', '#8b5a2b');
        return;
      }
    }
  }
}

function spawnParticles(gx, gy, color){
  var cx = gx*CELL + CELL/2;
  var cy = gy*CELL + CELL/2;
  for(var i=0;i<16;i++){
    var angle = (Math.PI*2) * (i/16) + Math.random()*0.5;
    var speed = 80 + Math.random()*150;
    game.particles.push({
      x:cx, y:cy,
      vx: Math.cos(angle)*speed,
      vy: Math.sin(angle)*speed - 80,
      life: 500 + Math.random()*400,
      maxLife: 900,
      color: color,
      size: 3 + Math.random()*5
    });
  }
  for(var i=0;i<6;i++){
    game.particles.push({
      x: cx + (Math.random()-0.5)*10,
      y: cy + (Math.random()-0.5)*10,
      vx: (Math.random()-0.5)*40,
      vy: -40 - Math.random()*60,
      life: 800 + Math.random()*400,
      maxLife: 1200,
      color: '#fff8dc',
      size: 1 + Math.random()*2,
      sparkle: true
    });
  }
}

function spawnFloatText(gx, gy, text, color){
  game.floatTexts.push({
    x: gx*CELL + CELL/2,
    y: gy*CELL + CELL/2,
    vy: -60,
    life: 1000,
    text: text,
    color: color,
    size: 16
  });
}

function updateHUD(){
  document.getElementById('score').textContent = game.score;
  document.getElementById('length').textContent = game.length;
  document.getElementById('difficulty').textContent = game.difficulty;
  document.getElementById('highScore').textContent = game.highScore;
  document.getElementById('coinsDisplay').textContent = game.coins;
  var shieldItem = document.getElementById('shieldItem');
  if(game.shieldCharges > 0){
    shieldItem.style.display = 'flex';
    document.getElementById('shieldDisplay').textContent = game.shieldCharges;
  } else {
    shieldItem.style.display = 'none';
  }
  var waveItem = document.getElementById('waveItem');
  if(game.difficulty > 0 && game.currentWave > 0){
    waveItem.style.display = 'flex';
    document.getElementById('waveLabel').textContent = game.currentWave;
  } else {
    waveItem.style.display = 'none';
  }
  var comboItem = document.getElementById('comboItem');
  if(game.comboCount > 1){
    comboItem.style.display = 'flex';
    document.getElementById('comboLabel').textContent = game.comboCount;
  } else {
    comboItem.style.display = 'none';
  }
}

function updateBadges(now){
  var badges = document.getElementById('statusBadges');
  badges.innerHTML = '';
  if(now < game.invincibleUntil){
    var remain = Math.ceil((game.invincibleUntil - now)/1000);
    var b = document.createElement('div');
    b.className = 'badge invincible';
    b.innerHTML = '⭐ 无敌 ' + remain + 's';
    badges.appendChild(b);
  }
  if(now < game.speedBoostUntil){
    var remain2 = Math.ceil((game.speedBoostUntil - now)/1000);
    var b2 = document.createElement('div');
    b2.className = 'badge speed';
    b2.innerHTML = '⚡ 加速 ' + remain2 + 's';
    badges.appendChild(b2);
  }
  if(game.comboCount >= 2){
    var b3 = document.createElement('div');
    b3.className = 'badge combo';
    b3.innerHTML = '🔥 连击 x' + game.comboCount;
    badges.appendChild(b3);
  }
}

function render(now){
  ctx.clearRect(0,0,canvas.width,canvas.height);
  var sx=0, sy=0;
  if(game.shakeTime > 0){
    sx = (Math.random()-0.5)*game.shakeMag;
    sy = (Math.random()-0.5)*game.shakeMag;
  }
  ctx.save();
  ctx.translate(sx, sy);
  drawTileBackground();
  drawWarnings(now);
  drawToilets(now);
  var t = game.stepProgress;
  drawSnake(t, now);
  drawFood(now);
  drawSpecialFoods(now);
  drawEnemySnakes(now);
  if(game.comboCount >= 2){
    ctx.save();
    var comboAlpha = 0.3 + 0.3*Math.sin(now*0.01);
    ctx.globalAlpha = comboAlpha;
    ctx.fillStyle = '#ff9eb5';
    ctx.font = 'bold 16px Fredoka, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'top';
    var comboStr = 'x' + game.comboCount + ' 连击!';
    ctx.fillText(comboStr, canvas.width/2, 80);
    ctx.globalAlpha = comboAlpha * 0.5;
    ctx.fillStyle = '#fff';
    ctx.fillText(comboStr, canvas.width/2 + 2, 82);
    ctx.restore();
  }

  if(now < game.speedBoostUntil){
    var speedPulse = 0.3 + 0.2*Math.sin(now*0.02);
    ctx.save();
    ctx.globalAlpha = speedPulse;
    ctx.fillStyle = '#ff6666';
    ctx.fillRect(0,0,canvas.width,4);
    ctx.fillRect(0,canvas.height-4,canvas.width,4);
    ctx.restore();
  }

  drawParticles();
  drawFloatTexts();
  drawNotifications();
  if(game.flashAlpha > 0){
    ctx.fillStyle = game.flashColor;
    ctx.globalAlpha = game.flashAlpha;
    ctx.fillRect(0,0,canvas.width,canvas.height);
    ctx.globalAlpha = 1;
  }
  var vignetteAlpha = 0;
  if(game.snake.length > 50) vignetteAlpha = Math.min(0.4, (game.snake.length-50)*0.02);
  if(game.enemySnakes.length > 0) vignetteAlpha = Math.max(vignetteAlpha, 0.15);
  if(vignetteAlpha > 0){
    var vg = ctx.createRadialGradient(canvas.width/2, canvas.height/2, canvas.width*0.2, canvas.width/2, canvas.height/2, canvas.width*0.75);
    vg.addColorStop(0, 'rgba(255,0,0,0)');
    vg.addColorStop(1, 'rgba(180,0,0,' + vignetteAlpha + ')');
    ctx.fillStyle = vg;
    ctx.fillRect(0,0,canvas.width,canvas.height);
  }
  ctx.restore();
}

function drawTileBackground(){
  var wallGrad = ctx.createLinearGradient(0,0,0,canvas.height);
  wallGrad.addColorStop(0, '#e8f0f5');
  wallGrad.addColorStop(0.3, '#dde8f0');
  wallGrad.addColorStop(1, '#c5d5e0');
  ctx.fillStyle = wallGrad;
  ctx.fillRect(0,0,canvas.width,canvas.height);
  
  for(var y=0;y<ROWS;y++){
    for(var x=0;x<COLS;x++){
      var px = x*CELL, py = y*CELL;
      var isAlt = (x+y)%2===0;
      var tileBase = isAlt ? '#e8f0f5' : '#dce6ed';
      ctx.fillStyle = tileBase;
      ctx.fillRect(px, py, CELL, CELL);
      
      ctx.save();
      ctx.globalAlpha = 0.12;
      ctx.strokeStyle = '#8a9bab';
      ctx.lineWidth = 1;
      var midX = px + CELL/2, midY = py + CELL/2;
      ctx.beginPath();
      ctx.moveTo(midX - CELL*0.25, midY);
      ctx.lineTo(midX + CELL*0.25, midY);
      ctx.moveTo(midX, midY - CELL*0.25);
      ctx.lineTo(midX, midY + CELL*0.25);
      ctx.stroke();
      ctx.fillStyle = '#8a9bab';
      ctx.beginPath();
      ctx.arc(midX, midY, 1.5, 0, Math.PI*2);
      ctx.fill();
      ctx.restore();
      
      ctx.strokeStyle = 'rgba(130,150,165,0.4)';
      ctx.lineWidth = 1;
      ctx.strokeRect(px+0.5, py+0.5, CELL-1, CELL-1);
    }
  }
  
  var wallH = canvas.height * 0.08;
  ctx.fillStyle = 'rgba(200,180,160,0.25)';
  ctx.fillRect(0, 0, canvas.width, wallH);
  
  var reflectGrad = ctx.createLinearGradient(0, canvas.height*0.6, 0, canvas.height);
  reflectGrad.addColorStop(0, 'rgba(255,255,255,0)');
  reflectGrad.addColorStop(0.5, 'rgba(255,255,255,0.08)');
  reflectGrad.addColorStop(1, 'rgba(180,200,215,0.2)');
  ctx.fillStyle = reflectGrad;
  ctx.fillRect(0, canvas.height*0.6, canvas.width, canvas.height*0.4);
  
  ctx.save();
  for(var i = 0; i < 8; i++){
    var hx = ((i * 73 + 19) % 100) / 100;
    var hy = ((i * 97 + 41) % 100) / 100;
    var sx = hx * canvas.width;
    var sy = hy * canvas.height;
    ctx.globalAlpha = 0.06;
    ctx.fillStyle = '#7a8a95';
    ctx.beginPath();
    ctx.arc(sx, sy, 6 + (i%3)*2, 0, Math.PI*2);
    ctx.fill();
  }
  ctx.restore();
  
  var topGlow = ctx.createRadialGradient(canvas.width/2, 0, 0, canvas.width/2, 0, canvas.width*0.8);
  topGlow.addColorStop(0, 'rgba(255,255,255,0.4)');
  topGlow.addColorStop(1, 'rgba(255,255,255,0)');
  ctx.fillStyle = topGlow;
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function drawWarnings(now){
  for(var i = 0; i < game.warnings.length; i++){
    var w = game.warnings[i];
    var cx = w.x * CELL + CELL/2;
    var s = CELL * 0.45;
    
    if(w.state === 'warn'){
      // 橙色闪烁警告
      var pulse = 0.5 + 0.5 * Math.sin(now/80);
      var alpha = 0.4 + 0.4 * pulse;
      ctx.save();
      ctx.globalAlpha = alpha;
      
      // 外圈
      ctx.strokeStyle = '#ffa500';
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.arc(cx, w.y*CELL + CELL/2, s*1.1, 0, Math.PI*2);
      ctx.stroke();
      
      // 内圈填充
      ctx.globalAlpha = alpha * 0.5;
      ctx.fillStyle = 'rgba(255,165,0,0.2)';
      ctx.beginPath();
      ctx.arc(cx, w.y*CELL + CELL/2, s*0.9, 0, Math.PI*2);
      ctx.fill();
      
      // "!" 标记
      ctx.globalAlpha = 1;
      ctx.fillStyle = '#ff8c00';
      ctx.font = 'bold ' + Math.round(CELL*0.55) + 'px sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText('!', cx, w.y*CELL + CELL/2);
      ctx.restore();
      
    } else if(w.state === 'countdown'){
      // 红色倒计时
      var pulse = 0.5 + 0.5 * Math.sin(now/60);
      var alpha = 0.5 + 0.5 * pulse;
      var elapsed = w.stateTime;
      var remaining = Math.max(1, Math.ceil(3 - elapsed/1000));
      
      ctx.save();
      ctx.globalAlpha = alpha;
      
      // 外圈闪烁
      ctx.strokeStyle = '#ff3b3b';
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.arc(cx, w.y*CELL + CELL/2, s*1.15, 0, Math.PI*2);
      ctx.stroke();
      
      // 内圈
      ctx.globalAlpha = alpha * 0.7;
      ctx.fillStyle = 'rgba(255,59,59,0.25)';
      ctx.beginPath();
      ctx.arc(cx, w.y*CELL + CELL/2, s*0.95, 0, Math.PI*2);
      ctx.fill();
      
      // 冲击波扩散
      var waveProgress = (elapsed % 1000) / 1000;
      ctx.globalAlpha = (1 - waveProgress) * 0.6;
      ctx.strokeStyle = '#ff3b3b';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(cx, w.y*CELL + CELL/2, s*0.5 + waveProgress * s * 1.5, 0, Math.PI*2);
      ctx.stroke();
      
      // 倒计时数字
      ctx.globalAlpha = 1;
      ctx.fillStyle = '#ff1a1a';
      ctx.strokeStyle = '#fff';
      ctx.lineWidth = 3;
      ctx.font = 'bold ' + Math.round(CELL*0.65) + 'px sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      var cy = w.y*CELL + CELL/2;
      ctx.strokeText(remaining, cx, cy);
      ctx.fillText(remaining, cx, cy);
      ctx.restore();
      
    } else if(w.state === 'falling'){
      // 马桶正在落下
      ctx.save();
      // 画拖影
      ctx.globalAlpha = 0.3;
      var targetY = w.y * CELL + CELL/2;
      var progress = w.stateTime / w.fallDuration;
      var trailLen = CELL * 2 * progress;
      var trailGrad = ctx.createLinearGradient(cx, w.fallY - trailLen, cx, w.fallY);
      trailGrad.addColorStop(0, 'rgba(100,150,200,0)');
      trailGrad.addColorStop(1, 'rgba(100,150,200,0.4)');
      ctx.fillStyle = trailGrad;
      ctx.fillRect(cx - CELL*0.35, w.fallY - trailLen, CELL*0.7, trailLen);
      
      // 画落下中的马桶
      ctx.globalAlpha = 1;
      var sizeScale = 0.7 + 0.3 * progress; // 越接近目标越大
      var tempS = CELL * 0.38 * sizeScale;
      
      // 简易马桶图形(落动态)
      var mx = cx;
      var my = w.fallY;
      
      // 地面阴影
      ctx.fillStyle = 'rgba(60,80,100,0.15)';
      ctx.beginPath();
      ctx.ellipse(mx, targetY + CELL*0.42, CELL*0.5*sizeScale, CELL*0.12, 0, 0, Math.PI*2);
      ctx.fill();
      
      // 马桶本体(简化版,落动态显示)
      var bodyGrad = ctx.createLinearGradient(mx-tempS, my-tempS, mx+tempS, my+tempS);
      bodyGrad.addColorStop(0, '#e8f4fa');
      bodyGrad.addColorStop(1, '#a8b8c4');
      ctx.fillStyle = bodyGrad;
      ctx.beginPath();
      ctx.ellipse(mx, my, tempS*0.9, tempS*1.05, 0, 0, Math.PI*2);
      ctx.fill();
      ctx.strokeStyle = '#5a7080';
      ctx.lineWidth = 2;
      ctx.stroke();
      
      // 水箱
      ctx.fillStyle = '#d0dde8';
      roundRect(ctx, mx-tempS*0.6, my-tempS*0.9, tempS*1.2, tempS*0.5, tempS*0.08);
      ctx.fill();
      ctx.stroke();
      
      // 马桶水
      ctx.fillStyle = '#5ca8c9';
      ctx.beginPath();
      ctx.ellipse(mx, my+tempS*0.15, tempS*0.45, tempS*0.35, 0, 0, Math.PI*2);
      ctx.fill();
      
      // 溅水特效
      if(progress > 0.5){
        var splashAlpha = (progress - 0.5) * 2;
        ctx.globalAlpha = splashAlpha * 0.6;
        ctx.fillStyle = '#7fc8e0';
        for(var si = 0; si < 8; si++){
          var angle = (si / 8) * Math.PI * 2;
          var dist = CELL * 0.4 * progress;
          var spx = mx + Math.cos(angle) * dist;
          var spy = targetY + Math.sin(angle) * dist * 0.5;
          ctx.beginPath();
          ctx.arc(spx, spy, 2 + Math.random()*2, 0, Math.PI*2);
          ctx.fill();
        }
      }
      
      ctx.restore();
    }
  }
}

function drawToilets(now){
  for(var i=0;i<game.toilets.length;i++){
    var t = game.toilets[i];
    var alpha = 1;
    // 最后3秒闪烁
    if(t.expireTime && now > 0){
      var remaining = t.expireTime - now;
      if(remaining < 3000 && remaining > 0){
        var blink = Math.sin(remaining * 0.02) * 0.3 + 0.7;
        alpha = blink;
      } else if(remaining <= 0){
        continue; // 过期的不绘制
      }
    }
    ctx.save();
    ctx.globalAlpha = alpha;
    drawToilet(t.x*CELL, t.y*CELL);
    ctx.restore();
  }
}

function drawToilet(px, py){
  var cx = px + CELL/2;
  var cy = py + CELL/2;
  var s = CELL*0.38;

  ctx.fillStyle = 'rgba(60,80,100,0.2)';
  ctx.beginPath();
  ctx.ellipse(cx, cy+s*1.1, s*1.05, s*0.22, 0, 0, Math.PI*2);
  ctx.fill();

  var baseGrad = ctx.createLinearGradient(cx-s*0.9, cy+s*0.6, cx+s*0.9, cy+s*1.1);
  baseGrad.addColorStop(0, '#e8edf2');
  baseGrad.addColorStop(1, '#b8c4d0');
  ctx.fillStyle = baseGrad;
  ctx.beginPath();
  ctx.ellipse(cx, cy+s*0.85, s*0.92, s*0.35, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle = '#7a8a98';
  ctx.lineWidth = 1.5;
  ctx.stroke();

  ctx.fillStyle = '#c0ccd6';
  ctx.beginPath();
  ctx.moveTo(cx-s*0.15, cy-s*0.1);
  ctx.lineTo(cx-s*0.18, cy-s*0.7);
  ctx.lineTo(cx+s*0.18, cy-s*0.7);
  ctx.lineTo(cx+s*0.15, cy-s*0.1);
  ctx.closePath();
  ctx.fill();

  var tankGrad = ctx.createLinearGradient(cx-s*0.65, cy-s*1.15, cx+s*0.65, cy-s*0.35);
  tankGrad.addColorStop(0, '#f0f5fa');
  tankGrad.addColorStop(0.4, '#d8e4ed');
  tankGrad.addColorStop(1, '#a8b8c4');
  ctx.fillStyle = tankGrad;
  roundRect(ctx, cx-s*0.62, cy-s*1.12, s*1.24, s*0.72, s*0.1);
  ctx.fill();
  ctx.strokeStyle = '#5a7080';
  ctx.lineWidth = 1.8;
  ctx.stroke();

  ctx.fillStyle = 'rgba(255,255,255,0.5)';
  ctx.beginPath();
  roundRect(ctx, cx-s*0.55, cy-s*1.08, s*0.2, s*0.6, s*0.05);
  ctx.fill();

  var btnGrad = ctx.createRadialGradient(cx, cy-s*0.78, 0, cx, cy-s*0.78, s*0.1);
  btnGrad.addColorStop(0, '#5a7080');
  btnGrad.addColorStop(1, '#3a4a55');
  ctx.fillStyle = btnGrad;
  ctx.beginPath();
  ctx.arc(cx, cy-s*0.78, s*0.09, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = 'rgba(255,255,255,0.3)';
  ctx.beginPath();
  ctx.arc(cx-s*0.03, cy-s*0.81, s*0.035, 0, Math.PI*2);
  ctx.fill();

  var bowlGrad = ctx.createRadialGradient(cx-s*0.1, cy+s*0.05, s*0.1, cx, cy+s*0.1, s*1);
  bowlGrad.addColorStop(0, '#ffffff');
  bowlGrad.addColorStop(0.5, '#e4ecf2');
  bowlGrad.addColorStop(0.85, '#c0d0dc');
  bowlGrad.addColorStop(1, '#90a4b4');
  ctx.fillStyle = bowlGrad;
  ctx.beginPath();
  ctx.ellipse(cx, cy+s*0.08, s*0.9, s*0.78, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle = '#5a7080';
  ctx.lineWidth = 2;
  ctx.stroke();

  var innerGrad = ctx.createRadialGradient(cx, cy+s*0.15, 0, cx, cy+s*0.18, s*0.6);
  innerGrad.addColorStop(0, '#a0b8c8');
  innerGrad.addColorStop(0.6, '#7a95a8');
  innerGrad.addColorStop(1, '#5a7080');
  ctx.fillStyle = innerGrad;
  ctx.beginPath();
  ctx.ellipse(cx, cy+s*0.15, s*0.6, s*0.5, 0, 0, Math.PI*2);
  ctx.fill();

  var waterGrad = ctx.createRadialGradient(cx-s*0.1, cy+s*0.12, 0, cx, cy+s*0.2, s*0.45);
  waterGrad.addColorStop(0, '#8ac5e0');
  waterGrad.addColorStop(0.4, '#5ca8c9');
  waterGrad.addColorStop(0.8, '#3a7a95');
  waterGrad.addColorStop(1, '#2a5a70');
  ctx.fillStyle = waterGrad;
  ctx.beginPath();
  ctx.ellipse(cx, cy+s*0.2, s*0.45, s*0.38, 0, 0, Math.PI*2);
  ctx.fill();

  ctx.strokeStyle = 'rgba(255,255,255,0.4)';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.ellipse(cx-s*0.05, cy+s*0.18, s*0.25, s*0.08, -0.2, 0, Math.PI*2);
  ctx.stroke();
  ctx.beginPath();
  ctx.ellipse(cx+s*0.05, cy+s*0.22, s*0.15, s*0.05, 0.3, 0, Math.PI*2);
  ctx.stroke();

  ctx.fillStyle = 'rgba(255,255,255,0.55)';
  ctx.beginPath();
  ctx.ellipse(cx-s*0.2, cy+s*0.05, s*0.12, s*0.06, -0.4, 0, Math.PI*2);
  ctx.fill();

  var seatGrad = ctx.createLinearGradient(cx, cy-s*0.15, cx, cy+s*0.05);
  seatGrad.addColorStop(0, '#f8fbfd');
  seatGrad.addColorStop(1, '#d8e4ed');
  ctx.fillStyle = seatGrad;
  ctx.beginPath();
  ctx.ellipse(cx, cy-s*0.05, s*0.88, s*0.38, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle = '#6a7f8f';
  ctx.lineWidth = 1.8;
  ctx.stroke();

  ctx.strokeStyle = 'rgba(90,112,128,0.6)';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.ellipse(cx, cy-s*0.03, s*0.62, s*0.48, 0, 0, Math.PI*2);
  ctx.stroke();

  ctx.save();
  ctx.translate(cx, cy-s*0.1);
  ctx.rotate(-0.35);
  var lidGrad = ctx.createLinearGradient(-s*0.7, -s*0.3, s*0.7, s*0.1);
  lidGrad.addColorStop(0, '#f0f5fa');
  lidGrad.addColorStop(0.5, '#d0dde8');
  lidGrad.addColorStop(1, '#b0c4d4');
  ctx.fillStyle = lidGrad;
  ctx.beginPath();
  ctx.ellipse(0, -s*0.12, s*0.72, s*0.18, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle = '#5a7080';
  ctx.lineWidth = 1.5;
  ctx.stroke();
  ctx.restore();

  ctx.fillStyle = 'rgba(255,255,255,0.35)';
  ctx.beginPath();
  ctx.ellipse(cx-s*0.35, cy-s*0.15, s*0.3, s*0.07, -0.3, 0, Math.PI);
  ctx.fill();

  var shineGrad = ctx.createLinearGradient(cx-s*0.6, cy+s*0.2, cx-s*0.3, cy+s*0.4);
  shineGrad.addColorStop(0, 'rgba(255,255,255,0.25)');
  shineGrad.addColorStop(1, 'rgba(255,255,255,0)');
  ctx.fillStyle = shineGrad;
  ctx.fillRect(cx-s*0.7, cy-s*0.05, s*0.4, s*0.6);
}

function drawSnake(t, now){
  var invincible = now < game.invincibleUntil;
  var speedBoost = now < game.speedBoostUntil;
  var pulse = game.pulseTime > 0 ? 1 + 0.15*(game.pulseTime/200) : 1;

  if(game.snake.length > 3){
    ctx.save();
    var trailColor = invincible ? 'rgba(255,215,0,' : (speedBoost ? 'rgba(255,100,100,' : 'rgba(139,90,43,');
    ctx.globalAlpha = (invincible?0.18:0.12) * (1 - t*0.5);
    for(var ti=game.snake.length-1;ti>=0;ti--){
      var ts = game.snake[ti];
      var tix = ts.prevX + (ts.x - ts.prevX) * t;
      var tiy = ts.prevY + (ts.y - ts.prevY) * t;
      tix = tix*CELL + CELL/2;
      tiy = tiy*CELL + CELL/2;
      var trailSize = CELL * 0.9 * (1 - ti/game.snake.length*0.5);
      var tg = ctx.createRadialGradient(tix, tiy, 0, tix, tiy, trailSize);
      tg.addColorStop(0, trailColor + '0.5)');
      tg.addColorStop(1, trailColor + '0)');
      ctx.fillStyle = tg;
      ctx.fillRect(tix-trailSize, tiy-trailSize, trailSize*2, trailSize*2);
    }
    ctx.restore();
  }

  for(var i=game.snake.length-1;i>=0;i--){
    var seg = game.snake[i];
    var ix = seg.prevX + (seg.x - seg.prevX) * t;
    var iy = seg.prevY + (seg.y - seg.prevY) * t;
    ix = (ix * CELL) + CELL/2;
    iy = (iy * CELL) + CELL/2;
    var isHead = i===0;
    var baseSize = CELL * 0.85;
    var size = isHead ? baseSize * pulse : baseSize;
    var flash = invincible ? (Math.sin(now/80)>0 ? 1 : 0.55) : 1;
    var color1, color2, color3;
    if(invincible){
      color1 = 'hsl(48, 100%, ' + (60+15*flash) + '%)';
      color2 = 'hsl(38, 100%, ' + (50+10*flash) + '%)';
      color3 = 'hsl(28, 100%, 40%)';
    } else if(speedBoost){
      var hue = (now/5)%360;
      color1 = 'hsl(' + hue + ', 90%, 65%)';
      color2 = 'hsl(' + ((hue+20)%360) + ', 90%, 55%)';
      color3 = '#cc2233';
    } else {
      var skin = SKINS[game.skinKey] || SKINS.default;
      var dark = Math.floor(i/2)%2===0;
      color1 = dark ? skin.colors[0] : skin.colors[1];
      color2 = skin.colors[2];
      color3 = darkenHex(skin.colors[2], 30);
    }
    ctx.save();
    if(invincible){ ctx.shadowColor = 'rgba(255,215,0,0.7)'; ctx.shadowBlur = 16; }
    else if(speedBoost){ ctx.shadowColor = 'rgba(255,100,100,0.55)'; ctx.shadowBlur = 14; }
    else{ ctx.shadowColor = 'rgba(0,0,0,0.22)'; ctx.shadowBlur = 5; }
    ctx.fillStyle = color1;
    roundRect(ctx, ix-size/2, iy-size/2, size, size, size*0.32);
    ctx.fill();
    ctx.restore();
    ctx.fillStyle = color2;
    roundRect(ctx, ix-size/2+3, iy-size/2+3, size-6, size*0.45, size*0.25);
    ctx.fill();
    ctx.fillStyle = color3;
    ctx.fillRect(ix-size*0.25, iy+size*0.15, size*0.5, size*0.08);
    if(isHead){
      var d = game.dir;
      var ex = ix + d.x*size*0.12;
      var ey = iy + d.y*size*0.12;
      var eo = size*0.18;
      var eyeSize = size*0.16;
      ctx.fillStyle = '#fff';
      ctx.beginPath();
      ctx.arc(ex - d.y*eo, ey + d.x*eo, eyeSize, 0, Math.PI*2);
      ctx.arc(ex + d.y*eo, ey - d.x*eo, eyeSize, 0, Math.PI*2);
      ctx.fill();
      ctx.fillStyle = '#1a1a1a';
      ctx.beginPath();
      ctx.arc(ex - d.y*eo + d.x*2, ey + d.x*eo + d.y*2, eyeSize*0.5, 0, Math.PI*2);
      ctx.arc(ex + d.y*eo + d.x*2, ey - d.x*eo + d.y*2, eyeSize*0.5, 0, Math.PI*2);
      ctx.fill();
      ctx.fillStyle = 'rgba(255,150,170,0.55)';
      ctx.beginPath();
      ctx.arc(ix - size*0.22, ey + size*0.18, size*0.09, 0, Math.PI*2);
      ctx.arc(ix + size*0.22, ey + size*0.18, size*0.09, 0, Math.PI*2);
      ctx.fill();
      ctx.strokeStyle = 'rgba(0,0,0,0.2)';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.arc(ix, iy+size*0.05, size*0.12, 0.2, Math.PI-0.2);
      ctx.stroke();
      
      // 蛇头上方显示玩家名字
      if(game.playerName && i===0){
        var name = game.playerName;
        ctx.save();
        ctx.font = 'bold 12px Fredoka, sans-serif';
        var nameWidth = ctx.measureText(name).width;
        var tagW = nameWidth + 12;
        var tagH = 16;
        var tagX = ix - tagW/2;
        var tagY = iy - size*0.75;
        
        // 名字背景胶囊
        ctx.fillStyle = 'rgba(255,158,181,0.92)';
        roundRect(ctx, tagX, tagY, tagW, tagH, 8);
        ctx.fill();
        ctx.strokeStyle = 'rgba(255,122,158,0.6)';
        ctx.lineWidth = 1.5;
        ctx.stroke();
        
        // 名字文字
        ctx.fillStyle = '#fff';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(name, ix, tagY + tagH/2);
        
        // 小三角指向蛇头
        ctx.fillStyle = 'rgba(255,158,181,0.92)';
        ctx.beginPath();
        ctx.moveTo(ix - 4, tagY + tagH);
        ctx.lineTo(ix, tagY + tagH + 4);
        ctx.lineTo(ix + 4, tagY + tagH);
        ctx.closePath();
        ctx.fill();
        ctx.restore();
      }
    }
  }
}

function drawFood(now){
  if(!game.food) return;
  var bob = Math.sin(now/300) * 2;
  var cx = game.food.x*CELL + CELL/2;
  var cy = game.food.y*CELL + CELL/2 + bob;
  var glow = ctx.createRadialGradient(cx, cy, 0, cx, cy, CELL*0.7);
  glow.addColorStop(0, 'rgba(139,69,19,0.3)');
  glow.addColorStop(1, 'transparent');
  ctx.fillStyle = glow;
  ctx.fillRect(cx-CELL, cy-CELL, CELL*2, CELL*2);
  drawPoopEmoji(cx, cy, CELL*0.75, '#8b4513', '#5d2e0a');
}

function drawPoopEmoji(cx, cy, size, mainColor, darkColor){
  var s = size;
  ctx.fillStyle = mainColor;
  ctx.beginPath();
  ctx.ellipse(cx, cy - s*0.05, s*0.44, s*0.4, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.beginPath();
  ctx.ellipse(cx, cy - s*0.3, s*0.32, s*0.27, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.beginPath();
  ctx.ellipse(cx, cy - s*0.52, s*0.2, s*0.16, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = darkColor;
  ctx.beginPath();
  ctx.ellipse(cx, cy + s*0.32, s*0.44, s*0.14, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.arc(cx - s*0.14, cy - s*0.4, s*0.065, 0, Math.PI*2);
  ctx.arc(cx + s*0.14, cy - s*0.4, s*0.065, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = '#1a1a1a';
  ctx.beginPath();
  ctx.arc(cx - s*0.14, cy - s*0.4, s*0.032, 0, Math.PI*2);
  ctx.arc(cx + s*0.14, cy - s*0.4, s*0.032, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = 'rgba(255,150,170,0.55)';
  ctx.beginPath();
  ctx.arc(cx - s*0.21, cy - s*0.3, s*0.055, 0, Math.PI*2);
  ctx.arc(cx + s*0.21, cy - s*0.3, s*0.055, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle = 'rgba(0,0,0,0.15)';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(cx - s*0.08, cy + s*0.05);
  ctx.quadraticCurveTo(cx, cy + s*0.12, cx + s*0.08, cy + s*0.05);
  ctx.stroke();
}

function drawSpecialFoods(now){
  for(var i=0;i<game.specialFoods.length;i++){
    var sf = game.specialFoods[i];
    var elapsed = now - sf.spawnTime;
    var life = sf.duration;
    var remaining = 1 - elapsed/life;
    var bob = Math.sin(now/200 + sf.x*0.7 + sf.y*0.5) * 3;
    var cx = sf.x*CELL + CELL/2;
    var cy = sf.y*CELL + CELL/2 + bob;
    var color = specialColor(sf.type);
    ctx.save();
    var glow = ctx.createRadialGradient(cx, cy, 0, cx, cy, CELL*0.8);
    glow.addColorStop(0, color + 'aa');
    glow.addColorStop(0.4, color + '44');
    glow.addColorStop(1, 'transparent');
    ctx.fillStyle = glow;
    ctx.fillRect(cx-CELL, cy-CELL, CELL*2, CELL*2);
    var pulse = 0.85 + 0.15*Math.sin(now/120);
    var alpha = remaining < 0.3 ? 0.35 + 0.65*Math.abs(Math.sin(now/60)) : 1;
    ctx.globalAlpha = alpha;
    ctx.shadowColor = color;
    ctx.shadowBlur = 15;
    // 所有特殊道具都用 emoji 大便形状,只是颜色不同
    var darkColor = darkenHex(color, 40);
    drawPoopEmoji(cx, cy, CELL*0.75*pulse, color, darkColor);
    ctx.fillStyle = '#fff';
    ctx.font = 'bold ' + Math.round(CELL*0.35) + 'px sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('?', cx, cy + CELL*0.22);
    ctx.shadowBlur = 0;
    ctx.restore();
    if(remaining < 0.5){
      var barW = CELL * 0.6;
      var barX = cx - barW/2;
      var barY = cy - CELL*0.55;
      ctx.fillStyle = 'rgba(0,0,0,0.3)';
      ctx.fillRect(barX, barY, barW, 3);
      ctx.fillStyle = color;
      ctx.fillRect(barX, barY, barW * Math.max(0, remaining*2-0.5), 3);
    }
  }
}

function drawStar(cx, cy, r, color){
  ctx.fillStyle = color;
  ctx.beginPath();
  for(var i=0;i<10;i++){
    var angle = (Math.PI/5)*i - Math.PI/2;
    var rad = i%2===0 ? r : r*0.45;
    var x = cx + Math.cos(angle)*rad;
    var y = cy + Math.sin(angle)*rad;
    if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
  }
  ctx.closePath();
  ctx.fill();
  ctx.strokeStyle = '#b8960f';
  ctx.lineWidth = 2;
  ctx.stroke();
  ctx.fillStyle = 'rgba(255,255,255,0.9)';
  ctx.beginPath();
  ctx.arc(cx-r*0.2, cy-r*0.2, r*0.15, 0, Math.PI*2);
  ctx.fill();
}

function drawWaterDrop(cx, cy, r, color){
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.moveTo(cx, cy-r);
  ctx.bezierCurveTo(cx+r*1.1, cy-r*0.2, cx+r*0.6, cy+r*0.9, cx, cy+r);
  ctx.bezierCurveTo(cx-r*0.6, cy+r*0.9, cx-r*1.1, cy-r*0.2, cx, cy-r);
  ctx.fill();
  ctx.fillStyle = 'rgba(255,255,255,0.65)';
  ctx.beginPath();
  ctx.ellipse(cx-r*0.25, cy-r*0.15, r*0.18, r*0.28, -0.3, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle = 'rgba(40,80,140,0.4)';
  ctx.lineWidth = 1.5;
  ctx.beginPath();
  ctx.moveTo(cx, cy-r);
  ctx.bezierCurveTo(cx+r*1.1, cy-r*0.2, cx+r*0.6, cy+r*0.9, cx, cy+r);
  ctx.stroke();
}

function drawBomb(cx, cy, r, color){
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.arc(cx, cy+r*0.05, r*0.85, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = '#3a3a3a';
  ctx.beginPath();
  ctx.arc(cx, cy-r*0.25, r*0.18, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle = '#3a3a3a';
  ctx.lineWidth = 3.5;
  ctx.lineCap = 'round';
  ctx.beginPath();
  ctx.moveTo(cx, cy-r*0.25);
  ctx.quadraticCurveTo(cx+r*0.2, cy-r*0.55, cx+r*0.45, cy-r*0.55);
  ctx.stroke();
  ctx.lineCap = 'butt';
  ctx.fillStyle = '#ff6600';
  ctx.beginPath();
  ctx.arc(cx+r*0.48, cy-r*0.55, r*0.12, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = '#ffdd00';
  ctx.beginPath();
  ctx.arc(cx+r*0.48, cy-r*0.55, r*0.06, 0, Math.PI*2);
  ctx.fill();
}

function drawLightning(cx, cy, r, color){
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.moveTo(cx-r*0.35, cy-r);
  ctx.lineTo(cx+r*0.25, cy-r*0.25);
  ctx.lineTo(cx-r*0.15, cy-r*0.05);
  ctx.lineTo(cx+r*0.45, cy+r);
  ctx.lineTo(cx-r*0.05, cy+r*0.15);
  ctx.lineTo(cx+r*0.15, cy);
  ctx.closePath();
  ctx.fill();
  ctx.strokeStyle = '#aa3333';
  ctx.lineWidth = 2;
  ctx.stroke();
  ctx.fillStyle = 'rgba(255,255,255,0.4)';
  ctx.beginPath();
  ctx.moveTo(cx-r*0.15, cy-r*0.5);
  ctx.lineTo(cx+r*0.05, cy-r*0.3);
  ctx.lineTo(cx-r*0.05, cy-r*0.1);
  ctx.closePath();
  ctx.fill();
}

function drawEnemySnakes(now){
  for(var e=0;e<game.enemySnakes.length;e++){
    var es = game.enemySnakes[e];
    if(!es.segments || es.segments.length===0) continue;
    var age = now - es.spawnTime;
    var lifeFrac = 1 - age/es.lifeTime;
    var alpha = lifeFrac < 0.25 ? (0.3 + 0.7*Math.abs(Math.sin(now/60))) : 1;
    ctx.globalAlpha = alpha;
    for(var i=0;i<es.segments.length;i++){
      var seg = es.segments[i];
      var ix, iy;
      if(es.prevSegments && es.prevSegments[i]){
        var p = es.prevSegments[i];
        ix = p.x + (seg.x - p.x) * 0.5;
        iy = p.y + (seg.y - p.y) * 0.5;
      } else {
        ix = seg.x;
        iy = seg.y;
      }
      ix = ix*CELL + CELL/2;
      iy = iy*CELL + CELL/2;
      var size = CELL * 0.8;
      var isHead = i===0;
      var mainColor = isHead ? '#6b4226' : '#8b5a2b';
      var darkColor = isHead ? '#4a2a15' : '#6b4226';
      ctx.save();
      ctx.shadowColor = 'rgba(100,60,30,0.5)';
      ctx.shadowBlur = 8;
      ctx.fillStyle = mainColor;
      roundRect(ctx, ix-size/2, iy-size/2, size, size, size*0.28);
      ctx.fill();
      ctx.restore();
      ctx.fillStyle = darkColor;
      roundRect(ctx, ix-size/2+2, iy-size/2+2, size-4, size*0.4, size*0.22);
      ctx.fill();
      if(isHead){
        ctx.fillStyle = '#ff0';
        ctx.beginPath();
        ctx.arc(ix-5, iy-3, 3, 0, Math.PI*2);
        ctx.arc(ix+5, iy-3, 3, 0, Math.PI*2);
        ctx.fill();
        ctx.fillStyle = '#000';
        ctx.beginPath();
        ctx.arc(ix-5, iy-3, 1.5, 0, Math.PI*2);
        ctx.arc(ix+5, iy-3, 1.5, 0, Math.PI*2);
        ctx.fill();
        ctx.fillStyle = 'rgba(255,100,100,0.6)';
        ctx.beginPath();
        ctx.arc(ix, iy+4, 4, 0, Math.PI, true);
        ctx.fill();
      }
    }
    ctx.globalAlpha = 1;
  }
}

function drawParticles(){
  for(var i=0;i<game.particles.length;i++){
    var p = game.particles[i];
    var alpha = Math.max(0, p.life / p.maxLife);
    ctx.globalAlpha = alpha;
    if(p.sparkle){
      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(p.vx * 0.02);
      ctx.fillStyle = p.color;
      ctx.fillRect(-p.size*0.5, -p.size*0.5, p.size, p.size);
      ctx.fillStyle = 'rgba(255,255,255,' + alpha + ')';
      ctx.fillRect(-p.size*0.2, -p.size*0.2, p.size*0.4, p.size*0.4);
      ctx.restore();
    } else {
      ctx.fillStyle = p.color;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI*2);
      ctx.fill();
    }
  }
  ctx.globalAlpha = 1;
}

function drawFloatTexts(){
  for(var i=0;i<game.floatTexts.length;i++){
    var f = game.floatTexts[i];
    var alpha = Math.max(0, f.life / 1000);
    ctx.globalAlpha = alpha;
    ctx.fillStyle = f.color;
    ctx.font = 'bold ' + f.size + 'px Fredoka, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.strokeStyle = 'rgba(255,255,255,0.9)';
    ctx.lineWidth = 3;
    ctx.strokeText(f.text, f.x, f.y);
    ctx.fillText(f.text, f.x, f.y);
  }
  ctx.globalAlpha = 1;
}

function roundRect(ctx, x, y, w, h, r){
  ctx.beginPath();
  ctx.moveTo(x+r, y);
  ctx.lineTo(x+w-r, y);
  ctx.quadraticCurveTo(x+w, y, x+w, y+r);
  ctx.lineTo(x+w, y+h-r);
  ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
  ctx.lineTo(x+r, y+h);
  ctx.quadraticCurveTo(x, y+h, x, y+h-r);
  ctx.lineTo(x, y+r);
  ctx.quadraticCurveTo(x, y, x+r, y);
  ctx.closePath();
}

function darkenHex(hex, amount){
  hex = hex.replace('#','');
  var r = Math.max(0, parseInt(hex.substr(0,2),16) - amount);
  var g = Math.max(0, parseInt(hex.substr(2,2),16) - amount);
  var b = Math.max(0, parseInt(hex.substr(4,2),16) - amount);
  return 'rgb('+r+','+g+','+b+')';
}

function setDir(nx, ny){
  if(!game || game.state!==STATE.PLAYING) return;
  if(game.dir.x === -nx && game.dir.y === -ny) return;
  if(game.dir.x === nx && game.dir.y === ny) return;
  game.nextDir = {x:nx, y:ny};
}

var diffOptions = document.getElementById('diffOptions');
for(var i=1;i<=10;i++){
  (function(val){
    var btn = document.createElement('button');
    btn.className = 'diff-btn';
    btn.textContent = val;
    btn.dataset.val = val;
    btn.addEventListener('click', function(){
      var btns = document.querySelectorAll('.diff-btn');
      for(var b=0;b<btns.length;b++) btns[b].classList.remove('selected');
      btn.classList.add('selected');
      diffOptions.dataset.selected = val;
    });
    diffOptions.appendChild(btn);
  })(i);
}
diffOptions.dataset.selected = '1';
diffOptions.querySelector('[data-val="1"]').classList.add('selected');

var ctrlSelect = document.getElementById('ctrlSelect');
if(ctrlSelect){
  var ctrlBtns = ctrlSelect.querySelectorAll('.diff-btn');
  for(var ci=0; ci<ctrlBtns.length; ci++){
    ctrlBtns[ci].addEventListener('click', (function(btn){
      return function(){
        for(var cj=0; cj<ctrlBtns.length; cj++) ctrlBtns[cj].classList.remove('active');
        btn.classList.add('active');
        ctrlSelect.dataset.selected = btn.dataset.ctrl;
      };
    })(ctrlBtns[ci]));
  }
  ctrlSelect.dataset.selected = ctrlBtns[0].dataset.ctrl;
}

document.getElementById('startBtn').addEventListener('click', function(){
  document.getElementById('startOverlay').classList.add('hidden');
  initGame();
});

document.getElementById('pauseBtn').addEventListener('click', function(){
  if(game && game.state===STATE.PLAYING){
    game.state = STATE.PAUSED;
    document.getElementById('pauseOverlay').classList.remove('hidden');
  }
});

document.getElementById('soundBtn').addEventListener('click', function(){
  game.soundMuted = !game.soundMuted;
  var btn = document.getElementById('soundBtn');
  if(game.soundMuted){
    btn.classList.add('muted');
    btn.textContent = '🔇';
  } else {
    btn.classList.remove('muted');
    btn.textContent = '🔊';
  }
});

document.getElementById('resumeBtn').addEventListener('click', function(){
  if(game && game.state===STATE.PAUSED){
    game.state = STATE.PLAYING;
    game.lastTime = performance.now();
    document.getElementById('pauseOverlay').classList.add('hidden');
  }
});

document.getElementById('restartBtn').addEventListener('click', function(){
  document.getElementById('pauseOverlay').classList.add('hidden');
  document.getElementById('startOverlay').classList.remove('hidden');
  game = null;
});

document.getElementById('playAgainBtn').addEventListener('click', function(){
  document.getElementById('gameOverOverlay').classList.add('hidden');
  initGame();
});

document.getElementById('backToMenuBtn').addEventListener('click', function(){
  document.getElementById('gameOverOverlay').classList.add('hidden');
  document.getElementById('startOverlay').classList.remove('hidden');
  game = null;
});

document.addEventListener('keydown', function(e){
  var k = e.key.toLowerCase();
  if(k==='arrowup'||k==='w'){ setDir(0,-1); e.preventDefault(); }
  else if(k==='arrowdown'||k==='s'){ setDir(0,1); e.preventDefault(); }
  else if(k==='arrowleft'||k==='a'){ setDir(-1,0); e.preventDefault(); }
  else if(k==='arrowright'||k==='d'){ setDir(1,0); e.preventDefault(); }
  else if(k===' '||k==='p'){
    e.preventDefault();
    if(game && game.state===STATE.PLAYING){
      game.state = STATE.PAUSED;
      document.getElementById('pauseOverlay').classList.remove('hidden');
    } else if(game && game.state===STATE.PAUSED){
      game.state = STATE.PLAYING;
      game.lastTime = performance.now();
      document.getElementById('pauseOverlay').classList.add('hidden');
    }
  }
});

var dpadBtns = document.querySelectorAll('.dpad-btn');
for(var di=0;di<dpadBtns.length;di++){
  (function(btn){
    var dir = btn.dataset.dir;
    var active = false;
    var holdInterval = null;
    var start = function(e){
      e.preventDefault();
      active = true;
      btn.classList.add('active');
      if(dir==='up') setDir(0,-1);
      else if(dir==='down') setDir(0,1);
      else if(dir==='left') setDir(-1,0);
      else if(dir==='right') setDir(1,0);
      holdInterval = setInterval(function(){
        if(!active) return;
        if(dir==='up') setDir(0,-1);
        else if(dir==='down') setDir(0,1);
        else if(dir==='left') setDir(-1,0);
        else if(dir==='right') setDir(1,0);
      }, 180);
    };
    var end = function(){
      active = false;
      btn.classList.remove('active');
      if(holdInterval){ clearInterval(holdInterval); holdInterval = null; }
    };
    btn.addEventListener('touchstart', start, {passive:false});
    btn.addEventListener('touchend', end);
    btn.addEventListener('touchcancel', end);
    btn.addEventListener('mousedown', start);
    btn.addEventListener('mouseup', end);
    btn.addEventListener('mouseleave', end);
  })(dpadBtns[di]);
}

document.addEventListener('visibilitychange', function(){
  if(document.hidden && game && game.state===STATE.PLAYING){
    game.state = STATE.PAUSED;
    document.getElementById('pauseOverlay').classList.remove('hidden');
  }
});

var touchStartX = 0, touchStartY = 0, touchActive = false;
document.addEventListener('touchstart', function(e){
  if(game.state!==STATE.PLAYING) return;
  if(game.controlType !== 'swipe') return;
  var t = e.touches[0];
  touchStartX = t.clientX;
  touchStartY = t.clientY;
  touchActive = true;
}, {passive:true});
document.addEventListener('touchmove', function(e){
  if(!touchActive || game.controlType !== 'swipe') return;
  var t = e.touches[0];
  var dx = t.clientX - touchStartX;
  var dy = t.clientY - touchStartY;
  var threshold = 25;
  if(Math.abs(dx) > threshold || Math.abs(dy) > threshold){
    if(Math.abs(dx) > Math.abs(dy)){
      setDir(dx > 0 ? 1 : -1, 0);
    } else {
      setDir(0, dy > 0 ? 1 : -1);
    }
    touchStartX = t.clientX;
    touchStartY = t.clientY;
  }
}, {passive:true});
document.addEventListener('touchend', function(){ touchActive = false; }, {passive:true});

function vibrate(ms){
  if(navigator.vibrate){
    try{ navigator.vibrate(ms); }catch(e){}
  }
}

var origTriggerGameOver = triggerGameOver;
triggerGameOver = function(reason){
  vibrate([80, 40, 120]);
  origTriggerGameOver(reason);
};

var origStepSnake = stepSnake;
stepSnake = function(now){
  var lenBefore = game.snake ? game.snake.length : 3;
  origStepSnake(now);
  if(game && game.snake && game.snake.length > lenBefore){
    vibrate(20);
  }
};

game = makeGame();
game.highScore = parseInt(localStorage.getItem(LS_KEY)||'0',10);
document.getElementById('highScore').textContent = game.highScore;
document.getElementById('coinsDisplay').textContent = game.coins;

function openShop(){
  if(!game){ game = makeGame(); }
  document.getElementById('shopOverlay').classList.remove('hidden');
  document.getElementById('shopTabSkins').classList.add('active');
  document.getElementById('shopTabUpgrades').classList.remove('active');
  document.getElementById('shopSkinsPanel').style.display = 'block';
  document.getElementById('shopUpgradesPanel').style.display = 'none';
  renderShop();
}
function closeShop(){
  document.getElementById('shopOverlay').classList.add('hidden');
}
function renderShop(){
  if(!game){ game = makeGame(); }
  game.coins = parseInt(localStorage.getItem('poop_snake_coins')||'0',10);
  document.getElementById('shopCoinsDisplay').textContent = '💰 ' + game.coins;
  renderSkinsPanel();
  renderUpgradesPanel();
}
function renderSkinsPanel(){
  var panel = document.getElementById('shopSkinsPanel');
  var purchased = getPurchasedSkins();
  var selected = localStorage.getItem('poop_snake_selected_skin') || 'default';
  var html = '';
  for(var key in SKINS){
    if(!SKINS.hasOwnProperty(key)) continue;
    var skin = SKINS[key];
    var owned = key==='default' || purchased.indexOf(key) >= 0;
    var isSelected = selected === key;
    var colorsPreview = '';
    for(var ci=0; ci<skin.colors.length; ci++){
      colorsPreview += '<span style="background:' + skin.colors[ci] + '"></span>';
    }
    html += '<div class="shop-item">' +
      '<div class="shop-item-info">' +
        '<div class="shop-color-preview">' + colorsPreview + '</div>' +
        '<div>' +
          '<div class="shop-item-name">' + skin.name + '</div>' +
          '<div class="shop-item-desc">价格: ' + (skin.price===0?'免费':skin.price+' 金币') + '</div>' +
        '</div>' +
      '</div>' +
      '<div>';
    if(isSelected){
      html += '<span class="shop-owned-tag">✓ 使用中</span>';
    } else if(owned){
      html += '<button class="shop-buy-btn shop-select-btn" data-skin="' + key + '">选择</button>';
    } else {
      var canAfford = game.coins >= skin.price;
      html += '<button class="shop-buy-btn" data-buy-skin="' + key + '"' + (!canAfford?' disabled':'') + '>购买</button>';
    }
    html += '</div></div>';
  }
  panel.innerHTML = html;
  var buyBtns = panel.querySelectorAll('[data-buy-skin]');
  for(var i=0; i<buyBtns.length; i++){
    buyBtns[i].addEventListener('click', function(){
      var key = this.getAttribute('data-buy-skin');
      buySkin(key);
    });
  }
  var selectBtns = panel.querySelectorAll('[data-skin]');
  for(var j=0; j<selectBtns.length; j++){
    selectBtns[j].addEventListener('click', function(){
      var key = this.getAttribute('data-skin');
      setSelectedSkin(key);
      game.skinKey = key;
      renderShop();
    });
  }
}
function buySkin(key){
  var skin = SKINS[key];
  if(!skin) return;
  if(game.coins < skin.price) return;
  game.coins -= skin.price;
  saveCoins();
  var purchased = getPurchasedSkins();
  if(purchased.indexOf(key) < 0){
    purchased.push(key);
    savePurchasedSkins(purchased);
  }
  setSelectedSkin(key);
  game.skinKey = key;
  renderShop();
}
function renderUpgradesPanel(){
  var panel = document.getElementById('shopUpgradesPanel');
  var level = parseInt(localStorage.getItem('poop_snake_upgrade')||'0',10);
  var html = '<div style="color:#fff;font-weight:600;margin-bottom:10px">当前等级: ' + level + ' / ' + UPGRADE_COSTS.length + '</div>';
  html += '<div style="color:#c0dde8;font-size:12px;margin-bottom:10px">每级提供 1 次抵挡伤害 (护盾)</div>';
  for(var i=0; i<UPGRADE_COSTS.length; i++){
    var cost = UPGRADE_COSTS[i];
    var lv = i + 1;
    var isCurrent = level >= lv;
    var isNext = level + 1 === lv;
    var canAfford = game.coins >= cost;
    html += '<div class="shop-item">' +
      '<div class="shop-item-info">' +
        '<div style="width:32px;height:32px;border-radius:50%;background:' + (isCurrent?'linear-gradient(135deg,#ffd700,#daa520)':'rgba(255,255,255,0.2)') + ';display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:14px">' + lv + '</div>' +
        '<div>' +
          '<div class="shop-item-name">等级 ' + lv + ' (' + cost + ' 金币)</div>' +
          '<div class="shop-item-desc">' + (isCurrent?'✓ 已解锁':'获得 '+lv+' 次护盾') + '</div>' +
        '</div>' +
      '</div>' +
      '<div>';
    if(isCurrent){
      html += '<span class="shop-owned-tag">✓</span>';
    } else if(isNext){
      html += '<button class="shop-buy-btn" data-upgrade="' + i + '"' + (!canAfford?' disabled':'') + '>升级</button>';
    } else {
      html += '<span style="color:#888;font-size:12px">锁定</span>';
    }
    html += '</div></div>';
  }
  panel.innerHTML = html;
  var upBtns = panel.querySelectorAll('[data-upgrade]');
  for(var j=0; j<upBtns.length; j++){
    upBtns[j].addEventListener('click', function(){
      var idx = parseInt(this.getAttribute('data-upgrade'), 10);
      buyUpgrade(idx);
    });
  }
}
function buyUpgrade(idx){
  var cost = UPGRADE_COSTS[idx];
  if(game.coins < cost) return;
  game.coins -= cost;
  saveCoins();
  var newLevel = idx + 1;
  saveUpgradeLevel(newLevel);
  game.upgradeLevel = newLevel;
  renderShop();
}

document.getElementById('shopBtn').addEventListener('click', openShop);
document.getElementById('shopBtnStart').addEventListener('click', openShop);
document.getElementById('shopBtnGameOver').addEventListener('click', openShop);
document.getElementById('closeShopBtn').addEventListener('click', closeShop);
document.getElementById('shopTabSkins').addEventListener('click', function(){
  this.classList.add('active');
  document.getElementById('shopTabUpgrades').classList.remove('active');
  document.getElementById('shopSkinsPanel').style.display = 'block';
  document.getElementById('shopUpgradesPanel').style.display = 'none';
});
document.getElementById('shopTabUpgrades').addEventListener('click', function(){
  this.classList.add('active');
  document.getElementById('shopTabSkins').classList.remove('active');
  document.getElementById('shopUpgradesPanel').style.display = 'block';
  document.getElementById('shopSkinsPanel').style.display = 'none';
});

// ============ 测试函数 ============
window.testSpawnWarning = function(){
  if(!game) return;
  // 在地图中央生成一个警告
  var x = Math.floor(COLS/2);
  var y = Math.floor(ROWS/2);
  // 避开蛇头
  if(game.snake && game.snake[0]){
    var hx = game.snake[0].x, hy = game.snake[0].y;
    if(Math.abs(x-hx)<2 && Math.abs(y-hy)<2){
      x = (x+3) % COLS;
    }
  }
  game.warnings.push({x:x, y:y, state:'warn', stateTime:0, warnDuration:1500, countDuration:3000, fallDuration:400, fallY:-1});
  console.log('🧪 测试:警告已生成 at', x, y);
};

window.testSpawnCountdown = function(){
  if(!game) return;
  var x = Math.floor(COLS/2) + 3;
  var y = Math.floor(ROWS/2);
  if(game.snake && game.snake[0]){
    var hx = game.snake[0].x, hy = game.snake[0].y;
    if(Math.abs(x-hx)<2 && Math.abs(y-hy)<2){ x = (x+4) % COLS; }
  }
  game.warnings.push({x:x, y:y, state:'countdown', stateTime:0, warnDuration:1500, countDuration:3000, fallDuration:400, fallY:-1});
  console.log('🧪 测试:倒计时已生成 at', x, y);
};

window.testSpawnFalling = function(){
  if(!game) return;
  var x = Math.floor(COLS/2);
  var y = Math.floor(ROWS/2) + 3;
  if(game.snake && game.snake[0]){
    var hx = game.snake[0].x, hy = game.snake[0].y;
    if(Math.abs(x-hx)<2 && Math.abs(y-hy)<2){ y = (y+4) % ROWS; }
  }
  game.warnings.push({x:x, y:y, state:'falling', stateTime:0, warnDuration:1500, countDuration:3000, fallDuration:600, fallY:-CELL*2});
  console.log('🧪 测试:马桶落下已生成 at', x, y);
};

window.testFullSequence = function(){
  if(!game) return;
  // 模拟完整流程:警告→倒计时→落下
  var x = Math.floor(COLS/2) + 5;
  var y = Math.floor(ROWS/2);
  if(game.snake && game.snake[0]){
    var hx = game.snake[0].x, hy = game.snake[0].y;
    if(Math.abs(x-hx)<3 && Math.abs(y-hy)<3){ x = (x+6) % COLS; y = (y+3) % ROWS; }
  }
  game.warnings.push({x:x, y:y, state:'warn', stateTime:0, warnDuration:1000, countDuration:2000, fallDuration:500, fallY:-1});
  console.log('🧪 测试:完整流程已生成 at', x, y);
};

window.testClearAll = function(){
  if(!game) return;
  game.warnings = [];
  game.toilets = [];
  console.log('🧪 测试:已清空所有警告和马桶');
};

window.testRandomWave = function(){
  if(!game) return;
  // 模拟难度5的一波
  var d = 5;
  for(var i=0;i<d*3;i++){
    var pos = null, tries = 0;
    while(tries < 100){
      pos = { x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS) };
      tries++;
      if(!isOccupied(pos.x,pos.y,true,true) && !isTooCloseToSnake(pos) && !isWarningAt(pos.x,pos.y)){
        break;
      }
      pos = null;
    }
    if(pos){
      game.warnings.push({x:pos.x, y:pos.y, state:'warn', stateTime:0, warnDuration:800+Math.random()*800, countDuration:2000+Math.random()*1000, fallDuration:400+Math.random()*200, fallY:-1});
    }
  }
  console.log('🧪 测试:随机一波已生成');
};

// 设置难度并测试
window.testSetDifficulty = function(d){
  if(!game) return;
  game.difficulty = d;
  game.toiletWaveInterval = 5000;
  game.toiletSpawnQueue = [];
  game.toiletSpawnTimer = 0;
  console.log('🧪 测试:难度已设置为 ' + d + ', 每波数量: ' + (d*4) + '个, 间隔: 5秒, 随机分散掉落');
  var el = document.getElementById('difficulty');
  if(el) el.textContent = d;
};

// 难度10级测试 - 快速生成多波马桶验证
window.testDifficulty10 = function(){
  if(!game) return;
  game.difficulty = 10;
  game.toiletWaveInterval = 5000;
  game.currentWave = 0;
  
  console.log('========== 难度10级测试开始 ==========');
  console.log('配置: 难度=10, 每波数量=' + (10*4) + '个, 间隔=5秒');
  console.log('警告时间: warn=1s + countdown=2s = 总计3秒');
  console.log('');
  
  // 立即生成第一波
  var wave1Count = 10 * 4;
  console.log('第1波: 准备生成 ' + wave1Count + ' 个马桶...');
  var w1 = spawnTestWave(wave1Count);
  console.log('第1波完成: 成功生成 ' + w1.success + ' 个警告, 失败 ' + w1.fail + ' 个');
  
  // 延迟生成后续波
  var testResults = [{wave:1, count:w1.success, time:Date.now()}];
  var waveNum = 2;
  
  var interval = setInterval(function(){
    if(waveNum > 3 || game.state === STATE.OVER){
      clearInterval(interval);
      console.log('');
      console.log('========== 测试结果汇总 ==========');
      console.log('总波数: ' + testResults.length);
      for(var r of testResults){
        console.log('第' + r.wave + '波: ' + r.count + ' 个马桶警告');
      }
      console.log('总计生成: ' + testResults.reduce(function(s,r){return s+r.count;},0) + ' 个警告');
      console.log('预期每波: 40个 (难度10 × 4)');
      console.log('警告状态检查:');
      var warnState = {warn:0, countdown:0, falling:0};
      for(var w of game.warnings){
        if(warnState[w.state] !== undefined) warnState[w.state]++;
      }
      console.log('  warn(警告): ' + warnState.warn + ' 个');
      console.log('  countdown(倒计时): ' + warnState.countdown + ' 个');
      console.log('  falling(落下中): ' + warnState.falling + ' 个');
      console.log('当前已落地马桶: ' + game.toilets.length + ' 个');
      console.log('===================================');
      return;
    }
    
    var count = 10 * 4;
    console.log('第' + waveNum + '波: 生成 ' + count + ' 个马桶...');
    var result = spawnTestWave(count);
    testResults.push({wave: waveNum, count: result.success, time: Date.now()});
    waveNum++;
  }, 500); // 0.5秒间隔快速测试
  
  document.getElementById('difficulty').textContent = 10;
};

// 辅助函数:生成一波测试马桶
function spawnTestWave(count){
  var success = 0;
  var fail = 0;
  for(var i=0;i<count;i++){
    var pos = null;
    var tries = 0;
    while(tries < 100){
      pos = { x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS) };
      tries++;
      if(!isOccupied(pos.x,pos.y,true,true) && !isTooCloseToSnake(pos) && !isWarningAt(pos.x,pos.y)){
        break;
      }
      pos = null;
    }
    if(pos){
      game.warnings.push({
        x: pos.x, 
        y: pos.y, 
        state: 'warn',
        stateTime: 0,
        warnDuration: 1000,
        countDuration: 2000,
        fallDuration: 400,
        fallY: -1,
        spawnTime: Date.now()
      });
      success++;
    } else {
      fail++;
    }
  }
  return {success: success, fail: fail};
}

// 强制生成一波马桶(忽略碰撞检测)
window.testForceWave = function(d){
  if(!game) return;
  d = d || 10;
  var count = d * 4;
  console.log('强制生成难度' + d + '的一波: ' + count + '个马桶');
  var success = 0;
  for(var i=0;i<count;i++){
    var x = Math.floor(Math.random()*COLS);
    var y = Math.floor(Math.random()*ROWS);
    game.warnings.push({
      x: x, y: y, state: 'warn', stateTime: 0,
      warnDuration: 1000, countDuration: 2000, fallDuration: 400, fallY: -1,
      spawnTime: Date.now()
    });
    success++;
  }
  console.log('成功生成: ' + success + ' 个警告');
};

// 统计当前测试状态
window.testCheckStatus = function(){
  if(!game) return;
  var status = {
    difficulty: game.difficulty,
    waveInterval: game.toiletWaveInterval + 'ms',
    warnings: game.warnings.length,
    toilets: game.toilets.length,
    warnState: {warn:0, countdown:0, falling:0}
  };
  for(var w of game.warnings){
    if(status.warnState[w.state] !== undefined) status.warnState[w.state]++;
  }
  console.log('========== 游戏状态检查 ==========');
  console.log('难度: ' + status.difficulty);
  console.log('波次间隔: ' + status.waveInterval);
  console.log('警告总数: ' + status.warnings);
  console.log('  - warn(橙色): ' + status.warnState.warn);
  console.log('  - countdown(红色): ' + status.warnState.countdown);
  console.log('  - falling(落下中): ' + status.warnState.falling);
  console.log('已落地马桶: ' + status.toilets);
  console.log('==================================');
  return status;
};

// 显示/隐藏测试面板
window.toggleTestPanel = function(){
  var panel = document.getElementById('testPanel');
  var toggle = document.getElementById('testToggle');
  if(panel.classList.contains('visible')){
    panel.classList.remove('visible');
    toggle.classList.remove('visible');
  } else {
    panel.classList.add('visible');
    toggle.classList.add('visible');
  }
};

// 初始化测试面板
document.getElementById('testToggle').addEventListener('click', window.toggleTestPanel);

// 添加测试快捷键
document.addEventListener('keydown', function(e){
  if(!game || game.state !== STATE.PLAYING) return;
  var k = e.key.toLowerCase();
  if(k === 'h'){
    e.preventDefault();
    window.toggleTestPanel();
  } else if(k === 'w'){
    e.preventDefault();
    window.testSpawnWarning();
  } else if(k === 'e'){
    e.preventDefault();
    window.testSpawnCountdown();
  } else if(k === 'r'){
    e.preventDefault();
    window.testSpawnFalling();
  } else if(k === 't'){
    e.preventDefault();
    window.testFullSequence();
  } else if(k === 'q'){
    e.preventDefault();
    window.testRandomWave();
  } else if(k === 'c'){
    e.preventDefault();
    window.testClearAll();
  } else if(k === 'd'){
    e.preventDefault();
    window.testDifficulty10();
  } else if(k === 's'){
    e.preventDefault();
    window.testCheckStatus();
  }
});

console.log('🧪 测试模式已激活!按 H 显示测试面板,或直接按 W/E/R/T/Q/C/D/S 快捷键');

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

Game Source: 便便蛇蛇 - Poop Snake

Creator: RocketWolf27

Libraries: none

Complexity: complex (2728 lines, 99.4 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: poop-snake-rocketwolf27" to link back to the original. Then publish at arcadelab.ai/publish.