import { AnticipationAnimationManager } from "./anticipation/AnticipationAnimationManager"; import { Style, ObjectBase } from "dsg-engine"; import { GridProps, SlotMachineProps, MaskingMode, SlotMachineState, SymbolData, SoundProps } from './types'; import { DynamicGridCollection, Grid, Reel, WinController, SpinController, SoundController } from "."; import { MaskFactory } from './mask/MaskFactory'; export class SlotMachine extends ObjectBase { private _staticGrid: Grid; private _dynamicGridCollection: DynamicGridCollection; private _finalSymbols: SymbolData[][] = []; private _winController: WinController; private _spinController: SpinController; private _soundController: SoundController; public get winController(): WinController { return this._winController; } public get soundController(): SoundController { return this._soundController; } public get state(): SlotMachineState { return this._spinController.state; } public get staticGrid(): Grid { return this._staticGrid; } public get finalSymbols(): SymbolData[][] { return this._finalSymbols; } /** * @example * ```typescript * // Setup symbols * const symbolsConfig = { * 0: { * spritesheet: gridAssets[ "butterfly" ].name, * id: 0, * name: gridAssets.butterfly.name, * scale: 0.7 * }, * 1: { * spritesheet: gridAssets.j.name, * id: 1, * name: gridAssets.j.name, * scale: 0.7 * }, * 2: { * spritesheet: gridAssets.k.name, * id: 2, * name: gridAssets.k.name, * scale: 0.7 * }, * } as SymbolsConfig; * * // Setup reels * const reelProps: ReelProps[] = [ * { * stepY: 15, * symbolCount: 2, * posY: -50, * }, * { * stepY: 15, * symbolCount: 3, * posY: 0, * }, * { * stepY: 15, * symbolCount: 4, * posY: 50, * }, * { * stepY: 15, * symbolCount: 3, * posY: 0, * }, * { * stepY: 15, * symbolCount: 2, * posY: -50, * } * ]; * * // Setup grid properties * const gridProps = { * offsetX: 250, * symbolsConfig: symbolsConfig, * reelProps: reelProps, * } as GridProps; * * // Setup slot machine properties * const slotMachineProps = { * spinningDuration: 1000, * startingSpeed: 50, * stoppingDelay: 150, * normalMinSpinTime: 2000, * turboMinSpinTime: 800, * jerkDistance: 50, * maskingMode: MaskingMode.Unified, * anticipationConfig: { * delayTime: 1000, * triggeringAmounts: { 0: 2 }, * animationsConfig: [ * { spritesheet: gridAssets.anticipation.name, x: -480, y: -325, scaleX: 0.6, scaleY: 0.8, animSpeed: 0.5 }, * { spritesheet: gridAssets.anticipation.name, x: -225, y: -250, scaleX: 0.6, scaleY: 1.1, animSpeed: 0.5 }, * { spritesheet: gridAssets.anticipation.name, x: 25, y: -325, scaleX: 0.6, scaleY: 0.8, animSpeed: 0.5 }, * { spritesheet: gridAssets.anticipation.name, x: 280, y: -300, scaleX: 0.6, scaleY: 0.55, animSpeed: 0.5 } * ] * } * } * * // Setup sounds * const soundProps = { * spinningSound: assetRegistry.get("sounds").spinning, * reelStopSound: assetRegistry.get("sounds").reelStop, * anticipationSound: assetRegistry.get("sounds").anticipation, * } * * // Create slot machine * const slotMachine = new SlotMachine(gridProps, slotMachineProps, soundProps, { width: "500px", height: '400px', x: "50%", y: "50%" }); * * // Spin * slotMachine.startSpin(false); * // Stop and animate symbols * slotMachine.stopSpin( * [ * [ 0, 0 ], * [ 0, 0, 0 ], * [ 0, 0, 0, 0 ], * [ 0, 0, 0 ], * [ 0, 0 ] * ], false * ).then((slotMachine) => { * slotMachine.winController.animateWinLines( * [ [ * [ 0, 1 ], * [ 0, 1, 0 ], * [ 0, 0, 1, 0 ], * [ 0, 0, 0 ], * [ 0, 0 ] * ], [ * [ 0, 0 ], * [ 1, 0, 0 ], * [ 0, 1, 0, 0 ], * [ 0, 0, 1 ], * [ 0, 0 ] * ] ], * true, 1500 * ).then(() => { * console.log("Win animation finished"); * }); * console.log("Spin stopped"); * }).catch(err => { * console.log(err); * }) * * ``` */ constructor( private _gridProps: GridProps, private _slotMachineProps: SlotMachineProps, soundProps: SoundProps, style?: Style ) { super({style}); this.createGrids(); this.createMask(this._slotMachineProps.maskingMode); let anticipationAnimationManager: AnticipationAnimationManager | undefined = undefined; if (_slotMachineProps.anticipationConfig) { anticipationAnimationManager = new AnticipationAnimationManager( this, _slotMachineProps.anticipationConfig.animationsConfig, this._staticGrid.reels.length ); } this._soundController = new SoundController(soundProps); this._winController = new WinController(this); this._spinController = new SpinController( this, this._dynamicGridCollection, _gridProps, _slotMachineProps, anticipationAnimationManager ); } /** * Creates the static grid and the dynamic grid collection */ private createGrids() { const dynamicReelProps = this._gridProps.reelProps.map(reel => { return { ...reel, symbolCount: reel.symbolCount * 4 } }); const dynamicGridProps = { ...this._gridProps, reelProps: dynamicReelProps }; this._staticGrid = new Grid(Reel, this._gridProps); this._staticGrid.pivot.x = this._staticGrid.getBounds().width / 2; this._dynamicGridCollection = new DynamicGridCollection(this._staticGrid, dynamicGridProps); // this._dynamicGridCollection.alpha = .5; this._dynamicGridCollection.pivot.x = this._dynamicGridCollection.getBounds().width / 2; this.addChild(this._staticGrid, this._dynamicGridCollection); } /** * Create a mask for the slot machine so that only the static grid is visible * @param mode can be "none", "simplified", "unified" or "unique". * * "none" means no mask will be created. Useful for debugging. * * "simplified" will create a simple rectangle mask using the static grids bounds. * * "unified" (default) will draw a mask around the single edges of the static grid. Good for most cases. * * "unique" will mask every single reel in the static grid. Good for edge cases. */ private createMask(mode: MaskingMode = MaskingMode.Unified) { if (mode === MaskingMode.None) { return; } const maskingMethod = MaskFactory.create(mode); const mask = maskingMethod.createMask(this._staticGrid); this.addChild(mask); this._dynamicGridCollection.mask = mask; } /** * Start the spinning. * @param turbo Switches turbo mode on or off. In turbo mode the spinning will stop faster. * @returns True if the spinning started, false if the spinning is already running. */ public startSpin(turbo = false) { if (this._spinController.state === SlotMachineState.Ready) { this._spinController.state = SlotMachineState.Starting; this._spinController.toggleTurboMode(turbo); this._spinController.startStoppingTimer(); this._spinController.startSpinningColumns(); return true; } return false; } /** * Stop the spinning. * @param finalSymbolsIDs IDs of the symbols that will be shown on the reels after stopping, must be the same format as the reels in the grid. Must be the same as in the given symbols config. * @param forceStop Use this to force stop the spinning. Meaning the spinning will stop instantly and the final symbols will be shown. * @returns Promise that resolves when the spinning is stopped. Can be used in combination with the {@link WinController}, you can get this slot machine object's win controller by using the provided public getter. */ public stopSpin = (finalSymbolsIDs: number[][], forceStop = false): Promise => new Promise((resolve, reject) => { try { this._finalSymbols = finalSymbolsIDs.map(symbolIDs => { return symbolIDs.map(symbolID => { return this.convertSymbolIDToSymbolData(symbolID); }); }); this._staticGrid.setSymbols(this._finalSymbols); const onCompleted = () => resolve(this); if (forceStop) { this._spinController.forceStop(onCompleted); } else { this._spinController.normalStop(onCompleted); } } catch (error) { reject(error); } }); private convertSymbolIDToSymbolData(symbolID: number): SymbolData { for (let symbol of Object.values(this._gridProps.symbolsConfig)) { if (symbol.id === symbolID) { return symbol; } } throw new Error("Symbol ID not found: " + symbolID); } }