| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Flappy City: Day & Night Adventure</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-family: 'Poppins', 'Arial', sans-serif; |
| touch-action: manipulation; |
| } |
| |
| .game-container { |
| width: 100%; |
| max-width: 500px; |
| height: 700px; |
| margin: 0 auto; |
| } |
| |
| #game-canvas { |
| width: 100%; |
| height: 100%; |
| border-radius: 14px; |
| box-shadow: 0 15px 30px rgba(0,0,0,0.25); |
| overflow: hidden; |
| image-rendering: crisp-edges; |
| } |
| |
| .pipe { |
| position: absolute; |
| width: 80px; |
| background-color: #4d9a17; |
| border: 4px solid #356811; |
| box-shadow: inset 0 0 10px rgba(0,0,0,0.2); |
| border-radius: 8px; |
| z-index: 9; |
| } |
| |
| .bird-shade { |
| position: absolute; |
| background: rgba(0,0,0,0.15); |
| border-radius: 50%; |
| filter: blur(4px); |
| z-index: 18; |
| } |
| |
| .bird-shade-animate { |
| animation: shadow-pulse 0.5s infinite alternate; |
| } |
| |
| @keyframes fadeIn { |
| 0% { opacity: 0; } |
| 100% { opacity: 1; } |
| } |
| |
| @keyframes shadow-pulse { |
| 0% { transform: scale(0.9); opacity: 0.7; } |
| 100% { transform: scale(1.1); opacity: 0.9; } |
| } |
| |
| #bird { |
| position: absolute; |
| z-index: 20; |
| transform-style: preserve-3d; |
| } |
| |
| .bird-shadow { |
| position: absolute; |
| background: rgba(0,0,0,0.15); |
| border-radius: 50%; |
| filter: blur(4px); |
| z-index: 18; |
| } |
| |
| .bird-wing { |
| transform-origin: center center; |
| } |
| |
| .building { |
| position: absolute; |
| background-color: #222; |
| border-left: 4px solid rgba(0,0,0,0.4); |
| border-right: 4px solid rgba(0,0,0,0.4); |
| box-shadow: 0 8px 15px -5px rgba(0,0,0,0.5); |
| border-top-left-radius: 8px; |
| border-top-right-radius: 8px; |
| z-index: 4; |
| } |
| |
| .building-side { |
| position: absolute; |
| background-color: #1a1a1a; |
| width: 8px; |
| height: 100%; |
| } |
| |
| |
| .settings-btn-pulse { |
| animation: pulse 2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(1); } |
| 50% { transform: scale(1.1); } |
| 100% { transform: scale(1); } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 min-h-screen flex items-center justify-center p-4"> |
| <div class="game-container relative"> |
| <canvas id="game-canvas" class="bg-sky-500"></canvas> |
| |
| <div id="start-screen" class="absolute inset-0 flex flex-col items-center justify-center bg-slate-700/50 backdrop-blur-sm p-6"> |
| <div class="bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl p-8 w-full max-w-xs text-center shadow-2xl"> |
| <div class="flex justify-center mb-6"> |
| <div class="bg-yellow-300 w-24 h-24 rounded-full flex items-center justify-center shadow-lg"> |
| <i class="fa-solid fa-dove text-5xl text-blue-600"></i> |
| </div> |
| </div> |
| <h2 class="text-white text-2xl font-bold mb-2">Flappy City</h2> |
| <p class="text-white/90 mb-6">Fly through the cityscape!</p> |
| |
| <button id="start-button" class="bg-gradient-to-r from-yellow-400 to-yellow-500 hover:from-yellow-500 hover:to-yellow-600 text-blue-900 font-bold py-3 px-6 rounded-full text-lg w-full transform transition-all duration-300 hover:scale-105 focus:outline-none shadow-lg active:scale-95"> |
| <span class="drop-shadow">Start Flight</span> |
| </button> |
| |
| <div class="mt-6"> |
| <p class="text-sm text-white/80"><i class="fa-solid fa-hand-pointer mr-1"></i> Tap to flap</p> |
| <p class="text-xs text-white/60 mt-2">Spacebar: Jump | P: Pause</p> |
| </div> |
| </div> |
| </div> |
| |
| <div id="settings-screen" class="absolute inset-0 flex flex-col items-center justify-center bg-slate-800/80 backdrop-blur-sm hidden"> |
| <div class="bg-gradient-to-br from-indigo-600 to-purple-700 rounded-2xl p-8 w-full max-w-xs"> |
| <h2 class="text-white text-2xl font-bold mb-6 text-center">Game Settings</h2> |
| |
| <div class="mb-6"> |
| <label class="flex items-center cursor-pointer"> |
| <div class="relative"> |
| <input id="sound-toggle" type="checkbox" class="sr-only" checked> |
| <div class="block bg-gray-600 w-14 h-8 rounded-full"></div> |
| <div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition transform"></div> |
| </div> |
| <div class="ml-3 text-white font-medium">Sound Effects</div> |
| </label> |
| </div> |
| |
| <div class="mb-6"> |
| <label class="text-white block mb-3 font-medium">Pipe Gap Size:</label> |
| <div class="flex items-center"> |
| <span class="text-white mr-2 text-sm">Easy</span> |
| <input id="gap-slider" type="range" min="150" max="250" value="200" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> |
| <span class="text-white ml-2 text-sm">Hard</span> |
| </div> |
| </div> |
| |
| <div class="mt-8"> |
| <button id="settings-back-button" class="w-full bg-gradient-to-r from-blue-500 to-indigo-600 hover:from-blue-600 hover:to-indigo-700 text-white font-bold py-3 px-6 rounded-full mb-3 transition-all"> |
| Back to Game |
| </button> |
| <button id="settings-restart-button" class="w-full bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 text-white font-bold py-3 px-6 rounded-full transition-all"> |
| Restart Game |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| <div id="game-over" class="absolute inset-0 flex flex-col items-center justify-center bg-slate-800/80 backdrop-blur-sm hidden"> |
| <div class="bg-gradient-to-br from-rose-600 to-rose-800 rounded-2xl p-8 max-w-xs w-full text-center shadow-xl"> |
| <h2 class="text-white text-2xl font-bold mb-2">Flight Over!</h2> |
| <p class="text-white/90 mb-2">Score: <span id="final-score" class="text-yellow-300 font-bold">0</span></p> |
| <p class="text-white/90 mb-6">Best: <span id="best-score" class="text-yellow-300 font-bold">0</span></p> |
| |
| <button id="restart-button" class="bg-gradient-to-r from-green-400 to-green-500 hover:from-green-500 hover:to-green-600 text-white font-bold py-3 px-6 rounded-full text-lg w-full transition duration-300 transform hover:scale-105 focus:outline-none mb-4 shadow-lg active:scale-95"> |
| Fly Again |
| </button> |
| |
| <button id="menu-button" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-3 px-6 rounded-full text-lg w-full transition duration-300 shadow active:bg-gray-500"> |
| Main Menu |
| </button> |
| |
| <button id="settings-button" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 px-6 rounded-full text-lg w-full mt-4 transition duration-300 shadow active:bg-indigo-700 settings-btn-pulse"> |
| <i class="fas fa-cog mr-2"></i>Settings |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', () => { |
| const canvas = document.getElementById('game-canvas'); |
| const ctx = canvas.getContext('2d'); |
| const startScreen = document.getElementById('start-screen'); |
| const startButton = document.getElementById('start-button'); |
| const gameOverScreen = document.getElementById('game-over'); |
| const restartButton = document.getElementById('restart-button'); |
| const menuButton = document.getElementById('menu-button'); |
| const settingsButton = document.getElementById('settings-button'); |
| const settingsScreen = document.getElementById('settings-screen'); |
| const settingsBackButton = document.getElementById('settings-back-button'); |
| const settingsRestartButton = document.getElementById('settings-restart-button'); |
| const gapSlider = document.getElementById('gap-slider'); |
| const soundToggle = document.getElementById('sound-toggle'); |
| const finalScoreElement = document.getElementById('final-score'); |
| const bestScoreElement = document.getElementById('best-score'); |
| |
| |
| canvas.width = canvas.clientWidth; |
| canvas.height = canvas.clientHeight; |
| |
| |
| let gameState = 'stopped'; |
| let frames = 0; |
| let score = 0; |
| let bestScore = localStorage.getItem('flappyBestScore') || 0; |
| let isDayTime = true; |
| let gravity = 0.25; |
| let animationFrame; |
| let pipeGap = 200; |
| let birdVerticalOffset = 0; |
| let wingRotation = 0; |
| let birdAngle = 0; |
| |
| |
| const pipeInterval = 120; |
| |
| |
| window.addEventListener('resize', resizeCanvas); |
| document.addEventListener('keydown', handleKeyPress); |
| startButton.addEventListener('click', startGame); |
| restartButton.addEventListener('click', startGame); |
| menuButton.addEventListener('click', showStartScreen); |
| settingsButton.addEventListener('click', showSettings); |
| settingsBackButton.addEventListener('click', hideSettings); |
| settingsRestartButton.addEventListener('click', () => { |
| hideSettings(); |
| startGame(); |
| }); |
| gapSlider.addEventListener('input', updatePipeGap); |
| soundToggle.addEventListener('change', toggleSound); |
| |
| |
| canvas.addEventListener('touchstart', function(e) { |
| e.preventDefault(); |
| if (gameState === 'stopped') { |
| startGame(); |
| } else if (gameState === 'started') { |
| bird.jump(); |
| } |
| }, { passive: false }); |
| |
| canvas.addEventListener('click', function() { |
| if (gameState === 'stopped') { |
| startGame(); |
| } else if (gameState === 'started') { |
| bird.jump(); |
| } |
| }); |
| |
| function resizeCanvas() { |
| canvas.width = canvas.clientWidth; |
| canvas.height = canvas.clientHeight; |
| buildingsCache = []; |
| drawBackground(); |
| } |
| |
| function showSettings() { |
| settingsScreen.classList.remove('hidden'); |
| } |
| |
| function hideSettings() { |
| settingsScreen.classList.add('hidden'); |
| } |
| |
| function updatePipeGap() { |
| pipeGap = parseInt(gapSlider.value); |
| } |
| |
| function toggleSound() { |
| console.log('Sound toggled:', soundToggle.checked); |
| } |
| |
| function handleKeyPress(e) { |
| if (e.code === 'Space') { |
| if (gameState === 'stopped') { |
| startGame(); |
| } else if (gameState === 'started') { |
| bird.jump(); |
| } |
| } else if (e.code === 'KeyR') { |
| startGame(); |
| } else if (e.code === 'KeyP') { |
| togglePause(); |
| } else if (e.code === 'KeyS') { |
| showSettings(); |
| } |
| } |
| |
| |
| const bird = { |
| x: 100, |
| y: canvas.height / 2, |
| radius: 22, |
| velocity: 0, |
| jumpHeight: 7.2, |
| |
| update: function() { |
| |
| if (gameState === 'started') { |
| this.velocity += gravity; |
| this.y += this.velocity; |
| |
| |
| birdVerticalOffset = Math.sin(Date.now() / 100) * 3; |
| |
| |
| wingRotation = Math.sin(Date.now() / 100) * 30; |
| |
| |
| birdAngle = this.velocity * 3; |
| if (birdAngle > 25) birdAngle = 25; |
| if (birdAngle < -25) birdAngle = -25; |
| } |
| |
| |
| if (this.y - this.radius <= 10) { |
| this.y = this.radius + 10; |
| this.velocity = 0; |
| } |
| }, |
| |
| jump: function() { |
| this.velocity = -this.jumpHeight; |
| }, |
| |
| draw: function() { |
| const centerX = this.x; |
| const centerY = this.y + birdVerticalOffset; |
| |
| |
| ctx.save(); |
| ctx.translate(centerX, centerY); |
| ctx.rotate(birdAngle * Math.PI / 180); |
| |
| |
| ctx.fillStyle = '#FFD32B'; |
| ctx.beginPath(); |
| ctx.ellipse(0, 0, this.radius, this.radius, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#FF9839'; |
| ctx.save(); |
| ctx.translate(0, 7); |
| ctx.rotate(wingRotation * Math.PI / 180); |
| ctx.beginPath(); |
| ctx.ellipse(0, 0, 12, 16, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| ctx.restore(); |
| |
| |
| ctx.fillStyle = '#000000'; |
| ctx.beginPath(); |
| ctx.ellipse(0, -5, 5, 2, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#FFFFFF'; |
| ctx.beginPath(); |
| ctx.ellipse(6, -5, 4, 4, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| ctx.fillStyle = '#000000'; |
| ctx.beginPath(); |
| ctx.ellipse(6, -5, 1.5, 1.5, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#FF7839'; |
| ctx.beginPath(); |
| ctx.moveTo(16, -2); |
| ctx.lineTo(22, 0); |
| ctx.lineTo(16, 4); |
| ctx.fill(); |
| |
| ctx.restore(); |
| |
| |
| ctx.fillStyle = '#FCE49B'; |
| ctx.fillRect(centerX - 10, centerY + this.radius, 20, 8); |
| }, |
| |
| reset: function() { |
| this.y = canvas.height / 2; |
| this.velocity = 0; |
| birdAngle = 0; |
| } |
| }; |
| |
| |
| const pipes = []; |
| |
| |
| function Pipe() { |
| this.topHeight = Math.floor(Math.random() * 180) + 60; |
| this.bottomHeight = canvas.height - this.topHeight - pipeGap - 80; |
| this.x = canvas.width + 80; |
| this.width = 80; |
| this.speed = 3.2; |
| this.color = '#4d9a17'; |
| this.highlight = false; |
| this.passed = false; |
| |
| this.update = function() { |
| this.x -= this.speed; |
| |
| |
| if ( |
| bird.x + bird.radius > this.x && |
| bird.x - bird.radius < this.x + this.width && |
| (bird.y - bird.radius < this.topHeight || |
| bird.y + bird.radius > canvas.height - this.bottomHeight - 80) |
| ) { |
| endGame(); |
| } |
| |
| |
| if (!this.passed && this.x < bird.x - this.width/2) { |
| score++; |
| this.passed = true; |
| this.highlight = true; |
| |
| |
| if (score % 5 === 0) { |
| isDayTime = !isDayTime; |
| } |
| } |
| }; |
| |
| this.draw = function() { |
| |
| ctx.fillStyle = '#356811'; |
| ctx.fillRect(this.x - 4, 0, this.width + 8, this.topHeight); |
| ctx.fillStyle = this.color; |
| ctx.fillRect(this.x, 0, this.width, this.topHeight); |
| |
| |
| ctx.fillStyle = '#3d8617'; |
| ctx.fillRect(this.x - 4, this.topHeight - 10, this.width + 8, 14); |
| ctx.fillRect(this.x - 4, this.topHeight - 14, this.width + 8, 4); |
| |
| |
| ctx.fillStyle = '#356811'; |
| ctx.fillRect( |
| this.x - 4, |
| canvas.height - this.bottomHeight - 80, |
| this.width + 8, |
| this.bottomHeight |
| ); |
| |
| ctx.fillStyle = this.color; |
| ctx.fillRect( |
| this.x, |
| canvas.height - this.bottomHeight - 80, |
| this.width, |
| this.bottomHeight |
| ); |
| |
| |
| ctx.fillStyle = '#3d8617'; |
| ctx.fillRect( |
| this.x - 4, |
| canvas.height - this.bottomHeight - 80, |
| this.width + 8, |
| 14 |
| ); |
| |
| if (this.highlight) { |
| ctx.fillStyle = 'rgba(255, 215, 0, 0.4)'; |
| ctx.fillRect(this.x, 0, this.width, this.topHeight); |
| ctx.fillRect( |
| this.x, |
| canvas.height - this.bottomHeight - 80, |
| this.width, |
| this.bottomHeight |
| ); |
| |
| this.highlight = false; |
| } |
| }; |
| } |
| |
| |
| let buildingsCache = []; |
| |
| function createBuildingCache() { |
| buildingsCache = []; |
| |
| const buildingColors = [ |
| {main: '#333', edge: '#2a2a2a'}, |
| {main: '#2a2a2a', edge: '#222'}, |
| {main: '#222', edge: '#1c1c1c'}, |
| {main: '#1c1c1c', edge: '#161616'} |
| ]; |
| |
| let xPos = 0; |
| while (xPos < canvas.width) { |
| const width = Math.floor(Math.random() * 100) + 60; |
| const height = Math.floor(canvas.height * (0.05 + Math.random() * 0.2)) + 80; |
| const colorIndex = Math.floor(Math.random() * buildingColors.length); |
| |
| buildingsCache.push({ |
| x: xPos, |
| width: width, |
| height: height, |
| color: buildingColors[colorIndex].main, |
| edgeColor: buildingColors[colorIndex].edge, |
| windows: [] |
| }); |
| |
| xPos += width + 15; |
| } |
| } |
| |
| |
| function drawBuildings() { |
| |
| if (buildingsCache.length === 0) { |
| createBuildingCache(); |
| } |
| |
| |
| ctx.fillStyle = 'rgba(85, 85, 85, 0.95)'; |
| ctx.fillRect(0, canvas.height - 80, canvas.width, 80); |
| ctx.fillStyle = '#666'; |
| ctx.fillRect(0, canvas.height - 80, canvas.width, 4); |
| |
| |
| for (let i = 0; i < canvas.width; i += 50) { |
| ctx.fillStyle = '#ffc107'; |
| ctx.fillRect(i, canvas.height - 55, 30, 8); |
| } |
| |
| buildingsCache.forEach(building => { |
| const buildingY = canvas.height - building.height - 80; |
| |
| |
| ctx.fillStyle = '#000'; |
| ctx.fillRect( |
| building.x - 8, |
| buildingY - 10, |
| building.width + 16, |
| building.height + 10 |
| ); |
| |
| |
| ctx.fillStyle = building.color; |
| ctx.fillRect( |
| building.x, |
| buildingY, |
| building.width, |
| building.height |
| ); |
| |
| |
| ctx.fillStyle = building.edgeColor; |
| ctx.fillRect( |
| building.x + building.width - 8, |
| buildingY, |
| 8, |
| building.height |
| ); |
| |
| |
| ctx.fillStyle = '#111'; |
| ctx.fillRect( |
| building.x - 8, |
| buildingY - 10, |
| building.width + 16, |
| 10 |
| ); |
| |
| |
| ctx.fillStyle = '#333'; |
| ctx.fillRect( |
| building.x - 15, |
| buildingY - 30, |
| building.width + 30, |
| 20 |
| ); |
| |
| |
| const windowRows = Math.floor(building.height / 25); |
| const windowCols = Math.floor(building.width / 15); |
| |
| const rowSpacing = building.height / windowRows; |
| const colSpacing = building.width / windowCols; |
| |
| for (let row = 1; row < windowRows; row++) { |
| for (let col = 1; col < windowCols; col++) { |
| if (Math.random() > 0.25) { |
| const windowX = building.x + (col * colSpacing) - 4; |
| const windowY = buildingY + (row * rowSpacing) - 6; |
| |
| const isLit = isDayTime || Math.random() > 0.6; |
| |
| |
| ctx.fillStyle = isLit ? |
| (isDayTime ? '#f6d186' : '#FFF59D') : |
| (isDayTime ? '#9fc4d4' : '#1a1a1a'); |
| |
| ctx.fillRect(windowX, windowY, 8, 12); |
| |
| |
| if (isLit) { |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; |
| ctx.beginPath(); |
| ctx.ellipse(windowX + 5, windowY + 3, 1.5, 1.5, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| function drawBackground() { |
| |
| if (isDayTime) { |
| |
| const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); |
| gradient.addColorStop(0, '#70d1ff'); |
| gradient.addColorStop(1, '#48a0ff'); |
| ctx.fillStyle = gradient; |
| } else { |
| |
| const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); |
| gradient.addColorStop(0, '#0f2f6e'); |
| gradient.addColorStop(1, '#1c1554'); |
| ctx.fillStyle = gradient; |
| } |
| |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| |
| if (isDayTime) { |
| ctx.fillStyle = 'rgba(255,255,255,0.8)'; |
| |
| |
| const cloudPositions = [ |
| {x: frames/15 % (canvas.width + 150) - 50, y: 100}, |
| {x: frames/20 % (canvas.width + 200) - 100, y: 180}, |
| {x: frames/25 % (canvas.width + 100) - 20, y: 240} |
| ]; |
| |
| cloudPositions.forEach(cloud => { |
| ctx.beginPath(); |
| ctx.arc(cloud.x, cloud.y, 30, 0, Math.PI * 2); |
| ctx.arc(cloud.x + 25, cloud.y - 10, 20, 0, Math.PI * 2); |
| ctx.arc(cloud.x + 15, cloud.y + 15, 25, 0, Math.PI * 2); |
| ctx.fill(); |
| }); |
| } else { |
| |
| ctx.fillStyle = 'rgba(255,255,255,0.8)'; |
| for (let i = 0; i < 30; i++) { |
| const starX = (frames + i * 100) % (canvas.width + 20) - 10; |
| const starY = Math.sin(i * 123) * 100 + 200; |
| |
| if (starX > 0 && starX < canvas.width) { |
| ctx.beginPath(); |
| ctx.arc(starX, starY, Math.random() * 1.2 + 0.3, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| } |
| } |
| |
| |
| drawBuildings(); |
| } |
| |
| function createPipes() { |
| |
| if (frames % pipeInterval === 0) { |
| pipes.push(new Pipe()); |
| |
| |
| if (pipes.length > 3) { |
| pipes.shift(); |
| } |
| } |
| } |
| |
| function updatePipes() { |
| for (let i = 0; i < pipes.length; i++) { |
| pipes[i].update(); |
| pipes[i].draw(); |
| |
| if (pipes[i].x + pipes[i].width < 0) { |
| pipes.splice(i, 1); |
| i--; |
| } |
| } |
| } |
| |
| function drawScore() { |
| ctx.font = 'bold 36px "Poppins", sans-serif'; |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.85)'; |
| ctx.textAlign = 'center'; |
| ctx.strokeStyle = '#0f172a'; |
| ctx.lineWidth = 6; |
| ctx.strokeText(score, canvas.width / 2, 60); |
| ctx.fillText(score, canvas.width / 2, 60); |
| } |
| |
| function endGame() { |
| gameState = 'over'; |
| gameOverScreen.classList.remove('hidden'); |
| finalScoreElement.textContent = score; |
| |
| |
| if (score > bestScore) { |
| bestScore = score; |
| localStorage.setItem('flappyBestScore', bestScore); |
| } |
| bestScoreElement.textContent = bestScore; |
| } |
| |
| function togglePause() { |
| if (gameState === 'started') { |
| gameState = 'paused'; |
| } else if (gameState === 'paused') { |
| gameState = 'started'; |
| gameLoop(); |
| } |
| } |
| |
| function startGame() { |
| gameState = 'started'; |
| startScreen.classList.add('hidden'); |
| gameOverScreen.classList.add('hidden'); |
| settingsScreen.classList.add('hidden'); |
| |
| |
| score = 0; |
| pipes.length = 0; |
| frames = 0; |
| isDayTime = true; |
| birdVerticalOffset = 0; |
| |
| bird.reset(); |
| createBuildingCache(); |
| |
| |
| if (animationFrame) cancelAnimationFrame(animationFrame); |
| gameLoop(); |
| } |
| |
| function showStartScreen() { |
| gameState = 'stopped'; |
| gameOverScreen.classList.add('hidden'); |
| settingsScreen.classList.add('hidden'); |
| startScreen.classList.remove('hidden'); |
| } |
| |
| |
| function gameLoop() { |
| |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| |
| drawBackground(); |
| |
| |
| if (gameState === 'started') { |
| |
| if (pipes.length === 0 || pipes[pipes.length-1].x < canvas.width - 250) { |
| createPipes(); |
| } |
| updatePipes(); |
| bird.update(); |
| bird.draw(); |
| } |
| |
| |
| drawScore(); |
| |
| frames++; |
| |
| |
| if (gameState === 'started') { |
| animationFrame = requestAnimationFrame(gameLoop); |
| } |
| } |
| |
| |
| showStartScreen(); |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=omnificat/flap" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |