|
<!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 {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.game-container {
text-align: center;
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 30px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
h1 {
color: #00ff88;
margin-bottom: 10px;
font-size: 2rem;
letter-spacing: 2px;
}
.score-board {
display: flex;
justify-content: center;
gap: 30px;
margin-bottom: 20px;
}
.score-item {
color: #ccc;
font-size: 1.1rem;
}
.score-item span {
color: #00ff88;
font-weight: bold;
font-size: 1.4rem;
margin-left: 5px;
}
canvas {
border: 2px solid #00ff88;
border-radius: 8px;
display: block;
background: #0a0a1a;
}
.controls {
margin-top: 20px;
}
.controls p {
color: #888;
margin-bottom: 10px;
}
.btn {
background: #00ff88;
color: #1a1a2e;
border: none;
padding: 12px 30px;
font-size: 1rem;
font-weight: bold;
border-radius: 8px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 255, 136, 0.4);
}
.game-over-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #ff4444;
font-size: 2rem;
font-weight: bold;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
text-shadow: 0 0 10px rgba(255, 68, 68, 0.8);
}
.game-over-overlay.visible {
opacity: 1;
}
</style>
</head>
<body>
<div class="game-container">
<h1>???? 贪吃蛇</h1>
<div class="score-board">
<div class="score-item">得分 <span id="score">0</span></div>
<div class="score-item">最高分 <span id="highScore">0</span></div>
</div>
<div style="position: relative;">
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div class="game-over-overlay" id="gameOver">游戏结束!</div>
</div>
<div class="controls">
<p>方向键 / WASD 控制移动 | 空格键暂停</p>
<button class="btn" id="restartBtn">重新开始</button>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const highScoreEl = document.getElementById('highScore');
const gameOverEl = document.getElementById('gameOver');
const restartBtn = document.getElementById('restartBtn');
// 游戏配置
const GRID_SIZE = 20;
const CELL_SIZE = canvas.width / GRID_SIZE;
const BASE_SPEED = 100; // 基础速度(毫秒/帧)
// 游戏状态
let snake = [];
let food = {};
let direction = { x: 1, y: 0 };
let nextDirection = { x: 1, y: 0 };
let score = 0;
let highScore = 0;
let gameRunning = false;
let gamePaused = false;
let gameLoop = null;
let speed = BASE_SPEED;
// 从 localStorage 读取最高分
try {
highScore = parseInt(localStorage.getItem('snakeHighScore')) || 0;
highScoreEl.textContent = highScore;
} catch (e) {
// localStorage 不可用时忽略
}
/**
* 初始化游戏
*/
function initGame() {
// 蛇初始位置:中间偏左,长度为 3
snake = [
{ x: 10, y: 10 },
{ x: 9, y: 10 },
{ x: 8, y: 10 },
];
direction = { x: 1, y: 0 };
nextDirection = { x: 1, y: 0 };
score = 0;
speed = BASE_SPEED;
gamePaused = false;
gameOverEl.classList.remove('visible');
scoreEl.textContent = '0';
generateFood();
}
/**
* 在空白位置随机生成食物
*/
function generateFood() {
const occupied = new Set(snake.map(s => `${s.x},${s.y}`));
const available = [];
for (let x = 0; x < GRID_SIZE; x++) {
for (let y = 0; y < GRID_SIZE; y++) {
if (!occupied.has(`${x},${y}`)) {
available.push({ x, y });
}
}
}
if (available.length === 0) {
// 蛇已经占满所有格子 = 胜利!
endGame(true);
return;
}
food = available[Math.floor(Math.random() * available.length)];
}
/**
* 游戏主循环
*/
function update() {
if (!gameRunning || gamePaused) return;
// 应用方向
direction = { ...nextDirection };
// 计算新头部位置
const head = snake[0];
const newHead = {
x: head.x + direction.x,
y: head.y + direction.y,
};
// 检测墙壁碰撞
if (
newHead.x < 0 || newHead.x >= GRID_SIZE ||
newHead.y < 0 || newHead.y >= GRID_SIZE
) {
endGame(false);
return;
}
// 检测自身碰撞(排除尾部,因为尾部即将移除)
for (let i = 0; i < snake.length - 1; i++) {
if (snake[i].x === newHead.x && snake[i].y === newHead.y) {
endGame(false);
return;
}
}
// 添加新头部
snake.unshift(newHead);
// 是否吃到食物
if (newHead.x === food.x && newHead.y === food.y) {
score += 10;
scoreEl.textContent = score;
// 每吃 5 个食物加速一次
if (score % 50 === 0 && speed > 40) {
speed -= 10;
clearInterval(gameLoop);
gameLoop = setInterval(update, speed);
}
generateFood();
} else {
// 移除尾部
snake.pop();
}
draw();
}
/**
* 渲染画面
*/
function draw() {
// 清空画布
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制网格线(弱化)
ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
ctx.lineWidth = 0.5;
for (let i = 0; i <= GRID_SIZE; i++) {
ctx.beginPath();
ctx.moveTo(i * CELL_SIZE, 0);
ctx.lineTo(i * CELL_SIZE, canvas.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, i * CELL_SIZE);
ctx.lineTo(canvas.width, i * CELL_SIZE);
ctx.stroke();
}
// 绘制食物(脉冲动画效果)
const pulse = 1 + 0.15 * Math.sin(Date.now() / 200);
const foodX = food.x * CELL_SIZE + CELL_SIZE / 2;
const foodY = food.y * CELL_SIZE + CELL_SIZE / 2;
const foodRadius = (CELL_SIZE / 2 - 2) * pulse;
ctx.fillStyle = '#ff4444';
ctx.shadowColor = '#ff4444';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(foodX, foodY, foodRadius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
// 绘制蛇身
snake.forEach((segment, index) => {
const x = segment.x * CELL_SIZE;
const y = segment.y * CELL_SIZE;
const padding = 1;
// 蛇头颜色不同
if (index === 0) {
ctx.fillStyle = '#00ff88';
ctx.shadowColor = '#00ff88';
ctx.shadowBlur = 8;
} else {
// 渐变颜色:越靠近尾部越暗
const alpha = 1 - (index / snake.length) * 0.6;
ctx.fillStyle = `rgba(0, 200, 100, ${alpha})`;
ctx.shadowBlur = 0;
}
ctx.beginPath();
ctx.roundRect(
x + padding,
y + padding,
CELL_SIZE - padding * 2,
CELL_SIZE - padding * 2,
3
);
ctx.fill();
});
ctx.shadowBlur = 0;
}
/**
* 结束游戏
* @param {boolean} won - 是否胜利
*/
function endGame(won) {
gameRunning = false;
clearInterval(gameLoop);
if (won) {
gameOverEl.textContent = '???? 恭喜通关!';
gameOverEl.style.color = '#00ff88';
} else {
gameOverEl.textContent = '游戏结束!';
gameOverEl.style.color = '#ff4444';
}
gameOverEl.classList.add('visible');
// 更新最高分
if (score > highScore) {
highScore = score;
highScoreEl.textContent = highScore;
try {
localStorage.setItem('snakeHighScore', highScore);
} catch (e) {
// ignore
}
}
}
/**
* 开始游戏
*/
function startGame() {
initGame();
gameRunning = true;
draw();
if (gameLoop) clearInterval(gameLoop);
gameLoop = setInterval(update, speed);
}
// 键盘控制
document.addEventListener('keydown', (e) => {
if (!gameRunning && e.key !== 'r') return;
switch (e.key) {
case 'ArrowUp':
case 'w':
case 'W':
e.preventDefault();
if (direction.y !== 1) {
nextDirection = { x: 0, y: -1 };
}
break;
case 'ArrowDown':
case 's':
case 'S':
e.preventDefault();
if (direction.y !== -1) {
nextDirection = { x: 0, y: 1 };
}
break;
case 'ArrowLeft':
case 'a':
case 'A':
e.preventDefault();
if (direction.x !== 1) {
nextDirection = { x: -1, y: 0 };
}
break;
case 'ArrowRight':
case 'd':
case 'D':
e.preventDefault();
if (direction.x !== -1) {
nextDirection = { x: 1, y: 0 };
}
break;
case ' ':
e.preventDefault();
if (gameRunning) {
gamePaused = !gamePaused;
gameOverEl.textContent = '已暂停';
gameOverEl.style.color = '#ffaa00';
if (gamePaused) {
gameOverEl.classList.add('visible');
} else {
gameOverEl.classList.remove('visible');
}
}
break;
}
});
// 重新开始按钮
restartBtn.addEventListener('click', startGame);
// 自动开始
startGame();
</script>
</body>
</html>
|