经典贪吃蛇
纯前端代码实现经典贪吃蛇游戏,含电脑端方向键控制,暂停,难度模式选择,以及特殊食物和保存历史最高分等功能。新增支持移动端控制
控制: 方向键 ↑↓←→ 或 WASD、暂停: P 键、难度: 简单、中等、困难三种模式

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>经典贪吃蛇 - 支持移动端</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#FF7D00',
dark: '#333333',
light: '#F5F7FA',
success: '#00B42A',
danger: '#F53F3F',
warning: '#FF7D00',
info: '#86909C',
snake: '#00B96B',
food: '#FF7D00',
special: '#FFD700'
},
fontFamily: {
inter: ['Inter', 'sans-serif'],
},
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
touch-action: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.swipe-control {
position: absolute;
bottom: 20px;
width: 180px;
height: 180px;
border-radius: 50%%;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
display: none;
z-index: 20;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.swipe-center {
position: absolute;
top: 50%%;
left: 50%%;
transform: translate(-50%%, -50%%);
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #333;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.swipe-direction {
position: absolute;
width: 50px;
height: 50px;
border-radius: 50%%;
background: rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #165DFF;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.swipe-up { top: 0; left: 50%%; transform: translateX(-50%%); }
.swipe-right { top: 50%%; right: 0; transform: translateY(-50%%); }
.swipe-down { bottom: 0; left: 50%%; transform: translateX(-50%%); }
.swipe-left { top: 50%%; left: 0; transform: translateY(-50%%); }
.snake-head {
position: relative;
}
.snake-eyes {
position: absolute;
top: 5px;
width: 4px;
height: 4px;
background: #FFF;
border-radius: 50%%;
}
.eye-left { left: 5px; }
.eye-right { right: 5px; }
.menu-item {
transition: all 0.2s ease;
}
.menu-item:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}
.menu-item:active {
transform: translateY(1px);
}
.high-score-item {
background: linear-gradient(90deg, rgba(22, 93, 255, 0.1) 0%%, rgba(255, 255, 255, 0) 100%%);
border-left: 4px solid #165DFF;
}
@media (max-width: 768px) {
.swipe-control {
display: block;
}
.controls-info {
display: none;
}
}
</style>
</head>
<body class="bg-gradient-to-br from-light to-info min-h-screen font-inter text-dark flex flex-col items-center justify-center p-4">
<!-- 游戏容器 -->
<div class="max-w-3xl w-full mx-auto flex flex-col items-center">
<!-- 游戏标题 -->
<h1 class="text-[clamp(2rem,5vw,3rem)] font-bold text-primary mb-4 flex items-center">
<i class="fa fa-gamepad mr-3"></i>经典贪吃蛇
</h1>
<!-- 游戏控制区 -->
<div class="w-full flex flex-col md:flex-row items-center justify-between mb-4 gap-4">
<!-- 分数和最高分 -->
<div class="flex items-center gap-4 bg-white/80 rounded-xl p-3 shadow-lg">
<div>
<p class="text-sm text-info">分数</p>
<p id="score" class="text-2xl font-bold text-primary">0</p>
</div>
<div class="h-10 w-px bg-info/20"></div>
<div>
<p class="text-sm text-info">最高分</p>
<p id="highScore" class="text-2xl font-bold text-secondary">0</p>
</div>
</div>
<!-- 游戏控制按钮 -->
<div class="flex flex-wrap items-center gap-3">
<select id="difficulty" class="bg-white/80 border border-info/30 rounded-xl px-4 py-2 text-dark focus:outline-none focus:ring-2 focus:ring-primary/50 appearance-none pr-8 text-sm">
<option value="easy">简单 (200ms)</option>
<option value="medium" selected>中等 (150ms)</option>
<option value="hard">困难 (100ms)</option>
</select>
<button id="startBtn" class="bg-primary hover:bg-primary/90 text-white rounded-xl px-6 py-2.5 transition-all flex items-center">
<i class="fa fa-play mr-2"></i>开始游戏
</button>
<button id="pauseBtn" class="bg-warning hover:bg-warning/90 text-white rounded-xl px-6 py-2.5 transition-all flex items-center hidden">
<i class="fa fa-pause mr-2"></i>暂停
</button>
<button id="restartBtn" class="bg-secondary hover:bg-secondary/90 text-white rounded-xl px-6 py-2.5 transition-all flex items-center hidden">
<i class="fa fa-refresh mr-2"></i>再来一次
</button>
</div>
</div>
<!-- 游戏区域 -->
<div class="relative w-full aspect-square max-w-2xl bg-white/90 rounded-2xl shadow-xl overflow-hidden">
<canvas id="gameCanvas" class="w-full h-full"></canvas>
<!-- 移动端滑动控制 -->
<div>
<div>
<i class="fa fa-arrows-alt"></i>
</div>
<div class="swipe-direction swipe-up">
<i class="fa fa-arrow-up"></i>
</div>
<div class="swipe-direction swipe-right">
<i class="fa fa-arrow-right"></i>
</div>
<div class="swipe-direction swipe-down">
<i class="fa fa-arrow-down"></i>
</div>
<div class="swipe-direction swipe-left">
<i class="fa fa-arrow-left"></i>
</div>
</div>
<!-- 游戏开始遮罩(主菜单) -->
<div id="startScreen" class="absolute inset-0 bg-dark/70 backdrop-blur flex flex-col items-center justify-center z-10 p-4">
<h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-white mb-6 text-center">贪吃蛇大作战</h2>
<div class="w-full max-w-xs flex flex-col gap-4">
<button id="playBtn" class="menu-item bg-primary hover:bg-primary/90 text-white rounded-xl px-8 py-4 text-lg flex items-center justify-center">
<i class="fa fa-play-circle mr-3"></i>开始游戏
</button>
<button id="highScoresBtn" class="menu-item bg-secondary hover:bg-secondary/90 text-white rounded-xl px-8 py-4 text-lg flex items-center justify-center">
<i class="fa fa-trophy mr-3"></i>查看高分
</button>
<button id="exitBtn" class="menu-item bg-info hover:bg-info/90 text-white rounded-xl px-8 py-4 text-lg flex items-center justify-center">
<i class="fa fa-sign-out mr-3"></i>退出游戏
</button>
</div>
<p class="text-white/70 mt-8 text-sm max-w-md text-center">使用方向键或 WASD 控制蛇的移动,吃到食物增长得分,不要撞到墙壁和自己的身体!</p>
</div>
<!-- 高分排行榜 -->
<div id="highScoresScreen" class="absolute inset-0 bg-dark/70 backdrop-blur flex flex-col items-center justify-center z-10 hidden p-4">
<h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-white mb-6 text-center">高分排行榜</h2>
<div class="w-full max-w-md bg-white/90 rounded-xl p-4 mb-6">
<div class="flex justify-between items-center mb-4">
<span class="text-info font-bold">排名</span>
<span class="text-info font-bold">玩家</span>
<span class="text-info font-bold">分数</span>
<span class="text-info font-bold">日期</span>
</div>
<div id="highScoresList">
<!-- 高分记录将通过JS动态生成 -->
</div>
</div>
<button id="backToMenuBtn" class="bg-primary hover:bg-primary/90 text-white rounded-xl px-8 py-3 text-lg flex items-center">
<i class="fa fa-arrow-left mr-2"></i>返回主菜单
</button>
</div>
<!-- 游戏暂停遮罩 -->
<div id="pauseScreen" class="absolute inset-0 bg-dark/70 backdrop-blur flex flex-col items-center justify-center z-10 hidden">
<h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-white mb-8">游戏暂停</h2>
<button id="resumeBtn" class="bg-primary hover:bg-primary/90 text-white rounded-xl px-8 py-3 text-lg flex items-center">
<i class="fa fa-play mr-2"></i>继续游戏
</button>
</div>
<!-- 游戏结束遮罩 -->
<div id="gameOverScreen" class="absolute inset-0 bg-dark/70 backdrop-blur flex flex-col items-center justify-center z-10 hidden">
<h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-danger mb-2">游戏结束</h2>
<p class="text-white/80 mb-2">你的得分: <span id="finalScore" class="font-bold text-primary">0</span></p>
<p class="text-white/80 mb-6">最高分: <span id="finalHighScore" class="font-bold text-secondary">0</span></p>
<button id="playAgainBtn" class="bg-primary hover:bg-primary/90 text-white rounded-xl px-8 py-3 text-lg flex items-center mb-3">
<i class="fa fa-refresh mr-2"></i>再玩一次
</button>
<button id="backToMenuBtnFromGameOver" class="bg-info hover:bg-info/90 text-white rounded-xl px-8 py-3 text-lg flex items-center">
<i class="fa fa-home mr-2"></i>返回菜单
</button>
</div>
</div>
<!-- 游戏说明 -->
<div class="mt-6 w-full bg-white/80 rounded-xl p-4 shadow-lg">
<h3 class="font-bold text-lg mb-2 flex items-center">
<i class="fa fa-info-circle mr-2 text-primary"></i>游戏说明
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p class="text-sm mb-2"><span>控制:</span> 方向键 ↑↓←→ 或 WASD</p>
<p class="text-sm mb-2"><span>暂停:</span> P 键</p>
<p><span>难度:</span> 简单、中等、困难三种模式</p>
</div>
<div>
<p class="text-sm mb-2"><span>目标:</span> 吃到食物,尽可能获得高分</p>
<p class="text-sm mb-2"><span>规则:</span> 撞到墙壁或自身即游戏结束</p>
<p><span>特殊食物:</span> 金色食物得3分,限时出现</p>
</div>
</div>
<div class="controls-info mt-4">
<p class="text-sm text-center text-info/80">在移动设备上,使用屏幕上的方向按钮控制蛇的移动</p>
</div>
</div>
</div>
<!-- 页脚 -->
<footer class="mt-8 text-center text-info/70 text-sm">
<p>© 2025 经典贪吃蛇 | 支持键盘和触摸控制</p>
</footer>
<script>
// DOM 元素
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreDisplay = document.getElementById('score');
const highScoreDisplay = document.getElementById('highScore');
const finalScoreDisplay = document.getElementById('finalScore');
const finalHighScoreDisplay = document.getElementById('finalHighScore');
const difficultySelect = document.getElementById('difficulty');
const startBtn = document.getElementById('startBtn');
const pauseBtn = document.getElementById('pauseBtn');
const restartBtn = document.getElementById('restartBtn');
const playBtn = document.getElementById('playBtn');
const resumeBtn = document.getElementById('resumeBtn');
const playAgainBtn = document.getElementById('playAgainBtn');
const backToMenuBtn = document.getElementById('backToMenuBtn');
const backToMenuBtnFromGameOver = document.getElementById('backToMenuBtnFromGameOver');
const startScreen = document.getElementById('startScreen');
const pauseScreen = document.getElementById('pauseScreen');
const gameOverScreen = document.getElementById('gameOverScreen');
const highScoresBtn = document.getElementById('highScoresBtn');
const highScoresScreen = document.getElementById('highScoresScreen');
const highScoresList = document.getElementById('highScoresList');
const exitBtn = document.getElementById('exitBtn');
// 滑动控制元素
const swipeControl = document.querySelector('.swipe-control');
const swipeDirections = document.querySelectorAll('.swipe-direction');
// 游戏配置
const gridSize = 20;
let gameSpeed = 150; // 默认中等难度
let gameInterval;
let isPaused = false;
let gameActive = false;
// 游戏状态
let snake = [];
let food = {};
let specialFood = null;
let specialFoodTimer = null;
let direction = '';
let nextDirection = '';
let score = 0;
let highScore = localStorage.getItem('snakeHighScore') || 0;
let highScores = JSON.parse(localStorage.getItem('snakeHighScores')) || [];
// 设置高分显示
highScoreDisplay.textContent = highScore;
// 调整Canvas大小以适应容器
function resizeCanvas() {
const container = canvas.parentElement;
const size = Math.min(container.clientWidth, container.clientHeight);
canvas.width = size;
canvas.height = size;
}
// 初始化游戏
function initGame() {
// 重置游戏状态
const center = Math.floor((canvas.width / gridSize) / 2);
snake = [
{x: center, y: center},
{x: center - 1, y: center},
{x: center - 2, y: center}
];
direction = 'right';
nextDirection = 'right';
score = 0;
scoreDisplay.textContent = score;
// 设置游戏速度
handleDifficultyChange();
// 生成初始食物
generateFood();
generateSpecialFood();
// 显示游戏画面,隐藏遮罩
startScreen.classList.add('hidden');
pauseScreen.classList.add('hidden');
gameOverScreen.classList.add('hidden');
highScoresScreen.classList.add('hidden');
pauseBtn.classList.remove('hidden');
restartBtn.classList.remove('hidden');
startBtn.classList.add('hidden');
// 开始游戏循环
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
gameActive = true;
}
// 生成普通食物
function generateFood() {
const gridWidth = canvas.width / gridSize;
const gridHeight = canvas.height / gridSize;
// 随机位置
let newFood;
do {
newFood = {
x: Math.floor(Math.random() * gridWidth),
y: Math.floor(Math.random() * gridHeight)
};
} while (isPositionOnSnake(newFood) || (specialFood && newFood.x === specialFood.x && newFood.y === specialFood.y));
food = newFood;
}
// 生成特殊食物
function generateSpecialFood() {
// 清除之前的计时器和特殊食物
if (specialFoodTimer) clearTimeout(specialFoodTimer);
specialFood = null;
// 随机决定是否生成特殊食物
if (Math.random() < 0.3) { // 30%%概率生成
const gridWidth = canvas.width / gridSize;
const gridHeight = canvas.height / gridSize;
let newSpecialFood;
do {
newSpecialFood = {
x: Math.floor(Math.random() * gridWidth),
y: Math.floor(Math.random() * gridHeight)
};
} while (isPositionOnSnake(newSpecialFood) || (newSpecialFood.x === food.x && newSpecialFood.y === food.y));
specialFood = newSpecialFood;
// 特殊食物10秒后消失
specialFoodTimer = setTimeout(() => {
specialFood = null;
// 3-8秒后尝试再次生成
setTimeout(generateSpecialFood, Math.random() * 5000 + 3000);
}, 10000);
} else {
// 3-8秒后尝试生成
setTimeout(generateSpecialFood, Math.random() * 5000 + 3000);
}
}
// 检查位置是否在蛇身上
function isPositionOnSnake(position) {
return snake.some(segment => segment.x === position.x && segment.y === position.y);
}
// 游戏主循环
function gameLoop() {
if (isPaused || !gameActive) return;
// 更新方向
direction = nextDirection;
// 移动蛇
const head = {x: snake[0].x, y: snake[0].y};
switch(direction) {
case 'up': head.y -= 1; break;
case 'down': head.y += 1; break;
case 'left': head.x -= 1; break;
case 'right': head.x += 1; break;
}
// 检查碰撞
if (checkCollision(head)) {
gameOver();
return;
}
// 检查是否吃到食物
let ate = false;
if (head.x === food.x && head.y === food.y) {
score += 1;
scoreDisplay.textContent = score;
generateFood();
ate = true;
updateGameSpeed();
}
// 检查是否吃到特殊食物
if (specialFood && head.x === specialFood.x && head.y === specialFood.y) {
score += 3;
scoreDisplay.textContent = score;
clearTimeout(specialFoodTimer);
generateSpecialFood();
ate = true;
updateGameSpeed();
}
// 添加新头部
snake.unshift(head);
// 如果没吃到食物,移除尾部
if (!ate) {
snake.pop();
}
// 绘制游戏
draw();
}
// 根据分数更新游戏速度
function updateGameSpeed() {
const baseSpeed = difficultySelect.value === 'easy' ? 200 :
difficultySelect.value === 'medium' ? 150 : 100;
// 每得10分速度增加10%%
const speedFactor = Math.min(0.5, Math.floor(score / 10) * 0.1);
const newSpeed = Math.max(50, baseSpeed * (1 - speedFactor));
if (newSpeed !== gameSpeed) {
gameSpeed = newSpeed;
// 重新设置游戏循环
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
}
}
// 检查碰撞
function checkCollision(head) {
const gridWidth = canvas.width / gridSize;
const gridHeight = canvas.height / gridSize;
// 检查是否撞到墙壁
if (head.x < 0 || head.x >= gridWidth || head.y < 0 || head.y >= gridHeight) {
return true;
}
// 检查是否撞到自己
for (let i = 1; i < snake.length; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
return true;
}
}
return false;
}
// 绘制游戏
function draw() {
// 清空画布
ctx.fillStyle = '#F5F7FA';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制网格(可选)
ctx.strokeStyle = '#E5E9F2';
ctx.lineWidth = 0.5;
for (let x = 0; x < canvas.width; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y < canvas.height; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
// 绘制蛇
snake.forEach((segment, index) => {
// 蛇头
if (index === 0) {
ctx.fillStyle = '#00B96B';
ctx.shadowColor = 'rgba(0, 185, 107, 0.5)';
ctx.shadowBlur = 10;
// 绘制头部
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2,
segment.y * gridSize + gridSize / 2,
gridSize / 2 - 1,
0,
Math.PI * 2
);
ctx.fill();
// 绘制眼睛
ctx.fillStyle = 'white';
let eyeOffsetX = 0;
let eyeOffsetY = 0;
if (direction === 'right') {
eyeOffsetX = gridSize / 4;
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2 + eyeOffsetX - gridSize / 8,
segment.y * gridSize + gridSize / 2 - gridSize / 8,
gridSize / 8,
0,
Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2 + eyeOffsetX - gridSize / 8,
segment.y * gridSize + gridSize / 2 + gridSize / 8,
gridSize / 8,
0,
Math.PI * 2
);
ctx.fill();
} else if (direction === 'left') {
eyeOffsetX = -gridSize / 4;
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2 + eyeOffsetX + gridSize / 8,
segment.y * gridSize + gridSize / 2 - gridSize / 8,
gridSize / 8,
0,
Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2 + eyeOffsetX + gridSize / 8,
segment.y * gridSize + gridSize / 2 + gridSize / 8,
gridSize / 8,
0,
Math.PI * 2
);
ctx.fill();
} else if (direction === 'up') {
eyeOffsetY = -gridSize / 4;
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2 - gridSize / 8,
segment.y * gridSize + gridSize / 2 + eyeOffsetY + gridSize / 8,
gridSize / 8,
0,
Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2 + gridSize / 8,
segment.y * gridSize + gridSize / 2 + eyeOffsetY + gridSize / 8,
gridSize / 8,
0,
Math.PI * 2
);
ctx.fill();
} else if (direction === 'down') {
eyeOffsetY = gridSize / 4;
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2 - gridSize / 8,
segment.y * gridSize + gridSize / 2 + eyeOffsetY - gridSize / 8,
gridSize / 8,
0,
Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
segment.x * gridSize + gridSize / 2 + gridSize / 8,
segment.y * gridSize + gridSize / 2 + eyeOffsetY - gridSize / 8,
gridSize / 8,
0,
Math.PI * 2
);
ctx.fill();
}
ctx.shadowBlur = 0;
}
// 蛇身体渐变
else {
// 根据位置设置渐变颜色
const colorPos = 1 - index / (snake.length + 5);
const r = Math.floor(0 + (185 - 0) * colorPos);
const g = Math.floor(185 + (107 - 185) * colorPos);
const b = Math.floor(107 + (0 - 107) * colorPos);
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
// 绘制圆角矩形身体
const radius = gridSize / 5;
const x = segment.x * gridSize + 0.5;
const y = segment.y * gridSize + 0.5;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + gridSize - radius, y);
ctx.arcTo(x + gridSize, y, x + gridSize, y + radius, radius);
ctx.lineTo(x + gridSize, y + gridSize - radius);
ctx.arcTo(x + gridSize, y + gridSize, x + gridSize - radius, y + gridSize, radius);
ctx.lineTo(x + radius, y + gridSize);
ctx.arcTo(x, y + gridSize, x, y + gridSize - radius, radius);
ctx.lineTo(x, y + radius);
ctx.arcTo(x, y, x + radius, y, radius);
ctx.closePath();
ctx.fill();
// 身体高光
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
ctx.beginPath();
ctx.ellipse(
x + gridSize / 2,
y + gridSize / 3,
gridSize / 4,
gridSize / 8,
0,
0,
Math.PI * 2
);
ctx.fill();
}
});
// 绘制食物
ctx.fillStyle = '#FF7D00';
ctx.shadowColor = 'rgba(255, 125, 0, 0.5)';
ctx.shadowBlur = 10;
// 绘制苹果形状的食物
ctx.beginPath();
ctx.arc(
food.x * gridSize + gridSize / 2,
food.y * gridSize + gridSize / 2,
gridSize / 2 - 1,
0,
Math.PI * 2
);
ctx.fill();
// 苹果柄
ctx.fillStyle = '#8B4513';
ctx.fillRect(
food.x * gridSize + gridSize / 2 - 1,
food.y * gridSize - 2,
2,
6
);
ctx.shadowBlur = 0;
// 绘制特殊食物(如果存在)
if (specialFood) {
ctx.fillStyle = '#FFD700';
ctx.shadowColor = 'rgba(255, 215, 0, 0.7)';
ctx.shadowBlur = 15;
// 绘制闪烁效果
const flashRate = 500; // 闪烁频率
const flash = Math.floor(Date.now() / flashRate) %% 2 === 0;
if (flash) {
// 绘制星形特殊食物
drawStar(
specialFood.x * gridSize + gridSize / 2,
specialFood.y * gridSize + gridSize / 2,
5,
gridSize / 2 - 1,
gridSize / 4
);
}
ctx.shadowBlur = 0;
}
}
// 绘制星形
function drawStar(cx, cy, spikes, outerRadius, innerRadius) {
let rot = Math.PI / 2 * 3;
let x = cx;
let y = cy;
let step = Math.PI / spikes;
ctx.beginPath();
ctx.moveTo(cx, cy - outerRadius);
for (let i = 0; i < spikes; i++) {
x = cx + Math.cos(rot) * outerRadius;
y = cy + Math.sin(rot) * outerRadius;
ctx.lineTo(x, y);
rot += step;
x = cx + Math.cos(rot) * innerRadius;
y = cy + Math.sin(rot) * innerRadius;
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(cx, cy - outerRadius);
ctx.closePath();
ctx.fill();
}
// 游戏暂停
function pauseGame() {
isPaused = true;
pauseScreen.classList.remove('hidden');
pauseBtn.innerHTML = '<i class="fa fa-play mr-2"></i>继续';
}
// 游戏继续
function resumeGame() {
isPaused = false;
pauseScreen.classList.add('hidden');
pauseBtn.innerHTML = '<i class="fa fa-pause mr-2"></i>暂停';
}
// 游戏结束
function gameOver() {
gameActive = false;
clearInterval(gameInterval);
// 更新最高分
if (score > highScore) {
highScore = score;
localStorage.setItem('snakeHighScore', highScore);
highScoreDisplay.textContent = highScore;
}
// 添加高分记录
addHighScore(score);
// 显示游戏结束画面
finalScoreDisplay.textContent = score;
finalHighScoreDisplay.textContent = highScore;
gameOverScreen.classList.remove('hidden');
pauseBtn.classList.add('hidden');
restartBtn.classList.add('hidden');
startBtn.classList.remove('hidden');
}
// 添加高分记录
function addHighScore(score) {
// 获取当前日期
const now = new Date();
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
// 添加新记录
highScores.push({
score: score,
date: dateStr,
player: '玩家'
});
// 排序(高分在前)
highScores.sort((a, b) => b.score - a.score);
// 只保留前10名
if (highScores.length > 10) {
highScores = highScores.slice(0, 10);
}
// 保存到本地存储
localStorage.setItem('snakeHighScores', JSON.stringify(highScores));
}
// 渲染高分记录
function renderHighScores() {
highScoresList.innerHTML = '';
if (highScores.length === 0) {
highScoresList.innerHTML = '<p class="text-center py-4 text-info">暂无高分记录</p>';
return;
}
highScores.forEach((item, index) => {
const scoreItem = document.createElement('div');
scoreItem.className = 'flex justify-between items-center py-2 px-3 rounded-lg high-score-item';
scoreItem.innerHTML = `
<span class="font-bold text-dark">${index + 1}</span>
<span>${item.player}</span>
<span class="font-bold text-secondary">${item.score}</span>
<span class="text-sm text-info">${item.date}</span>
`;
highScoresList.appendChild(scoreItem);
});
}
// 切换暂停状态
function togglePause() {
if (!gameActive) return;
if (isPaused) {
resumeGame();
} else {
pauseGame();
}
}
// 处理键盘输入
function handleKeydown(e) {
// 如果游戏未激活,不处理方向键
if (!gameActive && e.key !== 'p' && e.key !== 'P') return;
switch(e.key) {
case 'ArrowUp':
case 'w':
case 'W':
if (direction !== 'down') {
nextDirection = 'up';
}
break;
case 'ArrowDown':
case 's':
case 'S':
if (direction !== 'up') {
nextDirection = 'down';
}
break;
case 'ArrowLeft':
case 'a':
case 'A':
if (direction !== 'right') {
nextDirection = 'left';
}
break;
case 'ArrowRight':
case 'd':
case 'D':
if (direction !== 'left') {
nextDirection = 'right';
}
break;
case 'p':
case 'P':
togglePause();
break;
case ' ': // 空格键开始游戏
if (!gameActive) {
initGame();
}
break;
}
}
// 处理难度变化
function handleDifficultyChange() {
const selectedDifficulty = difficultySelect.value;
switch(selectedDifficulty) {
case 'easy':
gameSpeed = 200;
break;
case 'medium':
gameSpeed = 150;
break;
case 'hard':
gameSpeed = 100;
break;
}
// 如果游戏正在进行,更新游戏速度
if (gameActive && !isPaused) {
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
}
}
// 添加触摸事件处理
function setupTouchControls() {
// 为每个方向按钮添加触摸事件
swipeDirections.forEach(button => {
button.addEventListener('touchstart', (e) => {
e.preventDefault();
if (!gameActive || isPaused) return;
const direction = button.classList[1].split('-')[1];
if (direction === 'up' && nextDirection !== 'down') {
nextDirection = 'up';
} else if (direction === 'down' && nextDirection !== 'up') {
nextDirection = 'down';
} else if (direction === 'left' && nextDirection !== 'right') {
nextDirection = 'left';
} else if (direction === 'right' && nextDirection !== 'left') {
nextDirection = 'right';
}
// 添加点击反馈
button.style.transform = 'scale(0.9)';
setTimeout(() => {
button.style.transform = '';
}, 100);
});
});
// 添加滑动手势支持
let touchStartX = 0;
let touchStartY = 0;
canvas.addEventListener('touchstart', (e) => {
if (!gameActive || isPaused) return;
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) => {
if (!gameActive || isPaused) return;
const touch = e.changedTouches[0];
const touchEndX = touch.clientX;
const touchEndY = touch.clientY;
const dx = touchEndX - touchStartX;
const dy = touchEndY - touchStartY;
// 确定滑动方向
if (Math.abs(dx) > Math.abs(dy)) {
// 水平滑动
if (dx > 20 && nextDirection !== 'left') {
nextDirection = 'right';
} else if (dx < -20 && nextDirection !== 'right') {
nextDirection = 'left';
}
} else {
// 垂直滑动
if (dy > 20 && nextDirection !== 'up') {
nextDirection = 'down';
} else if (dy < -20 && nextDirection !== 'down') {
nextDirection = 'up';
}
}
});
}
// 事件监听
window.addEventListener('keydown', handleKeydown);
startBtn.addEventListener('click', initGame);
pauseBtn.addEventListener('click', togglePause);
restartBtn.addEventListener('click', initGame);
playBtn.addEventListener('click', initGame);
resumeBtn.addEventListener('click', resumeGame);
playAgainBtn.addEventListener('click', initGame);
backToMenuBtn.addEventListener('click', () => {
highScoresScreen.classList.add('hidden');
startScreen.classList.remove('hidden');
});
backToMenuBtnFromGameOver.addEventListener('click', () => {
gameOverScreen.classList.add('hidden');
startScreen.classList.remove('hidden');
});
difficultySelect.addEventListener('change', handleDifficultyChange);
highScoresBtn.addEventListener('click', () => {
startScreen.classList.add('hidden');
highScoresScreen.classList.remove('hidden');
renderHighScores();
});
exitBtn.addEventListener('click', () => {
if (confirm('确定要退出游戏吗?')) {
window.close();
}
});
// 窗口大小改变时调整Canvas
window.addEventListener('resize', () => {
const wasActive = gameActive;
const wasPaused = isPaused;
if (gameActive) {
clearInterval(gameInterval);
gameActive = false;
}
resizeCanvas();
if (wasActive) {
gameActive = true;
if (!wasPaused) {
gameInterval = setInterval(gameLoop, gameSpeed);
}
}
// 重新绘制游戏
if (snake.length > 0) {
draw();
}
});
// 初始化
resizeCanvas();
draw();
setupTouchControls();
</script>
</body>
</html>
本文最后更新时间 2025-06-01
文章链接地址:https://xzlo.blog/index.php/archives/47/
本站文章除注明[转载|引用|原文]出处外,均为本站原生内容,转载前请注明出处