Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 210 additions & 0 deletions game.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
class FlappyBirdGame {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.scoreElement = document.getElementById('score');
this.gameStateElement = document.getElementById('gameState');

this.GAME_STATES = {
READY: 'READY',
PLAYING: 'PLAYING',
GAMEOVER: 'GAMEOVER'
};

this.gameState = this.GAME_STATES.READY;
this.score = 0;
this.lastTime = 0;
this.frameCount = 0;
this.fpsCounter = 0;
this.fpsLastTime = 0;

this.init();
}

init() {
this.setupEventListeners();
this.updateUI();
this.gameLoop();
}

setupEventListeners() {
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
e.preventDefault();
this.handleSpacePress();
}
});

this.canvas.addEventListener('click', () => {
this.handleSpacePress();
});
}

handleSpacePress() {
switch (this.gameState) {
case this.GAME_STATES.READY:
this.startGame();
break;
case this.GAME_STATES.PLAYING:
break;
case this.GAME_STATES.GAMEOVER:
this.resetGame();
break;
}
}

startGame() {
this.gameState = this.GAME_STATES.PLAYING;
this.updateUI();
}

resetGame() {
this.gameState = this.GAME_STATES.READY;
this.score = 0;
this.updateUI();
}

update(deltaTime) {
if (this.gameState === this.GAME_STATES.PLAYING) {
}
}

render() {
this.clearCanvas();

switch (this.gameState) {
case this.GAME_STATES.READY:
this.renderReadyState();
break;
case this.GAME_STATES.PLAYING:
this.renderPlayingState();
break;
case this.GAME_STATES.GAMEOVER:
this.renderGameOverState();
break;
}

this.renderFPS();
}

clearCanvas() {
this.ctx.fillStyle = '#70C5CE';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

this.drawBackground();
}

drawBackground() {
const gradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height);
gradient.addColorStop(0, '#87CEEB');
gradient.addColorStop(0.7, '#70C5CE');
gradient.addColorStop(1, '#4682B4');

this.ctx.fillStyle = gradient;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

this.drawClouds();
}

drawClouds() {
this.ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';

const time = Date.now() * 0.001;
const clouds = [
{ x: 50 + Math.sin(time * 0.5) * 30, y: 80, size: 30 },
{ x: 200 + Math.sin(time * 0.3) * 20, y: 120, size: 40 },
{ x: 350 + Math.sin(time * 0.4) * 25, y: 60, size: 35 },
{ x: 100 + Math.sin(time * 0.6) * 15, y: 200, size: 25 }
];

clouds.forEach(cloud => {
this.drawCloud(cloud.x, cloud.y, cloud.size);
});
}

drawCloud(x, y, size) {
this.ctx.beginPath();
this.ctx.arc(x, y, size, 0, Math.PI * 2);
this.ctx.arc(x + size * 0.7, y, size * 0.8, 0, Math.PI * 2);
this.ctx.arc(x + size * 1.4, y, size * 0.6, 0, Math.PI * 2);
this.ctx.arc(x + size * 0.35, y - size * 0.5, size * 0.7, 0, Math.PI * 2);
this.ctx.arc(x + size * 1.05, y - size * 0.4, size * 0.5, 0, Math.PI * 2);
this.ctx.fill();
}

renderReadyState() {
this.drawText('Press SPACE to Start', this.canvas.width / 2, this.canvas.height / 2, '24px Arial', '#FFFFFF', 'center');
this.drawText('Click canvas or press SPACE to play', this.canvas.width / 2, this.canvas.height / 2 + 40, '16px Arial', '#FFFFFF', 'center');
}

renderPlayingState() {
this.drawText('Game Running!', this.canvas.width / 2, 50, '20px Arial', '#FFFFFF', 'center');
this.drawText('Press SPACE to flap', this.canvas.width / 2, this.canvas.height - 50, '16px Arial', '#FFFFFF', 'center');
}

renderGameOverState() {
this.drawText('Game Over!', this.canvas.width / 2, this.canvas.height / 2 - 40, '32px Arial', '#FF6B6B', 'center');
this.drawText(`Final Score: ${this.score}`, this.canvas.width / 2, this.canvas.height / 2, '20px Arial', '#FFFFFF', 'center');
this.drawText('Press SPACE to Restart', this.canvas.width / 2, this.canvas.height / 2 + 40, '18px Arial', '#FFFFFF', 'center');
}

renderFPS() {
if (this.fpsCounter > 0) {
this.drawText(`FPS: ${this.fpsCounter}`, 10, 25, '14px Arial', '#FFFFFF', 'left');
}
}

drawText(text, x, y, font, color, align = 'left') {
this.ctx.save();
this.ctx.font = font;
this.ctx.fillStyle = color;
this.ctx.textAlign = align;
this.ctx.strokeStyle = '#000000';
this.ctx.lineWidth = 3;
this.ctx.strokeText(text, x, y);
this.ctx.fillText(text, x, y);
this.ctx.restore();
}

updateUI() {
this.scoreElement.textContent = `Score: ${this.score}`;

switch (this.gameState) {
case this.GAME_STATES.READY:
this.gameStateElement.textContent = 'Press SPACE to start';
break;
case this.GAME_STATES.PLAYING:
this.gameStateElement.textContent = 'Playing - SPACE to flap';
break;
case this.GAME_STATES.GAMEOVER:
this.gameStateElement.textContent = 'Game Over - SPACE to restart';
break;
}
}

calculateFPS(currentTime) {
this.frameCount++;

if (currentTime - this.fpsLastTime >= 1000) {
this.fpsCounter = Math.round((this.frameCount * 1000) / (currentTime - this.fpsLastTime));
this.frameCount = 0;
this.fpsLastTime = currentTime;
}
}

gameLoop(currentTime = 0) {
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;

this.calculateFPS(currentTime);

this.update(deltaTime);
this.render();

requestAnimationFrame((time) => this.gameLoop(time));
}
}

document.addEventListener('DOMContentLoaded', () => {
const game = new FlappyBirdGame();
});
20 changes: 20 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!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>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="game-container">
<h1>Flappy Bird</h1>
<canvas id="gameCanvas" width="480" height="640"></canvas>
<div class="game-info">
<div id="score">Score: 0</div>
<div id="gameState">Press SPACE to start</div>
</div>
</div>
<script src="game.js"></script>
</body>
</html>
91 changes: 91 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #87CEEB 0%, #98FB98 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}

.game-container {
text-align: center;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}

h1 {
color: #333;
font-size: 2.5em;
margin-bottom: 20px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
font-weight: bold;
}

#gameCanvas {
border: 3px solid #333;
border-radius: 10px;
background: #70C5CE;
display: block;
margin: 0 auto 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}

.game-info {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 480px;
margin: 0 auto;
}

#score {
font-size: 1.5em;
font-weight: bold;
color: #333;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}

#gameState {
font-size: 1.2em;
color: #333;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}

@media (max-width: 600px) {
.game-container {
margin: 10px;
padding: 15px;
}

h1 {
font-size: 2em;
margin-bottom: 15px;
}

#gameCanvas {
width: 100%;
max-width: 480px;
height: auto;
}

.game-info {
flex-direction: column;
gap: 10px;
}

#score, #gameState {
font-size: 1.2em;
}
}