Spaces:
Running
Running
Download index.html from iplaystuff/flap: direct link, hf CLI and curl.
- Browser
- Download file 34.3 kB
-
https://huggingface.co/spaces/iplaystuff/flap/resolve/main/index.html
- Command line
-
hf download hf://spaces/iplaystuff/flap/index.html
-
curl -L -o index.html https://huggingface.co/spaces/iplaystuff/flap/resolve/main/index.html
34.3 kB
| <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 button animation */ | |
| .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'); | |
| // Set canvas dimensions | |
| canvas.width = canvas.clientWidth; | |
| canvas.height = canvas.clientHeight; | |
| // Game variables | |
| 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; | |
| // Fixed pipe interval | |
| const pipeInterval = 120; // Consistent spacing between pipes | |
| // Events | |
| 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); | |
| // Mobile touch controls | |
| 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 = []; // Rebuild on resize | |
| 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(); | |
| } | |
| } | |
| // Bird object with 2.5D effects | |
| const bird = { | |
| x: 100, | |
| y: canvas.height / 2, | |
| radius: 22, | |
| velocity: 0, | |
| jumpHeight: 7.2, | |
| update: function() { | |
| // Physics | |
| if (gameState === 'started') { | |
| this.velocity += gravity; | |
| this.y += this.velocity; | |
| // Bird flapping offset | |
| birdVerticalOffset = Math.sin(Date.now() / 100) * 3; | |
| // Wing movement | |
| wingRotation = Math.sin(Date.now() / 100) * 30; | |
| // Bird angle based on velocity | |
| birdAngle = this.velocity * 3; | |
| if (birdAngle > 25) birdAngle = 25; | |
| if (birdAngle < -25) birdAngle = -25; | |
| } | |
| // Boundary collision (except the ground - ground is handled by pipes/buildings) | |
| 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; | |
| // Bird body with perspective | |
| ctx.save(); | |
| ctx.translate(centerX, centerY); | |
| ctx.rotate(birdAngle * Math.PI / 180); | |
| // Bird body (yellow) | |
| ctx.fillStyle = '#FFD32B'; | |
| ctx.beginPath(); | |
| ctx.ellipse(0, 0, this.radius, this.radius, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Bird wing | |
| 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(); | |
| // Bird detail | |
| ctx.fillStyle = '#000000'; | |
| ctx.beginPath(); | |
| ctx.ellipse(0, -5, 5, 2, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Bird eye | |
| 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(); | |
| // Bird beak | |
| ctx.fillStyle = '#FF7839'; | |
| ctx.beginPath(); | |
| ctx.moveTo(16, -2); | |
| ctx.lineTo(22, 0); | |
| ctx.lineTo(16, 4); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // Draw bird shadow | |
| ctx.fillStyle = '#FCE49B'; | |
| ctx.fillRect(centerX - 10, centerY + this.radius, 20, 8); | |
| }, | |
| reset: function() { | |
| this.y = canvas.height / 2; | |
| this.velocity = 0; | |
| birdAngle = 0; | |
| } | |
| }; | |
| // Pipes array | |
| const pipes = []; | |
| // Enhanced Pipe object with 2.5D effect | |
| function Pipe() { | |
| this.topHeight = Math.floor(Math.random() * 180) + 60; | |
| this.bottomHeight = canvas.height - this.topHeight - pipeGap - 80; | |
| this.x = canvas.width + 80; // Start off-screen | |
| this.width = 80; | |
| this.speed = 3.2; | |
| this.color = '#4d9a17'; | |
| this.highlight = false; | |
| this.passed = false; | |
| this.update = function() { | |
| this.x -= this.speed; | |
| // Pipe collision | |
| 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(); | |
| } | |
| // Score counting | |
| if (!this.passed && this.x < bird.x - this.width/2) { | |
| score++; | |
| this.passed = true; | |
| this.highlight = true; | |
| // Toggle day/night every 5 points | |
| if (score % 5 === 0) { | |
| isDayTime = !isDayTime; | |
| } | |
| } | |
| }; | |
| this.draw = function() { | |
| // Top pipe | |
| 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); | |
| // Top pipe cap | |
| 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); | |
| // Bottom pipe | |
| 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 | |
| ); | |
| // Bottom pipe cap | |
| 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; | |
| } | |
| }; | |
| } | |
| // Create and cache buildings | |
| 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; | |
| } | |
| } | |
| // Draw buildings with 2.5D effect and better windows | |
| function drawBuildings() { | |
| // Create building cache if empty | |
| if (buildingsCache.length === 0) { | |
| createBuildingCache(); | |
| } | |
| // Draw base floor | |
| 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); | |
| // Draw street markings | |
| 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; | |
| // Draw building silhouette (3D effect) | |
| ctx.fillStyle = '#000'; | |
| ctx.fillRect( | |
| building.x - 8, | |
| buildingY - 10, | |
| building.width + 16, | |
| building.height + 10 | |
| ); | |
| // Draw main building body | |
| ctx.fillStyle = building.color; | |
| ctx.fillRect( | |
| building.x, | |
| buildingY, | |
| building.width, | |
| building.height | |
| ); | |
| // Draw building right edge for 3D | |
| ctx.fillStyle = building.edgeColor; | |
| ctx.fillRect( | |
| building.x + building.width - 8, | |
| buildingY, | |
| 8, | |
| building.height | |
| ); | |
| // Draw building top ledge | |
| ctx.fillStyle = '#111'; | |
| ctx.fillRect( | |
| building.x - 8, | |
| buildingY - 10, | |
| building.width + 16, | |
| 10 | |
| ); | |
| // Draw building roof | |
| ctx.fillStyle = '#333'; | |
| ctx.fillRect( | |
| building.x - 15, | |
| buildingY - 30, | |
| building.width + 30, | |
| 20 | |
| ); | |
| // Draw windows within canvas | |
| 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) { // Only place windows 75% of the time | |
| const windowX = building.x + (col * colSpacing) - 4; | |
| const windowY = buildingY + (row * rowSpacing) - 6; | |
| const isLit = isDayTime || Math.random() > 0.6; | |
| // Draw the window on canvas | |
| ctx.fillStyle = isLit ? | |
| (isDayTime ? '#f6d186' : '#FFF59D') : | |
| (isDayTime ? '#9fc4d4' : '#1a1a1a'); | |
| ctx.fillRect(windowX, windowY, 8, 12); | |
| // Add reflection if lit | |
| 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(); | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // Draw background with day/night effects | |
| function drawBackground() { | |
| // Sky gradient | |
| if (isDayTime) { | |
| // Day gradient | |
| const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); | |
| gradient.addColorStop(0, '#70d1ff'); | |
| gradient.addColorStop(1, '#48a0ff'); | |
| ctx.fillStyle = gradient; | |
| } else { | |
| // Night gradient | |
| 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); | |
| // Draw clouds during day | |
| if (isDayTime) { | |
| ctx.fillStyle = 'rgba(255,255,255,0.8)'; | |
| // Cloud positions | |
| 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 { | |
| // Draw stars at night | |
| 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(); | |
| } | |
| } | |
| } | |
| // Render city buildings | |
| drawBuildings(); | |
| } | |
| function createPipes() { | |
| // Add pipes with consistent spacing | |
| if (frames % pipeInterval === 0) { | |
| pipes.push(new Pipe()); | |
| // Limit pipes on screen to 3 max for performance | |
| 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; | |
| // Update best 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'); | |
| // Reset game values | |
| score = 0; | |
| pipes.length = 0; | |
| frames = 0; | |
| isDayTime = true; | |
| birdVerticalOffset = 0; | |
| bird.reset(); | |
| createBuildingCache(); | |
| // Start the game loop | |
| if (animationFrame) cancelAnimationFrame(animationFrame); | |
| gameLoop(); | |
| } | |
| function showStartScreen() { | |
| gameState = 'stopped'; | |
| gameOverScreen.classList.add('hidden'); | |
| settingsScreen.classList.add('hidden'); | |
| startScreen.classList.remove('hidden'); | |
| } | |
| // Main game loop | |
| function gameLoop() { | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw background | |
| drawBackground(); | |
| // Game animation update | |
| if (gameState === 'started') { | |
| // Update and draw game objects | |
| if (pipes.length === 0 || pipes[pipes.length-1].x < canvas.width - 250) { | |
| createPipes(); | |
| } | |
| updatePipes(); | |
| bird.update(); | |
| bird.draw(); | |
| } | |
| // Draw score last for layering | |
| drawScore(); | |
| frames++; | |
| // Continue game loop if game is active | |
| if (gameState === 'started') { | |
| animationFrame = requestAnimationFrame(gameLoop); | |
| } | |
| } | |
| // Initialize the game | |
| 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> |