|
|
<!DOCTYPE html>
|
|
|
<html lang="en">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>Flappy Bird with Hand Tracking</title>
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script>
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"></script>
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"></script>
|
|
|
<style>
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
font-family: Arial, sans-serif;
|
|
|
background: linear-gradient(135deg, #74ebd5, #acb6e5);
|
|
|
color: white;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
height: 100vh;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.header {
|
|
|
width: 100%;
|
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
|
padding: 10px 0;
|
|
|
text-align: center;
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
z-index: 100;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding: 0 20px;
|
|
|
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3);
|
|
|
}
|
|
|
|
|
|
.header div {
|
|
|
color: white;
|
|
|
font-size: 18px;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.game-container {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
padding-top: 70px;
|
|
|
}
|
|
|
|
|
|
canvas {
|
|
|
border: 2px solid #fff;
|
|
|
border-radius: 10px;
|
|
|
background: #222;
|
|
|
max-width: 90%;
|
|
|
max-height: 70%;
|
|
|
width: auto;
|
|
|
height: auto;
|
|
|
}
|
|
|
|
|
|
video {
|
|
|
display: none;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="header">
|
|
|
<div id="score">Score: 0</div>
|
|
|
<div id="timer">Time: 60</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="game-container">
|
|
|
<canvas id="gameCanvas" width="500" height="500"></canvas>
|
|
|
</div>
|
|
|
|
|
|
<video id="webcam" autoplay playsinline></video>
|
|
|
|
|
|
<script>
|
|
|
const canvas = document.getElementById('gameCanvas');
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
const scoreDisplay = document.getElementById('score');
|
|
|
const timerDisplay = document.getElementById('timer');
|
|
|
|
|
|
let bird = {
|
|
|
x: 50,
|
|
|
y: canvas.height / 2,
|
|
|
radius: 15,
|
|
|
dy: 0,
|
|
|
gravity: 0.6,
|
|
|
lift: -10,
|
|
|
color: 'yellow',
|
|
|
};
|
|
|
|
|
|
let pipes = [];
|
|
|
let score = 0;
|
|
|
let timeLeft = 60;
|
|
|
let gameOver = false;
|
|
|
|
|
|
|
|
|
function drawBird() {
|
|
|
ctx.beginPath();
|
|
|
ctx.arc(bird.x, bird.y, bird.radius, 0, Math.PI * 2);
|
|
|
ctx.fillStyle = bird.color;
|
|
|
ctx.fill();
|
|
|
ctx.closePath();
|
|
|
}
|
|
|
|
|
|
|
|
|
function drawPipes() {
|
|
|
ctx.fillStyle = 'green';
|
|
|
pipes.forEach(pipe => {
|
|
|
ctx.fillRect(pipe.x, 0, pipe.width, pipe.top);
|
|
|
ctx.fillRect(pipe.x, canvas.height - pipe.bottom, pipe.width, pipe.bottom);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
function updateBird() {
|
|
|
bird.dy += bird.gravity;
|
|
|
bird.y += bird.dy;
|
|
|
|
|
|
|
|
|
if (bird.y + bird.radius > canvas.height) {
|
|
|
bird.y = canvas.height - bird.radius;
|
|
|
bird.dy = 0;
|
|
|
}
|
|
|
if (bird.y - bird.radius < 0) {
|
|
|
bird.y = bird.radius;
|
|
|
bird.dy = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
function updatePipes() {
|
|
|
if (pipes.length === 0 || pipes[pipes.length - 1].x < canvas.width - 200) {
|
|
|
const gap = 100;
|
|
|
const top = Math.random() * (canvas.height - gap - 100) + 50;
|
|
|
const bottom = canvas.height - top - gap;
|
|
|
pipes.push({ x: canvas.width, width: 50, top, bottom });
|
|
|
}
|
|
|
|
|
|
pipes.forEach(pipe => {
|
|
|
pipe.x -= 2;
|
|
|
|
|
|
|
|
|
if (
|
|
|
bird.x + bird.radius > pipe.x &&
|
|
|
bird.x - bird.radius < pipe.x + pipe.width &&
|
|
|
(bird.y - bird.radius < pipe.top || bird.y + bird.radius > canvas.height - pipe.bottom)
|
|
|
) {
|
|
|
gameOver = true;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (pipe.x + pipe.width < bird.x - bird.radius && !pipe.passed) {
|
|
|
score++;
|
|
|
pipe.passed = true;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
pipes = pipes.filter(pipe => pipe.x + pipe.width > 0);
|
|
|
}
|
|
|
|
|
|
|
|
|
function startTimer() {
|
|
|
const timerInterval = setInterval(() => {
|
|
|
if (timeLeft > 0 && !gameOver) {
|
|
|
timeLeft--;
|
|
|
timerDisplay.textContent = `Time: ${timeLeft}`;
|
|
|
} else {
|
|
|
clearInterval(timerInterval);
|
|
|
if (gameOver) {
|
|
|
alert(`Game Over! Your score is ${score}`);
|
|
|
} else {
|
|
|
alert(`Time's up! Your score is ${score}`);
|
|
|
}
|
|
|
document.location.reload();
|
|
|
}
|
|
|
}, 1000);
|
|
|
}
|
|
|
|
|
|
|
|
|
function gameLoop() {
|
|
|
if (gameOver) return;
|
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
drawBird();
|
|
|
drawPipes();
|
|
|
updateBird();
|
|
|
updatePipes();
|
|
|
scoreDisplay.textContent = `Score: ${score}`;
|
|
|
requestAnimationFrame(gameLoop);
|
|
|
}
|
|
|
|
|
|
|
|
|
const hands = new Hands({
|
|
|
locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`,
|
|
|
});
|
|
|
|
|
|
hands.setOptions({
|
|
|
maxNumHands: 1,
|
|
|
modelComplexity: 1,
|
|
|
minDetectionConfidence: 0.7,
|
|
|
minTrackingConfidence: 0.7,
|
|
|
});
|
|
|
|
|
|
const videoElement = document.getElementById('webcam');
|
|
|
const camera = new Camera(videoElement, {
|
|
|
onFrame: async () => {
|
|
|
await hands.send({ image: videoElement });
|
|
|
},
|
|
|
width: 640,
|
|
|
height: 480,
|
|
|
});
|
|
|
camera.start();
|
|
|
|
|
|
hands.onResults((results) => {
|
|
|
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
|
|
|
const landmarks = results.multiHandLandmarks[0];
|
|
|
const indexFinger = landmarks[8];
|
|
|
|
|
|
|
|
|
if (indexFinger.y * canvas.height < bird.y) {
|
|
|
bird.dy = bird.lift;
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
startTimer();
|
|
|
gameLoop();
|
|
|
</script>
|
|
|
</body>
|
|
|
</html> |