<!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: 'Arial', 'Microsoft YaHei', sans-serif;
}
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #e6e6e6;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.game-container {
width: 100%;
max-width: 1200px;
background-color: rgba(30, 30, 46, 0.9);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.game-header {
background: linear-gradient(90deg, #0f3460 0%, #1a5f7a 100%);
padding: 20px;
text-align: center;
border-bottom: 3px solid #00b4d8;
}
.game-header h1 {
font-size: 2.5rem;
color: #ffffff;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
margin-bottom: 10px;
background: linear-gradient(90deg, #00b4d8, #90e0ef);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.game-content {
display: flex;
padding: 20px;
gap: 20px;
}
.control-panel {
flex: 1;
min-width: 300px;
background-color: rgba(40, 40, 60, 0.8);
border-radius: 10px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.game-area {
flex: 2;
display: flex;
flex-direction: column;
align-items: center;
}
.game-screen {
position: relative;
background-color: #0d1b2a;
border: 2px solid #00b4d8;
border-radius: 5px;
overflow: hidden;
}
canvas {
display: block;
}
.control-section {
margin-bottom: 25px;
padding-bottom: 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.control-section:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.control-section h3 {
color: #90e0ef;
margin-bottom: 15px;
font-size: 1.2rem;
display: flex;
align-items: center;
gap: 8px;
}
.control-section h3 i {
color: #00b4d8;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 8px;
color: #cccccc;
font-size: 0.95rem;
}
.input-group input, .input-group select {
width: 100%;
padding: 10px 15px;
background-color: rgba(20, 20, 35, 0.9);
border: 1px solid rgba(0, 180, 216, 0.5);
border-radius: 8px;
color: white;
font-size: 1rem;
transition: all 0.3s;
}
.input-group input:focus, .input-group select:focus {
outline: none;
border-color: #00b4d8;
box-shadow: 0 0 10px rgba(0, 180, 216, 0.3);
}
.number-input {
display: flex;
align-items: center;
gap: 10px;
}
.number-input input {
flex: 1;
}
.range-value {
min-width: 40px;
text-align: center;
font-weight: bold;
color: #00b4d8;
}
.ai-snake-controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 10px;
}
.ai-snake-control {
background-color: rgba(20, 20, 35, 0.9);
border-radius: 8px;
padding: 15px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.ai-snake-control h4 {
color: #90e0ef;
margin-bottom: 10px;
font-size: 1rem;
}
.map-options {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.map-option {
background-color: rgba(20, 20, 35, 0.9);
border-radius: 8px;
padding: 12px;
text-align: center;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s;
}
.map-option:hover {
border-color: rgba(0, 180, 216, 0.5);
transform: translateY(-3px);
}
.map-option.selected {
border-color: #00b4d8;
background-color: rgba(0, 180, 216, 0.1);
}
.map-size {
font-size: 1.1rem;
font-weight: bold;
color: #90e0ef;
margin-bottom: 5px;
}
.map-features {
font-size: 0.8rem;
color: #aaaaaa;
}
.button-group {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 20px;
}
button {
padding: 14px;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
#start-btn {
background: linear-gradient(90deg, #00b4d8, #0077b6);
color: white;
}
#start-btn:hover {
background: linear-gradient(90deg, #0096c7, #005f8a);
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 180, 216, 0.3);
}
#pause-btn {
background: linear-gradient(90deg, #ff9e00, #ff9100);
color: white;
}
#pause-btn:hover {
background: linear-gradient(90deg, #e68900, #cc7a00);
transform: translateY(-3px);
}
#reset-btn {
background: linear-gradient(90deg, #ef476f, #e63946);
color: white;
}
#reset-btn:hover {
background: linear-gradient(90deg, #d43d63, #c1121f);
transform: translateY(-3px);
}
.stats-panel {
display: flex;
justify-content: space-between;
margin-top: 20px;
background-color: rgba(40, 40, 60, 0.8);
border-radius: 10px;
padding: 15px;
border: 1px solid rgba(255, 255, 255, 0.1);
width: 100%;
max-width: 800px;
}
.stat-item {
text-align: center;
flex: 1;
}
.stat-label {
font-size: 0.9rem;
color: #90e0ef;
margin-bottom: 5px;
}
.stat-value {
font-size: 1.8rem;
font-weight: bold;
color: white;
font-family: 'Courier New', monospace;
}
.game-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(13, 27, 42, 0.9);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
}
.game-message {
background: linear-gradient(135deg, #0f3460, #1a5f7a);
padding: 40px;
border-radius: 15px;
text-align: center;
border: 3px solid #00b4d8;
max-width: 500px;
width: 90%;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.game-message h2 {
font-size: 2.5rem;
color: white;
margin-bottom: 20px;
background: linear-gradient(90deg, #00b4d8, #90e0ef);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.game-message p {
font-size: 1.2rem;
margin-bottom: 25px;
line-height: 1.5;
}
.snake-colors {
display: flex;
gap: 10px;
margin-top: 15px;
justify-content: center;
}
.color-sample {
width: 20px;
height: 20px;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.player-color {
background-color: #00b4d8;
}
.smart-ai-color {
background-color: #2a9d8f;
}
.dumb-ai-color {
background-color: #e76f51;
}
.food-color {
background-color: #e9c46a;
}
.obstacle-color {
background-color: #9d4edd;
}
.color-label {
display: flex;
align-items: center;
gap: 5px;
font-size: 0.8rem;
color: #cccccc;
}
.legend {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 15px;
flex-wrap: wrap;
}
.instructions {
background-color: rgba(40, 40, 60, 0.8);
border-radius: 10px;
padding: 20px;
margin-top: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
width: 100%;
max-width: 800px;
}
.instructions h3 {
color: #90e0ef;
margin-bottom: 15px;
font-size: 1.2rem;
}
.instructions ul {
padding-left: 20px;
color: #cccccc;
line-height: 1.6;
}
.instructions li {
margin-bottom: 8px;
}
@media (max-width: 1024px) {
.game-content {
flex-direction: column;
}
.control-panel {
min-width: 100%;
}
.game-screen {
width: 100%;
}
}
@media (max-width: 768px) {
.game-header h1 {
font-size: 2rem;
}
.stats-panel {
flex-wrap: wrap;
}
.stat-item {
flex: 0 0 50%;
margin-bottom: 15px;
}
.map-options {
grid-template-columns: 1fr;
}
.ai-snake-controls {
grid-template-columns: 1fr;
}
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="game-container">
<div class="game-header">
<h1><i class="fas fa-gamepad"></i> 多蛇竞技贪吃蛇</h1>
<p>控制你的蛇,与AI蛇同台竞技,成为最后的生存者!</p>
</div>
<div class="game-content">
<div class="control-panel">
<div class="control-section">
<h3><i class="fas fa-sliders-h"></i> 游戏设置</h3>
<div class="input-group">
<label for="smart-ai-count">聪明AI蛇数量</label>
<div class="number-input">
<input type="range" id="smart-ai-count" min="0" max="10" value="2">
<span class="range-value" id="smart-ai-value">2</span>
</div>
</div>
<div class="input-group">
<label for="dumb-ai-count">愚蠢AI蛇数量</label>
<div class="number-input">
<input type="range" id="dumb-ai-count" min="0" max="10" value="2">
<span class="range-value" id="dumb-ai-value">2</span>
</div>
</div>
<div class="input-group">
<label for="game-speed">游戏速度</label>
<div class="number-input">
<input type="range" id="game-speed" min="5" max="20" value="10">
<span class="range-value" id="speed-value">10</span>
</div>
</div>
</div>
<div class="control-section">
<h3><i class="fas fa-map"></i> 地图选择</h3>
<div class="map-options">
<div class="map-option selected" data-size="50" data-obstacles="false">
<div class="map-size">50×50</div>
<div class="map-features">基础地图</div>
</div>
<div class="map-option" data-size="50" data-obstacles="true">
<div class="map-size">50×50</div>
<div class="map-features">随机障碍</div>
</div>
<div class="map-option" data-size="100" data-obstacles="false">
<div class="map-size">100×100</div>
<div class="map-features">大地图</div>
</div>
<div class="map-option" data-size="150" data-obstacles="false">
<div class="map-size">150×150</div>
<div class="map-features">超大地图</div>
</div>
<div class="map-option" data-size="100" data-obstacles="true">
<div class="map-size">100×100</div>
<div class="map-features">大地图+障碍</div>
</div>
<div class="map-option" data-size="150" data-obstacles="true">
<div class="map-size">150×150</div>
<div class="map-features">超大+障碍</div>
</div>
</div>
</div>
<div class="control-section">
<h3><i class="fas fa-robot"></i> AI蛇设置</h3>
<div class="ai-snake-controls">
<div class="ai-snake-control">
<h4><i class="fas fa-brain"></i> 聪明AI</h4>
<p>智能路径寻找,会避开障碍和其他蛇</p>
</div>
<div class="ai-snake-control">
<h4><i class="fas fa-dizzy"></i> 愚蠢AI</h4>
<p>随机移动,经常撞墙或撞到其他蛇</p>
</div>
</div>
</div>
<div class="control-section">
<h3><i class="fas fa-keyboard"></i> 控制说明</h3>
<ul style="color: #cccccc; padding-left: 20px; line-height: 1.6;">
<li><strong>W/↑</strong>: 向上移动</li>
<li><strong>A/←</strong>: 向左移动</li>
<li><strong>S/↓</strong>: 向下移动</li>
<li><strong>D/→</strong>: 向右移动</li>
<li><strong>空格键</strong>: 暂停/继续游戏</li>
</ul>
</div>
<div class="button-group">
<button id="start-btn">
<i class="fas fa-play"></i> 开始游戏
</button>
<button id="pause-btn">
<i class="fas fa-pause"></i> 暂停游戏
</button>
<button id="reset-btn">
<i class="fas fa-redo"></i> 重置游戏
</button>
</div>
</div>
<div class="game-area">
<div class="game-screen">
<canvas id="game-canvas"></canvas>
<div class="game-overlay" id="game-overlay">
<div class="game-message">
<h2 id="game-title">多蛇竞技贪吃蛇</h2>
<p id="game-message">设置游戏参数,然后点击"开始游戏"</p>
<button id="overlay-start-btn" style="margin-top: 20px;">
<i class="fas fa-play"></i> 开始游戏
</button>
</div>
</div>
</div>
<div class="stats-panel">
<div class="stat-item">
<div class="stat-label">玩家长度</div>
<div id="player-length" class="stat-value">3</div>
</div>
<div class="stat-item">
<div class="stat-label">存活AI蛇</div>
<div id="alive-ai" class="stat-value">0</div>
</div>
<div class="stat-item">
<div class="stat-label">食物数量</div>
<div id="food-count" class="stat-value">1</div>
</div>
<div class="stat-item">
<div class="stat-label">游戏时间</div>
<div id="game-time" class="stat-value">0s</div>
</div>
</div>
<div class="legend">
<div class="color-label">
<div class="color-sample player-color"></div>
<span>玩家蛇</span>
</div>
<div class="color-label">
<div class="color-sample smart-ai-color"></div>
<span>聪明AI蛇</span>
</div>
<div class="color-label">
<div class="color-sample dumb-ai-color"></div>
<span>愚蠢AI蛇</span>
</div>
<div class="color-label">
<div class="color-sample food-color"></div>
<span>食物</span>
</div>
<div class="color-label">
<div class="color-sample obstacle-color"></div>
<span>障碍物</span>
</div>
</div>
<div class="instructions">
<h3><i class="fas fa-info-circle"></i> 游戏规则</h3>
<ul>
<li>控制你的蛇(蓝色)使用方向键或WASD键移动</li>
<li>吃掉食物(黄色)可以增加长度</li>
<li>避免撞到墙壁、障碍物(紫色)或其他蛇</li>
<li>生存到最后,成为最长的蛇</li>
<li>聪明AI蛇(绿色)会智能寻找食物和躲避危险</li>
<li>愚蠢AI蛇(红色)会随机移动,经常自毁</li>
</ul>
</div>
</div>
</div>
</div>
<script>
const config = {
gridSize: 50,
cellSize: 0,
obstacles: false,
gameSpeed: 10,
smartAICount: 2,
dumbAICount: 2,
foodCount: 5,
obstacleCount: 20,
gameRunning: false,
gamePaused: false,
gameTime: 0,
gameInterval: null
};
const gameState = {
playerSnake: null,
aiSnakes: [],
food: [],
obstacles: [],
allSnakes: []
};
const colors = {
player: '#00b4d8',
smartAI: '#2a9d8f',
dumbAI: '#e76f51',
food: '#e9c46a',
obstacle: '#9d4edd',
grid: '#14213d',
background: '#0d1b2a'
};
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const gameOverlay = document.getElementById('game-overlay');
const gameTitle = document.getElementById('game-title');
const gameMessage = document.getElementById('game-message');
class Snake {
constructor(x, y, color, isPlayer = false, intelligence = 'smart') {
this.x = x;
this.y = y;
this.color = color;
this.isPlayer = isPlayer;
this.intelligence = intelligence;
this.direction = this.getRandomDirection();
this.body = [{x, y}];
this.length = 3;
this.speed = 1;
this.alive = true;
this.score = 0;
this.moveCounter = 0;
this.moveDelay = isPlayer ? 1 : (intelligence === 'smart' ? 2 : 1);
for (let i = 1; i < this.length; i++) {
this.body.push({x: x - i, y});
}
}
getRandomDirection() {
const directions = ['up', 'down', 'left', 'right'];
return directions[Math.floor(Math.random() * directions.length)];
}
move() {
if (!this.alive) return;
this.moveCounter++;
if (this.moveCounter < this.moveDelay) return;
this.moveCounter = 0;
const head = {x: this.body[0].x, y: this.body[0].y};
switch(this.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 >= config.gridSize ||
head.y < 0 || head.y >= config.gridSize) {
this.die();
return;
}
for (const obstacle of gameState.obstacles) {
if (head.x === obstacle.x && head.y === obstacle.y) {
this.die();
return;
}
}
for (const snake of gameState.allSnakes) {
if (!snake.alive) continue;
for (let i = 0; i < snake.body.length; i++) {
if (snake === this && i === 0) continue;
const segment = snake.body[i];
if (head.x === segment.x && head.y === segment.y) {
this.die();
return;
}
}
}
this.body.unshift(head);
if (this.body.length > this.length) {
this.body.pop();
}
for (let i = 0; i < gameState.food.length; i++) {
const food = gameState.food[i];
if (head.x === food.x && head.y === food.y) {
this.length++;
this.score += 10;
gameState.food.splice(i, 1);
spawnFood();
updateStats();
break;
}
}
}
die() {
this.alive = false;
updateStats();
if (this.isPlayer) {
endGame(false);
}
}
draw() {
if (!this.alive) return;
for (let i = 0; i < this.body.length; i++) {
const segment = this.body[i];
const alpha = 1 - (i / this.body.length) * 0.7;
ctx.fillStyle = this.color;
ctx.globalAlpha = alpha;
ctx.fillRect(
segment.x * config.cellSize,
segment.y * config.cellSize,
config.cellSize - 1,
config.cellSize - 1
);
if (i === 0) {
ctx.fillStyle = 'white';
ctx.globalAlpha = 1;
const eyeSize = config.cellSize / 5;
const offset = config.cellSize / 3;
if (this.direction === 'right') {
ctx.fillRect(
segment.x * config.cellSize + config.cellSize - offset,
segment.y * config.cellSize + offset,
eyeSize, eyeSize
);
ctx.fillRect(
segment.x * config.cellSize + config.cellSize - offset,
segment.y * config.cellSize + config.cellSize - offset - eyeSize,
eyeSize, eyeSize
);
} else if (this.direction === 'left') {
ctx.fillRect(
segment.x * config.cellSize + offset - eyeSize,
segment.y * config.cellSize + offset,
eyeSize, eyeSize
);
ctx.fillRect(
segment.x * config.cellSize + offset - eyeSize,
segment.y * config.cellSize + config.cellSize - offset - eyeSize,
eyeSize, eyeSize
);
} else if (this.direction === 'up') {
ctx.fillRect(
segment.x * config.cellSize + offset,
segment.y * config.cellSize + offset - eyeSize,
eyeSize, eyeSize
);
ctx.fillRect(
segment.x * config.cellSize + config.cellSize - offset - eyeSize,
segment.y * config.cellSize + offset - eyeSize,
eyeSize, eyeSize
);
} else if (this.direction === 'down') {
ctx.fillRect(
segment.x * config.cellSize + offset,
segment.y * config.cellSize + config.cellSize - offset,
eyeSize, eyeSize
);
ctx.fillRect(
segment.x * config.cellSize + config.cellSize - offset - eyeSize,
segment.y * config.cellSize + config.cellSize - offset,
eyeSize, eyeSize
);
}
}
}
ctx.globalAlpha = 1;
}
makeDecision() {
if (this.isPlayer || !this.alive) return;
if (this.intelligence === 'smart') {
this.smartAI();
} else {
this.dumbAI();
}
}
smartAI() {
let nearestFood = null;
let nearestDistance = Infinity;
for (const food of gameState.food) {
const distance = Math.abs(this.body[0].x - food.x) + Math.abs(this.body[0].y - food.y);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestFood = food;
}
}
if (nearestFood) {
const directions = [];
const directionValues = {
'up': {x: 0, y: -1, value: 0},
'down': {x: 0, y: 1, value: 0},
'left': {x: -1, y: 0, value: 0},
'right': {x: 1, y: 0, value: 0}
};
for (const dir in directionValues) {
const dx = directionValues[dir].x;
const dy = directionValues[dir].y;
const newX = this.body[0].x + dx;
const newY = this.body[0].y + dy;
if (newX < 0 || newX >= config.gridSize ||
newY < 0 || newY >= config.gridSize) {
directionValues[dir].value = -1000;
continue;
}
let hitObstacle = false;
for (const obstacle of gameState.obstacles) {
if (newX === obstacle.x && newY === obstacle.y) {
hitObstacle = true;
break;
}
}
if (hitObstacle) {
directionValues[dir].value = -1000;
continue;
}
let hitSnake = false;
for (const snake of gameState.allSnakes) {
if (!snake.alive) continue;
for (let i = 0; i < snake.body.length; i++) {
if (snake === this && i === 0) continue;
const segment = snake.body[i];
if (newX === segment.x && newY === segment.y) {
hitSnake = true;
break;
}
}
if (hitSnake) break;
}
if (hitSnake) {
directionValues[dir].value = -500;
continue;
}
if (nearestFood) {
const distToFood = Math.abs(newX - nearestFood.x) + Math.abs(newY - nearestFood.y);
directionValues[dir].value = -distToFood;
}
if ((dir === 'up' && this.direction === 'down') ||
(dir === 'down' && this.direction === 'up') ||
(dir === 'left' && this.direction === 'right') ||
(dir === 'right' && this.direction === 'left')) {
directionValues[dir].value -= 50;
}
}
let bestDirection = this.direction;
let bestValue = -Infinity;
for (const dir in directionValues) {
if (directionValues[dir].value > bestValue) {
bestValue = directionValues[dir].value;
bestDirection = dir;
}
}
this.direction = bestDirection;
} else {
this.dumbAI();
}
}
dumbAI() {
if (Math.random() < 0.2) {
const directions = ['up', 'down', 'left', 'right'];
const validDirections = directions.filter(dir => {
return !((dir === 'up' && this.direction === 'down') ||
(dir === 'down' && this.direction === 'up') ||
(dir === 'left' && this.direction === 'right') ||
(dir === 'right' && this.direction === 'left'));
});
if (validDirections.length > 0) {
this.direction = validDirections[Math.floor(Math.random() * validDirections.length)];
}
}
}
}
function initGame() {
config.smartAICount = parseInt(document.getElementById('smart-ai-count').value);
config.dumbAICount = parseInt(document.getElementById('dumb-ai-count').value);
config.gameSpeed = parseInt(document.getElementById('game-speed').value);
gameState.playerSnake = null;
gameState.aiSnakes = [];
gameState.food = [];
gameState.obstacles = [];
gameState.allSnakes = [];
config.gameTime = 0;
const screenSize = Math.min(800, window.innerWidth - 400);
config.cellSize = Math.floor(screenSize / config.gridSize);
canvas.width = config.gridSize * config.cellSize;
canvas.height = config.gridSize * config.cellSize;
if (config.obstacles) {
generateObstacles();
}
const playerPos = getValidPosition();
gameState.playerSnake = new Snake(playerPos.x, playerPos.y, colors.player, true);
gameState.allSnakes.push(gameState.playerSnake);
for (let i = 0; i < config.smartAICount; i++) {
const pos = getValidPosition();
const snake = new Snake(pos.x, pos.y, colors.smartAI, false, 'smart');
gameState.aiSnakes.push(snake);
gameState.allSnakes.push(snake);
}
for (let i = 0; i < config.dumbAICount; i++) {
const pos = getValidPosition();
const snake = new Snake(pos.x, pos.y, colors.dumbAI, false, 'dumb');
gameState.aiSnakes.push(snake);
gameState.allSnakes.push(snake);
}
for (let i = 0; i < config.foodCount; i++) {
spawnFood();
}
updateStats();
drawGame();
gameOverlay.style.display = 'none';
config.gameRunning = true;
config.gamePaused = false;
document.getElementById('game-time').textContent = '0s';
}
function getValidPosition() {
let x, y, valid;
do {
valid = true;
x = Math.floor(Math.random() * config.gridSize);
y = Math.floor(Math.random() * config.gridSize);
for (const obstacle of gameState.obstacles) {
if (x === obstacle.x && y === obstacle.y) {
valid = false;
break;
}
}
if (valid) {
for (const snake of gameState.allSnakes) {
for (const segment of snake.body) {
if (x === segment.x && y === segment.y) {
valid = false;
break;
}
}
if (!valid) break;
}
}
if (valid) {
for (const food of gameState.food) {
if (x === food.x && y === food.y) {
valid = false;
break;
}
}
}
} while (!valid);
return {x, y};
}
function generateObstacles() {
gameState.obstacles = [];
const obstacleCount = config.obstacleCount;
for (let i = 0; i < obstacleCount; i++) {
const obstacle = getValidPosition();
gameState.obstacles.push(obstacle);
}
}
function spawnFood() {
const food = getValidPosition();
gameState.food.push(food);
}
function drawGame() {
ctx.fillStyle = colors.background;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = colors.grid;
ctx.lineWidth = 0.5;
for (let x = 0; x <= config.gridSize; x++) {
ctx.beginPath();
ctx.moveTo(x * config.cellSize, 0);
ctx.lineTo(x * config.cellSize, canvas.height);
ctx.stroke();
}
for (let y = 0; y <= config.gridSize; y++) {
ctx.beginPath();
ctx.moveTo(0, y * config.cellSize);
ctx.lineTo(canvas.width, y * config.cellSize);
ctx.stroke();
}
ctx.fillStyle = colors.obstacle;
for (const obstacle of gameState.obstacles) {
ctx.fillRect(
obstacle.x * config.cellSize,
obstacle.y * config.cellSize,
config.cellSize - 1,
config.cellSize - 1
);
}
ctx.fillStyle = colors.food;
for (const food of gameState.food) {
ctx.beginPath();
ctx.arc(
food.x * config.cellSize + config.cellSize / 2,
food.y * config.cellSize + config.cellSize / 2,
config.cellSize / 2 - 2,
0,
Math.PI * 2
);
ctx.fill();
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.arc(
food.x * config.cellSize + config.cellSize / 3,
food.y * config.cellSize + config.cellSize / 3,
config.cellSize / 6,
0,
Math.PI * 2
);
ctx.fill();
ctx.fillStyle = colors.food;
}
for (const snake of gameState.allSnakes) {
snake.draw();
}
}
function updateGame() {
if (!config.gameRunning || config.gamePaused) return;
gameState.playerSnake.move();
for (const snake of gameState.aiSnakes) {
snake.makeDecision();
snake.move();
}
checkGameEnd();
config.gameTime++;
if (config.gameTime % 10 === 0) {
const seconds = Math.floor(config.gameTime / 10);
document.getElementById('game-time').textContent = `${seconds}s`;
}
drawGame();
updateStats();
}
function updateStats() {
if (gameState.playerSnake && gameState.playerSnake.alive) {
document.getElementById('player-length').textContent = gameState.playerSnake.length;
}
const aliveAI = gameState.aiSnakes.filter(snake => snake.alive).length;
document.getElementById('alive-ai').textContent = aliveAI;
document.getElementById('food-count').textContent = gameState.food.length;
}
function checkGameEnd() {
if (!gameState.playerSnake.alive) {
endGame(false);
return;
}
const aliveAI = gameState.aiSnakes.filter(snake => snake.alive).length;
if (aliveAI === 0) {
endGame(true);
return;
}
}
function endGame(isWin) {
config.gameRunning = false;
clearInterval(config.gameInterval);
gameOverlay.style.display = 'flex';
if (isWin) {
gameTitle.textContent = '胜利!';
gameMessage.textContent = `恭喜!你击败了所有AI蛇!最终长度: ${gameState.playerSnake.length}`;
} else {
gameTitle.textContent = '游戏结束';
gameMessage.textContent = `你的蛇死亡了!最终长度: ${gameState.playerSnake.length}`;
}
}
function startGameLoop() {
if (config.gameInterval) {
clearInterval(config.gameInterval);
}
const interval = 1000 / config.gameSpeed;
config.gameInterval = setInterval(updateGame, interval);
}
function togglePause() {
if (!config.gameRunning) return;
config.gamePaused = !config.gamePaused;
if (config.gamePaused) {
gameOverlay.style.display = 'flex';
gameTitle.textContent = '游戏暂停';
gameMessage.textContent = '按空格键或点击"继续游戏"按钮继续';
} else {
gameOverlay.style.display = 'none';
startGameLoop();
}
}
function setupEventListeners() {
document.getElementById('smart-ai-count').addEventListener('input', function() {
document.getElementById('smart-ai-value').textContent = this.value;
});
document.getElementById('dumb-ai-count').addEventListener('input', function() {
document.getElementById('dumb-ai-value').textContent = this.value;
});
document.getElementById('game-speed').addEventListener('input', function() {
document.getElementById('speed-value').textContent = this.value;
if (config.gameRunning && !config.gamePaused) {
startGameLoop();
}
});
document.querySelectorAll('.map-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.map-option').forEach(opt => {
opt.classList.remove('selected');
});
this.classList.add('selected');
config.gridSize = parseInt(this.dataset.size);
config.obstacles = this.dataset.obstacles === 'true';
});
});
document.getElementById('start-btn').addEventListener('click', function() {
if (!config.gameRunning) {
initGame();
startGameLoop();
} else if (config.gamePaused) {
togglePause();
}
});
document.getElementById('overlay-start-btn').addEventListener('click', function() {
if (!config.gameRunning) {
initGame();
startGameLoop();
} else if (config.gamePaused) {
togglePause();
}
});
document.getElementById('pause-btn').addEventListener('click', togglePause);
document.getElementById('reset-btn').addEventListener('click', function() {
config.gameRunning = false;
config.gamePaused = false;
clearInterval(config.gameInterval);
initGame();
gameOverlay.style.display = 'flex';
gameTitle.textContent = '多蛇竞技贪吃蛇';
gameMessage.textContent = '设置游戏参数,然后点击"开始游戏"';
});
document.addEventListener('keydown', function(e) {
if (!config.gameRunning || !gameState.playerSnake || !gameState.playerSnake.alive) return;
switch(e.key.toLowerCase()) {
case 'w':
case 'arrowup':
if (gameState.playerSnake.direction !== 'down') {
gameState.playerSnake.direction = 'up';
}
break;
case 's':
case 'arrowdown':
if (gameState.playerSnake.direction !== 'up') {
gameState.playerSnake.direction = 'down';
}
break;
case 'a':
case 'arrowleft':
if (gameState.playerSnake.direction !== 'right') {
gameState.playerSnake.direction = 'left';
}
break;
case 'd':
case 'arrowright':
if (gameState.playerSnake.direction !== 'left') {
gameState.playerSnake.direction = 'right';
}
break;
case ' ':
e.preventDefault();
togglePause();
break;
}
});
}
window.onload = function() {
setupEventListeners();
config.cellSize = 16;
canvas.width = config.gridSize * config.cellSize;
canvas.height = config.gridSize * config.cellSize;
drawGame();
gameOverlay.style.display = 'flex';
};
</script>
</body>
</html>