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] || '

Project Coming Soon!

'; } +} function getTicTacToeHTML() { return ` diff --git a/web-app/js/projects/flappy-game.js b/web-app/js/projects/flappy-game.js index 0f3dbb8..21aa08f 100644 --- a/web-app/js/projects/flappy-game.js +++ b/web-app/js/projects/flappy-game.js @@ -1,39 +1,32 @@ function getFlappyGameHTML() { return `
-

🐦 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