Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Minimal Flappy Bird</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| background: linear-gradient(to bottom, #64b3f4, #c2e59c); | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| overflow: hidden; | |
| } | |
| .game-container { | |
| position: relative; | |
| width: 360px; | |
| text-align: center; | |
| } | |
| canvas { | |
| background: #70c5ce; | |
| border-radius: 10px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); | |
| display: block; | |
| margin: 0 auto; | |
| } | |
| h1 { | |
| color: #fff; | |
| text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); | |
| margin-bottom: 15px; | |
| font-size: 2.5rem; | |
| } | |
| .score-display { | |
| position: absolute; | |
| top: 20px; | |
| left: 0; | |
| right: 0; | |
| text-align: center; | |
| font-size: 50px; | |
| font-weight: bold; | |
| color: white; | |
| text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); | |
| z-index: 10; | |
| } | |
| .instructions { | |
| background: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| padding: 15px; | |
| border-radius: 10px; | |
| margin-top: 20px; | |
| font-size: 16px; | |
| line-height: 1.5; | |
| } | |
| .game-over { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(0, 0, 0, 0.85); | |
| color: white; | |
| padding: 30px; | |
| border-radius: 15px; | |
| text-align: center; | |
| display: none; | |
| z-index: 20; | |
| width: 80%; | |
| } | |
| .game-over h2 { | |
| font-size: 36px; | |
| margin-bottom: 15px; | |
| color: #ffcc00; | |
| } | |
| .final-score { | |
| font-size: 28px; | |
| margin: 15px 0; | |
| } | |
| button { | |
| background: #ffcc00; | |
| border: none; | |
| padding: 12px 30px; | |
| font-size: 18px; | |
| border-radius: 50px; | |
| cursor: pointer; | |
| font-weight: bold; | |
| margin-top: 15px; | |
| transition: all 0.2s; | |
| } | |
| button:hover { | |
| background: #ffd84d; | |
| transform: scale(1.05); | |
| } | |
| .pipe-counter { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 0; | |
| right: 0; | |
| text-align: center; | |
| color: white; | |
| font-size: 18px; | |
| text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="game-container"> | |
| <h1>Flappy Bird</h1> | |
| <div class="score-display">0</div> | |
| <canvas id="gameCanvas" width="360" height="640"></canvas> | |
| <div class="pipe-counter">Pipes Passed: <span id="pipesPassed">0</span></div> | |
| <div class="instructions"> | |
| <p>Press SPACE, CLICK, or TOUCH to flap your wings!</p> | |
| <p>Avoid the pipes and don't hit the ground!</p> | |
| </div> | |
| <div class="game-over" id="gameOverScreen"> | |
| <h2>Game Over!</h2> | |
| <div class="final-score">Score: <span id="finalScore">0</span></div> | |
| <div>Pipes Passed: <span id="finalPipes">0</span></div> | |
| <button id="restartButton">Play Again</button> | |
| </div> | |
| </div> | |
| <script> | |
| // Game variables | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const scoreDisplay = document.querySelector('.score-display'); | |
| const gameOverScreen = document.getElementById('gameOverScreen'); | |
| const finalScore = document.getElementById('finalScore'); | |
| const finalPipes = document.getElementById('finalPipes'); | |
| const restartButton = document.getElementById('restartButton'); | |
| const pipesPassedDisplay = document.getElementById('pipesPassed'); | |
| // Game state | |
| let gameState = "start"; // start, playing, gameover | |
| let score = 0; | |
| let pipesPassed = 0; | |
| let frames = 0; | |
| // Bird properties | |
| const bird = { | |
| x: 50, | |
| y: canvas.height / 2 - 10, | |
| width: 34, | |
| height: 24, | |
| gravity: 0.5, | |
| velocity: 0, | |
| jump: -10, | |
| draw: function() { | |
| ctx.fillStyle = '#FFD700'; // Yellow body | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, 15, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Draw eye | |
| ctx.fillStyle = 'black'; | |
| ctx.beginPath(); | |
| ctx.arc(this.x + 8, this.y - 3, 4, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Draw beak | |
| ctx.fillStyle = '#FF8C00'; | |
| ctx.beginPath(); | |
| ctx.moveTo(this.x + 15, this.y); | |
| ctx.lineTo(this.x + 30, this.y); | |
| ctx.lineTo(this.x + 15, this.y + 8); | |
| ctx.fill(); | |
| // Draw wing | |
| ctx.fillStyle = '#FFA500'; | |
| ctx.beginPath(); | |
| ctx.ellipse(this.x - 5, this.y + 5, 10, 7, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| }, | |
| update: function() { | |
| if (gameState === "playing") { | |
| this.velocity += this.gravity; | |
| this.y += this.velocity; | |
| // Floor collision | |
| if (this.y + this.height/2 >= canvas.height - groundHeight) { | |
| this.y = canvas.height - groundHeight - this.height/2; | |
| if (gameState === "playing") { | |
| gameOver(); | |
| } | |
| } | |
| // Ceiling collision | |
| if (this.y - this.height/2 <= 0) { | |
| this.y = this.height/2; | |
| this.velocity = 0; | |
| } | |
| } | |
| }, | |
| flap: function() { | |
| this.velocity = this.jump; | |
| }, | |
| reset: function() { | |
| this.y = canvas.height / 2 - 10; | |
| this.velocity = 0; | |
| } | |
| }; | |
| // Ground properties | |
| const groundHeight = 80; | |
| function drawGround() { | |
| ctx.fillStyle = '#DEB887'; // Brown ground | |
| ctx.fillRect(0, canvas.height - groundHeight, canvas.width, groundHeight); | |
| // Draw grass | |
| ctx.fillStyle = '#7CFC00'; // Green grass | |
| ctx.fillRect(0, canvas.height - groundHeight, canvas.width, 15); | |
| // Draw ground pattern | |
| ctx.fillStyle = '#8B4513'; | |
| for (let i = 0; i < canvas.width; i += 30) { | |
| ctx.fillRect(i, canvas.height - 20, 15, 5); | |
| } | |
| } | |
| // Pipes | |
| const pipes = { | |
| position: [], | |
| gap: 180, | |
| maxYPos: -150, | |
| dx: 2, | |
| draw: function() { | |
| for (let i = 0; i < this.position.length; i++) { | |
| let p = this.position[i]; | |
| // Top pipe | |
| ctx.fillStyle = '#228B22'; // Green pipe | |
| ctx.fillRect(p.x, p.y, p.width, p.height); | |
| // Pipe cap | |
| ctx.fillStyle = '#006400'; | |
| ctx.fillRect(p.x - 5, p.y + p.height - 20, p.width + 10, 20); | |
| // Bottom pipe | |
| ctx.fillStyle = '#228B22'; | |
| ctx.fillRect(p.x, p.y + p.height + this.gap, p.width, canvas.height); | |
| // Pipe cap | |
| ctx.fillStyle = '#006400'; | |
| ctx.fillRect(p.x - 5, p.y + p.height + this.gap, p.width + 10, 20); | |
| } | |
| }, | |
| update: function() { | |
| if (gameState !== "playing") return; | |
| if (frames % 100 === 0) { | |
| this.position.push({ | |
| x: canvas.width, | |
| y: this.maxYPos * (Math.random() + 1), | |
| width: 60, | |
| height: 300 | |
| }); | |
| } | |
| for (let i = 0; i < this.position.length; i++) { | |
| let p = this.position[i]; | |
| // Move pipe to the left | |
| p.x -= this.dx; | |
| // If pipe moves off screen, remove it | |
| if (p.x + p.width <= 0) { | |
| this.position.shift(); | |
| pipesPassed++; | |
| pipesPassedDisplay.textContent = pipesPassed; | |
| } | |
| // Collision detection | |
| // Top pipe | |
| if ( | |
| bird.x + bird.width/2 > p.x && | |
| bird.x - bird.width/2 < p.x + p.width && | |
| bird.y - bird.height/2 < p.y + p.height | |
| ) { | |
| gameOver(); | |
| } | |
| // Bottom pipe | |
| if ( | |
| bird.x + bird.width/2 > p.x && | |
| bird.x - bird.width/2 < p.x + p.width && | |
| bird.y + bird.height/2 > p.y + p.height + this.gap | |
| ) { | |
| gameOver(); | |
| } | |
| // Score when passing a pipe | |
| if (p.x + p.width < bird.x && !p.passed) { | |
| score++; | |
| scoreDisplay.textContent = score; | |
| p.passed = true; | |
| } | |
| } | |
| }, | |
| reset: function() { | |
| this.position = []; | |
| } | |
| }; | |
| // Background elements | |
| const background = { | |
| draw: function() { | |
| // Sky | |
| ctx.fillStyle = '#70c5ce'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Clouds | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; | |
| ctx.beginPath(); | |
| ctx.arc(100, 80, 30, 0, Math.PI * 2); | |
| ctx.arc(130, 70, 35, 0, Math.PI * 2); | |
| ctx.arc(160, 80, 25, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.arc(300, 120, 30, 0, Math.PI * 2); | |
| ctx.arc(330, 110, 35, 0, Math.PI * 2); | |
| ctx.arc(360, 120, 25, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| }; | |
| // Game functions | |
| function gameOver() { | |
| gameState = "gameover"; | |
| finalScore.textContent = score; | |
| finalPipes.textContent = pipesPassed; | |
| gameOverScreen.style.display = "block"; | |
| } | |
| function resetGame() { | |
| score = 0; | |
| pipesPassed = 0; | |
| frames = 0; | |
| scoreDisplay.textContent = score; | |
| pipesPassedDisplay.textContent = pipesPassed; | |
| bird.reset(); | |
| pipes.reset(); | |
| gameOverScreen.style.display = "none"; | |
| gameState = "playing"; | |
| } | |
| // Draw everything | |
| function draw() { | |
| background.draw(); | |
| pipes.draw(); | |
| drawGround(); | |
| bird.draw(); | |
| } | |
| // Update game state | |
| function update() { | |
| bird.update(); | |
| pipes.update(); | |
| } | |
| // Game loop | |
| function loop() { | |
| update(); | |
| draw(); | |
| frames++; | |
| requestAnimationFrame(loop); | |
| } | |
| // Event listeners | |
| function flap() { | |
| if (gameState === "start") { | |
| gameState = "playing"; | |
| } | |
| if (gameState === "playing") { | |
| bird.flap(); | |
| } | |
| } | |
| canvas.addEventListener("click", flap); | |
| document.addEventListener("keydown", function(e) { | |
| if (e.code === "Space") { | |
| flap(); | |
| } | |
| }); | |
| restartButton.addEventListener("click", resetGame); | |
| // Touch support for mobile devices | |
| canvas.addEventListener("touchstart", function(e) { | |
| e.preventDefault(); | |
| flap(); | |
| }); | |
| // Start the game | |
| loop(); | |
| </script> | |
| </body> | |
| </html> |