Last active
April 11, 2025 12:37
-
-
Save lolpack/412a1009be427dbecc29a3ab4ce85f92 to your computer and use it in GitHub Desktop.
Flappy Bird Learning Game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- View here https://gist.githack.com/lolpack/412a1009be427dbecc29a3ab4ce85f92/raw/index.html --> | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <title>Flappy You+ Configurable</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| display: flex; | |
| height: 100vh; | |
| font-family: sans-serif; | |
| } | |
| .code-pane, .game-pane { | |
| width: 50%; | |
| height: 100%; | |
| } | |
| .code-pane { | |
| background: #1e1e1e; | |
| color: #00ff90; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 10px; | |
| } | |
| #code-editor { | |
| flex-grow: 1; | |
| font-family: monospace; | |
| font-size: 16px; | |
| background: #1e1e1e; | |
| color: #00ff90; | |
| border: none; | |
| padding: 10px; | |
| resize: none; | |
| } | |
| button { | |
| padding: 12px; | |
| background: #00ff90; | |
| color: #000; | |
| font-weight: bold; | |
| font-size: 16px; | |
| cursor: pointer; | |
| border: none; | |
| margin-top: 10px; | |
| } | |
| .game-pane { | |
| background: #87ceeb; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| position: relative; | |
| } | |
| canvas { | |
| border: 2px solid black; | |
| margin-top: 10px; | |
| } | |
| video { | |
| display: none; | |
| } | |
| .score { | |
| position: absolute; | |
| top: 10px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| font-size: 24px; | |
| font-weight: bold; | |
| color: white; | |
| text-shadow: 1px 1px 3px black; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="code-pane"> | |
| <textarea id="code-editor"> | |
| // Hello Commodore John Rodgers! | |
| // Customize your game below and learn a little JavaScript! | |
| const playerName = "YourNameHere"; | |
| const tubeColor = "green"; | |
| const tubeCapColor = "darkgreen"; | |
| const gravitySetting = 0.25; | |
| const jumpForce = -6; | |
| const hatEmoji = ""; // Try 🧢 👑 🎩 🎓 👒 | |
| </textarea> | |
| <button id="start">Start</button> | |
| </div> | |
| <div class="game-pane"> | |
| <div class="score" id="score-display">Score: 0</div> | |
| <video id="video" autoplay playsinline></video> | |
| <canvas id="canvas" width="300" height="500"></canvas> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById("canvas"); | |
| const ctx = canvas.getContext("2d"); | |
| const video = document.getElementById("video"); | |
| const startBtn = document.getElementById("start"); | |
| const codeEditor = document.getElementById("code-editor"); | |
| const scoreDisplay = document.getElementById("score-display"); | |
| let playerName = "Player"; | |
| let tubeColor = "green"; | |
| let tubeCapColor = "darkgreen"; | |
| let gravity = 0.25; | |
| let jumpForce = -6; | |
| let faceImage = new Image(); | |
| let y = 200; | |
| let velocity = 0; | |
| let isPlaying = false; | |
| let hasGameStarted = false; | |
| let pipes = []; | |
| let hatEmoji = ""; | |
| const pipeGap = 240; | |
| const pipeSpeed = 2; | |
| let frame = 0; | |
| let animationId = null; | |
| let score = 0; | |
| startBtn.onclick = async () => { | |
| if (hasGameStarted) return; | |
| hasGameStarted = true; | |
| try { | |
| const code = codeEditor.value; | |
| // Safely evaluate user code in its own scope | |
| const userSettings = new Function(` | |
| ${code} | |
| return { | |
| playerName, | |
| tubeColor, | |
| tubeCapColor, | |
| gravitySetting, | |
| jumpForce, | |
| hatEmoji | |
| }; | |
| `)(); | |
| // Apply config | |
| playerName = userSettings.playerName || "Player"; | |
| tubeColor = userSettings.tubeColor || "green"; | |
| tubeCapColor = userSettings.tubeCapColor || "darkgreen"; | |
| gravity = userSettings.gravitySetting ?? 0.25; | |
| jumpForce = userSettings.jumpForce ?? -6; | |
| hatEmoji = userSettings.hatEmoji || ""; | |
| const stream = await navigator.mediaDevices.getUserMedia({ video: true }); | |
| video.srcObject = stream; | |
| await new Promise(res => setTimeout(res, 1000)); | |
| const snap = document.createElement("canvas"); | |
| snap.width = video.videoWidth; | |
| snap.height = video.videoHeight; | |
| const snapCtx = snap.getContext("2d"); | |
| snapCtx.drawImage(video, 0, 0); | |
| faceImage.src = snap.toDataURL(); | |
| stream.getTracks().forEach(t => t.stop()); | |
| y = 200; | |
| velocity = 0; | |
| pipes = []; | |
| frame = 0; | |
| score = 0; | |
| scoreDisplay.textContent = "Score: 0"; | |
| isPlaying = true; | |
| cancelAnimationFrame(animationId); | |
| loop(); | |
| } catch (e) { | |
| alert("Error: " + e.message); | |
| hasGameStarted = false; | |
| } | |
| }; | |
| function loop() { | |
| if (!isPlaying) return; | |
| animationId = requestAnimationFrame(loop); | |
| frame++; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = "#87ceeb"; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| if (frame > 60 && frame % 100 === 0) { | |
| const top = Math.floor(Math.random() * (canvas.height - pipeGap - 80)) + 20; | |
| pipes.push({ x: canvas.width, top, passed: false }); | |
| } | |
| for (let i = pipes.length - 1; i >= 0; i--) { | |
| pipes[i].x -= pipeSpeed; | |
| drawPipe(pipes[i].x, 0, pipes[i].top, true); | |
| drawPipe(pipes[i].x, pipes[i].top + pipeGap, canvas.height - pipes[i].top - pipeGap, false); | |
| if (frame > 100) { | |
| if ( | |
| 120 < pipes[i].x + 60 && | |
| 120 + 60 > pipes[i].x && | |
| (y < pipes[i].top || y + 60 > pipes[i].top + pipeGap) | |
| ) { | |
| gameOver(); | |
| } | |
| } | |
| if (!pipes[i].passed && pipes[i].x + 60 < 120) { | |
| pipes[i].passed = true; | |
| score++; | |
| scoreDisplay.textContent = "Score: " + score; | |
| } | |
| if (pipes[i].x < -60) pipes.splice(i, 1); | |
| } | |
| velocity += gravity; | |
| velocity *= 0.98; | |
| y += velocity; | |
| if (y > canvas.height - 60) { | |
| y = canvas.height - 60; | |
| velocity = 0; | |
| gameOver(); | |
| } | |
| if (y < 0) { | |
| y = 0; | |
| velocity = 0; | |
| } | |
| if (faceImage.complete && faceImage.naturalWidth > 0) { | |
| ctx.save(); | |
| ctx.translate(150, y + 30); | |
| ctx.rotate(velocity * 0.03); | |
| // Clip to circle for face | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, 30, 0, Math.PI * 2); | |
| ctx.closePath(); | |
| ctx.clip(); | |
| ctx.drawImage(faceImage, -30, -30, 60, 60); | |
| ctx.restore(); | |
| // Draw hat emoji slightly above head | |
| ctx.save(); | |
| ctx.translate(150, y + 60); | |
| ctx.rotate(velocity * 0.03); | |
| ctx.font = "50px serif"; | |
| ctx.textAlign = "center"; | |
| ctx.fillText(hatEmoji, 0, -40); // slightly above circle | |
| ctx.restore(); | |
| } | |
| ctx.fillStyle = "black"; | |
| ctx.font = "16px Arial"; | |
| ctx.textAlign = "center"; | |
| ctx.fillText(playerName, 150, y + 75); | |
| } | |
| function drawPipe(x, y, height, isTop) { | |
| ctx.fillStyle = tubeColor; | |
| ctx.fillRect(x, y, 60, height); | |
| ctx.fillStyle = tubeCapColor; | |
| if (isTop) { | |
| ctx.fillRect(x - 5, y + height - 10, 70, 10); | |
| } else { | |
| ctx.fillRect(x - 5, y, 70, 10); | |
| } | |
| } | |
| function gameOver() { | |
| isPlaying = false; | |
| hasGameStarted = false; | |
| cancelAnimationFrame(animationId); | |
| ctx.fillStyle = "rgba(0,0,0,0.6)"; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = "#fff"; | |
| ctx.font = "24px Arial"; | |
| ctx.textAlign = "center"; | |
| ctx.fillText("Game Over", canvas.width / 2, canvas.height / 2); | |
| ctx.font = "16px Arial"; | |
| ctx.fillText("Score: " + score, canvas.width / 2, canvas.height / 2 + 30); | |
| ctx.fillText("Click Start to try again", canvas.width / 2, canvas.height / 2 + 55); | |
| } | |
| document.addEventListener("keydown", (e) => { | |
| if (e.code === "Space" && isPlaying && y > 0) { | |
| velocity = jumpForce; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment