<think>
我们被要求用HTML、CSS和JavaScript实现贪吃蛇游戏,并且用户提供了两个引用作为参考。我们将结合这两个引用,并补充完整代码。
引用[1]提供了基本的HTML结构和部分JavaScript代码,但CSS和JavaScript不完整。
引用[2]提供了一段关于方向控制的代码片段。
因此,我们将完成一个完整的贪吃蛇游戏,包括以下部分:
1. HTML结构:包含标题、分数显示和画布。
2. CSS样式:居中显示,设置字体等。
3. JavaScript:实现游戏逻辑,包括蛇的移动、食物生成、碰撞检测和键盘控制。
我们将按照以下步骤实现:
HTML部分:
- 使用canvas元素绘制游戏区域。
CSS部分:
- 设置页面居中,字体等。
JavaScript部分:
- 定义常量:画布大小、网格大小等。
- 初始化蛇和食物的位置。
- 绘制蛇和食物的函数。
- 移动蛇的函数,并检测是否吃到食物或碰撞。
- 键盘事件监听,改变蛇的方向。
- 游戏循环,使用setInterval定期更新。
注意:引用[2]中提到了方向控制的限制,即蛇不能立即反向移动。
下面给出完整代码:
</think>
# HTML+CSS+JavaScript实现贪吃蛇游戏
我将为您创建一个完整、美观且可直接运行的贪吃蛇游戏。这个实现使用了Canvas绘制游戏区域,并添加了现代化的UI设计。
```html
<!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;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
overflow: hidden;
padding: 20px;
}
@keyframes gradientBG {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.game-container {
width: 100%;
max-width: 800px;
display: flex;
flex-direction: column;
gap: 20px;
}
header {
text-align: center;
color: white;
padding: 15px;
background: rgba(0, 0, 0, 0.7);
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
h1 {
font-size: 2.8rem;
margin-bottom: 10px;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
background: linear-gradient(to right, #ff9966, #ff5e62);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.game-stats {
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(0, 0, 0, 0.7);
border-radius: 15px;
padding: 15px 25px;
color: white;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.stat-box {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-label {
font-size: 1rem;
color: #aaa;
margin-bottom: 5px;
}
.stat-value {
font-size: 1.8rem;
font-weight: bold;
color: #4caf50;
}
.game-area {
display: flex;
flex-direction: column;
gap: 20px;
}
.canvas-container {
position: relative;
background: rgba(0, 0, 0, 0.8);
border-radius: 15px;
overflow: hidden;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);
padding: 5px;
}
canvas {
display: block;
margin: 0 auto;
background: #0d1b2a;
border-radius: 10px;
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
}
button {
padding: 12px 25px;
font-size: 1.1rem;
font-weight: bold;
background: linear-gradient(135deg, #4caf50, #2e7d32);
border: none;
border-radius: 50px;
color: white;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
background: linear-gradient(135deg, #66bb6a, #388e3c);
}
button:active {
transform: translateY(1px);
}
.restart-btn {
background: linear-gradient(135deg, #ff5722, #e64a19);
}
.restart-btn:hover {
background: linear-gradient(135deg, #ff7043, #f4511e);
}
.directions {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
margin-top: 10px;
}
.dir-key {
display: flex;
justify-content: center;
align-items: center;
height: 50px;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
font-weight: bold;
color: white;
font-size: 1.5rem;
}
.instructions {
background: rgba(0, 0, 0, 0.7);
border-radius: 15px;
padding: 20px;
color: #ddd;
line-height: 1.6;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.instructions h2 {
color: #4caf50;
margin-bottom: 15px;
font-size: 1.5rem;
}
.instructions ul {
padding-left: 25px;
}
.instructions li {
margin-bottom: 8px;
}
.game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
border-radius: 10px;
z-index: 10;
display: none;
}
.game-over h2 {
font-size: 3rem;
margin-bottom: 20px;
color: #ff5252;
}
.final-score {
font-size: 2rem;
margin-bottom: 30px;
}
.game-over button {
font-size: 1.4rem;
padding: 15px 40px;
}
@media (max-width: 600px) {
h1 {
font-size: 2rem;
}
.game-stats {
flex-direction: column;
gap: 15px;
}
.directions {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body>
<div class="game-container">
<header>
<h1>贪吃蛇游戏</h1>
<p>经典的贪吃蛇游戏 - 用方向键控制蛇的移动</p>
</header>
<div class="game-stats">
<div class="stat-box">
<span class="stat-label">分数</span>
<span class="stat-value" id="score">0</span>
</div>
<div class="stat-box">
<span class="stat-label">长度</span>
<span class="stat-value" id="length">1</span>
</div>
<div class="stat-box">
<span class="stat-label">速度</span>
<span class="stat-value" id="speed">100</span>
</div>
<div class="stat-box">
<span class="stat-label">食物</span>
<span class="stat-value" id="food">0</span>
</div>
</div>
<div class="game-area">
<div class="canvas-container">
<canvas id="game-board" width="400" height="400"></canvas>
<div class="game-over" id="game-over">
<h2>游戏结束!</h2>
<div class="final-score">最终得分: <span id="final-score">0</span></div>
<button id="restart-btn">重新开始</button>
</div>
</div>
<div class="controls">
<button id="start-btn">开始游戏</button>
<button id="pause-btn">暂停</button>
<button class="restart-btn" id="new-game-btn">新游戏</button>
</div>
<div class="directions">
<div class="dir-key">←</div>
<div class="dir-key">→</div>
<div class="dir-key">↑</div>
<div class="dir-key">↓</div>
</div>
</div>
<div class="instructions">
<h2>游戏说明</h2>
<ul>
<li>使用键盘方向键控制蛇的移动方向</li>
<li>吃到红色食物可以增加蛇的长度和得分</li>
<li>每吃到5个食物,游戏速度会增加</li>
<li>撞到墙壁或自己的身体会导致游戏结束</li>
<li>蛇可以穿过边界(从一边出来从另一边进入)</li>
</ul>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 游戏常量
const GRID_SIZE = 20;
const TILE_COUNT = 20;
const CANVAS_SIZE = GRID_SIZE * TILE_COUNT;
// DOM 元素
const canvas = document.getElementById('game-board');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const lengthElement = document.getElementById('length');
const speedElement = document.getElementById('speed');
const foodElement = document.getElementById('food');
const finalScoreElement = document.getElementById('final-score');
const gameOverScreen = document.getElementById('game-over');
const startBtn = document.getElementById('start-btn');
const pauseBtn = document.getElementById('pause-btn');
const newGameBtn = document.getElementById('new-game-btn');
const restartBtn = document.getElementById('restart-btn');
// 游戏状态
let snake = [{x: 10, y: 10}];
let food = generateFood();
let direction = {x: 0, y: 0};
let score = 0;
let foodEaten = 0;
let gameSpeed = 100;
let lastRenderTime = 0;
let gameRunning = false;
let gamePaused = false;
// 设置画布尺寸
canvas.width = CANVAS_SIZE;
canvas.height = CANVAS_SIZE;
// 生成食物位置
function generateFood() {
while (true) {
const foodPos = {
x: Math.floor(Math.random() * TILE_COUNT),
y: Math.floor(Math.random() * TILE_COUNT)
};
// 确保食物不会出现在蛇身上
if (!snake.some(segment => segment.x === foodPos.x && segment.y === foodPos.y)) {
return foodPos;
}
}
}
// 绘制游戏场景
function draw() {
// 清除画布
ctx.fillStyle = '#0d1b2a';
ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
// 绘制网格
ctx.strokeStyle = '#1b263b';
ctx.lineWidth = 0.3;
for (let i = 0; i <= TILE_COUNT; i++) {
// 横线
ctx.beginPath();
ctx.moveTo(0, i * GRID_SIZE);
ctx.lineTo(CANVAS_SIZE, i * GRID_SIZE);
ctx.stroke();
// 竖线
ctx.beginPath();
ctx.moveTo(i * GRID_SIZE, 0);
ctx.lineTo(i * GRID_SIZE, CANVAS_SIZE);
ctx.stroke();
}
// 绘制食物
ctx.fillStyle = '#ff5252';
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((segment, index) => {
// 蛇头用不同颜色
if (index === 0) {
ctx.fillStyle = '#4caf50';
} else {
// 蛇身使用渐变色
const colorValue = Math.max(100, 255 - index * 10);
ctx.fillStyle = `rgb(76, ${colorValue}, 80)`;
}
ctx.fillRect(segment.x * GRID_SIZE, segment.y * GRID_SIZE, GRID_SIZE, GRID_SIZE);
// 蛇身圆角
ctx.strokeStyle = '#1b5e20';
ctx.lineWidth = 2;
ctx.strokeRect(segment.x * GRID_SIZE, segment.y * GRID_SIZE, GRID_SIZE, GRID_SIZE);
});
}
// 更新游戏状态
function update() {
// 计算新蛇头位置
const head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y};
// 边界穿越逻辑
if (head.x < 0) head.x = TILE_COUNT - 1;
if (head.x >= TILE_COUNT) head.x = 0;
if (head.y < 0) head.y = TILE_COUNT - 1;
if (head.y >= TILE_COUNT) head.y = 0;
// 检查是否撞到自己
if (snake.some(segment => segment.x === head.x && segment.y === head.y)) {
gameOver();
return;
}
// 添加新蛇头
snake.unshift(head);
// 检查是否吃到食物
if (head.x === food.x && head.y === food.y) {
score += 10;
foodEaten++;
scoreElement.textContent = score;
foodElement.textContent = foodEaten;
// 每吃掉5个食物增加速度
if (foodEaten % 5 === 0) {
gameSpeed = Math.max(50, gameSpeed - 10);
speedElement.textContent = gameSpeed;
}
// 生成新食物
food = generateFood();
} else {
// 没吃到食物,移除蛇尾
snake.pop();
}
// 更新长度显示
lengthElement.textContent = snake.length;
}
// 游戏循环
function gameLoop(timestamp) {
if (gamePaused || !gameRunning) {
requestAnimationFrame(gameLoop);
return;
}
const secondsSinceLastRender = (timestamp - lastRenderTime) / 1000;
if (secondsSinceLastRender < 1 / (1000 / gameSpeed)) {
requestAnimationFrame(gameLoop);
return;
}
lastRenderTime = timestamp;
update();
draw();
requestAnimationFrame(gameLoop);
}
// 游戏结束
function gameOver() {
gameRunning = false;
gamePaused = false;
finalScoreElement.textContent = score;
gameOverScreen.style.display = 'flex';
}
// 开始游戏
function startGame() {
if (!gameRunning) {
gameRunning = true;
gamePaused = false;
gameOverScreen.style.display = 'none';
lastRenderTime = 0;
requestAnimationFrame(gameLoop);
}
}
// 暂停游戏
function togglePause() {
if (gameRunning) {
gamePaused = !gamePaused;
pauseBtn.textContent = gamePaused ? '继续' : '暂停';
}
}
// 开始新游戏
function newGame() {
snake = [{x: 10, y: 10}];
food = generateFood();
direction = {x: 0, y: 0};
score = 0;
foodEaten = 0;
gameSpeed = 100;
gameRunning = false;
gamePaused = false;
scoreElement.textContent = score;
lengthElement.textContent = snake.length;
speedElement.textContent = gameSpeed;
foodElement.textContent = foodEaten;
gameOverScreen.style.display = 'none';
pauseBtn.textContent = '暂停';
draw();
}
// 初始化游戏
newGame();
// 事件监听
document.addEventListener('keydown', e => {
if (!gameRunning) return;
// 防止在一次更新中多次改变方向
switch(e.key) {
case 'ArrowUp':
if (direction.y === 0) {
direction = {x: 0, y: -1};
}
break;
case 'ArrowDown':
if (direction.y === 0) {
direction = {x: 0, y: 1};
}
break;
case 'ArrowLeft':
if (direction.x === 0) {
direction = {x: -1, y: 0};
}
break;
case 'ArrowRight':
if (direction.x === 0) {
direction = {x: 1, y: 0};
}
break;
}
});
startBtn.addEventListener('click', startGame);
pauseBtn.addEventListener('click', togglePause);
newGameBtn.addEventListener('