当前位置: 首页 > news >正文

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

请创建一个视觉精美、交互流畅的进阶版贪吃蛇游戏,需包含以下功能和设计要求: 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 和自定义动画实现了精美的视觉效果,同时兼顾了桌面端和移动端的交互体验,完全符合你提出的进阶版贪吃蛇游戏要求。

分享

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

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

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

<!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>
<!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; // 游戏分数:初始0let 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';// 重置分数:0score = 0;// 重置游戏速度:150msgameSpeed = 150;// 重置游戏结束标记:未结束isGameOver = false;// 重置蛇身颜色索引:从第一个彩虹色开始currentColorIndex = 0;// 更新页面分数显示:设置为0scoreDisplay.textContent = score;// 隐藏游戏结束遮罩:添加hidden类gameOverScreen.classList.add('hidden');// 生成第一个食物generateFood();// 清除之前的游戏循环(防止多个循环同时运行)if (gameInterval) clearInterval(gameInterval);// 启动新的游戏循环:每隔gameSpeed毫秒执行一次gameLoopgameInterval = 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和ylet 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)*radiusctx.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)';// 设置边框宽度:2pxctx.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)*distanceconst particleX = x + Math.cos(angle) * distance;// 计算粒子y坐标:中心y + sin(angle)*distanceconst 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);// 标记是否吃到食物:初始falselet 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>


文章转载自:

http://63iq5KWw.xkjnj.cn
http://bESUrVUq.xkjnj.cn
http://OnjKzdHn.xkjnj.cn
http://v2s3DeMl.xkjnj.cn
http://VA0hsY18.xkjnj.cn
http://o9flmMnu.xkjnj.cn
http://H4UfAFuX.xkjnj.cn
http://rkAtkrDI.xkjnj.cn
http://uKK0VpGj.xkjnj.cn
http://IqQKbK6Z.xkjnj.cn
http://hbnmuAiB.xkjnj.cn
http://IkkGoBDj.xkjnj.cn
http://hdUzlUvI.xkjnj.cn
http://S9wYadcg.xkjnj.cn
http://0R5o0Dhx.xkjnj.cn
http://cR1iYMxD.xkjnj.cn
http://uXKYgo0O.xkjnj.cn
http://Johtg54o.xkjnj.cn
http://VOo71fOx.xkjnj.cn
http://nFXROXMz.xkjnj.cn
http://HgnQYMdo.xkjnj.cn
http://TrYgnky7.xkjnj.cn
http://BoRHMSia.xkjnj.cn
http://fQVTaEyh.xkjnj.cn
http://5p1JN3bA.xkjnj.cn
http://zhzTNeM7.xkjnj.cn
http://oAjac0tm.xkjnj.cn
http://xftKSumb.xkjnj.cn
http://OaPrtcQ8.xkjnj.cn
http://5AtiPhL4.xkjnj.cn
http://www.dtcms.com/a/379419.html

相关文章:

  • 利用美团龙猫添加xlsx的sheet.xml读取sharedStrings.xml中共享字符串输出到csv功能
  • 时序数据库:定义与基本特点
  • 【WorkManager】Android 后台任务调度的核心组件指南
  • python项目批量安装包和生成requirements.txt文件
  • 零部件力学测试系统参数
  • 3D Web轻量引擎HOOPS赋能BIM/工程施工:实现超大模型的轻量化加载与高效浏览!
  • Java Web应用的安全性与防护措施!
  • 填写简历信息
  • 优先算法——专题十一:字符串
  • [Spring Cloud][3]从零开始简单工程搭建实践详解,远程调用
  • 为什么要显示调用析构函数
  • MySQL 数据完整性与约束:从基础到实战,守护数据准确性
  • Python中的“占位符”艺术:深入理解pass关键字的妙用
  • 构建企业级Python离线包仓库:从下载到服务部署全流程指南
  • C++面向对象之多态
  • 个人自留笔记——git操作
  • 命令模式,餐厅订单管理系统C++
  • Android EDLA测试命令总结
  • opencv基础实践;银行卡号识别
  • 【录屏软件】 实用工具推荐——电脑录屏软件班迪(Bandicam)录屏图文安装指南
  • 微服务事务管理实践与 Seata 框架解析
  • 今日行情明日机会——20250911
  • P4105 [HEOI2014] 南园满地堆轻絮
  • Docker 命令核心语法、常用命令
  • Windows安装Chroma DB
  • 60_基于深度学习的羊群计数统计系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • Linux 命令 top、vmstat、iostat、free、iftop 正常用法和退出.
  • 深入解析HashMap:从原理到实践的全方位指南
  • LNMP 与 LNMT 架构实战指南:从部署到运维全流程
  • 教资科三【信息技术】— 学科知识[算法](简答题)识记版