
Prompt
Using React, create a clone of the Flappy Bird game, including gravity, obstacles (pipes), scoring, and collision detection.
A system prompt was added to support web rendering
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Flappy Bird Clone</title>
<style>
body {
margin: 0;
background: #d0f4f7;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
user-select: none;
overflow: hidden;
}
.game {
position: relative;
width: 400px;
height: 600px;
background: #70c5ce;
border: 2px solid #000;
overflow: hidden;
}
.bird {
position: absolute;
width: 34px;
height: 24px;
background: yellow;
border-radius: 50%;
}
.pipe {
position: absolute;
width: 60px;
background: green;
}
.score {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
font: bold 24px sans-serif;
color: #fff;
text-shadow: 2px 2px #000;
}
.game-over {
position: absolute;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: white;
font: bold 32px sans-serif;
}
.game-over div + div {
font-size: 20px;
margin-top: 10px;
}
</style>
<!-- React and Babel -->
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
class FlappyBird extends React.Component {
constructor(props) {
super(props);
this.gameWidth = 400;
this.gameHeight = 600;
this.birdX = 80;
this.birdSize = 34;
this.gravity = 0.6;
this.jumpVelocity = -10;
this.pipeWidth = 60;
this.pipeGap = 150;
this.pipeInterval = 90; // frames between pipes
this.pipeSpeed = 2;
this.state = {
birdY: this.gameHeight / 2,
velocity: 0,
pipes: [],
frameCount: 0,
score: 0,
gameOver: false
};
}
componentDidMount() {
document.addEventListener('keydown', this.handleInput);
document.addEventListener('mousedown', this.handleInput);
this.interval = setInterval(this.gameLoop, 20);
}
componentWillUnmount() {
clearInterval(this.interval);
document.removeEventListener('keydown', this.handleInput);
document.removeEventListener('mousedown', this.handleInput);
}
handleInput = (e) => {
if (!this.state.gameOver && (e.key === ' ' || e.type === 'mousedown')) {
this.setState({ velocity: this.jumpVelocity });
} else if (this.state.gameOver) {
this.resetGame();
}
};
gameLoop = () => {
let { birdY, velocity, pipes, frameCount, score, gameOver } = this.state;
if (gameOver) return;
// Bird physics
velocity += this.gravity;
birdY += velocity;
// Add new pipe
if (frameCount % this.pipeInterval === 0) {
const topHeight = Math.random() * (this.gameHeight - this.pipeGap - 100) + 50;
pipes.push({ x: this.gameWidth, top: topHeight, passed: false });
}
// Move pipes
const newPipes = pipes.map(pipe => ({ ...pipe, x: pipe.x - this.pipeSpeed }));
// Remove off-screen pipes
while (newPipes.length && newPipes[0].x < -this.pipeWidth) {
newPipes.shift();
}
// Update score
newPipes.forEach(pipe => {
if (!pipe.passed && pipe.x + this.pipeWidth < this.birdX) {
score++;
pipe.passed = true;
}
});
// Collision detection
if (birdY < 0 || birdY + this.birdSize > this.gameHeight) {
gameOver = true;
}
newPipes.forEach(pipe => {
if (this.isColliding(birdY, pipe)) {
gameOver = true;
}
});
if (gameOver) {
clearInterval(this.interval);
}
this.setState({
birdY,
velocity,
pipes: newPipes,
frameCount: frameCount + 1,
score,
gameOver
});
};
isColliding(birdY, pipe) {
const bx = this.birdX;
const by = birdY;
const bw = this.birdSize;
const bh = this.birdSize;
const px = pipe.x;
const pw = this.pipeWidth;
const topH = pipe.top;
const bottomY = topH + this.pipeGap;
if (bx + bw > px && bx < px + pw) {
if (by < topH || by + bh > bottomY) {
return true;
}
}
return false;
}
resetGame() {
this.setState({
birdY: this.gameHeight / 2,
velocity: 0,
pipes: [],
frameCount: 0,
score: 0,
gameOver: false
}, () => {
this.interval = setInterval(this.gameLoop, 20);
});
}
render() {
const { birdY, pipes, score, gameOver } = this.state;
return (
<div className="game">
<div
className="bird"
style={{ top: birdY, left: this.birdX }}
/>
{pipes.map((pipe, i) => (
<React.Fragment key={i}>
<div
className="pipe"
style={{
height: pipe.top,
top: 0,
left: pipe.x,
width: this.pipeWidth
}}
/>
<div
className="pipe"
style={{
height: this.gameHeight - pipe.top - this.pipeGap,
top: pipe.top + this.pipeGap,
left: pipe.x,
width: this.pipeWidth
}}
/>
</React.Fragment>
))}
<div className="score">{score}</div>
{gameOver && (
<div className="game-over">
<div>Game Over</div>
<div>Click or Press Space to Restart</div>
</div>
)}
</div>
);
}
}
ReactDOM.render(<FlappyBird />, document.getElementById('root'));
</script>
</body>
</html>
``````html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React Flappy Bird</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #333;
font-family: 'Arial', sans-serif;
}
</style>
</head>
<body>
<div id="root"></div>
<!-- React and Babel Libraries -->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- React Application -->
<script type="text/babel">
const { useState, useEffect, useCallback, useRef } = React;
// --- Game Constants ---
const GAME_WIDTH = 500;
const GAME_HEIGHT = 600;
const BIRD_WIDTH = 50;
const BIRD_HEIGHT = 40;
const BIRD_LEFT = GAME_WIDTH / 4;
const GRAVITY = 0.6;
const JUMP_STRENGTH = -11;
const PIPE_WIDTH = 80;
const PIPE_GAP = 200;
const PIPE_SPEED = 4;
const PIPE_INTERVAL = 1500; // time in ms between pipe spawns
// --- Reusable Styled Components ---
const GameBox = ({ children }) => (
<div style={{
position: 'relative',
width: `${GAME_WIDTH}px`,
height: `${GAME_HEIGHT}px`,
backgroundColor: '#87CEEB', // Sky blue
overflow: 'hidden',
border: '2px solid black',
}}>
{children}
</div>
);
const Bird = ({ birdY }) => (
<div style={{
position: 'absolute',
backgroundColor: 'yellow',
width: `${BIRD_WIDTH}px`,
height: `${BIRD_HEIGHT}px`,
borderRadius: '50%',
border: '3px solid black',
top: `${birdY}px`,
left: `${BIRD_LEFT}px`,
}} />
);
const Pipe = ({ x, topHeight }) => (
<>
{/* Top Pipe */}
<div style={{
position: 'absolute',
backgroundColor: '#008000', // Green
border: '3px solid black',
width: `${PIPE_WIDTH}px`,
height: `${topHeight}px`,
left: `${x}px`,
top: 0,
}} />
{/* Bottom Pipe */}
<div style={{
position: 'absolute',
backgroundColor: '#008000', // Green
border: '3px solid black',
width: `${PIPE_WIDTH}px`,
height: `${GAME_HEIGHT - topHeight - PIPE_GAP}px`,
left: `${x}px`,
bottom: 0,
}} />
</>
);
const ScoreDisplay = ({ score }) => (
<div style={{
position: 'absolute',
top: '20px',
left: '50%',
transform: 'translateX(-50%)',
color: 'white',
fontSize: '48px',
fontWeight: 'bold',
textShadow: '2px 2px 4px black',
zIndex: 10,
}}>
{score}
</div>
);
const Overlay = ({ gameState, score, onRestart }) => {
if (gameState === 'playing') return null;
const message = gameState === 'menu' ? 'Click to Start' : `Game Over! Score: ${score}`;
const buttonText = gameState === 'menu' ? 'Start' : 'Restart';
return (
<div style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
zIndex: 20
}}>
<h1 style={{ fontSize: '4rem', margin: 0, textShadow: '3px 3px 6px black' }}>{message}</h1>
{gameState === 'over' && (
<button onClick={onRestart} style={{
marginTop: '20px',
padding: '15px 30px',
fontSize: '1.5rem',
cursor: 'pointer',
borderRadius: '10px',
border: 'none',
backgroundColor: '#4CAF50',
color: 'white'
}}>
{buttonText}
</button>
)}
</div>
);
};
// --- Main Game Component ---
function FlappyBirdGame() {
const [gameState, setGameState] = useState('menu'); // 'menu', 'playing', 'over'
const [birdY, setBirdY] = useState(GAME_HEIGHT / 2 - BIRD_HEIGHT / 2);
const [birdVelocity, setBirdVelocity] = useState(0);
const [pipes, setPipes] = useState([]);
const [score, setScore] = useState(0);
const gameLoopRef = useRef();
const pipeTimerRef = useRef();
const resetGame = useCallback(() => {
setGameState('playing');
setBirdY(GAME_HEIGHT / 2 - BIRD_HEIGHT / 2);
setBirdVelocity(0);
setPipes([]);
setScore(0);
}, []);
// Game Loop
useEffect(() => {
if (gameState !== 'playing') {
return;
}
const gameLoop = () => {
// Gravity
const newVelocity = birdVelocity + GRAVITY;
const newBirdY = birdY + newVelocity;
setBirdVelocity(newVelocity);
setBirdY(newBirdY);
// Move Pipes
let scoreHasBeenUpdated = false;
setPipes(currentPipes => {
let newScore = score;
const newPipes = currentPipes.map(pipe => {
// Check for scoring
if (!pipe.passed && pipe.x + PIPE_WIDTH < BIRD_LEFT) {
newScore++;
scoreHasBeenUpdated = true;
return { ...pipe, x: pipe.x - PIPE_SPEED, passed: true };
}
return { ...pipe, x: pipe.x - PIPE_SPEED };
}).filter(pipe => pipe.x > -PIPE_WIDTH); // Remove off-screen pipes
if (scoreHasBeenUpdated) {
setScore(newScore);
}
return newPipes;
});
// Collision Detection
// 1. Ground/Ceiling collision
if (newBirdY > GAME_HEIGHT - BIRD_HEIGHT || newBirdY < 0) {
setGameState('over');
}
// 2. Pipe collision
const currentPipe = pipes.find(pipe =>
pipe.x < BIRD_LEFT + BIRD_WIDTH && pipe.x + PIPE_WIDTH > BIRD_LEFT
);
if (currentPipe) {
const hasCollidedWithTop = newBirdY < currentPipe.topHeight;
const hasCollidedWithBottom = newBirdY + BIRD_HEIGHT > currentPipe.topHeight + PIPE_GAP;
if (hasCollidedWithTop || hasCollidedWithBottom) {
setGameState('over');
}
}
gameLoopRef.current = requestAnimationFrame(gameLoop);
};
gameLoopRef.current = requestAnimationFrame(gameLoop);
return () => cancelAnimationFrame(gameLoopRef.current);
}, [gameState, birdY, birdVelocity, pipes, score]);
// Pipe Spawner
useEffect(() => {
if (gameState !== 'playing') {
return;
}
pipeTimerRef.current = setInterval(() => {
const topHeight = Math.floor(Math.random() * (GAME_HEIGHT - PIPE_GAP - 100)) + 50; // Random height, with 50px padding from top/bottom
setPipes(currentPipes => [...currentPipes, { x: GAME_WIDTH, topHeight, passed: false }]);
}, PIPE_INTERVAL);
return () => clearInterval(pipeTimerRef.current);
}, [gameState]);
const handleInteraction = () => {
if (gameState === 'menu') {
resetGame();
}
if (gameState === 'playing') {
setBirdVelocity(JUMP_STRENGTH);
}
};
return (
<div onClick={handleInteraction} tabIndex="0" style={{ outline: 'none' }}>
<GameBox>
{pipes.map((pipe, index) => (
<Pipe key={index} x={pipe.x} topHeight={pipe.topHeight} />
))}
<Bird birdY={birdY} />
<ScoreDisplay score={score} />
<Overlay gameState={gameState} score={score} onRestart={resetGame} />
</GameBox>
</div>
);
}
ReactDOM.render(<FlappyBirdGame />, document.getElementById('root'));
</script>
</body>
</html>
``````html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Flappy Bird Clone</title>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
#root {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #70c5ce;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script>
const { useState, useEffect, useRef, useReducer } = React;
const initialState = {
birdY: 150,
velocity: 0,
pipes: [],
score: 0,
gameOver: false,
running: false,
pipeGap: 120,
pipeWidth: 60,
pipeFrequency: 1500,
lastPipeTime: 0,
gravity: 0.1
};
const gravityForce = 0.1;
function gameReducer(state, action) {
switch(action.type) {
case 'START_GAME':
return {
...initialState,
running: true,
lastPipeTime: performance.now(),
};
case 'FLAP':
return {
...state,
velocity: -3.5
};
case 'UPDATE':
const now = performance.now();
const deltaTime = now - state.lastFrameTime;
const updatedLastFrameTime = now;
const velocity = state.velocity + state.gravity * deltaTime;
const birdY = state.birdY + velocity * deltaTime;
let pipes = state.pipes.map(pipe => ({
...pipe,
x: pipe.x - 1.5 * deltaTime / 10
}));
const passedPipes = pipes.filter(pipe => pipe.x + state.pipeWidth <= 50);
const newPasses = passedPipes.length > state.score ? passedPipes.length : state.score;
const score = newPasses;
// Remove pipes off-screen
pipes = pipes.filter(pipe => pipe.x + state.pipeWidth > 0);
const pipeGap = state.pipeGap;
const addPipe = now - state.lastPipeTime >= state.pipeFrequency;
if (addPipe) {
pipes.push({
x: window.innerWidth || 800,
gap: (Math.random() * 150) + 50
});
}
// Collision detection
let collision = false;
for (const pipe of pipes) {
const birdLeft = 50;
const birdRight = birdLeft + 20;
const birdTop = birdY;
const birdBottom = birdTop + 20;
const pipeLeft = pipe.x;
const pipeRight = pipe.x + state.pipeWidth;
const pipeTopBottom = pipe.gap;
const pipeBottomTop = pipe.gap + pipeGap;
// Check if bird is in horizontally aligned to pipe
if (birdRight > pipeLeft && birdLeft < pipeRight) {
if (birdTop <= pipeTopBottom || birdBottom >= pipeBottomTop) {
collision = true;
break;
}
}
}
// Check if bird is above top or below ground
if (birdY <= 0 || birdY + 20 >= 400) {
collision = true;
}
return {
...state,
birdY,
velocity,
pipes,
score,
gameOver: collision,
running: !collision,
lastPipeTime: addPipe ? now : state.lastPipeTime,
lastFrameTime: updatedLastFrameTime
};
default:
return state;
}
}
function FlappyBird() {
const [state, dispatch] = useReducer(gameReducer, initialState);
const canvasWidth = 800;
const canvasHeight = 400;
const animationId = useRef(null);
const startGame = () => {
dispatch({ type: 'START_GAME' });
dispatch({ type: 'UPDATE' });
animationId.current = requestAnimationFrame(gameLoop);
};
const flap = () => {
if (!state.running) {
startGame();
} else {
dispatch({ type: 'FLAP' });
}
};
function gameLoop() {
dispatch({ type: 'UPDATE' });
if (state.running) {
animationId.current = requestAnimationFrame(gameLoop);
}
}
useEffect(() => {
const handleKeyDown = (e) => {
if (e.code === "Space") {
e.preventDefault();
flap();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
cancelAnimationFrame(animationId.current);
};
}, [state.running]);
return (
<div style={{
position: 'relative',
width: `${canvasWidth}px`,
height: `${canvasHeight}px`,
margin: 'auto',
backgroundColor: '#70c5ce',
overflow: 'hidden',
borderRadius: '8px'
}}>
{/* Bird */}
{!state.gameOver && (
<div
onClick={flap}
style={{
position: 'absolute',
left: '50px',
top: `${state.birdY}px`,
width: '20px',
height: '20px',
backgroundColor: '#ff0',
borderRadius: '50%',
cursor: 'pointer'
}}
/>
)}
{/* Pipes */}
{state.pipes.map((pipe, idx) => (
<React.Fragment key={idx}>
{/* Top Pipe */}
<div
style={{
position: 'absolute',
left: `${pipe.x}px`,
top: '0px',
width: `${state.pipeWidth}px`,
height: `${pipe.gap}px`,
backgroundColor: '#228b22'
}}
/>
{/* Bottom Pipe */}
<div
style={{
position: 'absolute',
left: `${pipe.x}px`,
top: `${pipe.gap + state.pipeGap}px`,
width: `${state.pipeWidth}px`,
height: '500px',
backgroundColor: '#228b22'
}}
/>
</React.Fragment>
))}
{/* Score */}
<div style={{
position: 'absolute',
top: '10px',
left: '10px',
fontSize: '24px',
fontWeight: 'bold',
color: 'white',
textShadow: '1px 1px 2px black'
}}>
{state.score}
</div>
{/* Game Over */}
{state.gameOver && !state.running && (
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
fontSize: '36px',
fontWeight: 'bold',
textAlign: 'center',
color: '#fff',
textShadow: '2px 2px 4px #000'
}}>
Game Over!<br/>Score: {state.score}<br/>
Press Space to Restart
</div>
)}
</div>
);
}
ReactDOM.render(<FlappyBird />, document.getElementById('root'));
</script>
</body>
</html>
```# Flappy Bird Clone in React
## Overview
In this example, we'll create a simplified version of the popular game Flappy Bird using React. The game will feature a bird that can be controlled with a single button (spacebar or mouse click), gravity pulling it down, pipes as obstacles, scoring, and collision detection.
## Code
```jsx
import React, { useState, useEffect } from 'react';
// Game constants
const GRAVITY = 0.2;
const BIRD_SIZE = 30;
const PIPE_WIDTH = 80;
const PIPE_GAP = 120;
const INITIAL_BIRD_Y = 200;
const INITIAL_PIPES = [{ x: 400, y: 100 }, { x: 700, y: 250 }];
function App() {
const [birdY, setBirdY] = useState(INITIAL_BIRD_Y);
const [birdVelocity, setBirdVelocity] = useState(0);
const [pipes, setPipes] = useState(INITIAL_PIPES);
const [score, setScore] = useState(0);
const [gameOver, setGameOver] = useState(false);
// Handle user input (spacebar or mouse click)
const handleInput = () => {
if (!gameOver) {
setBirdVelocity(-5);
} else {
resetGame();
}
};
// Reset game state
const resetGame = () => {
setBirdY(INITIAL_BIRD_Y);
setBirdVelocity(0);
setPipes(INITIAL_PIPES);
setScore(0);
setGameOver(false);
};
useEffect(() => {
// Update bird position and velocity
const intervalId = setInterval(() => {
if (!gameOver) {
setBirdVelocity(birdVelocity + GRAVITY);
setBirdY(birdY + birdVelocity);
// Check collision with ground or top
if (birdY < 0 || birdY > window.innerHeight - BIRD_SIZE) {
setGameOver(true);
}
}
}, 16); // 60 FPS
return () => clearInterval(intervalId);
}, [birdY, birdVelocity, gameOver]);
useEffect(() => {
// Update pipes and check collisions
const intervalId = setInterval(() => {
if (!gameOver) {
setPipes((prevPipes) => {
const newPipes = prevPipes.map((pipe) => ({ ...pipe, x: pipe.x - 2 }));
// Check if pipe has passed the bird
if (newPipes[0].x < 50 && newPipes[0].x > 48) {
setScore((prevScore) => prevScore + 1);
}
// Check collision with pipes
if (newPipes.some((pipe) => {
return (pipe.x < 50 + BIRD_SIZE && pipe.x + PIPE_WIDTH > 50) &&
(birdY < pipe.y || birdY + BIRD_SIZE > pipe.y + PIPE_GAP);
})) {
setGameOver(true);
}
// Remove off-screen pipes and add new ones
if (newPipes[0].x < -PIPE_WIDTH) {
newPipes.shift();
newPipes.push({ x: newPipes[newPipes.length - 1].x + 300, y: Math.random() * (window.innerHeight - PIPE_GAP) });
}
return newPipes;
});
}
}, 16); // 60 FPS
return () => clearInterval(intervalId);
}, [pipes, birdY, gameOver]);
// Handle user input events
useEffect(() => {
document.addEventListener('keydown', (e) => {
if (e.key === ' ') {
handleInput();
}
});
document.addEventListener('click', handleInput);
return () => {
document.removeEventListener('keydown', handleInput);
document.removeEventListener('click', handleInput);
};
}, [handleInput]);
return (
<div style={{
position: 'relative',
width: '100%',
height: '100vh',
backgroundColor: '#87CEEB',
overflow: 'hidden',
}}>
<div style={{
position: 'absolute',
top: birdY,
left: 50,
width: BIRD_SIZE,
height: BIRD_SIZE,
backgroundColor: 'yellow',
borderRadius: '50%',
}} />
{pipes.map((pipe, index) => (
<div key={index} style={{
position: 'absolute',
top: 0,
left: pipe.x,
width: PIPE_WIDTH,
height: pipe.y,
backgroundColor: 'green',
}} />
))}
{pipes.map((pipe, index) => (
<div key={index + pipes.length} style={{
position: 'absolute',
top: pipe.y + PIPE_GAP,
left: pipe.x,
width: PIPE_WIDTH,
height: window.innerHeight,
backgroundColor: 'green',
}} />
))}
<div style={{
position: 'absolute',
top: 20,
left: 20,
fontSize: 24,
fontWeight: 'bold',
}}>
Score: {score}
</div>
{gameOver && (
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
fontSize: 36,
fontWeight: 'bold',
}}>
Game Over! Click or press Space to restart.
</div>
)}
</div>
);
}
export default App;
```
## How to Run
1. Create a new React project using `npx create-react-app flappy-bird-clone`.
2. Replace the contents of `src/App.js` with the above code.
3. Start the development server with `npm start`.
4. Open your web browser and navigate to `http://localhost:3000`.
## Controls
- **Spacebar** or **Mouse Click**: Flap the bird's wings to go up. If the game is over, restart the game.
- The game automatically updates the score as you pass through the pipes.
- The game ends when the bird collides with the ground, top, or pipes.# Flappy Bird React Clone
I'll create a complete Flappy Bird clone using React with all the requested features, including physics, collision detection, pipes, and scoring.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flappy Bird Clone</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.24.8/babel.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
font-family: 'Arial Rounded MT Bold', 'Helvetica Rounded', Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(to bottom, #1e5799, #207cca);
overflow: hidden;
touch-action: manipulation;
}
#root {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.game-container {
position: relative;
width: 360px;
height: 640px;
overflow: hidden;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
border-radius: 10px;
}
.game-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, #70c9e3 0%, #70c9e3 65%, #c9f1f9 65%, #c9f1f9 100%);
overflow: hidden;
}
.scene {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.sky {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 75%;
background: linear-gradient(to bottom, #70c9e3, #a2d9f0);
}
.cloud {
position: absolute;
background-color: white;
border-radius: 50px;
}
.cloud:nth-child(1) { top: 20%; left: 30%; width: 60px; height: 20px; }
.cloud:nth-child(2) { top: 40%; left: 60%; width: 80px; height: 25px; }
.cloud:nth-child(3) { top: 15%; left: 70%; width: 70px; height: 22px; }
.cloud:nth-child(4) { top: 35%; left: 20%; width: 90px; height: 30px; }
.ground {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 25%;
background: linear-gradient(to top, #e3b86c, #d9a44a);
border-top: 10px solid #a37c3e;
}
.grass {
position: absolute;
top: -25px;
width: 100%;
height: 30px;
background: linear-gradient(to bottom, #71c837, #62a328);
border-radius: 30px 30px 0 0;
}
.pipe {
position: absolute;
width: 70px;
display: flex;
flex-direction: column;
align-items: center;
}
.pipe-top {
height: 40px;
width: 80px;
background: linear-gradient(to right, #74c042, #5ca032);
border: 4px solid #3a8020;
border-bottom: none;
border-radius: 10px 10px 0 0;
z-index: 1;
}
.pipe-body {
height: 300px;
width: 70px;
background: linear-gradient(to right, #5dbd2b, #4da029);
border: 4px solid #3a8020;
display: flex;
justify-content: center;
}
.pipe.upper .pipe-body {
flex-direction: column-reverse;
}
.pipe.lower .pipe-body {
flex-direction: column;
border-top: none;
}
.bird {
position: absolute;
width: 40px;
height: 30px;
z-index: 10;
transition: transform 0.1s;
}
.bird-body {
position: absolute;
width: 40px;
height: 30px;
background: linear-gradient(45deg, #ffd900, #ffac00);
border-radius: 50% 50% 40% 40%;
top: 0;
left: 0;
}
.bird-eye {
position: absolute;
width: 10px;
height: 10px;
background-color: white;
border-radius: 50%;
top: 8px;
right: 8px;
display: flex;
justify-content: center;
align-items: center;
}
.bird-pupil {
width: 5px;
height: 5px;
background-color: black;
border-radius: 50%;
}
.bird-wing {
position: absolute;
width: 30px;
height: 15px;
background: linear-gradient(to bottom, #ffd900, #e09800);
border-radius: 50%;
top: 15px;
left: -5px;
transform-origin: 70% 50%;
}
.bird-beak {
position: absolute;
width: 15px;
height: 10px;
background: linear-gradient(to right, #ff6600, #cc5500);
border-radius: 50% 0 0 50%;
top: 12px;
right: -10px;
transform-origin: left center;
}
.score-display {
position: absolute;
top: 50px;
left: 0;
width: 100%;
text-align: center;
font-size: 60px;
font-weight: bold;
color: white;
text-shadow: 3px 3px 5px rgba(0, 0, 0, 0.5);
z-index: 5;
}
.instructions {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 300px;
background-color: rgba(255, 255, 255, 0.85);
padding: 20px;
border-radius: 15px;
text-align: center;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
z-index: 20;
}
.instructions h2 {
color: #ff5722;
margin-bottom: 20px;
font-size: 24px;
}
.controls {
display: flex;
justify-content: space-around;
margin: 20px 0;
}
.control-item {
display: flex;
flex-direction: column;
align-items: center;
}
.control-item span {
font-size: 18px;
font-weight: bold;
margin-top: 5px;
color: #333;
}
.key {
width: 60px;
height: 60px;
background: linear-gradient(to bottom, #fff, #ccc);
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
color: #333;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
}
.start-button {
margin-top: 20px;
padding: 12px 30px;
font-size: 18px;
font-weight: bold;
background: linear-gradient(to bottom, #ff9800, #f57c00);
border: none;
border-radius: 30px;
color: white;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.start-button:hover {
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
}
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.95);
padding: 30px;
border-radius: 20px;
text-align: center;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.4);
z-index: 20;
animation: pop-in 0.3s ease-out;
}
@keyframes pop-in {
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.game-over h2 {
font-size: 32px;
color: #f44336;
margin-bottom: 20px;
}
.game-over p {
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.game-over .final-score {
font-size: 36px;
font-weight: bold;
color: #ff9800;
}
.restart-button {
padding: 12px 30px;
font-size: 18px;
font-weight: bold;
background: linear-gradient(to bottom, #4caf50, #2e7d32);
border: none;
border-radius: 30px;
color: white;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.restart-button:hover {
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
}
.wing-flap {
animation: wing-flap 0.3s infinite;
}
@keyframes wing-flap {
0% { transform: rotate(-25deg); }
50% { transform: rotate(20deg); }
100% { transform: rotate(-25deg); }
}
.ground-texture {
position: absolute;
bottom: 5px;
left: 0;
width: 100%;
height: 20px;
background-image: repeating-linear-gradient(
to right,
transparent,
transparent 20px,
rgba(0, 0, 0, 0.1) 20px,
rgba(0, 0, 0, 0.1) 40px
);
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useRef } = React;
// Game constants
const GRAVITY = 0.6;
const JUMP_FORCE = -10;
const PIPE_SPEED = 3;
const PIPE_SPAWN_INTERVAL = 1800;
const PIPE_GAP = 180;
const BIRD_SIZE = 30;
const GROUND_HEIGHT = 160;
// Bird component
const Bird = ({ position }) => {
return (
<div
className="bird"
style={{
top: position.y + 'px',
left: position.x + 'px',
transform: `rotate(${position.rotation}deg)`
}}
>
<div className="bird-body">
<div className="bird-eye">
<div className="bird-pupil"></div>
</div>
<div className="bird-wing wing-flap"></div>
<div className="bird-beak"></div>
</div>
</div>
);
};
// Pipe component
const Pipe = ({ position, height, type }) => {
return (
<div
className={`pipe ${type}`}
style={{ right: position + 'px' }}
>
<div className="pipe-top"></div>
<div className="pipe-body" style={{ height: height + 'px' }}>
<div style={{ flex: 1, backgroundColor: '#4da029', width: '90%' }}></div>
</div>
</div>
);
};
// Game component
const FlappyBird = () => {
// Game state
const [gameState, setGameState] = useState('start'); // 'start', 'playing', 'gameOver'
const [birdPosition, setBirdPosition] = useState({ x: 100, y: 300, rotation: 0 });
const [birdVelocity, setBirdVelocity] = useState(0);
const [pipes, setPipes] = useState([]);
const [score, setScore] = useState(0);
const [highScore, setHighScore] = useState(0);
// Game area dimensions
const gameHeight = 640;
const gameWidth = 360;
const gameAreaRef = useRef(null);
// Game loop reference
const gameLoopRef = useRef(null);
// Handle jump
const jump = () => {
if (gameState === 'start') {
startGame();
return;
}
if (gameState === 'playing') {
setBirdVelocity(JUMP_FORCE);
} else if (gameState === 'gameOver') {
resetGame();
}
};
// Start game
const startGame = () => {
setGameState('playing');
setBirdPosition({ x: 100, y: 300, rotation: 0 });
setBirdVelocity(0);
setPipes([]);
setScore(0);
};
// Reset game
const resetGame = () => {
// Update high score if needed
if (score > highScore) {
setHighScore(score);
}
startGame();
};
// Game over
const gameOver = () => {
setGameState('gameOver');
cancelAnimationFrame(gameLoopRef.current);
};
// Handle user input
useEffect(() => {
const handleKeyDown = (e) => {
if (e.code === 'Space') {
jump();
}
};
const handleTouchStart = () => {
jump();
};
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('touchstart', handleTouchStart);
// Also listen for clicks on the game area to support mobile
const gameArea = gameAreaRef.current;
gameArea.addEventListener('click', jump);
return () => {
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('touchstart', handleTouchStart);
gameArea.removeEventListener('click', jump);
};
}, [gameState]);
// Game loop
useEffect(() => {
if (gameState !== 'playing') return;
let lastTime = 0;
let pipeTimer = 0;
const update = (timestamp) => {
const delta = timestamp - lastTime;
lastTime = timestamp;
// Update bird position and velocity based on gravity
setBirdPosition(prev => {
const newVelocity = prev.y + (birdVelocity + GRAVITY) * (delta / 16);
const rotation = Math.min(90, Math.max(-30, birdVelocity * 3));
return { ...prev, y: newVelocity, rotation };
});
setBirdVelocity(prev => prev + GRAVITY);
// Update pipes
pipeTimer += delta;
if (pipeTimer >= PIPE_SPAWN_INTERVAL) {
pipeTimer = 0;
// Generate random pipe gap position
const gapPosition = Math.floor(Math.random() * (gameHeight - GROUND_HEIGHT - PIPE_GAP - 100)) + 50;
setPipes(prev => [
...prev,
{
id: Date.now(),
x: gameWidth,
topHeight: gapPosition,
bottomHeight: gameHeight - GROUND_HEIGHT - gapPosition - PIPE_GAP,
passed: false
}
]);
}
setPipes(prev =>
prev
.map(pipe => {
// Move pipe
const newX = pipe.x - PIPE_SPEED;
// Check if bird passed the pipe
let passed = pipe.passed;
if (!pipe.passed && newX + 70 < birdPosition.x) {
setScore(prevScore => prevScore + 1);
passed = true;
}
return { ...pipe, x: newX, passed };
})
.filter(pipe => pipe.x > -100) // Remove pipes that are off-screen
);
// Request next frame
gameLoopRef.current = requestAnimationFrame(update);
};
gameLoopRef.current = requestAnimationFrame(update);
return () => {
if (gameLoopRef.current) {
cancelAnimationFrame(gameLoopRef.current);
}
};
}, [gameState, birdVelocity]);
// Collision detection
useEffect(() => {
if (gameState !== 'playing') return;
// Ground collision
if (birdPosition.y + BIRD_SIZE > gameHeight - GROUND_HEIGHT) {
gameOver();
return;
}
// Ceiling collision
if (birdPosition.y < 0) {
setBirdPosition(prev => ({ ...prev, y: 0 }));
setBirdVelocity(0);
}
// Pipe collision
const birdRect = {
left: birdPosition.x,
right: birdPosition.x + BIRD_SIZE,
top: birdPosition.y,
bottom: birdPosition.y + BIRD_SIZE
};
for (const pipe of pipes) {
const topPipeRect = {
left: gameWidth - pipe.x,
right: gameWidth - pipe.x + 70,
top: 0,
bottom: pipe.topHeight
};
const bottomPipeRect = {
left: gameWidth - pipe.x,
right: gameWidth - pipe.x + 70,
top: gameHeight - GROUND_HEIGHT - pipe.bottomHeight,
bottom: gameHeight - GROUND_HEIGHT
};
// Check collision with top pipe
if (
birdRect.right > topPipeRect.left &&
birdRect.left < topPipeRect.right &&
birdRect.bottom > topPipeRect.top &&
birdRect.top < topPipeRect.bottom
) {
gameOver();
break;
}
// Check collision with bottom pipe
if (
birdRect.right > bottomPipeRect.left &&
birdRect.left < bottomPipeRect.right &&
birdRect.bottom > bottomPipeRect.top &&
birdRect.top < bottomPipeRect.bottom
) {
gameOver();
break;
}
}
}, [birdPosition, pipes, gameState]);
return (
<div className="game-container" ref={gameAreaRef}>
<div className="game-background">
<div className="scene">
{/* Sky with clouds */}
<div className="sky">
<div className="cloud"></div>
<div className="cloud"></div>
<div className="cloud"></div>
<div className="cloud"></div>
</div>
{/* Pipes */}
{pipes.map(pipe => (
<React.Fragment key={pipe.id}>
<Pipe
position={pipe.x}
height={pipe.topHeight}
type="upper"
/>
<Pipe
position={pipe.x}
height={pipe.bottomHeight}
type="lower"
/>
</React.Fragment>
))}
{/* Bird */}
<Bird position={birdPosition} />
{/* Ground */}
<div className="ground">
<div className="grass"></div>
<div className="ground-texture"></div>
</div>
{/* Score */}
{gameState !== 'start' && (
<div className="score-display">
{score}
</div>
)}
{/* Start screen */}
{gameState === 'start' && (
<div className="instructions">
<h2>FLAPPY BIRD</h2>
<p>Score points without hitting pipes!</p>
<div className="controls">
<div className="control-item">
<div className="key">SPACE</div>
<span>Jump</span>
</div>
<div className="control-item">
<div className="key">Click</div>
<span>or Tap</span>
</div>
</div>
<button className="start-button" onClick={startGame}>
START GAME
</button>
{highScore > 0 && (
<p style={{ marginTop: '20px' }}>
High Score: <span className="final-score">{highScore}</span>
</p>
)}
</div>
)}
{/* Game over screen */}
{gameState === 'gameOver' && (
<div className="game-over">
<h2>GAME OVER</h2>
<p>Your score:</p>
<p className="final-score">{score}</p>
{score > highScore && <p>New high score!</p>}
{score <= highScore && highScore > 0 && (
<p>High score: {highScore}</p>
)}
<button className="restart-button" onClick={resetGame}>
PLAY AGAIN
</button>
</div>
)}
</div>
</div>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(<FlappyBird />);
</script>
</body>
</html>
```
This Flappy Bird clone includes:
1. **Physics System**: Implements gravity for the bird with proper jumping mechanics
2. **Bird Character**: Fully rendered yellow bird with wing animation
3. **Pipes**: Randomly generated pillars with varying gaps to navigate through
4. **Collision Detection**: Detects collisions with pipes, ceiling, and ground
5. **Scoring System**: Increases score when passing between pipes
6. **Game States**: Start screen, gameplay, and game over screens with score tracking
7. **High Score**: Tracks the highest score across gameplay sessions
8. **Controls**: Space bar, mouse click, or touch to make the bird jump
9. **Responsive Design**: Works on both desktop and mobile devices
10. **Visual Polish**: Detailed visuals with clouds, ground details, pipe designs, and animations
To play the game:
1. Click "START GAME" on the welcome screen
2. Press SPACE, tap, or click to make the bird jump
3. Navigate through the pipes without hitting them
4. When you hit a pipe or the ground, the game ends
5. Click PLAY AGAIN to restart
Enjoy the classic Flappy Bird gameplay!```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flappy Bird Clone</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
background: linear-gradient(to bottom, #87CEEB 0%, #98FB98 100%);
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.game-container {
position: relative;
width: 400px;
height: 600px;
background: linear-gradient(to bottom, #87CEEB 0%, #98FB98 100%);
border: 2px solid #333;
overflow: hidden;
cursor: pointer;
}
.bird {
position: absolute;
width: 30px;
height: 30px;
background: #FFD700;
border-radius: 50%;
border: 2px solid #FFA500;
transition: transform 0.1s;
}
.pipe {
position: absolute;
width: 50px;
background: #228B22;
border: 2px solid #006400;
}
.score {
position: absolute;
top: 20px;
left: 20px;
font-size: 24px;
font-weight: bold;
color: white;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
z-index: 10;
}
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 30px;
border-radius: 10px;
text-align: center;
z-index: 20;
}
.start-screen {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 30px;
border-radius: 10px;
text-align: center;
z-index: 20;
}
.button {
background: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
margin: 10px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
.button:hover {
background: #45a049;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useRef, useCallback } = React;
const GAME_CONFIG = {
gravity: 0.6,
jumpStrength: -12,
pipeWidth: 50,
pipeGap: 120,
pipeSpeed: 2,
birdSize: 30,
gameWidth: 400,
gameHeight: 600
};
function FlappyBird() {
const [bird, setBird] = useState({ x: 50, y: 200, velocity: 0 });
const [pipes, setPipes] = useState([]);
const [score, setScore] = useState(0);
const [gameState, setGameState] = useState('start'); // 'start', 'playing', 'gameOver'
const [gameSpeed, setGameSpeed] = useState(1);
const gameRef = useRef(null);
const animationRef = useRef(null);
const lastPipeRef = useRef(0);
const createPipe = useCallback((x) => {
const gapStart = Math.random() * (GAME_CONFIG.gameHeight - GAME_CONFIG.pipeGap - 100) + 50;
return {
id: Date.now(),
x: x,
topHeight: gapStart,
bottomHeight: GAME_CONFIG.gameHeight - gapStart - GAME_CONFIG.pipeGap,
passed: false
};
}, []);
const checkCollision = useCallback((birdPos, pipeList) => {
// Check ground and ceiling collision
if (birdPos.y <= 0 || birdPos.y >= GAME_CONFIG.gameHeight - GAME_CONFIG.birdSize) {
return true;
}
// Check pipe collision
for (let pipe of pipeList) {
const birdLeft = birdPos.x;
const birdRight = birdPos.x + GAME_CONFIG.birdSize;
const birdTop = birdPos.y;
const birdBottom = birdPos.y + GAME_CONFIG.birdSize;
const pipeLeft = pipe.x;
const pipeRight = pipe.x + GAME_CONFIG.pipeWidth;
// Check if bird is horizontally aligned with pipe
if (birdRight > pipeLeft && birdLeft < pipeRight) {
// Check collision with top pipe
if (birdTop < pipe.topHeight) {
return true;
}
// Check collision with bottom pipe
if (birdBottom > GAME_CONFIG.gameHeight - pipe.bottomHeight) {
return true;
}
}
}
return false;
}, []);
const jump = useCallback(() => {
if (gameState === 'playing') {
setBird(prev => ({
...prev,
velocity: GAME_CONFIG.jumpStrength
}));
}
}, [gameState]);
const startGame = useCallback(() => {
setBird({ x: 50, y: 200, velocity: 0 });
setPipes([]);
setScore(0);
setGameState('playing');
setGameSpeed(1);
lastPipeRef.current = 0;
}, []);
const gameLoop = useCallback(() => {
if (gameState !== 'playing') return;
setBird(prevBird => {
const newBird = {
...prevBird,
velocity: prevBird.velocity + GAME_CONFIG.gravity,
y: prevBird.y + prevBird.velocity + GAME_CONFIG.gravity
};
return newBird;
});
setPipes(prevPipes => {
let newPipes = prevPipes.map(pipe => ({
...pipe,
x: pipe.x - GAME_CONFIG.pipeSpeed * gameSpeed
})).filter(pipe => pipe.x > -GAME_CONFIG.pipeWidth);
// Add new pipes
if (newPipes.length === 0 || newPipes[newPipes.length - 1].x < GAME_CONFIG.gameWidth - 200) {
newPipes.push(createPipe(GAME_CONFIG.gameWidth));
}
return newPipes;
});
// Check for scoring
setPipes(prevPipes => {
const newPipes = [...prevPipes];
newPipes.forEach(pipe => {
if (!pipe.passed && pipe.x + GAME_CONFIG.pipeWidth < bird.x) {
pipe.passed = true;
setScore(prev => prev + 1);
// Increase game speed slightly every 5 points
if ((score + 1) % 5 === 0) {
setGameSpeed(prev => Math.min(prev + 0.2, 3));
}
}
});
return newPipes;
});
// Check collisions
if (checkCollision(bird, pipes)) {
setGameState('gameOver');
}
}, [gameState, bird, pipes, score, gameSpeed, checkCollision, createPipe]);
// Game loop effect
useEffect(() => {
if (gameState === 'playing') {
animationRef.current = requestAnimationFrame(gameLoop);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}
}, [gameLoop, gameState]);
// Handle click and keyboard events
useEffect(() => {
const handleInteraction = (e) => {
if (gameState === 'start') {
startGame();
} else if (gameState === 'playing') {
jump();
} else if (gameState === 'gameOver') {
startGame();
}
};
const handleKeyPress = (e) => {
if (e.code === 'Space' || e.key === ' ') {
e.preventDefault();
handleInteraction();
}
};
window.addEventListener('keydown', handleKeyPress);
return () => {
window.removeEventListener('keydown', handleKeyPress);
};
}, [gameState, jump, startGame]);
return (
<div
className="game-container"
ref={gameRef}
onClick={() => {
if (gameState === 'start') {
startGame();
} else if (gameState === 'playing') {
jump();
} else if (gameState === 'gameOver') {
startGame();
}
}}
>
<div className="score">Score: {score}</div>
{/* Bird */}
<div
className="bird"
style={{
left: bird.x,
top: bird.y,
transform: `rotate(${Math.min(Math.max(bird.velocity * 3, -30), 30)}deg)`
}}
/>
{/* Pipes */}
{pipes.map(pipe => (
<React.Fragment key={pipe.id}>
{/* Top pipe */}
<div
className="pipe"
style={{
left: pipe.x,
top: 0,
height: pipe.topHeight
}}
/>
{/* Bottom pipe */}
<div
className="pipe"
style={{
left: pipe.x,
bottom: 0,
height: pipe.bottomHeight
}}
/>
</React.Fragment>
))}
{/* Start Screen */}
{gameState === 'start' && (
<div className="start-screen">
<h1>Flappy Bird</h1>
<p>Click or press SPACE to start</p>
<p>Click or press SPACE to jump</p>
<button className="button" onClick={startGame}>
Start Game
</button>
</div>
)}
{/* Game Over Screen */}
{gameState === 'gameOver' && (
<div className="game-over">
<h2>Game Over!</h2>
<p>Final Score: {score}</p>
<p>Click or press SPACE to restart</p>
<button className="button" onClick={startGame}>
Play Again
</button>
</div>
)}
</div>
);
}
ReactDOM.render(<FlappyBird />, document.getElementById('root'));
</script>
</body>
</html>
```
This Flappy Bird clone includes all the requested features:
**Core Features:**
- **Gravity**: The bird continuously falls due to gravity
- **Jumping**: Click or press SPACE to make the bird jump
- **Pipes**: Green obstacles with gaps that move from right to left
- **Scoring**: Points awarded for passing through pipes
- **Collision Detection**: Game ends when hitting pipes, ground, or ceiling
**Additional Features:**
- **Progressive Difficulty**: Game speed increases every 5 points
- **Visual Effects**: Bird rotates based on velocity
- **Responsive Controls**: Works with both mouse clicks and keyboard
- **Game States**: Start screen, playing, and game over screens
- **Score Display**: Real-time score tracking
**Game Controls:**
- Click anywhere or press SPACE to start the game
- Click or press SPACE to make the bird jump
- Click or press SPACE to restart after game over
The game uses React hooks for state management and includes a smooth game loop with collision detection. The bird physics feel authentic with proper gravity and jump mechanics that mirror the original Flappy Bird experience.