import React, { useState, useEffect, useRef } from 'react';
import '../styles/slotmachine.scss';
import '../styles/tooltip.scss';
import Title from '../components/Title';
import Reel from '../components/Reel';
import { GameButton } from '../components/GameButton';
import Registration from './Registration';
import axiosInstance from '../utils/axiosInstance';
import { AxiosError } from 'axios';
import { GameConfig } from '../index';
import Model from '../components/Model';
import Tooltip from '../components/Tooltip';
interface SlotImage {
id: number;
image_path: string;
section_number: number;
prize_name?: string; // Optional field for prize name
}
const SlotMachine: React.FC<GameConfig> = ({
gameTitle = '',
titleColor = '',
backgroundImage = '',
reelBorder = '',
buttonBackgroundColor = '',
buttonTextColor = '',
}) => {
const [reels, setReels] = useState<string[][]>([]);
const [isSoundOn, setIsSoundOn] = useState(true);
const [slotImages, setSlotImages] = useState<SlotImage[]>([]);
const [error, setError] = useState<string | null>(null);
const [isSpinning, setIsSpinning] = useState(false);
const [completedReels, setCompletedReels] = useState(0);
const [isRegistrationOpen, setIsRegistrationOpen] = useState(false);
const [spinCombination, setSpinCombination] = useState<string | null>(null);
const [spinResult, setSpinResult] = useState<'win' | 'loss' | null>(null);
const [spinKey, setSpinKey] = useState(0);
const [isInfoOpen, setIsInfoOpen] = useState(false);
const [tooltips, setTooltips] = useState({
sound: false,
info: false,
});
const spinAudioRef = useRef<HTMLAudioElement | null>(null);
const winAudioRef = useRef<HTMLAudioElement | null>(null);
const loseAudioRef = useRef<HTMLAudioElement | null>(null);
const baseSpinDuration = 2000;
const delayBetweenStops = 600;
const DEFAULT_SLOT_COUNT = 3;
const API_BASE_URL = process.env.REACT_APP_API_URL;
const SPIN_AUDIO_URL = `${API_BASE_URL}audio/wheel-spin.mp3`;
const WIN_AUDIO_URL = `${API_BASE_URL}audio/winning_sound.mp3`;
const LOSE_AUDIO_URL = `${API_BASE_URL}audio/losing_game.mp3`;
useEffect(() => {
const fetchImages = async () => {
try {
const response = await axiosInstance.get('/api/slot/images');
if (response.data.status && response.data.data.images.length > 0) {
setSlotImages(response.data.data.images);
} else {
throw new Error(response.data.message || 'Failed to fetch images');
}
} catch (error) {
console.error('Error fetching slot images:', error);
const axiosError = error as AxiosError;
if (axiosError.message === 'Network Error' || !axiosError.response) {
setError('Server not responding');
} else {
setError('Error fetching slot images');
}
setIsRegistrationOpen(true);
}
};
fetchImages();
setReels(Array.from({ length: DEFAULT_SLOT_COUNT }, () => []));
}, []);
useEffect(() => {
if (!spinAudioRef.current) {
spinAudioRef.current = new Audio(SPIN_AUDIO_URL);
spinAudioRef.current.loop = true;
}
}, []);
useEffect(() => {
if (!winAudioRef.current) {
winAudioRef.current = new Audio(WIN_AUDIO_URL);
winAudioRef.current.loop = false;
}
}, []);
useEffect(() => {
if (!loseAudioRef.current) {
loseAudioRef.current = new Audio(LOSE_AUDIO_URL);
loseAudioRef.current.loop = false;
}
}, []);
const handleSpin = () => {
if (!isSpinning) {
setIsRegistrationOpen(true);
setSpinResult(null);
}
};
const handleRegistrationSubmit = (
username: string,
phone: string,
eligible: boolean,
combination: string,
result: string,
) => {
if (eligible) {
setSpinCombination(combination); // This will be used for game info
setSpinResult(result as 'win' | 'loss');
setIsSpinning(true);
setCompletedReels(0);
setIsRegistrationOpen(false);
setSpinKey((prev) => prev + 1);
if (isSoundOn && spinAudioRef.current) {
spinAudioRef.current.currentTime = 0;
spinAudioRef.current.play().catch((err) => console.error('Error playing spin audio:', err));
}
}
};
useEffect(() => {
if (completedReels === reels.length && isSpinning) {
setIsSpinning(false);
setTimeout(() => {
if (spinAudioRef.current && !spinAudioRef.current.paused) {
spinAudioRef.current.pause();
spinAudioRef.current.currentTime = 0;
}
if (isSoundOn) {
if (spinResult === 'win' && winAudioRef.current) {
winAudioRef.current.currentTime = 0;
winAudioRef.current
.play()
.catch((err) => console.error('Error playing win audio:', err));
} else if (spinResult === 'loss' && loseAudioRef.current) {
loseAudioRef.current.currentTime = 0;
loseAudioRef.current
.play()
.catch((err) => console.error('Error playing lose audio:', err));
}
}
setIsRegistrationOpen(true);
}, 3600);
}
}, [completedReels, reels.length, isSpinning, spinResult, isSoundOn]);
const handleReelComplete = () => {
setCompletedReels((prev) => prev + 1);
};
const toggleSound = () => {
setIsSoundOn((prev) => {
const newSoundState = !prev;
if (!newSoundState) {
if (spinAudioRef.current && !spinAudioRef.current.paused) {
spinAudioRef.current.pause();
spinAudioRef.current.currentTime = 0;
}
if (winAudioRef.current && !winAudioRef.current.paused) {
winAudioRef.current.pause();
winAudioRef.current.currentTime = 0;
}
if (loseAudioRef.current && !loseAudioRef.current.paused) {
loseAudioRef.current.pause();
loseAudioRef.current.currentTime = 0;
}
} else if (newSoundState && isSpinning && spinAudioRef.current) {
spinAudioRef.current.currentTime = 0;
spinAudioRef.current.play().catch((err) => console.error('Error playing spin audio:', err));
}
return newSoundState;
});
};
const infomessage = () => {
setIsInfoOpen(true);
};
const handleSoundMouseEnter = () => setTooltips((prev) => ({ ...prev, sound: true }));
const handleSoundMouseLeave = () => setTooltips((prev) => ({ ...prev, sound: false }));
const handleInfoMouseEnter = () => setTooltips((prev) => ({ ...prev, info: true }));
const handleInfoMouseLeave = () => setTooltips((prev) => ({ ...prev, info: false }));
const renderGameInfo = () => {
// For now, use spinCombination or a fallback if API isn't ready
const combination = spinCombination || '123'; // Fallback to '123' until API is integrated
// Placeholder logic: assume combination maps to a single prize (e.g., first digit or full string as key)
// Replace this with actual API mapping when available
const prizeId = parseInt(combination[0]); // Temporary: using first digit as prize ID
const slot = slotImages.find((img) => img.id === prizeId) || {
id: prizeId,
image_path: 'placeholder.png', // Placeholder until API data
prize_name: `Prize for ${combination}`,
};
return (
<div className="game-info-container">
<h3 className="game-info-title">Combination Prize Preview</h3>
<div className="game-info-item">
<img src={slot.image_path} alt={`Prize for ${combination}`} className="game-info-image" />
<p className="game-info-prize">{slot.prize_name || `Prize for ${combination}`}</p>
</div>
</div>
);
};
return (
<div className="main-slotmachine">
<div className="slot-machine">
<div
id="framework-center"
style={{
backgroundImage: `url(${backgroundImage})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
>
<div className="game-title" style={{ color: titleColor }}>
<Title gameTitle={gameTitle} titleColor={titleColor} />
</div>
<div className="control-buttons-container">
<Tooltip
text={isSoundOn ? 'Mute Sound' : 'Unmute Sound'}
visible={tooltips.sound}
position="bottom"
>
<GameButton
variant="sound"
isActive={isSoundOn}
onClick={toggleSound}
onMouseEnter={handleSoundMouseEnter}
onMouseLeave={handleSoundMouseLeave}
/>
</Tooltip>
<Tooltip text="View Info" visible={tooltips.info} position="bottom">
<GameButton
variant="info"
onClick={infomessage}
onMouseEnter={handleInfoMouseEnter}
onMouseLeave={handleInfoMouseLeave}
/>
</Tooltip>
</div>
<div className="reels-container" style={{ borderColor: reelBorder }}>
{reels.map((_, index) => {
const targetId = spinCombination ? parseInt(spinCombination[index] || '0') : -1;
return (
<Reel
key={`${index}-${spinKey}`}
slotImages={slotImages}
isSpinning={isSpinning}
spinDuration={baseSpinDuration + index * delayBetweenStops}
onSpinComplete={handleReelComplete}
targetId={targetId}
reelBorder={reelBorder}
/>
);
})}
</div>
<div className="spin-container">
<div className="spin-button-wrapper">
<GameButton
variant="spin"
onClick={handleSpin}
disabled={isSpinning}
buttonBackgroundColor={buttonBackgroundColor}
buttonTextColor={buttonTextColor}
reelBorder={reelBorder}
/>
</div>
</div>
</div>
<Registration
isOpen={isRegistrationOpen}
setIsOpen={setIsRegistrationOpen}
onSubmit={handleRegistrationSubmit}
spinResult={isSpinning ? null : spinResult}
error={error}
/>
<Model
isOpen={isInfoOpen}
onClose={() => setIsInfoOpen(false)}
title="Game Rules"
content={
<div>
<p>Welcome to the Slot Machine Game!</p>
{slotImages.length > 0 ? renderGameInfo() : <p>Loading prize preview...</p>}
</div>
}
showCloseButton={true}
/>
</div>
</div>
);
};
export default SlotMachine;