385 lines
14 KiB
HTML
385 lines
14 KiB
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>
|
||
body {
|
||
margin: 0;
|
||
padding: 20px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
min-height: 100vh;
|
||
background-color: #333;
|
||
font-family: Arial, sans-serif;
|
||
}
|
||
|
||
.game-container {
|
||
text-align: center;
|
||
}
|
||
|
||
canvas {
|
||
border: 2px solid #fff;
|
||
background-color: #111;
|
||
}
|
||
|
||
.info {
|
||
color: #fff;
|
||
margin: 10px 0;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.controls {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
button {
|
||
padding: 10px 20px;
|
||
font-size: 16px;
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
margin: 0 5px;
|
||
}
|
||
|
||
button:hover {
|
||
background-color: #45a049;
|
||
}
|
||
|
||
button:disabled {
|
||
background-color: #666;
|
||
cursor: not-allowed;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="game-container">
|
||
<div class="info">
|
||
<div>得分: <span id="score">0</span></div>
|
||
<div>蛇长度: <span id="length">1</span></div>
|
||
<div>游戏状态: <span id="status">准备开始</span></div>
|
||
</div>
|
||
<canvas id="gameCanvas" width="600" height="600"></canvas>
|
||
<div class="controls">
|
||
<button id="startBtn">开始游戏</button>
|
||
<button id="pauseBtn" disabled>暂停</button>
|
||
<button id="resetBtn">重置</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
class SnakeGame {
|
||
constructor() {
|
||
this.canvas = document.getElementById('gameCanvas');
|
||
this.ctx = this.canvas.getContext('2d');
|
||
this.gridSize = 20;
|
||
this.tileCount = this.canvas.width / this.gridSize;
|
||
|
||
this.reset();
|
||
this.setupEventListeners();
|
||
}
|
||
|
||
reset() {
|
||
this.snake = [
|
||
{x: 10, y: 10}
|
||
];
|
||
this.direction = {x: 0, y: 0};
|
||
this.apple = this.generateApple();
|
||
this.score = 0;
|
||
this.gameRunning = false;
|
||
this.gamePaused = false;
|
||
this.gameLoop = null;
|
||
this.speed = 100;
|
||
|
||
this.updateUI();
|
||
this.draw();
|
||
}
|
||
|
||
generateApple() {
|
||
let apple;
|
||
do {
|
||
apple = {
|
||
x: Math.floor(Math.random() * this.tileCount),
|
||
y: Math.floor(Math.random() * this.tileCount)
|
||
};
|
||
} while (this.snake.some(segment => segment.x === apple.x && segment.y === apple.y));
|
||
return apple;
|
||
}
|
||
|
||
setupEventListeners() {
|
||
document.getElementById('startBtn').addEventListener('click', () => this.start());
|
||
document.getElementById('pauseBtn').addEventListener('click', () => this.togglePause());
|
||
document.getElementById('resetBtn').addEventListener('click', () => this.reset());
|
||
}
|
||
|
||
start() {
|
||
if (!this.gameRunning) {
|
||
this.gameRunning = true;
|
||
this.gamePaused = false;
|
||
document.getElementById('startBtn').disabled = true;
|
||
document.getElementById('pauseBtn').disabled = false;
|
||
this.gameStep();
|
||
}
|
||
}
|
||
|
||
togglePause() {
|
||
if (this.gameRunning) {
|
||
this.gamePaused = !this.gamePaused;
|
||
document.getElementById('pauseBtn').textContent = this.gamePaused ? '继续' : '暂停';
|
||
if (!this.gamePaused) {
|
||
this.gameStep();
|
||
}
|
||
}
|
||
}
|
||
|
||
gameStep() {
|
||
if (!this.gameRunning || this.gamePaused) return;
|
||
|
||
this.moveSnake();
|
||
|
||
if (this.checkCollision()) {
|
||
this.gameOver();
|
||
return;
|
||
}
|
||
|
||
this.checkApple();
|
||
this.draw();
|
||
this.updateUI();
|
||
|
||
// 使用改进的AI控制蛇的移动
|
||
this.autoMove();
|
||
|
||
setTimeout(() => this.gameStep(), this.speed);
|
||
}
|
||
|
||
autoMove() {
|
||
// 改进的AI:优先保持直线移动,减少之字形路线
|
||
const head = this.snake[0];
|
||
const dx = this.apple.x - head.x;
|
||
const dy = this.apple.y - head.y;
|
||
|
||
// 如果当前方向是安全的且朝着苹果的大致方向,保持当前方向
|
||
const nextX = head.x + this.direction.x;
|
||
const nextY = head.y + this.direction.y;
|
||
|
||
if (this.isSafeMove(nextX, nextY) && this.isMovingTowardsApple(this.direction, dx, dy)) {
|
||
return; // 保持当前方向,走直线
|
||
}
|
||
|
||
// 否则,寻找最佳新方向
|
||
const directions = this.getPrioritizedDirections(dx, dy);
|
||
|
||
for (let dir of directions) {
|
||
const testX = head.x + dir.x;
|
||
const testY = head.y + dir.y;
|
||
|
||
if (this.isSafeMove(testX, testY)) {
|
||
this.direction = dir;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果没有找到安全方向,使用备用安全方向
|
||
this.findSafeDirection(head);
|
||
}
|
||
|
||
isMovingTowardsApple(direction, dx, dy) {
|
||
// 检查当前方向是否朝着苹果的大致方向
|
||
if (dx > 0 && direction.x > 0) return true; // 苹果在右,向右移动
|
||
if (dx < 0 && direction.x < 0) return true; // 苹果在左,向左移动
|
||
if (dy > 0 && direction.y > 0) return true; // 苹果在下,向下移动
|
||
if (dy < 0 && direction.y < 0) return true; // 苹果在上,向上移动
|
||
return false;
|
||
}
|
||
|
||
getPrioritizedDirections(dx, dy) {
|
||
// 根据苹果位置,返回优先级排序的方向
|
||
const directions = [];
|
||
|
||
// 确定主要方向
|
||
let primaryHorizontal = dx > 0 ? {x: 1, y: 0} : {x: -1, y: 0};
|
||
let primaryVertical = dy > 0 ? {x: 0, y: 1} : {x: 0, y: -1};
|
||
|
||
// 根据距离决定优先级,但避免频繁改变方向
|
||
if (Math.abs(dx) > Math.abs(dy)) {
|
||
// 水平距离更大,优先水平移动
|
||
directions.push(primaryHorizontal);
|
||
directions.push(primaryVertical);
|
||
} else if (Math.abs(dy) > Math.abs(dx)) {
|
||
// 垂直距离更大,优先垂直移动
|
||
directions.push(primaryVertical);
|
||
directions.push(primaryHorizontal);
|
||
} else {
|
||
// 距离相等,保持当前方向优先
|
||
if (this.direction.x !== 0) {
|
||
directions.push(primaryHorizontal);
|
||
directions.push(primaryVertical);
|
||
} else {
|
||
directions.push(primaryVertical);
|
||
directions.push(primaryHorizontal);
|
||
}
|
||
}
|
||
|
||
// 添加相反方向(最低优先级)
|
||
directions.push({x: -primaryHorizontal.x, y: -primaryHorizontal.y});
|
||
directions.push({x: -primaryVertical.x, y: -primaryVertical.y});
|
||
|
||
return directions;
|
||
}
|
||
|
||
isSafeMove(x, y) {
|
||
// 检查是否会撞墙
|
||
if (x < 0 || x >= this.tileCount || y < 0 || y >= this.tileCount) {
|
||
return false;
|
||
}
|
||
|
||
// 检查是否会撞到自己(除了尾巴)
|
||
for (let i = 0; i < this.snake.length - 1; i++) {
|
||
if (this.snake[i].x === x && this.snake[i].y === y) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
findSafeDirection(head) {
|
||
// 尝试四个方向,找到安全的方向
|
||
const directions = [
|
||
{x: 1, y: 0}, // 右
|
||
{x: -1, y: 0}, // 左
|
||
{x: 0, y: 1}, // 下
|
||
{x: 0, y: -1} // 上
|
||
];
|
||
|
||
for (let dir of directions) {
|
||
const nextX = head.x + dir.x;
|
||
const nextY = head.y + dir.y;
|
||
|
||
if (this.isSafeMove(nextX, nextY)) {
|
||
this.direction = dir;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果没有安全方向,保持当前方向(游戏即将结束)
|
||
}
|
||
|
||
moveSnake() {
|
||
const head = {x: this.snake[0].x + this.direction.x, y: this.snake[0].y + this.direction.y};
|
||
this.snake.unshift(head);
|
||
|
||
// 如果没有吃到苹果,移除尾巴
|
||
if (head.x !== this.apple.x || head.y !== this.apple.y) {
|
||
this.snake.pop();
|
||
}
|
||
}
|
||
|
||
checkCollision() {
|
||
const head = this.snake[0];
|
||
|
||
// 撞墙检测
|
||
if (head.x < 0 || head.x >= this.tileCount || head.y < 0 || head.y >= this.tileCount) {
|
||
return true;
|
||
}
|
||
|
||
// 撞到自己检测
|
||
for (let i = 1; i < this.snake.length; i++) {
|
||
if (head.x === this.snake[i].x && head.y === this.snake[i].y) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
checkApple() {
|
||
const head = this.snake[0];
|
||
if (head.x === this.apple.x && head.y === this.apple.y) {
|
||
this.score += 10;
|
||
this.apple = this.generateApple();
|
||
|
||
// 检查是否填满整个地图
|
||
if (this.snake.length >= this.tileCount * this.tileCount) {
|
||
this.gameWin();
|
||
}
|
||
}
|
||
}
|
||
|
||
gameWin() {
|
||
this.gameRunning = false;
|
||
document.getElementById('status').textContent = '恭喜!蛇已铺满整个地图!';
|
||
document.getElementById('startBtn').disabled = false;
|
||
document.getElementById('pauseBtn').disabled = true;
|
||
}
|
||
|
||
gameOver() {
|
||
this.gameRunning = false;
|
||
document.getElementById('status').textContent = '游戏结束';
|
||
document.getElementById('startBtn').disabled = false;
|
||
document.getElementById('pauseBtn').disabled = true;
|
||
}
|
||
|
||
draw() {
|
||
// 清空画布
|
||
this.ctx.fillStyle = '#111';
|
||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||
|
||
// 画蛇
|
||
this.ctx.fillStyle = '#4CAF50';
|
||
for (let i = 0; i < this.snake.length; i++) {
|
||
const segment = this.snake[i];
|
||
this.ctx.fillRect(segment.x * this.gridSize, segment.y * this.gridSize, this.gridSize - 2, this.gridSize - 2);
|
||
|
||
// 蛇头用不同颜色
|
||
if (i === 0) {
|
||
this.ctx.fillStyle = '#81C784';
|
||
}
|
||
}
|
||
|
||
// 画苹果
|
||
this.ctx.fillStyle = '#FF5722';
|
||
this.ctx.fillRect(this.apple.x * this.gridSize, this.apple.y * this.gridSize, this.gridSize - 2, this.gridSize - 2);
|
||
|
||
// 画网格
|
||
this.ctx.strokeStyle = '#333';
|
||
this.ctx.lineWidth = 1;
|
||
for (let i = 0; i <= this.tileCount; i++) {
|
||
this.ctx.beginPath();
|
||
this.ctx.moveTo(i * this.gridSize, 0);
|
||
this.ctx.lineTo(i * this.gridSize, this.canvas.height);
|
||
this.ctx.stroke();
|
||
|
||
this.ctx.beginPath();
|
||
this.ctx.moveTo(0, i * this.gridSize);
|
||
this.ctx.lineTo(this.canvas.width, i * this.gridSize);
|
||
this.ctx.stroke();
|
||
}
|
||
}
|
||
|
||
updateUI() {
|
||
document.getElementById('score').textContent = this.score;
|
||
document.getElementById('length').textContent = this.snake.length;
|
||
|
||
if (this.gameRunning && !this.gamePaused) {
|
||
document.getElementById('status').textContent = '游戏进行中';
|
||
} else if (this.gamePaused) {
|
||
document.getElementById('status').textContent = '游戏暂停';
|
||
} else if (!this.gameRunning && this.snake.length > 1) {
|
||
document.getElementById('status').textContent = '游戏结束';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 初始化游戏
|
||
window.addEventListener('DOMContentLoaded', () => {
|
||
new SnakeGame();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|