【Claude Code】试生成的贪吃蛇游戏
·
提问
帮我生成一个有背景音乐,有动画,画面精美,动感十足的贪吃蛇游戏,单机能持久化分数,可以暂停和继续,并且有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 移动 · P 暂停 · M 静音 · 支持触屏滑动</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>
更多推荐



所有评论(0)