"use client"; import { useEffect, useRef, useState } from "react"; import { useUser } from "@/lib/context"; import { Play, RotateCcw, Coins, ShieldAlert, Award, Heart } from "lucide-react"; interface Particle { x: number; y: number; vx: number; vy: number; color: string; alpha: number; size: number; } interface GameObject { x: number; y: number; size: number; speed: number; type: "coin" | "obstacle"; } export default function CoinCatcher() { const { addLyts } = useUser(); const canvasRef = useRef(null); // Game states const [gameState, setGameState] = useState<"idle" | "playing" | "gameover">("idle"); const [score, setScore] = useState(0); const [lives, setLives] = useState(3); const [claimed, setClaimed] = useState(false); // References for mutable game loop variables const requestRef = useRef(0); const objectsRef = useRef([]); const particlesRef = useRef([]); const playerXRef = useRef(200); const livesRef = useRef(3); const scoreRef = useRef(0); const spawnTimerRef = useRef(0); // Start the game const startGame = () => { setGameState("playing"); setScore(0); setLives(3); setClaimed(false); livesRef.current = 3; scoreRef.current = 0; objectsRef.current = []; particlesRef.current = []; playerXRef.current = 200; }; // Claim score as LYTs const claimEarnings = () => { if (score > 0 && !claimed) { addLyts(score); setClaimed(true); } }; useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; // Track mouse coordinates to control player position const handleMouseMove = (e: MouseEvent) => { const rect = canvas.getBoundingClientRect(); const relativeX = e.clientX - rect.left; // Clamp player position within canvas bounds playerXRef.current = Math.max(30, Math.min(canvas.width - 30, relativeX)); }; // Track touch events for mobile support const handleTouchMove = (e: TouchEvent) => { if (e.touches.length === 0) return; const rect = canvas.getBoundingClientRect(); const relativeX = e.touches[0].clientX - rect.left; playerXRef.current = Math.max(30, Math.min(canvas.width - 30, relativeX)); }; canvas.addEventListener("mousemove", handleMouseMove); canvas.addEventListener("touchmove", handleTouchMove); // Particle emitter for explosion effects const createExplosion = (x: number, y: number, color: string) => { for (let i = 0; i < 15; i++) { particlesRef.current.push({ x, y, vx: (Math.random() - 0.5) * 6, vy: (Math.random() - 0.5) * 6 - 2, color, alpha: 1, size: Math.random() * 3 + 2, }); } }; // Main physics and rendering game loop const gameLoop = () => { if (gameState !== "playing") return; // Clear Canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // 1. Draw Catcher Bucket (Player) const pX = playerXRef.current; const pY = canvas.height - 35; // Outer glow for player ctx.shadowBlur = 15; ctx.shadowColor = "#ffc72c"; ctx.fillStyle = "#ffc72c"; ctx.beginPath(); // Draw a sleek curved basket shape ctx.roundRect(pX - 35, pY, 70, 15, [4, 4, 10, 10]); ctx.fill(); // Inside mesh glow ctx.shadowBlur = 5; ctx.shadowColor = "#ff2e88"; ctx.fillStyle = "#ff2e88"; ctx.fillRect(pX - 25, pY + 4, 50, 2); ctx.shadowBlur = 0; // Reset shadow // 2. Spawn Items spawnTimerRef.current += 1; if (spawnTimerRef.current > 40) { spawnTimerRef.current = 0; const type = Math.random() > 0.35 ? "coin" : "obstacle"; objectsRef.current.push({ x: Math.random() * (canvas.width - 40) + 20, y: -20, size: type === "coin" ? 12 : 14, speed: Math.random() * 2.5 + 3 + scoreRef.current * 0.05, // Speed increases as score goes up type, }); } // 3. Update and Draw Falling Items const activeObjects: GameObject[] = []; objectsRef.current.forEach((obj) => { obj.y += obj.speed; // Collision Check (AABB / Circle collision against basket) const hitPlayer = obj.y + obj.size >= pY && obj.y - obj.size <= pY + 15 && obj.x >= pX - 45 && obj.x <= pX + 45; if (hitPlayer) { if (obj.type === "coin") { scoreRef.current += 1; setScore(scoreRef.current); createExplosion(obj.x, obj.y, "#ffc72c"); } else { livesRef.current -= 1; setLives(livesRef.current); createExplosion(obj.x, obj.y, "#ff2e88"); // Screen shake effect on bomb hit ctx.translate((Math.random() - 0.5) * 15, (Math.random() - 0.5) * 15); if (livesRef.current <= 0) { setGameState("gameover"); } } } else if (obj.y < canvas.height + 20) { activeObjects.push(obj); } }); objectsRef.current = activeObjects; // Draw active falling objects objectsRef.current.forEach((obj) => { ctx.shadowBlur = 12; if (obj.type === "coin") { // Yellow LYT Coin ctx.shadowColor = "#ffc72c"; ctx.fillStyle = "#ffc72c"; ctx.beginPath(); ctx.arc(obj.x, obj.y, obj.size, 0, Math.PI * 2); ctx.fill(); // Outer yellow shine ring ctx.strokeStyle = "#ffffff"; ctx.lineWidth = 1.5; ctx.stroke(); // Core details ctx.fillStyle = "#ffffff"; ctx.font = "bold 9px Arial"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("L", obj.x, obj.y); } else { // Purple Obstacle Spike ctx.shadowColor = "#8b3dff"; ctx.fillStyle = "#8b3dff"; ctx.beginPath(); // Draw simple triangle obstacle ctx.moveTo(obj.x, obj.y - obj.size); ctx.lineTo(obj.x + obj.size, obj.y + obj.size); ctx.lineTo(obj.x - obj.size, obj.y + obj.size); ctx.closePath(); ctx.fill(); // Spiky border ctx.strokeStyle = "#ff2e88"; ctx.lineWidth = 1; ctx.stroke(); } }); ctx.shadowBlur = 0; // 4. Update and Draw Particle Explosions particlesRef.current.forEach((p, idx) => { p.x += p.vx; p.y += p.vy; p.alpha -= 0.025; ctx.fillStyle = p.color; ctx.globalAlpha = Math.max(0, p.alpha); ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); }); ctx.globalAlpha = 1.0; particlesRef.current = particlesRef.current.filter((p) => p.alpha > 0); // Loop requestRef.current = requestAnimationFrame(gameLoop); }; if (gameState === "playing") { requestRef.current = requestAnimationFrame(gameLoop); } return () => { cancelAnimationFrame(requestRef.current); canvas.removeEventListener("mousemove", handleMouseMove); canvas.removeEventListener("touchmove", handleTouchMove); }; }, [gameState]); return (
{/* Background glow */}
{/* Header */}
Play & Earn

The LYT Catcher Mini-Game

Use your mouse or touch screen to catch the falling yellow LYT coins. Avoid the purple hazard spikes! Points are credited straight to your wallet.

{/* Game Box Container */}
{/* Top Game UI Bar (While Playing) */} {gameState === "playing" && (
{/* Score */}
Score: {score}
{/* Lives */}
{Array.from({ length: 3 }).map((_, i) => ( ))}
)} {/* Canvas */} {/* Screens overlays */} {/* 1. Idle Overlay */} {gameState === "idle" && (

Ready to Catch?

Move your mouse or finger horizontally to control the catcher basket. Grab coins, avoid spikes!

)} {/* 2. Gameover Overlay */} {gameState === "gameover" && (

Game Over

You caught {score} LYTs!

{/* Claim details */}
)}
); }