请创建一个视觉精美、交互流畅的进阶版贪吃蛇游戏

请创建一个视觉精美、交互流畅的进阶版贪吃蛇游戏,需包含以下功能和设计要求: 1. 核心玩法: - 采用经典贪吃蛇机制,通过键盘方向键控制蛇的移动(上、下、左、右) - 食物在游戏区域内随机生成,不会与蛇身重叠 - 蛇吃到食物后身体增长,得分增加 2. 特殊机制: - 每吃1个食物,蛇的移动速度轻微增加(最低速度限制为50ms间隔) - 蛇身颜色随得分增加按彩虹色循环变化(红→橙→黄→绿→蓝→靛→紫→红),且身体各段颜色略有差异以增强层次感 3. 视觉设计: - 蛇身:渐变色圆角矩形,带有微妙高光和"呼吸"缩放动画(轻微大小变化),头部有随方向变化的眼睛(带微动效果) - 食物:旋转的星形,带有从亮黄到橙红的径向渐变,同时有闪烁动画和发光效果 - 背景:深色渐变(深蓝到靛蓝),带有缓慢平移的网格线(30px×30px) - 分数显示:位于右上角,半透明背景配合模糊效果,分数变化时有缩放动画 - 游戏区域:带渐变色边框,悬停时有微妙阴影变化 4. 界面元素: - 顶部显示游戏标题("进阶版贪吃蛇"),采用渐变色文字和缓慢脉冲动画 - 底部显示操作说明(方向键控制) - 游戏结束时显示全屏半透明遮罩,包含"游戏结束"标题(渐变色)、最终分数和重新开始按钮(带悬停缩放效果) 5. 技术要求: - 游戏画面不显示滚动条,适配不同屏幕尺寸 - 调整背景网格数量,确保所有文字(标题、说明、分数)在任何设备上都能完整显示 - 支持触摸滑动控制(移动端适配) - 吃到食物时有彩色粒子爆炸效果,增强反馈感 6. 细节优化: - 游戏元素添加适当阴影和发光效果,增强视觉层次 - 界面过渡(如游戏结束画面)使用淡入动画 - 按钮和可交互元素有明确的状态反馈(悬停、点击)

画面优化的进阶版贪吃蛇游戏

V1

创建时间:23:11

产物已经生成完毕,如果你有更多灵感,请与我对话,继续完善或修改应用。

snake-game.html (1-579)

解释这段代码

已完成思考

这段代码实现了一个视觉精美、交互流畅的进阶版贪吃蛇游戏,完全满足了你提出的所有功能和设计要求。下面我将分部分解释代码的结构和功能:

1. 基础结构与资源引入

