snake-literario / index.html
Yoleo's picture
Add 2 files
fa9d6ca verified
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>炫酷道具贪吃蛇</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body {
font-family: 'Press Start 2P', cursive;
background: linear-gradient(135deg, #1a1a2e, #16213e);
color: #fff;
overflow: hidden;
}
.game-container {
position: relative;
box-shadow: 0 0 50px rgba(0, 255, 255, 0.3);
border: 4px solid rgba(0, 255, 255, 0.5);
border-radius: 8px;
overflow: hidden;
}
#game-canvas {
background-color: #0f0f23;
display: block;
}
.snake-cell {
box-shadow: 0 0 10px currentColor;
}
.food-cell {
animation: pulse 1.5s infinite;
}
.powerup-cell {
animation: glow 2s infinite alternate, spin 4s infinite linear;
}
.portal-cell {
animation: portalPulse 2s infinite;
}
.bomb-cell {
animation: bombPulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
@keyframes glow {
0% { filter: drop-shadow(0 0 5px currentColor); }
100% { filter: drop-shadow(0 0 15px currentColor); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes portalPulse {
0% { box-shadow: 0 0 10px #9c27b0, inset 0 0 10px #9c27b0; }
50% { box-shadow: 0 0 20px #9c27b0, inset 0 0 20px #9c27b0; }
100% { box-shadow: 0 0 10px #9c27b0, inset 0 0 10px #9c27b0; }
}
@keyframes bombPulse {
0% { opacity: 0.7; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.1); }
100% { opacity: 0.7; transform: scale(0.9); }
}
.score-display {
text-shadow: 0 0 10px #00ffaa;
font-size: 1.5rem;
letter-spacing: 2px;
}
.particle {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
pointer-events: none;
}
.game-over {
backdrop-filter: blur(5px);
background-color: rgba(0, 0, 0, 0.7);
}
.powerup-indicator {
animation: indicatorPulse 1s infinite alternate;
box-shadow: 0 0 10px currentColor;
}
@keyframes indicatorPulse {
0% { opacity: 0.7; transform: scale(0.9); }
100% { opacity: 1; transform: scale(1.1); }
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center justify-center p-4">
<div class="text-center mb-6">
<h1 class="text-4xl md:text-5xl font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-purple-500">
炫酷道具贪吃蛇
</h1>
<p class="text-amber-400 text-sm md:text-base">AI自动模式已启用 - 按空格键切换手动/自动</p>
</div>
<div class="flex flex-col md:flex-row gap-6 items-center justify-center">
<div class="game-container relative">
<canvas id="game-canvas" width="600" height="600"></canvas>
<div id="game-over" class="game-over absolute inset-0 flex flex-col items-center justify-center hidden">
<h2 class="text-4xl text-red-500 mb-6">游戏结束!</h2>
<p class="text-xl text-white mb-8">最终分数: <span id="final-score" class="text-green-400">0</span></p>
<button id="restart-btn" class="px-6 py-3 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-full font-bold uppercase tracking-wider hover:scale-105 transition-transform">
重新开始
</button>
</div>
</div>
<div class="w-full md:w-64 flex flex-col gap-6">
<div class="bg-gray-900 bg-opacity-70 p-4 rounded-lg border border-cyan-500 border-opacity-30">
<h3 class="text-xl text-cyan-400 mb-3 flex items-center gap-2">
<i class="fas fa-tachometer-alt"></i> 游戏状态
</h3>
<div class="space-y-4">
<div>
<p class="text-gray-400 text-sm mb-1">得分</p>
<p id="score" class="score-display text-2xl text-green-400">0</p>
</div>
<div>
<p class="text-gray-400 text-sm mb-1">长度</p>
<p id="length" class="text-xl text-white">1</p>
</div>
<div>
<p class="text-gray-400 text-sm mb-1">速度</p>
<p id="speed" class="text-xl text-white">1x</p>
</div>
<div>
<p class="text-gray-400 text-sm mb-1">模式</p>
<p id="mode" class="text-xl text-amber-400">AI自动</p>
</div>
</div>
</div>
<div class="bg-gray-900 bg-opacity-70 p-4 rounded-lg border border-purple-500 border-opacity-30">
<h3 class="text-xl text-purple-400 mb-3 flex items-center gap-2">
<i class="fas fa-magic"></i> 道具效果
</h3>
<div class="space-y-3">
<div class="flex items-center gap-3">
<div class="w-6 h-6 rounded-full bg-yellow-400 powerup-indicator" id="speed-indicator"></div>
<p class="text-sm">速度提升 <span id="speed-timer" class="text-yellow-400">0s</span></p>
</div>
<div class="flex items-center gap-3">
<div class="w-6 h-6 rounded-full bg-green-500 powerup-indicator" id="growth-indicator"></div>
<p class="text-sm">快速生长 <span id="growth-timer" class="text-green-500">0s</span></p>
</div>
<div class="flex items-center gap-3">
<div class="w-6 h-6 rounded-full bg-red-500 powerup-indicator" id="invincible-indicator"></div>
<p class="text-sm">无敌状态 <span id="invincible-timer" class="text-red-500">0s</span></p>
</div>
</div>
</div>
<div class="bg-gray-900 bg-opacity-70 p-4 rounded-lg border border-amber-500 border-opacity-30">
<h3 class="text-xl text-amber-400 mb-3 flex items-center gap-2">
<i class="fas fa-info-circle"></i> 道具说明
</h3>
<div class="space-y-3 text-xs">
<div class="flex items-start gap-2">
<div class="w-4 h-4 rounded-full bg-red-500 mt-1 flex-shrink-0"></div>
<p>普通食物 - +1分, 长度+1</p>
</div>
<div class="flex items-start gap-2">
<div class="w-4 h-4 rounded-full bg-yellow-400 mt-1 flex-shrink-0"></div>
<p>速度道具 - 移动速度加快</p>
</div>
<div class="flex items-start gap-2">
<div class="w-4 h-4 rounded-full bg-green-500 mt-1 flex-shrink-0"></div>
<p>生长道具 - 每次移动长度增加</p>
</div>
<div class="flex items-start gap-2">
<div class="w-4 h-4 rounded-full bg-blue-400 mt-1 flex-shrink-0"></div>
<p>无敌道具 - 可以穿过墙壁和身体</p>
</div>
<div class="flex items-start gap-2">
<div class="w-4 h-4 rounded-full bg-purple-500 mt-1 flex-shrink-0"></div>
<p>传送门 - 传送到随机位置</p>
</div>
<div class="flex items-start gap-2">
<div class="w-4 h-4 rounded-full bg-orange-500 mt-1 flex-shrink-0"></div>
<p>炸弹 - 长度减半</p>
</div>
</div>
</div>
</div>
</div>
<div class="mt-8 text-gray-400 text-xs">
<p>使用方向键或WASD控制 | 空格键切换手动/AI模式 | 躲避炸弹</p>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 游戏常量
const CELL_SIZE = 20;
const CELLS_X = 30;
const CELLS_Y = 30;
const FPS = 60;
const BASE_SPEED = 5; // 每x帧移动一次
const SPEED_INCREASE = 0.95; // 每次吃到食物速度增加系数
// 道具类型
const ITEM_TYPES = {
FOOD: { color: '#f44336', score: 1, grow: 1, lifetime: 0 },
SPEED: { color: '#ffeb3b', score: 2, grow: 0, lifetime: 10, effect: 'speed' },
GROWTH: { color: '#4caf50', score: 2, grow: 0, lifetime: 10, effect: 'growth' },
INVINCIBLE: { color: '#2196f3', score: 3, grow: 0, lifetime: 8, effect: 'invincible' },
PORTAL: { color: '#9c27b0', score: 0, grow: -1, lifetime: 0 },
BOMB: { color: '#ff9800', score: 0, grow: -0.5, lifetime: 0 }
};
// 游戏变量
let canvas = document.getElementById('game-canvas');
let ctx = canvas.getContext('2d');
let snake = [];
let direction = 'right';
let nextDirection = 'right';
let gameLoopInterval;
let gameSpeed = BASE_SPEED;
let speedCounter = 0;
let score = 0;
let items = [];
let particles = [];
let activeEffects = {};
let isGameOver = false;
let isAutoMode = true;
let lastPortalTime = 0;
let portalCooldown = 3; // seconds
// DOM 元素
const scoreElement = document.getElementById('score');
const lengthElement = document.getElementById('length');
const speedElement = document.getElementById('speed');
const modeElement = document.getElementById('mode');
const gameOverElement = document.getElementById('game-over');
const finalScoreElement = document.getElementById('final-score');
const restartButton = document.getElementById('restart-btn');
const speedIndicator = document.getElementById('speed-indicator');
const growthIndicator = document.getElementById('growth-indicator');
const invincibleIndicator = document.getElementById('invincible-indicator');
const speedTimer = document.getElementById('speed-timer');
const growthTimer = document.getElementById('growth-timer');
const invincibleTimer = document.getElementById('invincible-timer');
// 初始化游戏
function initGame() {
// 重置游戏状态
snake = [
{ x: 5, y: 15 },
{ x: 4, y: 15 },
{ x: 3, y: 15 }
];
direction = 'right';
nextDirection = 'right';
gameSpeed = BASE_SPEED;
speedCounter = 0;
score = 0;
items = [];
particles = [];
activeEffects = {};
isGameOver = false;
// 更新UI
updateScore(0);
lengthElement.textContent = snake.length;
speedElement.textContent = '1x';
modeElement.textContent = isAutoMode ? 'AI自动' : '手动控制';
modeElement.className = isAutoMode ? 'text-xl text-amber-400' : 'text-xl text-white';
// 隐藏游戏结束画面
gameOverElement.classList.add('hidden');
// 创建初始食物
createRandomItem(ITEM_TYPES.FOOD);
// 开始游戏循环
if (gameLoopInterval) clearInterval(gameLoopInterval);
gameLoopInterval = setInterval(gameLoop, 1000 / FPS);
}
// 游戏主循环
function gameLoop() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新计时器
speedCounter++;
updateEffects();
// AI控制
if (isAutoMode && speedCounter % 2 === 0) {
aiControl();
}
// 每gameSpeed帧移动一次
if (speedCounter >= gameSpeed) {
moveSnake();
speedCounter = 0;
}
// 绘制网格线
drawGrid();
// 绘制道具
drawItems();
// 绘制蛇
drawSnake();
// 绘制粒子效果
drawParticles();
// 更新粒子
updateParticles();
// 随机生成道具
if (Math.random() < 0.002) { // 0.2% 每帧的几率
const availableTypes = Object.values(ITEM_TYPES).filter(type =>
type !== ITEM_TYPES.FOOD &&
(!activeEffects[type.effect] || type.effect === undefined)
);
if (availableTypes.length > 0) {
const randomType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
createRandomItem(randomType);
}
}
}
// 绘制网格线
function drawGrid() {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
ctx.lineWidth = 1;
// 垂直线
for (let x = 0; x <= CELLS_X; x++) {
ctx.beginPath();
ctx.moveTo(x * CELL_SIZE, 0);
ctx.lineTo(x * CELL_SIZE, CELLS_Y * CELL_SIZE);
ctx.stroke();
}
// 水平线
for (let y = 0; y <= CELLS_Y; y++) {
ctx.beginPath();
ctx.moveTo(0, y * CELL_SIZE);
ctx.lineTo(CELLS_X * CELL_SIZE, y * CELL_SIZE);
ctx.stroke();
}
}
// 移动蛇
function moveSnake() {
direction = nextDirection;
// 创建新头部
const head = { ...snake[0] };
switch (direction) {
case 'up': head.y--; break;
case 'down': head.y++; break;
case 'left': head.x--; break;
case 'right': head.x++; break;
}
// 检查是否撞墙(无敌状态下可以穿过墙壁)
if (!activeEffects.invincible) {
if (head.x < 0 || head.x >= CELLS_X || head.y < 0 || head.y >= CELLS_Y) {
gameOver();
return;
}
} else {
// 穿墙处理
if (head.x < 0) head.x = CELLS_X - 1;
if (head.x >= CELLS_X) head.x = 0;
if (head.y < 0) head.y = CELLS_Y - 1;
if (head.y >= CELLS_Y) head.y = 0;
}
// 检查是否撞到自己(无敌状态下可以穿过自己)
if (!activeEffects.invincible) {
for (let i = 0; i < snake.length - 1; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
gameOver();
return;
}
}
}
// 添加到头部
snake.unshift(head);
// 检查是否吃到道具
let itemConsumed = false;
let itemIndex = -1;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (head.x === item.x && head.y === item.y) {
itemIndex = i;
// 应用道具效果
if (item.type.effect) {
activateEffect(item.type.effect, item.type.lifetime);
}
// 处理特殊道具
if (item.type === ITEM_TYPES.PORTAL) {
if (Date.now() - lastPortalTime > portalCooldown * 1000) {
teleportSnake();
lastPortalTime = Date.now();
} else {
continue; // 跳过传送门,因为冷却中
}
}
if (item.type === ITEM_TYPES.BOMB) {
createExplosion(head.x, head.y);
}
// 更新分数和长度
updateScore(score + item.type.score);
// 生长处理 (正数=增加长度,负数=减少长度)
if (item.type.grow > 0) {
// 如果吃的生长道具,额外添加几节
if (activeEffects.growth) {
for (let j = 0; j < 2; j++) {
snake.push({ ...snake[snake.length - 1] });
}
}
} else if (item.type.grow < 0) {
// 炸弹效果
const newLength = Math.ceil(snake.length / (Math.abs(item.type.grow) + 1));
snake = snake.slice(0, newLength);
if (snake.length < 1) {
gameOver();
return;
}
}
// 粒子效果
createParticles(head.x, head.y, item.type.color);
itemConsumed = true;
break;
}
}
// 移除吃到的道具
if (itemConsumed && itemIndex !== -1) {
items.splice(itemIndex, 1);
// 如果吃的是普通食物,创建新的食物
if (items.filter(item => item.type === ITEM_TYPES.FOOD).length < 1) {
createRandomItem(ITEM_TYPES.FOOD);
}
} else {
// 如果没有吃到道具,移除尾部
snake.pop();
}
// 快速生长效果
if (activeEffects.growth) {
snake.push({ ...snake[snake.length - 1] });
}
// 更新长度显示
lengthElement.textContent = snake.length;
}
// 绘制蛇
function drawSnake() {
const head = snake[0];
// 绘制身体
for (let i = 0; i < snake.length; i++) {
const segment = snake[i];
const isHead = i === 0;
let color;
if (isHead) {
if (activeEffects.invincible) {
// 头部无敌效果
color = `hsl(${(Date.now() / 20) % 360}, 100%, 60%)`;
} else {
color = '#00ffaa';
}
} else {
// 彩虹身体效果
const hue = (240 + i * 1) % 360;
color = `hsl(${hue}, 100%, 60%)`;
}
// 绘制蛇节
ctx.fillStyle = color;
ctx.beginPath();
ctx.roundRect(
segment.x * CELL_SIZE + 1,
segment.y * CELL_SIZE + 1,
CELL_SIZE - 2,
CELL_SIZE - 2,
4
);
ctx.fill();
// 添加视觉层次效果
if (i > 0) {
ctx.fillStyle = `rgba(255, 255, 255, 0.1)`;
ctx.beginPath();
ctx.roundRect(
segment.x * CELL_SIZE + 4,
segment.y * CELL_SIZE + 4,
CELL_SIZE - 8,
CELL_SIZE - 8,
2
);
ctx.fill();
}
// 如果是头部,添加眼睛
if (isHead) {
const eyeSize = CELL_SIZE * 0.15;
const eyeOffset = CELL_SIZE * 0.2;
ctx.fillStyle = 'white';
// 根据方向绘制眼睛
if (direction === 'right' || direction === 'left') {
// 水平方向 - 上下两只眼睛
ctx.beginPath();
ctx.arc(
segment.x * CELL_SIZE + (direction === 'right' ? CELL_SIZE - eyeOffset : eyeOffset),
segment.y * CELL_SIZE + eyeOffset,
eyeSize, 0, Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
segment.x * CELL_SIZE + (direction === 'right' ? CELL_SIZE - eyeOffset : eyeOffset),
segment.y * CELL_SIZE + CELL_SIZE - eyeOffset,
eyeSize, 0, Math.PI * 2
);
ctx.fill();
} else {
// 垂直方向 - 左右两只眼睛
ctx.beginPath();
ctx.arc(
segment.x * CELL_SIZE + eyeOffset,
segment.y * CELL_SIZE + (direction === 'down' ? CELL_SIZE - eyeOffset : eyeOffset),
eyeSize, 0, Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
segment.x * CELL_SIZE + CELL_SIZE - eyeOffset,
segment.y * CELL_SIZE + (direction === 'down' ? CELL_SIZE - eyeOffset : eyeOffset),
eyeSize, 0, Math.PI * 2
);
ctx.fill();
}
// 瞳孔
const pupilSize = eyeSize * 0.6;
ctx.fillStyle = '#333';
if (direction === 'right' || direction === 'left') {
// 水平方向
ctx.beginPath();
ctx.arc(
segment.x * CELL_SIZE + (direction === 'right' ? CELL_SIZE - eyeOffset : eyeOffset),
segment.y * CELL_SIZE + eyeOffset,
pupilSize, 0, Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
segment.x * CELL_SIZE + (direction === 'right' ? CELL_SIZE - eyeOffset : eyeOffset),
segment.y * CELL_SIZE + CELL_SIZE - eyeOffset,
pupilSize, 0, Math.PI * 2
);
ctx.fill();
} else {
// 垂直方向
ctx.beginPath();
ctx.arc(
segment.x * CELL_SIZE + eyeOffset,
segment.y * CELL_SIZE + (direction === 'down' ? CELL_SIZE - eyeOffset : eyeOffset),
pupilSize, 0, Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
segment.x * CELL_SIZE + CELL_SIZE - eyeOffset,
segment.y * CELL_SIZE + (direction === 'down' ? CELL_SIZE - eyeOffset : eyeOffset),
pupilSize, 0, Math.PI * 2
);
ctx.fill();
}
}
}
}
// 绘制道具
function drawItems() {
for (const item of items) {
const itemX = item.x * CELL_SIZE + CELL_SIZE / 2;
const itemY = item.y * CELL_SIZE + CELL_SIZE / 2;
const itemSize = CELL_SIZE * 0.6;
ctx.save();
ctx.translate(itemX, itemY);
// 不同类型的绘制效果
switch (item.type) {
case ITEM_TYPES.FOOD:
// 普通食物 - 圆形
ctx.fillStyle = item.type.color;
ctx.beginPath();
ctx.arc(0, 0, itemSize / 2, 0, Math.PI * 2);
ctx.fill();
// 高光
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.arc(-itemSize / 4, -itemSize / 4, itemSize / 5, 0, Math.PI * 2);
ctx.fill();
break;
case ITEM_TYPES.SPEED:
// 速度道具 - 闪电形状
ctx.fillStyle = item.type.color;
ctx.beginPath();
ctx.moveTo(-itemSize / 2, -itemSize / 4);
ctx.lineTo(itemSize / 4, 0);
ctx.lineTo(-itemSize / 4, 0);
ctx.lineTo(itemSize / 2, itemSize / 4);
ctx.lineTo(-itemSize / 4, itemSize / 4);
ctx.lineTo(itemSize / 4, -itemSize / 4);
ctx.closePath();
ctx.fill();
break;
case ITEM_TYPES.GROWTH:
// 生长道具 - 加号形状
ctx.fillStyle = item.type.color;
ctx.fillRect(-itemSize / 8, -itemSize / 2, itemSize / 4, itemSize);
ctx.fillRect(-itemSize / 2, -itemSize / 8, itemSize, itemSize / 4);
break;
case ITEM_TYPES.INVINCIBLE:
// 无敌道具 - 星星形状
ctx.fillStyle = item.type.color;
ctx.beginPath();
for (let i = 0; i < 5; i++) {
ctx.lineTo(
0,
-itemSize / 2
);
ctx.rotate(Math.PI / 5);
ctx.lineTo(
0,
-itemSize / 5
);
ctx.rotate(Math.PI / 5);
}
ctx.closePath();
ctx.fill();
break;
case ITEM_TYPES.PORTAL:
// 传送门 - 环形
ctx.fillStyle = item.type.color;
ctx.beginPath();
ctx.arc(0, 0, itemSize / 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#0f0f23';
ctx.beginPath();
ctx.arc(0, 0, itemSize / 3, 0, Math.PI * 2);
ctx.fill();
// 动态环
ctx.strokeStyle = item.type.color;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 0, itemSize / 2 - 2, 0, Math.PI * (1 + performance.now() / 1000 % 1));
ctx.stroke();
break;
case ITEM_TYPES.BOMB:
// 炸弹 - 圆形带引线
ctx.fillStyle = item.type.color;
ctx.beginPath();
ctx.arc(0, 0, itemSize / 2, 0, Math.PI * 2);
ctx.fill();
// 引线
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(itemSize / 2, -itemSize / 4);
ctx.lineTo(itemSize / 2 + 5, -itemSize / 2);
ctx.stroke();
// 火花
ctx.fillStyle = `hsl(${(Date.now() / 30) % 360}, 100%, 60%)`;
ctx.beginPath();
ctx.arc(itemSize / 2 + 5, -itemSize / 2, 2, 0, Math.PI * 2);
ctx.fill();
break;
}
ctx.restore();
}
}
// 创建随机道具
function createRandomItem(type) {
const item = {
x: Math.floor(Math.random() * CELLS_X),
y: Math.floor(Math.random() * CELLS_Y),
type: type,
createdAt: Date.now()
};
// 确保道具不会生成在蛇身上
let isValidPosition = true;
for (const segment of snake) {
if (segment.x === item.x && segment.y === item.y) {
isValidPosition = false;
break;
}
}
// 确保道具不会重叠
for (const existingItem of items) {
if (existingItem.x === item.x && existingItem.y === item.y) {
isValidPosition = false;
break;
}
}
if (isValidPosition) {
items.push(item);
return true;
} else {
// 如果位置无效,尝试递归创建(最多3次)
return createRandomItem(type);
}
}
// 更新分数
function updateScore(newScore) {
score = newScore;
scoreElement.textContent = score;
// 随着分数增加,速度会提升
const speedMultiplier = 1 + score * 0.005;
gameSpeed = Math.max(BASE_SPEED * SPEED_INCREASE ** (score / 5), 3);
speedElement.textContent = speedMultiplier.toFixed(1) + 'x';
}
// 游戏结束
function gameOver() {
isGameOver = true;
clearInterval(gameLoopInterval);
// 显示游戏结束画面
finalScoreElement.textContent = score;
gameOverElement.classList.remove('hidden');
// 创建爆炸效果
createExplosion(snake[0].x, snake[0].y);
}
// 传送蛇
function teleportSnake() {
// 找到有效位置
let newX, newY;
let attempts = 0;
const maxAttempts = 100;
do {
newX = Math.floor(Math.random() * (CELLS_X - 10)) + 5;
newY = Math.floor(Math.random() * (CELLS_Y - 10)) + 5;
attempts++;
} while (
(attempts < maxAttempts) &&
items.some(item => item.x === newX && item.y === newY)
);
if (attempts < maxAttempts) {
// 计算偏移量
const offsetX = newX - snake[0].x;
const offsetY = newY - snake[0].y;
// 重新定位整条蛇
for (let i = 0; i < snake.length; i++) {
snake[i].x += offsetX;
snake[i].y += offsetY;
// 确保蛇不会超出边界
if (snake[i].x < 0) snake[i].x = 0;
if (snake[i].x >= CELLS_X) snake[i].x = CELLS_X - 1;
if (snake[i].y < 0) snake[i].y = 0;
if (snake[i].y >= CELLS_Y) snake[i].y = CELLS_Y - 1;
}
// 传送门粒子效果
createPortalEffect(snake[0].x, snake[0].y);
}
}
// 激活效果
function activateEffect(effect, duration) {
// 重置或延长已有效果
if (activeEffects[effect]) {
clearTimeout(activeEffects[effect].timeout);
}
activeEffects[effect] = {
startTime: Date.now(),
duration: duration * 1000, // 转换为毫秒
timeout: setTimeout(() => {
delete activeEffects[effect];
updateEffectIndicators();
}, duration * 1000)
};
// 应用效果
switch (effect) {
case 'speed':
break;
case 'growth':
break;
case 'invincible':
break;
}
updateEffectIndicators();
}
// 更新效果计时器显示
function updateEffects() {
const now = Date.now();
for (const effect in activeEffects) {
const remaining = Math.max(0, (activeEffects[effect].startTime + activeEffects[effect].duration - now) / 1000);
switch (effect) {
case 'speed':
speedTimer.textContent = remaining.toFixed(1) + 's';
break;
case 'growth':
growthTimer.textContent = remaining.toFixed(1) + 's';
break;
case 'invincible':
invincibleTimer.textContent = remaining.toFixed(1) + 's';
break;
}
}
}
// 更新效果指示器
function updateEffectIndicators() {
speedIndicator.style.display = activeEffects.speed ? 'block' : 'none';
growthIndicator.style.display = activeEffects.growth ? 'block' : 'none';
invincibleIndicator.style.display = activeEffects.invincible ? 'block' : 'none';
speedTimer.textContent = '0s';
growthTimer.textContent = '0s';
invincibleTimer.textContent = '0s';
}
// 创建粒子效果
function createParticles(x, y, color) {
const particleCount = 20;
const centerX = x * CELL_SIZE + CELL_SIZE / 2;
const centerY = y * CELL_SIZE + CELL_SIZE / 2;
for (let i = 0; i < particleCount; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 3 + 1;
const size = Math.random() * 4 + 2;
const lifetime = Math.random() * 1000 + 500;
particles.push({
x: centerX,
y: centerY,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
size: size,
color: color,
life: lifetime,
maxLife: lifetime,
alpha: 1
});
}
}
// 创建爆炸效果
function createExplosion(x, y) {
const particleCount = 50;
const centerX = x * CELL_SIZE + CELL_SIZE / 2;
const centerY = y * CELL_SIZE + CELL_SIZE / 2;
for (let i = 0; i < particleCount; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 5 + 2;
const size = Math.random() * 6 + 3;
const lifetime = Math.random() * 1500 + 500;
const hue = Math.random() * 60; // 橙红色调
particles.push({
x: centerX,
y: centerY,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
size: size,
color: `hsl(${hue}, 100%, 50%)`,
life: lifetime,
maxLife: lifetime,
alpha: 1
});
}
}
// 创建传送门效果
function createPortalEffect(x, y) {
const particleCount = 40;
const centerX = x * CELL_SIZE + CELL_SIZE / 2;
const centerY = y * CELL_SIZE + CELL_SIZE / 2;
for (let i = 0; i < particleCount; i++) {
const angle = Math.random() * Math.PI * 2;
const radius = Math.random() * 20 + 10;
const startRadius = 0;
const endRadius = radius * 2;
const speed = Math.random() * 0.5 + 0.5;
const size = Math.random() * 3 + 2;
const lifetime = Math.random() * 2000 + 1000;
const hue = Math.random() * 40 + 280; // 紫色调
particles.push({
x: centerX + Math.cos(angle) * startRadius,
y: centerY + Math.sin(angle) * startRadius,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
targetX: centerX + Math.cos(angle) * endRadius,
targetY: centerY + Math.sin(angle) * endRadius,
size: size,
color: `hsl(${hue}, 100%, 60%)`,
life: lifetime,
maxLife: lifetime,
alpha: 1,
isPortal: true
});
}
}
// 更新粒子
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
// 更新位置
p.x += p.vx;
p.y += p.vy;
// 如果是传送门粒子,移动到目标位置
if (p.isPortal && p.targetX && p.targetY) {
const dx = p.targetX - p.x;
const dy = p.targetY - p.y;
const dist = Math.sqrt(dx * dx + dy * dy);
p.vx = dx / dist * p.vx * 2;
p.vy = dy / dist * p.vy * 2;
if (dist < 5) {
p.life = 0; // 到达目标后立即消失
}
} else {
// 重力效果
p.vy += 0.05;
// 摩擦力
p.vx *= 0.98;
p.vy *= 0.98;
}
// 更新生命周期
p.life -= 1000 / FPS;
p.alpha = p.life / p.maxLife;
// 移除死亡的粒子
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
// 绘制粒子
function drawParticles() {
for (const p of particles) {
ctx.globalAlpha = p.alpha;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
// AI控制算法
function aiControl() {
const head = snake[0];
const food = items.filter(item => item.type === ITEM_TYPES.FOOD)[0];
const dangerousItems = items.filter(item => item.type === ITEM_TYPES.BOMB);
// 如果没有食物,保持移动
if (!food) {
return;
}
// 计算到食物的方向
const dx = food.x - head.x;
const dy = food.y - head.y;
const adx = Math.abs(dx);
const ady = Math.abs(dy);
// 计算安全移动方向(避免撞墙和撞自己)
const safeDirections = [];
// 检查上方是否安全
if (!willCollide(head.x, head.y - 1) && direction !== 'down') {
safeDirections.push('up');
}
// 检查下方是否安全
if (!willCollide(head.x, head.y + 1) && direction !== 'up') {
safeDirections.push('down');
}
// 检查左方是否安全
if (!willCollide(head.x - 1, head.y) && direction !== 'right') {
safeDirections.push('left');
}
// 检查右方是否安全
if (!willCollide(head.x + 1, head.y) && direction !== 'left') {
safeDirections.push('right');
}
// 如果没有安全方向,保持当前方向(可能会撞上,但AI会尽量避障)
if (safeDirections.length === 0) {
nextDirection = direction;
return;
}
// 优先考虑靠近食物的方向
let bestDirection;
if (adx > ady) {
// 水平优先
if (dx > 0 && safeDirections.includes('right')) {
bestDirection = 'right';
} else if (dx < 0 && safeDirections.includes('left')) {
bestDirection = 'left';
} else {
// 水平不可行,尝试垂直
if (dy > 0 && safeDirections.includes('down')) {
bestDirection = 'down';
} else if (dy < 0 && safeDirections.includes('up')) {
bestDirection = 'up';
} else {
// 随机选择安全方向
bestDirection = safeDirections[Math.floor(Math.random() * safeDirections.length)];
}
}
} else {
// 垂直优先
if (dy > 0 && safeDirections.includes('down')) {
bestDirection = 'down';
} else if (dy < 0 && safeDirections.includes('up')) {
bestDirection = 'up';
} else {
// 垂直不可行,尝试水平
if (dx > 0 && safeDirections.includes('right')) {
bestDirection = 'right';
} else if (dx < 0 && safeDirections.includes('left')) {
bestDirection = 'left';
} else {
// 随机选择安全方向
bestDirection = safeDirections[Math.floor(Math.random() * safeDirections.length)];
}
}
}
// 检查危险的炸弹并避开
for (const bomb of dangerousItems) {
const distToBomb = Math.abs(bomb.x - head.x) + Math.abs(bomb.y - head.y);
if (distToBomb < 5) { // 如果在炸弹附近
// 计算远离炸弹的方向
const dxBomb = bomb.x - head.x;
const dyBomb = bomb.y - head.y;
// 尝试远离炸弹
const moveAwayDirections = [];
if (dxBomb > 0 && safeDirections.includes('left')) {
moveAwayDirections.push('left');
}
if (dxBomb < 0 && safeDirections.includes('right')) {
moveAwayDirections.push('right');
}
if (dyBomb > 0 && safeDirections.includes('up')) {
moveAwayDirections.push('up');
}
if (dyBomb < 0 && safeDirections.includes('down')) {
moveAwayDirections.push('down');
}
if (moveAwayDirections.length > 0) {
// 选择最远离炸弹的方向(如果可能)
let maxDist = 0;
let bestAvoidDirection = moveAwayDirections[0];
for (const dir of moveAwayDirections) {
let newX = head.x;
let newY = head.y;
switch (dir) {
case 'up': newY--; break;
case 'down': newY++; break;
case 'left': newX--; break;
case 'right': newX++; break;
}
const newDist = Math.abs(bomb.x - newX) + Math.abs(bomb.y - newY);
if (newDist > maxDist) {
maxDist = newDist;
bestAvoidDirection = dir;
}
}
bestDirection = bestAvoidDirection;
}
}
}
// 检查传送门,如果有无敌效果可以使用
const portal = items.find(item => item.type === ITEM_TYPES.PORTAL);
if (activeEffects.invincible && portal && Date.now() - lastPortalTime > portalCooldown * 1000) {
const dxPortal = portal.x - head.x;
const dyPortal = portal.y - head.y;
if (Math.abs(dxPortal) + Math.abs(dyPortal) < 3) { // 接近传送门
if (dxPortal > 0 && safeDirections.includes('right')) {
bestDirection = 'right';
} else if (dxPortal < 0 && safeDirections.includes('left')) {
bestDirection = 'left';
} else if (dyPortal > 0 && safeDirections.includes('down')) {
bestDirection = 'down';
} else if (dyPortal < 0 && safeDirections.includes('up')) {
bestDirection = 'up';
}
}
}
// 检查速度道具,如果有无敌效果可以使用
const speedItem = items.find(item => item.type === ITEM_TYPES.SPEED);
if (speedItem && !activeEffects.speed) {
const dxSpeed = speedItem.x - head.x;
const dySpeed = speedItem.y - head.y;
if (Math.abs(dxSpeed) + Math.abs(dySpeed) < 8) { // 在速度道具附近
if (dxSpeed > 0 && safeDirections.includes('right')) {
bestDirection = 'right';
} else if (dxSpeed < 0 && safeDirections.includes('left')) {
bestDirection = 'left';
} else if (dySpeed > 0 && safeDirections.includes('down')) {
bestDirection = 'down';
} else if (dySpeed < 0 && safeDirections.includes('up')) {
bestDirection = 'up';
}
}
}
nextDirection = bestDirection;
}
// 检查移动是否会碰撞
function willCollide(x, y) {
// 检查墙壁(无敌状态下可以穿过)
if (!activeEffects.invincible) {
if (x < 0 || x >= CELLS_X || y < 0 || y >= CELLS_Y) {
return true;
}
}
// 检查自己(无敌状态下可以穿过)
if (!activeEffects.invincible) {
for (let i = 0; i < snake.length; i++) {
if (x === snake[i].x && y === snake[i].y) {
return true;
}
}
}
return false;
}
// 键盘控制
document.addEventListener('keydown', (e) => {
if (isGameOver && e.key === 'r') {
initGame();
return;
}
// 空格键切换自动/手动模式
if (e.key === ' ') {
isAutoMode = !isAutoMode;
modeElement.textContent = isAutoMode ? 'AI自动' : '手动控制';
modeElement.className = isAutoMode ? 'text-xl text-amber-400' : 'text-xl text-white';
return;
}
// 手动控制
if (!isAutoMode) {
switch (e.key) {
case 'ArrowUp':
case 'w':
case 'W':
if (direction !== 'down') nextDirection = 'up';
break;
case 'ArrowDown':
case 's':
case 'S':
if (direction !== 'up') nextDirection = 'down';
break;
case 'ArrowLeft':
case 'a':
case 'A':
if (direction !== 'right') nextDirection = 'left';
break;
case 'ArrowRight':
case 'd':
case 'D':
if (direction !== 'left') nextDirection = 'right';
break;
}
}
});
// 重新开始按钮
restartButton.addEventListener('click', initGame);
// 开始游戏
initGame();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=yuliqing16/spider" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p><p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Yoleo/snake-literario" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>