Untitled

 avatar
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