Skip to content

Commit 518e793

Browse files
Merge pull request #115 from NeerajGoud1/flappybird
Designed FlappyBird game
2 parents 745d0d2 + 3df2764 commit 518e793

File tree

2 files changed

+351
-0
lines changed

2 files changed

+351
-0
lines changed

src/data/content.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import flagger from "../assets/games/flag guess/flagger.png";
2323
import Calculator from "../pages/activities/Calculator";
2424
import { DogHttpCode } from "../pages/activities/DogHttpCode";
2525
import { CatHttpCode } from "../pages/activities/CatHttpCode";
26+
import FlappyBird from "../pages/games/FlappyBird";
2627

2728
export const activities = [
2829
{
@@ -147,4 +148,11 @@ export const games = [
147148
urlTerm: "meme-caption-maker",
148149
element: <MemeCaptionMaker />,
149150
},
151+
{
152+
title: "Flappy Bird",
153+
description: "Fly the bird and avoid obstacles!",
154+
icon: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSap9rEhSD7ghcjTSYN6HuXx0wejnzigvKncg&s",
155+
urlTerm: "FlappyBird",
156+
element: <FlappyBird />,
157+
}
150158
];

src/pages/games/FlappyBird.js

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
import React, { useRef, useState, useEffect } from "react";
2+
3+
4+
const styles = {
5+
container: {
6+
fontFamily: "'Trebuchet MS', sans-serif",
7+
background: "linear-gradient(135deg, #00b4d8 0%, #90e0ef 100%)",
8+
height: "100vh",
9+
width: "100vw",
10+
overflow: "hidden",
11+
position: "relative",
12+
display: "flex",
13+
alignItems: "center",
14+
justifyContent: "center"
15+
},
16+
screen: {
17+
background: "rgba(255,255,255,0.95)",
18+
borderRadius: "18px",
19+
boxShadow: "0 4px 24px rgba(0,0,0,0.15)",
20+
padding: "32px 38px",
21+
minWidth: "340px",
22+
textAlign: "center",
23+
animation: "fadeInScreen 0.7s"
24+
},
25+
btn: {
26+
margin: "12px",
27+
padding: "10px 28px",
28+
borderRadius: "16px",
29+
border: "none",
30+
background: "linear-gradient(90deg,#00b4d8,#48cae4)",
31+
color: "#fff",
32+
fontSize: "18px",
33+
cursor: "pointer",
34+
fontWeight: 500,
35+
transition: "background .2s"
36+
},
37+
canvasBox: {
38+
borderRadius: "14px",
39+
overflow: "hidden",
40+
boxShadow: "0 4px 16px rgba(68,202,228,0.12)",
41+
},
42+
};
43+
44+
const DIFF_SETTINGS = {
45+
Easy: { pipeGap: 170, pipeSpeed: 2, duration: 45 },
46+
Medium: { pipeGap: 140, pipeSpeed: 2.6, duration: 40 },
47+
Hard: { pipeGap: 110, pipeSpeed: 3.6, duration: 35 },
48+
};
49+
50+
const CANVAS_WIDTH = 420;
51+
const CANVAS_HEIGHT = 500;
52+
53+
function randomPipeY(gap) {
54+
55+
return 80 + Math.random() * (CANVAS_HEIGHT - gap - 120);
56+
}
57+
58+
const welcomeText = (
59+
<div>
60+
<h2>Welcome to Flappy Bird!</h2>
61+
<div style={{ fontSize: 18, marginBottom: 12 }}>
62+
<b>How To Play:</b><br />
63+
Tap/press <b>Space</b> or click to make the bird .<br />
64+
Avoid hitting pipes — survive as long as you can.<br />
65+
Game lasts for a set duration depending on difficulty.<br />
66+
<span style={{ color: "#00b4d8", fontWeight: 700 }}>Score points for passing pipes!</span>
67+
</div>
68+
</div>
69+
);
70+
71+
72+
function FlappyBirdGame({ difficulty, onGameEnd }) {
73+
const canvasRef = useRef(null);
74+
const [score, setScore] = useState(0);
75+
const [isRunning, setIsRunning] = useState(true);
76+
const [secondsLeft, setSecondsLeft] = useState(DIFF_SETTINGS[difficulty].duration);
77+
78+
79+
const bird = useRef({
80+
x: 70,
81+
y: CANVAS_HEIGHT/2,
82+
vy: 0,
83+
radius: 18
84+
});
85+
86+
const pipes = useRef([
87+
{ x: CANVAS_WIDTH + 40, y: randomPipeY(DIFF_SETTINGS[difficulty].pipeGap) }
88+
]);
89+
90+
91+
useEffect(() => {
92+
93+
let timer = setInterval(() => {
94+
setSecondsLeft(sec => {
95+
if (sec > 0 && isRunning) return sec - 1;
96+
return sec;
97+
});
98+
}, 1000);
99+
100+
return () => clearInterval(timer);
101+
}, [difficulty, isRunning]);
102+
103+
useEffect(() => {
104+
if (secondsLeft === 0 && isRunning) {
105+
setIsRunning(false);
106+
setTimeout(() => onGameEnd(score), 530);
107+
}
108+
}, [secondsLeft, isRunning, onGameEnd, score]);
109+
110+
111+
useEffect(() => {
112+
const jump = () => {
113+
if (isRunning) bird.current.vy = -4.8;
114+
};
115+
window.addEventListener('keydown', e => {
116+
if (e.code === 'Space') jump();
117+
});
118+
window.addEventListener('mousedown', jump);
119+
return () => {
120+
window.removeEventListener('keydown', () => {});
121+
window.removeEventListener('mousedown', () => {});
122+
};
123+
}, [isRunning]);
124+
125+
126+
useEffect(() => {
127+
let requestId;
128+
const ctx = canvasRef.current.getContext("2d");
129+
let lastPassed = 0;
130+
131+
function draw() {
132+
133+
ctx.fillStyle = "#caf0f8";
134+
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
135+
136+
137+
for (let c=0; c<3; ++c) {
138+
ctx.globalAlpha = 0.39 + 0.25*Math.cos(Date.now()/700 + c*2);
139+
ctx.beginPath();
140+
ctx.arc((c*140+50 + Date.now()/c)/1.8 % CANVAS_WIDTH, 60+36*c, 36+c*10, 0, Math.PI*2);
141+
ctx.fillStyle = "#fff";
142+
ctx.fill();
143+
ctx.globalAlpha = 1;
144+
}
145+
146+
147+
ctx.save();
148+
ctx.shadowColor = "#90e0ef80";
149+
ctx.shadowBlur = 12;
150+
pipes.current.forEach((pipe,i) => {
151+
ctx.fillStyle = "#00b4d8";
152+
153+
ctx.fillRect(pipe.x, 0, 52, pipe.y);
154+
ctx.strokeStyle = "#90e0ef";
155+
ctx.strokeRect(pipe.x, 0, 52, pipe.y);
156+
157+
ctx.fillRect(pipe.x, pipe.y + DIFF_SETTINGS[difficulty].pipeGap, 52, CANVAS_HEIGHT - pipe.y - DIFF_SETTINGS[difficulty].pipeGap);
158+
ctx.strokeRect(pipe.x, pipe.y + DIFF_SETTINGS[difficulty].pipeGap, 52, CANVAS_HEIGHT - pipe.y - DIFF_SETTINGS[difficulty].pipeGap);
159+
});
160+
ctx.restore();
161+
162+
163+
ctx.save();
164+
ctx.globalAlpha = 0.25;
165+
ctx.beginPath();
166+
ctx.arc(bird.current.x, bird.current.y + 18, bird.current.radius, 0, Math.PI * 2);
167+
ctx.fillStyle = "#888";
168+
ctx.fill();
169+
ctx.restore();
170+
171+
172+
ctx.save();
173+
ctx.beginPath();
174+
ctx.arc(bird.current.x, bird.current.y, bird.current.radius, 0, Math.PI*2);
175+
ctx.fillStyle = "#ffbe0b";
176+
ctx.strokeStyle = "#ffd60a";
177+
ctx.lineWidth = 3 + 2*Math.abs(Math.sin(Date.now()/400));
178+
ctx.fill();
179+
ctx.stroke();
180+
181+
ctx.beginPath();
182+
ctx.arc(bird.current.x+8, bird.current.y-6, 4, 0, Math.PI*2);
183+
ctx.fillStyle = "#fff";
184+
ctx.fill();
185+
ctx.beginPath();
186+
ctx.arc(bird.current.x+10, bird.current.y-6, 1.5, 0, Math.PI*2);
187+
ctx.fillStyle = "#111";
188+
ctx.fill();
189+
190+
ctx.beginPath();
191+
ctx.ellipse(bird.current.x-8, bird.current.y, 13, 7 + 8*Math.abs(Math.cos(Date.now()/260)), 0, 0, Math.PI*2);
192+
ctx.fillStyle = "#fdfcdc";
193+
ctx.fill();
194+
ctx.restore();
195+
196+
197+
ctx.save();
198+
ctx.font = "21px Trebuchet MS";
199+
ctx.fillStyle = "#0077b6";
200+
ctx.fillText(`Score: ${score}`, 24, 42);
201+
ctx.fillStyle = "#03045e";
202+
ctx.fillText(`Time: ${secondsLeft}s`, CANVAS_WIDTH-120, 42);
203+
ctx.restore();
204+
}
205+
206+
function gameLoop() {
207+
if (!isRunning) return;
208+
209+
pipes.current.forEach(pipe => pipe.x -= DIFF_SETTINGS[difficulty].pipeSpeed);
210+
211+
212+
if (pipes.current.length && pipes.current[0].x < -52) pipes.current.shift();
213+
let lastPipe = pipes.current[pipes.current.length - 1];
214+
if (lastPipe.x < CANVAS_WIDTH - 180) {
215+
pipes.current.push({
216+
x: CANVAS_WIDTH + 44,
217+
y: randomPipeY(DIFF_SETTINGS[difficulty].pipeGap)
218+
});
219+
}
220+
221+
222+
bird.current.vy += 0.34;
223+
bird.current.y += bird.current.vy;
224+
225+
226+
if (bird.current.y > CANVAS_HEIGHT- bird.current.radius) {
227+
bird.current.y = CANVAS_HEIGHT- bird.current.radius;
228+
bird.current.vy = 0;
229+
setIsRunning(false);
230+
setTimeout(()=>onGameEnd(score),450);
231+
}
232+
if (bird.current.y < bird.current.radius) {
233+
bird.current.y = bird.current.radius + 3;
234+
bird.current.vy = 0.5;
235+
}
236+
237+
238+
for (let i=0; i<pipes.current.length; ++i) {
239+
let pipe = pipes.current[i];
240+
let cx = bird.current.x, cy = bird.current.y, r = bird.current.radius;
241+
let pipeX = pipe.x, pipeW = 52;
242+
let gapY = pipe.y, gapH = DIFF_SETTINGS[difficulty].pipeGap;
243+
244+
if (cx + r > pipeX && cx - r < pipeX + pipeW) {
245+
if (cy - r < gapY || cy + r > gapY + gapH) {
246+
setIsRunning(false);
247+
setTimeout(()=>onGameEnd(score),480);
248+
break;
249+
}
250+
}
251+
}
252+
253+
254+
pipes.current.forEach((pipe,idx) => {
255+
if (!pipe.passed && bird.current.x > pipe.x + 52) {
256+
pipe.passed = true;
257+
setScore(s => s + 1);
258+
}
259+
});
260+
261+
draw();
262+
requestId = requestAnimationFrame(gameLoop);
263+
}
264+
265+
draw();
266+
requestId = requestAnimationFrame(gameLoop);
267+
268+
return () => cancelAnimationFrame(requestId);
269+
}, [difficulty, isRunning, onGameEnd, score, secondsLeft]);
270+
271+
return (
272+
<div style={styles.canvasBox}>
273+
<canvas
274+
ref={canvasRef}
275+
width={CANVAS_WIDTH}
276+
height={CANVAS_HEIGHT}
277+
style={{ display:"block", background:"#caf0f8", borderRadius:14, margin:"auto" }}
278+
/>
279+
</div>
280+
);
281+
}
282+
283+
284+
export default function FlappyBirdMiniGame() {
285+
const [screen, setScreen] = useState("welcome");
286+
const [difficulty, setDifficulty] = useState(null);
287+
const [lastScore, setLastScore] = useState(0);
288+
289+
return (
290+
<div style={styles.container}>
291+
{screen === "welcome" &&
292+
<div style={styles.screen}>
293+
{welcomeText}
294+
<div style={{ marginTop:16 }}>
295+
<b>Select Difficulty:</b>
296+
<div>
297+
{Object.keys(DIFF_SETTINGS).map(diff => (
298+
<button
299+
key={diff}
300+
style={styles.btn}
301+
onClick={() => {
302+
setDifficulty(diff);
303+
setScreen("game");
304+
}}
305+
>{diff}</button>
306+
))}
307+
</div>
308+
</div>
309+
</div>
310+
}
311+
{screen === "game" &&
312+
<FlappyBirdGame
313+
difficulty={difficulty}
314+
onGameEnd={score => {
315+
setLastScore(score);
316+
setScreen("result");
317+
}}
318+
/>
319+
}
320+
{screen === "result" &&
321+
<div style={styles.screen}>
322+
<h2>Game Over!</h2>
323+
<div style={{ fontSize:22, fontWeight:600, color:'#0096c7', margin:"14px 0" }}>
324+
Final Score: {lastScore}
325+
</div>
326+
<div>Ready for another round?</div>
327+
<button
328+
style={styles.btn}
329+
onClick={() => setScreen("welcome")}
330+
>Play Again</button>
331+
</div>
332+
}
333+
<style>
334+
{`
335+
@keyframes fadeInScreen {
336+
from { opacity: 0; transform: scale(.98);}
337+
to { opacity: 1; transform: scale(1);}
338+
}
339+
`}
340+
</style>
341+
</div>
342+
);
343+
}

0 commit comments

Comments
 (0)