Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Neon Snake Adventure</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet"> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Orbitron', sans-serif; | |
| background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .game-container { | |
| position: relative; | |
| width: 500px; | |
| height: 500px; | |
| background-color: rgba(0, 0, 0, 0.3); | |
| border-radius: 10px; | |
| box-shadow: 0 0 30px rgba(255, 255, 255, 0.2); | |
| overflow: hidden; | |
| border: 2px solid rgba(255, 255, 255, 0.1); | |
| } | |
| canvas { | |
| display: block; | |
| border-radius: 8px; | |
| } | |
| .score-display { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| font-size: 1.5rem; | |
| color: #fff; | |
| z-index: 10; | |
| text-shadow: 0 0 10px #00f3ff; | |
| } | |
| .game-over { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| display: none; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 20; | |
| border-radius: 8px; | |
| } | |
| .game-over h2 { | |
| font-size: 3rem; | |
| margin-bottom: 20px; | |
| color: #ff4d4d; | |
| text-shadow: 0 0 15px #ff0000; | |
| animation: pulse 1.5s infinite; | |
| } | |
| .final-score { | |
| font-size: 1.8rem; | |
| margin-bottom: 30px; | |
| color: #00ff9d; | |
| text-shadow: 0 0 10px #00ffaa; | |
| } | |
| .btn { | |
| padding: 12px 30px; | |
| background: linear-gradient(45deg, #00c6ff, #0072ff); | |
| border: none; | |
| border-radius: 50px; | |
| color: white; | |
| font-size: 1.2rem; | |
| font-family: 'Orbitron', sans-serif; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); | |
| outline: none; | |
| } | |
| .btn:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); | |
| } | |
| .btn:active { | |
| transform: translateY(1px); | |
| } | |
| .controls { | |
| margin-top: 20px; | |
| text-align: center; | |
| } | |
| .controls p { | |
| margin-bottom: 10px; | |
| color: #ffffffaa; | |
| } | |
| .mobile-controls { | |
| display: none; | |
| grid-template-columns: repeat(3, 1fr); | |
| grid-template-rows: repeat(3, 1fr); | |
| gap: 10px; | |
| width: 200px; | |
| margin-top: 20px; | |
| } | |
| .mobile-btn { | |
| width: 60px; | |
| height: 60px; | |
| background-color: rgba(255, 255, 255, 0.1); | |
| border: none; | |
| border-radius: 50%; | |
| color: white; | |
| font-size: 1.5rem; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| cursor: pointer; | |
| user-select: none; | |
| } | |
| .mobile-btn:active { | |
| background-color: rgba(255, 255, 255, 0.3); | |
| } | |
| .up { | |
| grid-column: 2; | |
| grid-row: 1; | |
| } | |
| .down { | |
| grid-column: 2; | |
| grid-row: 3; | |
| } | |
| .left { | |
| grid-column: 1; | |
| grid-row: 2; | |
| } | |
| .right { | |
| grid-column: 3; | |
| grid-row: 2; | |
| } | |
| .title { | |
| font-size: 3rem; | |
| margin-bottom: 20px; | |
| background: linear-gradient(to right, #00f3ff, #00ff9d); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| text-shadow: 0 0 10px rgba(0, 255, 255, 0.3); | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| .particle { | |
| position: absolute; | |
| background-color: rgba(255, 255, 255, 0.7); | |
| border-radius: 50%; | |
| pointer-events: none; | |
| z-index: 5; | |
| } | |
| @media (max-width: 600px) { | |
| .game-container { | |
| width: 300px; | |
| height: 300px; | |
| } | |
| .mobile-controls { | |
| display: grid; | |
| } | |
| .controls p { | |
| display: none; | |
| } | |
| .title { | |
| font-size: 2rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1 class="title">NEON SNAKE</h1> | |
| <div class="game-container"> | |
| <div class="score-display">Score: 0</div> | |
| <canvas id="gameCanvas"></canvas> | |
| <div class="game-over"> | |
| <h2>GAME OVER</h2> | |
| <div class="final-score">Score: 0</div> | |
| <button class="btn" id="restartBtn">PLAY AGAIN</button> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <p>Use arrow keys to control the snake</p> | |
| <div class="mobile-controls"> | |
| <button class="mobile-btn up" id="upBtn">↑</button> | |
| <button class="mobile-btn down" id="downBtn">↓</button> | |
| <button class="mobile-btn left" id="leftBtn">←</button> | |
| <button class="mobile-btn right" id="rightBtn">→</button> | |
| </div> | |
| </div> | |
| <script> | |
| // Game variables | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const scoreDisplay = document.querySelector('.score-display'); | |
| const gameOverScreen = document.querySelector('.game-over'); | |
| const finalScoreDisplay = document.querySelector('.final-score'); | |
| const restartBtn = document.getElementById('restartBtn'); | |
| // Set canvas size | |
| canvas.width = canvas.parentElement.offsetWidth; | |
| canvas.height = canvas.parentElement.offsetHeight; | |
| // Game settings | |
| const gridSize = 20; | |
| const tileCountX = Math.floor(canvas.width / gridSize); | |
| const tileCountY = Math.floor(canvas.height / gridSize); | |
| // Game state | |
| let snake = []; | |
| let food = {}; | |
| let direction = 'right'; | |
| let nextDirection = 'right'; | |
| let score = 0; | |
| let gameSpeed = 120; | |
| let gameRunning = false; | |
| let gameLoop; | |
| let particles = []; | |
| // Sound effects | |
| const eatSound = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...'); // Short base64 encoded sound | |
| const gameOverSound = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...'); // Short base64 encoded sound | |
| // Initialize game | |
| function initGame() { | |
| snake = [ | |
| {x: 5, y: 10}, | |
| {x: 4, y: 10}, | |
| {x: 3, y: 10} | |
| ]; | |
| spawnFood(); | |
| direction = 'right'; | |
| nextDirection = 'right'; | |
| score = 0; | |
| gameSpeed = 120; | |
| scoreDisplay.textContent = `Score: ${score}`; | |
| particles = []; | |
| gameRunning = true; | |
| if (gameLoop) clearInterval(gameLoop); | |
| gameLoop = setInterval(gameStep, gameSpeed); | |
| } | |
| // Spawn food at random position | |
| function spawnFood() { | |
| food = { | |
| x: Math.floor(Math.random() * tileCountX), | |
| y: Math.floor(Math.random() * tileCountY) | |
| }; | |
| // Make sure food doesn't spawn on snake | |
| for (let segment of snake) { | |
| if (segment.x === food.x && segment.y === food.y) { | |
| return spawnFood(); | |
| } | |
| } | |
| createParticles(food.x * gridSize + gridSize/2, food.y * gridSize + gridSize/2, 10, '#FF5252'); | |
| } | |
| // Main game loop step | |
| function gameStep() { | |
| if (!gameRunning) return; | |
| moveSnake(); | |
| checkCollision(); | |
| if (gameRunning) { | |
| drawGame(); | |
| } | |
| } | |
| // Move the snake | |
| function moveSnake() { | |
| direction = nextDirection; | |
| // Get the head of the snake | |
| const head = {x: snake[0].x, y: snake[0].y}; | |
| // Move head based on direction | |
| 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; | |
| } | |
| // Add new head | |
| snake.unshift(head); | |
| // Check if snake ate food | |
| if (head.x === food.x && head.y === food.y) { | |
| score += 10; | |
| scoreDisplay.textContent = `Score: ${score}`; | |
| // Play sound and create particles | |
| if (eatSound) { | |
| eatSound.currentTime = 0; | |
| eatSound.play(); | |
| } | |
| createParticles(food.x * gridSize + gridSize/2, food.y * gridSize + gridSize/2, 15, '#4CAF50'); | |
| // Increase speed every 50 points | |
| if (score % 50 === 0 && gameSpeed > 50) { | |
| gameSpeed -= 10; | |
| clearInterval(gameLoop); | |
| gameLoop = setInterval(gameStep, gameSpeed); | |
| } | |
| spawnFood(); | |
| } else { | |
| // Remove tail only if didn't eat food | |
| snake.pop(); | |
| } | |
| } | |
| // Check for collisions | |
| function checkCollision() { | |
| const head = snake[0]; | |
| // Wall collision | |
| if (head.x < 0 || head.x >= tileCountX || head.y < 0 || head.y >= tileCountY) { | |
| gameOver(); | |
| return; | |
| } | |
| // Self collision (skip head) | |
| for (let i = 1; i < snake.length; i++) { | |
| if (head.x === snake[i].x && head.y === snake[i].y) { | |
| gameOver(); | |
| return; | |
| } | |
| } | |
| } | |
| // Game over sequence | |
| function gameOver() { | |
| gameRunning = false; | |
| clearInterval(gameLoop); | |
| // Play sound | |
| if (gameOverSound) { | |
| gameOverSound.currentTime = 0; | |
| gameOverSound.play(); | |
| } | |
| // Create explosion particles | |
| for (let segment of snake) { | |
| createParticles(segment.x * gridSize + gridSize/2, segment.y * gridSize + gridSize/2, 5, '#FFEB3B'); | |
| } | |
| // Show game over screen | |
| finalScoreDisplay.textContent = `Score: ${score}`; | |
| gameOverScreen.style.display = 'flex'; | |
| } | |
| // Draw game elements | |
| function drawGame() { | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw background grid | |
| drawGrid(); | |
| // Draw food | |
| drawFood(); | |
| // Draw snake | |
| drawSnake(); | |
| // Draw particles | |
| drawParticles(); | |
| } | |
| // Draw background grid | |
| function drawGrid() { | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.05)'; | |
| for (let x = 0; x < tileCountX; x++) { | |
| for (let y = 0; y < tileCountY; y++) { | |
| ctx.fillRect(x * gridSize, y * gridSize, gridSize - 1, gridSize - 1); | |
| } | |
| } | |
| } | |
| // Draw food with glow effect | |
| function drawFood() { | |
| const centerX = food.x * gridSize + gridSize / 2; | |
| const centerY = food.y * gridSize + gridSize / 2; | |
| const radius = gridSize / 2 - 2; | |
| // Glow effect | |
| const gradient = ctx.createRadialGradient( | |
| centerX, centerY, radius * 0.2, | |
| centerX, centerY, radius * 1.5 | |
| ); | |
| gradient.addColorStop(0, 'rgba(255, 82, 82, 0.8)'); | |
| gradient.addColorStop(1, 'rgba(255, 82, 82, 0)'); | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY, radius * 1.5, 0, Math.PI * 2); | |
| ctx.fillStyle = gradient; | |
| ctx.fill(); | |
| // Main food circle | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); | |
| ctx.fillStyle = '#FF5252'; | |
| ctx.fill(); | |
| ctx.shadowBlur = 15; | |
| ctx.shadowColor = '#FF5252'; | |
| ctx.fill(); | |
| ctx.shadowBlur = 0; | |
| // Inner detail | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY, radius / 2, 0, Math.PI * 2); | |
| ctx.fillStyle = '#FF8A80'; | |
| ctx.fill(); | |
| } | |
| // Draw snake with gradient and glow | |
| function drawSnake() { | |
| for (let i = 0; i < snake.length; i++) { | |
| const segment = snake[i]; | |
| const posX = segment.x * gridSize; | |
| const posY = segment.y * gridSize; | |
| const size = gridSize - 2; | |
| // Head gets different color | |
| if (i === 0) { | |
| const gradient = ctx.createLinearGradient( | |
| posX, posY, | |
| posX + size, posY + size | |
| ); | |
| gradient.addColorStop(0, '#00E5FF'); | |
| gradient.addColorStop(1, '#00B0FF'); | |
| ctx.fillStyle = gradient; | |
| } else { | |
| // Body gradient based on position | |
| const gradient = ctx.createLinearGradient( | |
| posX, posY, | |
| posX + size, posY + size | |
| ); | |
| const hue = (120 + i * 2) % 360; | |
| gradient.addColorStop(0, `hsl(${hue}, 100%, 65%)`); | |
| gradient.addColorStop(1, `hsl(${(hue + 20) % 360}, 100%, 50%)`); | |
| ctx.fillStyle = gradient; | |
| } | |
| // Glow effect | |
| ctx.shadowBlur = 10; | |
| ctx.shadowColor = ctx.fillStyle; | |
| // Rounded corners | |
| const cornerRadius = 5; | |
| ctx.beginPath(); | |
| ctx.moveTo(posX + cornerRadius, posY); | |
| ctx.lineTo(posX + size - cornerRadius, posY); | |
| ctx.quadraticCurveTo(posX + size, posY, posX + size, posY + cornerRadius); | |
| ctx.lineTo(posX + size, posY + size - cornerRadius); | |
| ctx.quadraticCurveTo(posX + size, posY + size, posX + size - cornerRadius, posY + size); | |
| ctx.lineTo(posX + cornerRadius, posY + size); | |
| ctx.quadraticCurveTo(posX, posY + size, posX, posY + size - cornerRadius); | |
| ctx.lineTo(posX, posY + cornerRadius); | |
| ctx.quadraticCurveTo(posX, posY, posX + cornerRadius, posY); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Reset shadow | |
| ctx.shadowBlur = 0; | |
| // Eyes on head | |
| if (i === 0) { | |
| const eyeSize = size / 6; | |
| ctx.fillStyle = 'white'; | |
| // Calculate eye positions based on direction | |
| let leftEyeX, leftEyeY, rightEyeX, rightEyeY; | |
| switch (direction) { | |
| case 'up': | |
| leftEyeX = posX + size / 3; | |
| leftEyeY = posY + size / 3; | |
| rightEyeX = posX + size * 2/3; | |
| rightEyeY = posY + size / 3; | |
| break; | |
| case 'down': | |
| leftEyeX = posX + size / 3; | |
| leftEyeY = posY + size * 2/3; | |
| rightEyeX = posX + size * 2/3; | |
| rightEyeY = posY + size * 2/3; | |
| break; | |
| case 'left': | |
| leftEyeX = posX + size / 3; | |
| leftEyeY = posY + size / 3; | |
| rightEyeX = posX + size / 3; | |
| rightEyeY = posY + size * 2/3; | |
| break; | |
| case 'right': | |
| leftEyeX = posX + size * 2/3; | |
| leftEyeY = posY + size / 3; | |
| rightEyeX = posX + size * 2/3; | |
| rightEyeY = posY + size * 2/3; | |
| break; | |
| } | |
| // Draw eyes | |
| ctx.beginPath(); | |
| ctx.arc(leftEyeX, leftEyeY, eyeSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(rightEyeX, rightEyeY, eyeSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Pupils | |
| ctx.fillStyle = '#222'; | |
| const pupilSize = eyeSize / 2; | |
| ctx.beginPath(); | |
| ctx.arc(leftEyeX, leftEyeY, pupilSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(rightEyeX, rightEyeY, pupilSize, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| } | |
| // Create particles | |
| function createParticles(x, y, count, color) { | |
| for (let i = 0; i < count; i++) { | |
| particles.push({ | |
| x: x, | |
| y: y, | |
| size: Math.random() * 3 + 1, | |
| color: color, | |
| speedX: Math.random() * 6 - 3, | |
| speedY: Math.random() * 6 - 3, | |
| life: 30 + Math.random() * 30, | |
| opacity: 1 | |
| }); | |
| } | |
| } | |
| // Draw and update particles | |
| function drawParticles() { | |
| for (let i = particles.length - 1; i >= 0; i--) { | |
| const p = particles[i]; | |
| // Update position and life | |
| p.x += p.speedX; | |
| p.y += p.speedY; | |
| p.life--; | |
| p.opacity = p.life / 60; | |
| // Remove if life is over | |
| if (p.life <= 0) { | |
| particles.splice(i, 1); | |
| continue; | |
| } | |
| // Draw particle | |
| ctx.globalAlpha = p.opacity; | |
| ctx.fillStyle = p.color; | |
| ctx.beginPath(); | |
| ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.globalAlpha = 1; | |
| } | |
| // Keyboard controls | |
| function handleKeyDown(e) { | |
| const key = e.key; | |
| // Prevent reverse direction | |
| if (key === 'ArrowUp' && direction !== 'down') { | |
| nextDirection = 'up'; | |
| } else if (key === 'ArrowDown' && direction !== 'up') { | |
| nextDirection = 'down'; | |
| } else if (key === 'ArrowLeft' && direction !== 'right') { | |
| nextDirection = 'left'; | |
| } else if (key === 'ArrowRight' && direction !== 'left') { | |
| nextDirection = 'right'; | |
| } | |
| } | |
| // Mobile controls | |
| document.getElementById('upBtn').addEventListener('click', () => { | |
| if (direction !== 'down') nextDirection = 'up'; | |
| }); | |
| document.getElementById('downBtn').addEventListener('click', () => { | |
| if (direction !== 'up') nextDirection = 'down'; | |
| }); | |
| document.getElementById('leftBtn').addEventListener('click', () => { | |
| if (direction !== 'right') nextDirection = 'left'; | |
| }); | |
| document.getElementById('rightBtn').addEventListener('click', () => { | |
| if (direction !== 'left') nextDirection = 'right'; | |
| }); | |
| // Restart game | |
| restartBtn.addEventListener('click', () => { | |
| gameOverScreen.style.display = 'none'; | |
| initGame(); | |
| }); | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| canvas.width = canvas.parentElement.offsetWidth; | |
| canvas.height = canvas.parentElement.offsetHeight; | |
| if (gameRunning) drawGame(); | |
| }); | |
| // Initialize | |
| document.addEventListener('keydown', handleKeyDown); | |
| 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 <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
| </html> |