代码以标准 HTML5 结构编写,包含<head><body>两部分:

  • 引入了Tailwind CSS(用于快速构建响应式 UI)和Font Awesome(提供图标支持)
  • 配置了 Tailwind 自定义主题,定义了游戏专用颜色(如primarysecondary、深色系背景)和字体(font-game

2. 自定义样式与动画(<style>部分)

通过@layer utilities定义了游戏所需的特殊样式和动画,核心包括:

  • 背景效果grid-bg实现带缓慢平移动画的网格背景(30px×30px)
  • 视觉增强text-shadow(文字阴影)、glow(发光效果)、border-gradient(渐变边框)
  • 彩虹色定义rainbow-1rainbow-8共 8 种渐变色,实现蛇身的彩虹循环效果
  • 动画关键帧gridShift(网格平移)、pulse(脉冲缩放)、fadeIn(淡入)等,用于界面元素的动态效果

3. 页面布局(<body>部分)

采用响应式设计,主要包含 4 个核心区域:

  • 标题与分数区:顶部显示渐变色标题(带脉冲动画)和半透明分数面板(带发光效果)
  • 游戏主区域 :中间的canvas元素,用于绘制蛇、食物和粒子效果,容器带有渐变边框和悬停阴影效果
  • 游戏结束界面:全屏半透明遮罩,包含渐变色 "游戏结束" 标题、最终分数和带悬停效果的重新开始按钮
  • 操作说明区:底部显示方向键控制说明和游戏机制提示

4. 游戏核心逻辑(<script>部分)

这部分是游戏的灵魂,实现了所有交互和视觉效果,主要包括:

初始化与配置
  • resizeCanvas():确保游戏画布适配不同屏幕尺寸,无滚动条
  • 定义游戏核心变量:蛇的位置数组(snake)、食物位置(food)、移动方向(direction)、分数(score)、速度(gameSpeed)等
核心功能函数
  • initGame():游戏初始化,设置蛇的初始位置、分数、速度,生成首个食物并启动游戏循环
  • generateFood():随机生成食物,确保不与蛇身重叠
  • drawSnake() :绘制蛇身,实现了:
    • 彩虹渐变色(随分数循环变化)
    • 身体各段颜色差异(增强层次感)
    • 呼吸动画(轻微缩放效果)
    • 头部眼睛(随移动方向变化,带微动效果)
  • drawFood() :绘制旋转的星形食物,带有:
    • 黄到橙红的径向渐变
    • 闪烁动画(大小变化)
    • 发光边框效果
  • gameLoop() :游戏主循环,处理:
    • 蛇的移动逻辑
    • 食物碰撞检测(吃到食物后增长身体、增加分数、提升速度)
    • 边界和自碰撞检测(游戏结束判断)
  • drawParticles():吃到食物时显示彩色粒子爆炸效果,增强反馈
交互控制
  • 键盘控制:通过方向键控制蛇的移动,支持游戏结束后按空格重启
  • 触摸控制:支持移动端滑动操作,通过计算滑动方向判断移动意图

5. 特色机制实现

  • 速度递增 :每吃 1 个食物,速度轻微增加(gameSpeed递减),最低限制为 50ms
  • 颜色循环 :通过currentColorIndex控制蛇身颜色,按彩虹色序循环变化
  • 视觉反馈:分数变化时有缩放动画,吃到食物有粒子效果,按钮有悬停状态反馈

整体而言,代码通过 Canvas 绘图 API 实现游戏核心逻辑,结合 Tailwind CSS 和自定义动画实现了精美的视觉效果,同时兼顾了桌面端和移动端的交互体验,完全符合你提出的进阶版贪吃蛇游戏要求。

分享

除了彩虹色,还可以为蛇身设计哪些独特的颜色变化?

怎样增加游戏的趣味性,比如设置关卡或特殊道具?

如何实现蛇身碰撞检测和游戏结束判定?

html 复制代码
<!DOCTYPE html>
<html lang="en">
<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 href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#4F46E5',
                        secondary: '#EC4899',
                        dark: '#0F172A',
                        darker: '#020617',
                        'dark-accent': '#1E293B',
                        light: '#F9FAFB'
                    },
                    fontFamily: {
                        game: ['Poppins', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .grid-bg {
                background-size: 30px 30px;
                background-image: 
                    linear-gradient(to right, rgba(255,255,255,0.07) 1px, transparent 1px),
                    linear-gradient(to bottom, rgba(255,255,255,0.07) 1px, transparent 1px);
                animation: gridShift 20s linear infinite;
            }
            .text-shadow {
                text-shadow: 0 2px 8px rgba(0,0,0,0.4);
            }
            .glow {
                filter: drop-shadow(0 0 8px rgba(79, 70, 229, 0.5));
            }
            .food-glow {
                filter: drop-shadow(0 0 10px rgba(255, 165, 0, 0.7));
            }
            .bg-game-gradient {
                background: linear-gradient(135deg, #0F172A 0%, #1E293B 100%);
            }
            .border-gradient {
                border-image: linear-gradient(45deg, #4F46E5, #EC4899) 1;
            }
            .rainbow-1 { background: linear-gradient(90deg, #FF5E5E, #FF7D5E); }
            .rainbow-2 { background: linear-gradient(90deg, #FF7D5E, #FFB347); }
            .rainbow-3 { background: linear-gradient(90deg, #FFB347, #FFF275); }
            .rainbow-4 { background: linear-gradient(90deg, #FFF275, #93FF96); }
            .rainbow-5 { background: linear-gradient(90deg, #93FF96, #42D7F5); }
            .rainbow-6 { background: linear-gradient(90deg, #42D7F5, #7D5FFF); }
            .rainbow-7 { background: linear-gradient(90deg, #7D5FFF, #C774E8); }
            .rainbow-8 { background: linear-gradient(90deg, #C774E8, #FF5E5E); }
            
            @keyframes gridShift {
                0% { background-position: 0 0; }
                100% { background-position: 30px 30px; }
            }
            @keyframes pulse {
                0%, 100% { transform: scale(1); }
                50% { transform: scale(1.05); }
            }
            @keyframes fadeIn {
                from { opacity: 0; transform: scale(0.9); }
                to { opacity: 1; transform: scale(1); }
            }
            .animate-fade-in {
                animation: fadeIn 0.5s ease-out forwards;
            }
            .animate-pulse-slow {
                animation: pulse 3s infinite;
            }
        }
    </style>
</head>
<body class="bg-game-gradient font-game text-light min-h-screen flex flex-col items-center justify-start p-4 overflow-hidden pt-4">
    <div class="max-w-3xl w-full flex flex-col h-[calc(100vh-2rem)]">
        <header class="mb-4 flex justify-between items-center flex-shrink-0">
            <h1 class="text-[clamp(1.2rem,4vw,2rem)] font-bold text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary text-shadow animate-pulse-slow">进阶版贪吃蛇</h1>
            <div id="score-display" class="bg-dark/60 backdrop-blur-md px-4 py-2 rounded-lg border border-white/15 shadow-lg glow transition-all duration-300 hover:shadow-primary/20">
                <span class="text-sm font-medium text-white/70">分数:</span>
                <span id="score" class="text-xl font-bold ml-2 transition-all duration-300">0</span>
            </div>
        </header>
        
        <div class="relative bg-dark-accent/80 backdrop-blur-sm rounded-xl overflow-hidden shadow-2xl border-2 border-transparent border-gradient flex-grow mb-4 transition-all duration-500 hover:shadow-primary/10">
            <canvas id="gameCanvas" class="w-full h-full grid-bg"></canvas>
            
            <div id="gameOver" class="absolute inset-0 bg-dark/70 backdrop-blur-md flex flex-col items-center justify-center hidden animate-fade-in">
                <h2 class="text-[clamp(2rem,6vw,3.5rem)] font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-red-500 to-yellow-500 text-shadow">游戏结束!</h2>
                <p class="text-[clamp(1rem,3vw,1.5rem)] mb-6">最终分数: <span id="finalScore" class="font-bold">0</span></p>
                <button id="restartButton" class="px-8 py-3 bg-gradient-to-r from-primary to-secondary rounded-full text-white font-bold shadow-lg hover:shadow-primary/30 hover:scale-105 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-primary/50">
                    <i class="fa fa-refresh mr-2"></i>重新开始
                </button>
            </div>
        </div>
        
        <div class="mt-2 text-center text-white/60 text-sm flex-shrink-0">
            <p class="flex flex-wrap justify-center items-center gap-x-1">使用方向键 
                <i class="fa fa-arrow-up mx-1"></i> 
                <i class="fa fa-arrow-down mx-1"></i> 
                <i class="fa fa-arrow-left mx-1"></i> 
                <i class="fa fa-arrow-right mx-1"></i> 
                控制蛇的移动
            </p>
            <p class="mt-1">每吃一个食物,蛇的速度会增加,颜色也会变化</p>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const canvas = document.getElementById('gameCanvas');
            const ctx = canvas.getContext('2d');
            const scoreDisplay = document.getElementById('score');
            const gameOverScreen = document.getElementById('gameOver');
            const finalScoreDisplay = document.getElementById('finalScore');
            const restartButton = document.getElementById('restartButton');
            const gameContainer = canvas.parentElement;
            
            // 设置Canvas尺寸
            function resizeCanvas() {
                const containerWidth = gameContainer.clientWidth;
                const containerHeight = gameContainer.clientHeight;
                canvas.width = containerWidth;
                canvas.height = containerHeight;
            }
            
            resizeCanvas();
            window.addEventListener('resize', resizeCanvas);
            
            // 游戏变量
            let snake = [];
            let food = {};
            let direction = 'right';
            let nextDirection = 'right';
            let score = 0;
            let gameSpeed = 150;
            let gameInterval;
            let isGameOver = false;
            let gridSize;
            let snakeSize;
            let foodSize;
            let rainbowColors = [
                'rainbow-1', 'rainbow-2', 'rainbow-3', 'rainbow-4',
                'rainbow-5', 'rainbow-6', 'rainbow-7', 'rainbow-8'
            ];
            let currentColorIndex = 0;
            let foodBlinkState = 1;
            let foodBlinkRate = 0.02;
            
            // 初始化游戏
            function initGame() {
                gridSize = Math.floor(canvas.width / 25);
                snakeSize = gridSize * 0.8;
                foodSize = gridSize * 0.9;
                
                snake = [
                    {x: 5 * gridSize, y: 5 * gridSize},
                    {x: 4 * gridSize, y: 5 * gridSize},
                    {x: 3 * gridSize, y: 5 * gridSize}
                ];
                direction = 'right';
                nextDirection = 'right';
                score = 0;
                gameSpeed = 150;
                isGameOver = false;
                currentColorIndex = 0;
                
                scoreDisplay.textContent = score;
                gameOverScreen.classList.add('hidden');
                
                generateFood();
                
                if (gameInterval) clearInterval(gameInterval);
                gameInterval = setInterval(gameLoop, gameSpeed);
            }
            
            // 生成食物
            function generateFood() {
                const maxX = Math.floor(canvas.width / gridSize) - 1;
                const maxY = Math.floor(canvas.height / gridSize) - 1;
                
                let overlapping;
                do {
                    overlapping = false;
                    food = {
                        x: Math.floor(Math.random() * maxX) * gridSize,
                        y: Math.floor(Math.random() * maxY) * gridSize,
                        blink: 1,
                        blinkDir: -foodBlinkRate
                    };
                    
                    for (let segment of snake) {
                        if (segment.x === food.x && segment.y === food.y) {
                            overlapping = true;
                            break;
                        }
                    }
                } while (overlapping);
            }
            
            // 绘制蛇(带平滑动画效果)
            function drawSnake() {
                snake.forEach((segment, index) => {
                    // 计算每个段的颜色(略微变化以增强层次感)
                    const colorIndex = (currentColorIndex + Math.floor(index / 3)) % rainbowColors.length;
                    const colorClass = rainbowColors[colorIndex];
                    const colors = getGradientColors(colorClass);
                    
                    // 创建渐变色
                    const gradient = ctx.createLinearGradient(
                        segment.x, segment.y, 
                        segment.x + snakeSize, segment.y + snakeSize
                    );
                    gradient.addColorStop(0, colors.start);
                    gradient.addColorStop(1, colors.end);
                    
                    // 蛇身体轻微缩放动画(呼吸效果)
                    const scale = 1 + Math.sin(Date.now() / 300 + index * 0.5) * 0.02;
                    
                    ctx.save();
                    ctx.translate(segment.x + gridSize/2, segment.y + gridSize/2);
                    ctx.scale(scale, scale);
                    ctx.translate(-(segment.x + gridSize/2), -(segment.y + gridSize/2));
                    
                    // 绘制蛇身体(带圆角)
                    ctx.fillStyle = gradient;
                    ctx.beginPath();
                    ctx.roundRect(
                        segment.x + (gridSize - snakeSize) / 2,
                        segment.y + (gridSize - snakeSize) / 2,
                        snakeSize, snakeSize, snakeSize / 4
                    );
                    ctx.fill();
                    
                    // 添加高光效果
                    ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
                    ctx.beginPath();
                    ctx.roundRect(
                        segment.x + (gridSize - snakeSize) / 2,
                        segment.y + (gridSize - snakeSize) / 2,
                        snakeSize * 0.6, snakeSize * 0.6, snakeSize / 8
                    );
                    ctx.fill();
                    
                    ctx.restore();
                    
                    // 绘制眼睛(只在头部)
                    if (index === 0) {
                        drawEyes(segment);
                    }
                });
            }
            
            // 绘制蛇的眼睛
            function drawEyes(segment) {
                ctx.fillStyle = '#fff';
                
                // 根据方向调整眼睛位置
                let leftEyeX, leftEyeY, rightEyeX, rightEyeY;
                const eyeSize = snakeSize / 8;
                const eyeOffset = Math.sin(Date.now() / 300) * eyeSize * 0.3; // 眼睛动画
                
                switch(direction) {
                    case 'right':
                        leftEyeX = segment.x + snakeSize * 0.7 + eyeOffset;
                        leftEyeY = segment.y + snakeSize * 0.3;
                        rightEyeX = segment.x + snakeSize * 0.7 + eyeOffset;
                        rightEyeY = segment.y + snakeSize * 0.7;
                        break;
                    case 'left':
                        leftEyeX = segment.x + snakeSize * 0.3 - eyeOffset;
                        leftEyeY = segment.y + snakeSize * 0.3;
                        rightEyeX = segment.x + snakeSize * 0.3 - eyeOffset;
                        rightEyeY = segment.y + snakeSize * 0.7;
                        break;
                    case 'up':
                        leftEyeX = segment.x + snakeSize * 0.3;
                        leftEyeY = segment.y + snakeSize * 0.3 - eyeOffset;
                        rightEyeX = segment.x + snakeSize * 0.7;
                        rightEyeY = segment.y + snakeSize * 0.3 - eyeOffset;
                        break;
                    case 'down':
                        leftEyeX = segment.x + snakeSize * 0.3;
                        leftEyeY = segment.y + snakeSize * 0.7 + eyeOffset;
                        rightEyeX = segment.x + snakeSize * 0.7;
                        rightEyeY = segment.y + snakeSize * 0.7 + eyeOffset;
                        break;
                }
                
                // 眼睛白色部分
                ctx.beginPath();
                ctx.arc(leftEyeX, leftEyeY, eyeSize, 0, Math.PI * 2);
                ctx.arc(rightEyeX, rightEyeY, eyeSize, 0, Math.PI * 2);
                ctx.fill();
                
                // 瞳孔(随时间微动)
                const pupilOffsetX = Math.sin(Date.now() / 500) * eyeSize * 0.2;
                const pupilOffsetY = Math.cos(Date.now() / 500) * eyeSize * 0.2;
                
                ctx.fillStyle = '#000';
                ctx.beginPath();
                ctx.arc(leftEyeX + pupilOffsetX, leftEyeY + pupilOffsetY, eyeSize / 2, 0, Math.PI * 2);
                ctx.arc(rightEyeX + pupilOffsetX, rightEyeY + pupilOffsetY, eyeSize / 2, 0, Math.PI * 2);
                ctx.fill();
            }
            
            // 获取渐变颜色
            function getGradientColors(colorClass) {
                const gradients = {
                    'rainbow-1': { start: '#FF5E5E', end: '#FF7D5E' },
                    'rainbow-2': { start: '#FF7D5E', end: '#FFB347' },
                    'rainbow-3': { start: '#FFB347', end: '#FFF275' },
                    'rainbow-4': { start: '#FFF275', end: '#93FF96' },
                    'rainbow-5': { start: '#93FF96', end: '#42D7F5' },
                    'rainbow-6': { start: '#42D7F5', end: '#7D5FFF' },
                    'rainbow-7': { start: '#7D5FFF', end: '#C774E8' },
                    'rainbow-8': { start: '#C774E8', end: '#FF5E5E' }
                };
                
                return gradients[colorClass] || { start: '#4F46E5', end: '#EC4899' };
            }
            
            // 绘制食物(增强动画效果)
            function drawFood() {
                // 更新食物闪烁状态
                food.blink += food.blinkDir;
                if (food.blink <= 0.8 || food.blink >= 1.2) {
                    food.blinkDir *= -1;
                }
                
                ctx.save();
                ctx.translate(food.x + gridSize / 2, food.y + gridSize / 2);
                ctx.rotate(Date.now() / 800); // 稍快的旋转速度
                ctx.scale(food.blink, food.blink); // 闪烁效果
                
                // 绘制星星
                ctx.beginPath();
                ctx.moveTo(0, -foodSize / 2);
                for (let i = 1; i < 10; i++) {
                    const angle = (i * 2 * Math.PI / 10) - (Math.PI / 2);
                    const radius = i % 2 === 0 ? foodSize / 2 : foodSize / 5;
                    ctx.lineTo(Math.cos(angle) * radius, Math.sin(angle) * radius);
                }
                ctx.closePath();
                
                // 创建更丰富的径向渐变
                const gradient = ctx.createRadialGradient(
                    0, 0, 0,
                    0, 0, foodSize / 2
                );
                gradient.addColorStop(0, '#FFEE58');
                gradient.addColorStop(0.5, '#FFC107');
                gradient.addColorStop(1, '#FF8F00');
                
                ctx.fillStyle = gradient;
                ctx.fill();
                
                // 添加更亮的边框和发光效果
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.9)';
                ctx.lineWidth = 2;
                ctx.stroke();
                
                // 中心高光
                ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
                ctx.beginPath();
                ctx.arc(0, 0, foodSize / 8, 0, Math.PI * 2);
                ctx.fill();
                
                ctx.restore();
            }
            
            // 绘制吃到食物的粒子效果
            function drawParticles(x, y) {
                for (let i = 0; i < 8; i++) {
                    const angle = (i * Math.PI / 4);
                    const distance = gridSize * 0.6;
                    const particleX = x + Math.cos(angle) * distance;
                    const particleY = y + Math.sin(angle) * distance;
                    
                    const colorClass = rainbowColors[i % rainbowColors.length];
                    const colors = getGradientColors(colorClass);
                    
                    ctx.fillStyle = colors.start;
                    ctx.beginPath();
                    ctx.arc(particleX, particleY, gridSize / 8, 0, Math.PI * 2);
                    ctx.fill();
                }
            }
            
            // 检查碰撞
            function checkCollision() {
                const head = {...snake[0]};
                
                if (
                    head.x < 0 || 
                    head.x >= canvas.width || 
                    head.y < 0 || 
                    head.y >= canvas.height
                ) {
                    return true;
                }
                
                for (let i = 1; i < snake.length; i++) {
                    if (head.x === snake[i].x && head.y === snake[i].y) {
                        return true;
                    }
                }
                
                return false;
            }
            
            // 分数增加动画
            function animateScoreIncrease() {
                const scoreElement = document.getElementById('score');
                scoreElement.classList.add('scale-125', 'text-secondary');
                setTimeout(() => {
                    scoreElement.classList.remove('scale-125', 'text-secondary');
                }, 300);
            }
            
            // 游戏主循环
            function gameLoop() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                
                direction = nextDirection;
                
                const head = {x: snake[0].x, y: snake[0].y};
                
                switch(direction) {
                    case 'up':
                        head.y -= gridSize;
                        break;
                    case 'down':
                        head.y += gridSize;
                        break;
                    case 'left':
                        head.x -= gridSize;
                        break;
                    case 'right':
                        head.x += gridSize;
                        break;
                }
                
                snake.unshift(head);
                let ateFood = false;
                
                if (head.x === food.x && head.y === food.y) {
                    score += 10;
                    scoreDisplay.textContent = score;
                    animateScoreIncrease();
                    
                    gameSpeed = Math.max(50, gameSpeed - 5);
                    currentColorIndex++;
                    generateFood();
                    clearInterval(gameInterval);
                    gameInterval = setInterval(gameLoop, gameSpeed);
                    ateFood = true;
                } else {
                    snake.pop();
                }
                
                if (checkCollision()) {
                    gameOver();
                    return;
                }
                
                drawSnake();
                drawFood();
                
                // 如果吃到食物,绘制粒子效果
                if (ateFood) {
                    drawParticles(head.x + gridSize/2, head.y + gridSize/2);
                }
            }
            
            // 游戏结束
            function gameOver() {
                isGameOver = true;
                clearInterval(gameInterval);
                finalScoreDisplay.textContent = score;
                gameOverScreen.classList.remove('hidden');
            }
            
            // 键盘控制
            document.addEventListener('keydown', (e) => {
                if (isGameOver && e.key === ' ') {
                    initGame();
                    return;
                }
                
                if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
                    e.preventDefault();
                }
                
                switch(e.key) {
                    case 'ArrowUp':
                        if (direction !== 'down') {
                            nextDirection = 'up';
                        }
                        break;
                    case 'ArrowDown':
                        if (direction !== 'up') {
                            nextDirection = 'down';
                        }
                        break;
                    case 'ArrowLeft':
                        if (direction !== 'right') {
                            nextDirection = 'left';
                        }
                        break;
                    case 'ArrowRight':
                        if (direction !== 'left') {
                            nextDirection = 'right';
                        }
                        break;
                }
            });
            
            // 触摸控制
            let touchStartX = 0;
            let touchStartY = 0;
            
            canvas.addEventListener('touchstart', (e) => {
                touchStartX = e.touches[0].clientX;
                touchStartY = e.touches[0].clientY;
                e.preventDefault();
            }, { passive: false });
            
            canvas.addEventListener('touchmove', (e) => {
                e.preventDefault();
            }, { passive: false });
            
            canvas.addEventListener('touchend', (e) => {
                if (!touchStartX || !touchStartY) return;
                
                const touchEndX = e.changedTouches[0].clientX;
                const touchEndY = e.changedTouches[0].clientY;
                
                const diffX = touchEndX - touchStartX;
                const diffY = touchEndY - touchStartY;
                
                if (Math.abs(diffX) > Math.abs(diffY)) {
                    if (diffX > 0 && direction !== 'left') {
                        nextDirection = 'right';
                    } else if (diffX < 0 && direction !== 'right') {
                        nextDirection = 'left';
                    }
                } else {
                    if (diffY > 0 && direction !== 'up') {
                        nextDirection = 'down';
                    } else if (diffY < 0 && direction !== 'down') {
                        nextDirection = 'up';
                    }
                }
                
                touchStartX = 0;
                touchStartY = 0;
                e.preventDefault();
            }, { passive: false });
            
            // 重新开始按钮
            restartButton.addEventListener('click', initGame);
            
            // 初始化游戏
            initGame();
        });
    </script>
</body>
</html>
html 复制代码
<!DOCTYPE html>
<!-- 声明这是HTML5文档类型,告诉浏览器按照HTML5标准解析页面 -->
<html lang="en">
<!-- html标签:页面根元素,lang="en"表示页面主要语言是英文(可根据需求改为zh-CN) -->
<head>
    <!-- meta标签:设置页面字符编码为UTF-8,确保中文等特殊字符正常显示 -->
    <meta charset="UTF-8">
    <!-- meta标签:适配移动端屏幕,width=device-width表示宽度等于设备宽度,initial-scale=1.0表示初始缩放比例为1(无缩放) -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- title标签:设置浏览器标签页显示的标题 -->
    <title>进阶版贪吃蛇</title>
    <!-- 引入Tailwind CSS框架(CDN方式),用于快速构建响应式、美观的UI,无需手写大量原生CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 引入Font Awesome图标库(CDN方式),用于获取游戏中的方向键、刷新等图标 -->
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <!-- 配置Tailwind CSS自定义主题,扩展默认样式以适配游戏需求 -->
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        // 自定义游戏专用颜色:primary(主色,蓝紫色)、secondary(辅助色,粉色)
                        primary: '#4F46E5',
                        secondary: '#EC4899',
                        // 深色系背景:dark(深灰蓝)、darker(近黑)、dark-accent(中灰蓝),适合游戏暗色调风格
                        dark: '#0F172A',
                        darker: '#020617',
                        'dark-accent': '#1E293B',
                        // 浅色文本:light(近白),确保在深色背景上清晰可见
                        light: '#F9FAFB'
                    },
                    fontFamily: {
                        // 自定义游戏字体:Poppins(无衬线字体,清晰易读), fallback为系统默认无衬线字体
                        game: ['Poppins', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    <!-- 自定义Tailwind工具类和动画,用于游戏专属样式 -->
    <style type="text/tailwindcss">
        @layer utilities {
            /* 优化性能:内容可见时才渲染,减少不必要的计算 */
            .content-auto {
                content-visibility: auto;
            }
            /* 游戏背景网格:30px×30px网格线,半透明白色,加平移动画 */
            .grid-bg {
                background-size: 30px 30px; /* 网格大小 */
                background-image: 
                    /* 水平网格线:白色半透明(0.07透明度),1px粗 */
                    linear-gradient(to right, rgba(255,255,255,0.07) 1px, transparent 1px),
                    /* 垂直网格线:同上 */
                    linear-gradient(to bottom, rgba(255,255,255,0.07) 1px, transparent 1px);
                animation: gridShift 20s linear infinite; /* 网格20秒匀速平移,无限循环 */
            }
            /* 文字阴影:增强文字立体感,0偏移2px模糊8px黑色半透明 */
            .text-shadow {
                text-shadow: 0 2px 8px rgba(0,0,0,0.4);
            }
            /* 发光效果:给元素加蓝紫色(primary色)发光阴影,增强视觉层次 */
            .glow {
                filter: drop-shadow(0 0 8px rgba(79, 70, 229, 0.5));
            }
            /* 食物发光效果:给食物加橙色发光阴影,突出食物 */
            .food-glow {
                filter: drop-shadow(0 0 10px rgba(255, 165, 0, 0.7));
            }
            /* 游戏背景渐变:从深灰蓝(#0F172A)到中灰蓝(#1E293B)的135度渐变 */
            .bg-game-gradient {
                background: linear-gradient(135deg, #0F172A 0%, #1E293B 100%);
            }
            /* 渐变边框:45度渐变(主色到辅助色),边框宽度1px */
            .border-gradient {
                border-image: linear-gradient(45deg, #4F46E5, #EC4899) 1;
            }
            /* 彩虹色1:从红色(#FF5E5E)到橙红色(#FF7D5E)的水平渐变,用于蛇身颜色循环 */
            .rainbow-1 { background: linear-gradient(90deg, #FF5E5E, #FF7D5E); }
            /* 彩虹色2:从橙红色(#FF7D5E)到橙色(#FFB347)的水平渐变 */
            .rainbow-2 { background: linear-gradient(90deg, #FF7D5E, #FFB347); }
            /* 彩虹色3:从橙色(#FFB347)到黄色(#FFF275)的水平渐变 */
            .rainbow-3 { background: linear-gradient(90deg, #FFB347, #FFF275); }
            /* 彩虹色4:从黄色(#FFF275)到绿色(#93FF96)的水平渐变 */
            .rainbow-4 { background: linear-gradient(90deg, #FFF275, #93FF96); }
            /* 彩虹色5:从绿色(#93FF96)到蓝色(#42D7F5)的水平渐变 */
            .rainbow-5 { background: linear-gradient(90deg, #93FF96, #42D7F5); }
            /* 彩虹色6:从蓝色(#42D7F5)到蓝紫色(#7D5FFF)的水平渐变 */
            .rainbow-6 { background: linear-gradient(90deg, #42D7F5, #7D5FFF); }
            /* 彩虹色7:从蓝紫色(#7D5FFF)到紫色(#C774E8)的水平渐变 */
            .rainbow-7 { background: linear-gradient(90deg, #7D5FFF, #C774E8); }
            /* 彩虹色8:从紫色(#C774E8)到红色(#FF5E5E)的水平渐变,完成彩虹循环 */
            .rainbow-8 { background: linear-gradient(90deg, #C774E8, #FF5E5E); }
            
            /* 网格平移动画:从(0,0)位置移到(30px,30px),和网格大小一致,实现无缝平移 */
            @keyframes gridShift {
                0% { background-position: 0 0; } /* 动画开始:网格在初始位置 */
                100% { background-position: 30px 30px; } /* 动画结束:网格平移30px */
            }
            /* 脉冲动画:元素缩放效果,中间放大到1.05倍,增强视觉活力 */
            @keyframes pulse {
                0%, 100% { transform: scale(1); } /* 开始和结束:原大小 */
                50% { transform: scale(1.05); } /* 中间:放大5% */
            }
            /* 淡入动画:元素从透明(0)和缩小(0.9)到不透明(1)和原大小,用于页面过渡 */
            @keyframes fadeIn {
                from { opacity: 0; transform: scale(0.9); } /* 开始:透明+缩小 */
                to { opacity: 1; transform: scale(1); } /* 结束:不透明+原大小 */
            }
            /* 应用淡入动画:使用fadeIn动画,0.5秒缓出效果,保持最终状态 */
            .animate-fade-in {
                animation: fadeIn 0.5s ease-out forwards;
            }
            /* 应用慢速脉冲动画:使用pulse动画,3秒一次,无限循环 */
            .animate-pulse-slow {
                animation: pulse 3s infinite;
            }
        }
    </style>
</head>
<!-- body:页面内容区域,游戏的所有可视元素都在这里 -->
<body class="bg-game-gradient font-game text-light min-h-screen flex flex-col items-center justify-start p-4 overflow-hidden pt-4">
    <!-- 游戏主容器:最大宽度3xl(适配中等屏幕),宽度100%,flex垂直布局,高度=屏幕高度-2rem(减去上下内边距) -->
    <div class="max-w-3xl w-full flex flex-col h-[calc(100vh-2rem)]">
        <!-- 头部区域:包含标题和分数,底部外边距4px,flex横向布局(两端对齐),不压缩 -->
        <header class="mb-4 flex justify-between items-center flex-shrink-0">
            <!-- 游戏标题:字体大小自适应(1.2rem到2rem),加粗,渐变色文字(主色到辅助色),文字阴影,慢速脉冲动画 -->
            <h1 class="text-[clamp(1.2rem,4vw,2rem)] font-bold text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary text-shadow animate-pulse-slow">进阶版贪吃蛇</h1>
            <!-- 分数面板:半透明深灰背景(60%透明度),毛玻璃效果,内边距4px 2px,圆角,白色边框(15%透明度),阴影,发光效果,hover时阴影变色(主色20%透明度) -->
            <div id="score-display" class="bg-dark/60 backdrop-blur-md px-4 py-2 rounded-lg border border-white/15 shadow-lg glow transition-all duration-300 hover:shadow-primary/20">
                <!-- 分数标签:小号字体,中等粗细,白色70%透明度 -->
                <span class="text-sm font-medium text-white/70">分数:</span>
                <!-- 分数数值:大号字体,加粗,左边距2px,过渡效果(300ms) -->
                <span id="score" class="text-xl font-bold ml-2 transition-all duration-300">0</span>
            </div>
        </header>
        
        <!-- 游戏容器:相对定位(用于内部绝对定位元素),半透明中灰背景(80%透明度),毛玻璃效果,圆角,溢出隐藏,2px边框(透明,用渐变边框),flex占满剩余空间,底部外边距4px,过渡效果(500ms),hover时阴影变色(主色10%透明度) -->
        <div class="relative bg-dark-accent/80 backdrop-blur-sm rounded-xl overflow-hidden shadow-2xl border-2 border-transparent border-gradient flex-grow mb-4 transition-all duration-500 hover:shadow-primary/10">
            <!-- 游戏画布:宽度100%,高度100%,网格背景 -->
            <canvas id="gameCanvas" class="w-full h-full grid-bg"></canvas>
            
            <!-- 游戏结束遮罩:绝对定位(占满父容器),半透明深灰背景(70%透明度),毛玻璃效果,flex垂直居中,水平居中,默认隐藏,淡入动画 -->
            <div id="gameOver" class="absolute inset-0 bg-dark/70 backdrop-blur-md flex flex-col items-center justify-center hidden animate-fade-in">
                <!-- 游戏结束标题:字体大小自适应(2rem到3.5rem),加粗,底部外边距2px,渐变色文字(红色到黄色),文字阴影 -->
                <h2 class="text-[clamp(2rem,6vw,3.5rem)] font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-red-500 to-yellow-500 text-shadow">游戏结束!</h2>
                <!-- 最终分数文本:字体大小自适应(1rem到1.5rem),底部外边距6px -->
                <p class="text-[clamp(1rem,3vw,1.5rem)] mb-6">最终分数: <span id="finalScore" class="font-bold">0</span></p>
                <!-- 重新开始按钮:内边距8px 3px,渐变背景(主色到辅助色),圆角全圆,白色文字,加粗,阴影,hover时阴影变色(主色30%透明度)+放大5%,过渡效果(300ms),聚焦时无轮廓+2px边框(主色50%透明度) -->
                <button id="restartButton" class="px-8 py-3 bg-gradient-to-r from-primary to-secondary rounded-full text-white font-bold shadow-lg hover:shadow-primary/30 hover:scale-105 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-primary/50">
                    <!-- 刷新图标:左边距2px -->
                    <i class="fa fa-refresh mr-2"></i>重新开始
                </button>
            </div>
        </div>
        
        <!-- 操作说明区:顶部外边距2px,文本居中,白色60%透明度,小号字体,不压缩 -->
        <div class="mt-2 text-center text-white/60 text-sm flex-shrink-0">
            <!-- 控制说明:flex横向布局(换行),水平居中,垂直居中,元素间距1px -->
            <p class="flex flex-wrap justify-center items-center gap-x-1">使用方向键 
                <i class="fa fa-arrow-up mx-1"></i> <!-- 上箭头图标,左右边距1px -->
                <i class="fa fa-arrow-down mx-1"></i> <!-- 下箭头图标,左右边距1px -->
                <i class="fa fa-arrow-left mx-1"></i> <!-- 左箭头图标,左右边距1px -->
                <i class="fa fa-arrow-right mx-1"></i> <!-- 右箭头图标,左右边距1px -->
                控制蛇的移动
            </p>
            <!-- 游戏机制说明:顶部外边距1px -->
            <p class="mt-1">每吃一个食物,蛇的速度会增加,颜色也会变化</p>
        </div>
    </div>

    <!-- JavaScript:游戏核心逻辑,页面加载完成后执行 -->
    <script>
        // 监听页面DOM元素加载完成事件,确保DOM元素都存在后再执行游戏逻辑
        document.addEventListener('DOMContentLoaded', () => {
            // 获取画布元素(游戏绘图区域)
            const canvas = document.getElementById('gameCanvas');
            // 获取画布2D绘图上下文(用于在画布上绘制图形)
            const ctx = canvas.getContext('2d');
            // 获取分数显示元素(实时更新分数)
            const scoreDisplay = document.getElementById('score');
            // 获取游戏结束遮罩元素(游戏结束时显示)
            const gameOverScreen = document.getElementById('gameOver');
            // 获取最终分数显示元素(游戏结束时显示最终分数)
            const finalScoreDisplay = document.getElementById('finalScore');
            // 获取重新开始按钮元素(游戏结束后点击重启)
            const restartButton = document.getElementById('restartButton');
            // 获取游戏容器元素(用于适配画布尺寸)
            const gameContainer = canvas.parentElement;
            
            // 函数:设置画布尺寸(适配游戏容器大小)
            function resizeCanvas() {
                // 获取游戏容器的宽度(作为画布宽度)
                const containerWidth = gameContainer.clientWidth;
                // 获取游戏容器的高度(作为画布高度)
                const containerHeight = gameContainer.clientHeight;
                // 设置画布实际宽度
                canvas.width = containerWidth;
                // 设置画布实际高度
                canvas.height = containerHeight;
            }
            
            // 首次调用:初始化画布尺寸
            resizeCanvas();
            // 监听窗口"大小改变"事件,窗口缩放时重新调整画布尺寸
            window.addEventListener('resize', resizeCanvas);
            
            // 游戏核心变量定义
            let snake = []; // 蛇的身体数组:每个元素是一个对象,包含x(水平坐标)和y(垂直坐标)
            let food = {}; // 食物对象:包含x、y坐标,blink(闪烁状态),blinkDir(闪烁方向)
            let direction = 'right'; // 当前移动方向:初始向右
            let nextDirection = 'right'; // 下一个移动方向:防止快速转向(比如向右时直接向左)
            let score = 0; // 游戏分数:初始0
            let gameSpeed = 150; // 游戏速度(毫秒):初始150ms,值越小越快
            let gameInterval; // 游戏循环定时器:控制游戏帧频
            let isGameOver = false; // 游戏结束标记:初始未结束
            let gridSize; // 网格大小(每个格子的像素数):根据画布宽度计算
            let snakeSize; // 蛇身大小(像素):网格大小的80%(留边距)
            let foodSize; // 食物大小(像素):网格大小的90%(比蛇身略大)
            // 彩虹色数组:存储蛇身循环的颜色类名
            let rainbowColors = [
                'rainbow-1', 'rainbow-2', 'rainbow-3', 'rainbow-4',
                'rainbow-5', 'rainbow-6', 'rainbow-7', 'rainbow-8'
            ];
            let currentColorIndex = 0; // 当前蛇身颜色索引:控制彩虹循环
            let foodBlinkState = 1; // 食物闪烁状态:备用变量(实际用food.blink)
            let foodBlinkRate = 0.02; // 食物闪烁速率:每次帧更新的缩放变化量
            
            // 函数:初始化游戏(重置游戏状态,开始新游戏)
            function initGame() {
                // 计算网格大小:画布宽度 / 25(分成25列,保证格子数量合适),取整数
                gridSize = Math.floor(canvas.width / 25);
                // 蛇身大小 = 网格大小 * 0.8(比网格小,避免占满格子)
                snakeSize = gridSize * 0.8;
                // 食物大小 = 网格大小 * 0.9(比蛇身大,容易识别)
                foodSize = gridSize * 0.9;
                
                // 设置蛇的初始位置:3段身体,水平排列(x从3→4→5,y都是5)
                snake = [
                    {x: 5 * gridSize, y: 5 * gridSize}, // 蛇头:第5列第5行
                    {x: 4 * gridSize, y: 5 * gridSize}, // 蛇身1:第4列第5行
                    {x: 3 * gridSize, y: 5 * gridSize}  // 蛇身2:第3列第5行
                ];
                // 重置移动方向:初始向右
                direction = 'right';
                nextDirection = 'right';
                // 重置分数:0
                score = 0;
                // 重置游戏速度:150ms
                gameSpeed = 150;
                // 重置游戏结束标记:未结束
                isGameOver = false;
                // 重置蛇身颜色索引:从第一个彩虹色开始
                currentColorIndex = 0;
                
                // 更新页面分数显示:设置为0
                scoreDisplay.textContent = score;
                // 隐藏游戏结束遮罩:添加hidden类
                gameOverScreen.classList.add('hidden');
                
                // 生成第一个食物
                generateFood();
                
                // 清除之前的游戏循环(防止多个循环同时运行)
                if (gameInterval) clearInterval(gameInterval);
                // 启动新的游戏循环:每隔gameSpeed毫秒执行一次gameLoop
                gameInterval = setInterval(gameLoop, gameSpeed);
            }
            
            // 函数:生成食物(随机位置,不与蛇身重叠)
            function generateFood() {
                // 计算最大X坐标:画布宽度 / 网格大小 - 1(避免食物超出边界),取整数
                const maxX = Math.floor(canvas.width / gridSize) - 1;
                // 计算最大Y坐标:同上
                const maxY = Math.floor(canvas.height / gridSize) - 1;
                
                let overlapping; // 标记食物是否与蛇身重叠
                do {
                    overlapping = false; // 初始假设不重叠
                    // 生成食物对象:
                    food = {
                        x: Math.floor(Math.random() * maxX) * gridSize, // 随机X坐标(乘以网格大小,对齐格子)
                        y: Math.floor(Math.random() * maxY) * gridSize, // 随机Y坐标
                        blink: 1, // 闪烁初始状态(1=原大小)
                        blinkDir: -foodBlinkRate // 闪烁方向(-0.02=初始缩小)
                    };
                    
                    // 遍历蛇的每一段,检查食物是否与蛇身重叠
                    for (let segment of snake) {
                        // 如果食物坐标和某段蛇身坐标相同,说明重叠
                        if (segment.x === food.x && segment.y === food.y) {
                            overlapping = true; // 标记重叠
                            break; // 退出循环,重新生成
                        }
                    }
                } while (overlapping); // 只要重叠,就重新生成食物
            }
            
            // 函数:绘制蛇(带渐变、呼吸动画、眼睛)
            function drawSnake() {
                // 遍历蛇的每一段身体(index=0是蛇头,index>0是蛇身)
                snake.forEach((segment, index) => {
                    // 计算当前段的颜色索引:(当前颜色索引 + 每3段变一次色)% 彩虹色数量,实现层次感
                    const colorIndex = (currentColorIndex + Math.floor(index / 3)) % rainbowColors.length;
                    // 获取当前段的颜色类名(从彩虹色数组中取)
                    const colorClass = rainbowColors[colorIndex];
                    // 获取该颜色类的渐变起始和结束色(调用getGradientColors函数)
                    const colors = getGradientColors(colorClass);
                    
                    // 创建线性渐变:从蛇段左上角(x,y)到右下角(x+snakeSize,y+snakeSize)
                    const gradient = ctx.createLinearGradient(
                        segment.x, segment.y, 
                        segment.x + snakeSize, segment.y + snakeSize
                    );
                    // 设置渐变起点颜色(colors.start)
                    gradient.addColorStop(0, colors.start);
                    // 设置渐变终点颜色(colors.end)
                    gradient.addColorStop(1, colors.end);
                    
                    // 蛇身呼吸动画:根据时间和索引计算缩放比例(1 ± 2%,sin函数实现循环)
                    const scale = 1 + Math.sin(Date.now() / 300 + index * 0.5) * 0.02;
                    
                    // 保存当前绘图状态(防止后续操作影响其他绘制)
                    ctx.save();
                    // 平移到蛇段中心(为了缩放时居中):蛇段x+网格一半,y+网格一半
                    ctx.translate(segment.x + gridSize/2, segment.y + gridSize/2);
                    // 缩放蛇段:x和y方向都按scale比例缩放
                    ctx.scale(scale, scale);
                    // 平移回原位置(抵消之前的中心平移)
                    ctx.translate(-(segment.x + gridSize/2), -(segment.y + gridSize/2));
                    
                    // 设置蛇身填充色为渐变
                    ctx.fillStyle = gradient;
                    // 开始绘制路径(准备画蛇段)
                    ctx.beginPath();
                    // 绘制圆角矩形(蛇段):
                    // x坐标:蛇段x + (网格大小 - 蛇身大小)/2(居中)
                    // y坐标:同上
                    // 宽度:蛇身大小
                    // 高度:蛇身大小
                    // 圆角半径:蛇身大小/4(圆润效果)
                    ctx.roundRect(
                        segment.x + (gridSize - snakeSize) / 2,
                        segment.y + (gridSize - snakeSize) / 2,
                        snakeSize, snakeSize, snakeSize / 4
                    );
                    // 填充蛇段(用渐变颜色)
                    ctx.fill();
                    
                    // 设置高光填充色:白色半透明(10%透明度,增强立体感)
                    ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
                    // 开始绘制高光路径
                    ctx.beginPath();
                    // 绘制小的圆角矩形(高光):比蛇身小,位置居中
                    ctx.roundRect(
                        segment.x + (gridSize - snakeSize) / 2,
                        segment.y + (gridSize - snakeSize) / 2,
                        snakeSize * 0.6, snakeSize * 0.6, snakeSize / 8
                    );
                    // 填充高光
                    ctx.fill();
                    
                    // 恢复之前保存的绘图状态(结束当前蛇段的特殊效果)
                    ctx.restore();
                    
                    // 如果是蛇头(index=0),绘制眼睛
                    if (index === 0) {
                        drawEyes(segment);
                    }
                });
            }
            
            // 函数:绘制蛇头的眼睛(随移动方向变化,带微动效果)
            function drawEyes(segment) {
                // 设置眼睛填充色:白色
                ctx.fillStyle = '#fff';
                
                // 定义眼睛坐标变量:左右眼的x和y
                let leftEyeX, leftEyeY, rightEyeX, rightEyeY;
                // 眼睛大小:蛇身大小/8(比例合适)
                const eyeSize = snakeSize / 8;
                // 眼睛微动效果:根据时间变化的偏移量(sin函数循环,最大偏移眼睛大小的30%)
                const eyeOffset = Math.sin(Date.now() / 300) * eyeSize * 0.3;
                
                // 根据当前移动方向,设置眼睛位置(确保眼睛朝向移动方向)
                switch(direction) {
                    case 'right': // 向右移动:眼睛在蛇头右侧
                        leftEyeX = segment.x + snakeSize * 0.7 + eyeOffset;
                        leftEyeY = segment.y + snakeSize * 0.3;
                        rightEyeX = segment.x + snakeSize * 0.7 + eyeOffset;
                        rightEyeY = segment.y + snakeSize * 0.7;
                        break;
                    case 'left': // 向左移动:眼睛在蛇头左侧
                        leftEyeX = segment.x + snakeSize * 0.3 - eyeOffset;
                        leftEyeY = segment.y + snakeSize * 0.3;
                        rightEyeX = segment.x + snakeSize * 0.3 - eyeOffset;
                        rightEyeY = segment.y + snakeSize * 0.7;
                        break;
                    case 'up': // 向上移动:眼睛在蛇头上方
                        leftEyeX = segment.x + snakeSize * 0.3;
                        leftEyeY = segment.y + snakeSize * 0.3 - eyeOffset;
                        rightEyeX = segment.x + snakeSize * 0.7;
                        rightEyeY = segment.y + snakeSize * 0.3 - eyeOffset;
                        break;
                    case 'down': // 向下移动:眼睛在蛇头下方
                        leftEyeX = segment.x + snakeSize * 0.3;
                        leftEyeY = segment.y + snakeSize * 0.7 + eyeOffset;
                        rightEyeX = segment.x + snakeSize * 0.7;
                        rightEyeY = segment.y + snakeSize * 0.7 + eyeOffset;
                        break;
                }
                
                // 开始绘制左眼和右眼(白色部分)
                ctx.beginPath();
                // 左眼:圆弧(x,y,半径,起始角度0,结束角度2π(全圆))
                ctx.arc(leftEyeX, leftEyeY, eyeSize, 0, Math.PI * 2);
                // 右眼:同上
                ctx.arc(rightEyeX, rightEyeY, eyeSize, 0, Math.PI * 2);
                // 填充眼睛白色部分
                ctx.fill();
                
                // 瞳孔微动效果:根据时间变化的偏移量(x和y方向,最大偏移眼睛大小的20%)
                const pupilOffsetX = Math.sin(Date.now() / 500) * eyeSize * 0.2;
                const pupilOffsetY = Math.cos(Date.now() / 500) * eyeSize * 0.2;
                
                // 设置瞳孔填充色:黑色
                ctx.fillStyle = '#000';
                // 开始绘制瞳孔
                ctx.beginPath();
                // 左瞳孔:比眼睛小一半(半径eyeSize/2),带偏移
                ctx.arc(leftEyeX + pupilOffsetX, leftEyeY + pupilOffsetY, eyeSize / 2, 0, Math.PI * 2);
                // 右瞳孔:同上
                ctx.arc(rightEyeX + pupilOffsetX, rightEyeY + pupilOffsetY, eyeSize / 2, 0, Math.PI * 2);
                // 填充瞳孔
                ctx.fill();
            }
            
            // 函数:获取彩虹色类对应的渐变颜色(返回起始色和结束色)
            function getGradientColors(colorClass) {
                // 定义颜色映射对象:键是颜色类名,值是{start: 起始色, end: 结束色}
                const gradients = {
                    'rainbow-1': { start: '#FF5E5E', end: '#FF7D5E' },
                    'rainbow-2': { start: '#FF7D5E', end: '#FFB347' },
                    'rainbow-3': { start: '#FFB347', end: '#FFF275' },
                    'rainbow-4': { start: '#FFF275', end: '#93FF96' },
                    'rainbow-5': { start: '#93FF96', end: '#42D7F5' },
                    'rainbow-6': { start: '#42D7F5', end: '#7D5FFF' },
                    'rainbow-7': { start: '#7D5FFF', end: '#C774E8' },
                    'rainbow-8': { start: '#C774E8', end: '#FF5E5E' }
                };
                
                // 返回对应颜色类的渐变值,如果没有找到,默认用主色到辅助色的渐变
                return gradients[colorClass] || { start: '#4F46E5', end: '#EC4899' };
            }
            
            // 函数:绘制食物(带旋转、闪烁、渐变效果)
            function drawFood() {
                // 更新食物闪烁状态:blink += 闪烁方向(blinkDir)
                food.blink += food.blinkDir;
                // 闪烁边界判断:如果blink≤0.8(最小缩放)或≥1.2(最大缩放),反转闪烁方向
                if (food.blink <= 0.8 || food.blink >= 1.2) {
                    food.blinkDir *= -1;
                }
                
                // 保存当前绘图状态
                ctx.save();
                // 平移到食物中心(为了旋转和缩放居中):食物x+网格一半,y+网格一半
                ctx.translate(food.x + gridSize / 2, food.y + gridSize / 2);
                // 旋转食物:根据时间变化(每800毫秒转一圈,Date.now()返回毫秒数)
                ctx.rotate(Date.now() / 800);
                // 缩放食物:根据blink状态(实现闪烁效果)
                ctx.scale(food.blink, food.blink);
                
                // 开始绘制食物路径(星形)
                ctx.beginPath();
                // 起点:顶部(0, -食物大小/2)
                ctx.moveTo(0, -foodSize / 2);
                // 循环10次(绘制5个角的星形)
                for (let i = 1; i < 10; i++) {
                    // 计算每个点的角度:(i*2π/10) - π/2(从顶部开始,顺时针旋转)
                    const angle = (i * 2 * Math.PI / 10) - (Math.PI / 2);
                    // 计算每个点的半径:偶数i(凹点)用小半径,奇数i(凸点)用大半径
                    const radius = i % 2 === 0 ? foodSize / 2 : foodSize / 5;
                    // 绘制线段到当前点:x=cos(angle)*radius,y=sin(angle)*radius
                    ctx.lineTo(Math.cos(angle) * radius, Math.sin(angle) * radius);
                }
                // 闭合路径(连接起点和最后一个点)
                ctx.closePath();
                
                // 创建径向渐变(从中心到边缘,模拟发光效果)
                const gradient = ctx.createRadialGradient(
                    0, 0, 0, // 渐变中心(x,y,半径0)
                    0, 0, foodSize / 2 // 渐变边缘(x,y,半径=食物大小/2)
                );
                // 渐变起点颜色:亮黄色(#FFEE58)
                gradient.addColorStop(0, '#FFEE58');
                // 渐变中间颜色:橙色(#FFC107)
                gradient.addColorStop(0.5, '#FFC107');
                // 渐变终点颜色:深橙色(#FF8F00)
                gradient.addColorStop(1, '#FF8F00');
                
                // 设置食物填充色为径向渐变
                ctx.fillStyle = gradient;
                // 填充食物(星形)
                ctx.fill();
                
                // 设置食物边框色:白色半透明(90%透明度)
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.9)';
                // 设置边框宽度:2px
                ctx.lineWidth = 2;
                // 绘制食物边框(增强轮廓)
                ctx.stroke();
                
                // 设置食物中心高光色:白色半透明(60%透明度)
                ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
                // 开始绘制中心高光
                ctx.beginPath();
                // 中心高光:小圆(半径=食物大小/8)
                ctx.arc(0, 0, foodSize / 8, 0, Math.PI * 2);
                // 填充高光
                ctx.fill();
                
                // 恢复之前的绘图状态
                ctx.restore();
            }
            
            // 函数:绘制吃到食物的粒子效果(彩色小圆圈,增强反馈)
            function drawParticles(x, y) {
                // 循环8次:生成8个粒子(对应8种彩虹色)
                for (let i = 0; i < 8; i++) {
                    // 计算粒子角度:每45度一个(i*π/4),围成一圈
                    const angle = (i * Math.PI / 4);
                    // 粒子距离中心的距离:网格大小*0.6(合适的扩散范围)
                    const distance = gridSize * 0.6;
                    // 计算粒子x坐标:中心x + cos(angle)*distance
                    const particleX = x + Math.cos(angle) * distance;
                    // 计算粒子y坐标:中心y + sin(angle)*distance
                    const particleY = y + Math.sin(angle) * distance;
                    
                    // 获取粒子颜色类名:i%彩虹色数量(循环使用8种颜色)
                    const colorClass = rainbowColors[i % rainbowColors.length];
                    // 获取该颜色的起始色(粒子用纯色,不用渐变)
                    const colors = getGradientColors(colorClass);
                    
                    // 设置粒子填充色为颜色的起始色
                    ctx.fillStyle = colors.start;
                    // 开始绘制粒子
                    ctx.beginPath();
                    // 粒子:小圆(半径=网格大小/8)
                    ctx.arc(particleX, particleY, gridSize / 8, 0, Math.PI * 2);
                    // 填充粒子
                    ctx.fill();
                }
            }
            
            // 函数:检测碰撞(边界碰撞或自碰撞)
            function checkCollision() {
                // 复制蛇头坐标(避免修改原蛇头数据)
                const head = {...snake[0]};
                
                // 检测边界碰撞:蛇头超出画布范围(左<0,右≥画布宽度,上<0,下≥画布高度)
                if (
                    head.x < 0 || 
                    head.x >= canvas.width || 
                    head.y < 0 || 
                    head.y >= canvas.height
                ) {
                    return true; // 碰撞边界,返回true
                }
                
                // 检测自碰撞:蛇头与身体其他段重叠
                for (let i = 1; i < snake.length; i++) {
                    // 如果蛇头坐标和第i段蛇身坐标相同,说明自撞
                    if (head.x === snake[i].x && head.y === snake[i].y) {
                        return true; // 自碰撞,返回true
                    }
                }
                
                return false; // 无碰撞,返回false
            }
            
            // 函数:分数增加动画(分数变化时放大+变色,增强反馈)
            function animateScoreIncrease() {
                // 获取分数数值元素
                const scoreElement = document.getElementById('score');
                // 添加动画类:放大到125%,文字变辅助色
                scoreElement.classList.add('scale-125', 'text-secondary');
                // 300毫秒后移除动画类(恢复原状态)
                setTimeout(() => {
                    scoreElement.classList.remove('scale-125', 'text-secondary');
                }, 300);
            }
            
            // 函数:游戏主循环(每帧执行,控制蛇移动、碰撞检测、绘制)
            function gameLoop() {
                // 清除画布:从(0,0)到(画布宽度,画布高度),清除所有绘制内容
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                
                // 更新当前方向为下一个方向(防止快速反向)
                direction = nextDirection;
                
                // 复制蛇头坐标(用于计算新蛇头位置)
                const head = {x: snake[0].x, y: snake[0].y};
                
                // 根据当前方向,计算新蛇头位置(移动一个网格大小)
                switch(direction) {
                    case 'up': // 向上:y坐标减少网格大小
                        head.y -= gridSize;
                        break;
                    case 'down': // 向下:y坐标增加网格大小
                        head.y += gridSize;
                        break;
                    case 'left': // 向左:x坐标减少网格大小
                        head.x -= gridSize;
                        break;
                    case 'right': // 向右:x坐标增加网格大小
                        head.x += gridSize;
                        break;
                }
                
                // 在蛇头前面添加新蛇头(实现移动效果)
                snake.unshift(head);
                // 标记是否吃到食物:初始false
                let ateFood = false;
                
                // 检查是否吃到食物:新蛇头坐标和食物坐标相同
                if (head.x === food.x && head.y === food.y) {
                    // 分数增加10分
                    score += 10;
                    // 更新页面分数显示
                    scoreDisplay.textContent = score;
                    // 播放分数增加动画
                    animateScoreIncrease();
                    
                    // 提高游戏速度:当前速度-5ms,最低不低于50ms(避免太快无法操作)
                    gameSpeed = Math.max(50, gameSpeed - 5);
                    // 切换蛇身颜色:颜色索引+1(彩虹循环)
                    currentColorIndex++;
                    // 生成新的食物
                    generateFood();
                    // 清除旧的游戏循环(速度变了,需要重新设置)
                    clearInterval(gameInterval);
                    // 启动新的游戏循环(用新速度)
                    gameInterval = setInterval(gameLoop, gameSpeed);
                    // 标记吃到食物
                    ateFood = true;
                } else {
                    // 没吃到食物:移除蛇尾(保持蛇身长度不变)
                    snake.pop();
                }
                
                // 检测碰撞:如果碰撞(边界或自撞)
                if (checkCollision()) {
                    // 执行游戏结束逻辑
                    gameOver();
                    // 退出当前循环(不再继续绘制)
                    return;
                }
                
                // 绘制蛇
                drawSnake();
                // 绘制食物
                drawFood();
                
                // 如果吃到食物,绘制粒子效果(粒子中心在蛇头中心)
                if (ateFood) {
                    drawParticles(head.x + gridSize/2, head.y + gridSize/2);
                }
            }
            
            // 函数:游戏结束处理
            function gameOver() {
                // 标记游戏结束
                isGameOver = true;
                // 清除游戏循环(停止蛇移动)
                clearInterval(gameInterval);
                // 更新最终分数显示
                finalScoreDisplay.textContent = score;
                // 显示游戏结束遮罩(移除hidden类)
                gameOverScreen.classList.remove('hidden');
            }
            
            // 键盘控制:监听键盘按下事件
            document.addEventListener('keydown', (e) => {
                // 如果游戏结束且按下空格键,重新开始游戏
                if (isGameOver && e.key === ' ') {
                    initGame();
                    return; // 退出事件处理,避免后续方向键逻辑
                }
                
                // 阻止方向键和空格键的默认行为(比如页面滚动)
                if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
                    e.preventDefault();
                }
                
                // 根据按下的方向键,设置下一个移动方向(防止反向)
                switch(e.key) {
                    case 'ArrowUp': // 上箭头:当前方向不是向下时,才能向上
                        if (direction !== 'down') {
                            nextDirection = 'up';
                        }
                        break;
                    case 'ArrowDown': // 下箭头:当前方向不是向上时,才能向下
                        if (direction !== 'up') {
                            nextDirection = 'down';
                        }
                        break;
                    case 'ArrowLeft': // 左箭头:当前方向不是向右时,才能向左
                        if (direction !== 'right') {
                            nextDirection = 'left';
                        }
                        break;
                    case 'ArrowRight': // 右箭头:当前方向不是向左时,才能向右
                        if (direction !== 'left') {
                            nextDirection = 'right';
                        }
                        break;
                }
            });
            
            // 触摸控制:适配移动端,监听触摸事件
            let touchStartX = 0; // 触摸开始时的x坐标
            let touchStartY = 0; // 触摸开始时的y坐标
            
            // 触摸开始事件:记录初始触摸位置
            canvas.addEventListener('touchstart', (e) => {
                // 获取第一个触摸点的x坐标
                touchStartX = e.touches[0].clientX;
                // 获取第一个触摸点的y坐标
                touchStartY = e.touches[0].clientY;
                // 阻止默认行为(比如页面滚动)
                e.preventDefault();
            }, { passive: false }); // passive: false允许preventDefault
            
            // 触摸移动事件:阻止默认行为(避免滚动)
            canvas.addEventListener('touchmove', (e) => {
                e.preventDefault();
            }, { passive: false });
            
            // 触摸结束事件:计算滑动方向,控制蛇移动
            canvas.addEventListener('touchend', (e) => {
                // 如果没有记录初始触摸位置,退出(防止异常)
                if (!touchStartX || !touchStartY) return;
                
                // 获取触摸结束时的x坐标
                const touchEndX = e.changedTouches[0].clientX;
                // 获取触摸结束时的y坐标
                const touchEndY = e.changedTouches[0].clientY;
                
                // 计算x方向滑动距离(结束-开始)
                const diffX = touchEndX - touchStartX;
                // 计算y方向滑动距离(结束-开始)
                const diffY = touchEndY - touchStartY;
                
                // 判断滑动方向:水平滑动距离大于垂直滑动(左右滑动)
                if (Math.abs(diffX) > Math.abs(diffY)) {
                    // 向右滑动(diffX>0)且当前方向不是向左,设置下方向为右
                    if (diffX > 0 && direction !== 'left') {
                        nextDirection = 'right';
                    } 
                    // 向左滑动(diffX<0)且当前方向不是向右,设置下方向为左
                    else if (diffX < 0 && direction !== 'right') {
                        nextDirection = 'left';
                    }
                } else {
                    // 垂直滑动距离大于水平滑动(上下滑动)
                    // 向下滑动(diffY>0)且当前方向不是向上,设置下方向为下
                    if (diffY > 0 && direction !== 'up') {
                        nextDirection = 'down';
                    } 
                    // 向上滑动(diffY<0)且当前方向不是向下,设置下方向为上
                    else if (diffY < 0 && direction !== 'down') {
                        nextDirection = 'up';
                    }
                }
                
                // 重置初始触摸位置(准备下一次触摸)
                touchStartX = 0;
                touchStartY = 0;
                // 阻止默认行为
                e.preventDefault();
            }, { passive: false });
            
            // 重新开始按钮:点击事件,调用initGame重启游戏
            restartButton.addEventListener('click', initGame);
            
            // 初始化游戏:页面加载完成后,启动第一次游戏
            initGame();
        });
    </script>
</body>
</html>
相关推荐
小欣加油11 小时前
leetcode56 合并区间
c++·算法·leetcode·职场和发展
lqqjuly11 小时前
前沿算法深度解析(二)
人工智能·算法·机器学习
海南java第二人11 小时前
Nebula Graph 实战:基于图数据库存储 CMDB 实体关系
数据库·图数据库·nebula
曹牧12 小时前
oracle:“not all variables bound”
数据库·oracle
数据库百宝箱12 小时前
Oracle RMAN Image Copy 本地恢复
数据库·oracle
清风66666612 小时前
基于单片机与DAC0832的双路波形信号发生系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
徐小夕12 小时前
万字长文!千万级文档 RAG 知识库系统落地实践
前端·算法·github
akunkuntaimei13 小时前
2026年高考数学各省真题及答案(完整版)
算法·高考
zuYM4g7Dp13 小时前
NoSql数据库设计心得
数据库·nosql
Hello:CodeWorld13 小时前
C 风格变参 vs C++ 变参模板:核心区别与选型指南
c语言·c++·算法