yuanbao-game
/ index.html
6107 字节
<!DOCTYPE html>
<html lang="zh">
<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 {
background: #0f0f1a;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: 'Courier New', monospace;
color: #e0e0e0;
user-select: none;
}
h1 { color: #e11d48; margin-bottom: 10px; font-size: 1.8rem; }
.score-board {
display: flex;
gap: 30px;
margin-bottom: 12px;
font-size: 1.1rem;
}
.score-board span { color: #f59e0b; }
#gameCanvas {
border: 2px solid #e11d48;
border-radius: 8px;
background: #1a1a2e;
box-shadow: 0 0 20px rgba(225,29,72,0.3);
}
.controls {
margin-top: 14px;
display: flex;
gap: 10px;
}
button {
background: #e11d48;
color: white;
border: none;
padding: 8px 20px;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
font-family: inherit;
transition: background 0.2s;
}
button:hover { background: #be123c; }
.tip {
margin-top: 10px;
font-size: 0.85rem;
color: #888;
}
.overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
z-index: 10;
}
.overlay h2 { color: #e11d48; font-size: 2rem; margin-bottom: 10px; }
.overlay p { color: #f59e0b; font-size: 1.2rem; margin-bottom: 20px; }
.hidden { display: none !important; }
</style>
</head>
<body>
<h1>🐍 贪吃蛇</h1>
<div class="score-board">
<div>得分: <span id="score">0</span></div>
<div>最高: <span id="best">0</span></div>
</div>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div class="controls">
<button onclick="startGame()">开始游戏</button>
</div>
<p class="tip">方向键 / WASD 控制方向 | 手机请用下方按钮</p>
<div style="margin-top:10px; display:flex; gap:6px; flex-wrap:wrap; justify-content:center; max-width:200px;">
<button onclick="setDir('up')">⬆️</button>
<button onclick="setDir('left')">⬅️</button>
<button onclick="setDir('down')">⬇️</button>
<button onclick="setDir('right')">➡️</button>
</div>
<div class="overlay" id="overlay">
<h2 id="overlayTitle">🐍 贪吃蛇</h2>
<p id="overlayMsg">准备开始!</p>
<button onclick="startGame()">开始游戏</button>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const GRID = 20;
const COLS = canvas.width / GRID;
const ROWS = canvas.height / GRID;
let snake, dir, nextDir, food, score, best, gameLoop, running;
best = parseInt(localStorage.getItem('snakeBest') || '0');
document.getElementById('best').textContent = best;
function init() {
snake = [{x:10,y:10}];
dir = {x:1,y:0};
nextDir = {x:1,y:0};
score = 0;
document.getElementById('score').textContent = 0;
spawnFood();
}
function spawnFood() {
do {
food = {x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS)};
} while (snake.some(s => s.x===food.x && s.y===food.y));
}
function draw() {
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0,0,canvas.width,canvas.height);
// grid
ctx.strokeStyle = '#22223a';
for(let i=0;i<=COLS;i++) {
ctx.beginPath(); ctx.moveTo(i*GRID,0); ctx.lineTo(i*GRID,canvas.height); ctx.stroke();
}
for(let i=0;i<=ROWS;i++) {
ctx.beginPath(); ctx.moveTo(0,i*GRID); ctx.lineTo(canvas.width,i*GRID); ctx.stroke();
}
// food
ctx.fillStyle = '#e11d48';
ctx.shadowColor = '#e11d48';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(food.x*GRID+GRID/2, food.y*GRID+GRID/2, GRID/2-2, 0, Math.PI*2);
ctx.fill();
ctx.shadowBlur = 0;
// snake
snake.forEach((seg, i) => {
const brightness = 1 - i * 0.03;
ctx.fillStyle = `rgba(${Math.floor(34*brightness)},${Math.floor(197*brightness)},${Math.floor(94*brightness)},1)`;
ctx.shadowColor = '#22c55e';
ctx.shadowBlur = i===0 ? 12 : 4;
ctx.beginPath();
ctx.roundRect(seg.x*GRID+1, seg.y*GRID+1, GRID-2, GRID-2, 4);
ctx.fill();
});
ctx.shadowBlur = 0;
}
function step() {
dir = {...nextDir};
const head = {x: snake[0].x + dir.x, y: snake[0].y + dir.y};
// 穿墙
head.x = (head.x+COLS)%COLS;
head.y = (head.y+ROWS)%ROWS;
// 撞自己
if (snake.some(s => s.x===head.x && s.y===head.y)) {
gameOver(); return;
}
snake.unshift(head);
if (head.x===food.x && head.y===food.y) {
score += 10;
document.getElementById('score').textContent = score;
spawnFood();
// 加速
clearInterval(gameLoop);
const speed = Math.max(80, 150 - Math.floor(score/30)*5);
gameLoop = setInterval(step, speed);
} else {
snake.pop();
}
draw();
}
function gameOver() {
running = false;
clearInterval(gameLoop);
if (score > best) {
best = score;
localStorage.setItem('snakeBest', best);
document.getElementById('best').textContent = best;
}
document.getElementById('overlayTitle').textContent = '💀 游戏结束';
document.getElementById('overlayMsg').textContent = `得分: ${score} 分`;
document.getElementById('overlay').classList.remove('hidden');
}
function startGame() {
document.getElementById('overlay').classList.add('hidden');
clearInterval(gameLoop);
init();
running = true;
draw();
gameLoop = setInterval(step, 150);
}
function setDir(d) {
if (!running) return;
const map = {
up: {x:0,y:-1},
down: {x:0,y:1},
left: {x:-1,y:0},
right: {x:1,y:0}
};
const nd = map[d];
if (nd && !(nd.x===-dir.x && nd.y===-dir.y)) {
nextDir = nd;
}
}
document.addEventListener('keydown', e => {
const map = {
ArrowUp:'up', w:'up', W:'up',
ArrowDown:'down', s:'down', S:'down',
ArrowLeft:'left', a:'left', A:'left',
ArrowRight:'right', d:'right', D:'right',
};
if (map[e.key]) { e.preventDefault(); setDir(map[e.key]); }
});
// init
init();
draw();
</script>
</body>
</html>