Download index.html from cfalk43/DinoGame: direct link, hf CLI and curl.
- Browser
- Download file 22 kB
-
https://huggingface.co/spaces/cfalk43/DinoGame/resolve/main/index.html
- Command line
-
hf download hf://spaces/cfalk43/DinoGame/index.html
-
curl -L -o index.html https://huggingface.co/spaces/cfalk43/DinoGame/resolve/main/index.html
22 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Dino Runner Game</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet"> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); | |
| font-family: 'Press Start 2P', cursive; | |
| overflow: hidden; | |
| } | |
| header { | |
| width: 100%; | |
| padding: 1rem; | |
| text-align: center; | |
| background: rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(10px); | |
| border-bottom: 2px solid rgba(255, 255, 255, 0.2); | |
| } | |
| header a { | |
| color: #00d4ff; | |
| text-decoration: none; | |
| font-size: 0.7rem; | |
| transition: all 0.3s ease; | |
| } | |
| header a:hover { | |
| color: #fff; | |
| text-shadow: 0 0 10px #00d4ff; | |
| } | |
| .game-container { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| flex: 1; | |
| padding: 2rem; | |
| width: 100%; | |
| max-width: 900px; | |
| } | |
| h1 { | |
| color: #fff; | |
| font-size: clamp(1rem, 4vw, 2rem); | |
| margin-bottom: 1rem; | |
| text-shadow: 0 0 20px rgba(0, 212, 255, 0.5); | |
| text-align: center; | |
| } | |
| .score-board { | |
| display: flex; | |
| gap: 2rem; | |
| margin-bottom: 1rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .score-item { | |
| color: #fff; | |
| font-size: clamp(0.6rem, 2vw, 0.9rem); | |
| padding: 0.5rem 1rem; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 10px; | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .score-item span { | |
| color: #00d4ff; | |
| } | |
| #gameCanvas { | |
| border: 4px solid #00d4ff; | |
| border-radius: 15px; | |
| box-shadow: 0 0 30px rgba(0, 212, 255, 0.3), | |
| inset 0 0 50px rgba(0, 0, 0, 0.5); | |
| background: linear-gradient(180deg, #87CEEB 0%, #E0F6FF 60%, #90EE90 60%, #228B22 100%); | |
| max-width: 100%; | |
| cursor: pointer; | |
| } | |
| .instructions { | |
| color: rgba(255, 255, 255, 0.7); | |
| font-size: clamp(0.5rem, 1.5vw, 0.7rem); | |
| margin-top: 1rem; | |
| text-align: center; | |
| line-height: 2; | |
| } | |
| .instructions kbd { | |
| background: rgba(255, 255, 255, 0.2); | |
| padding: 0.3rem 0.6rem; | |
| border-radius: 5px; | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| } | |
| .game-over-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.8); | |
| display: none; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 100; | |
| } | |
| .game-over-content { | |
| text-align: center; | |
| color: #fff; | |
| padding: 2rem; | |
| background: linear-gradient(135deg, #1a1a2e, #16213e); | |
| border-radius: 20px; | |
| border: 3px solid #00d4ff; | |
| box-shadow: 0 0 50px rgba(0, 212, 255, 0.5); | |
| } | |
| .game-over-content h2 { | |
| font-size: clamp(1.5rem, 5vw, 2.5rem); | |
| margin-bottom: 1rem; | |
| color: #ff4757; | |
| } | |
| .game-over-content p { | |
| font-size: clamp(0.7rem, 2vw, 1rem); | |
| margin-bottom: 1.5rem; | |
| } | |
| .restart-btn { | |
| font-family: 'Press Start 2P', cursive; | |
| font-size: clamp(0.6rem, 2vw, 0.9rem); | |
| padding: 1rem 2rem; | |
| background: linear-gradient(135deg, #00d4ff, #0099cc); | |
| color: #fff; | |
| border: none; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .restart-btn:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 0 20px rgba(0, 212, 255, 0.5); | |
| } | |
| .start-screen { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.9); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 100; | |
| } | |
| .start-content { | |
| text-align: center; | |
| color: #fff; | |
| padding: 2rem; | |
| } | |
| .start-content h2 { | |
| font-size: clamp(1.5rem, 5vw, 3rem); | |
| margin-bottom: 2rem; | |
| color: #00d4ff; | |
| text-shadow: 0 0 30px rgba(0, 212, 255, 0.7); | |
| } | |
| .dino-art { | |
| font-size: clamp(3rem, 10vw, 6rem); | |
| margin-bottom: 2rem; | |
| animation: bounce 1s infinite; | |
| } | |
| @keyframes bounce { | |
| 0%, | |
| 100% { | |
| transform: translateY(0); | |
| } | |
| 50% { | |
| transform: translateY(-20px); | |
| } | |
| } | |
| .start-btn { | |
| font-family: 'Press Start 2P', cursive; | |
| font-size: clamp(0.8rem, 2.5vw, 1.2rem); | |
| padding: 1.5rem 3rem; | |
| background: linear-gradient(135deg, #00d4ff, #0099cc); | |
| color: #fff; | |
| border: none; | |
| border-radius: 15px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, | |
| 100% { | |
| box-shadow: 0 0 20px rgba(0, 212, 255, 0.5); | |
| } | |
| 50% { | |
| box-shadow: 0 0 40px rgba(0, 212, 255, 0.8); | |
| } | |
| } | |
| .start-btn:hover { | |
| transform: scale(1.1); | |
| } | |
| @media (max-width: 600px) { | |
| .game-container { | |
| padding: 1rem; | |
| } | |
| .score-board { | |
| gap: 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a> | |
| </header> | |
| <div class="start-screen" id="startScreen"> | |
| <div class="start-content"> | |
| <h2>🦖 DINO RUNNER 🦖</h2> | |
| <div class="dino-art">🦕</div> | |
| <button class="start-btn" id="startBtn">START GAME</button> | |
| </div> | |
| </div> | |
| <div class="game-container"> | |
| <h1>🦖 DINO RUNNER 🦖</h1> | |
| <div class="score-board"> | |
| <div class="score-item">SCORE: <span id="score">0</span></div> | |
| <div class="score-item">HIGH SCORE: <span id="highScore">0</span></div> | |
| </div> | |
| <canvas id="gameCanvas" width="800" height="300"></canvas> | |
| <div class="instructions"> | |
| Press <kbd>SPACE</kbd> or <kbd>↑</kbd> to jump | <kbd>↓</kbd> to duck | <kbd>TAP</kbd> on mobile | |
| </div> | |
| </div> | |
| <div class="game-over-overlay" id="gameOverOverlay"> | |
| <div class="game-over-content"> | |
| <h2>GAME OVER!</h2> | |
| <p>Your Score: <span id="finalScore">0</span></p> | |
| <button class="restart-btn" id="restartBtn">PLAY AGAIN</button> | |
| </div> | |
| </div> | |
| <script> | |
| 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 gameOverOverlay = document.getElementById('gameOverOverlay'); | |
| const startScreen = document.getElementById('startScreen'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const restartBtn = document.getElementById('restartBtn'); | |
| // Responsive canvas | |
| function resizeCanvas() { | |
| const maxWidth = Math.min(800, window.innerWidth - 40); | |
| const ratio = 300 / 800; | |
| canvas.style.width = maxWidth + 'px'; | |
| canvas.style.height = (maxWidth * ratio) + 'px'; | |
| } | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| // Game variables | |
| let gameRunning = false; | |
| let gameSpeed = 6; | |
| let score = 0; | |
| let highScore = localStorage.getItem('dinoHighScore') || 0; | |
| highScoreElement.textContent = highScore; | |
| // Dino | |
| const dino = { | |
| x: 50, | |
| y: 220, | |
| width: 50, | |
| height: 60, | |
| velocityY: 0, | |
| jumping: false, | |
| ducking: false, | |
| duckHeight: 35, | |
| normalHeight: 60, | |
| groundY: 220, | |
| frame: 0, | |
| frameCount: 0 | |
| }; | |
| // Obstacles | |
| let obstacles = []; | |
| let obstacleTimer = 0; | |
| const obstacleInterval = 100; | |
| // Clouds | |
| let clouds = []; | |
| // Ground | |
| let groundX = 0; | |
| // Physics | |
| const gravity = 0.8; | |
| const jumpForce = -15; | |
| // Initialize clouds | |
| function initClouds() { | |
| clouds = []; | |
| for (let i = 0; i < 5; i++) { | |
| clouds.push({ | |
| x: Math.random() * canvas.width, | |
| y: 30 + Math.random() * 60, | |
| width: 60 + Math.random() * 40, | |
| speed: 1 + Math.random() | |
| }); | |
| } | |
| } | |
| // Draw cloud | |
| function drawCloud(cloud) { | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; | |
| ctx.beginPath(); | |
| ctx.arc(cloud.x, cloud.y, 20, 0, Math.PI * 2); | |
| ctx.arc(cloud.x + 25, cloud.y - 10, 25, 0, Math.PI * 2); | |
| ctx.arc(cloud.x + 50, cloud.y, 20, 0, Math.PI * 2); | |
| ctx.arc(cloud.x + 25, cloud.y + 5, 22, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| // Draw dino | |
| function drawDino() { | |
| const height = dino.ducking ? dino.duckHeight : dino.normalHeight; | |
| const y = dino.ducking ? dino.groundY + (dino.normalHeight - dino.duckHeight) : dino.y; | |
| // Body | |
| ctx.fillStyle = '#2d5016'; | |
| ctx.beginPath(); | |
| ctx.roundRect(dino.x, y, dino.width - 10, height, 10); | |
| ctx.fill(); | |
| // Head | |
| ctx.fillStyle = '#3d6b1e'; | |
| if (!dino.ducking) { | |
| ctx.beginPath(); | |
| ctx.roundRect(dino.x + 15, y - 25, 35, 30, 8); | |
| ctx.fill(); | |
| // Eye | |
| ctx.fillStyle = '#fff'; | |
| ctx.beginPath(); | |
| ctx.arc(dino.x + 40, y - 12, 6, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = '#000'; | |
| ctx.beginPath(); | |
| ctx.arc(dino.x + 42, y - 12, 3, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Mouth | |
| ctx.strokeStyle = '#1a3d0c'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(dino.x + 45, y - 5); | |
| ctx.lineTo(dino.x + 50, y - 5); | |
| ctx.stroke(); | |
| } else { | |
| // Ducking head | |
| ctx.beginPath(); | |
| ctx.roundRect(dino.x + 30, y - 5, 30, 20, 5); | |
| ctx.fill(); | |
| // Eye when ducking | |
| ctx.fillStyle = '#fff'; | |
| ctx.beginPath(); | |
| ctx.arc(dino.x + 50, y + 5, 4, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = '#000'; | |
| ctx.beginPath(); | |
| ctx.arc(dino.x + 51, y + 5, 2, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| // Legs animation | |
| ctx.fillStyle = '#2d5016'; | |
| dino.frameCount++; | |
| if (dino.frameCount > 5) { | |
| dino.frame = (dino.frame + 1) % 2; | |
| dino.frameCount = 0; | |
| } | |
| if (!dino.jumping) { | |
| const legOffset = dino.frame === 0 ? 5 : -5; | |
| ctx.fillRect(dino.x + 8, y + height - 5, 8, 15 + legOffset); | |
| ctx.fillRect(dino.x + 25, y + height - 5, 8, 15 - legOffset); | |
| } else { | |
| ctx.fillRect(dino.x + 8, y + height - 5, 8, 12); | |
| ctx.fillRect(dino.x + 25, y + height - 5, 8, 12); | |
| } | |
| // Tail | |
| ctx.fillStyle = '#3d6b1e'; | |
| ctx.beginPath(); | |
| ctx.moveTo(dino.x, y + 20); | |
| ctx.lineTo(dino.x - 20, y + 15); | |
| ctx.lineTo(dino.x, y + 35); | |
| ctx.fill(); | |
| } | |
| // Draw cactus obstacle | |
| function drawCactus(obstacle) { | |
| ctx.fillStyle = '#228B22'; | |
| // Main stem | |
| ctx.beginPath(); | |
| ctx.roundRect(obstacle.x + 10, obstacle.y, 20, obstacle.height, 5); | |
| ctx.fill(); | |
| // Left arm | |
| ctx.beginPath(); | |
| ctx.roundRect(obstacle.x, obstacle.y + 15, 12, 25, 4); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.roundRect(obstacle.x, obstacle.y + 10, 8, 15, 3); | |
| ctx.fill(); | |
| // Right arm | |
| ctx.beginPath(); | |
| ctx.roundRect(obstacle.x + 28, obstacle.y + 25, 12, 20, 4); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.roundRect(obstacle.x + 32, obstacle.y + 20, 8, 12, 3); | |
| ctx.fill(); | |
| // Spikes | |
| ctx.fillStyle = '#1a6b1a'; | |
| for (let i = 0; i < 5; i++) { | |
| ctx.beginPath(); | |
| ctx.arc(obstacle.x + 20, obstacle.y + i * 12, 2, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| // Draw bird obstacle | |
| function drawBird(obstacle) { | |
| ctx.fillStyle = '#8B4513'; | |
| // Body | |
| ctx.beginPath(); | |
| ctx.ellipse(obstacle.x + 20, obstacle.y + 15, 20, 12, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Head | |
| ctx.beginPath(); | |
| ctx.arc(obstacle.x + 40, obstacle.y + 10, 10, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Beak | |
| ctx.fillStyle = '#FFA500'; | |
| ctx.beginPath(); | |
| ctx.moveTo(obstacle.x + 50, obstacle.y + 10); | |
| ctx.lineTo(obstacle.x + 60, obstacle.y + 12); | |
| ctx.lineTo(obstacle.x + 50, obstacle.y + 14); | |
| ctx.fill(); | |
| // Eye | |
| ctx.fillStyle = '#fff'; | |
| ctx.beginPath(); | |
| ctx.arc(obstacle.x + 43, obstacle.y + 8, 4, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = '#000'; | |
| ctx.beginPath(); | |
| ctx.arc(obstacle.x + 44, obstacle.y + 8, 2, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Wings animation | |
| ctx.fillStyle = '#6B3E26'; | |
| const wingY = Math.sin(Date.now() / 100) * 10; | |
| ctx.beginPath(); | |
| ctx.moveTo(obstacle.x + 10, obstacle.y + 15); | |
| ctx.lineTo(obstacle.x - 10, obstacle.y + wingY); | |
| ctx.lineTo(obstacle.x + 20, obstacle.y + 15); | |
| ctx.fill(); | |
| } | |
| // Draw ground | |
| function drawGround() { | |
| ctx.fillStyle = '#8B4513'; | |
| ctx.fillRect(0, 280, canvas.width, 20); | |
| ctx.fillStyle = '#654321'; | |
| groundX -= gameSpeed; | |
| if (groundX <= -20) groundX = 0; | |
| for (let i = groundX; i < canvas.width; i += 20) { | |
| ctx.fillRect(i, 282, 15, 3); | |
| ctx.fillRect(i + 7, 288, 10, 2); | |
| } | |
| // Grass | |
| ctx.fillStyle = '#228B22'; | |
| ctx.fillRect(0, 275, canvas.width, 5); | |
| } | |
| // Spawn obstacle | |
| function spawnObstacle() { | |
| const type = Math.random() > 0.7 ? 'bird' : 'cactus'; | |
| const obstacle = { | |
| x: canvas.width, | |
| type: type, | |
| width: type === 'cactus' ? 40 : 60, | |
| height: type === 'cactus' ? 50 + Math.random() * 20 : 30 | |
| }; | |
| if (type === 'cactus') { | |
| obstacle.y = 280 - obstacle.height; | |
| } else { | |
| obstacle.y = Math.random() > 0.5 ? 180 : 230; | |
| } | |
| obstacles.push(obstacle); | |
| } | |
| // Check collision | |
| function checkCollision() { | |
| const dinoHeight = dino.ducking ? dino.duckHeight : dino.normalHeight; | |
| const dinoY = dino.ducking ? dino.groundY + (dino.normalHeight - dino.duckHeight) : dino.y; | |
| for (let obstacle of obstacles) { | |
| if (dino.x + dino.width - 15 > obstacle.x && | |
| dino.x + 10 < obstacle.x + obstacle.width && | |
| dinoY + dinoHeight > obstacle.y && | |
| dinoY < obstacle.y + obstacle.height) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // Game over | |
| function gameOver() { | |
| gameRunning = false; | |
| finalScoreElement.textContent = score; | |
| if (score > highScore) { | |
| highScore = score; | |
| localStorage.setItem('dinoHighScore', highScore); | |
| highScoreElement.textContent = highScore; | |
| } | |
| gameOverOverlay.style.display = 'flex'; | |
| } | |
| // Reset game | |
| function resetGame() { | |
| score = 0; | |
| gameSpeed = 6; | |
| obstacles = []; | |
| obstacleTimer = 0; | |
| dino.y = dino.groundY; | |
| dino.velocityY = 0; | |
| dino.jumping = false; | |
| dino.ducking = false; | |
| initClouds(); | |
| gameOverOverlay.style.display = 'none'; | |
| gameRunning = true; | |
| gameLoop(); | |
| } | |
| // Update game | |
| function update() { | |
| // Dino physics | |
| if (dino.jumping) { | |
| dino.velocityY += gravity; | |
| dino.y += dino.velocityY; | |
| if (dino.y >= dino.groundY) { | |
| dino.y = dino.groundY; | |
| dino.jumping = false; | |
| dino.velocityY = 0; | |
| } | |
| } | |
| // Update obstacles | |
| obstacleTimer++; | |
| if (obstacleTimer >= obstacleInterval - Math.min(score / 10, 50)) { | |
| spawnObstacle(); | |
| obstacleTimer = 0; | |
| } | |
| for (let i = obstacles.length - 1; i >= 0; i--) { | |
| obstacles[i].x -= gameSpeed; | |
| if (obstacles[i].x + obstacles[i].width < 0) { | |
| obstacles.splice(i, 1); | |
| score++; | |
| scoreElement.textContent = score; | |
| } | |
| } | |
| // Update clouds | |
| for (let cloud of clouds) { | |
| cloud.x -= cloud.speed; | |
| if (cloud.x + cloud.width < 0) { | |
| cloud.x = canvas.width + 50; | |
| cloud.y = 30 + Math.random() * 60; | |
| } | |
| } | |
| // Increase speed | |
| if (score > 0 && score % 10 === 0) { | |
| gameSpeed = 6 + Math.floor(score / 10) * 0.5; | |
| } | |
| // Check collision | |
| if (checkCollision()) { | |
| gameOver(); | |
| } | |
| } | |
| // Draw game | |
| function draw() { | |
| // Sky gradient | |
| const gradient = ctx.createLinearGradient(0, 0, 0, 280); | |
| gradient.addColorStop(0, '#87CEEB'); | |
| gradient.addColorStop(0.6, '#E0F6FF'); | |
| gradient.addColorStop(0.6, '#90EE90'); | |
| gradient.addColorStop(1, '#228B22'); | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Sun | |
| ctx.fillStyle = '#FFD700'; | |
| ctx.beginPath(); | |
| ctx.arc(700, 50, 30, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = 'rgba(255, 215, 0, 0.3)'; | |
| ctx.beginPath(); | |
| ctx.arc(700, 50, 45, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Clouds | |
| for (let cloud of clouds) { | |
| drawCloud(cloud); | |
| } | |
| // Ground | |
| drawGround(); | |
| // Obstacles | |
| for (let obstacle of obstacles) { | |
| if (obstacle.type === 'cactus') { | |
| drawCactus(obstacle); | |
| } else { | |
| drawBird(obstacle); | |
| } | |
| } | |
| // Dino | |
| drawDino(); | |
| } | |
| // Game loop | |
| function gameLoop() { | |
| if (!gameRunning) return; | |
| update(); | |
| draw(); | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // Controls | |
| function jump() { | |
| if (!dino.jumping && !dino.ducking) { | |
| dino.jumping = true; | |
| dino.velocityY = jumpForce; | |
| } | |
| } | |
| function duck(isDucking) { | |
| if (!dino.jumping) { | |
| dino.ducking = isDucking; | |
| } | |
| } | |
| // Event listeners | |
| document.addEventListener('keydown', (e) => { | |
| if (!gameRunning) return; | |
| if (e.code === 'Space' || e.code === 'ArrowUp') { | |
| e.preventDefault(); | |
| jump(); | |
| } | |
| if (e.code === 'ArrowDown') { | |
| e.preventDefault(); | |
| duck(true); | |
| } | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| if (e.code === 'ArrowDown') { | |
| duck(false); | |
| } | |
| }); | |
| // Touch controls | |
| canvas.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| if (gameRunning) { | |
| const touch = e.touches[0]; | |
| const rect = canvas.getBoundingClientRect(); | |
| const y = touch.clientY - rect.top; | |
| if (y > rect.height / 2) { | |
| duck(true); | |
| } else { | |
| jump(); | |
| } | |
| } | |
| }); | |
| canvas.addEventListener('touchend', (e) => { | |
| duck(false); | |
| }); | |
| canvas.addEventListener('click', () => { | |
| if (gameRunning) { | |
| jump(); | |
| } | |
| }); | |
| // Start and restart buttons | |
| startBtn.addEventListener('click', () => { | |
| startScreen.style.display = 'none'; | |
| resetGame(); | |
| }); | |
| restartBtn.addEventListener('click', resetGame); | |
| // Initialize | |
| initClouds(); | |
| draw(); | |
| </script> | |
| </body> | |
| </html> |