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 - The Sparkling Adventure</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: 'Arial', sans-serif; | |
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); | |
color: white; | |
height: 100vh; | |
overflow: hidden; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
position: relative; | |
} | |
body::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><circle cx="50" cy="50" r="0.5" fill="white" opacity="0.1"/></svg>'); | |
z-index: -1; | |
animation: stars 50s linear infinite; | |
} | |
@keyframes stars { | |
0% { background-position: 0 0; } | |
100% { background-position: 1000px 1000px; } | |
} | |
.game-container { | |
position: relative; | |
width: 500px; | |
height: 500px; | |
border-radius: 10px; | |
overflow: hidden; | |
box-shadow: 0 0 40px rgba(100, 65, 255, 0.5); | |
border: 2px solid rgba(255, 255, 255, 0.1); | |
} | |
canvas { | |
background-color: rgba(15, 10, 40, 0.7); | |
display: block; | |
} | |
.overlay { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
background-color: rgba(0, 0, 0, 0.7); | |
z-index: 10; | |
} | |
.game-title { | |
font-size: 3rem; | |
font-weight: bold; | |
margin-bottom: 20px; | |
background: linear-gradient(90deg, #ff00cc, #3333ff); | |
-webkit-background-clip: text; | |
background-clip: text; | |
color: transparent; | |
text-shadow: 0 0 20px rgba(255, 255, 255, 0.3); | |
} | |
.btn { | |
padding: 12px 30px; | |
font-size: 1.2rem; | |
background: linear-gradient(135deg, #833ab4, #fd1d1d, #fcb045); | |
border: none; | |
color: white; | |
border-radius: 50px; | |
cursor: pointer; | |
margin: 10px; | |
box-shadow: 0 0 20px rgba(252, 176, 69, 0.5); | |
transition: all 0.3s ease; | |
position: relative; | |
overflow: hidden; | |
} | |
.btn:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 0 30px rgba(252, 176, 69, 0.8); | |
} | |
.btn::before { | |
content: ''; | |
position: absolute; | |
top: -50%; | |
left: -50%; | |
width: 200%; | |
height: 200%; | |
background: linear-gradient( | |
to bottom right, | |
transparent, | |
transparent, | |
transparent, | |
rgba(255, 255, 255, 0.2), | |
transparent, | |
transparent, | |
transparent | |
); | |
animation: shine 3s infinite; | |
} | |
@keyframes shine { | |
0% { transform: rotate(0deg) translate(-30%, -30%); } | |
100% { transform: rotate(360deg) translate(-30%, -30%); } | |
} | |
.score-display { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
font-size: 1.5rem; | |
background: rgba(0, 0, 0, 0.5); | |
padding: 10px 20px; | |
border-radius: 50px; | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
box-shadow: 0 0 15px rgba(255, 255, 255, 0.1); | |
} | |
.controls { | |
position: absolute; | |
bottom: 20px; | |
display: flex; | |
gap: 10px; | |
flex-wrap: wrap; | |
justify-content: center; | |
width: 100%; | |
} | |
.control-btn { | |
width: 60px; | |
height: 60px; | |
background: rgba(255, 255, 255, 0.1); | |
border-radius: 50%; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
cursor: pointer; | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
transition: all 0.2s ease; | |
} | |
.control-btn:hover { | |
background: rgba(255, 255, 255, 0.2); | |
} | |
.control-btn i { | |
font-size: 1.5rem; | |
color: rgba(255, 255, 255, 0.7); | |
} | |
.sparkle { | |
position: absolute; | |
width: 10px; | |
height: 10px; | |
background: white; | |
border-radius: 50%; | |
pointer-events: none; | |
transform: scale(1); | |
opacity: 0; | |
animation: sparkle 1s forwards; | |
} | |
@keyframes sparkle { | |
0% { | |
transform: translate(var(--tx), var(--ty)) scale(0); | |
opacity: 0; | |
} | |
50% { | |
opacity: 0.8; | |
} | |
100% { | |
transform: translate(var(--tx), var(--ty)) scale(1.5); | |
opacity: 0; | |
} | |
} | |
.game-over { | |
text-align: center; | |
} | |
.final-score { | |
font-size: 2rem; | |
margin: 20px 0; | |
color: #fcb045; | |
} | |
.instructions { | |
position: absolute; | |
top: 20px; | |
left: 20px; | |
background: rgba(0, 0, 0, 0.5); | |
padding: 15px; | |
border-radius: 10px; | |
font-size: 1rem; | |
max-width: 250px; | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
} | |
.power-ups { | |
position: absolute; | |
bottom: 100px; | |
left: 20px; | |
background: rgba(0, 0, 0, 0.5); | |
padding: 15px; | |
border-radius: 10px; | |
font-size: 1rem; | |
max-width: 250px; | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
} | |
.power-up-item { | |
display: flex; | |
align-items: center; | |
margin: 5px 0; | |
} | |
.power-up-color { | |
width: 20px; | |
height: 20px; | |
border-radius: 50%; | |
margin-right: 10px; | |
} | |
.hidden { | |
display: none; | |
} | |
/* Responsive adjustments */ | |
@media (max-width: 600px) { | |
.game-container { | |
width: 90vw; | |
height: 90vw; | |
} | |
.game-title { | |
font-size: 2rem; | |
} | |
.instructions, .power-ups { | |
max-width: 200px; | |
font-size: 0.8rem; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="game-container"> | |
<canvas id="gameCanvas"></canvas> | |
<div class="score-display hidden"> | |
Score: <span id="score">0</span> | |
</div> | |
<div class="overlay" id="startScreen"> | |
<h1 class="game-title">Neon Snake</h1> | |
<button class="btn" id="startBtn">Start Game</button> | |
<button class="btn" id="howToPlayBtn">How to Play</button> | |
</div> | |
<div class="overlay hidden" id="gameOverScreen"> | |
<div class="game-over"> | |
<h1 class="game-title">Game Over!</h1> | |
<div class="final-score">Your score: <span id="finalScore">0</span></div> | |
<button class="btn" id="restartBtn">Play Again</button> | |
</div> | |
</div> | |
<div class="instructions hidden" id="instructions"> | |
<h3>How to Play</h3> | |
<p>Use arrow keys or on-screen controls to guide the snake 🐍</p> | |
<p>Eat the colorful food to grow larger and earn points 💎</p> | |
<p>Avoid hitting the walls or your own tail ⚠️</p> | |
<p>Special power-ups appear occasionally - grab them for bonuses ✨</p> | |
<button class="btn" id="backBtn" style="margin-top: 10px;">Back</button> | |
</div> | |
<div class="power-ups hidden"> | |
<h3>Power-Ups</h3> | |
<div class="power-up-item"> | |
<div class="power-up-color" style="background: gold;"></div> | |
<span>+5 points</span> | |
</div> | |
<div class="power-up-item"> | |
<div class="power-up-color" style="background: #ff00ff;"></div> | |
<span>Speed boost</span> | |
</div> | |
<div class="power-up-item"> | |
<div class="power-up-color" style="background: #00ffff;"></div> | |
<span>Invincibility</span> | |
</div> | |
</div> | |
<div class="controls hidden"> | |
<div class="control-btn" id="upBtn"><i class="fas fa-arrow-up"></i></div> | |
<div class="control-btn" id="leftBtn"><i class="fas fa-arrow-left"></i></div> | |
<div class="control-btn" id="downBtn"><i class="fas fa-arrow-down"></i></div> | |
<div class="control-btn" id="rightBtn"><i class="fas fa-arrow-right"></i></div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
// Game elements | |
const canvas = document.getElementById('gameCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const startScreen = document.getElementById('startScreen'); | |
const gameOverScreen = document.getElementById('gameOverScreen'); | |
const instructionsScreen = document.getElementById('instructions'); | |
const scoreDisplay = document.querySelector('.score-display'); | |
const gameScore = document.getElementById('score'); | |
const finalScore = document.getElementById('finalScore'); | |
const startBtn = document.getElementById('startBtn'); | |
const restartBtn = document.getElementById('restartBtn'); | |
const howToPlayBtn = document.getElementById('howToPlayBtn'); | |
const backBtn = document.getElementById('backBtn'); | |
// Control buttons | |
const upBtn = document.getElementById('upBtn'); | |
const leftBtn = document.getElementById('leftBtn'); | |
const downBtn = document.getElementById('downBtn'); | |
const rightBtn = document.getElementById('rightBtn'); | |
// Set canvas size | |
canvas.width = canvas.parentElement.offsetWidth; | |
canvas.height = canvas.parentElement.offsetHeight; | |
// Game variables | |
let snake = []; | |
let food = {}; | |
let powerUp = null; | |
let direction = 'right'; | |
let nextDirection = 'right'; | |
let gridSize = 20; | |
let tileCountX = Math.floor(canvas.width / gridSize); | |
let tileCountY = Math.floor(canvas.height / gridSize); | |
let speed = 7; // Initial speed | |
let speedBoost = 0; | |
let score = 0; | |
let gameLoop; | |
let invincible = false; | |
let gameRunning = false; | |
let foodType = 'normal'; | |
let powerUpActive = false; | |
let powerUpTime = 0; | |
// Colors | |
const colors = { | |
snakeHead: '#4dffea', | |
snakeBody: '#00ff9d', | |
snakeTail: '#009dff', | |
normalFood: '#ff3e3e', | |
powerFood: '#FFD700', | |
speedFood: '#ff00ff', | |
invincibleFood: '#00ffff', | |
powerUpText: '#ffffff', | |
speedBoost: '#ff0000' | |
}; | |
// Initialize game | |
function initGame() { | |
snake = []; | |
for (let i = 3; i >= 0; i--) { | |
snake.push({ | |
x: i, | |
y: 0 | |
}); | |
} | |
direction = 'right'; | |
nextDirection = 'right'; | |
speed = 7; | |
speedBoost = 0; | |
score = 0; | |
invincible = false; | |
powerUp = null; | |
powerUpActive = false; | |
foodType = 'normal'; | |
spawnFood(); | |
updateScore(); | |
// Hide screens | |
startScreen.classList.add('hidden'); | |
gameOverScreen.classList.add('hidden'); | |
instructionsScreen.classList.add('hidden'); | |
// Show game elements | |
scoreDisplay.classList.remove('hidden'); | |
document.querySelector('.power-ups').classList.remove('hidden'); | |
document.querySelector('.controls').classList.remove('hidden'); | |
gameRunning = true; | |
} | |
// Main game loop | |
function gameUpdate() { | |
if (!gameRunning) return; | |
// Clear canvas with partial transparency for trail effect | |
ctx.fillStyle = 'rgba(15, 10, 40, 0.2)'; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
// Update direction | |
direction = nextDirection; | |
// Move snake | |
let headX = snake[0].x; | |
let headY = snake[0].y; | |
if (direction === 'right') headX++; | |
if (direction === 'left') headX--; | |
if (direction === 'up') headY--; | |
if (direction === 'down') headY++; | |
// Wrap around screen | |
if (headX < 0) headX = tileCountX - 1; | |
if (headX >= tileCountX) headX = 0; | |
if (headY < 0) headY = tileCountY - 1; | |
if (headY >= tileCountY) headY = 0; | |
// Check for collision with self (unless invincible) | |
if (!invincible) { | |
for (let i = 0; i < snake.length; i++) { | |
if (snake[i].x === headX && snake[i].y === headY) { | |
gameOver(); | |
return; | |
} | |
} | |
} | |
// Create new head | |
const newHead = { | |
x: headX, | |
y: headY | |
}; | |
snake.unshift(newHead); | |
// Check if snake ate food | |
if (headX === food.x && headY === food.y) { | |
// Play sound (not included in this example) | |
// Add sparkles | |
createSparkles(food.x * gridSize + gridSize/2, food.y * gridSize + gridSize/2, foodType); | |
// Different effects based on food type | |
if (foodType === 'power') { | |
score += 5; // Bonus points for power food | |
} else if (foodType === 'speed') { | |
speedBoost = 3; | |
} else if (foodType === 'invincible') { | |
invincible = true; | |
setTimeout(() => { | |
invincible = false; | |
}, 10000); // 10 seconds of invincibility | |
} else { | |
score++; // Normal food | |
} | |
updateScore(); | |
// Spawn new food | |
spawnFood(); | |
// Random chance to spawn a power-up | |
if (Math.random() < 0.1 && !powerUp) { | |
spawnPowerUp(); | |
} | |
} else if (powerUp && headX === powerUp.x && headY === powerUp.y) { | |
// Snake ate power-up | |
createSparkles(powerUp.x * gridSize + gridSize/2, powerUp.y * gridSize + gridSize/2, 'power'); | |
// Activate power-up effect | |
if (Math.random() < 0.7) { | |
// Speed boost (more common) | |
speedBoost = 5; | |
} else { | |
// Invincibility (less common) | |
invincible = true; | |
setTimeout(() => { | |
invincible = false; | |
}, 5000); // 5 seconds of invincibility | |
} | |
score += 2; | |
updateScore(); | |
powerUp = null; | |
} else { | |
// Remove tail (unless we ate food) | |
snake.pop(); | |
} | |
// Draw food | |
drawFood(); | |
if (powerUp) { | |
drawPowerUp(); | |
} | |
// Draw snake | |
drawSnake(); | |
// Check if power-up should disappear | |
if (powerUp && powerUpTime + 5000 < Date.now()) { | |
powerUp = null; | |
} | |
// Calculate speed with any boosts | |
const currentSpeed = speed + speedBoost; | |
speedBoost = Math.max(0, speedBoost - 0.1); | |
// Schedule next frame | |
setTimeout(gameUpdate, 1000 / currentSpeed); | |
} | |
// Draw the snake | |
function drawSnake() { | |
for (let i = 0; i < snake.length; i++) { | |
// Calculate color gradient from head to tail | |
let ratio = i / snake.length; | |
let r, g, b; | |
if (invincible) { | |
// Rainbow effect when invincible | |
r = Math.floor(Math.sin(ratio * Math.PI + Date.now() * 0.01) * 127 + 128); | |
g = Math.floor(Math.sin(ratio * Math.PI + Date.now() * 0.01 + 2) * 127 + 128); | |
b = Math.floor(Math.sin(ratio * Math.PI + Date.now() * 0.01 + 4) * 127 + 128); | |
} else { | |
// Normal gradient | |
r = 0 + ratio * (0x4d - 0); | |
g = 0xff - ratio * (0xff - 0x9d); | |
b = 0xff - ratio * (0xff - 0x3e); | |
} | |
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; | |
// Add glow effect | |
if (i === 0) { // Head has stronger glow | |
ctx.shadowBlur = 15; | |
ctx.shadowColor = `rgb(${r}, ${g}, ${b})`; | |
} else { | |
ctx.shadowBlur = 8 - (5 * ratio); | |
ctx.shadowColor = `rgba(${r}, ${g}, ${b}, ${0.5 + ratio * 0.5})`; | |
} | |
// Draw snake segment | |
ctx.fillRect( | |
snake[i].x * gridSize, | |
snake[i].y * gridSize, | |
gridSize - 2, | |
gridSize - 2 | |
); | |
// Reset shadow | |
ctx.shadowBlur = 0; | |
// Draw eyes on head | |
if (i === 0) { | |
ctx.fillStyle = 'white'; | |
let eyeSize = gridSize / 5; | |
let eyeOffsetX = gridSize / 3; | |
let eyeOffsetY = gridSize / 3; | |
// Position eyes based on direction | |
if (direction === 'right') { | |
ctx.fillRect( | |
snake[i].x * gridSize + gridSize - eyeSize - 2, | |
snake[i].y * gridSize + eyeOffsetY, | |
eyeSize, | |
eyeSize | |
); | |
ctx.fillRect( | |
snake[i].x * gridSize + gridSize - eyeSize - 2, | |
snake[i].y * gridSize + gridSize - eyeOffsetY - eyeSize, | |
eyeSize, | |
eyeSize | |
); | |
} else if (direction === 'left') { | |
ctx.fillRect( | |
snake[i].x * gridSize + 2, | |
snake[i].y * gridSize + eyeOffsetY, | |
eyeSize, | |
eyeSize | |
); | |
ctx.fillRect( | |
snake[i].x * gridSize + 2, | |
snake[i].y * gridSize + gridSize - eyeOffsetY - eyeSize, | |
eyeSize, | |
eyeSize | |
); | |
} else if (direction === 'up') { | |
ctx.fillRect( | |
snake[i].x * gridSize + eyeOffsetX, | |
snake[i].y * gridSize + 2, | |
eyeSize, | |
eyeSize | |
); | |
ctx.fillRect( | |
snake[i].x * gridSize + gridSize - eyeOffsetX - eyeSize, | |
snake[i].y * gridSize + 2, | |
eyeSize, | |
eyeSize | |
); | |
} else if (direction === 'down') { | |
ctx.fillRect( | |
snake[i].x * gridSize + eyeOffsetX, | |
snake[i].y * gridSize + gridSize - eyeSize - 2, | |
eyeSize, | |
eyeSize | |
); | |
ctx.fillRect( | |
snake[i].x * gridSize + gridSize - eyeOffsetX - eyeSize, | |
snake[i].y * gridSize + gridSize - eyeSize - 2, | |
eyeSize, | |
eyeSize | |
); | |
} | |
} | |
} | |
} | |
// Draw food | |
function drawFood() { | |
ctx.shadowBlur = 15; | |
if (foodType === 'power') { | |
ctx.fillStyle = colors.powerFood; | |
ctx.shadowColor = 'rgba(255, 215, 0, 0.7)'; | |
} else if (foodType === 'speed') { | |
ctx.fillStyle = colors.speedFood; | |
ctx.shadowColor = 'rgba(255, 0, 255, 0.7)'; | |
} else if (foodType === 'invincible') { | |
ctx.fillStyle = colors.invincibleFood; | |
ctx.shadowColor = 'rgba(0, 255, 255, 0.7)'; | |
} else { | |
ctx.fillStyle = colors.normalFood; | |
ctx.shadowColor = 'rgba(255, 62, 62, 0.7)'; | |
} | |
// Draw rotating diamond shape | |
const centerX = food.x * gridSize + gridSize / 2; | |
const centerY = food.y * gridSize + gridSize / 2; | |
const size = gridSize - 4; | |
const rotation = (Date.now() * 0.05) % 360; | |
ctx.save(); | |
ctx.translate(centerX, centerY); | |
ctx.rotate(rotation * Math.PI / 180); | |
ctx.beginPath(); | |
ctx.moveTo(0, -size/2); | |
ctx.lineTo(size/2, 0); | |
ctx.lineTo(0, size/2); | |
ctx.lineTo(-size/2, 0); | |
ctx.closePath(); | |
ctx.fill(); | |
ctx.restore(); | |
ctx.shadowBlur = 0; | |
} | |
// Draw power-up | |
function drawPowerUp() { | |
if (!powerUp) return; | |
ctx.shadowBlur = 20; | |
ctx.shadowColor = 'rgba(255, 255, 255, 0.8)'; | |
const size = gridSize - 4; | |
const centerX = powerUp.x * gridSize + gridSize / 2; | |
const centerY = powerUp.y * gridSize + gridSize / 2; | |
// Draw pulsing star | |
const pulse = 0.8 + 0.2 * Math.sin(Date.now() * 0.01); | |
ctx.save(); | |
ctx.translate(centerX, centerY); | |
ctx.scale(pulse, pulse); | |
ctx.fillStyle = 'rgba(255, 215, 0, 0.8)'; | |
// Draw star shape | |
ctx.beginPath(); | |
for (let i = 0; i < 5; i++) { | |
const angle = (i * 2 * Math.PI / 5) - Math.PI/2; | |
const outerX = Math.cos(angle) * size/2; | |
const outerY = Math.sin(angle) * size/2; | |
const innerX = Math.cos(angle + Math.PI/5) * size/4; | |
const innerY = Math.sin(angle + Math.PI/5) * size/4; | |
if (i === 0) ctx.moveTo(outerX, outerY); | |
else ctx.lineTo(outerX, outerY); | |
ctx.lineTo(innerX, innerY); | |
} | |
ctx.closePath(); | |
ctx.fill(); | |
ctx.restore(); | |
ctx.shadowBlur = 0; | |
} | |
// Spawn food at random location | |
function spawnFood() { | |
// Decide food type randomly | |
const r = Math.random(); | |
if (r < 0.1) { | |
foodType = 'power'; // 10% chance | |
} else if (r < 0.2) { | |
foodType = 'speed'; // 10% chance | |
} else if (r < 0.25) { | |
foodType = 'invincible'; // 5% chance | |
} else { | |
foodType = 'normal'; // 75% chance | |
} | |
// Find empty spot | |
let emptySpot = false; | |
let foodX, foodY; | |
while (!emptySpot) { | |
foodX = Math.floor(Math.random() * tileCountX); | |
foodY = Math.floor(Math.random() * tileCountY); | |
emptySpot = true; | |
// Check if spot is occupied by snake | |
for (let i = 0; i < snake.length; i++) { | |
if (snake[i].x === foodX && snake[i].y === foodY) { | |
emptySpot = false; | |
break; | |
} | |
} | |
// Check if spot is occupied by power-up | |
if (powerUp && powerUp.x === foodX && powerUp.y === foodY) { | |
emptySpot = false; | |
} | |
} | |
food = { | |
x: foodX, | |
y: foodY | |
}; | |
} | |
// Spawn power-up | |
function spawnPowerUp() { | |
let emptySpot = false; | |
let puX, puY; | |
while (!emptySpot) { | |
puX = Math.floor(Math.random() * tileCountX); | |
puY = Math.floor(Math.random() * tileCountY); | |
emptySpot = true; | |
// Check if spot is occupied by snake | |
for (let i = 0; i < snake.length; i++) { | |
if (snake[i].x === puX && snake[i].y === puY) { | |
emptySpot = false; | |
break; | |
} | |
} | |
// Check if spot is occupied by food | |
if (food.x === puX && food.y === puY) { | |
emptySpot = false; | |
} | |
} | |
powerUp = { | |
x: puX, | |
y: puY | |
}; | |
powerUpTime = Date.now(); | |
} | |
// Update score display | |
function updateScore() { | |
gameScore.textContent = score; | |
} | |
// Game over | |
function gameOver() { | |
gameRunning = false; | |
clearTimeout(gameLoop); | |
// Create explosion effect | |
for (let i = 0; i < snake.length; i++) { | |
setTimeout(() => { | |
createSparkles( | |
snake[i].x * gridSize + gridSize/2, | |
snake[i].y * gridSize + gridSize/2, | |
'explosion' | |
); | |
}, i * 50); | |
} | |
// Show game over screen | |
setTimeout(() => { | |
finalScore.textContent = score; | |
gameOverScreen.classList.remove('hidden'); | |
scoreDisplay.classList.add('hidden'); | |
document.querySelector('.power-ups').classList.add('hidden'); | |
document.querySelector('.controls').classList.add('hidden'); | |
}, 500); | |
} | |
// Create sparkle effects | |
function createSparkles(x, y, type) { | |
const colors = { | |
normal: ['#ff3e3e', '#ff7b7b', '#ff9999'], | |
power: ['#FFD700', '#FFEE58', '#FFF59D'], | |
speed: ['#ff00ff', '#ff66ff', '#ff99ff'], | |
invincible: ['#00ffff', '#66ffff', '#99ffff'], | |
explosion: ['#ff0000', '#ff6600', '#ffff00'] | |
}; | |
const sparkleCount = type === 'explosion' ? 30 : 15; | |
const sparkleSize = type === 'explosion' ? 6 : 4; | |
const lifetime = type === 'explosion' ? '1s' : '0.8s'; | |
for (let i = 0; i < sparkleCount; i++) { | |
const sparkle = document.createElement('div'); | |
sparkle.className = 'sparkle'; | |
// Random properties for each sparkle | |
const angle = Math.random() * Math.PI * 2; | |
const distance = Math.random() * 30 + 20; | |
const tx = Math.cos(angle) * distance; | |
const ty = Math.sin(angle) * distance; | |
const size = Math.random() * sparkleSize + sparkleSize/2; | |
const delay = Math.random() * 0.2; | |
const color = colors[type][Math.floor(Math.random() * colors[type].length)]; | |
sparkle.style.setProperty('--tx', tx + 'px'); | |
sparkle.style.setProperty('--ty', ty + 'px'); | |
sparkle.style.width = size + 'px'; | |
sparkle.style.height = size + 'px'; | |
sparkle.style.background = color; | |
sparkle.style.left = x + 'px'; | |
sparkle.style.top = y + 'px'; | |
sparkle.style.animation = `sparkle ${lifetime} ${delay}s forwards`; | |
document.body.appendChild(sparkle); | |
// Remove sparkle after animation | |
setTimeout(() => { | |
sparkle.remove(); | |
}, 1000); | |
} | |
} | |
// Event listeners | |
startBtn.addEventListener('click', () => { | |
initGame(); | |
gameUpdate(); | |
}); | |
restartBtn.addEventListener('click', () => { | |
initGame(); | |
gameUpdate(); | |
}); | |
howToPlayBtn.addEventListener('click', () => { | |
startScreen.classList.add('hidden'); | |
instructionsScreen.classList.remove('hidden'); | |
}); | |
backBtn.addEventListener('click', () => { | |
instructionsScreen.classList.add('hidden'); | |
startScreen.classList.remove('hidden'); | |
}); | |
// Keyboard controls | |
document.addEventListener('keydown', (e) => { | |
if (!gameRunning) return; | |
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; | |
} | |
}); | |
// Touch controls | |
upBtn.addEventListener('click', () => { | |
if (gameRunning && direction !== 'down') nextDirection = 'up'; | |
}); | |
leftBtn.addEventListener('click', () => { | |
if (gameRunning && direction !== 'right') nextDirection = 'left'; | |
}); | |
downBtn.addEventListener('click', () => { | |
if (gameRunning && direction !== 'up') nextDirection = 'down'; | |
}); | |
rightBtn.addEventListener('click', () => { | |
if (gameRunning && direction !== 'left') nextDirection = 'right'; | |
}); | |
// Handle window resize | |
window.addEventListener('resize', () => { | |
const container = canvas.parentElement; | |
canvas.width = container.offsetWidth; | |
canvas.height = container.offsetHeight; | |
// Recalculate tile counts | |
tileCountX = Math.floor(canvas.width / gridSize); | |
tileCountY = Math.floor(canvas.height / gridSize); | |
}); | |
}); | |
</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> |