<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI贪吃蛇大战</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft Yahei", sans-serif;
}
body {
background-color: #f0f0f0;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.game-container {
position: relative;
border: 2px solid #333;
background-color: #fff;
}
.start-panel {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
margin-bottom: 20px;
}
.start-panel h2 {
text-align: center;
margin-bottom: 20px;
color: #333;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input, .form-group select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.ai-type-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 15px;
}
.ai-type-item {
flex: 1;
min-width: 120px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:hover {
background-color: #45a049;
}
.rank-panel {
position: absolute;
top: 10px;
right: 10px;
background-color: rgba(0,0,0,0.7);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
min-width: 150px;
}
.rank-panel h3 {
text-align: center;
margin-bottom: 5px;
}
.rank-item {
margin: 3px 0;
}
.game-info {
margin-top: 15px;
font-size: 18px;
color: #333;
}
</style>
</head>
<body>
<div class="start-panel" id="startPanel">
<h2>AI贪吃蛇大战设置</h2>
<div class="form-group">
<label>游戏模式</label>
<select id="gameMode">
<option value="player">玩家游玩模式(有主角蛇)</option>
<option value="aiOnly">纯AI搏杀模式(无主角蛇)</option>
</select>
</div>
<div class="form-group">
<label>地图尺寸</label>
<select id="mapSize">
<option value="50">50x50</option>
<option value="100">100x100</option>
<option value="150">150x150</option>
<option value="200">200x200</option>
</select>
</div>
<div class="form-group">
<label>障碍比例</label>
<select id="obstacleRate">
<option value="0">无障碍</option>
<option value="1">障碍1%</option>
<option value="5">障碍5%</option>
<option value="10">障碍10%</option>
<option value="20">障碍20%</option>
<option value="50">障碍50%</option>
</select>
</div>
<div class="form-group">
<label>AI蛇总数</label>
<input type="number" id="totalAiSnakes" min="0" max="20" value="4" placeholder="输入AI蛇数量(0-20)">
</div>
<div class="ai-type-group">
<div class="ai-type-item">
<label>天才AI数量</label>
<input type="number" id="geniusAi" min="0" value="1" placeholder="天才AI数量">
</div>
<div class="ai-type-item">
<label>聪明AI数量</label>
<input type="number" id="smartAi" min="0" value="1" placeholder="聪明AI数量">
</div>
<div class="ai-type-item">
<label>平凡AI数量</label>
<input type="number" id="ordinaryAi" min="0" value="1" placeholder="平凡AI数量">
</div>
<div class="ai-type-item">
<label>愚蠢AI数量</label>
<input type="number" id="stupidAi" min="0" value="1" placeholder="愚蠢AI数量">
</div>
</div>
<button onclick="startGame()">开始游戏</button>
</div>
<div class="game-container" id="gameContainer"></div>
<div class="rank-panel" id="rankPanel" style="display: none;">
<h3>AI蛇排行</h3>
<div id="rankList"></div>
</div>
<div class="game-info" id="gameInfo"></div>
<script>
let canvas, ctx;
let mapSize, cellSize;
let obstacleRate;
let gameMode;
let obstacles = [];
let foods = [];
let snakes = [];
let playerSnake = null;
let gameInterval;
let aiSnakeConfigs = {
genius: 0,
smart: 0,
ordinary: 0,
stupid: 0
};
const directions = {
UP: { x: 0, y: -1 },
DOWN: { x: 0, y: 1 },
LEFT: { x: -1, y: 0 },
RIGHT: { x: 1, y: 0 }
};
const snakeColors = [
'#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF',
'#800000', '#008000', '#000080', '#808000', '#800080', '#008080'
];
function startGame() {
gameMode = document.getElementById('gameMode').value;
mapSize = parseInt(document.getElementById('mapSize').value);
obstacleRate = parseInt(document.getElementById('obstacleRate').value) / 100;
aiSnakeConfigs.genius = parseInt(document.getElementById('geniusAi').value) || 0;
aiSnakeConfigs.smart = parseInt(document.getElementById('smartAi').value) || 0;
aiSnakeConfigs.ordinary = parseInt(document.getElementById('ordinaryAi').value) || 0;
aiSnakeConfigs.stupid = parseInt(document.getElementById('stupidAi').value) || 0;
const totalAi = aiSnakeConfigs.genius + aiSnakeConfigs.smart +
aiSnakeConfigs.ordinary + aiSnakeConfigs.stupid;
const inputTotal = parseInt(document.getElementById('totalAiSnakes').value) || 0;
if (totalAi !== inputTotal) {
alert(`AI蛇数量不匹配!各类型总和(${totalAi})与输入总数(${inputTotal})不一致`);
return;
}
document.getElementById('startPanel').style.display = 'none';
const gameContainer = document.getElementById('gameContainer');
gameContainer.style.display = 'block';
if (gameMode === 'aiOnly') {
document.getElementById('rankPanel').style.display = 'block';
}
cellSize = Math.floor(800 / mapSize);
canvas = document.createElement('canvas');
canvas.width = mapSize * cellSize;
canvas.height = mapSize * cellSize;
gameContainer.innerHTML = '';
gameContainer.appendChild(canvas);
ctx = canvas.getContext('2d');
initObstacles();
initFoods(10);
initSnakes();
if (gameMode === 'player') {
bindPlayerControls();
}
gameInterval = setInterval(gameLoop, 100);
}
function initObstacles() {
obstacles = [];
const totalCells = mapSize * mapSize;
const obstacleCount = Math.floor(totalCells * obstacleRate);
for (let i = 0; i < obstacleCount; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (mapSize - 2)) + 1;
y = Math.floor(Math.random() * (mapSize - 2)) + 1;
} while (obstacles.some(obs => obs.x === x && obs.y === y));
obstacles.push({ x, y });
}
}
function initFoods(count) {
foods = [];
for (let i = 0; i < count; i++) {
spawnFood();
}
}
function spawnFood(excludePositions = []) {
let x, y;
do {
x = Math.floor(Math.random() * mapSize);
y = Math.floor(Math.random() * mapSize);
} while (
obstacles.some(obs => obs.x === x && obs.y === y) ||
foods.some(food => food.x === x && food.y === y) ||
snakes.some(snake => snake.body.some(part => part.x === x && part.y === y)) ||
excludePositions.some(pos => pos.x === x && pos.y === y)
);
foods.push({ x, y });
}
function initSnakes() {
snakes = [];
let snakeId = 0;
if (gameMode === 'player') {
playerSnake = createSnake(snakeId++, 'player', '#000000', mapSize / 2, mapSize / 2);
snakes.push(playerSnake);
}
let colorIndex = 0;
for (let i = 0; i < aiSnakeConfigs.genius; i++) {
const [x, y] = getRandomSafePosition();
snakes.push(createSnake(
snakeId++,
'genius',
snakeColors[colorIndex++ % snakeColors.length],
x, y,
`天才-${i+1}`
));
}
for (let i = 0; i < aiSnakeConfigs.smart; i++) {
const [x, y] = getRandomSafePosition();
snakes.push(createSnake(
snakeId++,
'smart',
snakeColors[colorIndex++ % snakeColors.length],
x, y,
`聪明-${i+1}`
));
}
for (let i = 0; i < aiSnakeConfigs.ordinary; i++) {
const [x, y] = getRandomSafePosition();
snakes.push(createSnake(
snakeId++,
'ordinary',
snakeColors[colorIndex++ % snakeColors.length],
x, y,
`平凡-${i+1}`
));
}
for (let i = 0; i < aiSnakeConfigs.stupid; i++) {
const [x, y] = getRandomSafePosition();
snakes.push(createSnake(
snakeId++,
'stupid',
snakeColors[colorIndex++ % snakeColors.length],
x, y,
`愚蠢-${i+1}`
));
}
}
function createSnake(id, type, color, x, y, name = 'AI蛇') {
const initialDirection = Object.values(directions)[Math.floor(Math.random() * 4)];
return {
id,
type,
name,
color,
body: [
{ x, y },
{ x: x - initialDirection.x, y: y - initialDirection.y },
{ x: x - 2 * initialDirection.x, y: y - 2 * initialDirection.y }
],
direction: initialDirection,
nextDirection: initialDirection,
alive: true,
score: 0
};
}
function getRandomSafePosition() {
let x, y;
do {
x = Math.floor(Math.random() * (mapSize - 10)) + 5;
y = Math.floor(Math.random() * (mapSize - 10)) + 5;
} while (
obstacles.some(obs => obs.x === x && obs.y === y) ||
snakes.some(snake => snake.body.some(part => part.x === x && part.y === y))
);
return [x, y];
}
function bindPlayerControls() {
document.addEventListener('keydown', (e) => {
if (!playerSnake || !playerSnake.alive) return;
switch (e.key) {
case 'ArrowUp':
if (playerSnake.direction !== directions.DOWN) {
playerSnake.nextDirection = directions.UP;
}
break;
case 'ArrowDown':
if (playerSnake.direction !== directions.UP) {
playerSnake.nextDirection = directions.DOWN;
}
break;
case 'ArrowLeft':
if (playerSnake.direction !== directions.RIGHT) {
playerSnake.nextDirection = directions.LEFT;
}
break;
case 'ArrowRight':
if (playerSnake.direction !== directions.LEFT) {
playerSnake.nextDirection = directions.RIGHT;
}
break;
}
});
}
function aiDecision(snake) {
if (!snake.alive) return;
const head = snake.body[0];
const possibleDirections = getSafeDirections(snake);
if (possibleDirections.length === 0) {
snake.nextDirection = Object.values(directions)[Math.floor(Math.random() * 4)];
return;
}
switch (snake.type) {
case 'genius':
geniusAiDecision(snake, possibleDirections);
break;
case 'smart':
smartAiDecision(snake, possibleDirections);
break;
case 'ordinary':
ordinaryAiDecision(snake, possibleDirections);
break;
case 'stupid':
stupidAiDecision(snake, possibleDirections);
break;
}
}
function getSafeDirections(snake) {
const head = snake.body[0];
const safeDirs = [];
Object.values(directions).forEach(dir => {
const nextX = head.x + dir.x;
const nextY = head.y + dir.y;
if (nextX < 0 || nextX >= mapSize || nextY < 0 || nextY >= mapSize) return;
if (obstacles.some(obs => obs.x === nextX && obs.y === nextY)) return;
if (snake.body.some((part, index) => index > 0 && part.x === nextX && part.y === nextY)) return;
safeDirs.push(dir);
});
return safeDirs;
}
function geniusAiDecision(snake, possibleDirections) {
const head = snake.body[0];
const snakeLength = snake.body.length;
let targetDir = null;
let longestSnake = null;
let maxLength = 0;
snakes.forEach(s => {
if (s.alive && s.id !== snake.id && s.body.length > maxLength) {
maxLength = s.body.length;
longestSnake = s;
}
});
const prioritizeFood = snakeLength < 15 || (longestSnake && longestSnake.body.length > snakeLength * 1.5);
if (prioritizeFood) {
targetDir = findDirectionToNearestFood(snake, possibleDirections);
} else {
targetDir = findDirectionToNearestSnake(snake, possibleDirections);
}
snake.nextDirection = targetDir || possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
}
function smartAiDecision(snake, possibleDirections) {
const targetDir = findDirectionToNearestFood(snake, possibleDirections);
snake.nextDirection = targetDir || possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
}
function ordinaryAiDecision(snake, possibleDirections) {
let bestDir = possibleDirections[0];
let maxSpace = 0;
possibleDirections.forEach(dir => {
const space = calculateSafeSpace(snake.body[0], dir, snake);
if (space > maxSpace) {
maxSpace = space;
bestDir = dir;
}
});
snake.nextDirection = bestDir;
}
function stupidAiDecision(snake, possibleDirections) {
snake.nextDirection = possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
}
function calculateSafeSpace(startPos, dir, snake) {
let x = startPos.x + dir.x;
let y = startPos.y + dir.y;
let space = 0;
while (
x >= 0 && x < mapSize && y >= 0 && y < mapSize &&
!obstacles.some(obs => obs.x === x && obs.y === y) &&
!snakes.some(s => s.alive && s.body.some(part => part.x === x && part.y === y))
) {
space++;
x += dir.x;
y += dir.y;
}
return space;
}
function findDirectionToNearestFood(snake, possibleDirections) {
const head = snake.body[0];
let nearestFood = null;
let minDistance = Infinity;
foods.forEach(food => {
const distance = Math.hypot(head.x - food.x, head.y - food.y);
if (distance < minDistance) {
minDistance = distance;
nearestFood = food;
}
});
if (!nearestFood) return null;
let bestDir = null;
let bestDistance = Infinity;
possibleDirections.forEach(dir => {
const nextX = head.x + dir.x;
const nextY = head.y + dir.y;
const distance = Math.hypot(nextX - nearestFood.x, nextY - nearestFood.y);
if (distance < bestDistance) {
bestDistance = distance;
bestDir = dir;
}
});
return bestDir;
}
function findDirectionToNearestSnake(snake, possibleDirections) {
const head = snake.body[0];
let nearestSnakePart = null;
let minDistance = Infinity;
snakes.forEach(s => {
if (s.alive && s.id !== snake.id) {
s.body.forEach(part => {
const distance = Math.hypot(head.x - part.x, head.y - part.y);
if (distance < minDistance && distance > 0) {
minDistance = distance;
nearestSnakePart = part;
}
});
}
});
if (!nearestSnakePart) return null;
let bestDir = null;
let bestDistance = Infinity;
possibleDirections.forEach(dir => {
const nextX = head.x + dir.x;
const nextY = head.y + dir.y;
const distance = Math.hypot(nextX - nearestSnakePart.x, nextY - nearestSnakePart.y);
if (distance < bestDistance) {
bestDistance = distance;
bestDir = dir;
}
});
return bestDir;
}
function moveSnake(snake) {
if (!snake.alive) return;
snake.direction = snake.nextDirection;
const head = { ...snake.body[0] };
head.x += snake.direction.x;
head.y += snake.direction.y;
if (checkCollision(head, snake)) {
snake.alive = false;
handleSnakeDeath(snake);
return;
}
snake.body.unshift(head);
const foodIndex = foods.findIndex(food => food.x === head.x && food.y === head.y);
if (foodIndex !== -1) {
foods.splice(foodIndex, 1);
snake.score += 10;
spawnFood();
} else {
snake.body.pop();
}
}
function checkCollision(head, snake) {
if (head.x < 0 || head.x >= mapSize || head.y < 0 || head.y >= mapSize) {
return true;
}
if (obstacles.some(obs => obs.x === head.x && obs.y === head.y)) {
return true;
}
for (const s of snakes) {
if (s.alive) {
for (let i = 0; i < s.body.length; i++) {
if (s.id === snake.id && i === s.body.length - 1) continue;
if (s.body[i].x === head.x && s.body[i].y === head.y) {
return true;
}
}
}
}
return false;
}
function handleSnakeDeath(snake) {
const foodCount = Math.floor(snake.body.length * 0.5);
const bodyPartsToFood = snake.body.slice(0, foodCount);
bodyPartsToFood.forEach(part => {
spawnFood([...foods, ...obstacles]);
});
if (snake.type === 'player') {
document.getElementById('gameInfo').textContent = '游戏结束!你的蛇死亡了';
clearInterval(gameInterval);
}
}
function updateRankings() {
const rankList = document.getElementById('rankList');
const sortedSnakes = snakes
.filter(s => s.alive && s.type !== 'player')
.sort((a, b) => b.body.length - a.body.length);
let html = '';
sortedSnakes.forEach((snake, index) => {
html += `<div class="rank-item">${index+1}. ${snake.name} - 长度: ${snake.body.length}</div>`;
});
rankList.innerHTML = html;
}
function drawGame() {
ctx.fillStyle = '#f8f8f8';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#888888';
obstacles.forEach(obs => {
ctx.fillRect(obs.x * cellSize, obs.y * cellSize, cellSize - 1, cellSize - 1);
});
ctx.fillStyle = '#FF4444';
foods.forEach(food => {
ctx.fillRect(food.x * cellSize, food.y * cellSize, cellSize - 1, cellSize - 1);
});
snakes.forEach(snake => {
if (snake.alive) {
ctx.fillStyle = snake.color;
ctx.fillRect(snake.body[0].x * cellSize, snake.body[0].y * cellSize, cellSize - 1, cellSize - 1);
ctx.fillStyle = adjustColor(snake.color, -30);
for (let i = 1; i < snake.body.length; i++) {
ctx.fillRect(snake.body[i].x * cellSize, snake.body[i].y * cellSize, cellSize - 1, cellSize - 1);
}
}
});
if (playerSnake && playerSnake.alive) {
document.getElementById('gameInfo').textContent = `你的蛇长度: ${playerSnake.body.length} | 剩余AI蛇: ${snakes.filter(s => s.alive && s.type !== 'player').length}`;
} else if (gameMode === 'aiOnly') {
document.getElementById('gameInfo').textContent = `剩余AI蛇: ${snakes.filter(s => s.alive).length}`;
}
}
function adjustColor(color, amount) {
let usePound = false;
if (color[0] === '#') {
color = color.slice(1);
usePound = true;
}
let num = parseInt(color, 16);
let r = (num >> 16) + amount;
let g = (num >> 8 & 0x00FF) + amount;
let b = (num & 0x0000FF) + amount;
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));
return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16).padStart(6, '0');
}
function gameLoop() {
snakes.forEach(snake => {
if (snake.alive && snake.type !== 'player') {
aiDecision(snake);
}
});
snakes.forEach(snake => {
if (snake.alive) {
moveSnake(snake);
}
});
if (gameMode === 'aiOnly') {
const aliveAis = snakes.filter(s => s.alive);
if (aliveAis.length <= 1) {
clearInterval(gameInterval);
document.getElementById('gameInfo').textContent = `游戏结束!胜利者: ${aliveAis[0]?.name || '无'}`;
}
}
if (gameMode === 'aiOnly') {
updateRankings();
}
drawGame();
}
</script>
</body>
</html>