| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Rainbow Neon Snake</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); |
| |
| body { |
| font-family: 'Press Start 2P', cursive; |
| background: linear-gradient(135deg, #0f172a, #1e293b); |
| overflow: hidden; |
| touch-action: none; |
| } |
| |
| #gameCanvas { |
| box-shadow: 0 0 30px rgba(99, 102, 241, 0.8), |
| 0 0 60px rgba(139, 92, 246, 0.6), |
| 0 0 90px rgba(217, 70, 239, 0.4); |
| border-radius: 8px; |
| border: 2px solid #6366f1; |
| } |
| |
| .rainbow-text { |
| background: linear-gradient(90deg, |
| #ef4444, #f97316, #eab308, #22c55e, |
| #3b82f6, #6366f1, #8b5cf6, #d946ef); |
| -webkit-background-clip: text; |
| background-clip: text; |
| color: transparent; |
| text-shadow: 0 0 10px rgba(255, 255, 255, 0.3); |
| } |
| |
| .neon-button { |
| transition: all 0.3s; |
| box-shadow: 0 0 15px rgba(99, 102, 241, 0.7); |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .neon-button:hover { |
| box-shadow: 0 0 25px rgba(139, 92, 246, 0.8); |
| transform: translateY(-2px); |
| } |
| |
| .neon-button::before { |
| content: ''; |
| position: absolute; |
| top: -50%; |
| left: -50%; |
| width: 200%; |
| height: 200%; |
| background: linear-gradient( |
| to bottom right, |
| rgba(255, 255, 255, 0.3) 0%, |
| rgba(255, 255, 255, 0) 60% |
| ); |
| transform: rotate(30deg); |
| transition: all 0.3s; |
| } |
| |
| .neon-button:hover::before { |
| left: 100%; |
| } |
| |
| .pulse { |
| animation: pulse 1.5s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(0.98); opacity: 0.9; } |
| 50% { transform: scale(1.02); opacity: 1; } |
| 100% { transform: scale(0.98); opacity: 0.9; } |
| } |
| |
| .glow { |
| animation: glow 2s infinite alternate; |
| } |
| |
| @keyframes glow { |
| from { |
| box-shadow: 0 0 15px rgba(99, 102, 241, 0.7), |
| 0 0 30px rgba(139, 92, 246, 0.5); |
| } |
| to { |
| box-shadow: 0 0 25px rgba(99, 102, 241, 0.9), |
| 0 0 50px rgba(139, 92, 246, 0.7), |
| 0 0 75px rgba(217, 70, 239, 0.5); |
| } |
| } |
| |
| .particle { |
| position: absolute; |
| border-radius: 50%; |
| pointer-events: none; |
| z-index: 10; |
| } |
| |
| .controls { |
| touch-action: none; |
| } |
| |
| .control-btn { |
| width: 70px; |
| height: 70px; |
| background: rgba(30, 41, 59, 0.7); |
| border: 2px solid; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| font-size: 28px; |
| user-select: none; |
| touch-action: none; |
| transition: all 0.2s; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .control-btn::before { |
| content: ''; |
| position: absolute; |
| top: -50%; |
| left: -50%; |
| width: 200%; |
| height: 200%; |
| background: linear-gradient( |
| to bottom right, |
| rgba(255, 255, 255, 0.2) 0%, |
| rgba(255, 255, 255, 0) 60% |
| ); |
| transform: rotate(30deg); |
| } |
| |
| .control-btn:active { |
| transform: scale(0.95); |
| } |
| |
| #upBtn { |
| border-color: #22c55e; |
| box-shadow: 0 0 15px rgba(34, 197, 94, 0.5); |
| } |
| |
| #downBtn { |
| border-color: #3b82f6; |
| box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); |
| } |
| |
| #leftBtn { |
| border-color: #f97316; |
| box-shadow: 0 0 15px rgba(249, 115, 22, 0.5); |
| } |
| |
| #rightBtn { |
| border-color: #d946ef; |
| box-shadow: 0 0 15px rgba(217, 70, 239, 0.5); |
| } |
| |
| .score-box { |
| background: rgba(30, 41, 59, 0.7); |
| border: 2px solid #6366f1; |
| border-radius: 10px; |
| box-shadow: 0 0 15px rgba(99, 102, 241, 0.5); |
| padding: 10px 20px; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .score-box::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| height: 3px; |
| background: linear-gradient(90deg, |
| #ef4444, #f97316, #eab308, #22c55e, |
| #3b82f6, #6366f1, #8b5cf6, #d946ef); |
| } |
| |
| .trail { |
| position: absolute; |
| width: 20px; |
| height: 20px; |
| border-radius: 50%; |
| background: rgba(255, 255, 255, 0.4); |
| pointer-events: none; |
| z-index: 5; |
| } |
| |
| .power-up { |
| position: absolute; |
| width: 20px; |
| height: 20px; |
| border-radius: 50%; |
| background: #eab308; |
| box-shadow: 0 0 10px #eab308, 0 0 20px rgba(234, 179, 8, 0.7); |
| animation: float 3s ease-in-out infinite; |
| z-index: 5; |
| } |
| |
| @keyframes float { |
| 0%, 100% { transform: translateY(0); } |
| 50% { transform: translateY(-10px); } |
| } |
| |
| .power-up::after { |
| content: ''; |
| position: absolute; |
| top: -5px; |
| left: -5px; |
| right: -5px; |
| bottom: -5px; |
| border: 2px solid #eab308; |
| border-radius: 50%; |
| animation: pulse-ring 2s ease-out infinite; |
| opacity: 0; |
| } |
| |
| @keyframes pulse-ring { |
| 0% { transform: scale(0.8); opacity: 0.8; } |
| 100% { transform: scale(1.5); opacity: 0; } |
| } |
| </style> |
| </head> |
| <body class="min-h-screen flex flex-col items-center justify-center p-4"> |
| <div class="text-center mb-8"> |
| <h1 class="text-4xl md:text-5xl font-bold rainbow-text mb-4">RAINBOW SNAKE</h1> |
| <div class="flex justify-center gap-8 mb-6"> |
| <div class="score-box"> |
| <p class="text-sm text-blue-300">SCORE</p> |
| <p id="score" class="text-2xl text-white">0</p> |
| </div> |
| <div class="score-box"> |
| <p class="text-sm text-blue-300">HIGH SCORE</p> |
| <p id="highScore" class="text-2xl text-white">0</p> |
| </div> |
| </div> |
| </div> |
| |
| <div class="relative"> |
| <canvas id="gameCanvas" width="400" height="400" class="bg-gray-900"></canvas> |
| |
| <div id="gameOver" class="hidden absolute inset-0 flex flex-col items-center justify-center bg-black bg-opacity-90 rounded"> |
| <h2 class="text-red-500 text-3xl mb-6 rainbow-text">GAME OVER!</h2> |
| <p class="text-blue-300 mb-4">Your score: <span id="finalScore" class="text-white">0</span></p> |
| <p class="text-blue-300 mb-8">High score: <span id="finalHighScore" class="text-white">0</span></p> |
| <button id="restartButton" class="px-6 py-3 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-full neon-button font-bold glow"> |
| <i class="fas fa-redo mr-2"></i> PLAY AGAIN |
| </button> |
| </div> |
| |
| <div id="startScreen" class="absolute inset-0 flex flex-col items-center justify-center bg-black bg-opacity-90 rounded"> |
| <h2 class="rainbow-text text-3xl mb-6 pulse">RAINBOW SNAKE</h2> |
| <p class="text-blue-300 mb-8 text-center px-4">Eat the food to grow longer! Collect power-ups for bonuses!</p> |
| <button id="startButton" class="px-6 py-3 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-full neon-button font-bold glow"> |
| <i class="fas fa-play mr-2"></i> START GAME |
| </button> |
| </div> |
| </div> |
| |
| <div class="mt-6 grid grid-cols-3 gap-4 controls" id="mobileControls"> |
| <div></div> |
| <button class="control-btn" id="upBtn"><i class="fas fa-arrow-up"></i></button> |
| <div></div> |
| <button class="control-btn" id="leftBtn"><i class="fas fa-arrow-left"></i></button> |
| <div class="flex items-center justify-center"> |
| <div class="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white"> |
| <i class="fas fa-gamepad"></i> |
| </div> |
| </div> |
| <button class="control-btn" id="rightBtn"><i class="fas fa-arrow-right"></i></button> |
| <div></div> |
| <button class="control-btn" id="downBtn"><i class="fas fa-arrow-down"></i></button> |
| <div></div> |
| </div> |
| |
| <div class="mt-4 text-blue-300 text-sm text-center"> |
| <p>Controls: Arrow Keys, WASD, or Touch Buttons</p> |
| </div> |
| |
| <script> |
| document.addEventListener('DOMContentLoaded', () => { |
| const canvas = document.getElementById('gameCanvas'); |
| const ctx = canvas.getContext('2d'); |
| const scoreElement = document.getElementById('score'); |
| const highScoreElement = document.getElementById('highScore'); |
| const finalScoreElement = document.getElementById('finalScore'); |
| const finalHighScoreElement = document.getElementById('finalHighScore'); |
| const gameOverScreen = document.getElementById('gameOver'); |
| const startScreen = document.getElementById('startScreen'); |
| const startButton = document.getElementById('startButton'); |
| const restartButton = document.getElementById('restartButton'); |
| const mobileControls = document.getElementById('mobileControls'); |
| |
| |
| const gridSize = 20; |
| const tileCount = canvas.width / gridSize; |
| let speed = 7; |
| |
| |
| let snake = [{x: 10, y: 10}]; |
| let food = {x: 5, y: 5}; |
| let powerUps = []; |
| let direction = {x: 0, y: 0}; |
| let nextDirection = {x: 0, y: 0}; |
| let score = 0; |
| let highScore = localStorage.getItem('snakeHighScore') || 0; |
| let gameRunning = false; |
| let gameLoop; |
| let particles = []; |
| let trails = []; |
| let lastRenderTime = 0; |
| let powerUpTimer = 0; |
| let invincible = false; |
| let rainbowMode = false; |
| let rainbowTimer = 0; |
| |
| highScoreElement.textContent = highScore; |
| |
| |
| const colors = [ |
| '#ef4444', |
| '#f97316', |
| '#eab308', |
| '#22c55e', |
| '#3b82f6', |
| '#6366f1', |
| '#8b5cf6', |
| '#d946ef' |
| ]; |
| |
| |
| function createParticles(x, y, color, count = 15) { |
| for (let i = 0; i < count; i++) { |
| const size = Math.random() * 4 + 2; |
| particles.push({ |
| x: x, |
| y: y, |
| size: size, |
| color: color || colors[Math.floor(Math.random() * colors.length)], |
| speedX: Math.random() * 8 - 4, |
| speedY: Math.random() * 8 - 4, |
| life: 30 + Math.random() * 20, |
| shrink: size * 0.05 |
| }); |
| } |
| } |
| |
| function createTrail(x, y) { |
| if (Math.random() > 0.3) return; |
| |
| trails.push({ |
| x: x * gridSize + gridSize / 2, |
| y: y * gridSize + gridSize / 2, |
| size: Math.random() * 10 + 5, |
| opacity: 0.7, |
| fade: 0.02 |
| }); |
| } |
| |
| function updateParticles() { |
| for (let i = particles.length - 1; i >= 0; i--) { |
| const p = particles[i]; |
| p.x += p.speedX; |
| p.y += p.speedY; |
| p.life--; |
| p.size -= p.shrink; |
| |
| if (p.life <= 0 || p.size <= 0) { |
| particles.splice(i, 1); |
| } |
| } |
| |
| for (let i = trails.length - 1; i >= 0; i--) { |
| const t = trails[i]; |
| t.opacity -= t.fade; |
| if (t.opacity <= 0) { |
| trails.splice(i, 1); |
| } |
| } |
| } |
| |
| function drawParticles() { |
| |
| trails.forEach(t => { |
| ctx.globalAlpha = t.opacity; |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; |
| ctx.beginPath(); |
| ctx.arc(t.x, t.y, t.size, 0, Math.PI * 2); |
| ctx.fill(); |
| }); |
| |
| |
| particles.forEach(p => { |
| ctx.globalAlpha = p.life / 50; |
| ctx.fillStyle = p.color; |
| ctx.beginPath(); |
| ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); |
| ctx.fill(); |
| }); |
| |
| ctx.globalAlpha = 1; |
| } |
| |
| |
| function drawSnake() { |
| snake.forEach((segment, index) => { |
| |
| let gradient; |
| const x = segment.x * gridSize; |
| const y = segment.y * gridSize; |
| |
| if (rainbowMode) { |
| |
| gradient = ctx.createLinearGradient(x, y, x + gridSize, y + gridSize); |
| gradient.addColorStop(0, colors[index % colors.length]); |
| gradient.addColorStop(1, colors[(index + 4) % colors.length]); |
| } else { |
| |
| if (index === 0) { |
| gradient = ctx.createRadialGradient( |
| x + gridSize / 2, |
| y + gridSize / 2, |
| 0, |
| x + gridSize / 2, |
| y + gridSize / 2, |
| gridSize |
| ); |
| gradient.addColorStop(0, '#3b82f6'); |
| gradient.addColorStop(1, 'rgba(59, 130, 246, 0.2)'); |
| } else { |
| |
| const intensity = 200 - Math.min(190, index * 5); |
| gradient = `rgb(59, 130, ${intensity})`; |
| } |
| } |
| |
| ctx.fillStyle = gradient; |
| |
| |
| const radius = gridSize / 4; |
| |
| ctx.beginPath(); |
| ctx.moveTo(x + radius, y); |
| ctx.lineTo(x + gridSize - radius, y); |
| ctx.quadraticCurveTo(x + gridSize, y, x + gridSize, y + radius); |
| ctx.lineTo(x + gridSize, y + gridSize - radius); |
| ctx.quadraticCurveTo(x + gridSize, y + gridSize, x + gridSize - radius, y + gridSize); |
| ctx.lineTo(x + radius, y + gridSize); |
| ctx.quadraticCurveTo(x, y + gridSize, x, y + gridSize - radius); |
| ctx.lineTo(x, y + radius); |
| ctx.quadraticCurveTo(x, y, x + radius, y); |
| ctx.closePath(); |
| ctx.fill(); |
| |
| |
| if (index === 0) { |
| ctx.shadowColor = rainbowMode ? colors[Math.floor(Math.random() * colors.length)] : '#3b82f6'; |
| ctx.shadowBlur = 15; |
| ctx.fill(); |
| ctx.shadowBlur = 0; |
| } |
| |
| |
| if (gameRunning && index === 0) { |
| createTrail(segment.x, segment.y); |
| } |
| }); |
| } |
| |
| function drawFood() { |
| const x = food.x * gridSize + gridSize / 2; |
| const y = food.y * gridSize + gridSize / 2; |
| const radius = gridSize / 2; |
| |
| |
| ctx.shadowColor = '#ef4444'; |
| ctx.shadowBlur = 15; |
| |
| |
| const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius); |
| gradient.addColorStop(0, '#ef4444'); |
| gradient.addColorStop(1, '#7f1d1d'); |
| ctx.fillStyle = gradient; |
| |
| ctx.beginPath(); |
| ctx.arc(x, y, radius, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; |
| ctx.beginPath(); |
| ctx.arc(x - radius/3, y - radius/3, radius/4, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| const spikeCount = 8; |
| for (let i = 0; i < spikeCount; i++) { |
| const angle = (i / spikeCount) * Math.PI * 2; |
| const spikeLength = radius * 0.6; |
| const spikeWidth = radius * 0.2; |
| |
| ctx.fillStyle = '#ef4444'; |
| ctx.beginPath(); |
| ctx.moveTo( |
| x + Math.cos(angle) * radius, |
| y + Math.sin(angle) * radius |
| ); |
| ctx.lineTo( |
| x + Math.cos(angle) * (radius + spikeLength), |
| y + Math.sin(angle) * (radius + spikeLength) |
| ); |
| ctx.lineTo( |
| x + Math.cos(angle + 0.2) * (radius + spikeWidth), |
| y + Math.sin(angle + 0.2) * (radius + spikeWidth) |
| ); |
| ctx.closePath(); |
| ctx.fill(); |
| } |
| |
| |
| ctx.shadowBlur = 0; |
| } |
| |
| function drawPowerUps() { |
| powerUps.forEach(pu => { |
| const x = pu.x * gridSize + gridSize / 2; |
| const y = pu.y * gridSize + gridSize / 2; |
| const radius = gridSize / 2; |
| |
| |
| ctx.shadowColor = '#eab308'; |
| ctx.shadowBlur = 15; |
| |
| |
| const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius); |
| gradient.addColorStop(0, '#eab308'); |
| gradient.addColorStop(1, '#713f12'); |
| ctx.fillStyle = gradient; |
| |
| ctx.beginPath(); |
| ctx.arc(x, y, radius, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; |
| ctx.beginPath(); |
| drawStar(x, y, 5, radius * 0.5, radius * 0.2); |
| ctx.fill(); |
| |
| |
| ctx.shadowBlur = 0; |
| }); |
| } |
| |
| function drawStar(cx, cy, spikes, outerRadius, innerRadius) { |
| let rot = Math.PI / 2 * 3; |
| let x = cx; |
| let y = cy; |
| let step = Math.PI / spikes; |
| |
| ctx.beginPath(); |
| ctx.moveTo(cx, cy - outerRadius); |
| |
| for (let i = 0; i < spikes; i++) { |
| x = cx + Math.cos(rot) * outerRadius; |
| y = cy + Math.sin(rot) * outerRadius; |
| ctx.lineTo(x, y); |
| rot += step; |
| |
| x = cx + Math.cos(rot) * innerRadius; |
| y = cy + Math.sin(rot) * innerRadius; |
| ctx.lineTo(x, y); |
| rot += step; |
| } |
| |
| ctx.lineTo(cx, cy - outerRadius); |
| ctx.closePath(); |
| } |
| |
| function drawGrid() { |
| ctx.strokeStyle = 'rgba(30, 41, 59, 0.5)'; |
| ctx.lineWidth = 0.5; |
| |
| for (let i = 0; i < tileCount; i++) { |
| |
| ctx.beginPath(); |
| ctx.moveTo(i * gridSize, 0); |
| ctx.lineTo(i * gridSize, canvas.height); |
| ctx.stroke(); |
| |
| |
| ctx.beginPath(); |
| ctx.moveTo(0, i * gridSize); |
| ctx.lineTo(canvas.width, i * gridSize); |
| ctx.stroke(); |
| } |
| } |
| |
| function drawBackground() { |
| |
| const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); |
| gradient.addColorStop(0, '#0f172a'); |
| gradient.addColorStop(1, '#1e293b'); |
| ctx.fillStyle = gradient; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| |
| if (Math.random() < 0.02) { |
| const x = Math.random() * canvas.width; |
| const y = Math.random() * canvas.height; |
| const size = Math.random() * 2 + 1; |
| const opacity = Math.random() * 0.5 + 0.3; |
| |
| ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`; |
| ctx.beginPath(); |
| ctx.arc(x, y, size, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| } |
| |
| |
| function update() { |
| |
| direction = {...nextDirection}; |
| |
| |
| const head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y}; |
| |
| |
| if (!invincible && (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount)) { |
| createParticles( |
| head.x * gridSize + gridSize / 2, |
| head.y * gridSize + gridSize / 2, |
| '#ef4444', |
| 30 |
| ); |
| gameOver(); |
| return; |
| } |
| |
| |
| if (invincible) { |
| if (head.x < 0) head.x = tileCount - 1; |
| if (head.x >= tileCount) head.x = 0; |
| if (head.y < 0) head.y = tileCount - 1; |
| if (head.y >= tileCount) head.y = 0; |
| } |
| |
| |
| if (!invincible) { |
| for (let i = 1; i < snake.length; i++) { |
| if (head.x === snake[i].x && head.y === snake[i].y) { |
| createParticles( |
| head.x * gridSize + gridSize / 2, |
| head.y * gridSize + gridSize / 2, |
| '#ef4444', |
| 30 |
| ); |
| gameOver(); |
| return; |
| } |
| } |
| } |
| |
| |
| snake.unshift(head); |
| |
| |
| if (head.x === food.x && head.y === food.y) { |
| |
| createParticles( |
| food.x * gridSize + gridSize / 2, |
| food.y * gridSize + gridSize / 2, |
| null, |
| 20 |
| ); |
| |
| |
| score += 10; |
| scoreElement.textContent = score; |
| |
| |
| generateFood(); |
| |
| |
| if (score % 50 === 0 && speed < 15) { |
| speed += 0.5; |
| } |
| |
| |
| if (Math.random() < 0.3 && powerUps.length < 2) { |
| generatePowerUp(); |
| } |
| } else { |
| |
| snake.pop(); |
| } |
| |
| |
| for (let i = powerUps.length - 1; i >= 0; i--) { |
| const pu = powerUps[i]; |
| if (head.x === pu.x && head.y === pu.y) { |
| |
| applyPowerUp(pu.type); |
| |
| |
| powerUps.splice(i, 1); |
| |
| |
| createParticles( |
| pu.x * gridSize + gridSize / 2, |
| pu.y * gridSize + gridSize / 2, |
| '#eab308', |
| 25 |
| ); |
| } |
| } |
| |
| |
| if (powerUpTimer > 0) { |
| powerUpTimer--; |
| if (powerUpTimer === 0) { |
| invincible = false; |
| rainbowMode = false; |
| } |
| } |
| |
| |
| if (rainbowTimer > 0) { |
| rainbowTimer--; |
| if (rainbowTimer === 0) { |
| rainbowMode = false; |
| } |
| } |
| } |
| |
| function applyPowerUp(type) { |
| switch(type) { |
| case 'invincible': |
| invincible = true; |
| powerUpTimer = 300; |
| break; |
| case 'rainbow': |
| rainbowMode = true; |
| rainbowTimer = 450; |
| break; |
| case 'speed': |
| speed = Math.max(5, speed - 2); |
| powerUpTimer = 300; |
| break; |
| } |
| |
| |
| score += 50; |
| scoreElement.textContent = score; |
| } |
| |
| function generateFood() { |
| let validPosition = false; |
| |
| while (!validPosition) { |
| food = { |
| x: Math.floor(Math.random() * tileCount), |
| y: Math.floor(Math.random() * tileCount) |
| }; |
| |
| |
| validPosition = true; |
| |
| for (let segment of snake) { |
| if (segment.x === food.x && segment.y === food.y) { |
| validPosition = false; |
| break; |
| } |
| } |
| |
| if (validPosition) { |
| for (let pu of powerUps) { |
| if (pu.x === food.x && pu.y === food.y) { |
| validPosition = false; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| function generatePowerUp() { |
| let validPosition = false; |
| const types = ['invincible', 'rainbow', 'speed']; |
| const type = types[Math.floor(Math.random() * types.length)]; |
| |
| while (!validPosition) { |
| const pu = { |
| x: Math.floor(Math.random() * tileCount), |
| y: Math.floor(Math.random() * tileCount), |
| type: type |
| }; |
| |
| |
| validPosition = true; |
| |
| for (let segment of snake) { |
| if (segment.x === pu.x && segment.y === pu.y) { |
| validPosition = false; |
| break; |
| } |
| } |
| |
| if (validPosition && (food.x === pu.x && food.y === pu.y)) { |
| validPosition = false; |
| } |
| |
| if (validPosition) { |
| for (let existingPu of powerUps) { |
| if (existingPu.x === pu.x && existingPu.y === pu.y) { |
| validPosition = false; |
| break; |
| } |
| } |
| } |
| |
| if (validPosition) { |
| powerUps.push(pu); |
| break; |
| } |
| } |
| } |
| |
| function gameOver() { |
| cancelAnimationFrame(gameLoop); |
| gameRunning = false; |
| |
| |
| if (score > highScore) { |
| highScore = score; |
| localStorage.setItem('snakeHighScore', highScore); |
| highScoreElement.textContent = highScore; |
| } |
| |
| finalScoreElement.textContent = score; |
| finalHighScoreElement.textContent = highScore; |
| gameOverScreen.classList.remove('hidden'); |
| } |
| |
| function resetGame() { |
| snake = [{x: 10, y: 10}]; |
| direction = {x: 0, y: 0}; |
| nextDirection = {x: 0, y: 0}; |
| score = 0; |
| scoreElement.textContent = score; |
| speed = 7; |
| powerUps = []; |
| particles = []; |
| trails = []; |
| invincible = false; |
| rainbowMode = false; |
| powerUpTimer = 0; |
| rainbowTimer = 0; |
| generateFood(); |
| gameOverScreen.classList.add('hidden'); |
| } |
| |
| function gameLoop(timestamp) { |
| if (!gameRunning) return; |
| |
| const secondsSinceLastRender = (timestamp - lastRenderTime) / 1000; |
| if (secondsSinceLastRender < 1 / speed) { |
| requestAnimationFrame(gameLoop); |
| return; |
| } |
| lastRenderTime = timestamp; |
| |
| drawBackground(); |
| drawGrid(); |
| updateParticles(); |
| drawParticles(); |
| drawPowerUps(); |
| drawFood(); |
| drawSnake(); |
| update(); |
| |
| requestAnimationFrame(gameLoop); |
| } |
| |
| function startGame() { |
| resetGame(); |
| startScreen.classList.add('hidden'); |
| gameRunning = true; |
| lastRenderTime = 0; |
| requestAnimationFrame(gameLoop); |
| } |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (!gameRunning && (e.key === ' ' || e.key === 'Enter')) { |
| startGame(); |
| return; |
| } |
| |
| |
| switch (e.key) { |
| case 'ArrowUp': |
| case 'w': |
| case 'W': |
| if (direction.y === 0) nextDirection = {x: 0, y: -1}; |
| break; |
| case 'ArrowDown': |
| case 's': |
| case 'S': |
| if (direction.y === 0) nextDirection = {x: 0, y: 1}; |
| break; |
| case 'ArrowLeft': |
| case 'a': |
| case 'A': |
| if (direction.x === 0) nextDirection = {x: -1, y: 0}; |
| break; |
| case 'ArrowRight': |
| case 'd': |
| case 'D': |
| if (direction.x === 0) nextDirection = {x: 1, y: 0}; |
| break; |
| } |
| }); |
| |
| |
| document.getElementById('upBtn').addEventListener('touchstart', (e) => { |
| e.preventDefault(); |
| if (direction.y === 0) nextDirection = {x: 0, y: -1}; |
| }); |
| |
| document.getElementById('downBtn').addEventListener('touchstart', (e) => { |
| e.preventDefault(); |
| if (direction.y === 0) nextDirection = {x: 0, y: 1}; |
| }); |
| |
| document.getElementById('leftBtn').addEventListener('touchstart', (e) => { |
| e.preventDefault(); |
| if (direction.x === 0) nextDirection = {x: -1, y: 0}; |
| }); |
| |
| document.getElementById('rightBtn').addEventListener('touchstart', (e) => { |
| e.preventDefault(); |
| if (direction.x === 0) nextDirection = {x: 1, y: 0}; |
| }); |
| |
| |
| canvas.addEventListener('click', (e) => { |
| if (!gameRunning) return; |
| |
| const rect = canvas.getBoundingClientRect(); |
| const clickX = e.clientX - rect.left; |
| const clickY = e.clientY - rect.top; |
| const headX = snake[0].x * gridSize + gridSize / 2; |
| const headY = snake[0].y * gridSize + gridSize / 2; |
| |
| const diffX = clickX - headX; |
| const diffY = clickY - headY; |
| |
| |
| if (Math.abs(diffX) > Math.abs(diffY)) { |
| |
| if (diffX > 0 && direction.x === 0) { |
| nextDirection = {x: 1, y: 0}; |
| } else if (diffX < 0 && direction.x === 0) { |
| nextDirection = {x: -1, y: 0}; |
| } |
| } else { |
| |
| if (diffY > 0 && direction.y === 0) { |
| nextDirection = {x: 0, y: 1}; |
| } else if (diffY < 0 && direction.y === 0) { |
| nextDirection = {x: 0, y: -1}; |
| } |
| } |
| }); |
| |
| |
| let touchStartX = 0; |
| let touchStartY = 0; |
| |
| canvas.addEventListener('touchstart', (e) => { |
| touchStartX = e.touches[0].clientX; |
| touchStartY = e.touches[0].clientY; |
| e.preventDefault(); |
| }, { passive: false }); |
| |
| canvas.addEventListener('touchmove', (e) => { |
| if (!touchStartX || !touchStartY || !gameRunning) 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 > 30 && direction.x === 0) { |
| nextDirection = {x: -1, y: 0}; |
| touchStartX = 0; |
| touchStartY = 0; |
| } else if (diffX < -30 && direction.x === 0) { |
| nextDirection = {x: 1, y: 0}; |
| touchStartX = 0; |
| touchStartY = 0; |
| } |
| } else { |
| |
| if (diffY > 30 && direction.y === 0) { |
| nextDirection = {x: 0, y: -1}; |
| touchStartX = 0; |
| touchStartY = 0; |
| } else if (diffY < -30 && direction.y === 0) { |
| nextDirection = {x: 0, y: 1}; |
| touchStartX = 0; |
| touchStartY = 0; |
| } |
| } |
| |
| e.preventDefault(); |
| }, { passive: false }); |
| |
| |
| startButton.addEventListener('click', startGame); |
| restartButton.addEventListener('click', startGame); |
| |
| |
| startButton.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter' || e.key === ' ') { |
| startGame(); |
| } |
| }); |
| |
| restartButton.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter' || e.key === ' ') { |
| startGame(); |
| } |
| }); |
| |
| |
| drawBackground(); |
| drawGrid(); |
| drawFood(); |
| drawSnake(); |
| |
| |
| if ('ontouchstart' in window || navigator.maxTouchPoints) { |
| mobileControls.classList.remove('hidden'); |
| } else { |
| mobileControls.classList.add('hidden'); |
| } |
| }); |
| </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=pijou/snake" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |