School Administrators of Montana

DaRud Cup Golf Challenge

import React, { useEffect, useMemo, useRef, useState } from "react";import { motion } from "framer-motion";import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";import { Button } from "@/components/ui/button";import { Progress } from "@/components/ui/progress";import { Badge } from "@/components/ui/badge";import { RotateCcw, Trophy, Flag, Wind, Target, Coins } from "lucide-react";/** * Mobile-first Hole In One Game * * Optimized for phones first, then scales up for tablet/desktop. * * Props: * - memberName?: string * - attemptsPerRound?: number * - onRoundComplete?: ({ score, holesInOne, attemptsUsed }) => void * - onShotTaken?: ({ result, distanceToPin, power, angle, wind }) => void */export default function HoleInOneGame({ memberName = "Member", attemptsPerRound = 5, onRoundComplete, onShotTaken,}) { const COURSE_WIDTH = 360; const COURSE_HEIGHT = 540; const TEE_X = 64; const TEE_Y = 445; const HOLE_X = 290; const HOLE_Y = 130; const CUP_RADIUS = 11; const BALL_RADIUS = 7; const MAX_POWER = 100; const [attempt, setAttempt] = useState(1); const [score, setScore] = useState(0); const [holesInOne, setHolesInOne] = useState(0); const [streak, setStreak] = useState(0); const [message, setMessage] = useState("Drag on the course to aim, then tap Shoot."); const [power, setPower] = useState(58); const [angle, setAngle] = useState(-53); const [wind, setWind] = useState(0); const [isFlying, setIsFlying] = useState(false); const [roundComplete, setRoundComplete] = useState(false); const [showConfetti, setShowConfetti] = useState(false); const [ballPos, setBallPos] = useState({ x: TEE_X, y: TEE_Y }); const [lastDistance, setLastDistance] = useState(null); const [bestShot, setBestShot] = useState(null); const courseRef = useRef(null); const dragRef = useRef(false); const attemptsLeft = Math.max(attemptsPerRound - attempt + 1, 0); const progressValue = ((attempt - 1) / attemptsPerRound) * 100; useEffect(() => { setWind(randomWind()); }, []); const playerTitle = useMemo(() => { if (holesInOne >= 3) return "Ace Machine"; if (score >= 350) return "Club Legend"; if (score >= 200) return "Sharp Shooter"; return "Weekend Golfer"; }, [holesInOne, score]); function randomWind() { return Number((Math.random() * 1.8 - 0.9).toFixed(2)); } function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); } function resetBall() { setBallPos({ x: TEE_X, y: TEE_Y }); } function resetGame() { setAttempt(1); setScore(0); setHolesInOne(0); setStreak(0); setMessage("Drag on the course to aim, then tap Shoot."); setPower(58); setAngle(-53); setWind(randomWind()); setIsFlying(false); setRoundComplete(false); setShowConfetti(false); setLastDistance(null); setBestShot(null); resetBall(); } function screenToAim(clientX, clientY) { const rect = courseRef.current?.getBoundingClientRect(); if (!rect) return; const scaleX = COURSE_WIDTH / rect.width; const scaleY = COURSE_HEIGHT / rect.height; const x = (clientX - rect.left) * scaleX; const y = (clientY - rect.top) * scaleY; const dx = x - TEE_X; const dy = y - TEE_Y; const rawAngle = (Math.atan2(dy, dx) * 180) / Math.PI; const safeAngle = clamp(rawAngle, -85, -18); const dist = Math.sqrt(dx * dx + dy * dy); const safePower = clamp((dist / 3.2) * 1.1, 22, MAX_POWER); setAngle(Number(safeAngle.toFixed(0))); setPower(Number(safePower.toFixed(0))); } function handlePointerDown(e) { if (isFlying || roundComplete) return; dragRef.current = true; screenToAim(e.clientX, e.clientY); } function handlePointerMove(e) { if (!dragRef.current || isFlying || roundComplete) return; screenToAim(e.clientX, e.clientY); } function handlePointerUp() { dragRef.current = false; } function finishRound(finalScore, finalHolesInOne, attemptsUsed) { setRoundComplete(true); setMessage( finalHolesInOne > 0 ? `Round complete. ${finalHolesInOne} ace${finalHolesInOne > 1 ? "s" : ""}. That'll play.` : "Round complete. No ace this time, but the golf gods like persistence." ); onRoundComplete?.({ score: finalScore, holesInOne: finalHolesInOne, attemptsUsed, }); } function takeShot() { if (isFlying || roundComplete) return; setIsFlying(true); setMessage("Ball in the air..."); const angleRad = (angle * Math.PI) / 180; const baseDistance = power * 4.1; const windEffect = wind * 18; const skillVariance = (Math.random() - 0.5) * 26; const heightVariance = (Math.random() - 0.5) * 22; const targetX = TEE_X + Math.cos(angleRad) * baseDistance + windEffect + skillVariance; const targetY = TEE_Y + Math.sin(angleRad) * baseDistance + heightVariance; const clampedX = clamp(targetX, 28, COURSE_WIDTH - 28); const clampedY = clamp(targetY, 70, COURSE_HEIGHT - 35); setBallPos({ x: clampedX, y: clampedY }); window.setTimeout(() => { const distanceToPin = Math.sqrt( Math.pow(clampedX - HOLE_X, 2) + Math.pow(clampedY - HOLE_Y, 2) ); const isHoleInOne = distanceToPin <= CUP_RADIUS + 5; const isClose = distanceToPin <= 30; const isPrettyGood = distanceToPin <= 58; let earned = 0; let nextStreak = 0; let nextMessage = ""; let nextHolesInOne = holesInOne; if (isHoleInOne) { nextHolesInOne += 1; nextStreak = streak + 1;