提问

帮我生成一个有背景音乐,有动画,画面精美,动感十足的贪吃蛇游戏,单机能持久化分数,可以暂停和继续,并且有4种不同关卡,对应春夏秋冬的场景。

代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>四季贪吃蛇</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
  font-family:'Microsoft YaHei','PingFang SC',sans-serif;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  min-height:100vh; overflow:hidden; transition:background 1.2s ease;
}
body.spring { background:radial-gradient(ellipse at 50% 0%, #fce4ec 0%, #f48fb1 25%, #a5d6a7 65%, #66bb6a 100%); }
body.summer { background:radial-gradient(ellipse at 50% 0%, #e1f5fe 0%, #4fc3f7 35%, #81c784 65%, #2e7d32 100%); }
body.autumn { background:radial-gradient(ellipse at 50% 0%, #fff9c4 0%, #ffcc02 20%, #ff8f00 55%, #bf360c 85%, #4e342e 100%); }
body.winter { background:radial-gradient(ellipse at 50% 0%, #f5f5f5 0%, #e3f2fd 30%, #90caf9 65%, #3f51b5 100%); }

#game-wrap { display:flex; flex-direction:column; align-items:center; gap:12px; z-index:1; }

#hud {
  width:600px; display:flex; align-items:center; justify-content:space-between;
  padding:10px 22px; background:rgba(0,0,0,0.45); border-radius:14px; color:#fff;
  backdrop-filter:blur(12px); border:1px solid rgba(255,255,255,0.15);
}
.hud-sc { text-align:center; }
.hud-lb { font-size:11px; letter-spacing:1px; opacity:.7; text-transform:uppercase; }
.hud-vl { font-size:28px; font-weight:900; line-height:1.1; }
#season-tag {
  font-size:15px; font-weight:700; padding:6px 16px;
  background:rgba(255,255,255,0.2); border-radius:20px;
  border:1px solid rgba(255,255,255,0.3);
}
#canvas-wrap {
  position:relative; border-radius:16px; overflow:hidden;
  box-shadow:0 0 0 2px rgba(255,255,255,0.2), 0 12px 50px rgba(0,0,0,0.5);
}
#gameCanvas { display:block; }

#btns { display:flex; gap:10px; }
.btn {
  padding:9px 22px; border:1px solid rgba(255,255,255,0.3); border-radius:22px;
  background:rgba(0,0,0,0.4); color:#fff; font-size:14px; font-weight:600;
  cursor:pointer; backdrop-filter:blur(8px); transition:all .15s;
}
.btn:hover { background:rgba(255,255,255,0.2); transform:translateY(-2px); }
.btn:active { transform:translateY(0); }
.btn.acc { background:rgba(255,255,255,0.88); color:#333; border-color:transparent; }
.btn.acc:hover { background:#fff; }
#hints { font-size:12px; color:rgba(255,255,255,0.6); letter-spacing:.5px; }

/* Overlays */
.ov {
  position:fixed; inset:0; display:flex; align-items:center; justify-content:center;
  background:rgba(0,0,0,0.65); backdrop-filter:blur(10px); z-index:200;
}
.ov.hidden { display:none; }
.card {
  background:rgba(15,15,35,0.65); border:1px solid rgba(255,255,255,0.2);
  border-radius:28px; padding:44px 52px; color:#fff; text-align:center;
  backdrop-filter:blur(24px); box-shadow:0 12px 60px rgba(0,0,0,0.5);
  max-width:480px; width:92%;
}
.big-t { font-size:44px; font-weight:900; margin-bottom:6px; }
.sub-t { font-size:15px; opacity:.72; margin-bottom:28px; }

.sgrid { display:grid; grid-template-columns:1fr 1fr; gap:14px; margin-bottom:24px; }
.sc {
  padding:18px 12px; border-radius:16px; cursor:pointer;
  border:2px solid transparent; transition:transform .2s, border-color .2s, box-shadow .2s;
}
.sc:hover { transform:scale(1.05); box-shadow:0 4px 20px rgba(0,0,0,0.3); }
.sc.on { border-color:#fff; box-shadow:0 0 20px rgba(255,255,255,0.35); }
.sc .ico { font-size:34px; margin-bottom:6px; }
.sc .nm  { font-size:15px; font-weight:700; color:#fff; }
.sc .bs  { font-size:11px; color:rgba(255,255,255,.75); margin-top:2px; }
.sc0 { background:linear-gradient(135deg,#ec407a,#66bb6a); }
.sc1 { background:linear-gradient(135deg,#29b6f6,#43a047); }
.sc2 { background:linear-gradient(135deg,#ffa726,#e64a19); }
.sc3 { background:linear-gradient(135deg,#7986cb,#ab47bc); }

.score-big {
  font-size:64px; font-weight:900; margin:10px 0;
  background:linear-gradient(135deg,#ffd700,#ff6b6b);
  -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text;
}
.spop {
  position:absolute; font-size:22px; font-weight:900; color:#fff;
  text-shadow:0 2px 10px rgba(0,0,0,.6); pointer-events:none;
  animation:spopA .9s ease-out forwards; z-index:10;
  transform:translateX(-50%);
}
@keyframes spopA {
  0%   { transform:translateX(-50%) translateY(0) scale(1); opacity:1; }
  60%  { transform:translateX(-50%) translateY(-38px) scale(1.3); opacity:1; }
  100% { transform:translateX(-50%) translateY(-68px) scale(.8); opacity:0; }
}
</style>
</head>
<body class="spring">

<!-- Start -->
<div class="ov" id="scrStart">
  <div class="card">
    <div class="big-t">🐍 四季贪吃蛇</div>
    <div class="sub-t">选择季节关卡,踏上美丽冒险之旅!</div>
    <div class="sgrid">
      <div class="sc sc0 on" data-s="0" onclick="pickSeason(0)">
        <div class="ico">🌸</div><div class="nm">春 · Spring</div><div class="bs" id="b0">最高: 0</div>
      </div>
      <div class="sc sc1" data-s="1" onclick="pickSeason(1)">
        <div class="ico">☀️</div><div class="nm">夏 · Summer</div><div class="bs" id="b1">最高: 0</div>
      </div>
      <div class="sc sc2" data-s="2" onclick="pickSeason(2)">
        <div class="ico">🍂</div><div class="nm">秋 · Autumn</div><div class="bs" id="b2">最高: 0</div>
      </div>
      <div class="sc sc3" data-s="3" onclick="pickSeason(3)">
        <div class="ico">❄️</div><div class="nm">冬 · Winter</div><div class="bs" id="b3">最高: 0</div>
      </div>
    </div>
    <button class="btn acc" style="width:100%;padding:14px;font-size:17px;" onclick="startGame()">▶ 开始游戏</button>
  </div>
</div>

<!-- Pause -->
<div class="ov hidden" id="scrPause">
  <div class="card" style="padding:36px 48px;">
    <div class="big-t" style="font-size:38px;">⏸ 游戏暂停</div>
    <div class="sub-t">随时可以继续冒险!</div>
    <div style="display:flex;gap:12px;justify-content:center;">
      <button class="btn acc" onclick="togglePause()">▶ 继续</button>
      <button class="btn" onclick="goMenu()">🏠 菜单</button>
    </div>
  </div>
</div>

<!-- Game Over -->
<div class="ov hidden" id="scrOver">
  <div class="card">
    <div class="big-t" style="font-size:36px;" id="overTitle">💀 游戏结束</div>
    <div class="sub-t" id="overMsg">再接再厉!</div>
    <div class="score-big" id="overScore">0</div>
    <div style="opacity:.7;font-size:14px;margin-bottom:28px;" id="overBest">历史最高: 0</div>
    <div style="display:flex;gap:12px;justify-content:center;">
      <button class="btn acc" onclick="restartGame()">🔄 再来一次</button>
      <button class="btn" onclick="goMenu()">🏠 换关卡</button>
    </div>
  </div>
</div>

<!-- Game -->
<div id="game-wrap">
  <div id="hud">
    <div class="hud-sc"><div class="hud-lb">得 分</div><div class="hud-vl" id="hudScore">0</div></div>
    <div id="season-tag">🌸 春天</div>
    <div class="hud-sc"><div class="hud-lb">最 高</div><div class="hud-vl" id="hudBest">0</div></div>
  </div>
  <div id="canvas-wrap">
    <canvas id="gameCanvas" width="600" height="600"></canvas>
  </div>
  <div id="btns">
    <button class="btn" onclick="togglePause()">⏸ 暂停 [P]</button>
    <button class="btn" id="muteBtn" onclick="toggleMute()">🔊 音效 [M]</button>
    <button class="btn" onclick="goMenu()">🏠 菜单</button>
  </div>
  <div id="hints">方向键 / WASD 移动 &nbsp;·&nbsp; P 暂停 &nbsp;·&nbsp; M 静音 &nbsp;·&nbsp; 支持触屏滑动</div>
</div>

<script>
// ── Config ──────────────────────────────────────────────────────────────────
const GRID = 30, CELL = 20, W = 600, H = 600;

const CFG = [
  { // 0 = Spring
    name:'春天', badge:'🌸 春天', cls:'spring',
    hd:'#1b5e20', body:['#388e3c','#66bb6a','#81c784','#a5d6a7'],
    foodClr:'#e91e63', foodEmoji:'🍓',
    bg:['#fce4ec','#f8bbd0','#dcedc8','#c8e6c9'],
    grid:'rgba(255,255,255,0.06)', ptType:'petal',
    spd:185, spdInc:2,
    melBase:261.63, mel:[0,4,7,4,0,2,5,9,7,5,4,2], noteMs:215, oscType:'sine'
  },
  { // 1 = Summer
    name:'夏天', badge:'☀️ 夏天', cls:'summer',
    hd:'#01579b', body:['#0277bd','#03a9f4','#29b6f6','#81d4fa'],
    foodClr:'#ff5722', foodEmoji:'🍉',
    bg:['#e1f5fe','#81d4fa','#a5d6a7','#2e7d32'],
    grid:'rgba(255,255,255,0.05)', ptType:'firefly',
    spd:160, spdInc:3,
    melBase:392.00, mel:[0,2,4,7,4,2,0,2,4,9,7,4], noteMs:175, oscType:'triangle'
  },
  { // 2 = Autumn
    name:'秋天', badge:'🍂 秋天', cls:'autumn',
    hd:'#3e2723', body:['#6d4c41','#a1887f','#bf8070','#d4956a'],
    foodClr:'#c62828', foodEmoji:'🍎',
    bg:['#fff9c4','#ffe082','#ff8f00','#bf360c'],
    grid:'rgba(0,0,0,0.07)', ptType:'leaf',
    spd:145, spdInc:3,
    melBase:440.00, mel:[0,-3,-5,-7,-5,-3,0,-2,-3,-5,-7,-5], noteMs:250, oscType:'sine'
  },
  { // 3 = Winter
    name:'冬天', badge:'❄️ 冬天', cls:'winter',
    hd:'#1a237e', body:['#283593','#3949ab','#5c6bc0','#9fa8da'],
    foodClr:'#bbdefb', foodEmoji:'⭐',
    bg:['#eceff1','#cfd8dc','#90a4ae','#3949ab'],
    grid:'rgba(255,255,255,0.08)', ptType:'snow',
    spd:130, spdInc:4,
    melBase:261.63, mel:[0,2,4,2,0,-3,0,2,4,7,4,2], noteMs:300, oscType:'sine'
  }
];

function noteFreq(base, semi) { return base * Math.pow(2, semi / 12); }

// ── State ────────────────────────────────────────────────────────────────────
let season=0, selSeason=0;
let score=0, hiScores=[0,0,0,0];
let snake=[], dir={x:1,y:0}, nDir={x:1,y:0};
let food={x:15,y:15};
let particles=[];
let running=false, paused=false, muted=false;
let gameInterval=null, rafId=null;
let speed=185, flashAlpha=0, foodAnim=0;
let noteIdx=0, musicTimer=null;

// ── Persistence ───────────────────────────────────────────────────────────────
function loadHi() {
  try { const d = localStorage.getItem('s4hi'); if (d) hiScores = JSON.parse(d); } catch(e){}
  refreshHiUI();
}
function saveHi() { try { localStorage.setItem('s4hi', JSON.stringify(hiScores)); } catch(e){} }
function refreshHiUI() {
  for (let i=0;i<4;i++) { const el=document.getElementById('b'+i); if(el) el.textContent='最高: '+hiScores[i]; }
}

// ── Audio ────────────────────────────────────────────────────────────────────
let ac=null, masterGain=null;

function initAC() {
  if (ac) return;
  ac = new (window.AudioContext||window.webkitAudioContext)();
  masterGain = ac.createGain(); masterGain.gain.value=0.18;
  masterGain.connect(ac.destination);
}

function pNote(freq, dur, type='sine', vol=0.35) {
  if (!ac||muted) return;
  const o=ac.createOscillator(), g=ac.createGain();
  o.connect(g); g.connect(masterGain);
  o.frequency.value=freq; o.type=type;
  const t=ac.currentTime;
  g.gain.setValueAtTime(vol, t);
  g.gain.exponentialRampToValueAtTime(0.001, t+dur);
  o.start(t); o.stop(t+dur+0.05);
}

function sfxEat() {
  if (!ac||muted) return;
  pNote(880,0.08,'square',0.25);
  setTimeout(()=>pNote(1108.73,0.12,'square',0.15),65);
}
function sfxDie() {
  if (!ac||muted) return;
  pNote(220,0.25,'sawtooth',0.35);
  setTimeout(()=>pNote(185,0.4,'sawtooth',0.25),120);
  setTimeout(()=>pNote(140,0.7,'sawtooth',0.15),360);
}

function startMusic() {
  if (!ac) return;
  ac.resume().then(()=>{ stopMusic(); noteIdx=0; tickMusic(); });
}
function stopMusic() { if(musicTimer){clearTimeout(musicTimer);musicTimer=null;} }
function tickMusic() {
  if (!running||paused||muted||!ac) return;
  const c=CFG[season];
  const freq=noteFreq(c.melBase, c.mel[noteIdx % c.mel.length]);
  pNote(freq, (c.noteMs/1000)*0.75, c.oscType, 0.18);
  if (noteIdx%4===0) pNote(noteFreq(c.melBase/2, c.mel[noteIdx%c.mel.length]), (c.noteMs/1000)*0.6, 'sine', 0.07);
  noteIdx++;
  musicTimer = setTimeout(tickMusic, c.noteMs);
}

function toggleMute() {
  muted=!muted;
  document.getElementById('muteBtn').textContent = muted ? '🔇 音效 [M]' : '🔊 音效 [M]';
  if (!muted&&running&&!paused) startMusic();
}

// ── Particles ────────────────────────────────────────────────────────────────
const PETAL_C=['#f48fb1','#f06292','#fce4ec','#ff80ab','#ffcdd2'];
const LEAF_C=['#e65100','#d84315','#ff8f00','#bf360c','#795548','#f57f17'];
const FIRE_C=['#ffff00','#aaff44','#00ffaa','#ffdd44'];

function mkPt(type, init=false) {
  const p={type, x:Math.random()*W, y:init?Math.random()*H:-30,
    rot:Math.random()*Math.PI*2, rotV:(Math.random()-.5)*0.06, alpha:Math.random()*0.5+0.4};
  if (type==='petal') {
    p.color=PETAL_C[~~(Math.random()*5)]; p.w=Math.random()*10+6; p.h=Math.random()*6+4;
    p.vy=Math.random()*1.1+0.4; p.vx=(Math.random()-.5)*0.6;
  } else if (type==='leaf') {
    p.color=LEAF_C[~~(Math.random()*6)]; p.w=Math.random()*14+8; p.h=Math.random()*8+5;
    p.vy=Math.random()*1.7+0.6; p.vx=(Math.random()-.5)*1.5;
  } else if (type==='snow') {
    p.r=Math.random()*4+2; p.vy=Math.random()*0.85+0.3; p.vx=(Math.random()-.5)*0.4;
  } else if (type==='firefly') {
    p.color=FIRE_C[~~(Math.random()*4)]; p.r=Math.random()*3+2;
    p.y=Math.random()*H; p.vx=(Math.random()-.5)*0.8; p.vy=(Math.random()-.5)*0.6;
    p.phase=Math.random()*Math.PI*2;
  } else if (type==='burst') {
    p.vx=Math.cos(p.angle)*(Math.random()*4+1.5);
    p.vy=Math.sin(p.angle)*(Math.random()*4+1.5);
    p.r=Math.random()*4+3; p.decay=0.04; p.alpha=1;
  }
  return p;
}

function initParticles() {
  const type=CFG[season].ptType;
  const cnt=type==='snow'?55:type==='petal'?35:type==='leaf'?28:22;
  particles=Array.from({length:cnt},()=>mkPt(type,true));
}

function burstAt(cx,cy,color) {
  for (let i=0;i<10;i++) {
    const p=mkPt('burst');
    p.x=cx; p.y=cy; p.angle=(i/10)*Math.PI*2; p.color=color;
    p.vx=Math.cos(p.angle)*(Math.random()*4+1.5);
    p.vy=Math.sin(p.angle)*(Math.random()*4+1.5);
    particles.push(p);
  }
}

function updateParticles() {
  const type=CFG[season].ptType;
  for (let i=particles.length-1;i>=0;i--) {
    const p=particles[i];
    if (p.type==='burst') {
      p.x+=p.vx; p.y+=p.vy; p.vy+=0.18; p.alpha-=p.decay;
      if (p.alpha<=0) { particles.splice(i,1); continue; }
    } else if (p.type==='firefly') {
      p.phase+=0.04;
      p.x+=p.vx+Math.sin(p.phase*.7)*.5; p.y+=p.vy+Math.cos(p.phase*.5)*.3;
      if (p.x<-10) p.x=W+10; if (p.x>W+10) p.x=-10;
      if (p.y<-10) p.y=H+10; if (p.y>H+10) p.y=-10;
    } else {
      p.x+=p.vx; p.y+=p.vy; p.rot+=p.rotV;
      if (p.y>H+30) particles[i]=mkPt(type);
    }
  }
}

function drawParticles(ctx) {
  particles.forEach(p=>{
    ctx.save();
    ctx.globalAlpha=p.alpha;
    if (p.type==='burst') {
      ctx.shadowColor=p.color; ctx.shadowBlur=8; ctx.fillStyle=p.color;
      ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
    } else if (p.type==='firefly') {
      const g=(Math.sin(p.phase)+1)/2;
      ctx.globalAlpha=p.alpha*(g*.85+0.1);
      ctx.shadowColor=p.color; ctx.shadowBlur=12; ctx.fillStyle=p.color;
      ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
    } else if (p.type==='snow') {
      ctx.translate(p.x,p.y); ctx.rotate(p.rot);
      ctx.fillStyle='rgba(255,255,255,.9)'; ctx.shadowColor='#c5e8ff'; ctx.shadowBlur=7;
      for (let a=0;a<6;a++) {
        ctx.save(); ctx.rotate(a*Math.PI/3);
        ctx.fillRect(-.9,0,1.8,p.r*1.9); ctx.restore();
      }
      ctx.beginPath(); ctx.arc(0,0,p.r*.55,0,Math.PI*2); ctx.fill();
    } else if (p.type==='petal') {
      ctx.translate(p.x,p.y); ctx.rotate(p.rot);
      ctx.fillStyle=p.color; ctx.shadowColor=p.color; ctx.shadowBlur=5;
      ctx.beginPath(); ctx.ellipse(0,0,p.w/2,p.h/2,0,0,Math.PI*2); ctx.fill();
    } else if (p.type==='leaf') {
      ctx.translate(p.x,p.y); ctx.rotate(p.rot);
      ctx.fillStyle=p.color; ctx.shadowColor=p.color; ctx.shadowBlur=4;
      ctx.beginPath();
      ctx.moveTo(0,-p.h/2);
      ctx.bezierCurveTo(p.w/2,-p.h/4, p.w/2,p.h/4, 0,p.h/2);
      ctx.bezierCurveTo(-p.w/2,p.h/4, -p.w/2,-p.h/4, 0,-p.h/2);
      ctx.fill();
      ctx.strokeStyle='rgba(0,0,0,.18)'; ctx.lineWidth=.8;
      ctx.beginPath(); ctx.moveTo(0,-p.h/2); ctx.lineTo(0,p.h/2); ctx.stroke();
    }
    ctx.restore();
  });
}

// ── Canvas Drawing ───────────────────────────────────────────────────────────
const canvas=document.getElementById('gameCanvas');
const ctx=canvas.getContext('2d');

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

function drawBg() {
  const c=CFG[season];
  const gr=ctx.createLinearGradient(0,0,W,H);
  gr.addColorStop(0,c.bg[0]); gr.addColorStop(.3,c.bg[1]);
  gr.addColorStop(.7,c.bg[2]); gr.addColorStop(1,c.bg[3]);
  ctx.fillStyle=gr; ctx.fillRect(0,0,W,H);
  ctx.strokeStyle=c.grid; ctx.lineWidth=.5;
  for (let x=0;x<=W;x+=CELL){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke();}
  for (let y=0;y<=H;y+=CELL){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();}
}

function drawSnake() {
  const c=CFG[season];
  snake.forEach((seg,i)=>{
    const px=seg.x*CELL, py=seg.y*CELL;
    if (i===0) {
      ctx.shadowColor=c.hd; ctx.shadowBlur=14; ctx.fillStyle=c.hd;
      rrect(px+1,py+1,CELL-2,CELL-2,7); ctx.fill(); ctx.shadowBlur=0;
      // highlight
      ctx.fillStyle='rgba(255,255,255,0.18)';
      rrect(px+3,py+3,CELL-6,(CELL-6)/2,3); ctx.fill();
      // eyes
      const d=dir.x>0?'R':dir.x<0?'L':dir.y<0?'U':'D';
      const EYES={R:[{ex:14,ey:5},{ex:14,ey:13}],L:[{ex:4,ey:5},{ex:4,ey:13}],
                  U:[{ex:5,ey:4},{ex:13,ey:4}],D:[{ex:5,ey:14},{ex:13,ey:14}]};
      EYES[d].forEach(e=>{
        ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(px+e.ex,py+e.ey,3,0,Math.PI*2); ctx.fill();
        ctx.fillStyle='#111'; ctx.beginPath(); ctx.arc(px+e.ex+dir.x*1.2,py+e.ey+dir.y*1.2,1.5,0,Math.PI*2); ctx.fill();
      });
    } else {
      const ci=Math.min(i-1,c.body.length-1);
      const sh=Math.min(i*.12,2.5);
      ctx.shadowColor=c.body[0]; ctx.shadowBlur=7; ctx.fillStyle=c.body[ci];
      rrect(px+2+sh/2,py+2+sh/2,CELL-4-sh,CELL-4-sh,5); ctx.fill(); ctx.shadowBlur=0;
      if (i%2===0) {
        ctx.fillStyle='rgba(255,255,255,0.1)';
        rrect(px+3+sh/2,py+3+sh/2,(CELL-6-sh)/2,(CELL-6-sh)/2,2); ctx.fill();
      }
    }
  });
}

function drawFood() {
  const c=CFG[season];
  foodAnim+=0.07;
  const pulse=Math.sin(foodAnim)*2;
  const cx=food.x*CELL+CELL/2, cy=food.y*CELL+CELL/2;
  const r=CELL/2-2+Math.abs(pulse)*.4;
  // glow ring
  ctx.strokeStyle=c.foodClr+'60'; ctx.lineWidth=2.5;
  ctx.shadowColor=c.foodClr; ctx.shadowBlur=20+pulse*2;
  ctx.beginPath(); ctx.arc(cx,cy,r+4,0,Math.PI*2); ctx.stroke();
  // body
  ctx.fillStyle=c.foodClr; ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.fill();
  ctx.shadowBlur=0;
  // emoji
  ctx.font=`${CELL-3}px serif`; ctx.textAlign='center'; ctx.textBaseline='middle';
  ctx.fillText(c.foodEmoji,cx,cy+1);
}

function drawFlash() {
  if (flashAlpha>0) {
    ctx.fillStyle=`rgba(255,255,200,${flashAlpha})`;
    ctx.fillRect(0,0,W,H);
    flashAlpha=Math.max(0,flashAlpha-0.07);
  }
}

function render() {
  ctx.clearRect(0,0,W,H);
  drawBg(); drawParticles(ctx); drawFood(); drawSnake(); drawFlash();
  if (running) updateParticles();
  rafId=requestAnimationFrame(render);
}

// ── Game Logic ───────────────────────────────────────────────────────────────
function initSnake() {
  snake=[{x:15,y:15},{x:14,y:15},{x:13,y:15}];
  dir={x:1,y:0}; nDir={x:1,y:0};
}

function placeFood() {
  let p;
  do { p={x:~~(Math.random()*GRID),y:~~(Math.random()*GRID)}; }
  while (snake.some(s=>s.x===p.x&&s.y===p.y));
  food=p;
}

function tick() {
  if (!running||paused) return;
  dir={...nDir};
  const hd={x:snake[0].x+dir.x, y:snake[0].y+dir.y};
  if (hd.x<0||hd.x>=GRID||hd.y<0||hd.y>=GRID){doGameOver();return;}
  if (snake.slice(0,-1).some(s=>s.x===hd.x&&s.y===hd.y)){doGameOver();return;}
  snake.unshift(hd);
  if (hd.x===food.x&&hd.y===food.y) {
    score+=10;
    document.getElementById('hudScore').textContent=score;
    flashAlpha=0.55; sfxEat();
    burstAt(food.x*CELL+CELL/2, food.y*CELL+CELL/2, CFG[season].foodClr);
    showPop(food.x*CELL+CELL/2, food.y*CELL);
    placeFood();
    speed=Math.max(60, speed-CFG[season].spdInc);
    resetInterval();
  } else { snake.pop(); }
}

function resetInterval() { clearInterval(gameInterval); gameInterval=setInterval(tick,speed); }

function doGameOver() {
  running=false; clearInterval(gameInterval); gameInterval=null;
  stopMusic(); sfxDie();
  if (score>hiScores[season]) { hiScores[season]=score; saveHi(); refreshHiUI(); }
  document.getElementById('overScore').textContent=score;
  document.getElementById('overBest').textContent='历史最高: '+hiScores[season];
  const emoji=score>=200?'🏆':score>=100?'🌟':'💀';
  document.getElementById('overTitle').textContent=emoji+(score>=100?' 精彩表现!':' 游戏结束');
  document.getElementById('overMsg').textContent=score>=100?CFG[season].name+'的冒险圆满完成!':'很可惜,再接再厉!';
  document.getElementById('scrOver').classList.remove('hidden');
}

// ── Score Pop ────────────────────────────────────────────────────────────────
function showPop(x,y) {
  const wrap=document.getElementById('canvas-wrap');
  const d=document.createElement('div');
  d.className='spop'; d.textContent='+10';
  d.style.left=x+'px'; d.style.top=y+'px';
  wrap.appendChild(d); setTimeout(()=>d.remove(),950);
}

// ── UI ───────────────────────────────────────────────────────────────────────
function pickSeason(i) {
  selSeason=i;
  document.querySelectorAll('.sc').forEach(c=>c.classList.remove('on'));
  document.querySelector('[data-s="'+i+'"]').classList.add('on');
  document.body.className=CFG[i].cls;
}

function startGame() {
  season=selSeason; score=0; speed=CFG[season].spd;
  document.body.className=CFG[season].cls;
  document.getElementById('season-tag').textContent=CFG[season].badge;
  document.getElementById('hudScore').textContent='0';
  document.getElementById('hudBest').textContent=hiScores[season];
  document.getElementById('scrStart').classList.add('hidden');
  document.getElementById('scrOver').classList.add('hidden');
  initSnake(); placeFood(); initParticles();
  running=true; paused=false;
  cancelAnimationFrame(rafId); resetInterval(); render();
  initAC(); setTimeout(startMusic,80);
}

function restartGame() {
  document.getElementById('scrOver').classList.add('hidden');
  score=0; speed=CFG[season].spd;
  document.getElementById('hudScore').textContent='0';
  document.getElementById('hudBest').textContent=hiScores[season];
  initSnake(); placeFood(); initParticles();
  running=true; paused=false;
  cancelAnimationFrame(rafId); resetInterval(); render(); startMusic();
}

function goMenu() {
  running=false; paused=false;
  clearInterval(gameInterval); gameInterval=null;
  stopMusic(); cancelAnimationFrame(rafId);
  document.getElementById('scrPause').classList.add('hidden');
  document.getElementById('scrOver').classList.add('hidden');
  document.getElementById('scrStart').classList.remove('hidden');
  refreshHiUI();
}

function togglePause() {
  if (!running) return;
  paused=!paused;
  if (paused) {
    document.getElementById('scrPause').classList.remove('hidden');
    clearInterval(gameInterval); gameInterval=null; stopMusic();
  } else {
    document.getElementById('scrPause').classList.add('hidden');
    resetInterval(); startMusic();
  }
}

// ── Input ────────────────────────────────────────────────────────────────────
const KMAP={ArrowUp:'U',w:'U',W:'U',ArrowDown:'D',s:'D',S:'D',
            ArrowLeft:'L',a:'L',A:'L',ArrowRight:'R',d:'R',D:'R'};
const DMAP={U:{x:0,y:-1},D:{x:0,y:1},L:{x:-1,y:0},R:{x:1,y:0}};

document.addEventListener('keydown',e=>{
  const k=KMAP[e.key];
  if (k) { e.preventDefault(); const nd=DMAP[k]; if(nd.x!==-dir.x||nd.y!==-dir.y) nDir=nd; }
  if (e.key==='p'||e.key==='P') togglePause();
  if (e.key==='m'||e.key==='M') toggleMute();
});

let ts=null;
canvas.addEventListener('touchstart',e=>{ts={x:e.touches[0].clientX,y:e.touches[0].clientY};e.preventDefault();},{passive:false});
canvas.addEventListener('touchend',e=>{
  if (!ts) return;
  const dx=e.changedTouches[0].clientX-ts.x, dy=e.changedTouches[0].clientY-ts.y; ts=null;
  let nd=null;
  if (Math.abs(dx)>Math.abs(dy)) { if(dx>20) nd={x:1,y:0}; else if(dx<-20) nd={x:-1,y:0}; }
  else { if(dy>20) nd={x:0,y:1}; else if(dy<-20) nd={x:0,y:-1}; }
  if (nd&&(nd.x!==-dir.x||nd.y!==-dir.y)) nDir=nd;
  e.preventDefault();
},{passive:false});

// ── Boot ─────────────────────────────────────────────────────────────────────
loadHi();
// Draw initial canvas
(()=>{
  const g=ctx.createLinearGradient(0,0,W,H);
  g.addColorStop(0,'#fce4ec'); g.addColorStop(1,'#c8e6c9');
  ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
})();
</script>
</body>
</html>
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