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 (