| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Deep Sea Jumper</title> |
| <style> |
| body { |
| margin: 0; |
| padding: 0; |
| background: linear-gradient(180deg, #87CEEB 0%, #4682B4 50%, #191970 100%); |
| font-family: 'Arial', sans-serif; |
| overflow: hidden; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| min-height: 100vh; |
| } |
| |
| #gameContainer { |
| position: relative; |
| width: 400px; |
| height: 600px; |
| background: linear-gradient(180deg, #87CEEB 0%, #4682B4 30%, #191970 70%, #000080 100%); |
| border: 3px solid #2F4F4F; |
| border-radius: 10px; |
| overflow: hidden; |
| box-shadow: 0 0 30px rgba(0, 100, 200, 0.5); |
| } |
| |
| #gameCanvas { |
| width: 100%; |
| height: 100%; |
| display: block; |
| } |
| |
| #ui { |
| position: absolute; |
| top: 10px; |
| left: 10px; |
| color: white; |
| font-size: 18px; |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.8); |
| z-index: 10; |
| } |
| |
| #gameOver { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: rgba(0, 0, 0, 0.8); |
| color: white; |
| padding: 30px; |
| border-radius: 15px; |
| text-align: center; |
| display: none; |
| z-index: 20; |
| } |
| |
| #startButton, #restartButton { |
| background: linear-gradient(45deg, #FF6B6B, #FF8E8E); |
| color: white; |
| border: none; |
| padding: 12px 24px; |
| font-size: 16px; |
| border-radius: 25px; |
| cursor: pointer; |
| margin-top: 15px; |
| transition: all 0.3s ease; |
| } |
| |
| #startButton:hover, #restartButton:hover { |
| transform: scale(1.05); |
| box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4); |
| } |
| |
| .bubble { |
| position: absolute; |
| background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.3)); |
| border-radius: 50%; |
| animation: float 3s ease-in-out infinite; |
| } |
| |
| @keyframes float { |
| 0%, 100% { transform: translateY(0px) rotate(0deg); } |
| 50% { transform: translateY(-20px) rotate(180deg); } |
| } |
| |
| #instructions { |
| position: absolute; |
| bottom: 10px; |
| left: 50%; |
| transform: translateX(-50%); |
| color: rgba(255, 255, 255, 0.8); |
| font-size: 12px; |
| text-align: center; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="gameContainer"> |
| <canvas id="gameCanvas" width="400" height="600"></canvas> |
| <div id="ui"> |
| <div>Depth: <span id="depth">0</span>m</div> |
| <div>Score: <span id="score">0</span></div> |
| </div> |
| <div id="gameOver"> |
| <h2>Game Over!</h2> |
| <p>Final Depth: <span id="finalDepth">0</span>m</p> |
| <p>Final Score: <span id="finalScore">0</span></p> |
| <button id="restartButton">Dive Again</button> |
| </div> |
| <div id="instructions"> |
| Use ARROW KEYS or A/D to move<br> |
| Space to jump higher |
| </div> |
| </div> |
|
|
| <script> |
| const canvas = document.getElementById('gameCanvas'); |
| const ctx = canvas.getContext('2d'); |
| const gameOverDiv = document.getElementById('gameOver'); |
| |
| |
| let audioContext; |
| let sounds = {}; |
| |
| function initAudio() { |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| |
| |
| sounds.jump = createSound(300, 0.1, 'sine'); |
| sounds.land = createSound(150, 0.15, 'square'); |
| sounds.ambient = createAmbientSound(); |
| } |
| |
| function createSound(frequency, duration, type = 'sine') { |
| return function() { |
| if (!audioContext) return; |
| const oscillator = audioContext.createOscillator(); |
| const gainNode = audioContext.createGain(); |
| |
| oscillator.connect(gainNode); |
| gainNode.connect(audioContext.destination); |
| |
| oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); |
| oscillator.type = type; |
| |
| gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration); |
| |
| oscillator.start(audioContext.currentTime); |
| oscillator.stop(audioContext.currentTime + duration); |
| }; |
| } |
| |
| function createAmbientSound() { |
| return function() { |
| if (!audioContext) return; |
| const oscillator = audioContext.createOscillator(); |
| const gainNode = audioContext.createGain(); |
| |
| oscillator.connect(gainNode); |
| gainNode.connect(audioContext.destination); |
| |
| oscillator.frequency.setValueAtTime(60, audioContext.currentTime); |
| oscillator.type = 'sine'; |
| |
| gainNode.gain.setValueAtTime(0.02, audioContext.currentTime); |
| |
| oscillator.start(audioContext.currentTime); |
| oscillator.stop(audioContext.currentTime + 0.5); |
| }; |
| } |
| |
| class Game { |
| constructor() { |
| this.player = { |
| x: 200, |
| y: 500, |
| width: 20, |
| height: 20, |
| vx: 0, |
| vy: 0, |
| onGround: false, |
| color: '#FF6B6B', |
| lastJumpTime: 0 |
| }; |
| |
| this.platforms = []; |
| this.camera = { y: 0 }; |
| this.depth = 0; |
| this.score = 0; |
| this.gameRunning = false; |
| this.keys = {}; |
| this.particles = []; |
| this.bubbles = []; |
| this.seaCreatures = []; |
| this.snake = null; |
| this.lastCreatureSpawn = 0; |
| |
| this.gravity = 0.5; |
| this.jumpPower = -12; |
| this.moveSpeed = 5; |
| |
| this.generateInitialPlatforms(); |
| this.setupEventListeners(); |
| } |
| |
| generateInitialPlatforms() { |
| |
| this.platforms.push({ |
| x: 150, |
| y: 550, |
| width: 100, |
| height: 15, |
| color: '#8FBC8F' |
| }); |
| |
| |
| for (let i = 1; i < 50; i++) { |
| this.platforms.push({ |
| x: Math.random() * (canvas.width - 80), |
| y: 550 + i * 80, |
| width: 80 + Math.random() * 40, |
| height: 12, |
| color: this.getPlatformColor(i * 80) |
| }); |
| } |
| } |
| |
| getPlatformColor(depth) { |
| if (depth < 200) return '#8FBC8F'; |
| if (depth < 500) return '#20B2AA'; |
| if (depth < 1000) return '#4682B4'; |
| if (depth < 1500) return '#483D8B'; |
| return '#2F4F4F'; |
| } |
| |
| getSeaCreatureType(depth) { |
| if (depth < 200) return { type: 'fish', color: '#FFD700', size: 15 }; |
| if (depth < 400) return { type: 'jellyfish', color: '#FF69B4', size: 20 }; |
| if (depth < 600) return { type: 'octopus', color: '#8A2BE2', size: 25 }; |
| if (depth < 800) return { type: 'shark', color: '#708090', size: 30 }; |
| if (depth < 1000) return { type: 'whale', color: '#4169E1', size: 40 }; |
| return { type: 'anglerfish', color: '#32CD32', size: 35 }; |
| } |
| |
| spawnSeaCreature() { |
| const creatureInfo = this.getSeaCreatureType(this.depth); |
| const creature = { |
| x: Math.random() * canvas.width, |
| y: this.camera.y + Math.random() * canvas.height, |
| vx: (Math.random() - 0.5) * 3, |
| vy: (Math.random() - 0.5) * 2, |
| type: creatureInfo.type, |
| color: creatureInfo.color, |
| size: creatureInfo.size, |
| angle: Math.random() * Math.PI * 2, |
| life: 300 + Math.random() * 200 |
| }; |
| this.seaCreatures.push(creature); |
| } |
| |
| spawnSnake() { |
| if (this.snake) return; |
| |
| this.snake = { |
| x: Math.random() * canvas.width, |
| y: this.player.y + 200, |
| vx: 0, |
| vy: 0, |
| speed: 2.5, |
| size: 25, |
| color: '#8B0000', |
| segments: [], |
| targetX: this.player.x, |
| targetY: this.player.y |
| }; |
| |
| |
| for (let i = 0; i < 8; i++) { |
| this.snake.segments.push({ |
| x: this.snake.x - i * 15, |
| y: this.snake.y, |
| angle: 0 |
| }); |
| } |
| } |
| |
| setupEventListeners() { |
| document.addEventListener('keydown', (e) => { |
| this.keys[e.code] = true; |
| if (e.code === 'Space') { |
| e.preventDefault(); |
| if (!this.gameRunning) { |
| this.start(); |
| } |
| } |
| }); |
| |
| document.addEventListener('keyup', (e) => { |
| this.keys[e.code] = false; |
| }); |
| |
| document.getElementById('restartButton').addEventListener('click', () => { |
| this.restart(); |
| }); |
| |
| |
| canvas.addEventListener('click', () => { |
| if (!audioContext) { |
| initAudio(); |
| } |
| if (!this.gameRunning) { |
| this.start(); |
| } |
| }); |
| } |
| |
| start() { |
| if (!audioContext) { |
| initAudio(); |
| } |
| this.gameRunning = true; |
| gameOverDiv.style.display = 'none'; |
| this.gameLoop(); |
| } |
| |
| restart() { |
| this.player.x = 200; |
| this.player.y = 500; |
| this.player.vx = 0; |
| this.player.vy = 0; |
| this.player.onGround = false; |
| this.player.lastJumpTime = 0; |
| this.camera.y = 0; |
| this.depth = 0; |
| this.score = 0; |
| this.particles = []; |
| this.bubbles = []; |
| this.seaCreatures = []; |
| this.snake = null; |
| this.lastCreatureSpawn = 0; |
| this.platforms = []; |
| this.generateInitialPlatforms(); |
| this.start(); |
| } |
| |
| update() { |
| if (!this.gameRunning) return; |
| |
| |
| if (this.keys['ArrowLeft'] || this.keys['KeyA']) { |
| this.player.vx = -this.moveSpeed; |
| } |
| if (this.keys['ArrowRight'] || this.keys['KeyD']) { |
| this.player.vx = this.moveSpeed; |
| } |
| |
| |
| this.player.vx *= 0.8; |
| |
| |
| this.player.vy += this.gravity; |
| |
| |
| this.player.x += this.player.vx; |
| this.player.y += this.player.vy; |
| |
| |
| if (this.player.x < 0) this.player.x = canvas.width; |
| if (this.player.x > canvas.width) this.player.x = 0; |
| |
| |
| this.player.onGround = false; |
| for (let platform of this.platforms) { |
| if (this.player.x < platform.x + platform.width && |
| this.player.x + this.player.width > platform.x && |
| this.player.y < platform.y + platform.height && |
| this.player.y + this.player.height > platform.y) { |
| |
| if (this.player.vy > 0 && this.player.y < platform.y) { |
| this.player.y = platform.y - this.player.height; |
| this.player.vy = 0; |
| this.player.onGround = true; |
| |
| |
| this.createParticles(this.player.x + this.player.width/2, this.player.y + this.player.height); |
| |
| if (sounds.land) sounds.land(); |
| } |
| } |
| } |
| |
| |
| if ((this.keys['Space'] || this.keys['ArrowUp'] || this.keys['KeyW']) && this.player.onGround) { |
| this.player.vy = this.jumpPower; |
| this.player.onGround = false; |
| this.player.lastJumpTime = Date.now(); |
| if (sounds.jump) sounds.jump(); |
| |
| |
| this.createParticles(this.player.x + this.player.width/2, this.player.y + this.player.height); |
| } |
| |
| |
| if (Date.now() - this.lastCreatureSpawn > 2000 + Math.random() * 3000) { |
| this.spawnSeaCreature(); |
| this.lastCreatureSpawn = Date.now(); |
| } |
| |
| |
| if (this.score > 0 && this.score % 100 === 0 && !this.snake && this.score !== this.lastSnakeSpawn) { |
| this.spawnSnake(); |
| this.lastSnakeSpawn = this.score; |
| } |
| |
| |
| this.seaCreatures = this.seaCreatures.filter(creature => { |
| creature.x += creature.vx; |
| creature.y += creature.vy; |
| creature.angle += 0.05; |
| |
| |
| if (Math.random() < 0.02) { |
| creature.vx += (Math.random() - 0.5) * 0.5; |
| creature.vy += (Math.random() - 0.5) * 0.5; |
| } |
| |
| |
| if (creature.x < 0 || creature.x > canvas.width) creature.vx *= -1; |
| |
| creature.life--; |
| return creature.life > 0 && creature.y > this.camera.y - 100 && creature.y < this.camera.y + canvas.height + 100; |
| }); |
| |
| |
| if (this.snake) { |
| |
| const dx = this.player.x - this.snake.x; |
| const dy = this.player.y - this.snake.y; |
| const distance = Math.sqrt(dx * dx + dy * dy); |
| |
| |
| this.snake.targetX = this.player.x; |
| this.snake.targetY = this.player.y; |
| |
| const targetDx = this.snake.targetX - this.snake.x; |
| const targetDy = this.snake.targetY - this.snake.y; |
| const targetDistance = Math.sqrt(targetDx * targetDx + targetDy * targetDy); |
| |
| if (targetDistance > 5) { |
| this.snake.vx = (targetDx / targetDistance) * this.snake.speed; |
| this.snake.vy = (targetDy / targetDistance) * this.snake.speed; |
| } |
| |
| this.snake.x += this.snake.vx; |
| this.snake.y += this.snake.vy; |
| |
| |
| for (let i = this.snake.segments.length - 1; i > 0; i--) { |
| const segment = this.snake.segments[i]; |
| const prevSegment = this.snake.segments[i - 1]; |
| |
| const segDx = prevSegment.x - segment.x; |
| const segDy = prevSegment.y - segment.y; |
| const segDistance = Math.sqrt(segDx * segDx + segDy * segDy); |
| |
| if (segDistance > 15) { |
| segment.x = prevSegment.x - (segDx / segDistance) * 15; |
| segment.y = prevSegment.y - (segDy / segDistance) * 15; |
| } |
| |
| segment.angle = Math.atan2(segDy, segDx); |
| } |
| |
| |
| this.snake.segments[0].x = this.snake.x; |
| this.snake.segments[0].y = this.snake.y; |
| this.snake.segments[0].angle = Math.atan2(this.snake.vy, this.snake.vx); |
| |
| |
| const timeSinceLastJump = Date.now() - this.player.lastJumpTime; |
| if (this.player.onGround && timeSinceLastJump > 4000) { |
| |
| if (distance < 40) { |
| this.gameOver("The sea snake caught you! Keep moving!"); |
| return; |
| } |
| } |
| |
| |
| if (this.snake.y < this.camera.y - 300) { |
| this.snake = null; |
| } |
| } |
| |
| |
| let targetCameraY = this.player.y - canvas.height/2; |
| this.camera.y += (targetCameraY - this.camera.y) * 0.1; |
| |
| |
| this.depth = Math.max(0, Math.floor((this.player.y - 500) / 10)); |
| this.score = this.depth; |
| |
| |
| if (this.player.y > this.platforms[this.platforms.length - 1].y - 500) { |
| for (let i = 0; i < 10; i++) { |
| let lastY = this.platforms[this.platforms.length - 1].y; |
| this.platforms.push({ |
| x: Math.random() * (canvas.width - 80), |
| y: lastY + 80 + Math.random() * 40, |
| width: 80 + Math.random() * 40, |
| height: 12, |
| color: this.getPlatformColor(lastY + 80) |
| }); |
| } |
| } |
| |
| |
| this.particles = this.particles.filter(particle => { |
| particle.x += particle.vx; |
| particle.y += particle.vy; |
| particle.vy += 0.2; |
| particle.life--; |
| return particle.life > 0; |
| }); |
| |
| |
| this.bubbles = this.bubbles.filter(bubble => { |
| bubble.y -= bubble.speed; |
| bubble.x += Math.sin(bubble.y * 0.01) * 0.5; |
| return bubble.y > this.camera.y - 100; |
| }); |
| |
| |
| if (Math.random() < 0.1) { |
| this.bubbles.push({ |
| x: Math.random() * canvas.width, |
| y: this.camera.y + canvas.height + 20, |
| size: 3 + Math.random() * 8, |
| speed: 1 + Math.random() * 2 |
| }); |
| } |
| |
| |
| if (this.player.y > this.camera.y + canvas.height + 100) { |
| this.gameOver("You fell into the abyss!"); |
| } |
| |
| |
| document.getElementById('depth').textContent = this.depth; |
| document.getElementById('score').textContent = this.score; |
| } |
| |
| createParticles(x, y) { |
| for (let i = 0; i < 5; i++) { |
| this.particles.push({ |
| x: x + (Math.random() - 0.5) * 20, |
| y: y + (Math.random() - 0.5) * 10, |
| vx: (Math.random() - 0.5) * 6, |
| vy: (Math.random() - 0.5) * 6, |
| life: 20 + Math.random() * 20, |
| color: `hsl(${180 + Math.random() * 60}, 70%, 60%)` |
| }); |
| } |
| } |
| |
| render() { |
| |
| const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); |
| let depthRatio = Math.min(this.depth / 1000, 1); |
| gradient.addColorStop(0, `hsl(200, 60%, ${80 - depthRatio * 30}%)`); |
| gradient.addColorStop(1, `hsl(220, 80%, ${20 - depthRatio * 15}%)`); |
| |
| ctx.fillStyle = gradient; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| ctx.save(); |
| ctx.translate(0, -this.camera.y); |
| |
| |
| ctx.globalAlpha = 0.6; |
| for (let bubble of this.bubbles) { |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; |
| ctx.beginPath(); |
| ctx.arc(bubble.x, bubble.y, bubble.size, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| ctx.globalAlpha = 1; |
| |
| |
| for (let platform of this.platforms) { |
| if (platform.y > this.camera.y - 50 && platform.y < this.camera.y + canvas.height + 50) { |
| ctx.fillStyle = platform.color; |
| ctx.fillRect(platform.x, platform.y, platform.width, platform.height); |
| |
| |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; |
| ctx.fillRect(platform.x, platform.y, platform.width, 3); |
| } |
| } |
| |
| |
| for (let particle of this.particles) { |
| ctx.fillStyle = particle.color; |
| ctx.globalAlpha = particle.life / 40; |
| ctx.fillRect(particle.x, particle.y, 3, 3); |
| } |
| ctx.globalAlpha = 1; |
| |
| |
| ctx.fillStyle = this.player.color; |
| ctx.fillRect(this.player.x, this.player.y, this.player.width, this.player.height); |
| |
| |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; |
| ctx.fillRect(this.player.x, this.player.y, this.player.width, 5); |
| |
| ctx.restore(); |
| } |
| |
| gameOver() { |
| this.gameRunning = false; |
| document.getElementById('finalDepth').textContent = this.depth; |
| document.getElementById('finalScore').textContent = this.score; |
| gameOverDiv.style.display = 'block'; |
| } |
| |
| gameLoop() { |
| if (this.gameRunning) { |
| this.update(); |
| this.render(); |
| requestAnimationFrame(() => this.gameLoop()); |
| } |
| } |
| } |
| |
| |
| const game = new Game(); |
| |
| |
| ctx.fillStyle = 'white'; |
| ctx.font = '24px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText('Deep Sea Jumper', canvas.width/2, canvas.height/2 - 50); |
| ctx.font = '16px Arial'; |
| ctx.fillText('Click or Press SPACE to Start', canvas.width/2, canvas.height/2); |
| ctx.fillText('Dive as deep as you can!', canvas.width/2, canvas.height/2 + 30); |
| </script> |
| </body> |
| </html> |