|
|
<!DOCTYPE html> |
|
|
<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'); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
let gameRunning = false; |
|
|
let gameSpeed = 6; |
|
|
let score = 0; |
|
|
let highScore = localStorage.getItem('dinoHighScore') || 0; |
|
|
highScoreElement.textContent = highScore; |
|
|
|
|
|
|
|
|
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 |
|
|
}; |
|
|
|
|
|
|
|
|
let obstacles = []; |
|
|
let obstacleTimer = 0; |
|
|
const obstacleInterval = 100; |
|
|
|
|
|
|
|
|
let clouds = []; |
|
|
|
|
|
|
|
|
let groundX = 0; |
|
|
|
|
|
|
|
|
const gravity = 0.8; |
|
|
const jumpForce = -15; |
|
|
|
|
|
|
|
|
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() |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
|
|
|
|
|
|
function drawDino() { |
|
|
const height = dino.ducking ? dino.duckHeight : dino.normalHeight; |
|
|
const y = dino.ducking ? dino.groundY + (dino.normalHeight - dino.duckHeight) : dino.y; |
|
|
|
|
|
|
|
|
ctx.fillStyle = '#2d5016'; |
|
|
ctx.beginPath(); |
|
|
ctx.roundRect(dino.x, y, dino.width - 10, height, 10); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
ctx.fillStyle = '#3d6b1e'; |
|
|
if (!dino.ducking) { |
|
|
ctx.beginPath(); |
|
|
ctx.roundRect(dino.x + 15, y - 25, 35, 30, 8); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.roundRect(dino.x + 30, y - 5, 30, 20, 5); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
|
|
|
|
|
|
function drawCactus(obstacle) { |
|
|
ctx.fillStyle = '#228B22'; |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.roundRect(obstacle.x + 10, obstacle.y, 20, obstacle.height, 5); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function drawBird(obstacle) { |
|
|
ctx.fillStyle = '#8B4513'; |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.ellipse(obstacle.x + 20, obstacle.y + 15, 20, 12, 0, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.arc(obstacle.x + 40, obstacle.y + 10, 10, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
ctx.fillStyle = '#228B22'; |
|
|
ctx.fillRect(0, 275, canvas.width, 5); |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
function gameOver() { |
|
|
gameRunning = false; |
|
|
finalScoreElement.textContent = score; |
|
|
|
|
|
if (score > highScore) { |
|
|
highScore = score; |
|
|
localStorage.setItem('dinoHighScore', highScore); |
|
|
highScoreElement.textContent = highScore; |
|
|
} |
|
|
|
|
|
gameOverOverlay.style.display = 'flex'; |
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
|
|
|
|
|
|
function update() { |
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (score > 0 && score % 10 === 0) { |
|
|
gameSpeed = 6 + Math.floor(score / 10) * 0.5; |
|
|
} |
|
|
|
|
|
|
|
|
if (checkCollision()) { |
|
|
gameOver(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function draw() { |
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
for (let cloud of clouds) { |
|
|
drawCloud(cloud); |
|
|
} |
|
|
|
|
|
|
|
|
drawGround(); |
|
|
|
|
|
|
|
|
for (let obstacle of obstacles) { |
|
|
if (obstacle.type === 'cactus') { |
|
|
drawCactus(obstacle); |
|
|
} else { |
|
|
drawBird(obstacle); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
drawDino(); |
|
|
} |
|
|
|
|
|
|
|
|
function gameLoop() { |
|
|
if (!gameRunning) return; |
|
|
|
|
|
update(); |
|
|
draw(); |
|
|
requestAnimationFrame(gameLoop); |
|
|
} |
|
|
|
|
|
|
|
|
function jump() { |
|
|
if (!dino.jumping && !dino.ducking) { |
|
|
dino.jumping = true; |
|
|
dino.velocityY = jumpForce; |
|
|
} |
|
|
} |
|
|
|
|
|
function duck(isDucking) { |
|
|
if (!dino.jumping) { |
|
|
dino.ducking = isDucking; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
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(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
startBtn.addEventListener('click', () => { |
|
|
startScreen.style.display = 'none'; |
|
|
resetGame(); |
|
|
}); |
|
|
|
|
|
restartBtn.addEventListener('click', resetGame); |
|
|
|
|
|
|
|
|
initClouds(); |
|
|
draw(); |
|
|
</script> |
|
|
</body> |
|
|
|
|
|
</html> |