diff --git a/web-app/js/projects.js b/web-app/js/projects.js
index ecf9b64..e7bc683 100644
--- a/web-app/js/projects.js
+++ b/web-app/js/projects.js
@@ -3552,6 +3552,7 @@ function initTowerOfHanoi() {
return projects[projectName] || '
-
🐦 Flappy Game
-
-
+
🐦 Flappy Bird
+
🎮 How to Play
- - 👆 Click anywhere on the game screen to make the bird jump!
- - 🚫 Avoid touching the neon purple pipes (balls).
- - ⚡ Stay within the top and bottom bounds of the screen.
-
-
-
🏆 Scoring System
-
- - ⭐️ Earn 1 point for every pipe you successfully dodge and pass.
+ - Press SPACE or click to jump.
+ - Avoid the green pipes.
+ - Stay inside the game screen.
-
+
-
-
-
-
-
+
+
+
+
`;
}
function initFlappyGame() {
- const startScreen = document.getElementById('flappyStartScreen');
- const gameScreen = document.getElementById('flappyGameScreen');
- const startBtn = document.getElementById('flappyStartBtn');
- const backBtn = document.getElementById('flappyBackBtn');
-
- const canvas = document.getElementById('flappyCanvas');
- if (!canvas) return;
- const ctx = canvas.getContext('2d');
-
- // turtle coordinates: center is (0,0), x goes -200 to 200, y goes -200 to 200.
- // canvas coordinates: top-left is (0,0), x goes 0 to 400, y goes 0 to 400.
- // convert turtle(x,y) to canvas(cx,cy):
- // cx = x + 200
- // cy = 200 - y
-
- let bird = { x: 0, y: 0 };
- let balls = [];
+ const startScreen = document.getElementById("flappyStartScreen");
+ const gameScreen = document.getElementById("flappyGameScreen");
+ const startBtn = document.getElementById("flappyStartBtn");
+ const backBtn = document.getElementById("flappyBackBtn");
+ const canvas = document.getElementById("flappyCanvas");
+
+ if (!startScreen || !gameScreen || !startBtn || !backBtn || !canvas) {
+ console.warn("Flappy Bird elements not found.");
+ return;
+ }
+
+ const ctx = canvas.getContext("2d");
+
+ const WIDTH = canvas.width;
+ const HEIGHT = canvas.height;
+
+ const SKY_BLUE = "#87ceeb";
+ const GREEN = "#00c800";
+ const DARK_GREEN = "#009600";
+ const YELLOW = "#ffdc00";
+ const WHITE = "#ffffff";
+ const BLACK = "#000000";
+
+ const bird = {
+ x: 80,
+ y: HEIGHT / 2,
+ radius: 15,
+ velocity: 0,
+ gravity: 0.35,
+ jumpStrength: -7
+ };
+
+ const pipeWidth = 60;
+ const pipeGap = 180;
+ const pipeSpeed = 2;
+
+ let pipes = [];
let score = 0;
- let gameOver = false;
- let gameLoop;
+ let gameRunning = false;
+ let animationId = null;
- function draw() {
- //clear screen
- ctx.fillStyle = '#0f172a'; // dark slate
- ctx.fillRect(0, 0, canvas.width, canvas.height);
-
- //draw score
- ctx.fillStyle = 'white';
- ctx.font = 'bold 14px Arial';
- ctx.textAlign = 'left';
- ctx.fillText(`Score: ${score}`, 10, 20);
-
- //draw balls
- ctx.fillStyle = '#8b5cf6'; // neon purple
- balls.forEach(ball => {
- let cx = ball.x + 200;
- let cy = 200 - ball.y;
- ctx.beginPath();
- ctx.arc(cx, cy, 10, 0, Math.PI * 2);
- ctx.fill();
- });
+ function createPipe() {
+ const gapY = Math.floor(Math.random() * ((HEIGHT - 200) - 150 + 1)) + 150;
+ pipes.push({ x: WIDTH, gapY: gapY, passed: false });
+ }
- //draw bird
- let bx = bird.x + 200;
- let by = 200 - bird.y;
+ function drawBird() {
+ // Yellow body
+ ctx.fillStyle = YELLOW;
+ ctx.beginPath();
+ ctx.arc(bird.x, bird.y, bird.radius, 0, Math.PI * 2);
+ ctx.fill();
- ctx.fillStyle = gameOver ? '#ef4444' : '#06b6d4'; // neon red or cyan
+ // Black eye
+ ctx.fillStyle = BLACK;
ctx.beginPath();
- ctx.arc(bx, by, 5, 0, Math.PI * 2);
+ ctx.arc(bird.x + 6, bird.y - 5, 3, 0, Math.PI * 2);
ctx.fill();
- //draw game over text
- if (gameOver) {
- ctx.fillStyle = 'white';
- ctx.textAlign = 'center';
- ctx.font = 'bold 24px Arial';
- ctx.fillText("💥 GAME OVER 💥", 200, 180);
- ctx.font = 'normal 14px Arial';
- ctx.fillText("🔄 Click anywhere to Play Again", 200, 220);
+ // Black beak
+ ctx.fillStyle = BLACK;
+ ctx.beginPath();
+ ctx.moveTo(bird.x + bird.radius + 5, bird.y); // tip of beak
+ ctx.lineTo(bird.x + bird.radius, bird.y - 6); // upper base
+ ctx.lineTo(bird.x + bird.radius, bird.y + 6); // lower base\
+ ctx.closePath();
+ ctx.fill();
+ }
+
+ function drawPipes() {
+ pipes.forEach(pipe => {
+ const bottomY = pipe.gapY + pipeGap;
+
+ ctx.fillStyle = GREEN;
+ ctx.fillRect(pipe.x, 0, pipeWidth, pipe.gapY);
+ ctx.fillRect(pipe.x, bottomY, pipeWidth, HEIGHT - bottomY);
+
+ ctx.fillStyle = DARK_GREEN;
+ ctx.fillRect(pipe.x, pipe.gapY - 20, pipeWidth, 20);
+ ctx.fillRect(pipe.x, bottomY, pipeWidth, 20);
+ });
+ }
+
+ function checkCollision() {
+ if (bird.y - bird.radius <= 0 || bird.y + bird.radius >= HEIGHT) {
+ return true;
}
+
+ for (const pipe of pipes) {
+ const birdLeft = bird.x - bird.radius;
+ const birdRight = bird.x + bird.radius;
+ const birdTop = bird.y - bird.radius;
+ const birdBottom = bird.y + bird.radius;
+
+ const pipeLeft = pipe.x;
+ const pipeRight = pipe.x + pipeWidth;
+ const topPipeBottom = pipe.gapY;
+ const bottomPipeTop = pipe.gapY + pipeGap;
+
+ const insidePipeX = birdRight > pipeLeft && birdLeft < pipeRight;
+ const hitTopPipe = birdTop < topPipeBottom;
+ const hitBottomPipe = birdBottom > bottomPipeTop;
+
+ if (insidePipeX && (hitTopPipe || hitBottomPipe)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function drawScore() {
+ ctx.fillStyle = WHITE;
+ ctx.font = "32px Arial";
+ ctx.textAlign = "left";
+ ctx.fillText(`Score: ${score}`, 10, 40);
+ }
+
+ function gameOverScreen() {
+ ctx.fillStyle = SKY_BLUE;
+ ctx.fillRect(0, 0, WIDTH, HEIGHT);
+
+ ctx.fillStyle = BLACK;
+ ctx.textAlign = "center";
+
+ ctx.font = "40px Arial";
+ ctx.fillText("Game Over!", WIDTH / 2, 220);
+
+ ctx.font = "36px Arial";
+ ctx.fillText(`Score: ${score}`, WIDTH / 2, 270);
+
+ ctx.font = "22px Arial";
+ ctx.fillText("Press SPACE or Click to Restart", WIDTH / 2, 340);
}
- function inside(p) {
- return -200 < p.x && p.x < 200 && -200 < p.y && p.y < 200;
+ function resetGame() {
+ bird.y = HEIGHT / 2;
+ bird.velocity = 0;
+ pipes = [];
+ score = 0;
+ gameRunning = true;
+ createPipe();
}
- function move() {
- if (gameOver) return;
+ function update() {
+ if (!gameRunning) return;
- bird.y -= 5;
+ bird.velocity += bird.gravity;
+ bird.y += bird.velocity;
- balls.forEach(ball => {
- ball.x -= 3;
+ pipes.forEach(pipe => {
+ pipe.x -= pipeSpeed;
});
- if (Math.floor(Math.random() * 10) === 0) {
- let y = Math.floor(Math.random() * 398) - 199;
- balls.push({ x: 199, y: y });
+ if (pipes.length === 0 || pipes[pipes.length - 1].x < WIDTH - 220) {
+ createPipe();
}
- while (balls.length > 0 && !inside(balls[0])) {
- balls.shift();
- score += 1;
- }
+ pipes = pipes.filter(pipe => pipe.x + pipeWidth > 0);
- if (!inside(bird)) {
- gameOver = true;
- draw();
- return;
+ pipes.forEach(pipe => {
+ if (!pipe.passed && pipe.x + pipeWidth < bird.x) {
+ score++;
+ pipe.passed = true;
+ }
+ });
+
+ if (checkCollision()) {
+ gameRunning = false;
}
+ }
- for (let i = 0; i < balls.length; i++) {
- let dx = balls[i].x - bird.x;
- let dy = balls[i].y - bird.y;
- let dist = Math.sqrt(dx * dx + dy * dy);
- if (dist < 15) {
- gameOver = true;
- draw();
- return;
- }
+ function draw() {
+ if (gameRunning) {
+ ctx.fillStyle = SKY_BLUE;
+ ctx.fillRect(0, 0, WIDTH, HEIGHT);
+ drawBird();
+ drawPipes();
+ drawScore();
+ } else {
+ gameOverScreen();
}
+ }
+ function gameLoop() {
+ update();
draw();
+ animationId = requestAnimationFrame(gameLoop);
}
- function resetGame() {
- gameOver = false;
- score = 0;
- bird = { x: 0, y: 0 };
- balls = [];
- if (gameLoop) clearInterval(gameLoop);
- gameLoop = setInterval(move, 50);
- draw();
+ function startGame() {
+ if (animationId) {
+ cancelAnimationFrame(animationId);
+ }
+
+ resetGame();
+ gameLoop();
}
- function tap() {
- if (gameOver) {
- resetGame();
+ function jump() {
+ if (gameScreen.style.display === "none") return;
+
+ if (gameRunning) {
+ bird.velocity = bird.jumpStrength;
} else {
- bird.y += 30;
+ resetGame();
+ }
+ }
+
+ function handleKeyDown(event) {
+ if (gameScreen.style.display === "none") return;
+
+ if (event.code === "Space") {
+ event.preventDefault();
+ jump();
}
}
- canvas.addEventListener('mousedown', tap);
+ function stopGame() {
+ gameRunning = false;
- // UI Event Listeners
- startBtn.addEventListener('click', () => {
- startScreen.style.display = 'none';
- gameScreen.style.display = 'flex';
- resetGame(); // Start the game
+ if (animationId) {
+ cancelAnimationFrame(animationId);
+ animationId = null;
+ }
+ }
+
+ startBtn.addEventListener("click", function () {
+ startScreen.style.display = "none";
+ gameScreen.style.display = "flex";
+ startGame();
});
- backBtn.addEventListener('click', () => {
- gameScreen.style.display = 'none';
- startScreen.style.display = 'flex';
- if (gameLoop) clearInterval(gameLoop);
+ backBtn.addEventListener("click", function () {
+ stopGame();
+ gameScreen.style.display = "none";
+ startScreen.style.display = "flex";
});
- const modalCloseBtn = document.getElementById('modalClose');
- if (modalCloseBtn) {
- const cleanup = () => {
- if (gameLoop) clearInterval(gameLoop);
- modalCloseBtn.removeEventListener('click', cleanup);
- };
- modalCloseBtn.addEventListener('click', cleanup);
- }
-}
+ canvas.addEventListener("click", jump);
+ document.addEventListener("keydown", handleKeyDown);
+}
\ No newline at end of file