<!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, user-scalable=no">
<title>贪吃蛇</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #1a1a2e;
font-family: 'Segoe UI', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
touch-action: none;
user-select: none;
-webkit-user-select: none;
}
.game-container {
background: #16213e;
border-radius: 16px;
padding: 15px;
box-shadow: 0 8px 30px rgba(0,0,0,0.6);
max-width: 95vw;
max-height: 95vh;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.header {
display: flex;
justify-content: space-between;
width: 100%;
color: #e0e0e0;
font-size: 1.2rem;
font-weight: bold;
}
.score {
color: #f7c948;
}
.best {
color: #ff6b6b;
}
canvas {
background: #0f0f1a;
border-radius: 8px;
display: block;
touch-action: none;
box-shadow: inset 0 0 10px rgba(0,0,0,0.5);
}
.controls {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
justify-content: center;
}
.btn {
background: #0f3460;
color: white;
border: none;
border-radius: 8px;
padding: 10px 20px;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s;
min-width: 70px;
}
.btn:hover {
background: #1a4a7a;
}
.btn:active {
transform: scale(0.95);
}
.dpad {
display: grid;
grid-template-columns: repeat(3, 50px);
grid-template-rows: repeat(3, 50px);
gap: 5px;
justify-content: center;
margin-top: 5px;
}
.dpad-btn {
background: #1a1a3a;
border: 2px solid #444;
border-radius: 10px;
color: white;
font-size: 1.5rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background 0.2s;
}
.dpad-btn:active {
background: #2a2a5a;
}
.dpad-btn.up { grid-column: 2; grid-row: 1; }
.dpad-btn.left { grid-column: 1; grid-row: 2; }
.dpad-btn.down { grid-column: 2; grid-row: 3; }
.dpad-btn.right { grid-column: 3; grid-row: 2; }
.status {
color: #aaa;
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="game-container">
<div class="header">
<span>🐍 贪吃蛇</span>
<span>分数: <span id="score">0</span></span>
<span>最高: <span id="best">0</span></span>
</div>
<canvas id="gameCanvas"></canvas>
<div class="controls">
<button class="btn" id="restartBtn">重新开始</button>
<button class="btn" id="pauseBtn">暂停</button>
</div>
<div class="dpad">
<div class="dpad-btn up" data-dir="up">▲</div>
<div class="dpad-btn left" data-dir="left">◀</div>
<div class="dpad-btn down" data-dir="down">▼</div>
<div class="dpad-btn right" data-dir="right">▶</div>
</div>
<div class="status" id="status">按方向键或点击按钮开始</div>
</div>
<script>
(function() {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const statusEl = document.getElementById('status');
const restartBtn = document.getElementById('restartBtn');
const pauseBtn = document.getElementById('pauseBtn');
// 游戏参数
const GRID_SIZE = 20; // 格子大小
let cols, rows;
let snake, direction, nextDirection, food, score, bestScore, gameOver, paused, gameLoop;
let speed = 150; // 毫秒
// 初始化画布大小
function resizeCanvas() {
const maxWidth = Math.min(window.innerWidth - 40, 400);
const maxHeight = Math.min(window.innerHeight - 200, 400);
const size = Math.min(maxWidth, maxHeight);
canvas.width = size;
canvas.height = size;
cols = Math.floor(size / GRID_SIZE);
rows = Math.floor(size / GRID_SIZE);
// 确保格子数至少15
if (cols < 15) cols = 15;
if (rows < 15) rows = 15;
canvas.width = cols * GRID_SIZE;
canvas.height = rows * GRID_SIZE;
// 重新初始化游戏(如果游戏已开始)
if (snake) {
resetGame();
}
}
// 初始化或重置游戏
function resetGame() {
snake = [
{x: Math.floor(cols/2), y: Math.floor(rows/2)},
{x: Math.floor(cols/2)-1, y: Math.floor(rows/2)},
{x: Math.floor(cols/2)-2, y: Math.floor(rows/2)},
];
direction = 'right';
nextDirection = 'right';
score = 0;
gameOver = false;
paused = false;
pauseBtn.textContent = '暂停';
statusEl.textContent = '游戏进行中';
scoreEl.textContent = '0';
generateFood();
clearInterval(gameLoop);
gameLoop = setInterval(gameTick, speed);
}
// 生成食物
function generateFood() {
while (true) {
food = {x: Math.floor(Math.random()*cols), y: Math.floor(Math.random()*rows)};
if (!snake.some(s => s.x === food.x && s.y === food.y)) break;
}
}
// 游戏主循环
function gameTick() {
if (gameOver || paused) return;
// 更新方向
direction = nextDirection;
// 计算新蛇头
let head = {...snake[0]};
switch(direction) {
case 'up': head.y--; break;
case 'down': head.y++; break;
case 'left': head.x--; break;
case 'right': head.x++; break;
}
// 检查碰撞
if (head.x < 0 || head.x >= cols || head.y < 0 || head.y >= rows ||
snake.some(s => s.x === head.x && s.y === head.y)) {
gameOver = true;
clearInterval(gameLoop);
statusEl.textContent = '游戏结束!点击重新开始';
if (score > bestScore) {
bestScore = score;
localStorage.setItem('snakeBest', bestScore);
bestEl.textContent = bestScore;
}
draw();
return;
}
// 移动蛇
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score++;
scoreEl.textContent = score;
generateFood();
} else {
snake.pop();
}
draw();
}
// 绘制
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制网格线(浅色)
ctx.strokeStyle = '#2a2a4a';
ctx.lineWidth = 1;
for (let i = 0; i <= cols; i++) {
ctx.beginPath();
ctx.moveTo(i*GRID_SIZE, 0);
ctx.lineTo(i*GRID_SIZE, canvas.height);
ctx.stroke();
}
for (let i = 0; i <= rows; i++) {
ctx.beginPath();
ctx.moveTo(0, i*GRID_SIZE);
ctx.lineTo(canvas.width, i*GRID_SIZE);
ctx.stroke();
}
// 绘制食物
ctx.fillStyle = '#ff4757';
ctx.beginPath();
ctx.arc(food.x*GRID_SIZE + GRID_SIZE/2, food.y*GRID_SIZE + GRID_SIZE/2, GRID_SIZE/2 - 2, 0, Math.PI*2);
ctx.fill();
// 绘制蛇
snake.forEach((seg, idx) => {
const gradient = ctx.createRadialGradient(
seg.x*GRID_SIZE + GRID_SIZE/2, seg.y*GRID_SIZE + GRID_SIZE/2, 2,
seg.x*GRID_SIZE + GRID_SIZE/2, seg.y*GRID_SIZE + GRID_SIZE/2, GRID_SIZE/2
);
if (idx === 0) {
gradient.addColorStop(0, '#7bed9f');
gradient.addColorStop(1, '#2ed573');
} else {
gradient.addColorStop(0, '#3ae374');
gradient.addColorStop(1, '#1e9e4a');
}
ctx.fillStyle = gradient;
ctx.fillRect(seg.x*GRID_SIZE + 1, seg.y*GRID_SIZE + 1, GRID_SIZE - 2, GRID_SIZE - 2);
// 蛇头眼睛
if (idx === 0) {
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(seg.x*GRID_SIZE + GRID_SIZE/2 - 4, seg.y*GRID_SIZE + GRID_SIZE/2 - 3, 3, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(seg.x*GRID_SIZE + GRID_SIZE/2 + 4, seg.y*GRID_SIZE + GRID_SIZE/2 - 3, 3, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(seg.x*GRID_SIZE + GRID_SIZE/2 - 4, seg.y*GRID_SIZE + GRID_SIZE/2 - 3, 1.5, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(seg.x*GRID_SIZE + GRID_SIZE/2 + 4, seg.y*GRID_SIZE + GRID_SIZE/2 - 3, 1.5, 0, Math.PI*2);
ctx.fill();
}
});
// 游戏结束后显示遮罩
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = 'bold 24px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('游戏结束', canvas.width/2, canvas.height/2 - 10);
ctx.font = '16px sans-serif';
ctx.fillText('点击重新开始', canvas.width/2, canvas.height/2 + 25);
}
}
// 设置方向
function setDirection(dir) {
if (gameOver) return;
// 防止反向
if ((direction === 'up' && dir === 'down') ||
(direction === 'down' && dir === 'up') ||
(direction === 'left' && dir === 'right') ||
(direction === 'right' && dir === 'left')) {
return;
}
nextDirection = dir;
}
// 键盘事件
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowUp': e.preventDefault(); setDirection('up'); break;
case 'ArrowDown': e.preventDefault(); setDirection('down'); break;
case 'ArrowLeft': e.preventDefault(); setDirection('left'); break;
case 'ArrowRight': e.preventDefault(); setDirection('right'); break;
case ' ': e.preventDefault(); togglePause(); break;
}
});
// 按钮事件
restartBtn.addEventListener('click', () => {
resetGame();
draw();
});
pauseBtn.addEventListener('click', togglePause);
function togglePause() {
if (gameOver) return;
paused = !paused;
pauseBtn.textContent = paused ? '继续' : '暂停';
statusEl.textContent = paused ? '已暂停' : '游戏进行中';
}
// 方向按钮
document.querySelectorAll('.dpad-btn').forEach(btn => {
btn.addEventListener('click', () => {
setDirection(btn.dataset.dir);
});
});
// 触摸滑动(在canvas上)
let touchStartX, touchStartY;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
touchStartX = touch.clientX;
touchStartY = touch.clientY;
}, {passive: false});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
}, {passive: false});
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
if (touchStartX === undefined) return;
const touch = e.changedTouches[0];
const dx = touch.clientX - touchStartX;
const dy = touch.clientY - touchStartY;
if (Math.abs(dx) < 20 && Math.abs(dy) < 20) return;
if (Math.abs(dx) > Math.abs(dy)) {
setDirection(dx > 0 ? 'right' : 'left');
} else {
setDirection(dy > 0 ? 'down' : 'up');
}
touchStartX = undefined;
touchStartY = undefined;
}, {passive: false});
// 窗口大小改变时重置
window.addEventListener('resize', () => {
resizeCanvas();
draw();
});
// 初始化
bestScore = parseInt(localStorage.getItem('snakeBest') || '0');
bestEl.textContent = bestScore;
resizeCanvas();
resetGame();
draw();
})();
</script>
</body>
</html>