| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Neon Snake Game</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); |
| |
| body { |
| font-family: 'Press Start 2P', cursive; |
| background-color: #0f172a; |
| overflow: hidden; |
| } |
| |
| .game-container { |
| position: relative; |
| } |
| |
| .snake-segment { |
| position: absolute; |
| border-radius: 4px; |
| transition: all 0.1s ease; |
| } |
| |
| .food { |
| position: absolute; |
| border-radius: 50%; |
| animation: pulse 1s infinite alternate; |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(1); } |
| 100% { transform: scale(1.2); } |
| } |
| |
| .game-over { |
| animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both; |
| } |
| |
| @keyframes shake { |
| 10%, 90% { transform: translateX(-1px); } |
| 20%, 80% { transform: translateX(2px); } |
| 30%, 50%, 70% { transform: translateX(-4px); } |
| 40%, 60% { transform: translateX(4px); } |
| } |
| |
| .controls-btn { |
| transition: all 0.2s ease; |
| } |
| |
| .controls-btn:active { |
| transform: scale(0.9); |
| filter: brightness(1.2); |
| } |
| </style> |
| </head> |
| <body class="min-h-screen flex flex-col items-center justify-center p-4"> |
| <div class="text-center mb-6"> |
| <h1 class="text-4xl md:text-5xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 to-blue-500 mb-2"> |
| NEON SNAKE |
| </h1> |
| <div class="flex justify-center items-center gap-4 mb-4"> |
| <div class="text-green-400"> |
| Score: <span id="score" class="text-white">0</span> |
| </div> |
| <div class="text-purple-400"> |
| High Score: <span id="high-score" class="text-white">0</span> |
| </div> |
| </div> |
| </div> |
| |
| <div class="game-container relative bg-gray-900 rounded-lg shadow-2xl shadow-blue-500/20 border border-gray-800 overflow-hidden" |
| style="width: min(90vw, 500px); height: min(90vw, 500px);"> |
| <canvas id="game-canvas" class="w-full h-full"></canvas> |
| |
| <div id="start-screen" class="absolute inset-0 flex flex-col items-center justify-center bg-black bg-opacity-80 p-4"> |
| <div class="text-center"> |
| <h2 class="text-2xl md:text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 to-blue-500 mb-6"> |
| NEON SNAKE |
| </h2> |
| <p class="text-gray-300 mb-8 text-sm md:text-base"> |
| Use arrow keys or swipe to control the snake.<br> |
| Eat the glowing food to grow! |
| </p> |
| <button id="start-btn" class="px-8 py-3 bg-gradient-to-r from-green-500 to-blue-600 rounded-full font-bold text-white shadow-lg hover:shadow-green-500/30 transition-all"> |
| START GAME |
| </button> |
| </div> |
| </div> |
| |
| <div id="game-over-screen" class="absolute inset-0 hidden flex-col items-center justify-center bg-black bg-opacity-80 p-4"> |
| <div class="text-center"> |
| <h2 class="text-2xl md:text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-red-400 to-pink-500 mb-2"> |
| GAME OVER! |
| </h2> |
| <p class="text-gray-300 mb-6">Your score: <span id="final-score" class="text-white">0</span></p> |
| <div class="flex gap-4 justify-center"> |
| <button id="restart-btn" class="px-6 py-2 bg-gradient-to-r from-green-500 to-blue-600 rounded-full font-bold text-white text-sm"> |
| PLAY AGAIN |
| </button> |
| <button id="menu-btn" class="px-6 py-2 bg-gradient-to-r from-purple-500 to-pink-600 rounded-full font-bold text-white text-sm"> |
| MAIN MENU |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="mt-8 md:hidden"> |
| <div class="grid grid-cols-3 gap-2 w-48 h-48"> |
| <div></div> |
| <button id="up-btn" class="controls-btn bg-gray-800 text-white rounded-lg flex items-center justify-center p-4"> |
| ↑ |
| </button> |
| <div></div> |
| <button id="left-btn" class="controls-btn bg-gray-800 text-white rounded-lg flex items-center justify-center p-4"> |
| ← |
| </button> |
| <button id="down-btn" class="controls-btn bg-gray-800 text-white rounded-lg flex items-center justify-center p-4"> |
| ↓ |
| </button> |
| <button id="right-btn" class="controls-btn bg-gray-800 text-white rounded-lg flex items-center justify-center p-4"> |
| → |
| </button> |
| </div> |
| </div> |
| |
| <div class="mt-8 text-gray-400 text-xs text-center"> |
| <p>Use arrow keys or on-screen controls to play</p> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| const canvas = document.getElementById('game-canvas'); |
| const ctx = canvas.getContext('2d'); |
| const startScreen = document.getElementById('start-screen'); |
| const gameOverScreen = document.getElementById('game-over-screen'); |
| const startBtn = document.getElementById('start-btn'); |
| const restartBtn = document.getElementById('restart-btn'); |
| const menuBtn = document.getElementById('menu-btn'); |
| const scoreDisplay = document.getElementById('score'); |
| const highScoreDisplay = document.getElementById('high-score'); |
| const finalScoreDisplay = document.getElementById('final-score'); |
| |
| |
| const upBtn = document.getElementById('up-btn'); |
| const downBtn = document.getElementById('down-btn'); |
| const leftBtn = document.getElementById('left-btn'); |
| const rightBtn = document.getElementById('right-btn'); |
| |
| |
| let snake = []; |
| let food = {}; |
| let direction = 'right'; |
| let nextDirection = 'right'; |
| let gameSpeed = 150; |
| let score = 0; |
| let highScore = localStorage.getItem('snakeHighScore') || 0; |
| let gameLoop; |
| let gridSize = 20; |
| let canvasSize = Math.min(500, window.innerWidth * 0.9); |
| let tileSize = canvasSize / gridSize; |
| let touchStartX = 0; |
| let touchStartY = 0; |
| |
| |
| canvas.width = canvasSize; |
| canvas.height = canvasSize; |
| |
| |
| highScoreDisplay.textContent = highScore; |
| |
| |
| function initGame() { |
| |
| snake = [ |
| {x: 5, y: 10}, |
| {x: 4, y: 10}, |
| {x: 3, y: 10} |
| ]; |
| |
| |
| direction = 'right'; |
| nextDirection = 'right'; |
| |
| |
| score = 0; |
| scoreDisplay.textContent = score; |
| |
| |
| generateFood(); |
| |
| |
| if (gameLoop) clearInterval(gameLoop); |
| gameLoop = setInterval(gameStep, gameSpeed); |
| } |
| |
| |
| function generateFood() { |
| let foodPosition; |
| let overlapping; |
| |
| do { |
| overlapping = false; |
| foodPosition = { |
| x: Math.floor(Math.random() * gridSize), |
| y: Math.floor(Math.random() * gridSize) |
| }; |
| |
| |
| for (let segment of snake) { |
| if (segment.x === foodPosition.x && segment.y === foodPosition.y) { |
| overlapping = true; |
| break; |
| } |
| } |
| } while (overlapping); |
| |
| food = foodPosition; |
| } |
| |
| |
| function gameStep() { |
| |
| direction = nextDirection; |
| |
| |
| const head = {x: snake[0].x, y: snake[0].y}; |
| |
| switch (direction) { |
| case 'up': |
| head.y -= 1; |
| break; |
| case 'down': |
| head.y += 1; |
| break; |
| case 'left': |
| head.x -= 1; |
| break; |
| case 'right': |
| head.x += 1; |
| break; |
| } |
| |
| |
| if ( |
| head.x < 0 || head.x >= gridSize || |
| head.y < 0 || head.y >= gridSize || |
| snake.some(segment => segment.x === head.x && segment.y === head.y) |
| ) { |
| gameOver(); |
| return; |
| } |
| |
| |
| snake.unshift(head); |
| |
| |
| if (head.x === food.x && head.y === food.y) { |
| |
| score += 10; |
| scoreDisplay.textContent = score; |
| |
| |
| generateFood(); |
| |
| |
| if (score % 50 === 0 && gameSpeed > 50) { |
| gameSpeed -= 5; |
| clearInterval(gameLoop); |
| gameLoop = setInterval(gameStep, gameSpeed); |
| } |
| } else { |
| |
| snake.pop(); |
| } |
| |
| |
| drawGame(); |
| } |
| |
| |
| function drawGame() { |
| |
| ctx.fillStyle = '#0f172a'; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| |
| ctx.strokeStyle = '#1e293b'; |
| ctx.lineWidth = 0.5; |
| |
| for (let i = 0; i <= gridSize; i++) { |
| |
| ctx.beginPath(); |
| ctx.moveTo(i * tileSize, 0); |
| ctx.lineTo(i * tileSize, canvas.height); |
| ctx.stroke(); |
| |
| |
| ctx.beginPath(); |
| ctx.moveTo(0, i * tileSize); |
| ctx.lineTo(canvas.width, i * tileSize); |
| ctx.stroke(); |
| } |
| |
| |
| snake.forEach((segment, index) => { |
| const isHead = index === 0; |
| const gradient = ctx.createLinearGradient( |
| segment.x * tileSize, |
| segment.y * tileSize, |
| segment.x * tileSize + tileSize, |
| segment.y * tileSize + tileSize |
| ); |
| |
| if (isHead) { |
| gradient.addColorStop(0, '#10b981'); |
| gradient.addColorStop(1, '#3b82f6'); |
| } else { |
| gradient.addColorStop(0, '#3b82f6'); |
| gradient.addColorStop(1, '#8b5cf6'); |
| } |
| |
| ctx.fillStyle = gradient; |
| ctx.shadowBlur = isHead ? 10 : 5; |
| ctx.shadowColor = isHead ? '#10b981' : '#8b5cf6'; |
| |
| |
| const radius = isHead ? tileSize / 2 : |
| index === snake.length - 1 ? tileSize / 4 : |
| tileSize / 8; |
| |
| roundRect( |
| ctx, |
| segment.x * tileSize + 1, |
| segment.y * tileSize + 1, |
| tileSize - 2, |
| tileSize - 2, |
| radius, |
| true, |
| false |
| ); |
| |
| ctx.shadowBlur = 0; |
| }); |
| |
| |
| const foodGradient = ctx.createRadialGradient( |
| food.x * tileSize + tileSize / 2, |
| food.y * tileSize + tileSize / 2, |
| 0, |
| food.x * tileSize + tileSize / 2, |
| food.y * tileSize + tileSize / 2, |
| tileSize / 2 |
| ); |
| |
| foodGradient.addColorStop(0, '#f43f5e'); |
| foodGradient.addColorStop(1, '#f97316'); |
| |
| ctx.fillStyle = foodGradient; |
| ctx.shadowBlur = 15; |
| ctx.shadowColor = '#f43f5e'; |
| |
| ctx.beginPath(); |
| ctx.arc( |
| food.x * tileSize + tileSize / 2, |
| food.y * tileSize + tileSize / 2, |
| tileSize / 2 - 2, |
| 0, |
| Math.PI * 2 |
| ); |
| ctx.fill(); |
| ctx.shadowBlur = 0; |
| } |
| |
| |
| function roundRect(ctx, x, y, width, height, radius, fill, stroke) { |
| ctx.beginPath(); |
| ctx.moveTo(x + radius, y); |
| ctx.lineTo(x + width - radius, y); |
| ctx.quadraticCurveTo(x + width, y, x + width, y + radius); |
| ctx.lineTo(x + width, y + height - radius); |
| ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); |
| ctx.lineTo(x + radius, y + height); |
| ctx.quadraticCurveTo(x, y + height, x, y + height - radius); |
| ctx.lineTo(x, y + radius); |
| ctx.quadraticCurveTo(x, y, x + radius, y); |
| ctx.closePath(); |
| if (fill) ctx.fill(); |
| if (stroke) ctx.stroke(); |
| } |
| |
| |
| function gameOver() { |
| clearInterval(gameLoop); |
| |
| |
| if (score > highScore) { |
| highScore = score; |
| localStorage.setItem('snakeHighScore', highScore); |
| highScoreDisplay.textContent = highScore; |
| } |
| |
| |
| finalScoreDisplay.textContent = score; |
| gameOverScreen.classList.remove('hidden'); |
| gameOverScreen.classList.add('game-over', 'flex'); |
| } |
| |
| |
| startBtn.addEventListener('click', () => { |
| startScreen.classList.add('hidden'); |
| initGame(); |
| }); |
| |
| restartBtn.addEventListener('click', () => { |
| gameOverScreen.classList.add('hidden'); |
| gameOverScreen.classList.remove('flex'); |
| initGame(); |
| }); |
| |
| menuBtn.addEventListener('click', () => { |
| gameOverScreen.classList.add('hidden'); |
| gameOverScreen.classList.remove('flex'); |
| startScreen.classList.remove('hidden'); |
| }); |
| |
| |
| document.addEventListener('keydown', (e) => { |
| 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; |
| } |
| }); |
| |
| |
| upBtn.addEventListener('click', () => { |
| if (direction !== 'down') nextDirection = 'up'; |
| }); |
| |
| downBtn.addEventListener('click', () => { |
| if (direction !== 'up') nextDirection = 'down'; |
| }); |
| |
| leftBtn.addEventListener('click', () => { |
| if (direction !== 'right') nextDirection = 'left'; |
| }); |
| |
| rightBtn.addEventListener('click', () => { |
| if (direction !== 'left') nextDirection = 'right'; |
| }); |
| |
| |
| canvas.addEventListener('touchstart', (e) => { |
| touchStartX = e.touches[0].clientX; |
| touchStartY = e.touches[0].clientY; |
| }, false); |
| |
| canvas.addEventListener('touchmove', (e) => { |
| e.preventDefault(); |
| |
| if (!touchStartX || !touchStartY) return; |
| |
| const touchEndX = e.touches[0].clientX; |
| const touchEndY = e.touches[0].clientY; |
| |
| const diffX = touchStartX - touchEndX; |
| const diffY = touchStartY - touchEndY; |
| |
| |
| if (Math.abs(diffX) > Math.abs(diffY)) { |
| |
| if (diffX > 0) { |
| |
| if (direction !== 'right') nextDirection = 'left'; |
| } else { |
| |
| if (direction !== 'left') nextDirection = 'right'; |
| } |
| } else { |
| |
| if (diffY > 0) { |
| |
| if (direction !== 'down') nextDirection = 'up'; |
| } else { |
| |
| if (direction !== 'up') nextDirection = 'down'; |
| } |
| } |
| |
| |
| touchStartX = 0; |
| touchStartY = 0; |
| }, false); |
| |
| |
| drawGame(); |
| }); |
| </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/tanchishe" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |