import { useEffect, useRef, useState, useMemo } from 'react'; import { motion, useAnimation } from 'framer-motion'; const NUM_DUPLICATES = 5; // How many loops for the spin effect // Fisher-Yates shuffle algorithm const shuffleArray = (array) => { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; }; // Responsive slot heights based on screen size const getSlotHeight = () => { if (typeof window === 'undefined') return 320; const width = window.innerWidth; if (width < 640) return 96; // mobile (h-24) if (width < 768) return 128; // sm (h-32) if (width < 1024) return 176; // md (h-44) if (width < 1280) return 224; // lg (h-56) if (width < 1536) return 288; // xl (h-72) return 320; // 2xl (h-80) }; export function SlotMachine({ winner, isSpinning, onAnimationComplete, onDraw, poolLength }) { const controls1 = useAnimation(); const controls2 = useAnimation(); const controls3 = useAnimation(); const [slotHeight, setSlotHeight] = useState(getSlotHeight()); // Generate randomized sequences for each reel (memoized so they stay consistent) // Always start with 0 at position 0, then shuffle the rest const randomSequences = useMemo(() => { // First reel (hundreds): only 0 and 1 const createSequenceFirstReel = () => { const remaining = [1]; const shuffled = shuffleArray(remaining); return [0, ...shuffled]; // Always put 0 first, then 1 }; // Second and third reels: 0-9 const createSequence = () => { const remaining = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const shuffled = shuffleArray(remaining); return [0, ...shuffled]; // Always put 0 first }; return [ createSequenceFirstReel(), // Reel 1: [0, 1] createSequence(), // Reel 2: [0, 1-9 shuffled] createSequence(), // Reel 3: [0, 1-9 shuffled] ]; }, []); useEffect(() => { const handleResize = () => setSlotHeight(getSlotHeight()); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); useEffect(() => { if (isSpinning && winner !== null) { const digits = winner.toString().padStart(3, '0').split('').map(Number); animateReels(digits); } }, [isSpinning, winner]); const animateReels = async (digits) => { // Reset positions instantly await Promise.all([ controls1.set({ y: 0 }), controls2.set({ y: 0 }), controls3.set({ y: 0 }) ]); // Animate each reel with stagger // For each digit, find its position in the randomized sequence const spin = (control, digit, reelIndex, delay) => { // Find where this digit appears in the last cycle of the randomized sequence const lastCycleStart = NUM_DUPLICATES * 10; const digitIndexInSequence = randomSequences[reelIndex].indexOf(digit); const targetIndex = lastCycleStart + digitIndexInSequence; const targetY = -(targetIndex * slotHeight); return control.start({ y: targetY, transition: { duration: 20 + delay, ease: [0.1, 0.9, 0.2, 1], // "Luxury" ease-out delay: 0 // We can rely on duration difference for staggering or explicit delay } }); }; // Staggered finish times: 20s, 20.5s, 21s await Promise.all([ spin(controls1, digits[0], 0, 0), spin(controls2, digits[1], 1, 0.5), spin(controls3, digits[2], 2, 1.0) ]); if (onAnimationComplete) { onAnimationComplete(); } }; const Strip = ({ controls, reelIndex }) => { // Generate the number strip with randomized sequence const numbers = []; const sequence = randomSequences[reelIndex]; // For the first reel (only 0-1), we need more repetitions to fill the strip // For other reels (0-9), use normal repetitions const repetitions = reelIndex === 0 ? (NUM_DUPLICATES + 1) * 5 // First reel: repeat 30 times (2 numbers × 30 = 60 total) : (NUM_DUPLICATES + 1); // Other reels: repeat 6 times (10 numbers × 6 = 60 total) for (let i = 0; i < repetitions; i++) { sequence.forEach(num => numbers.push(num)); } return ( {numbers.map((num, i) => (
{num}
))}
); }; const handleClick = () => { if (!isSpinning && poolLength > 0 && onDraw) { onDraw(); } }; return (
0 ? 'cursor-pointer' : 'cursor-not-allowed' }`} onClick={handleClick} >
0 ? 'hover:scale-105 active:scale-95' : '' }`}>
); } const SlotBox = ({ children }) => (
{children}
);