/** * Reels Manager * Handles creation, spinning, and management of slot reels */ class ReelsManager { constructor() { this.reelsContainer = document.getElementById('reels-container'); this.reels = []; this.reelsData = []; this.spinning = false; this.currentState = CONFIG.STATE.IDLE; this.stopPromises = []; this.init(); } /** * Initialize the reels */ init() { this.reelsContainer.innerHTML = ''; this.reels = []; this.reelsData = []; // Create reels for (let i = 0; i < CONFIG.reels; i++) { this.createReel(i); } // Initial population of symbols this.populateAllReels(); } /** * Create a single reel * @param {number} index - Reel index */ createReel(index) { // Create reel element const reel = document.createElement('div'); reel.className = 'reel'; reel.id = `reel-${index}`; // Create reel wrapper for spinning const reelWrapper = document.createElement('div'); reelWrapper.className = 'reel-wrapper'; reelWrapper.id = `reel-wrapper-${index}`; reel.appendChild(reelWrapper); this.reelsContainer.appendChild(reel); // Store reel reference this.reels.push({ element: reel, wrapper: reelWrapper, symbols: [] }); // Initialize reel data this.reelsData.push([]); } /** * Populate all reels with random symbols */ populateAllReels() { for (let i = 0; i < this.reels.length; i++) { this.populateReel(i); } } /** * Populate a single reel with random symbols * @param {number} reelIndex - Index of the reel to populate */ populateReel(reelIndex) { const reelWrapper = this.reels[reelIndex].wrapper; reelWrapper.innerHTML = ''; // Generate symbols for this reel const reelSymbols = []; // We generate more symbols than visible to allow for spinning const totalSymbols = CONFIG.visibleSymbols * 3; // 3x the visible symbols for (let i = 0; i < totalSymbols; i++) { const symbol = this.createSymbolElement(reelIndex, i); reelWrapper.appendChild(symbol.element); reelSymbols.push(symbol); } // Store symbols for this reel this.reels[reelIndex].symbols = reelSymbols; // Reset position reelWrapper.style.transform = 'translateY(0)'; // Store actual data for only the visible symbols this.reelsData[reelIndex] = reelSymbols.slice(0, CONFIG.visibleSymbols).map(s => s.data); } /** * Create a symbol element * @param {number} reelIndex - Index of the reel * @param {number} symbolIndex - Index of the symbol in the reel * @returns {Object} Symbol object with element and data */ createSymbolElement(reelIndex, symbolIndex) { // Get a random symbol const symbolData = getRandomSymbol(); // Create element const symbolElement = document.createElement('div'); symbolElement.className = 'symbol'; symbolElement.id = `symbol-${reelIndex}-${symbolIndex}`; // Set background image symbolElement.style.backgroundImage = `url(${CONFIG.paths.symbols}${symbolData.imageUrl})`; // Optional: Add symbol name as data attribute symbolElement.dataset.symbol = symbolData.id; return { element: symbolElement, data: symbolData }; } /** * Spin all reels * @returns {Promise} Promise that resolves when all reels have stopped */ spin() { if (this.spinning) return Promise.reject('Already spinning'); this.spinning = true; this.currentState = CONFIG.STATE.SPINNING; this.stopPromises = []; // Generate new symbols for all reels this.reelsData = []; for (let i = 0; i < CONFIG.reels; i++) { const reelSymbols = []; for (let j = 0; j < CONFIG.visibleSymbols; j++) { reelSymbols.push(getRandomSymbol()); } this.reelsData.push(reelSymbols); } // Spin each reel with a delay for (let i = 0; i < this.reels.length; i++) { const delay = i * 200; // stagger start of each reel const spinDuration = this.getRandomSpinDuration(); setTimeout(() => { const promise = this.spinReel(i, spinDuration); this.stopPromises.push(promise); }, delay); } // Return a promise that resolves when all reels have stopped return Promise.all(this.stopPromises) .then(() => { this.spinning = false; this.currentState = CONFIG.STATE.IDLE; return this.reelsData; }); } /** * Spin a single reel * @param {number} reelIndex - Index of the reel to spin * @param {number} duration - Duration of spin in ms * @returns {Promise} Promise that resolves when reel has stopped */ spinReel(reelIndex, duration) { return new Promise(resolve => { const reel = this.reels[reelIndex]; const wrapper = reel.wrapper; // Generate new symbols for spinning for (let i = 0; i < reel.symbols.length; i++) { const newSymbol = this.createSymbolElement(reelIndex, i); // Replace the DOM element wrapper.replaceChild(newSymbol.element, reel.symbols[i].element); // Update the reference reel.symbols[i] = newSymbol; } // Prepare final symbols (visible after spin) const finalSymbols = this.reelsData[reelIndex]; // Set final symbols at the bottom of the reel (will be visible after spin) for (let i = 0; i < CONFIG.visibleSymbols; i++) { const symbolIndex = reel.symbols.length - CONFIG.visibleSymbols + i; const symbol = reel.symbols[symbolIndex]; // Replace with predetermined symbol if (finalSymbols[i]) { // Update data symbol.data = finalSymbols[i]; // Update visual symbol.element.style.backgroundImage = `url(${CONFIG.paths.symbols}${finalSymbols[i].imageUrl})`; symbol.element.dataset.symbol = finalSymbols[i].id; } } // Reset position wrapper.style.transition = 'none'; wrapper.style.transform = 'translateY(0)'; // Force reflow wrapper.offsetHeight; // Start spinning wrapper.style.transition = `transform ${duration}ms ease-in-out`; wrapper.style.transform = `translateY(calc(-100% + ${CONFIG.visibleSymbols * 100 / 3}%))`; // When animation ends setTimeout(() => { // Reset position and put final symbols at the top wrapper.style.transition = 'none'; wrapper.style.transform = 'translateY(0)'; // Update symbol elements to show final state for (let i = 0; i < CONFIG.visibleSymbols; i++) { const symbol = reel.symbols[i]; // Replace with predetermined symbol if (finalSymbols[i]) { // Update data symbol.data = finalSymbols[i]; // Update visual symbol.element.style.backgroundImage = `url(${CONFIG.paths.symbols}${finalSymbols[i].imageUrl})`; symbol.element.dataset.symbol = finalSymbols[i].id; } } resolve(); }, duration); }); } /** * Get a random spin duration * @returns {number} Spin duration in ms */ getRandomSpinDuration() { return CONFIG.spinTime.min + Math.random() * (CONFIG.spinTime.max - CONFIG.spinTime.min); } /** * Highlight winning symbols * @param {Array} winningSymbols - Array of coordinates of winning symbols */ highlightWinningSymbols(winningSymbols) { // Clear previous highlights this.clearHighlights(); // Add highlight class to winning symbols winningSymbols.forEach(coord => { const reel = this.reels[coord.reel]; if (reel && reel.symbols[coord.position]) { const symbolElement = reel.symbols[coord.position].element; symbolElement.classList.add('winner'); } }); } /** * Clear highlights from all symbols */ clearHighlights() { this.reels.forEach(reel => { reel.symbols.forEach(symbol => { symbol.element.classList.remove('winner'); }); }); // Clear paylines const paylines = document.querySelectorAll('.payline'); paylines.forEach(line => line.classList.remove('active')); } /** * Show a specific payline * @param {number} paylineId - ID of the payline to show */ showPayline(paylineId) { const payline = document.getElementById(`payline-${paylineId}`); if (payline) { payline.classList.add('active'); } } /** * Get the current state of the reels * @returns {Array} 2D array of symbols on the reels */ getCurrentState() { return this.reelsData; } } // Create and export an instance of ReelsManager const reelsManager = new ReelsManager(); if (typeof module !== 'undefined') { module.exports = reelsManager; }