| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Whale Runner</title> |
| <style> |
| body { |
| margin: 0; |
| padding: 0; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| min-height: 100vh; |
| background: linear-gradient(180deg, #87CEEB 0%, #4682B4 50%, #191970 100%); |
| font-family: 'Arial', sans-serif; |
| overflow: hidden; |
| } |
| |
| #gameContainer { |
| position: relative; |
| width: 800px; |
| height: 600px; |
| background: linear-gradient(180deg, #87CEEB 0%, #4682B4 30%, #1e3a8a 70%, #0f172a 100%); |
| border: 3px solid #2c5aa0; |
| border-radius: 10px; |
| overflow: hidden; |
| box-shadow: 0 0 20px rgba(0,0,0,0.5); |
| } |
| |
| #gameCanvas { |
| display: block; |
| width: 100%; |
| height: 100%; |
| } |
| |
| #gameUI { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| pointer-events: none; |
| color: white; |
| z-index: 10; |
| } |
| |
| #score { |
| position: absolute; |
| top: 20px; |
| left: 20px; |
| font-size: 24px; |
| font-weight: bold; |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.8); |
| } |
| |
| #gameOver { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| text-align: center; |
| background: rgba(0,0,0,0.8); |
| padding: 30px; |
| border-radius: 15px; |
| display: none; |
| pointer-events: all; |
| } |
| |
| #gameOver h2 { |
| margin: 0 0 10px 0; |
| color: #ff6b6b; |
| font-size: 36px; |
| } |
| |
| #gameOver p { |
| margin: 10px 0; |
| font-size: 18px; |
| } |
| |
| #restartBtn { |
| background: #4CAF50; |
| color: white; |
| border: none; |
| padding: 15px 30px; |
| font-size: 18px; |
| border-radius: 8px; |
| cursor: pointer; |
| margin-top: 15px; |
| transition: background 0.3s; |
| } |
| |
| #restartBtn:hover { |
| background: #45a049; |
| } |
| |
| #instructions { |
| position: absolute; |
| bottom: 20px; |
| left: 50%; |
| transform: translateX(-50%); |
| text-align: center; |
| font-size: 16px; |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.8); |
| } |
| </style> |
| </head> |
| <body> |
| <div id="gameContainer"> |
| <canvas id="gameCanvas" width="800" height="600"></canvas> |
| <div id="gameUI"> |
| <div id="score">Score: 0</div> |
| <div id="gameOver"> |
| <h2>Game Over!</h2> |
| <p>Final Score: <span id="finalScore">0</span></p> |
| <p>Best Score: <span id="bestScore">0</span></p> |
| <button id="restartBtn">Play Again</button> |
| </div> |
| <div id="instructions"> |
| Click or Press SPACE to make the whale swim up!<br> |
| Avoid the obstacles and collect bubbles for points! |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| const canvas = document.getElementById('gameCanvas'); |
| const ctx = canvas.getContext('2d'); |
| const scoreElement = document.getElementById('score'); |
| const gameOverElement = document.getElementById('gameOver'); |
| const finalScoreElement = document.getElementById('finalScore'); |
| const bestScoreElement = document.getElementById('bestScore'); |
| const restartBtn = document.getElementById('restartBtn'); |
| const instructionsElement = document.getElementById('instructions'); |
| |
| |
| let gameState = { |
| running: true, |
| score: 0, |
| bestScore: 0, |
| speed: 2 |
| }; |
| |
| |
| const whale = { |
| x: 150, |
| y: 300, |
| width: 60, |
| height: 40, |
| velocity: 0, |
| jumpPower: -12, |
| gravity: 0.8, |
| maxVelocity: 15 |
| }; |
| |
| |
| let obstacles = []; |
| let bubbles = []; |
| let particles = []; |
| let backgroundFish = []; |
| let seaSnakes = []; |
| let jellyfish = []; |
| |
| |
| function createObstacle() { |
| const gapSize = 180; |
| const minHeight = 80; |
| const maxHeight = canvas.height - gapSize - minHeight; |
| const topHeight = Math.random() * (maxHeight - minHeight) + minHeight; |
| |
| obstacles.push({ |
| x: canvas.width, |
| topHeight: topHeight, |
| bottomY: topHeight + gapSize, |
| bottomHeight: canvas.height - (topHeight + gapSize), |
| width: 60, |
| passed: false |
| }); |
| } |
| |
| |
| function createBackgroundFish() { |
| backgroundFish.push({ |
| x: canvas.width + Math.random() * 200, |
| y: Math.random() * canvas.height, |
| size: Math.random() * 20 + 15, |
| speed: Math.random() * 1 + 0.5, |
| color: Math.random() > 0.5 ? '#FF6B35' : '#F7931E', |
| wiggle: Math.random() * Math.PI * 2, |
| direction: Math.random() > 0.5 ? 1 : -1 |
| }); |
| } |
| |
| function createSeaSnake() { |
| const segments = 8; |
| let snake = { |
| segments: [], |
| speed: Math.random() * 1.5 + 1, |
| direction: Math.random() * Math.PI * 2, |
| turnSpeed: 0.02 |
| }; |
| |
| for (let i = 0; i < segments; i++) { |
| snake.segments.push({ |
| x: canvas.width + i * 15, |
| y: Math.random() * canvas.height, |
| size: Math.max(8 - i, 3) |
| }); |
| } |
| seaSnakes.push(snake); |
| } |
| |
| function createJellyfish() { |
| jellyfish.push({ |
| x: canvas.width + Math.random() * 300, |
| y: Math.random() * (canvas.height - 100) + 50, |
| size: Math.random() * 30 + 25, |
| speed: Math.random() * 0.8 + 0.3, |
| pulse: Math.random() * Math.PI * 2, |
| tentacles: Array.from({length: 6}, () => ({ |
| length: Math.random() * 40 + 30, |
| wave: Math.random() * Math.PI * 2, |
| offset: Math.random() * Math.PI * 2 |
| })), |
| color: Math.random() > 0.5 ? 'rgba(255,192,203,0.7)' : 'rgba(173,216,230,0.7)' |
| }); |
| } |
| |
| |
| function createBubble() { |
| bubbles.push({ |
| x: canvas.width + Math.random() * 200, |
| y: Math.random() * (canvas.height - 100) + 50, |
| radius: Math.random() * 15 + 10, |
| collected: false, |
| bob: Math.random() * Math.PI * 2 |
| }); |
| } |
| |
| |
| function createParticles(x, y, color, count = 5) { |
| for (let i = 0; i < count; i++) { |
| particles.push({ |
| x: x, |
| y: y, |
| vx: (Math.random() - 0.5) * 8, |
| vy: (Math.random() - 0.5) * 8, |
| life: 30, |
| maxLife: 30, |
| color: color, |
| size: Math.random() * 4 + 2 |
| }); |
| } |
| } |
| |
| |
| function jump() { |
| if (gameState.running) { |
| whale.velocity = whale.jumpPower; |
| createParticles(whale.x + whale.width/2, whale.y + whale.height, '#87CEEB', 3); |
| instructionsElement.style.display = 'none'; |
| } |
| } |
| |
| document.addEventListener('keydown', (e) => { |
| if (e.code === 'Space') { |
| e.preventDefault(); |
| jump(); |
| } |
| }); |
| |
| canvas.addEventListener('click', jump); |
| restartBtn.addEventListener('click', resetGame); |
| |
| |
| function drawBackground() { |
| |
| const time = Date.now() * 0.001; |
| |
| |
| for (let layer = 0; layer < 3; layer++) { |
| const alpha = 0.03 + layer * 0.02; |
| const speed = 0.5 + layer * 0.3; |
| |
| for (let i = 0; i < canvas.width; i += 30) { |
| const wave1 = Math.sin(i * 0.008 + time * speed) * 15; |
| const wave2 = Math.sin(i * 0.012 + time * speed * 1.5) * 10; |
| const wave = wave1 + wave2; |
| |
| ctx.fillStyle = `rgba(30, 144, 255, ${alpha})`; |
| ctx.fillRect(i, wave + layer * 50, 30, canvas.height); |
| } |
| } |
| |
| |
| ctx.strokeStyle = 'rgba(34, 139, 34, 0.4)'; |
| ctx.lineWidth = 3; |
| for (let i = 0; i < canvas.width; i += 80) { |
| const baseX = i + Math.sin(time + i * 0.01) * 5; |
| const seaweedHeight = 60 + Math.sin(i * 0.02) * 20; |
| |
| ctx.beginPath(); |
| ctx.moveTo(baseX, canvas.height); |
| |
| for (let j = 0; j < seaweedHeight; j += 5) { |
| const swayX = baseX + Math.sin(time * 2 + j * 0.1) * (j * 0.2); |
| ctx.lineTo(swayX, canvas.height - j); |
| } |
| ctx.stroke(); |
| } |
| |
| |
| ctx.fillStyle = 'rgba(160, 82, 45, 0.3)'; |
| for (let i = 0; i < canvas.width; i += 20) { |
| const sandHeight = 5 + Math.sin(i * 0.02 + time * 0.5) * 3; |
| ctx.fillRect(i, canvas.height - sandHeight, 20, sandHeight); |
| } |
| } |
| |
| function drawWhale() { |
| ctx.save(); |
| |
| |
| const gradient = ctx.createLinearGradient(whale.x, whale.y, whale.x + whale.width, whale.y + whale.height); |
| gradient.addColorStop(0, '#4682B4'); |
| gradient.addColorStop(0.5, '#87CEEB'); |
| gradient.addColorStop(1, '#4169E1'); |
| |
| ctx.fillStyle = gradient; |
| ctx.beginPath(); |
| ctx.ellipse(whale.x + whale.width/2, whale.y + whale.height/2, whale.width/2, whale.height/2, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#4169E1'; |
| ctx.beginPath(); |
| ctx.moveTo(whale.x - 10, whale.y + whale.height/2); |
| ctx.lineTo(whale.x - 25, whale.y + 5); |
| ctx.lineTo(whale.x - 25, whale.y + whale.height - 5); |
| ctx.closePath(); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = 'white'; |
| ctx.beginPath(); |
| ctx.arc(whale.x + whale.width - 15, whale.y + 12, 8, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| ctx.fillStyle = 'black'; |
| ctx.beginPath(); |
| ctx.arc(whale.x + whale.width - 12, whale.y + 12, 4, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| ctx.restore(); |
| } |
| |
| function drawObstacles() { |
| ctx.fillStyle = '#2F4F4F'; |
| obstacles.forEach(obstacle => { |
| |
| ctx.fillRect(obstacle.x, 0, obstacle.width, obstacle.topHeight); |
| |
| |
| ctx.fillRect(obstacle.x, obstacle.bottomY, obstacle.width, obstacle.bottomHeight); |
| |
| |
| ctx.fillStyle = '#1C3A3A'; |
| for (let i = 0; i < 5; i++) { |
| ctx.fillRect(obstacle.x + Math.random() * obstacle.width, Math.random() * obstacle.topHeight, 3, 3); |
| ctx.fillRect(obstacle.x + Math.random() * obstacle.width, obstacle.bottomY + Math.random() * obstacle.bottomHeight, 3, 3); |
| } |
| ctx.fillStyle = '#2F4F4F'; |
| }); |
| } |
| |
| function drawBubbles() { |
| bubbles.forEach(bubble => { |
| if (!bubble.collected) { |
| bubble.bob += 0.1; |
| const bobOffset = Math.sin(bubble.bob) * 3; |
| |
| const gradient = ctx.createRadialGradient( |
| bubble.x, bubble.y + bobOffset, 0, |
| bubble.x, bubble.y + bobOffset, bubble.radius |
| ); |
| gradient.addColorStop(0, 'rgba(255,255,255,0.8)'); |
| gradient.addColorStop(0.7, 'rgba(173,216,230,0.6)'); |
| gradient.addColorStop(1, 'rgba(135,206,235,0.3)'); |
| |
| ctx.fillStyle = gradient; |
| ctx.beginPath(); |
| ctx.arc(bubble.x, bubble.y + bobOffset, bubble.radius, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = 'rgba(255,255,255,0.9)'; |
| ctx.beginPath(); |
| ctx.arc(bubble.x - bubble.radius/3, bubble.y + bobOffset - bubble.radius/3, bubble.radius/4, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| }); |
| } |
| |
| function drawBackgroundFish() { |
| backgroundFish.forEach(fish => { |
| ctx.save(); |
| |
| |
| const gradient = ctx.createLinearGradient(fish.x - fish.size, fish.y, fish.x + fish.size, fish.y); |
| gradient.addColorStop(0, fish.color); |
| gradient.addColorStop(1, fish.color + '80'); |
| |
| ctx.fillStyle = gradient; |
| ctx.beginPath(); |
| ctx.ellipse(fish.x, fish.y, fish.size, fish.size * 0.6, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = fish.color; |
| ctx.beginPath(); |
| const tailOffset = Math.sin(fish.wiggle) * 3; |
| ctx.moveTo(fish.x - fish.size, fish.y); |
| ctx.lineTo(fish.x - fish.size * 1.5, fish.y - fish.size * 0.4 + tailOffset); |
| ctx.lineTo(fish.x - fish.size * 1.5, fish.y + fish.size * 0.4 + tailOffset); |
| ctx.closePath(); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = 'white'; |
| ctx.beginPath(); |
| ctx.arc(fish.x + fish.size * 0.3, fish.y - fish.size * 0.2, fish.size * 0.15, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| ctx.fillStyle = 'black'; |
| ctx.beginPath(); |
| ctx.arc(fish.x + fish.size * 0.35, fish.y - fish.size * 0.2, fish.size * 0.08, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| ctx.restore(); |
| }); |
| } |
| |
| function drawSeaSnakes() { |
| seaSnakes.forEach(snake => { |
| ctx.strokeStyle = '#228B22'; |
| ctx.lineWidth = 4; |
| ctx.lineCap = 'round'; |
| |
| |
| ctx.beginPath(); |
| snake.segments.forEach((segment, index) => { |
| if (index === 0) { |
| ctx.moveTo(segment.x, segment.y); |
| } else { |
| ctx.lineTo(segment.x, segment.y); |
| } |
| }); |
| ctx.stroke(); |
| |
| |
| snake.segments.forEach((segment, index) => { |
| const alpha = 1 - (index / snake.segments.length) * 0.3; |
| ctx.fillStyle = `rgba(34, 139, 34, ${alpha})`; |
| ctx.beginPath(); |
| ctx.arc(segment.x, segment.y, segment.size, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| if (index === 0) { |
| ctx.fillStyle = 'rgba(255, 255, 0, 0.8)'; |
| ctx.beginPath(); |
| ctx.arc(segment.x - 3, segment.y - 2, 2, 0, Math.PI * 2); |
| ctx.fill(); |
| ctx.beginPath(); |
| ctx.arc(segment.x - 3, segment.y + 2, 2, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| }); |
| }); |
| } |
| |
| function drawJellyfish() { |
| jellyfish.forEach(jelly => { |
| const time = Date.now() * 0.003; |
| const pulseFactor = 0.8 + Math.sin(jelly.pulse + time * 3) * 0.2; |
| const currentSize = jelly.size * pulseFactor; |
| |
| |
| const gradient = ctx.createRadialGradient( |
| jelly.x, jelly.y, 0, |
| jelly.x, jelly.y, currentSize |
| ); |
| gradient.addColorStop(0, jelly.color); |
| gradient.addColorStop(0.8, jelly.color.replace('0.7', '0.4')); |
| gradient.addColorStop(1, jelly.color.replace('0.7', '0.1')); |
| |
| ctx.fillStyle = gradient; |
| ctx.beginPath(); |
| ctx.arc(jelly.x, jelly.y, currentSize, 0, Math.PI); |
| ctx.fill(); |
| |
| |
| ctx.strokeStyle = jelly.color.replace('0.7', '0.5'); |
| ctx.lineWidth = 2; |
| ctx.lineCap = 'round'; |
| |
| jelly.tentacles.forEach(tentacle => { |
| ctx.beginPath(); |
| ctx.moveTo(jelly.x + (Math.random() - 0.5) * currentSize, jelly.y); |
| |
| for (let i = 0; i < tentacle.length; i += 5) { |
| const waveX = Math.sin(tentacle.wave + time * 2 + i * 0.1) * 8; |
| const waveY = Math.sin(tentacle.offset + time + i * 0.05) * 3; |
| ctx.lineTo( |
| jelly.x + waveX + (Math.random() - 0.5) * 3, |
| jelly.y + i + waveY |
| ); |
| } |
| ctx.stroke(); |
| }); |
| }); |
| } |
| |
| function drawParticles() { |
| particles.forEach((particle, index) => { |
| const alpha = particle.life / particle.maxLife; |
| ctx.fillStyle = particle.color + Math.floor(alpha * 255).toString(16).padStart(2, '0'); |
| ctx.beginPath(); |
| ctx.arc(particle.x, particle.y, particle.size * alpha, 0, Math.PI * 2); |
| ctx.fill(); |
| }); |
| } |
| |
| |
| function updateWhale() { |
| whale.velocity += whale.gravity; |
| whale.velocity = Math.min(whale.velocity, whale.maxVelocity); |
| whale.y += whale.velocity; |
| |
| |
| if (whale.y < 0) { |
| whale.y = 0; |
| whale.velocity = 0; |
| } |
| if (whale.y + whale.height > canvas.height) { |
| whale.y = canvas.height - whale.height; |
| whale.velocity = 0; |
| gameOver(); |
| } |
| } |
| |
| function updateObstacles() { |
| obstacles.forEach((obstacle, index) => { |
| obstacle.x -= gameState.speed; |
| |
| |
| if (!obstacle.passed && obstacle.x + obstacle.width < whale.x) { |
| obstacle.passed = true; |
| gameState.score += 10; |
| createParticles(whale.x + whale.width/2, whale.y + whale.height/2, '#FFD700', 8); |
| } |
| |
| |
| if (obstacle.x + obstacle.width < 0) { |
| obstacles.splice(index, 1); |
| } |
| }); |
| } |
| |
| function updateBubbles() { |
| bubbles.forEach((bubble, index) => { |
| bubble.x -= gameState.speed * 0.5; |
| |
| |
| if (!bubble.collected) { |
| const dx = bubble.x - (whale.x + whale.width/2); |
| const dy = bubble.y - (whale.y + whale.height/2); |
| const distance = Math.sqrt(dx*dx + dy*dy); |
| |
| if (distance < bubble.radius + 20) { |
| bubble.collected = true; |
| gameState.score += 5; |
| createParticles(bubble.x, bubble.y, '#87CEEB', 10); |
| } |
| } |
| |
| |
| if (bubble.x + bubble.radius < 0) { |
| bubbles.splice(index, 1); |
| } |
| }); |
| } |
| |
| function updateBackgroundCreatures() { |
| |
| backgroundFish.forEach((fish, index) => { |
| fish.x -= fish.speed; |
| fish.wiggle += 0.1; |
| fish.y += Math.sin(fish.wiggle) * 0.5 * fish.direction; |
| |
| |
| if (fish.y < 20) fish.direction = 1; |
| if (fish.y > canvas.height - 20) fish.direction = -1; |
| |
| |
| if (fish.x + fish.size < 0) { |
| backgroundFish.splice(index, 1); |
| } |
| }); |
| |
| |
| seaSnakes.forEach((snake, index) => { |
| snake.direction += (Math.random() - 0.5) * snake.turnSpeed; |
| |
| |
| snake.segments[0].x -= snake.speed; |
| snake.segments[0].y += Math.sin(snake.direction) * 2; |
| |
| |
| if (snake.segments[0].y < 30) snake.segments[0].y = 30; |
| if (snake.segments[0].y > canvas.height - 30) snake.segments[0].y = canvas.height - 30; |
| |
| |
| for (let i = 1; i < snake.segments.length; i++) { |
| const prev = snake.segments[i - 1]; |
| const curr = snake.segments[i]; |
| const dx = prev.x - curr.x; |
| const dy = prev.y - curr.y; |
| const distance = Math.sqrt(dx * dx + dy * dy); |
| |
| if (distance > 15) { |
| curr.x += (dx / distance) * 2; |
| curr.y += (dy / distance) * 2; |
| } |
| } |
| |
| |
| if (snake.segments[0].x < -100) { |
| seaSnakes.splice(index, 1); |
| } |
| }); |
| |
| |
| jellyfish.forEach((jelly, index) => { |
| jelly.x -= jelly.speed; |
| jelly.pulse += 0.05; |
| jelly.y += Math.sin(jelly.pulse) * 0.8; |
| |
| |
| jelly.tentacles.forEach(tentacle => { |
| tentacle.wave += 0.08; |
| tentacle.offset += 0.03; |
| }); |
| |
| |
| if (jelly.x + jelly.size < 0) { |
| jellyfish.splice(index, 1); |
| } |
| }); |
| } |
| |
| function updateParticles() { |
| particles.forEach((particle, index) => { |
| particle.x += particle.vx; |
| particle.y += particle.vy; |
| particle.life--; |
| particle.vy += 0.2; |
| |
| if (particle.life <= 0) { |
| particles.splice(index, 1); |
| } |
| }); |
| } |
| |
| function checkCollisions() { |
| obstacles.forEach(obstacle => { |
| |
| if (whale.x + whale.width > obstacle.x && |
| whale.x < obstacle.x + obstacle.width && |
| whale.y < obstacle.topHeight) { |
| gameOver(); |
| } |
| |
| |
| if (whale.x + whale.width > obstacle.x && |
| whale.x < obstacle.x + obstacle.width && |
| whale.y + whale.height > obstacle.bottomY) { |
| gameOver(); |
| } |
| }); |
| } |
| |
| function gameOver() { |
| gameState.running = false; |
| |
| if (gameState.score > gameState.bestScore) { |
| gameState.bestScore = gameState.score; |
| } |
| |
| finalScoreElement.textContent = gameState.score; |
| bestScoreElement.textContent = gameState.bestScore; |
| gameOverElement.style.display = 'block'; |
| |
| |
| createParticles(whale.x + whale.width/2, whale.y + whale.height/2, '#ff6b6b', 15); |
| } |
| |
| function resetGame() { |
| gameState.running = true; |
| gameState.score = 0; |
| gameState.speed = 2; |
| |
| whale.y = 300; |
| whale.velocity = 0; |
| |
| obstacles.length = 0; |
| bubbles.length = 0; |
| particles.length = 0; |
| backgroundFish.length = 0; |
| seaSnakes.length = 0; |
| jellyfish.length = 0; |
| |
| gameOverElement.style.display = 'none'; |
| instructionsElement.style.display = 'block'; |
| } |
| |
| |
| let lastObstacleTime = 0; |
| let lastBubbleTime = 0; |
| let lastFishTime = 0; |
| let lastSnakeTime = 0; |
| let lastJellyfishTime = 0; |
| |
| function gameLoop() { |
| |
| ctx.fillStyle = 'rgba(0,0,0,0.1)'; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| drawBackground(); |
| |
| if (gameState.running) { |
| updateWhale(); |
| updateObstacles(); |
| updateBubbles(); |
| updateBackgroundCreatures(); |
| updateParticles(); |
| checkCollisions(); |
| |
| |
| const now = Date.now(); |
| if (now - lastObstacleTime > 2000) { |
| createObstacle(); |
| lastObstacleTime = now; |
| } |
| |
| |
| if (now - lastBubbleTime > 3000 + Math.random() * 2000) { |
| createBubble(); |
| lastBubbleTime = now; |
| } |
| |
| |
| if (now - lastFishTime > 1500 + Math.random() * 2000) { |
| createBackgroundFish(); |
| lastFishTime = now; |
| } |
| |
| |
| if (now - lastSnakeTime > 4000 + Math.random() * 3000) { |
| createSeaSnake(); |
| lastSnakeTime = now; |
| } |
| |
| |
| if (now - lastJellyfishTime > 3500 + Math.random() * 4000) { |
| createJellyfish(); |
| lastJellyfishTime = now; |
| } |
| |
| |
| gameState.speed = Math.min(2 + gameState.score * 0.01, 6); |
| |
| scoreElement.textContent = `Score: ${gameState.score}`; |
| } |
| |
| |
| drawBackgroundFish(); |
| drawSeaSnakes(); |
| drawJellyfish(); |
| drawObstacles(); |
| drawBubbles(); |
| drawWhale(); |
| drawParticles(); |
| |
| requestAnimationFrame(gameLoop); |
| } |
| |
| |
| bestScoreElement.textContent = gameState.bestScore; |
| gameLoop(); |
| </script> |
| </body> |
| </html> |