Untitled
unknown
plain_text
3 months ago
14 kB
6
Indexable
import React, { useEffect, useRef, useState } from "react";
export default function RacingGame() {
return (
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center p-6">
<div className="text-center max-w-3xl w-full">
<h1 className="text-4xl font-bold mb-2">2D Racing Game</h1>
<p className="text-gray-300 mb-6">Desktop arrows ya mobile touch buttons se car control karo.</p>
<GameCanvas />
</div>
</div>
);
}
function GameCanvas() {
const canvasRef = useRef(null);
const [score, setScore] = useState(0);
const [bestScore, setBestScore] = useState(0);
const [status, setStatus] = useState("ready");
const [coins, setCoins] = useState(0);
const gameRef = useRef(null);
useEffect(() => {
const savedBest = Number(localStorage.getItem("racing-best-score") || 0);
setBestScore(savedBest);
}, []);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const state = {
coins: 0,
nitro: 100,
paused: false,
coinItems: [],
animationFrame: null,
roadOffset: 0,
spawnTimer: 0,
points: 0,
speedScale: 1,
ended: false,
keys: {},
touchLeft: false,
touchRight: false,
audioContext: null,
player: {
x: 180,
y: 500,
w: 40,
h: 70,
speed: 7,
},
traffic: [],
};
gameRef.current = state;
const ensureAudio = () => {
if (!state.audioContext) {
const AudioCtx = window.AudioContext || window.webkitAudioContext;
if (AudioCtx) state.audioContext = new AudioCtx();
}
if (state.audioContext?.state === "suspended") {
state.audioContext.resume();
}
};
const beep = (frequency, duration, type = "square") => {
ensureAudio();
const ac = state.audioContext;
if (!ac) return;
const osc = ac.createOscillator();
const gain = ac.createGain();
osc.type = type;
osc.frequency.value = frequency;
gain.gain.value = 0.03;
osc.connect(gain);
gain.connect(ac.destination);
osc.start();
gain.gain.exponentialRampToValueAtTime(0.0001, ac.currentTime + duration);
osc.stop(ac.currentTime + duration);
};
const lanes = [120, 180, 240];
const resetGame = () => {
state.roadOffset = 0;
state.spawnTimer = 0;
state.points = 0;
state.coins = 0;
state.nitro = 100;
state.paused = false;
state.coinItems = [];
setCoins(0);
state.speedScale = 1;
state.ended = false;
state.traffic = [];
state.player.x = 180;
setScore(0);
setStatus("running");
cancelAnimationFrame(state.animationFrame);
loop();
};
state.resetGame = resetGame;
const spawnTraffic = () => {
const lane = lanes[Math.floor(Math.random() * lanes.length)];
state.traffic.push({
x: lane,
y: -80,
w: 40,
h: 70,
speed: (4 + Math.random() * 2) * state.speedScale,
color: ["#ef4444", "#f59e0b", "#10b981", "#8b5cf6"][Math.floor(Math.random() * 4)],
});
if (Math.random() < 0.35) {
state.coinItems.push({
x: lane + 12,
y: -40,
r: 10,
speed: 4.5 * state.speedScale,
});
}
};
const keyDown = (e) => {
if (["ArrowLeft", "ArrowRight", " ", "p", "P", "Shift"].includes(e.key)) e.preventDefault();
state.keys[e.key] = true;
if (e.key === " " && status !== "running") resetGame();
if ((e.key === "p" || e.key === "P") && status !== "ready") {
state.paused = !state.paused;
setStatus(state.paused ? "paused" : "running");
}
ensureAudio();
};
const keyUp = (e) => {
state.keys[e.key] = false;
};
window.addEventListener("keydown", keyDown, { passive: false });
window.addEventListener("keyup", keyUp);
const drawCar = (x, y, color) => {
ctx.fillStyle = color;
ctx.fillRect(x, y, 40, 70);
ctx.fillStyle = "#111827";
ctx.fillRect(x + 8, y + 10, 24, 15);
ctx.fillRect(x + 8, y + 45, 24, 15);
ctx.fillStyle = "black";
ctx.fillRect(x - 4, y + 10, 6, 14);
ctx.fillRect(x + 38, y + 10, 6, 14);
ctx.fillRect(x - 4, y + 46, 6, 14);
ctx.fillRect(x + 38, y + 46, 6, 14);
};
const drawCoin = (x, y, r) => {
ctx.beginPath();
ctx.fillStyle = "#facc15";
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
ctx.fillStyle = "#92400e";
ctx.font = "bold 12px Arial";
ctx.fillText("C", x - 4, y + 4);
};
const finishGame = () => {
state.ended = true;
setStatus("gameover");
beep(120, 0.35, "sawtooth");
const nextBest = Math.max(bestScore, state.points);
if (nextBest > bestScore) {
setBestScore(nextBest);
localStorage.setItem("racing-best-score", String(nextBest));
}
};
const loop = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#166534";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#374151";
ctx.fillRect(80, 0, 200, canvas.height);
ctx.strokeStyle = "white";
ctx.lineWidth = 4;
ctx.setLineDash([30, 20]);
state.roadOffset += 8 * state.speedScale;
ctx.lineDashOffset = state.roadOffset;
ctx.beginPath();
ctx.moveTo(180, 0);
ctx.lineTo(180, canvas.height);
ctx.moveTo(240, 0);
ctx.lineTo(240, canvas.height);
ctx.stroke();
ctx.setLineDash([]);
if (!state.ended && !state.paused) {
const nitroActive = state.keys["Shift"] && state.nitro > 0;
const moveLeft = state.keys["ArrowLeft"] || state.touchLeft;
const moveRight = state.keys["ArrowRight"] || state.touchRight;
const speedBoost = nitroActive ? 1.8 : 1;
if (moveLeft) state.player.x -= state.player.speed * speedBoost;
if (moveRight) state.player.x += state.player.speed * speedBoost;
state.player.x = Math.max(90, Math.min(250, state.player.x));
if (nitroActive) {
state.nitro = Math.max(0, state.nitro - 0.9);
} else {
state.nitro = Math.min(100, state.nitro + 0.25);
}
state.spawnTimer++;
const spawnLimit = Math.max(20, 48 - Math.floor(state.speedScale * 5));
if (state.spawnTimer > spawnLimit) {
spawnTraffic();
state.spawnTimer = 0;
}
for (let i = state.traffic.length - 1; i >= 0; i--) {
const car = state.traffic[i];
car.y += car.speed;
drawCar(car.x, car.y, car.color);
const hit =
state.player.x < car.x + car.w &&
state.player.x + state.player.w > car.x &&
state.player.y < car.y + car.h &&
state.player.y + state.player.h > car.y;
if (hit) {
finishGame();
}
if (car.y > canvas.height) {
state.traffic.splice(i, 1);
state.points += 10;
setScore(state.points);
if (state.points % 50 === 0) {
state.speedScale += 0.12;
beep(660, 0.08, "triangle");
}
}
}
for (let i = state.coinItems.length - 1; i >= 0; i--) {
const coin = state.coinItems[i];
coin.y += coin.speed;
drawCoin(coin.x, coin.y, coin.r);
const gotCoin =
state.player.x < coin.x + coin.r &&
state.player.x + state.player.w > coin.x - coin.r &&
state.player.y < coin.y + coin.r &&
state.player.y + state.player.h > coin.y - coin.r;
if (gotCoin) {
state.coinItems.splice(i, 1);
state.coins += 1;
setCoins(state.coins);
beep(880, 0.05, "triangle");
continue;
}
if (coin.y > canvas.height) {
state.coinItems.splice(i, 1);
}
}
} else {
for (const coin of state.coinItems) {
drawCoin(coin.x, coin.y, coin.r);
}
for (const car of state.traffic) {
drawCar(car.x, car.y, car.color);
}
}
drawCar(state.player.x, state.player.y, "#3b82f6");
ctx.fillStyle = "white";
ctx.font = "20px Arial";
ctx.fillText(`Score: ${state.points}`, 20, 30);
ctx.fillText(`Best: ${Math.max(bestScore, state.points)}`, 20, 55);
ctx.fillText(`Level: ${Math.floor((state.speedScale - 1) / 0.12) + 1}`, 20, 80);
ctx.fillText(`Coins: ${state.coins}`, 20, 105);
ctx.fillText(`Nitro: ${Math.floor(state.nitro)}%`, 20, 130);
if (status === "ready") {
ctx.fillStyle = "rgba(0,0,0,0.55)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "white";
ctx.font = "bold 28px Arial";
ctx.fillText("Tap Start", 120, 270);
ctx.font = "18px Arial";
ctx.fillText("Arrow keys ya touch buttons use karo", 48, 310);
}
if (state.paused && !state.ended) {
ctx.fillStyle = "rgba(0,0,0,0.55)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "white";
ctx.font = "bold 30px Arial";
ctx.fillText("Paused", 128, 290);
ctx.font = "18px Arial";
ctx.fillText("P dabao aur game continue karo", 75, 325);
}
if (state.ended) {
ctx.fillStyle = "rgba(0,0,0,0.6)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "white";
ctx.font = "bold 32px Arial";
ctx.fillText("Game Over", 100, 280);
ctx.font = "20px Arial";
ctx.fillText(`Final Score: ${state.points}`, 110, 320);
ctx.fillText("Restart button dabao", 102, 352);
}
state.animationFrame = requestAnimationFrame(loop);
};
loop();
return () => {
cancelAnimationFrame(state.animationFrame);
window.removeEventListener("keydown", keyDown);
window.removeEventListener("keyup", keyUp);
if (state.audioContext && state.audioContext.state !== "closed") {
state.audioContext.close();
}
};
}, [bestScore, status]);
const startOrRestart = () => {
if (gameRef.current?.resetGame) {
gameRef.current.resetGame();
}
};
const hold = (dir, active) => {
if (!gameRef.current) return;
if (dir === "left") gameRef.current.touchLeft = active;
if (dir === "right") gameRef.current.touchRight = active;
};
return (
<div className="flex flex-col items-center gap-4">
<div className="flex flex-wrap justify-center gap-3 text-sm text-gray-300">
<span className="px-3 py-1 rounded-full bg-gray-800">Score: {score}</span>
<span className="px-3 py-1 rounded-full bg-gray-800">Best: {bestScore}</span>
<span className="px-3 py-1 rounded-full bg-gray-800">Coins: {coins}</span>
<span className="px-3 py-1 rounded-full bg-gray-800">
{status === "running" ? "Running" : status === "gameover" ? "Game Over" : status === "paused" ? "Paused" : "Ready"}
</span>
</div>
<canvas
ref={canvasRef}
width={360}
height={640}
className="rounded-3xl shadow-2xl border border-gray-700 bg-black max-w-full"
/>
<div className="flex gap-3 flex-wrap justify-center">
<button
onClick={startOrRestart}
className="px-4 py-2 rounded-xl bg-blue-600 hover:bg-blue-500 active:bg-blue-700 text-white font-semibold"
>
{status === "ready" ? "Start Game" : "Restart Game"}
</button>
<button
onClick={() => {
if (gameRef.current && status !== "ready" && status !== "gameover") {
gameRef.current.paused = !gameRef.current.paused;
setStatus(gameRef.current.paused ? "paused" : "running");
}
}}
className="px-4 py-2 rounded-xl bg-gray-700 hover:bg-gray-600 active:bg-gray-800 text-white font-semibold"
>
Pause / Resume
</button>
</div>
<div className="grid grid-cols-2 gap-3 w-full max-w-xs">
<button
className="rounded-2xl bg-gray-800 hover:bg-gray-700 active:bg-gray-600 py-4 text-lg font-semibold"
onTouchStart={() => hold("left", true)}
onTouchEnd={() => hold("left", false)}
onMouseDown={() => hold("left", true)}
onMouseUp={() => hold("left", false)}
onMouseLeave={() => hold("left", false)}
>
Left
</button>
<button
className="rounded-2xl bg-gray-800 hover:bg-gray-700 active:bg-gray-600 py-4 text-lg font-semibold"
onTouchStart={() => hold("right", true)}
onTouchEnd={() => hold("right", false)}
onMouseDown={() => hold("right", true)}
onMouseUp={() => hold("right", false)}
onMouseLeave={() => hold("right", false)}
>
Right
</button>
</div>
<p className="text-gray-400 text-sm">Features: mobile controls, speed levels, restart button, pause, nitro boost, coins, aur sound effects. Nitro ke liye Shift dabao, pause ke liye P.</p>
</div>
);
}
Editor is loading...
Leave a Comment