| <!DOCTYPE html> |
| <html lang="zh-CN"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>贪吃蛇游戏 - Snake Customizer</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); |
| |
| :root { |
| --grid-size: 20px; |
| --grid-color: #374151; |
| --snake-color: #10B981; |
| --food-color: #EF4444; |
| --obstacle-color: #6B7280; |
| --bg-color: #111827; |
| --speed: 150; |
| } |
| |
| .game-container { |
| position: relative; |
| background-color: var(--bg-color); |
| background-image: |
| linear-gradient(var(--grid-color) 1px, transparent 1px), |
| linear-gradient(90deg, var(--grid-color) 1px, transparent 1px); |
| background-size: var(--grid-size) var(--grid-size); |
| } |
| |
| .snake-head { |
| background-color: var(--snake-color); |
| border-radius: 4px; |
| box-shadow: 0 0 5px rgba(16, 185, 129, 0.7); |
| } |
| |
| .snake-body { |
| background-color: var(--snake-color); |
| border-radius: 2px; |
| } |
| |
| .food { |
| background-color: var(--food-color); |
| border-radius: 50%; |
| box-shadow: 0 0 8px rgba(239, 68, 68, 0.8); |
| animation: pulse 1.5s infinite; |
| } |
| |
| .obstacle { |
| background-color: var(--obstacle-color); |
| border-radius: 3px; |
| } |
| |
| .retro-font { |
| font-family: 'Press Start 2P', cursive; |
| } |
| |
| .custom-slider::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| appearance: none; |
| width: 20px; |
| height: 20px; |
| border-radius: 50%; |
| background: #10B981; |
| cursor: pointer; |
| } |
| |
| .custom-slider::-moz-range-thumb { |
| width: 20px; |
| height: 20px; |
| border-radius: 50%; |
| background: #10B981; |
| cursor: pointer; |
| } |
| |
| .color-preview { |
| width: 30px; |
| height: 30px; |
| border-radius: 4px; |
| cursor: pointer; |
| } |
| |
| .settings-panel { |
| transition: transform 0.3s ease-in-out; |
| } |
| |
| .settings-panel.hidden { |
| transform: translateX(100%); |
| } |
| |
| .game-over { |
| background: rgba(0, 0, 0, 0.8); |
| backdrop-filter: blur(4px); |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(1); } |
| 50% { transform: scale(1.1); } |
| 100% { transform: scale(1); } |
| } |
| |
| @keyframes snakeMove { |
| 0% { transform: scale(0.9); } |
| 50% { transform: scale(1); } |
| 100% { transform: scale(0.9); } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col"> |
| |
| <header class="py-4 px-6 bg-gray-800 border-b border-green-500"> |
| <div class="container mx-auto flex justify-between items-center"> |
| <h1 class="text-2xl md:text-3xl font-bold text-green-400 retro-font flex items-center"> |
| <i class="fas fa-snake mr-3"></i>贪吃蛇游戏 - Snake Customizer |
| </h1> |
| <div class="flex items-center space-x-2"> |
| <span id="score-display" class="bg-gray-700 px-4 py-2 rounded-lg text-green-300 retro-font">分数: 0</span> |
| <button id="fullscreen-btn" class="bg-gray-600 hover:bg-gray-500 text-white p-2 rounded-lg transition" title="全屏"> |
| <i class="fas fa-expand"></i> |
| </button> |
| <button id="settings-toggle" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition"> |
| <i class="fas fa-cog"></i> 设置 |
| </button> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <main class="flex-grow flex flex-col md:flex-row container mx-auto px-4 py-6 gap-6"> |
| |
| <div class="flex-grow flex flex-col items-center"> |
| <div class="game-container w-full max-w-2xl h-96 md:h-[500px] rounded-xl overflow-hidden border-2 border-green-500 relative" id="game-canvas"> |
| |
| <div id="game-over" class="game-over absolute inset-0 flex flex-col items-center justify-center hidden"> |
| <h2 class="text-3xl font-bold text-red-500 mb-4 retro-font">游戏结束!</h2> |
| <p class="text-xl text-green-300 mb-6 retro-font">最终分数: <span id="final-score">0</span></p> |
| <button id="restart-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-lg text-xl transition"> |
| <i class="fas fa-redo mr-2"></i>重新开始 |
| </button> |
| </div> |
| </div> |
| |
| |
| <div class="mt-6 flex flex-wrap justify-center gap-4"> |
| <button id="start-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-lg text-lg transition"> |
| <i class="fas fa-play mr-2"></i>开始游戏 |
| </button> |
| <button id="pause-btn" class="bg-yellow-600 hover:bg-yellow-700 text-white px-6 py-3 rounded-lg text-lg transition"> |
| <i class="fas fa-pause mr-2"></i>暂停 |
| </button> |
| <button id="reset-btn" class="bg-red-600 hover:bg-red-700 text-white px-6 py-3 rounded-lg text-lg transition"> |
| <i class="fas fa-sync mr-2"></i>重置 |
| </button> |
| </div> |
| |
| |
| <div class="mt-6 grid grid-cols-3 gap-2 w-64"> |
| <div></div> |
| <button id="up-btn" class="bg-gray-700 hover:bg-gray-600 text-white p-4 rounded-lg text-center"> |
| <i class="fas fa-arrow-up"></i> |
| </button> |
| <div></div> |
| <button id="left-btn" class="bg-gray-700 hover:bg-gray-600 text-white p-4 rounded-lg text-center"> |
| <i class="fas fa-arrow-left"></i> |
| </button> |
| <div></div> |
| <button id="right-btn" class="bg-gray-700 hover:bg-gray-600 text-white p-4 rounded-lg text-center"> |
| <i class="fas fa-arrow-right"></i> |
| </button> |
| <div></div> |
| <button id="down-btn" class="bg-gray-700 hover:bg-gray-600 text-white p-4 rounded-lg text-center"> |
| <i class="fas fa-arrow-down"></i> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="settings-panel" class="settings-panel w-full md:w-80 bg-gray-800 rounded-xl p-6 border border-green-500 h-fit"> |
| <div class="flex justify-between items-center mb-6"> |
| <h2 class="text-xl font-bold text-green-400"><i class="fas fa-sliders-h mr-2"></i>游戏设置</h2> |
| <button id="close-settings" class="text-gray-400 hover:text-white"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="space-y-6"> |
| |
| <div> |
| <h3 class="text-lg font-semibold mb-2 text-gray-300"><i class="fas fa-tachometer-alt mr-2"></i>难度设置</h3> |
| <div class="space-y-4"> |
| <div> |
| <label class="block mb-1 text-gray-400">游戏速度</label> |
| <div class="flex items-center"> |
| <span class="mr-2 text-sm">慢</span> |
| <input type="range" min="50" max="300" value="150" class="custom-slider w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer" id="speed-slider"> |
| <span class="ml-2 text-sm">快</span> |
| </div> |
| <div class="text-right text-sm text-green-400" id="speed-value">中等</div> |
| </div> |
| |
| <div> |
| <label class="block mb-1 text-gray-400">网格大小</label> |
| <div class="flex gap-2"> |
| <button data-size="15" class="grid-size-btn flex-1 bg-gray-700 hover:bg-gray-600 py-2 rounded">小</button> |
| <button data-size="20" class="grid-size-btn flex-1 bg-green-600 py-2 rounded">中</button> |
| <button data-size="25" class="grid-size-btn flex-1 bg-gray-700 hover:bg-gray-600 py-2 rounded">大</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div> |
| <h3 class="text-lg font-semibold mb-2 text-gray-300"><i class="fas fa-gamepad mr-2"></i>游戏模式</h3> |
| <div class="space-y-2"> |
| <label class="flex items-center cursor-pointer"> |
| <input type="checkbox" id="wall-collision" class="form-checkbox h-5 w-5 text-green-500" checked> |
| <span class="ml-2 text-gray-300">墙壁碰撞</span> |
| </label> |
| |
| <label class="flex items-center cursor-pointer"> |
| <input type="checkbox" id="obstacles" class="form-checkbox h-5 w-5 text-green-500"> |
| <span class="ml-2 text-gray-300">添加障碍物</span> |
| </label> |
| |
| <label class="flex items-center cursor-pointer"> |
| <input type="checkbox" id="grow-mode" class="form-checkbox h-5 w-5 text-green-500"> |
| <span class="ml-2 text-gray-300">成长模式</span> |
| </label> |
| </div> |
| </div> |
| |
| |
| <div> |
| <h3 class="text-lg font-semibold mb-2 text-gray-300"><i class="fas fa-palette mr-2"></i>外观设置</h3> |
| <div class="grid grid-cols-2 gap-4"> |
| <div> |
| <label class="block mb-1 text-gray-400">蛇的颜色</label> |
| <div class="flex gap-2"> |
| <div class="color-preview bg-green-500" data-color="#10B981" title="绿色"></div> |
| <div class="color-preview bg-blue-500" data-color="#3B82F6" title="蓝色"></div> |
| <div class="color-preview bg-purple-500" data-color="#8B5CF6" title="紫色"></div> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block mb-1 text-gray-400">食物颜色</label> |
| <div class="flex gap-2"> |
| <div class="color-preview bg-red-500" data-color="#EF4444" title="红色"></div> |
| <div class="color-preview bg-yellow-500" data-color="#F59E0B" title="黄色"></div> |
| <div class="color-preview bg-pink-500" data-color="#EC4899" title="粉色"></div> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block mb-1 text-gray-400">网格颜色</label> |
| <div class="flex gap-2"> |
| <div class="color-preview bg-gray-500" data-color="#6B7280" title="灰色"></div> |
| <div class="color-preview bg-blue-400" data-color="#60A5FA" title="浅蓝"></div> |
| <div class="color-preview bg-green-400" data-color="#34D399" title="浅绿"></div> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block mb-1 text-gray-400">背景颜色</label> |
| <div class="flex gap-2"> |
| <div class="color-preview bg-gray-900" data-color="#111827" title="深灰"></div> |
| <div class="color-preview bg-gray-800" data-color="#1F2937" title="中灰"></div> |
| <div class="color-preview bg-gray-700" data-color="#374151" title="浅灰"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div> |
| <h3 class="text-lg font-semibold mb-2 text-gray-300"><i class="fas fa-cogs mr-2"></i>高级设置</h3> |
| <div class="space-y-2"> |
| <div> |
| <label class="block mb-1 text-gray-400">初始长度</label> |
| <input type="range" min="3" max="10" value="4" class="custom-slider w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer" id="initial-length"> |
| <div class="text-right text-sm text-green-400" id="length-value">4</div> |
| </div> |
| |
| <div> |
| <label class="block mb-1 text-gray-400">食物数量</label> |
| <input type="range" min="1" max="5" value="1" class="custom-slider w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer" id="food-count"> |
| <div class="text-right text-sm text-green-400" id="food-count-value">1</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <button id="apply-settings" class="mt-8 w-full bg-green-600 hover:bg-green-700 text-white py-3 rounded-lg text-lg transition"> |
| <i class="fas fa-check-circle mr-2"></i>应用设置 |
| </button> |
| </div> |
| </main> |
|
|
| |
| <footer class="bg-gray-800 border-t border-green-500 py-6 px-4"> |
| <div class="container mx-auto"> |
| <h2 class="text-xl font-bold text-green-400 mb-4"><i class="fas fa-info-circle mr-2"></i>游戏说明</h2> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-gray-300"> |
| <div class="bg-gray-700 p-4 rounded-lg"> |
| <h3 class="font-semibold text-green-300 mb-2"><i class="fas fa-keyboard mr-2"></i>控制方式</h3> |
| <p>键盘方向键或WASD控制蛇的移动方向</p> |
| <p>移动设备可使用屏幕上的方向按钮</p> |
| </div> |
| <div class="bg-gray-700 p-4 rounded-lg"> |
| <h3 class="font-semibold text-green-300 mb-2"><i class="fas fa-star mr-2"></i>游戏目标</h3> |
| <p>控制蛇吃到食物(红色圆点)</p> |
| <p>每吃到一个食物,蛇身长度增加,分数增加</p> |
| </div> |
| <div class="bg-gray-700 p-4 rounded-lg"> |
| <h3 class="font-semibold text-green-300 mb-2"><i class="fas fa-skull mr-2"></i>游戏结束</h3> |
| <p>蛇撞到墙壁(如果开启墙壁碰撞)</p> |
| <p>蛇撞到自己的身体</p> |
| <p>蛇撞到障碍物(如果开启障碍物)</p> |
| </div> |
| </div> |
| </div> |
| </footer> |
|
|
| <script> |
| |
| let game = { |
| canvas: null, |
| ctx: null, |
| gridSize: 20, |
| width: 0, |
| height: 0, |
| snake: [], |
| direction: 'right', |
| nextDirection: 'right', |
| food: [], |
| obstacles: [], |
| score: 0, |
| isRunning: false, |
| isPaused: false, |
| gameLoop: null, |
| speed: 150, |
| wallCollision: true, |
| obstaclesEnabled: false, |
| growMode: false, |
| initialLength: 4, |
| foodCount: 1 |
| }; |
| |
| |
| const colors = { |
| snake: '#10B981', |
| food: '#EF4444', |
| grid: '#374151', |
| bg: '#111827', |
| obstacle: '#6B7280' |
| }; |
| |
| |
| const elements = { |
| gameContainer: document.getElementById('game-canvas'), |
| fullscreenBtn: document.getElementById('fullscreen-btn'), |
| scoreDisplay: document.getElementById('score-display'), |
| gameOver: document.getElementById('game-over'), |
| finalScore: document.getElementById('final-score'), |
| restartBtn: document.getElementById('restart-btn'), |
| startBtn: document.getElementById('start-btn'), |
| pauseBtn: document.getElementById('pause-btn'), |
| resetBtn: document.getElementById('reset-btn'), |
| settingsPanel: document.getElementById('settings-panel'), |
| settingsToggle: document.getElementById('settings-toggle'), |
| closeSettings: document.getElementById('close-settings'), |
| applySettings: document.getElementById('apply-settings'), |
| speedSlider: document.getElementById('speed-slider'), |
| speedValue: document.getElementById('speed-value'), |
| wallCollision: document.getElementById('wall-collision'), |
| obstacles: document.getElementById('obstacles'), |
| growMode: document.getElementById('grow-mode'), |
| initialLength: document.getElementById('initial-length'), |
| lengthValue: document.getElementById('length-value'), |
| foodCount: document.getElementById('food-count'), |
| foodCountValue: document.getElementById('food-count-value'), |
| gridSizeBtns: document.querySelectorAll('.grid-size-btn'), |
| colorPreviews: document.querySelectorAll('.color-preview'), |
| upBtn: document.getElementById('up-btn'), |
| downBtn: document.getElementById('down-btn'), |
| leftBtn: document.getElementById('left-btn'), |
| rightBtn: document.getElementById('right-btn') |
| }; |
| |
| |
| function initGame() { |
| |
| const rect = elements.gameContainer.getBoundingClientRect(); |
| game.width = Math.floor(rect.width / game.gridSize) * game.gridSize; |
| game.height = Math.floor(rect.height / game.gridSize) * game.gridSize; |
| |
| |
| game.canvas = document.createElement('canvas'); |
| game.canvas.width = game.width; |
| game.canvas.height = game.height; |
| game.canvas.classList.add('absolute'); |
| elements.gameContainer.innerHTML = ''; |
| elements.gameContainer.appendChild(game.canvas); |
| game.ctx = game.canvas.getContext('2d'); |
| |
| |
| resetGame(); |
| |
| |
| updateCSSVariables(); |
| } |
| |
| |
| function resetGame() { |
| game.snake = []; |
| game.food = []; |
| game.obstacles = []; |
| game.direction = 'right'; |
| game.nextDirection = 'right'; |
| game.score = 0; |
| game.isRunning = false; |
| game.isPaused = false; |
| |
| |
| elements.gameOver.classList.add('hidden'); |
| |
| |
| updateScore(); |
| |
| |
| createSnake(); |
| |
| |
| createFood(); |
| |
| |
| if (game.obstaclesEnabled) { |
| createObstacles(); |
| } |
| |
| |
| drawGame(); |
| } |
| |
| |
| function createSnake() { |
| game.snake = []; |
| const startX = Math.floor(game.width / game.gridSize / 4) * game.gridSize; |
| const startY = Math.floor(game.height / game.gridSize / 2) * game.gridSize; |
| |
| for (let i = 0; i < game.initialLength; i++) { |
| game.snake.push({ |
| x: startX - i * game.gridSize, |
| y: startY |
| }); |
| } |
| } |
| |
| |
| function createFood() { |
| for (let i = 0; i < game.foodCount; i++) { |
| let newFood; |
| let overlapping; |
| |
| do { |
| overlapping = false; |
| newFood = { |
| x: Math.floor(Math.random() * (game.width / game.gridSize)) * game.gridSize, |
| y: Math.floor(Math.random() * (game.height / game.gridSize)) * game.gridSize |
| }; |
| |
| |
| for (const segment of game.snake) { |
| if (segment.x === newFood.x && segment.y === newFood.y) { |
| overlapping = true; |
| break; |
| } |
| } |
| |
| |
| for (const food of game.food) { |
| if (food.x === newFood.x && food.y === newFood.y) { |
| overlapping = true; |
| break; |
| } |
| } |
| |
| |
| for (const obstacle of game.obstacles) { |
| if (obstacle.x === newFood.x && obstacle.y === newFood.y) { |
| overlapping = true; |
| break; |
| } |
| } |
| } while (overlapping); |
| |
| game.food.push(newFood); |
| } |
| } |
| |
| |
| function createObstacles() { |
| const obstacleCount = Math.floor((game.width / game.gridSize) * (game.height / game.gridSize) * 0.05); |
| |
| for (let i = 0; i < obstacleCount; i++) { |
| let newObstacle; |
| let overlapping; |
| |
| do { |
| overlapping = false; |
| newObstacle = { |
| x: Math.floor(Math.random() * (game.width / game.gridSize)) * game.gridSize, |
| y: Math.floor(Math.random() * (game.height / game.gridSize)) * game.gridSize |
| }; |
| |
| |
| for (const segment of game.snake) { |
| if (segment.x === newObstacle.x && segment.y === newObstacle.y) { |
| overlapping = true; |
| break; |
| } |
| } |
| |
| |
| for (const food of game.food) { |
| if (food.x === newObstacle.x && food.y === newObstacle.y) { |
| overlapping = true; |
| break; |
| } |
| } |
| |
| |
| for (const obstacle of game.obstacles) { |
| if (obstacle.x === newObstacle.x && obstacle.y === newObstacle.y) { |
| overlapping = true; |
| break; |
| } |
| } |
| } while (overlapping); |
| |
| game.obstacles.push(newObstacle); |
| } |
| } |
| |
| |
| function moveSnake() { |
| if (!game.isRunning || game.isPaused) return; |
| |
| |
| game.direction = game.nextDirection; |
| |
| |
| const head = {...game.snake[0]}; |
| |
| switch (game.direction) { |
| case 'up': |
| head.y -= game.gridSize; |
| break; |
| case 'down': |
| head.y += game.gridSize; |
| break; |
| case 'left': |
| head.x -= game.gridSize; |
| break; |
| case 'right': |
| head.x += game.gridSize; |
| break; |
| } |
| |
| |
| if (game.wallCollision) { |
| if (head.x < 0 || head.x >= game.width || head.y < 0 || head.y >= game.height) { |
| gameOver(); |
| return; |
| } |
| } else { |
| |
| if (head.x < 0) head.x = game.width - game.gridSize; |
| if (head.x >= game.width) head.x = 0; |
| if (head.y < 0) head.y = game.height - game.gridSize; |
| if (head.y >= game.height) head.y = 0; |
| } |
| |
| |
| for (let i = 0; i < game.snake.length; i++) { |
| if (head.x === game.snake[i].x && head.y === game.snake[i].y) { |
| gameOver(); |
| return; |
| } |
| } |
| |
| |
| if (game.obstaclesEnabled) { |
| for (const obstacle of game.obstacles) { |
| if (head.x === obstacle.x && head.y === obstacle.y) { |
| gameOver(); |
| return; |
| } |
| } |
| } |
| |
| |
| game.snake.unshift(head); |
| |
| |
| let ateFood = false; |
| for (let i = 0; i < game.food.length; i++) { |
| if (head.x === game.food[i].x && head.y === game.food[i].y) { |
| |
| game.food.splice(i, 1); |
| |
| |
| game.score += 10; |
| updateScore(); |
| |
| |
| createFood(); |
| |
| ateFood = true; |
| break; |
| } |
| } |
| |
| |
| if (!ateFood && !game.growMode) { |
| game.snake.pop(); |
| } |
| |
| |
| drawGame(); |
| } |
| |
| |
| function drawGame() { |
| |
| game.ctx.clearRect(0, 0, game.width, game.height); |
| |
| |
| for (let i = 0; i < game.snake.length; i++) { |
| const segment = game.snake[i]; |
| game.ctx.fillStyle = i === 0 ? colors.snake : colors.snake; |
| game.ctx.fillRect(segment.x, segment.y, game.gridSize, game.gridSize); |
| |
| |
| if (i === 0) { |
| game.ctx.fillStyle = '#047857'; |
| game.ctx.fillRect(segment.x + 2, segment.y + 2, game.gridSize - 4, game.gridSize - 4); |
| } else { |
| game.ctx.fillStyle = '#065F46'; |
| game.ctx.fillRect(segment.x + 1, segment.y + 1, game.gridSize - 2, game.gridSize - 2); |
| } |
| } |
| |
| |
| for (const food of game.food) { |
| game.ctx.fillStyle = colors.food; |
| game.ctx.beginPath(); |
| game.ctx.arc(food.x + game.gridSize/2, food.y + game.gridSize/2, game.gridSize/2 - 1, 0, Math.PI * 2); |
| game.ctx.fill(); |
| |
| |
| game.ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; |
| game.ctx.beginPath(); |
| game.ctx.arc(food.x + game.gridSize/3, food.y + game.gridSize/3, game.gridSize/6, 0, Math.PI * 2); |
| game.ctx.fill(); |
| } |
| |
| |
| for (const obstacle of game.obstacles) { |
| game.ctx.fillStyle = colors.obstacle; |
| game.ctx.fillRect(obstacle.x, obstacle.y, game.gridSize, game.gridSize); |
| |
| |
| game.ctx.fillStyle = '#4B5563'; |
| game.ctx.fillRect(obstacle.x + 2, obstacle.y + 2, game.gridSize - 4, game.gridSize - 4); |
| } |
| } |
| |
| |
| function gameOver() { |
| game.isRunning = false; |
| clearInterval(game.gameLoop); |
| |
| |
| elements.finalScore.textContent = game.score; |
| elements.gameOver.classList.remove('hidden'); |
| } |
| |
| |
| function startGame() { |
| if (game.isRunning) return; |
| |
| game.isRunning = true; |
| game.isPaused = false; |
| |
| |
| game.gameLoop = setInterval(moveSnake, 300 - game.speed); |
| } |
| |
| |
| function pauseGame() { |
| if (!game.isRunning) return; |
| |
| game.isPaused = !game.isPaused; |
| |
| if (game.isPaused) { |
| clearInterval(game.gameLoop); |
| elements.pauseBtn.innerHTML = '<i class="fas fa-play mr-2"></i>继续'; |
| } else { |
| game.gameLoop = setInterval(moveSnake, 300 - game.speed); |
| elements.pauseBtn.innerHTML = '<i class="fas fa-pause mr-2"></i>暂停'; |
| } |
| } |
| |
| |
| function updateScore() { |
| elements.scoreDisplay.textContent = `分数: ${game.score}`; |
| } |
| |
| |
| function updateCSSVariables() { |
| document.documentElement.style.setProperty('--grid-size', `${game.gridSize}px`); |
| document.documentElement.style.setProperty('--grid-color', colors.grid); |
| document.documentElement.style.setProperty('--snake-color', colors.snake); |
| document.documentElement.style.setProperty('--food-color', colors.food); |
| document.documentElement.style.setProperty('--obstacle-color', colors.obstacle); |
| document.documentElement.style.setProperty('--bg-color', colors.bg); |
| document.documentElement.style.setProperty('--speed', game.speed); |
| } |
| |
| |
| function applySettings() { |
| |
| game.speed = parseInt(elements.speedSlider.value); |
| game.wallCollision = elements.wallCollision.checked; |
| game.obstaclesEnabled = elements.obstacles.checked; |
| game.growMode = elements.growMode.checked; |
| game.initialLength = parseInt(elements.initialLength.value); |
| game.foodCount = parseInt(elements.foodCount.value); |
| |
| |
| if (game.speed < 100) { |
| elements.speedValue.textContent = '慢'; |
| } else if (game.speed < 200) { |
| elements.speedValue.textContent = '中等'; |
| } else { |
| elements.speedValue.textContent = '快'; |
| } |
| |
| |
| elements.lengthValue.textContent = game.initialLength; |
| |
| |
| elements.foodCountValue.textContent = game.foodCount; |
| |
| |
| initGame(); |
| |
| |
| elements.settingsPanel.classList.add('hidden'); |
| } |
| |
| |
| function setupEventListeners() { |
| |
| document.addEventListener('keydown', (e) => { |
| if (!game.isRunning) return; |
| |
| switch (e.key) { |
| case 'ArrowUp': |
| case 'w': |
| case 'W': |
| if (game.direction !== 'down') game.nextDirection = 'up'; |
| break; |
| case 'ArrowDown': |
| case 's': |
| case 'S': |
| if (game.direction !== 'up') game.nextDirection = 'down'; |
| break; |
| case 'ArrowLeft': |
| case 'a': |
| case 'A': |
| if (game.direction !== 'right') game.nextDirection = 'left'; |
| break; |
| case 'ArrowRight': |
| case 'd': |
| case 'D': |
| if (game.direction !== 'left') game.nextDirection = 'right'; |
| break; |
| } |
| }); |
| |
| |
| elements.startBtn.addEventListener('click', startGame); |
| elements.pauseBtn.addEventListener('click', pauseGame); |
| elements.resetBtn.addEventListener('click', resetGame); |
| elements.restartBtn.addEventListener('click', resetGame); |
| |
| |
| elements.settingsToggle.addEventListener('click', () => { |
| elements.settingsPanel.classList.remove('hidden'); |
| }); |
| |
| elements.closeSettings.addEventListener('click', () => { |
| elements.settingsPanel.classList.add('hidden'); |
| }); |
| |
| elements.applySettings.addEventListener('click', applySettings); |
| |
| |
| elements.gridSizeBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| elements.gridSizeBtns.forEach(b => b.classList.remove('bg-green-600')); |
| btn.classList.add('bg-green-600'); |
| game.gridSize = parseInt(btn.dataset.size); |
| initGame(); |
| }); |
| }); |
| |
| |
| elements.colorPreviews.forEach(preview => { |
| preview.addEventListener('click', () => { |
| const color = preview.dataset.color; |
| const role = preview.parentElement.querySelector('label').textContent; |
| |
| switch (role) { |
| case '蛇的颜色': |
| colors.snake = color; |
| break; |
| case '食物颜色': |
| colors.food = color; |
| break; |
| case '网格颜色': |
| colors.grid = color; |
| break; |
| case '背景颜色': |
| colors.bg = color; |
| break; |
| } |
| |
| updateCSSVariables(); |
| drawGame(); |
| }); |
| }); |
| |
| |
| elements.speedSlider.addEventListener('input', () => { |
| const speed = parseInt(elements.speedSlider.value); |
| if (speed < 100) { |
| elements.speedValue.textContent = '慢'; |
| } else if (speed < 200) { |
| elements.speedValue.textContent = '中等'; |
| } else { |
| elements.speedValue.textContent = '快'; |
| } |
| }); |
| |
| elements.initialLength.addEventListener('input', () => { |
| elements.lengthValue.textContent = elements.initialLength.value; |
| }); |
| |
| elements.foodCount.addEventListener('input', () => { |
| elements.foodCountValue.textContent = elements.foodCount.value; |
| }); |
| |
| |
| elements.fullscreenBtn.addEventListener('click', toggleFullscreen); |
| |
| |
| elements.upBtn.addEventListener('click', () => { |
| if (game.direction !== 'down') game.nextDirection = 'up'; |
| }); |
| |
| elements.downBtn.addEventListener('click', () => { |
| if (game.direction !== 'up') game.nextDirection = 'down'; |
| }); |
| |
| elements.leftBtn.addEventListener('click', () => { |
| if (game.direction !== 'right') game.nextDirection = 'left'; |
| }); |
| |
| elements.rightBtn.addEventListener('click', () => { |
| if (game.direction !== 'left') game.nextDirection = 'right'; |
| }); |
| } |
| |
| |
| window.addEventListener('load', () => { |
| initGame(); |
| setupEventListeners(); |
| }); |
| |
| |
| function toggleFullscreen() { |
| if (!document.fullscreenElement) { |
| elements.gameContainer.requestFullscreen().catch(err => { |
| console.error(`全屏错误: ${err.message}`); |
| }); |
| elements.fullscreenBtn.innerHTML = '<i class="fas fa-compress"></i>'; |
| elements.fullscreenBtn.setAttribute('title', '退出全屏'); |
| } else { |
| document.exitFullscreen(); |
| elements.fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>'; |
| elements.fullscreenBtn.setAttribute('title', '全屏'); |
| } |
| } |
| |
| |
| document.addEventListener('fullscreenchange', () => { |
| if (!document.fullscreenElement) { |
| elements.fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>'; |
| elements.fullscreenBtn.setAttribute('title', '全屏'); |
| } |
| }); |
| |
| |
| window.addEventListener('resize', initGame); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=erdes/snake" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |