Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Underwater Snake Game</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| background: linear-gradient(180deg, #0066cc 0%, #004080 50%, #001a33 100%); | |
| font-family: 'Arial', sans-serif; | |
| color: white; | |
| overflow: hidden; | |
| } | |
| .bubbles { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| z-index: 1; | |
| } | |
| .bubble { | |
| position: absolute; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 50%; | |
| animation: rise 8s infinite linear; | |
| } | |
| @keyframes rise { | |
| 0% { transform: translateY(100vh) scale(0); opacity: 0; } | |
| 10% { opacity: 1; } | |
| 90% { opacity: 1; } | |
| 100% { transform: translateY(-100px) scale(1); opacity: 0; } | |
| } | |
| .game-container { | |
| text-align: center; | |
| background: rgba(0, 50, 100, 0.3); | |
| backdrop-filter: blur(10px); | |
| border-radius: 20px; | |
| padding: 30px; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); | |
| z-index: 2; | |
| position: relative; | |
| } | |
| h1 { | |
| margin: 0 0 20px 0; | |
| font-size: 2.5em; | |
| text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); | |
| color: #00ccff; | |
| } | |
| .score { | |
| font-size: 1.5em; | |
| margin-bottom: 20px; | |
| font-weight: bold; | |
| color: #66ff99; | |
| } | |
| .level { | |
| font-size: 1.2em; | |
| margin-bottom: 10px; | |
| color: #ffcc00; | |
| } | |
| #gameCanvas { | |
| border: 3px solid rgba(0, 200, 255, 0.5); | |
| border-radius: 15px; | |
| background: radial-gradient(ellipse at center, rgba(0, 100, 200, 0.3) 0%, rgba(0, 50, 150, 0.6) 100%); | |
| box-shadow: inset 0 0 30px rgba(0, 150, 255, 0.3); | |
| } | |
| .controls { | |
| margin-top: 20px; | |
| font-size: 1.1em; | |
| opacity: 0.9; | |
| color: #ccffff; | |
| } | |
| .control-buttons { | |
| margin-top: 15px; | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 10px; | |
| max-width: 200px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| } | |
| .control-btn { | |
| background: rgba(0, 150, 255, 0.3); | |
| border: 2px solid rgba(0, 200, 255, 0.6); | |
| color: white; | |
| padding: 15px; | |
| font-size: 1.2em; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| user-select: none; | |
| } | |
| .control-btn:hover { | |
| background: rgba(0, 150, 255, 0.5); | |
| transform: scale(1.1); | |
| } | |
| .control-btn:active { | |
| transform: scale(0.95); | |
| } | |
| .empty { opacity: 0; pointer-events: none; } | |
| .game-over { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(0, 30, 60, 0.9); | |
| padding: 30px; | |
| border-radius: 15px; | |
| text-align: center; | |
| display: none; | |
| border: 2px solid rgba(0, 200, 255, 0.5); | |
| } | |
| .restart-btn { | |
| background: linear-gradient(45deg, #00ccff, #0080ff); | |
| border: none; | |
| color: white; | |
| padding: 12px 25px; | |
| font-size: 1.2em; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| margin-top: 15px; | |
| transition: transform 0.2s; | |
| } | |
| .restart-btn:hover { | |
| transform: scale(1.05); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bubbles" id="bubbles"></div> | |
| <div class="game-container"> | |
| <h1>🐍 Underwater Snake 🌊</h1> | |
| <div class="level">Level: <span id="level">1</span></div> | |
| <div class="score">Fish Caught: <span id="score">0</span></div> | |
| <canvas id="gameCanvas" width="400" height="400"></canvas> | |
| <div class="controls"> | |
| Control your sea snake to catch swimming fish! | |
| </div> | |
| <div class="control-buttons"> | |
| <div class="empty"></div> | |
| <button class="control-btn" onmousedown="setDirection(0, -1)" ontouchstart="setDirection(0, -1)">↑</button> | |
| <div class="empty"></div> | |
| <button class="control-btn" onmousedown="setDirection(-1, 0)" ontouchstart="setDirection(-1, 0)">←</button> | |
| <div class="empty"></div> | |
| <button class="control-btn" onmousedown="setDirection(1, 0)" ontouchstart="setDirection(1, 0)">→</button> | |
| <div class="empty"></div> | |
| <button class="control-btn" onmousedown="setDirection(0, 1)" ontouchstart="setDirection(0, 1)">↓</button> | |
| <div class="empty"></div> | |
| </div> | |
| <div class="game-over" id="gameOver"> | |
| <h2>Game Over!</h2> | |
| <p>Level Reached: <span id="finalLevel">1</span></p> | |
| <p>Fish Caught: <span id="finalScore">0</span></p> | |
| <button class="restart-btn" onclick="restartGame()">Dive Again</button> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const scoreElement = document.getElementById('score'); | |
| const levelElement = document.getElementById('level'); | |
| const gameOverElement = document.getElementById('gameOver'); | |
| const finalScoreElement = document.getElementById('finalScore'); | |
| const finalLevelElement = document.getElementById('finalLevel'); | |
| // Game variables | |
| const gridSize = 20; | |
| const tileCount = canvas.width / gridSize; | |
| let snake = [ | |
| {x: 10, y: 10} | |
| ]; | |
| let fish = []; | |
| let bloodDrops = []; | |
| let dx = 0; | |
| let dy = 0; | |
| let score = 0; | |
| let level = 1; | |
| let gameSpeed = 200; // Starting speed (slower) | |
| let gameRunning = true; | |
| let audioContext; | |
| let shouldExtendTail = false; | |
| // Initialize audio context | |
| function initAudio() { | |
| try { | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| } catch (e) { | |
| console.log('Web Audio API not supported'); | |
| } | |
| } | |
| // Play eating sound | |
| function playEatingSound() { | |
| if (!audioContext) return; | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContext.destination); | |
| // Create a "chomp" sound effect | |
| oscillator.frequency.setValueAtTime(200, audioContext.currentTime); | |
| oscillator.frequency.exponentialRampToValueAtTime(100, audioContext.currentTime + 0.1); | |
| gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2); | |
| oscillator.type = 'square'; | |
| oscillator.start(audioContext.currentTime); | |
| oscillator.stop(audioContext.currentTime + 0.2); | |
| } | |
| // Blood drop class | |
| class BloodDrop { | |
| constructor(x, y) { | |
| this.x = x * gridSize + gridSize / 2; | |
| this.y = y * gridSize + gridSize / 2; | |
| this.vx = (Math.random() - 0.5) * 4; | |
| this.vy = Math.random() * 2 + 1; | |
| this.gravity = 0.3; | |
| this.life = 60; // frames | |
| this.maxLife = 60; | |
| this.size = Math.random() * 3 + 2; | |
| } | |
| update() { | |
| this.x += this.vx; | |
| this.y += this.vy; | |
| this.vy += this.gravity; | |
| this.life--; | |
| // Bounce off sides | |
| if (this.x <= 0 || this.x >= canvas.width) { | |
| this.vx *= -0.5; | |
| this.x = Math.max(0, Math.min(canvas.width, this.x)); | |
| } | |
| // Stop at bottom | |
| if (this.y >= canvas.height - this.size) { | |
| this.y = canvas.height - this.size; | |
| this.vy = 0; | |
| this.vx *= 0.8; | |
| } | |
| } | |
| draw() { | |
| const alpha = this.life / this.maxLife; | |
| ctx.save(); | |
| ctx.globalAlpha = alpha; | |
| // Blood drop gradient | |
| const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size); | |
| gradient.addColorStop(0, '#ff0000'); | |
| gradient.addColorStop(0.5, '#cc0000'); | |
| gradient.addColorStop(1, '#800000'); | |
| ctx.fillStyle = gradient; | |
| ctx.shadowColor = '#ff0000'; | |
| ctx.shadowBlur = 5; | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| isDead() { | |
| return this.life <= 0; | |
| } | |
| } | |
| class Fish { | |
| constructor() { | |
| this.x = Math.floor(Math.random() * tileCount); | |
| this.y = Math.floor(Math.random() * tileCount); | |
| this.dx = (Math.random() - 0.5) * 0.3; | |
| this.dy = (Math.random() - 0.5) * 0.3; | |
| this.color = this.getRandomFishColor(); | |
| this.size = Math.random() * 0.3 + 0.7; | |
| this.swimTimer = 0; | |
| } | |
| getRandomFishColor() { | |
| const colors = ['#ff6b35', '#f7931e', '#ffdd00', '#ff1744', '#e91e63', '#9c27b0']; | |
| return colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| update() { | |
| this.swimTimer += 0.1; | |
| // Random direction changes | |
| if (Math.random() < 0.02) { | |
| this.dx = (Math.random() - 0.5) * 0.3; | |
| this.dy = (Math.random() - 0.5) * 0.3; | |
| } | |
| // Move fish | |
| this.x += this.dx; | |
| this.y += this.dy; | |
| // Bounce off walls | |
| if (this.x <= 0 || this.x >= tileCount - 1) { | |
| this.dx *= -1; | |
| this.x = Math.max(0, Math.min(tileCount - 1, this.x)); | |
| } | |
| if (this.y <= 0 || this.y >= tileCount - 1) { | |
| this.dy *= -1; | |
| this.y = Math.max(0, Math.min(tileCount - 1, this.y)); | |
| } | |
| } | |
| draw() { | |
| const x = this.x * gridSize + gridSize / 2; | |
| const y = this.y * gridSize + gridSize / 2; | |
| const size = gridSize * this.size; | |
| ctx.save(); | |
| ctx.translate(x, y); | |
| // Fish body | |
| ctx.fillStyle = this.color; | |
| ctx.shadowColor = this.color; | |
| ctx.shadowBlur = 8; | |
| ctx.beginPath(); | |
| ctx.ellipse(0, 0, size * 0.6, size * 0.4, 0, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| // Fish tail | |
| ctx.beginPath(); | |
| ctx.moveTo(-size * 0.6, 0); | |
| ctx.lineTo(-size * 0.9, -size * 0.3); | |
| ctx.lineTo(-size * 0.9, size * 0.3); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Fish eye | |
| ctx.fillStyle = 'white'; | |
| ctx.shadowBlur = 0; | |
| ctx.beginPath(); | |
| ctx.arc(size * 0.2, -size * 0.1, size * 0.15, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| ctx.fillStyle = 'black'; | |
| ctx.beginPath(); | |
| ctx.arc(size * 0.25, -size * 0.1, size * 0.08, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| } | |
| // Draw snake head with mouth and direction | |
| function drawSnakeHead(x, y, direction) { | |
| const centerX = x + gridSize / 2; | |
| const centerY = y + gridSize / 2; | |
| const size = gridSize * 0.4; | |
| ctx.save(); | |
| ctx.translate(centerX, centerY); | |
| // Rotate based on direction | |
| let angle = 0; | |
| if (direction.x === 1) angle = 0; // Right | |
| else if (direction.x === -1) angle = Math.PI; // Left | |
| else if (direction.y === 1) angle = Math.PI / 2; // Down | |
| else if (direction.y === -1) angle = -Math.PI / 2; // Up | |
| ctx.rotate(angle); | |
| // Head shape | |
| ctx.fillStyle = '#00ff80'; | |
| ctx.shadowColor = '#00ff80'; | |
| ctx.shadowBlur = 15; | |
| ctx.beginPath(); | |
| ctx.ellipse(0, 0, size + 2, size, 0, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| // Snake pattern on head | |
| ctx.shadowBlur = 0; | |
| ctx.fillStyle = '#00cc60'; | |
| ctx.beginPath(); | |
| ctx.ellipse(-2, -size/3, size/3, size/6, 0, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.ellipse(-2, size/3, size/3, size/6, 0, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| // Eyes | |
| ctx.fillStyle = '#ff0000'; | |
| ctx.beginPath(); | |
| ctx.arc(size/2, -size/3, 3, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(size/2, size/3, 3, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| // Eye pupils | |
| ctx.fillStyle = '#000000'; | |
| ctx.beginPath(); | |
| ctx.arc(size/2 + 1, -size/3, 1.5, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(size/2 + 1, size/3, 1.5, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| // Mouth | |
| ctx.strokeStyle = '#003300'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(size + 2, -3); | |
| ctx.lineTo(size + 6, 0); | |
| ctx.lineTo(size + 2, 3); | |
| ctx.stroke(); | |
| // Forked tongue (occasionally) | |
| if (Math.random() < 0.1) { | |
| ctx.strokeStyle = '#ff3366'; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.moveTo(size + 6, 0); | |
| ctx.lineTo(size + 10, -2); | |
| ctx.moveTo(size + 6, 0); | |
| ctx.lineTo(size + 10, 2); | |
| ctx.stroke(); | |
| } | |
| ctx.restore(); | |
| } | |
| // Draw snake tail | |
| function drawSnakeTail(x, y, prevX, prevY) { | |
| const centerX = x + gridSize / 2; | |
| const centerY = y + gridSize / 2; | |
| // Direction from current to previous segment | |
| const dirX = prevX - x; | |
| const dirY = prevY - y; | |
| ctx.save(); | |
| ctx.translate(centerX, centerY); | |
| // Rotate based on direction | |
| let angle = 0; | |
| if (dirX > 0) angle = 0; // Right | |
| else if (dirX < 0) angle = Math.PI; // Left | |
| else if (dirY > 0) angle = Math.PI / 2; // Down | |
| else if (dirY < 0) angle = -Math.PI / 2; // Up | |
| ctx.rotate(angle); | |
| // Tail gradient | |
| const gradient = ctx.createLinearGradient(-gridSize/2, 0, gridSize/2, 0); | |
| gradient.addColorStop(0, 'rgba(0, 255, 128, 0.8)'); | |
| gradient.addColorStop(1, 'rgba(0, 255, 128, 0.2)'); | |
| ctx.fillStyle = gradient; | |
| ctx.shadowColor = '#00ff80'; | |
| ctx.shadowBlur = 8; | |
| // Tapered tail shape | |
| ctx.beginPath(); | |
| ctx.moveTo(-gridSize/3, -gridSize/4); | |
| ctx.lineTo(gridSize/2, -2); | |
| ctx.lineTo(gridSize/2, 2); | |
| ctx.lineTo(-gridSize/3, gridSize/4); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| // Generate fish | |
| function spawnFish() { | |
| // Always maintain 3-4 fish on screen | |
| while (fish.length < 3 + Math.floor(level / 3)) { | |
| fish.push(new Fish()); | |
| } | |
| } | |
| // Draw underwater background effects | |
| function drawWaterEffects() { | |
| // Water gradient | |
| const gradient = ctx.createRadialGradient(canvas.width/2, canvas.height/4, 0, canvas.width/2, canvas.height/2, canvas.width); | |
| gradient.addColorStop(0, 'rgba(0, 150, 255, 0.1)'); | |
| gradient.addColorStop(0.5, 'rgba(0, 100, 200, 0.2)'); | |
| gradient.addColorStop(1, 'rgba(0, 50, 150, 0.3)'); | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Animated water lines | |
| ctx.strokeStyle = 'rgba(0, 200, 255, 0.1)'; | |
| ctx.lineWidth = 1; | |
| for (let i = 0; i < 5; i++) { | |
| ctx.beginPath(); | |
| ctx.moveTo(0, i * 80 + Math.sin(Date.now() * 0.001 + i) * 10); | |
| ctx.lineTo(canvas.width, i * 80 + Math.sin(Date.now() * 0.001 + i + 1) * 10); | |
| ctx.stroke(); | |
| } | |
| } | |
| // Draw game elements | |
| function drawGame() { | |
| drawWaterEffects(); | |
| // Draw snake body segments | |
| ctx.shadowBlur = 10; | |
| for (let i = snake.length - 1; i >= 0; i--) { | |
| const segment = snake[i]; | |
| const x = segment.x * gridSize; | |
| const y = segment.y * gridSize; | |
| if (i === 0) { | |
| // Snake head - draw last so it's on top | |
| const direction = {x: dx, y: dy}; | |
| drawSnakeHead(x, y, direction); | |
| } else if (i === snake.length - 1) { | |
| // Snake tail | |
| const prevSegment = snake[i - 1]; | |
| drawSnakeTail(x, y, prevSegment.x * gridSize, prevSegment.y * gridSize); | |
| } else { | |
| // Snake body with gradient | |
| const alpha = (snake.length - i) / snake.length; | |
| ctx.fillStyle = `rgba(0, 255, 128, ${alpha * 0.9})`; | |
| ctx.shadowColor = '#00ff80'; | |
| ctx.beginPath(); | |
| ctx.roundRect(x + 1, y + 1, gridSize - 2, gridSize - 2, 8); | |
| ctx.fill(); | |
| // Add scales pattern | |
| ctx.shadowBlur = 0; | |
| ctx.fillStyle = `rgba(0, 200, 100, ${alpha * 0.6})`; | |
| ctx.fillRect(x + 3, y + 3, gridSize - 6, 2); | |
| ctx.fillRect(x + 3, y + gridSize - 5, gridSize - 6, 2); | |
| ctx.shadowBlur = 10; | |
| } | |
| } | |
| ctx.shadowBlur = 0; | |
| // Update and draw fish | |
| fish.forEach((f, index) => { | |
| f.update(); | |
| f.draw(); | |
| }); | |
| // Update and draw blood drops | |
| for (let i = bloodDrops.length - 1; i >= 0; i--) { | |
| const drop = bloodDrops[i]; | |
| drop.update(); | |
| drop.draw(); | |
| if (drop.isDead()) { | |
| bloodDrops.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Move snake | |
| function moveSnake() { | |
| if (dx === 0 && dy === 0) return; | |
| const head = {x: snake[0].x + dx, y: snake[0].y + dy}; | |
| // Check wall collision | |
| if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) { | |
| gameOver(); | |
| return; | |
| } | |
| // Check self collision | |
| for (let segment of snake) { | |
| if (head.x === segment.x && head.y === segment.y) { | |
| gameOver(); | |
| return; | |
| } | |
| } | |
| snake.unshift(head); | |
| // Check fish collision | |
| let fishEaten = false; | |
| for (let i = fish.length - 1; i >= 0; i--) { | |
| const f = fish[i]; | |
| if (Math.floor(f.x) === head.x && Math.floor(f.y) === head.y) { | |
| // Fish caught! | |
| score += 10; | |
| scoreElement.textContent = score; | |
| // Play eating sound | |
| playEatingSound(); | |
| // Create blood drops at fish position | |
| for (let j = 0; j < 8; j++) { | |
| bloodDrops.push(new BloodDrop(f.x, f.y)); | |
| } | |
| // Remove eaten fish | |
| fish.splice(i, 1); | |
| // Mark that tail should extend | |
| fishEaten = true; | |
| // Level up every 50 points | |
| const newLevel = Math.floor(score / 50) + 1; | |
| if (newLevel > level) { | |
| level = newLevel; | |
| levelElement.textContent = level; | |
| // Increase speed (decrease interval) | |
| gameSpeed = Math.max(80, 200 - (level - 1) * 15); | |
| clearInterval(gameInterval); | |
| gameInterval = setInterval(gameLoop, gameSpeed); | |
| } | |
| break; | |
| } | |
| } | |
| // Only remove tail if no fish was eaten | |
| if (!fishEaten) { | |
| snake.pop(); | |
| } | |
| } | |
| // Set direction function for buttons | |
| function setDirection(newDx, newDy) { | |
| if (!audioContext) { | |
| initAudio(); | |
| } | |
| if (!gameRunning) return; | |
| // Prevent reverse direction | |
| if ((newDx === 1 && dx !== -1) || (newDx === -1 && dx !== 1) || newDx === 0) { | |
| if ((newDy === 1 && dy !== -1) || (newDy === -1 && dy !== 1) || newDy === 0) { | |
| dx = newDx; | |
| dy = newDy; | |
| } | |
| } | |
| } | |
| // Game over | |
| function gameOver() { | |
| gameRunning = false; | |
| clearInterval(gameInterval); | |
| finalScoreElement.textContent = score; | |
| finalLevelElement.textContent = level; | |
| gameOverElement.style.display = 'block'; | |
| } | |
| // Restart game | |
| function restartGame() { | |
| snake = [{x: 10, y: 10}]; | |
| fish = []; | |
| bloodDrops = []; | |
| dx = 0; | |
| dy = 0; | |
| score = 0; | |
| level = 1; | |
| gameSpeed = 200; | |
| gameRunning = true; | |
| scoreElement.textContent = score; | |
| levelElement.textContent = level; | |
| gameOverElement.style.display = 'none'; | |
| clearInterval(gameInterval); | |
| gameInterval = setInterval(gameLoop, gameSpeed); | |
| } | |
| // Handle keyboard input | |
| document.addEventListener('keydown', (e) => { | |
| // Initialize audio on first user interaction | |
| if (!audioContext) { | |
| initAudio(); | |
| } | |
| const key = e.key.toLowerCase(); | |
| if (key === 'arrowleft' || key === 'a') setDirection(-1, 0); | |
| if (key === 'arrowup' || key === 'w') setDirection(0, -1); | |
| if (key === 'arrowright' || key === 'd') setDirection(1, 0); | |
| if (key === 'arrowdown' || key === 's') setDirection(0, 1); | |
| }); | |
| // Initialize audio on button click | |
| function setDirection(newDx, newDy) { | |
| if (!audioContext) { | |
| initAudio(); | |
| } | |
| if (!gameRunning) return; | |
| // Prevent reverse direction | |
| if ((newDx === 1 && dx !== -1) || (newDx === -1 && dx !== 1) || newDx === 0) { | |
| if ((newDy === 1 && dy !== -1) || (newDy === -1 && dy !== 1) || newDy === 0) { | |
| dx = newDx; | |
| dy = newDy; | |
| } | |
| } | |
| } | |
| // Create floating bubbles | |
| function createBubbles() { | |
| const bubblesContainer = document.getElementById('bubbles'); | |
| setInterval(() => { | |
| const bubble = document.createElement('div'); | |
| bubble.className = 'bubble'; | |
| bubble.style.left = Math.random() * 100 + '%'; | |
| bubble.style.width = bubble.style.height = Math.random() * 20 + 10 + 'px'; | |
| bubble.style.animationDuration = (Math.random() * 3 + 5) + 's'; | |
| bubblesContainer.appendChild(bubble); | |
| setTimeout(() => { | |
| bubble.remove(); | |
| }, 8000); | |
| }, 300); | |
| } | |
| // Game loop | |
| function gameLoop() { | |
| if (gameRunning) { | |
| moveSnake(); | |
| spawnFish(); | |
| drawGame(); | |
| } | |
| } | |
| // Initialize game | |
| let gameInterval; | |
| initAudio(); | |
| createBubbles(); | |
| spawnFish(); // Initial fish spawn | |
| drawGame(); | |
| gameInterval = setInterval(gameLoop, gameSpeed); | |
| </script> | |
| </body> | |
| </html> |