Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Snake Game - Direction Pad Control</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
| <style> | |
| .game-container { | |
| position: relative; | |
| background-color: #1a202c; | |
| border-radius: 0.5rem; | |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); | |
| } | |
| .game-grid { | |
| display: grid; | |
| grid-template-columns: repeat(var(--grid-size), 1fr); | |
| grid-template-rows: repeat(var(--grid-size), 1fr); | |
| gap: 1px; | |
| background-color: #2d3748; | |
| } | |
| .grid-cell { | |
| aspect-ratio: 1/1; | |
| background-color: #1a202c; | |
| position: relative; | |
| } | |
| .snake { | |
| background-color: #48bb78; | |
| border-radius: 20%; | |
| transition: all 0.1s ease; | |
| z-index: 2; | |
| position: relative; | |
| } | |
| .snake-head { | |
| background-color: #38a169; | |
| border-radius: 30%; | |
| } | |
| .food { | |
| background-color: #f56565; | |
| border-radius: 50%; | |
| animation: pulse 1.5s infinite; | |
| z-index: 2; | |
| position: relative; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(0.95); } | |
| 50% { transform: scale(1.1); } | |
| 100% { transform: scale(0.95); } | |
| } | |
| .game-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| background-color: rgba(26, 32, 44, 0.9); | |
| border-radius: 0.5rem; | |
| z-index: 10; | |
| } | |
| .dpad-container { | |
| position: relative; | |
| width: 150px; | |
| height: 150px; | |
| margin: 20px auto; | |
| } | |
| .dpad-outer { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| border-radius: 50%; | |
| background: rgba(72, 187, 120, 0.1); | |
| border: 2px solid rgba(72, 187, 120, 0.3); | |
| } | |
| .dpad-center { | |
| position: absolute; | |
| width: 50px; | |
| height: 50px; | |
| background: rgba(72, 187, 120, 0.2); | |
| border-radius: 50%; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| } | |
| .dpad-direction { | |
| position: absolute; | |
| width: 40px; | |
| height: 40px; | |
| background: rgba(72, 187, 120, 0.3); | |
| border-radius: 50%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| font-size: 16px; | |
| transition: all 0.1s ease; | |
| cursor: pointer; | |
| } | |
| .dpad-direction:active { | |
| transform: scale(1.3); | |
| background: rgba(72, 187, 120, 0.6); | |
| } | |
| .dpad-up { | |
| top: 10px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| } | |
| .dpad-down { | |
| bottom: 10px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| } | |
| .dpad-left { | |
| left: 10px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| } | |
| .dpad-right { | |
| right: 10px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| } | |
| .mobile-controls { | |
| display: none; | |
| } | |
| @media (max-width: 640px) { | |
| .mobile-controls { | |
| display: block; | |
| } | |
| .desktop-instructions { | |
| display: none; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div class="max-w-md w-full"> | |
| <h1 class="text-3xl font-bold text-center text-green-400 mb-2">🐍 方向键控制贪吃蛇</h1> | |
| <div class="flex justify-between items-center mb-4"> | |
| <div class="text-white font-semibold"> | |
| 分数: <span id="score" class="text-green-400">0</span> | |
| </div> | |
| <div class="text-white font-semibold"> | |
| 最高分: <span id="high-score" class="text-yellow-400">0</span> | |
| </div> | |
| </div> | |
| <div class="game-container"> | |
| <div id="game-board" class="game-grid w-full" style="--grid-size: 20;"></div> | |
| <div id="game-overlay" class="game-overlay"> | |
| <h2 class="text-3xl font-bold text-white mb-4" id="overlay-title">方向键控制贪吃蛇</h2> | |
| <p class="text-gray-300 mb-6 text-center px-4" id="overlay-message"> | |
| 使用键盘方向键或下方控制盘控制蛇的移动方向 | |
| </p> | |
| <button id="start-btn" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-6 rounded-full transition"> | |
| 开始游戏 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Direction pad for mobile/touch devices --> | |
| <div class="mobile-controls"> | |
| <div class="dpad-container"> | |
| <div class="dpad-outer"></div> | |
| <div class="dpad-center"></div> | |
| <div class="dpad-direction dpad-up" id="btn-touch-up"> | |
| <i class="fas fa-arrow-up"></i> | |
| </div> | |
| <div class="dpad-direction dpad-down" id="btn-touch-down"> | |
| <i class="fas fa-arrow-down"></i> | |
| </div> | |
| <div class="dpad-direction dpad-left" id="btn-touch-left"> | |
| <i class="fas fa-arrow-left"></i> | |
| </div> | |
| <div class="dpad-direction dpad-right" id="btn-touch-right"> | |
| <i class="fas fa-arrow-right"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-4 text-center text-gray-400 text-sm desktop-instructions"> | |
| <p>使用键盘方向键控制方向 | 按空格键暂停</p> | |
| </div> | |
| </div> | |
| <script> | |
| // Game configuration | |
| const config = { | |
| gridSize: 20, | |
| initialSpeed: 150, | |
| speedIncrease: 5, | |
| maxSpeed: 80, | |
| minSpeed: 40 | |
| }; | |
| // Game state | |
| let snake = []; | |
| let food = {}; | |
| let direction = {x: 1, y: 0}; // Initial direction: right | |
| let nextDirection = {x: 1, y: 0}; | |
| let score = 0; | |
| let highScore = localStorage.getItem('snakeHighScore') || 0; | |
| let gameInterval; | |
| let isPaused = false; | |
| let gameStarted = false; | |
| let gameSpeed = config.initialSpeed; | |
| // DOM elements | |
| const gameBoard = document.getElementById('game-board'); | |
| const gameOverlay = document.getElementById('game-overlay'); | |
| const overlayTitle = document.getElementById('overlay-title'); | |
| const overlayMessage = document.getElementById('overlay-message'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const scoreElement = document.getElementById('score'); | |
| const highScoreElement = document.getElementById('high-score'); | |
| // Touch direction buttons | |
| const btnTouchUp = document.getElementById('btn-touch-up'); | |
| const btnTouchDown = document.getElementById('btn-touch-down'); | |
| const btnTouchLeft = document.getElementById('btn-touch-left'); | |
| const btnTouchRight = document.getElementById('btn-touch-right'); | |
| // Initialize the game | |
| function initGame() { | |
| // Set high score | |
| highScoreElement.textContent = highScore; | |
| // Create grid cells | |
| gameBoard.innerHTML = ''; | |
| gameBoard.style.setProperty('--grid-size', config.gridSize); | |
| for (let i = 0; i < config.gridSize * config.gridSize; i++) { | |
| const cell = document.createElement('div'); | |
| cell.classList.add('grid-cell'); | |
| cell.dataset.index = i; | |
| // Get position from index | |
| const x = i % config.gridSize; | |
| const y = Math.floor(i / config.gridSize); | |
| cell.dataset.x = x; | |
| cell.dataset.y = y; | |
| gameBoard.appendChild(cell); | |
| } | |
| // Event listeners | |
| startBtn.addEventListener('click', startGame); | |
| document.addEventListener('keydown', handleKeyDown); | |
| // Touch direction event listeners | |
| btnTouchUp.addEventListener('mousedown', () => changeDirection(0, -1)); | |
| btnTouchUp.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| changeDirection(0, -1); | |
| }); | |
| btnTouchDown.addEventListener('mousedown', () => changeDirection(0, 1)); | |
| btnTouchDown.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| changeDirection(0, 1); | |
| }); | |
| btnTouchLeft.addEventListener('mousedown', () => changeDirection(-1, 0)); | |
| btnTouchLeft.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| changeDirection(-1, 0); | |
| }); | |
| btnTouchRight.addEventListener('mousedown', () => changeDirection(1, 0)); | |
| btnTouchRight.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| changeDirection(1, 0); | |
| }); | |
| // Show start screen | |
| showOverlay('方向键控制贪吃蛇', '使用键盘方向键或下方控制盘控制蛇的移动方向', '开始游戏'); | |
| } | |
| // Change direction | |
| function changeDirection(x, y) { | |
| // Prevent 180-degree turn (snake can't reverse direction) | |
| if ((x !== -direction.x || y !== -direction.y) && | |
| (x !== 0 || y !== 0)) { // Ignore if both are 0 | |
| nextDirection = {x, y}; | |
| } | |
| } | |
| // Start the game | |
| function startGame() { | |
| // Reset game state | |
| snake = [ | |
| {x: 5, y: 10}, | |
| {x: 4, y: 10}, | |
| {x: 3, y: 10} | |
| ]; | |
| direction = {x: 1, y: 0}; // Initial direction: right | |
| nextDirection = {x: 1, y: 0}; | |
| score = 0; | |
| gameSpeed = config.initialSpeed; | |
| scoreElement.textContent = score; | |
| // Generate first food | |
| generateFood(); | |
| // Hide overlay | |
| hideOverlay(); | |
| // Start game loop | |
| if (gameInterval) clearInterval(gameInterval); | |
| gameInterval = setInterval(gameLoop, gameSpeed); | |
| gameStarted = true; | |
| isPaused = false; | |
| } | |
| // Game loop | |
| function gameLoop() { | |
| if (isPaused) return; | |
| // Update direction at the start of each move | |
| direction = {...nextDirection}; | |
| // Move snake | |
| const head = {...snake[0]}; | |
| head.x += direction.x; | |
| head.y += direction.y; | |
| // Check for collisions | |
| if ( | |
| head.x < 0 || head.x >= config.gridSize || | |
| head.y < 0 || head.y >= config.gridSize || | |
| snake.some((segment, index) => index > 0 && segment.x === head.x && segment.y === head.y) | |
| ) { | |
| gameOver(); | |
| return; | |
| } | |
| // Add new head | |
| snake.unshift(head); | |
| // Check for food | |
| if (head.x === food.x && head.y === food.y) { | |
| // Increase score | |
| score += 10; | |
| scoreElement.textContent = score; | |
| // Increase speed (up to a max) | |
| if (gameSpeed > config.maxSpeed) { | |
| gameSpeed -= config.speedIncrease; | |
| clearInterval(gameInterval); | |
| gameInterval = setInterval(gameLoop, gameSpeed); | |
| } | |
| // Generate new food | |
| generateFood(); | |
| } else { | |
| // Remove tail if no food eaten | |
| snake.pop(); | |
| } | |
| // Render the game | |
| renderGame(); | |
| } | |
| // Render the game | |
| function renderGame() { | |
| // Clear all cells | |
| document.querySelectorAll('.grid-cell').forEach(cell => { | |
| cell.classList.remove('snake', 'snake-head', 'food'); | |
| }); | |
| // Render snake | |
| snake.forEach((segment, index) => { | |
| const cellIndex = segment.y * config.gridSize + segment.x; | |
| const cell = document.querySelector(`.grid-cell[data-index="${cellIndex}"]`); | |
| if (cell) { | |
| cell.classList.add(index === 0 ? 'snake-head' : 'snake'); | |
| } | |
| }); | |
| // Render food | |
| const foodIndex = food.y * config.gridSize + food.x; | |
| const foodCell = document.querySelector(`.grid-cell[data-index="${foodIndex}"]`); | |
| if (foodCell) { | |
| foodCell.classList.add('food'); | |
| } | |
| } | |
| // Generate food at random position | |
| function generateFood() { | |
| let newFood; | |
| do { | |
| newFood = { | |
| x: Math.floor(Math.random() * config.gridSize), | |
| y: Math.floor(Math.random() * config.gridSize) | |
| }; | |
| } while (snake.some(segment => segment.x === newFood.x && segment.y === newFood.y)); | |
| food = newFood; | |
| } | |
| // Game over | |
| function gameOver() { | |
| clearInterval(gameInterval); | |
| gameStarted = false; | |
| // Update high score if needed | |
| if (score > highScore) { | |
| highScore = score; | |
| localStorage.setItem('snakeHighScore', highScore); | |
| highScoreElement.textContent = highScore; | |
| } | |
| showOverlay('游戏结束', `你的分数: ${score}`, '再玩一次'); | |
| } | |
| // Handle keyboard input | |
| function handleKeyDown(e) { | |
| // Only process if game is started or starting the game | |
| if (!gameStarted && e.key === 'Enter') { | |
| startGame(); | |
| return; | |
| } | |
| if (!gameStarted) return; | |
| switch (e.key) { | |
| case ' ': | |
| togglePause(); | |
| break; | |
| case 'ArrowUp': | |
| changeDirection(0, -1); | |
| break; | |
| case 'ArrowDown': | |
| changeDirection(0, 1); | |
| break; | |
| case 'ArrowLeft': | |
| changeDirection(-1, 0); | |
| break; | |
| case 'ArrowRight': | |
| changeDirection(1, 0); | |
| break; | |
| } | |
| } | |
| // Toggle pause | |
| function togglePause() { | |
| if (!gameStarted) return; | |
| isPaused = !isPaused; | |
| if (isPaused) { | |
| showOverlay('游戏暂停', '按空格键继续', '继续'); | |
| } else { | |
| hideOverlay(); | |
| } | |
| } | |
| // Show overlay | |
| function showOverlay(title, message, buttonText) { | |
| overlayTitle.textContent = title; | |
| overlayMessage.textContent = message; | |
| startBtn.textContent = buttonText; | |
| gameOverlay.style.display = 'flex'; | |
| } | |
| // Hide overlay | |
| function hideOverlay() { | |
| gameOverlay.style.display = 'none'; | |
| } | |
| // Initialize the game when the page loads | |
| window.addEventListener('DOMContentLoaded', 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=DavidHouse2024/snake2" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
| </html> |