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> |