@slot-engine/core 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1073 -857
- package/dist/index.d.ts +1073 -857
- package/dist/index.js +2239 -1865
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2233 -1857
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -6
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts","../src/GameConfig.ts","../src/GameMode.ts","../src/GameSymbol.ts","../src/ReelGenerator.ts","../utils.ts","../src/ResultSet.ts","../src/Wallet.ts","../src/Book.ts","../src/GameState.ts","../src/Board.ts","../src/WinType.ts","../src/winTypes/LinesWinType.ts","../src/winTypes/ClusterWinType.ts","../src/winTypes/ManywaysWinType.ts","../src/optimizer/OptimizationConditions.ts","../src/optimizer/OptimizationScaling.ts","../src/optimizer/OptimizationParameters.ts","../src/Simulation.ts","../src/analysis/index.ts","../src/analysis/utils.ts","../src/utils/math-config.ts","../src/utils/setup-file.ts","../src/optimizer/index.ts","../src/SlotGame.ts"],"sourcesContent":["import { GameConfig } from \"./src/GameConfig\"\nimport { type SimulationContext } from \"./src/Simulation\"\nimport { GameMode, GameModeName } from \"./src/GameMode\"\nimport { GameSymbol } from \"./src/GameSymbol\"\nimport { ReelGenerator, Reels } from \"./src/ReelGenerator\"\nimport { ResultSet, EvaluationContext } from \"./src/ResultSet\"\nimport { StandaloneBoard } from \"./src/Board\"\nimport { WinType } from \"./src/WinType\"\nimport { LinesWinType } from \"./src/winTypes/LinesWinType\"\nimport { ClusterWinType } from \"./src/winTypes/ClusterWinType\"\nimport { ManywaysWinType } from \"./src/winTypes/ManywaysWinType\"\nimport { OptimizationConditions } from \"./src/optimizer/OptimizationConditions\"\nimport { OptimizationScaling } from \"./src/optimizer/OptimizationScaling\"\nimport { OptimizationParameters } from \"./src/optimizer/OptimizationParameters\"\nimport { SlotGame } from \"./src/SlotGame\"\nexport { weightedRandom } from \"./utils\"\n\nexport {\n StandaloneBoard,\n GameConfig,\n GameMode,\n GameSymbol,\n WinType,\n LinesWinType,\n ClusterWinType,\n ManywaysWinType,\n OptimizationConditions,\n OptimizationScaling,\n OptimizationParameters,\n ReelGenerator,\n ResultSet,\n type Reels,\n}\n\n/**\n * @internal\n */\nexport interface CommonGameOptions<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> {\n id: string\n name: string\n gameModes: Record<GameModeName, GameMode>\n symbols: TSymbols\n scatterToFreespins: Record<string, Record<number, number>>\n padSymbols?: number\n maxWinX: number\n hooks: GameHooks<TGameModes, TSymbols, TUserState>\n userState?: TUserState\n}\n\n/**\n * @internal\n */\nexport type AnyUserData = Record<string, any>\n\n/**\n * @internal\n */\nexport type AnyGameModes = Record<string, GameMode>\n\n/**\n * @internal\n */\nexport type AnySymbols = Record<string, GameSymbol>\n\n/**\n * @internal\n */\nexport interface GameHooks<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> {\n /**\n * This hook is called after the simulation state is prepared for a spin,\\\n * and the core is ready to handle the game flow.\n *\n * **The developer is responsible for implementing the entire game flow here, including:**\n * - Drawing the board\n * - Evaluating wins\n * - Tumbling mechanics\n * - Updating wallet\n * - Handling free spins\n * - Recording events\n * - ... and everything in between.\n *\n * You can access the config and state from the context.\n *\n * The game flow is not built into the core, because it can vary greatly between different games.\\\n * This hook provides the flexibility to implement any game flow you need.\n */\n onHandleGameFlow: (ctx: SimulationContext<TGameModes, TSymbols, TUserState>) => void\n /**\n * This hook is called whenever a simulation is accepted, i.e. when the criteria of the current ResultSet is met.\n */\n onSimulationAccepted?: (\n ctx: SimulationContext<TGameModes, TSymbols, TUserState>,\n ) => void\n}\n\nexport type InferUserState<T> = T extends SlotGame<infer U> ? U : never\n\nexport type HookContext<T> =\n T extends SlotGame<infer G, infer S, infer U> ? SimulationContext<G, S, U> : never\n\nexport { type EvaluationContext }\n\nexport type InferGameType<\n TGameModes extends AnyGameModes,\n TSymbols extends AnySymbols,\n TUserState extends AnyUserData,\n> = SlotGame<TGameModes, TSymbols, TUserState>\n\nexport interface CreateSlotGameOpts<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> {\n /**\n * The unique identifier of the game, used for configuration and identification.\n */\n id: CommonGameOptions[\"id\"]\n /**\n * The name of the game, used for display purposes.\n */\n name: CommonGameOptions[\"name\"]\n /**\n * A GameMode is the core structure of a slot, defining the board,\\\n * bet cost, win type, and other properties.\n */\n gameModes: TGameModes\n /**\n * A list of all symbols that will appear on the reels.\n */\n symbols: TSymbols\n /**\n * A mapping from spin type to scatter counts to the number of free spins awarded.\n *\n * @example\n * ```ts\n * scatterToFreespins: {\n * [GameConfig.SPIN_TYPE.BASE_GAME]: {\n * 3: 10,\n * 4: 12,\n * 5: 15,\n * },\n * [GameConfig.SPIN_TYPE.FREE_SPINS]: {\n * 3: 6,\n * 4: 8,\n * 5: 10,\n * },\n * },\n * ```\n */\n scatterToFreespins: CommonGameOptions[\"scatterToFreespins\"]\n /**\n * If set, this will pad the board with symbols on the top and bottom of the reels.\\\n * Useful for teasing symbols right above or below the active board.\n *\n * Default: 1\n */\n padSymbols?: CommonGameOptions[\"padSymbols\"]\n /**\n * The maximum win multiplier of the game, e.g. 5000 for a 5000x max win.\n */\n maxWinX: CommonGameOptions[\"maxWinX\"]\n /**\n * Custom additional state that can be used in game flow logic.\n */\n userState?: TUserState\n /**\n * Hooks are used to inject custom logic at specific points in the game flow.\\\n * Some required hooks must be implemented for certain features to work.\n */\n hooks: CommonGameOptions<TGameModes, TSymbols, TUserState>[\"hooks\"]\n}\n\nexport function createSlotGame<TGame>(\n opts: TGame extends InferGameType<infer G, infer S, infer U>\n ? CreateSlotGameOpts<G, S, U>\n : never,\n) {\n return new SlotGame(opts) as TGame\n}\n\nexport const defineUserState = <TUserState extends AnyUserData>(data: TUserState) => data\n\nexport const defineSymbols = <TSymbols extends AnySymbols>(symbols: TSymbols) => symbols\n\nexport const defineGameModes = <TGameModes extends AnyGameModes>(gameModes: TGameModes) =>\n gameModes\n\nexport const defineReelSets = <TSymbols extends AnySymbols>(reelSets: ReelGenerator[]) =>\n reelSets\n","import assert from \"assert\"\nimport {\n AnyGameModes,\n AnySymbols,\n AnyUserData,\n CommonGameOptions,\n GameHooks,\n} from \"../index\"\nimport { GameMode, GameModeName } from \"./GameMode\"\n\n/**\n * Static configuration for a slot game.\\\n * This shouldn't change during gameplay.\n */\nexport class GameConfig<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> {\n readonly config: {\n readonly id: string\n readonly name: string\n readonly gameModes: Record<GameModeName, GameMode>\n readonly symbols: Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>\n readonly padSymbols?: number\n readonly scatterToFreespins: Record<string, Record<number, number>>\n readonly anticipationTriggers: Record<SpinType, number>\n readonly maxWinX: number\n readonly outputDir: string\n readonly hooks: GameHooks<TGameModes, TSymbols, TUserState>\n readonly userState?: TUserState\n }\n\n constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>) {\n this.config = {\n id: opts.id,\n name: opts.name,\n gameModes: opts.gameModes,\n symbols: new Map(),\n padSymbols: opts.padSymbols || 0,\n scatterToFreespins: opts.scatterToFreespins,\n anticipationTriggers: {\n [GameConfig.SPIN_TYPE.BASE_GAME]: getAnticipationTrigger(\n GameConfig.SPIN_TYPE.BASE_GAME,\n ),\n [GameConfig.SPIN_TYPE.FREE_SPINS]: getAnticipationTrigger(\n GameConfig.SPIN_TYPE.FREE_SPINS,\n ),\n },\n maxWinX: opts.maxWinX,\n hooks: opts.hooks,\n userState: opts.userState,\n outputDir: \"__build__\",\n }\n\n for (const [key, value] of Object.entries(opts.symbols)) {\n assert(\n value.id === key,\n `Symbol key \"${key}\" does not match symbol id \"${value.id}\"`,\n )\n this.config.symbols.set(key, value as TSymbols[keyof TSymbols])\n }\n\n function getAnticipationTrigger(spinType: string) {\n return Math.min(...Object.keys(opts.scatterToFreespins[spinType]!).map(Number)) - 1\n }\n }\n\n /**\n * Generates reelset CSV files for all game modes.\n */\n generateReelsetFiles() {\n for (const mode of Object.values(this.config.gameModes)) {\n if (mode.reelSets && mode.reelSets.length > 0) {\n for (const reelGenerator of Object.values(mode.reelSets)) {\n reelGenerator.associatedGameModeName = mode.name\n reelGenerator.generateReels(this)\n }\n } else {\n throw new Error(\n `Game mode \"${mode.name}\" has no reel sets defined. Cannot generate reelset files.`,\n )\n }\n }\n }\n\n /**\n * Retrieves a reel set by its ID within a specific game mode.\n */\n getReelsetById(gameMode: string, id: string) {\n const reelSet = this.config.gameModes[gameMode]!.reelSets.find((rs) => rs.id === id)\n if (!reelSet) {\n throw new Error(\n `Reel set with id \"${id}\" not found in game mode \"${gameMode}\". Available reel sets: ${this.config.gameModes[\n gameMode\n ]!.reelSets.map((rs) => rs.id).join(\", \")}`,\n )\n }\n return reelSet.reels\n }\n\n /**\n * Retrieves the number of free spins awarded for a given spin type and scatter count.\n */\n getFreeSpinsForScatters(spinType: SpinType, scatterCount: number) {\n const freespinsConfig = this.config.scatterToFreespins[spinType]\n if (!freespinsConfig) {\n throw new Error(\n `No free spins configuration found for spin type \"${spinType}\". Please check your game configuration.`,\n )\n }\n return freespinsConfig[scatterCount] || 0\n }\n\n /**\n * Retrieves a result set by its criteria within a specific game mode.\n */\n getResultSetByCriteria(mode: string, criteria: string) {\n const gameMode = this.config.gameModes[mode]\n if (!gameMode) {\n throw new Error(`Game mode \"${mode}\" not found in game config.`)\n }\n const resultSet = gameMode.resultSets.find((rs) => rs.criteria === criteria)\n if (!resultSet) {\n throw new Error(\n `Criteria \"${criteria}\" not found in game mode \"${mode}\". Available criteria: ${gameMode.resultSets\n .map((rs) => rs.criteria)\n .join(\", \")}`,\n )\n }\n return resultSet\n }\n\n /**\n * Returns all configured symbols as an array.\n */\n getSymbolArray() {\n return Array.from(this.config.symbols).map(([n, v]) => v)\n }\n\n static SPIN_TYPE = {\n BASE_GAME: \"basegame\",\n FREE_SPINS: \"freespins\",\n } as const\n}\n\nexport type SpinType = (typeof GameConfig.SPIN_TYPE)[keyof typeof GameConfig.SPIN_TYPE]\n\nexport type AnyGameConfig = GameConfig<any, any, any>\n","import assert from \"assert\"\nimport { type ReelGenerator } from \"./ReelGenerator\"\nimport { type ResultSet } from \"./ResultSet\"\n\nexport class GameMode {\n name: GameModeName\n reelsAmount: number\n symbolsPerReel: number[]\n cost: number\n rtp: number\n reelSets: ReelGenerator[]\n resultSets: ResultSet<any>[]\n isBonusBuy: boolean\n\n constructor(opts: GameModeOpts) {\n this.name = opts.name\n this.reelsAmount = opts.reelsAmount\n this.symbolsPerReel = opts.symbolsPerReel\n this.cost = opts.cost\n this.rtp = opts.rtp\n this.reelSets = opts.reelSets\n this.resultSets = opts.resultSets\n this.isBonusBuy = opts.isBonusBuy\n\n assert(this.rtp >= 0.9 && this.rtp <= 0.99, \"RTP must be between 0.9 and 0.99\")\n\n assert(\n this.symbolsPerReel.length === this.reelsAmount,\n \"symbolsPerReel length must match reelsAmount.\",\n )\n\n assert(this.reelSets.length > 0, \"GameMode must have at least one ReelSet defined.\")\n }\n}\n\nexport interface GameModeOpts {\n /**\n * Name of the game mode.\n */\n name: GameModeName\n /**\n * Number of reels the board has.\n */\n reelsAmount: number\n /**\n * How many symbols each reel has. Array length must match `reelsAmount`.\\\n * The number at an array index represents the number of symbols on that reel.\n */\n symbolsPerReel: number[]\n /**\n * Cost of the game mode, multiplied by the base bet.\n */\n cost: number\n /**\n * The target RTP of the game.\n */\n rtp: number\n /**\n * Defines and generates all reels for the game.\\\n * Which reels are used in a spin is determined by the ResultSet of the current game mode.\n *\n * It is common to have one reel set for the base game and another for free spins.\\\n * Each `ResultSet` can then set the weights of these reel sets to control which\\\n * reel set is used for a specific criteria.\n *\n * The generator can be adjusted to match the reels to your games needs.\n */\n reelSets: ReelGenerator[]\n /**\n * A ResultSet defines how often a specific outcome should be generated.\\\n * For example, a ResultSet can be used to force a specific ratio of max wins\\\n * in the simulations to ensure there are different frontend representations.\n */\n resultSets: ResultSet<any>[]\n /**\n * Whether this game mode is a bonus buy.\n */\n isBonusBuy: boolean\n}\n\nexport type GameModeName =\n | \"base\"\n | \"base-extra-chance-2x\"\n | \"base-extra-chance-10x\"\n | \"bonus\"\n | string\n","export class GameSymbol {\n id: string\n pays?: Record<number, number>\n properties: Map<string, any>\n\n constructor(opts: GameSymbolOpts) {\n this.id = opts.id\n this.pays = opts.pays\n this.properties = new Map<string, any>(Object.entries(opts.properties || {}))\n\n if (this.pays && Object.keys(this.pays).length === 0) {\n throw new Error(`GameSymbol \"${this.id}\" must have pays defined.`)\n }\n }\n\n /**\n * Compares this symbol to another symbol or a set of properties.\n */\n compare(symbolOrProperties?: GameSymbol | Record<string, any>) {\n if (!symbolOrProperties) {\n console.warn(\"No symbol or properties provided for comparison.\")\n return false\n }\n if (symbolOrProperties instanceof GameSymbol) {\n return this.id === symbolOrProperties.id\n } else {\n for (const [key, value] of Object.entries(symbolOrProperties)) {\n if (!this.properties.has(key) || this.properties.get(key) !== value) {\n return false\n }\n }\n return true\n }\n }\n}\n\nexport interface GameSymbolOpts {\n /**\n * Unique identifier for the symbol, e.g. \"W\", \"H1\", \"L5\", etc.\n */\n id: string\n /**\n * Paytable for the symbol, where the key is the number of symbols and the value is the payout multiplier.\n */\n pays?: Record<number, number>\n /**\n * Additional properties for the symbol, e.g. `multiplier` or `isWild`.\n *\n * Properties can help identify special symbols.\n * \n * @example\n * If your game has a \"normal\" scatter and a \"super\" scatter, you can define them like this:\n * \n * ```ts\n * properties: {\n * isScatter: true,\n * }\n * ```\n */\n properties?: Record<string, any>\n}\n","import fs from \"fs\"\nimport path from \"path\"\nimport { AnyGameConfig, GameConfig } from \"./GameConfig\"\nimport { GameSymbol } from \"./GameSymbol\"\nimport { RandomNumberGenerator, weightedRandom } from \"../utils\"\nimport { isMainThread } from \"worker_threads\"\n\n/**\n * This class is responsible for generating reel sets for slot games based on specified configurations.\n *\n * **While it offers a high degree of customization, some configurations may lead to unsolvable scenarios.**\n *\n * If the reel generator is unable to fulfill niche constraints,\\\n * you might need to adjust your configuration, or edit the generated reels manually.\\\n * Setting a different seed may also help.\n */\nexport class ReelGenerator {\n id: string\n associatedGameModeName: string = \"\"\n protected readonly symbolWeights: Map<string, number> = new Map()\n protected readonly rowsAmount: number\n reels: Reels = []\n protected limitSymbolsToReels?: Record<string, number[]>\n protected readonly spaceBetweenSameSymbols?: number | Record<string, number>\n protected readonly spaceBetweenSymbols?: Record<string, Record<string, number>>\n protected readonly preferStackedSymbols?: number\n protected readonly symbolStacks?: Record<\n string,\n {\n chance: number | Record<string, number>\n min?: number | Record<string, number>\n max?: number | Record<string, number>\n }\n >\n protected readonly symbolQuotas?: Record<string, number | Record<string, number>>\n csvPath: string = \"\"\n overrideExisting: boolean\n rng: RandomNumberGenerator\n\n constructor(opts: ReelGeneratorOpts) {\n this.id = opts.id\n this.symbolWeights = new Map(Object.entries(opts.symbolWeights))\n this.rowsAmount = opts.rowsAmount || 250\n\n if (opts.limitSymbolsToReels) this.limitSymbolsToReels = opts.limitSymbolsToReels\n\n this.overrideExisting = opts.overrideExisting || false\n this.spaceBetweenSameSymbols = opts.spaceBetweenSameSymbols\n this.spaceBetweenSymbols = opts.spaceBetweenSymbols\n this.preferStackedSymbols = opts.preferStackedSymbols\n this.symbolStacks = opts.symbolStacks\n this.symbolQuotas = opts.symbolQuotas\n\n if (\n (typeof this.spaceBetweenSameSymbols == \"number\" &&\n (this.spaceBetweenSameSymbols < 1 || this.spaceBetweenSameSymbols > 8)) ||\n (typeof this.spaceBetweenSameSymbols == \"object\" &&\n Object.values(this.spaceBetweenSameSymbols).some((v) => v < 1 || v > 8))\n ) {\n throw new Error(\n `spaceBetweenSameSymbols must be between 1 and 8, got ${this.spaceBetweenSameSymbols}.`,\n )\n }\n\n if (\n Object.values(this.spaceBetweenSymbols || {}).some((o) =>\n Object.values(o).some((v) => v < 1 || v > 8),\n )\n ) {\n throw new Error(\n `spaceBetweenSymbols must be between 1 and 8, got ${this.spaceBetweenSymbols}.`,\n )\n }\n\n if (\n this.preferStackedSymbols &&\n (this.preferStackedSymbols < 0 || this.preferStackedSymbols > 100)\n ) {\n throw new Error(\n `preferStackedSymbols must be between 0 and 100, got ${this.preferStackedSymbols}.`,\n )\n }\n\n this.rng = new RandomNumberGenerator()\n this.rng.setSeed(opts.seed ?? 0)\n }\n\n private validateConfig({ config }: GameConfig) {\n this.symbolWeights.forEach((_, symbol) => {\n if (!config.symbols.has(symbol)) {\n throw new Error(\n `Symbol \"${symbol}\" of the reel generator ${this.id} for mode ${this.associatedGameModeName} is not defined in the game config`,\n )\n }\n })\n\n if (this.limitSymbolsToReels && Object.keys(this.limitSymbolsToReels).length == 0) {\n this.limitSymbolsToReels = undefined\n }\n }\n\n private isSymbolAllowedOnReel(symbolId: string, reelIdx: number) {\n if (!this.limitSymbolsToReels) return true\n const allowedReels = this.limitSymbolsToReels[symbolId]\n if (!allowedReels || allowedReels.length === 0) return true\n return allowedReels.includes(reelIdx)\n }\n\n private resolveStacking(symbolId: string, reelIdx: number) {\n const cfg = this.symbolStacks?.[symbolId]\n if (!cfg) return null\n\n const STACKING_MIN = 1\n const STACKING_MAX = 4\n\n const chance =\n typeof cfg.chance === \"number\" ? cfg.chance : (cfg.chance?.[reelIdx] ?? 0)\n if (chance <= 0) return null\n\n let min = typeof cfg.min === \"number\" ? cfg.min : (cfg.min?.[reelIdx] ?? STACKING_MIN)\n let max = typeof cfg.max === \"number\" ? cfg.max : (cfg.max?.[reelIdx] ?? STACKING_MAX)\n\n return { chance, min, max }\n }\n\n private tryPlaceStack(\n reel: Array<GameSymbol | null>,\n gameConf: GameConfig,\n reelIdx: number,\n symbolId: string,\n startIndex: number,\n maxStack: number,\n ) {\n if (!this.isSymbolAllowedOnReel(symbolId, reelIdx)) return 0\n\n let canPlace = 0\n for (let j = 0; j < maxStack; j++) {\n const idx = (startIndex + j) % this.rowsAmount\n if (reel[idx] !== null) break\n canPlace++\n }\n if (canPlace === 0) return 0\n\n const symObj = gameConf.config.symbols.get(symbolId)\n if (!symObj) {\n throw new Error(\n `Symbol with id \"${symbolId}\" not found in the game config symbols map.`,\n )\n }\n\n for (let j = 0; j < canPlace; j++) {\n const idx = (startIndex + j) % reel.length\n reel[idx] = symObj\n }\n return canPlace\n }\n\n /**\n * Checks if a symbol can be placed at the target index without violating spacing rules.\n */\n private violatesSpacing(\n reel: Array<GameSymbol | null>,\n symbolId: string,\n targetIndex: number,\n ) {\n const circDist = (a: number, b: number) => {\n const diff = Math.abs(a - b)\n return Math.min(diff, this.rowsAmount - diff)\n }\n\n const spacingType = this.spaceBetweenSameSymbols ?? undefined\n const sameSpacing =\n typeof spacingType === \"number\" ? spacingType : (spacingType?.[symbolId] ?? 0)\n\n for (let i = 0; i <= reel.length; i++) {\n const placed = reel[i]\n if (!placed) continue\n\n const dist = circDist(targetIndex, i)\n\n // Same symbol spacing\n if (sameSpacing >= 1 && placed.id === symbolId) {\n if (dist <= sameSpacing) return true\n }\n\n // Cross-symbol spacing\n if (this.spaceBetweenSymbols) {\n const forward = this.spaceBetweenSymbols[symbolId]?.[placed.id] ?? 0\n if (forward >= 1 && dist <= forward) return true\n\n const reverse = this.spaceBetweenSymbols[placed.id]?.[symbolId] ?? 0\n if (reverse >= 1 && dist <= reverse) return true\n }\n }\n\n return false\n }\n\n generateReels(gameConf: AnyGameConfig) {\n this.validateConfig(gameConf)\n\n const gameMode = gameConf.config.gameModes[this.associatedGameModeName]\n\n if (!gameMode) {\n throw new Error(\n `Error generating reels for game mode \"${this.associatedGameModeName}\". It's not defined in the game config.`,\n )\n }\n\n const filePath = path.join(\n gameConf.config.outputDir,\n `reels_${this.associatedGameModeName}-${this.id}.csv`,\n )\n this.csvPath = filePath\n\n const exists = fs.existsSync(filePath)\n\n if (exists && !this.overrideExisting) {\n this.reels = this.parseReelsetCSV(filePath, gameConf)\n return\n }\n\n if (!exists && this.symbolWeights.size === 0) {\n throw new Error(\n `Cannot generate reels for generator \"${this.id}\" of mode \"${this.associatedGameModeName}\" because the symbol weights are empty.`,\n )\n }\n\n const reelsAmount = gameMode.reelsAmount\n const weightsObj = Object.fromEntries(this.symbolWeights)\n\n // Generate initial reels with random symbols\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n const reel: Array<GameSymbol | null> = new Array(this.rowsAmount).fill(null)\n\n const reelQuotas: Record<string, number> = {}\n const quotaCounts: Record<string, number> = {}\n let totalReelsQuota = 0\n\n // Get quotas for this reel, across all symbols\n for (const [sym, quotaConf] of Object.entries(this.symbolQuotas || {})) {\n const q = typeof quotaConf === \"number\" ? quotaConf : quotaConf[ridx]\n if (!q) continue\n reelQuotas[sym] = q\n totalReelsQuota += q\n }\n\n if (totalReelsQuota > 100) {\n throw new Error(\n `Total symbol quotas for reel ${ridx} exceed 100%. Adjust your configuration on ReelGenerator \"${this.id}\".`,\n )\n }\n\n if (totalReelsQuota > 0) {\n for (const [sym, quota] of Object.entries(reelQuotas)) {\n const quotaCount = Math.max(1, Math.floor((this.rowsAmount * quota) / 100))\n quotaCounts[sym] = quotaCount\n }\n }\n\n // Place required quotas first (use stacking over spacing, if configured)\n for (const [sym, targetCount] of Object.entries(quotaCounts)) {\n let remaining = targetCount\n let attempts = 0\n\n while (remaining > 0) {\n if (attempts++ > this.rowsAmount * 10) {\n throw new Error(\n `Failed to place ${targetCount} of symbol ${sym} on reel ${ridx} (likely spacing/stacking too strict).`,\n )\n }\n\n const pos = Math.round(this.rng.randomFloat(0, this.rowsAmount - 1))\n const stackCfg = this.resolveStacking(sym, ridx)\n let placed = 0\n\n // Try to place a symbol stack first, if configured\n if (stackCfg && Math.round(this.rng.randomFloat(1, 100)) <= stackCfg.chance) {\n const stackSize = Math.max(\n 0,\n Math.round(this.rng.randomFloat(stackCfg.min, stackCfg.max)),\n )\n const toPlace = Math.min(stackSize, remaining)\n placed = this.tryPlaceStack(reel, gameConf, ridx, sym, pos, toPlace)\n }\n\n // Not enough space, fall back to placing single symbols\n if (\n placed === 0 &&\n reel[pos] === null &&\n this.isSymbolAllowedOnReel(sym, ridx) &&\n !this.violatesSpacing(reel, sym, pos)\n ) {\n reel[pos] = gameConf.config.symbols.get(sym)!\n placed = 1\n }\n\n remaining -= placed\n }\n }\n\n // Fill the rest of the reel randomly\n for (let r = 0; r < this.rowsAmount; r++) {\n if (reel[r] !== null) continue // already placed quota\n\n let chosenSymbolId = weightedRandom(weightsObj, this.rng)\n\n // If symbolStacks is NOT configured for the next choice, allow \"preferStackedSymbols\" fallback\n const nextHasStackCfg = !!this.resolveStacking(chosenSymbolId, ridx)\n if (!nextHasStackCfg && this.preferStackedSymbols && reel.length > 0) {\n const prevSymbol = r - 1 >= 0 ? reel[r - 1] : reel[reel.length - 1]\n if (\n prevSymbol &&\n Math.round(this.rng.randomFloat(1, 100)) <= this.preferStackedSymbols &&\n (!this.spaceBetweenSameSymbols ||\n !this.violatesSpacing(reel, prevSymbol.id, r))\n ) {\n chosenSymbolId = prevSymbol.id\n }\n }\n\n // Check for stacking preference\n if (this.preferStackedSymbols && reel.length > 0) {\n const prevSymbol = r - 1 >= 0 ? reel[r - 1] : reel[reel.length - 1]\n\n if (\n Math.round(this.rng.randomFloat(1, 100)) <= this.preferStackedSymbols &&\n (!this.spaceBetweenSameSymbols ||\n !this.violatesSpacing(reel, prevSymbol!.id, r))\n ) {\n chosenSymbolId = prevSymbol!.id\n }\n }\n\n // If symbol has stack config, try to place a stack (ignore spacing)\n const stackCfg = this.resolveStacking(chosenSymbolId, ridx)\n if (stackCfg && this.isSymbolAllowedOnReel(chosenSymbolId, ridx)) {\n const roll = Math.round(this.rng.randomFloat(1, 100))\n if (roll <= stackCfg.chance) {\n const desiredSize = Math.max(\n 1,\n Math.round(this.rng.randomFloat(stackCfg.min, stackCfg.max)),\n )\n const placed = this.tryPlaceStack(\n reel,\n gameConf,\n ridx,\n chosenSymbolId,\n r,\n desiredSize,\n )\n if (placed > 0) {\n // advance loop to skip the cells we just filled on this side of the boundary\n // (wrapped cells at the start are already filled and will be skipped when encountered)\n r += placed - 1\n continue\n }\n }\n }\n\n let tries = 0\n const maxTries = 2500\n\n while (\n !this.isSymbolAllowedOnReel(chosenSymbolId, ridx) ||\n this.violatesSpacing(reel, chosenSymbolId, r)\n ) {\n if (++tries > maxTries) {\n throw new Error(\n [\n `Failed to place a symbol on reel ${ridx} at position ${r} after ${maxTries} attempts.\\n`,\n \"Try to change the seed or adjust your configuration.\\n\",\n ].join(\" \"),\n )\n }\n chosenSymbolId = weightedRandom(weightsObj, this.rng)\n\n const hasStackCfg = !!this.resolveStacking(chosenSymbolId, ridx)\n if (!hasStackCfg && this.preferStackedSymbols && reel.length > 0) {\n const prevSymbol = r - 1 >= 0 ? reel[r - 1] : reel[reel.length - 1]\n if (\n prevSymbol &&\n Math.round(this.rng.randomFloat(1, 100)) <= this.preferStackedSymbols &&\n (!this.spaceBetweenSameSymbols ||\n !this.violatesSpacing(reel, prevSymbol.id, r))\n ) {\n chosenSymbolId = prevSymbol.id\n }\n }\n }\n\n const symbol = gameConf.config.symbols.get(chosenSymbolId)\n\n if (!symbol) {\n throw new Error(\n `Symbol with id \"${chosenSymbolId}\" not found in the game config symbols map.`,\n )\n }\n\n reel[r] = symbol\n }\n\n if (reel.some((s) => s === null)) {\n throw new Error(`Reel ${ridx} has unfilled positions after generation.`)\n }\n\n this.reels.push(reel as GameSymbol[])\n }\n\n // Write the CSV\n const csvRows: string[][] = Array.from({ length: this.rowsAmount }, () =>\n Array.from({ length: reelsAmount }, () => \"\"),\n )\n\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n for (let r = 0; r < this.rowsAmount; r++) {\n csvRows[r]![ridx] = this.reels[ridx]![r]!.id\n }\n }\n\n const csvString = csvRows.map((row) => row.join(\",\")).join(\"\\n\")\n\n if (isMainThread) {\n fs.writeFileSync(filePath, csvString)\n\n this.reels = this.parseReelsetCSV(filePath, gameConf)\n console.log(\n `Generated reelset ${this.id} for game mode ${this.associatedGameModeName}`,\n )\n }\n }\n\n /**\n * Reads a reelset CSV file and returns the reels as arrays of GameSymbols.\n */\n parseReelsetCSV(reelSetPath: string, { config }: GameConfig) {\n const csvData = fs.readFileSync(reelSetPath, \"utf8\")\n const rows = csvData.split(\"\\n\").filter((line) => line.trim() !== \"\")\n const reels: Reels = Array.from(\n { length: config.gameModes[this.associatedGameModeName]!.reelsAmount },\n () => [],\n )\n rows.forEach((row) => {\n const symsInRow = row.split(\",\").map((symbolId) => {\n const symbol = config.symbols.get(symbolId.trim())\n if (!symbol) {\n throw new Error(`Symbol with id \"${symbolId}\" not found in game config.`)\n }\n return symbol\n })\n symsInRow.forEach((symbol, ridx) => {\n reels[ridx]!.push(symbol)\n })\n })\n\n const reelLengths = reels.map((r) => r.length)\n const uniqueLengths = new Set(reelLengths)\n if (uniqueLengths.size > 1) {\n throw new Error(\n `Inconsistent reel lengths in reelset CSV at ${reelSetPath}: ${[\n ...uniqueLengths,\n ].join(\", \")}`,\n )\n }\n\n return reels\n }\n}\n\ninterface ReelGeneratorOpts {\n /**\n * The unique identifier of the reel generator.\\\n * Must be unique per game mode.\n */\n id: string\n /**\n * The weights of the symbols in the reelset.\\\n * This is a mapping of symbol IDs to their respective weights.\n */\n symbolWeights: Record<string, number>\n /**\n * The number of rows in the reelset.\\\n * Default is 250, but can be adjusted as needed.\n */\n rowsAmount?: number\n /**\n * Prevent the same symbol from appearing directly above or below itself.\\\n * This can be a single number for all symbols, or a mapping of symbol IDs to\n * their respective spacing values.\n *\n * Must be 1 or higher, if set.\n *\n * **This is overridden by `symbolStacks`**\n */\n spaceBetweenSameSymbols?: number | Record<string, number>\n /**\n * Prevents specific symbols from appearing within a certain distance of each other.\n *\n * Useful for preventing scatter and super scatter symbols from appearing too close to each other.\n *\n * **This is overridden by `symbolStacks`**\n */\n spaceBetweenSymbols?: Record<string, Record<string, number>>\n /**\n * A percentage value 0-100 that indicates the likelihood of a symbol being stacked.\\\n * A value of 0 means no stacked symbols, while 100 means all symbols are stacked.\n *\n * This is only a preference. Symbols may still not be stacked if\\\n * other restrictions (like `spaceBetweenSameSymbols`) prevent it.\n *\n * **This is overridden by `symbolStacks`**\n */\n preferStackedSymbols?: number\n /**\n * A mapping of symbols to their respective advanced stacking configuration.\n *\n * @example\n * ```ts\n * symbolStacks: {\n * \"W\": {\n * chance: { \"1\": 20, \"2\": 20, \"3\": 20, \"4\": 20 }, // 20% chance to be stacked on reels 2-5\n * min: 2, // At least 2 wilds in a stack\n * max: 4, // At most 4 wilds in a stack\n * }\n * }\n * ```\n */\n symbolStacks?: Record<\n string,\n {\n chance: number | Record<string, number>\n min?: number | Record<string, number>\n max?: number | Record<string, number>\n }\n >\n /**\n * Configures symbols to be limited to specific reels.\\\n * For example, you could configure Scatters to appear only on reels 1, 3 and 5.\n *\n * @example\n * ```ts\n * limitSymbolsToReels: {\n * \"S\": [0, 2, 4], // Remember that reels are 0-indexed.\n * }\n * ```\n */\n limitSymbolsToReels?: Record<string, number[]>\n /**\n * Defines optional quotas for symbols on the reels.\\\n * The quota (1-100%) defines how often a symbol should appear in the reelset, or in a specific reel.\n *\n * This is particularly useful for controlling the frequency of special symbols like scatters or wilds.\n *\n * Reels not provided for a symbol will use the weights from `symbolWeights`.\n *\n * _Any_ small quota will ensure that the symbol appears at least once on the reel.\n *\n * @example\n * ```ts\n * symbolQuotas: {\n * \"S\": 3, // 3% of symbols on each reel will be scatters\n * \"W\": { \"1\": 10, \"2\": 5, \"3\": 3, \"4\": 1 }, // Wilds will appear with different quotas on selected reels\n * }\n * ```\n */\n symbolQuotas?: Record<string, number | Record<string, number>>\n /**\n * If true, existing reels CSV files will be overwritten.\n */\n overrideExisting?: boolean\n /**\n * Optional seed for the RNG to ensure reproducible results.\n *\n * Default seed is `0`.\n *\n * Note: Seeds 0 and 1 produce the same results.\n */\n seed?: number\n}\n\nexport type Reels = GameSymbol[][]\n","import fs from \"fs\"\nimport { Board } from \"./src/Board\"\n\nexport function weightedRandom<T extends Record<string, number>>(\n weights: T,\n rng: RandomNumberGenerator,\n) {\n const totalWeight = Object.values(weights).reduce(\n (sum: number, weight) => sum + (weight as number),\n 0,\n )\n const randomValue = rng.randomFloat(0, 1) * totalWeight\n\n let cumulativeWeight = 0\n for (const [key, weight] of Object.entries(weights)) {\n cumulativeWeight += weight as number\n if (randomValue < cumulativeWeight) {\n return key\n }\n }\n\n throw new Error(\"No item selected in weighted random selection.\")\n}\n\nexport function randomItem<T>(array: T[], rng: RandomNumberGenerator) {\n if (array.length === 0) {\n throw new Error(\"Cannot select a random item from an empty array.\")\n }\n const randomIndex = Math.floor(rng.randomFloat(0, 1) * array.length)\n return array[randomIndex]!\n}\n\nexport function createDirIfNotExists(dirPath: string): void {\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true })\n }\n}\n\nexport function shuffle<T>(array: T[], rng: RandomNumberGenerator): T[] {\n const newArray = [...array]\n let currentIndex = newArray.length,\n randomIndex\n\n while (currentIndex != 0) {\n randomIndex = Math.floor(rng.randomFloat(0, 1) * currentIndex)\n currentIndex--\n ;[newArray[currentIndex] as any, newArray[randomIndex] as any] = [\n newArray[randomIndex],\n newArray[currentIndex],\n ]\n }\n\n return newArray\n}\n\nexport function writeJsonFile(filePath: string, data: object | any[]) {\n try {\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), {\n encoding: \"utf8\",\n })\n } catch (error) {\n throw new Error(`Failed to write JSON file at ${filePath}: ${error}`)\n }\n}\n\nexport function writeFile(filePath: string, data: string) {\n try {\n fs.writeFileSync(filePath, data, { encoding: \"utf8\" })\n } catch (error) {\n throw new Error(`Failed to write file at ${filePath}: ${error}`)\n }\n}\n\nexport class RandomNumberGenerator {\n mIdum: number\n mIy: number\n mIv: Array<number>\n NTAB: number\n IA: number\n IM: number\n IQ: number\n IR: number\n NDIV: number\n AM: number\n RNMX: number\n\n protected _currentSeed: number = 0\n\n constructor() {\n this.mIdum = 0\n this.mIy = 0\n this.mIv = []\n\n this.NTAB = 32\n this.IA = 16807\n this.IM = 2147483647\n this.IQ = 127773\n this.IR = 2836\n this.NDIV = 1 + (this.IM - 1) / this.NTAB\n this.AM = 1.0 / this.IM\n this.RNMX = 1.0 - 1.2e-7\n }\n\n getCurrentSeed() {\n return this._currentSeed\n }\n\n protected setCurrentSeed(seed: number) {\n this._currentSeed = seed\n }\n\n setSeed(seed: number): void {\n this.mIdum = seed\n this.setCurrentSeed(seed)\n\n if (seed >= 0) {\n this.mIdum = -seed\n }\n\n this.mIy = 0\n }\n\n setSeedIfDifferent(seed: number) {\n if (this.getCurrentSeed() !== seed) {\n this.setSeed(seed)\n }\n }\n\n generateRandomNumber(): number {\n let k: number\n let j: number\n\n if (this.mIdum <= 0 || this.mIy === 0) {\n if (-this.mIdum < 1) {\n this.mIdum = 1\n } else {\n this.mIdum = -this.mIdum\n }\n\n for (j = this.NTAB + 7; j >= 0; j -= 1) {\n k = Math.floor(this.mIdum / this.IQ)\n this.mIdum = Math.floor(this.IA * (this.mIdum - k * this.IQ) - this.IR * k)\n\n if (this.mIdum < 0) {\n this.mIdum += this.IM\n }\n\n if (j < this.NTAB) {\n this.mIv[j] = this.mIdum\n }\n }\n\n ;[this.mIy as any] = this.mIv\n }\n\n k = Math.floor(this.mIdum / this.IQ)\n this.mIdum = Math.floor(this.IA * (this.mIdum - k * this.IQ) - this.IR * k)\n\n if (this.mIdum < 0) {\n this.mIdum += this.IM\n }\n\n j = Math.floor(this.mIy / this.NDIV)\n\n this.mIy = Math.floor(this.mIv[j] as any)\n this.mIv[j] = this.mIdum\n\n return this.mIy\n }\n\n randomFloat(low: number, high: number): number {\n let float: number = this.AM * this.generateRandomNumber()\n\n if (float > this.RNMX) {\n float = this.RNMX\n }\n\n return float * (high - low) + low\n }\n}\n\n/**\n * Creates a deep copy of an object or array.\n */\nexport function copy<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj))\n}\n\n/**\n * Prints the board to the console in a readable format.\n */\nexport function printBoard({ board, config }: Board<any, any, any>) {\n const fullBoard = board.reels.map((reel, ridx) => {\n if (config.padSymbols && config.padSymbols > 0) {\n return [...board.paddingTop[ridx]!, ...reel, ...board.paddingBottom[ridx]!]\n }\n return reel\n })\n\n const rows = Math.max(...fullBoard.map((reel) => reel.length))\n const cellWidth = 4 // inner width of symbol area\n\n const padSymbol = (sym: string) => {\n if (sym.length > cellWidth) sym = sym.slice(0, cellWidth)\n const left = Math.floor((cellWidth - sym.length) / 2)\n const right = cellWidth - sym.length - left\n return \" \".repeat(left) + sym + \" \".repeat(right)\n }\n\n const maxTop = Math.max(...board.paddingTop.map((p) => p?.length ?? 0))\n const maxBottom = Math.max(...board.paddingBottom.map((p) => p?.length ?? 0))\n const boardStart = maxTop\n const boardEnd = rows - maxBottom - 1\n\n const makeSeparator = () => {\n return fullBoard.map(() => `═${\"═\".repeat(cellWidth)}═ `).join(\"\")\n }\n\n for (let row = 0; row < rows; row++) {\n if (row === boardStart) {\n console.log(makeSeparator()) // top border of board\n }\n\n let top = \"\"\n let mid = \"\"\n let bot = \"\"\n for (let col = 0; col < fullBoard.length; col++) {\n const sym = fullBoard[col]![row]?.id ?? \" \"\n const padded = padSymbol(sym)\n top += `┌${\"─\".repeat(cellWidth)}┐ `\n mid += `│${padded}│ `\n bot += `└${\"─\".repeat(cellWidth)}┘ `\n }\n\n console.log(top)\n console.log(mid)\n console.log(bot)\n\n if (row === boardEnd) {\n console.log(makeSeparator()) // bottom border of board\n }\n }\n}\n\nexport function weightedAverage(dist: Record<number, number>) {\n const keys = Object.keys(dist).map(Number)\n const values = Object.values(dist)\n\n const totalWeight = round(values.reduce((a, b) => a + b, 0), 6)\n const weightedSum = keys.reduce((sum, key, i) => sum + key * values[i]!, 0)\n\n return weightedSum / totalWeight\n}\n\nexport class JSONL {\n public static stringify(array: object[]): string {\n return array.map((object) => JSON.stringify(object)).join(\"\\n\")\n }\n\n public static parse<T>(jsonl: string): Array<T> {\n return jsonl\n .split(\"\\n\")\n .filter((s) => s !== \"\")\n .map((str) => JSON.parse(str))\n }\n}\n\nexport function round(value: number, decimals: number) {\n return Number(Math.round(Number(value + \"e\" + decimals)) + \"e-\" + decimals)\n}\n","import assert from \"assert\"\nimport { AnyUserData } from \"../index\"\nimport { copy, RandomNumberGenerator, shuffle, weightedRandom } from \"../utils\"\nimport { Board } from \"./Board\"\nimport { GameConfig } from \"./GameConfig\"\nimport { AnySimulationContext, Simulation } from \"./Simulation\"\n\nexport class ResultSet<TUserState extends AnyUserData> {\n criteria: string\n quota: number\n multiplier?: number\n reelWeights: ReelWeights<TUserState>\n userData?: Record<string, any>\n forceMaxWin?: boolean\n forceFreespins?: boolean\n evaluate?: (ctx: AnySimulationContext<any, any, TUserState>) => boolean\n\n constructor(opts: ResultSetOpts<TUserState>) {\n this.criteria = opts.criteria\n this.quota = opts.quota\n this.multiplier = opts.multiplier\n this.reelWeights = opts.reelWeights\n this.userData = opts.userData\n this.forceMaxWin = opts.forceMaxWin\n this.forceFreespins = opts.forceFreespins\n this.evaluate = opts.evaluate\n }\n\n static assignCriteriaToSimulations(ctx: Simulation, gameModeName: string) {\n const rng = new RandomNumberGenerator()\n rng.setSeed(0)\n\n assert(ctx.simRunsAmount, \"Simulation configuration is not set.\")\n\n const simNums = ctx.simRunsAmount[gameModeName]\n const resultSets = ctx.gameConfig.config.gameModes[gameModeName]?.resultSets\n\n if (!resultSets || resultSets.length === 0) {\n throw new Error(`No ResultSets found for game mode: ${gameModeName}.`)\n }\n\n if (simNums === undefined || simNums <= 0) {\n throw new Error(`No simulations configured for game mode \"${gameModeName}\".`)\n }\n\n const totalQuota = resultSets.reduce((sum, rs) => sum + rs.quota, 0)\n\n const numberOfSimsForCriteria: Record<string, number> = Object.fromEntries(\n resultSets.map((rs) => {\n const normalizedQuota = totalQuota > 0 ? rs.quota / totalQuota : 0\n return [rs.criteria, Math.max(Math.floor(normalizedQuota * simNums), 1)]\n }),\n )\n\n let totalSims = Object.values(numberOfSimsForCriteria).reduce(\n (sum, num) => sum + num,\n 0,\n )\n\n let reduceSims = totalSims > simNums\n\n const criteriaToWeights = Object.fromEntries(\n resultSets.map((rs) => [rs.criteria, rs.quota]),\n )\n\n while (totalSims != simNums) {\n const rs = weightedRandom(criteriaToWeights, rng)\n if (reduceSims && numberOfSimsForCriteria[rs]! > 1) {\n numberOfSimsForCriteria[rs]! -= 1\n } else if (!reduceSims) {\n numberOfSimsForCriteria[rs]! += 1\n }\n\n totalSims = Object.values(numberOfSimsForCriteria).reduce(\n (sum, num) => sum + num,\n 0,\n )\n reduceSims = totalSims > simNums\n }\n\n let allCriteria: string[] = []\n const simNumsToCriteria: Record<number, string> = {}\n\n Object.entries(numberOfSimsForCriteria).forEach(([criteria, num]) => {\n for (let i = 0; i <= num; i++) {\n allCriteria.push(criteria)\n }\n })\n\n allCriteria = shuffle(allCriteria, rng)\n\n for (let i = 1; i <= Math.min(simNums, allCriteria.length); i++) {\n simNumsToCriteria[i] = allCriteria[i]!\n }\n\n return simNumsToCriteria\n }\n\n /**\n * Checks if core criteria is met, e.g. target multiplier or max win.\n */\n meetsCriteria(ctx: AnySimulationContext<any, any, TUserState>) {\n const customEval = this.evaluate?.(copy(ctx))\n\n const freespinsMet = this.forceFreespins ? ctx.state.triggeredFreespins : true\n\n const multiplierMet =\n this.multiplier !== undefined\n ? ctx.wallet.getCurrentWin() === this.multiplier && !this.forceMaxWin\n : ctx.wallet.getCurrentWin() > 0 && (!this.forceMaxWin || true)\n\n const maxWinMet = this.forceMaxWin\n ? ctx.wallet.getCurrentWin() >= ctx.config.maxWinX\n : true\n\n const coreCriteriaMet = freespinsMet && multiplierMet && maxWinMet\n\n const finalResult =\n customEval !== undefined ? coreCriteriaMet && customEval === true : coreCriteriaMet\n\n if (this.forceMaxWin && maxWinMet) {\n ctx.record({\n maxwin: true,\n })\n }\n\n return finalResult\n }\n}\n\ninterface ResultSetOpts<TUserState extends AnyUserData> {\n /**\n * A short string to describe the criteria for this ResultSet.\n */\n criteria: string\n /**\n * The quota of spins, out of the total simulations, that must be forced to meet the specified criteria.\\\n * **Float from 0 to 1. Total quota of all ResultSets in a GameMode must be 1.**\n */\n quota: number\n /**\n * The required multiplier for a simulated spin to be accepted.\n */\n multiplier?: number\n /**\n * Configure the weights of the reels in this ResultSet.\n *\n * If you need to support dynamic / special reel weights based on the simulation context,\\\n * you can provide an `evaluate` function that returns the desired weights.\n *\n * If the `evaluate` function returns a falsy value, the usual spin type based weights will be used.\n *\n * @example\n * ```ts\n * new ResultSet({\n * criteria: \"superFreespins\",\n * quota: 0.05,\n * forceFreespins: true,\n * reelWeights: {\n * [GameConfig.SPIN_TYPE.BASE_GAME]: { base1: 1 },\n * [GameConfig.SPIN_TYPE.FREE_SPINS]: { bonus1: 1, bonus2: 2 },\n * evaluate: (ctx) => {\n * if (ctx.state.userData.triggeredSuperFreespins) {\n * return { superbonus: 1 }\n * }\n * }\n * },\n * userData: { forceSuperFreespins: true },\n * }),\n * ```\n */\n reelWeights: ReelWeights<TUserState>\n /**\n * Optional data to use when evaluating the criteria.\\\n * This can be used to pass additional context or parameters needed for the evaluation.\n */\n userData?: Record<string, any>\n /**\n * If set, this will force the game to always trigger a max win.\n */\n forceMaxWin?: boolean\n /**\n * If set, this will force the game to always trigger free spins.\n */\n forceFreespins?: boolean\n /**\n * Custom function to evaluate if the criteria is met.\n *\n * E.g. use this to check for free spins that upgraded to super free spins\\\n * or other arbitrary simulation criteria.\n */\n evaluate?: (ctx: EvaluationContext<TUserState>) => boolean\n}\n\ninterface ReelWeights<TUserState extends AnyUserData> {\n [GameConfig.SPIN_TYPE.BASE_GAME]: Record<string, number>\n [GameConfig.SPIN_TYPE.FREE_SPINS]: Record<string, number>\n evaluate?: (\n ctx: EvaluationContext<TUserState>,\n ) => Record<string, number> | undefined | null | false\n}\n\nexport type EvaluationContext<TUserState extends AnyUserData> = Board<\n any,\n any,\n TUserState\n>\n","import { GameConfig, SpinType } from \"./GameConfig\"\nimport { AnySimulationContext } from \"./Simulation\"\n\n/**\n * Stores win amounts for simulations.\n */\nexport class Wallet {\n /**\n * Total win amount (as the bet multiplier) from all simulations.\n */\n protected cumulativeWins = 0\n /**\n * Total win amount (as the bet multiplier) per spin type.\n *\n * @example\n * ```ts\n * {\n * basegame: 50,\n * freespins: 100,\n * superfreespins: 200,\n * }\n * ```\n */\n protected cumulativeWinsPerSpinType = {\n [GameConfig.SPIN_TYPE.BASE_GAME]: 0,\n [GameConfig.SPIN_TYPE.FREE_SPINS]: 0,\n }\n /**\n * Current win amount (as the bet multiplier) for the ongoing simulation.\n */\n protected currentWin = 0\n /**\n * Current win amount (as the bet multiplier) for the ongoing simulation per spin type.\n *\n * @example\n * ```ts\n * {\n * basegame: 50,\n * freespins: 100,\n * superfreespins: 200,\n * }\n * ```\n */\n protected currentWinPerSpinType = {\n [GameConfig.SPIN_TYPE.BASE_GAME]: 0,\n [GameConfig.SPIN_TYPE.FREE_SPINS]: 0,\n }\n /**\n * Holds the current win amount for a single (free) spin.\\\n * After each spin, this amount is added to `currentWinPerSpinType` and then reset to zero.\n */\n protected currentSpinWin = 0\n /**\n * Current win amount (as the bet multiplier) for the ongoing tumble sequence.\n */\n protected currentTumbleWin = 0\n\n constructor() {}\n\n /**\n * Updates the win for the current spin.\n *\n * Should be called after each tumble event, if applicable.\\\n * Or generally call this to add wins during a spin.\n *\n * After each (free) spin, this amount should be added to `currentWinPerSpinType` via `confirmSpinWin()`\n */\n addSpinWin(amount: number) {\n this.currentSpinWin += amount\n }\n\n /**\n * Assigns a win amount to the given spin type.\n *\n * Should be called after `addSpinWin()`, and after your tumble events are played out,\\\n * and after a (free) spin is played out to finalize the win.\n */\n confirmSpinWin(spinType: SpinType) {\n if (!Object.keys(this.currentWinPerSpinType).includes(spinType)) {\n throw new Error(`Spin type \"${spinType}\" does not exist in the wallet.`)\n }\n this.currentWinPerSpinType[spinType]! += this.currentSpinWin\n this.currentWin += this.currentSpinWin\n this.currentSpinWin = 0\n }\n\n /**\n * Returns the accumulated win amount (as the bet multiplier) from all simulations.\n */\n getCumulativeWins() {\n return this.cumulativeWins\n }\n\n /**\n * Returns the accumulated win amount (as the bet multiplier) per spin type from all simulations.\n */\n getCumulativeWinsPerSpinType() {\n return this.cumulativeWinsPerSpinType\n }\n\n /**\n * Returns the current win amount (as the bet multiplier) for the ongoing simulation.\n */\n getCurrentWin() {\n return this.currentWin\n }\n\n /**\n * Returns the current win amount (as the bet multiplier) per spin type for the ongoing simulation.\n */\n getCurrentWinPerSpinType() {\n return this.currentWinPerSpinType\n }\n\n /**\n * Adds a win to `currentSpinWin` and `currentTumbleWin`.\n *\n * After each (free) spin, this amount should be added to `currentWinPerSpinType` via `confirmSpinWin()`\n */\n addTumbleWin(amount: number) {\n this.currentTumbleWin += amount\n this.addSpinWin(amount)\n }\n\n /**\n * Resets the current win amounts to zero.\n */\n resetCurrentWin() {\n this.currentWin = 0\n this.currentSpinWin = 0\n this.currentTumbleWin = 0\n\n for (const spinType of Object.keys(this.currentWinPerSpinType)) {\n this.currentWinPerSpinType[spinType as SpinType] = 0\n }\n }\n\n /**\n * Adds current wins to cumulative wins and resets current wins to zero.\n */\n confirmWins(ctx: AnySimulationContext) {\n function process(number: number) {\n return Math.round(Math.min(number, ctx.config.maxWinX) * 100) / 100\n }\n\n ctx.state.book.writePayout(ctx)\n\n this.currentWin = process(this.currentWin)\n this.cumulativeWins += this.currentWin\n let spinTypeWins = 0\n\n for (const spinType of Object.keys(this.currentWinPerSpinType)) {\n const st = spinType as SpinType\n const spinTypeWin = process(this.currentWinPerSpinType[st])\n this.cumulativeWinsPerSpinType[st]! += spinTypeWin\n spinTypeWins += spinTypeWin\n }\n\n if (process(spinTypeWins) !== this.currentWin) {\n throw new Error(\n `Inconsistent wallet state: currentWin (${this.currentWin}) does not equal spinTypeWins (${spinTypeWins}).`,\n )\n }\n\n this.resetCurrentWin()\n }\n\n serialize() {\n return {\n cumulativeWins: this.cumulativeWins,\n cumulativeWinsPerSpinType: this.cumulativeWinsPerSpinType,\n currentWin: this.currentWin,\n currentWinPerSpinType: this.currentWinPerSpinType,\n currentSpinWin: this.currentSpinWin,\n currentTumbleWin: this.currentTumbleWin,\n }\n }\n\n merge(wallet: Wallet) {\n this.cumulativeWins += wallet.getCumulativeWins()\n const otherWinsPerSpinType = wallet.getCumulativeWinsPerSpinType()\n\n for (const spinType of Object.keys(this.cumulativeWinsPerSpinType)) {\n this.cumulativeWinsPerSpinType[spinType as SpinType]! +=\n otherWinsPerSpinType[spinType as SpinType] || 0\n }\n }\n\n mergeSerialized(data: ReturnType<Wallet[\"serialize\"]>) {\n this.cumulativeWins += data.cumulativeWins\n for (const spinType of Object.keys(this.cumulativeWinsPerSpinType)) {\n this.cumulativeWinsPerSpinType[spinType as SpinType]! +=\n data.cumulativeWinsPerSpinType[spinType as SpinType] || 0\n }\n this.currentWin += data.currentWin\n this.currentSpinWin += data.currentSpinWin\n this.currentTumbleWin += data.currentTumbleWin\n for (const spinType of Object.keys(this.currentWinPerSpinType)) {\n this.currentWinPerSpinType[spinType as SpinType]! +=\n data.currentWinPerSpinType[spinType as SpinType] || 0\n }\n }\n}\n","import { GameConfig } from \"./GameConfig\"\nimport { AnySimulationContext } from \"./Simulation\"\n\nexport class Book {\n id: number\n criteria: string = \"N/A\"\n protected events: BookEvent[] = []\n protected payout: number = 0\n protected basegameWins: number = 0\n protected freespinsWins: number = 0\n\n constructor(opts: BookOpts) {\n this.id = opts.id\n }\n\n /**\n * Adds an event to the book.\n */\n addEvent(event: Omit<BookEvent, \"index\">) {\n const index = this.events.length + 1\n this.events.push({ index, ...event })\n }\n\n /**\n * Transfers the win data from the wallet to the book.\n */\n writePayout(ctx: AnySimulationContext) {\n function process(number: number) {\n return Math.round(Math.min(number, ctx.config.maxWinX) * 100) / 100\n }\n\n this.payout = Math.round(process(ctx.wallet.getCurrentWin()) * 100)\n this.basegameWins = process(\n ctx.wallet.getCurrentWinPerSpinType()[GameConfig.SPIN_TYPE.BASE_GAME] || 0,\n )\n this.freespinsWins = process(\n ctx.wallet.getCurrentWinPerSpinType()[GameConfig.SPIN_TYPE.FREE_SPINS] || 0,\n )\n }\n\n getPayout() {\n return this.payout\n }\n\n getBasegameWins() {\n return this.basegameWins\n }\n\n getFreespinsWins() {\n return this.freespinsWins\n }\n\n serialize() {\n return {\n id: this.id,\n criteria: this.criteria,\n events: this.events,\n payout: this.payout,\n basegameWins: this.basegameWins,\n freespinsWins: this.freespinsWins,\n }\n }\n\n static fromSerialized(data: ReturnType<Book[\"serialize\"]>) {\n const book = new Book({ id: data.id })\n book.criteria = data.criteria\n book.events = data.events\n book.payout = data.payout\n book.basegameWins = data.basegameWins\n book.freespinsWins = data.freespinsWins\n return book\n }\n}\n\nexport interface BookEvent {\n index: number\n type: string\n data: Record<string, any>\n}\n\ninterface BookOpts {\n id: number\n}\n","import { GameConfig, SpinType } from \"./GameConfig\"\nimport { Wallet } from \"./Wallet\"\nimport { Book } from \"./Book\"\nimport { ResultSet } from \"./ResultSet\"\nimport { RandomNumberGenerator } from \"../utils\"\nimport { AnyGameModes, AnySymbols, AnyUserData, CommonGameOptions } from \"../index\"\n\n/**\n * The GameState manages the current state of the game.\n */\nexport class GameState<\n TGameModes extends AnyGameModes,\n TSymbols extends AnySymbols,\n TUserState extends AnyUserData,\n> extends GameConfig<TGameModes, TSymbols, TUserState> {\n state: {\n currentSimulationId: number\n /**\n * e.g. \"base\", \"freespins\", etc. (depending on the game config)\n */\n currentGameMode: string\n /**\n * Spin type constant as defined in `GameConfig.SPIN_TYPE`\n */\n currentSpinType: SpinType\n /**\n * The current ResultSet for the active simulation run.\n */\n currentResultSet: ResultSet<any>\n /**\n * Whether the criteria in the ResultSet for the current simulation has been met.\n */\n isCriteriaMet: boolean\n /**\n * Number of freespins remaining in the current freespin round.\n */\n currentFreespinAmount: number\n /**\n * Total amount of freespins awarded during the active simulation.\n */\n totalFreespinAmount: number\n /**\n * A library of all completed books, indexed by their ID.\n */\n library: Map<string, Book>\n /**\n * The current book being recorded.\n */\n book: Book\n /**\n * Seeded random number generator instance for the current simulation.\n */\n rng: RandomNumberGenerator\n /**\n * Custom user data that can be used in game flow logic.\n */\n userData: TUserState\n /**\n * Whether a max win has been triggered during the active simulation.\n */\n triggeredMaxWin: boolean\n /**\n * Whether freespins have been triggered during the active simulation.\n */\n triggeredFreespins: boolean\n }\n\n /**\n * The wallet stores win data for the current and all simulations, respectively.\n */\n wallet: Wallet\n\n /**\n * Recorder for statistical analysis (e.g. symbol occurrences, etc.).\n */\n private recorder: {\n pendingRecords: PendingRecord[]\n readonly records: RecordItem[]\n }\n\n constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>) {\n super(opts)\n\n this.state = {\n currentSpinType: GameConfig.SPIN_TYPE.BASE_GAME,\n library: new Map(),\n book: new Book({ id: 0 }),\n currentGameMode: \"N/A\",\n currentSimulationId: 0,\n isCriteriaMet: false,\n currentFreespinAmount: 0,\n totalFreespinAmount: 0,\n rng: new RandomNumberGenerator(),\n userData: opts.userState || ({} as TUserState),\n triggeredMaxWin: false,\n triggeredFreespins: false,\n // This is a placeholder ResultSet to avoid null checks elsewhere.\n currentResultSet: new ResultSet({\n criteria: \"N/A\",\n quota: 0,\n reelWeights: {\n [GameConfig.SPIN_TYPE.BASE_GAME]: {},\n [GameConfig.SPIN_TYPE.FREE_SPINS]: {},\n },\n }),\n }\n\n this.wallet = new Wallet()\n\n this.recorder = {\n pendingRecords: [],\n records: [],\n }\n }\n\n /**\n * Gets the configuration for the current game mode.\n */\n getCurrentGameMode() {\n return this.config.gameModes[this.state.currentGameMode]!\n }\n\n resetState() {\n this.state.rng.setSeedIfDifferent(this.state.currentSimulationId)\n this.state.book = new Book({ id: this.state.currentSimulationId })\n this.state.currentSpinType = GameConfig.SPIN_TYPE.BASE_GAME\n this.state.currentFreespinAmount = 0\n this.state.totalFreespinAmount = 0\n this.state.triggeredMaxWin = false\n this.state.triggeredFreespins = false\n this.wallet.resetCurrentWin()\n this.clearPendingRecords()\n this.state.userData = this.config.userState || ({} as TUserState)\n }\n\n /**\n * Empties the list of pending records in the recorder.\n */\n clearPendingRecords() {\n this.recorder.pendingRecords = []\n }\n\n /**\n * Confirms all pending records and adds them to the main records list.\n */\n confirmRecords() {\n for (const pendingRecord of this.recorder.pendingRecords) {\n const search = Object.entries(pendingRecord.properties)\n .map(([name, value]) => ({ name, value }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n let record = this.recorder.records.find((r) => {\n if (r.search.length !== search.length) return false\n for (let i = 0; i < r.search.length; i++) {\n if (r.search[i]!.name !== search[i]!.name) return false\n if (r.search[i]!.value !== search[i]!.value) return false\n }\n return true\n })\n if (!record) {\n record = {\n search,\n timesTriggered: 0,\n bookIds: [],\n }\n this.recorder.records.push(record)\n }\n record.timesTriggered++\n if (!record.bookIds.includes(pendingRecord.bookId)) {\n record.bookIds.push(pendingRecord.bookId)\n }\n }\n\n this.clearPendingRecords()\n }\n\n /**\n * Record data for statistical analysis.\n */\n record(data: Record<string, string | number | boolean>) {\n this.recorder.pendingRecords.push({\n bookId: this.state.currentSimulationId,\n properties: Object.fromEntries(\n Object.entries(data).map(([k, v]) => [k, String(v)]),\n ),\n })\n }\n\n /**\n * Records a symbol occurrence for statistical analysis.\n *\n * Calls `this.record()` with the provided data.\n */\n recordSymbolOccurrence(data: {\n kind: number\n symbolId: string\n spinType: SpinType\n [key: string]: any\n }) {\n this.record(data)\n }\n\n /**\n * Gets all confirmed records.\n */\n getRecords() {\n return this.recorder.records\n }\n\n /**\n * Moves the current book to the library and resets the current book.\n */\n moveBookToLibrary() {\n this.state.library.set(this.state.book.id.toString(), this.state.book)\n this.state.book = new Book({ id: 0 })\n }\n\n /**\n * Increases the freespin count by the specified amount.\n *\n * Also sets `state.triggeredFreespins` to true.\n */\n awardFreespins(amount: number) {\n this.state.currentFreespinAmount += amount\n this.state.totalFreespinAmount += amount\n this.state.triggeredFreespins = true\n }\n\n /**\n * Ensures the requested number of scatters is valid based on the game configuration.\\\n * Returns a valid number of scatters.\n */\n verifyScatterCount(numScatters: number) {\n const scatterCounts = this.config.scatterToFreespins[this.state.currentSpinType]\n if (!scatterCounts) {\n throw new Error(\n `No scatter counts defined for spin type \"${this.state.currentSpinType}\". Please check your game configuration.`,\n )\n }\n const validCounts = Object.keys(scatterCounts).map((key) => parseInt(key, 10))\n if (validCounts.length === 0) {\n throw new Error(\n `No scatter counts defined for spin type \"${this.state.currentSpinType}\". Please check your game configuration.`,\n )\n }\n if (numScatters < Math.min(...validCounts)) {\n return Math.min(...validCounts)\n }\n if (numScatters > Math.max(...validCounts)) {\n return Math.max(...validCounts)\n }\n return numScatters\n }\n}\n\ninterface PendingRecord {\n bookId: number\n properties: Record<string, string>\n}\n\nexport interface RecordItem {\n search: Array<{ name: string; value: string }>\n timesTriggered: number\n bookIds: number[]\n}\n","import { AnyGameModes, AnySymbols, AnyUserData, CommonGameOptions } from \"../index\"\nimport { randomItem, weightedRandom } from \"../utils\"\nimport { GameState } from \"./GameState\"\nimport { GameSymbol } from \"./GameSymbol\"\nimport { Reels } from \"./ReelGenerator\"\nimport { AnySimulationContext } from \"./Simulation\"\n\n/**\n * A version of the Board class that is disconnected from the actual game state\\\n * and operates on a copy of the game context.\n *\n * Can be used in custom game logic where you need to evaluate an additional board or reels independent of the main board,\\\n * similar to the top and bottom reels of the game \"San Quentin\".\n */\nexport class StandaloneBoard {\n protected reels: Reels\n protected paddingTop: Reels\n protected paddingBottom: Reels\n protected anticipation: number[]\n protected ctx: AnySimulationContext\n\n constructor(opts: StandaloneBoardOpts) {\n this.reels = []\n this.paddingTop = []\n this.paddingBottom = []\n this.anticipation = []\n this.ctx = opts.ctx\n }\n\n /**\n * Updates the context used by this board instance.\n */\n context(ctx: AnySimulationContext) {\n this.ctx = ctx\n }\n\n /**\n * Resets the board to an empty state.\\\n * This is called before drawing a new board.\n */\n resetBoard() {\n this.resetReels()\n }\n\n private makeEmptyReels() {\n return Array.from({ length: this.ctx.getCurrentGameMode().reelsAmount }, () => [])\n }\n\n private resetReels() {\n this.reels = this.makeEmptyReels()\n this.anticipation = Array.from(\n { length: this.ctx.getCurrentGameMode().reelsAmount },\n () => 0,\n )\n if (this.ctx.config.padSymbols && this.ctx.config.padSymbols > 0) {\n this.paddingTop = this.makeEmptyReels()\n this.paddingBottom = this.makeEmptyReels()\n }\n }\n\n /**\n * Counts how many symbols matching the criteria are on a specific reel.\n */\n countSymbolsOnReel(\n symbolOrProperties: GameSymbol | Record<string, any>,\n reelIndex: number,\n ) {\n let total = 0\n\n for (const symbol of this.reels[reelIndex]!) {\n let matches = true\n if (symbolOrProperties instanceof GameSymbol) {\n if (symbol.id !== symbolOrProperties.id) matches = false\n } else {\n for (const [key, value] of Object.entries(symbolOrProperties)) {\n if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {\n matches = false\n break\n }\n }\n }\n if (matches) {\n total++\n }\n }\n\n return total\n }\n\n /**\n * Counts how many symbols matching the criteria are on the board.\n *\n * Passing a GameSymbol will compare by ID, passing a properties object will compare by properties.\n *\n * Returns a tuple where the first element is the total count, and the second element is a record of counts per reel index.\n */\n countSymbolsOnBoard(\n symbolOrProperties: GameSymbol | Record<string, any>,\n ): [number, Record<number, number>] {\n let total = 0\n const onReel: Record<number, number> = {}\n\n for (const [ridx, reel] of this.reels.entries()) {\n for (const symbol of reel) {\n let matches = true\n\n if (symbolOrProperties instanceof GameSymbol) {\n if (symbol.id !== symbolOrProperties.id) matches = false\n } else {\n for (const [key, value] of Object.entries(symbolOrProperties)) {\n if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {\n matches = false\n break\n }\n }\n }\n\n if (matches) {\n total++\n if (onReel[ridx] === undefined) {\n onReel[ridx] = 1\n } else {\n onReel[ridx]++\n }\n }\n }\n }\n\n return [total, onReel]\n }\n\n /**\n * Checks if a symbol appears more than once on any reel in the current reel set.\n *\n * Useful to check for \"forbidden\" generations, e.g. 2 scatters on one reel.\n */\n isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol) {\n for (const reel of this.reels) {\n let count = 0\n for (const sym of reel) {\n if (sym.id === symbol.id) {\n count++\n }\n if (count > 1) {\n return true\n }\n }\n }\n return false\n }\n\n /**\n * Draws a board using specified reel stops.\n */\n drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>) {\n this.drawBoardMixed(reels, forcedStops)\n }\n\n /**\n * Draws a board using random reel stops.\n */\n drawBoardWithRandomStops(reels: Reels) {\n this.drawBoardMixed(reels)\n }\n\n private drawBoardMixed(reels: Reels, forcedStops?: Record<string, number>) {\n this.resetReels()\n\n const finalReelStops: (number | null)[] = Array.from(\n { length: this.ctx.getCurrentGameMode().reelsAmount },\n () => null,\n )\n\n if (forcedStops) {\n // Fill in forced stops\n for (const [r, stopPos] of Object.entries(forcedStops)) {\n const reelIdx = Number(r)\n const symCount = this.ctx.getCurrentGameMode().symbolsPerReel[reelIdx]!\n finalReelStops[reelIdx] =\n stopPos - Math.round(this.ctx.state.rng.randomFloat(0, symCount - 1))\n }\n }\n\n // Fill in random stops for reels without a forced stop\n for (let i = 0; i < finalReelStops.length; i++) {\n if (finalReelStops[i] === null) {\n finalReelStops[i] = Math.floor(\n this.ctx.state.rng.randomFloat(0, reels[i]!.length - 1),\n )\n }\n }\n\n for (let ridx = 0; ridx < this.ctx.getCurrentGameMode().reelsAmount; ridx++) {\n const reelPos = finalReelStops[ridx]!\n\n if (this.ctx.config.padSymbols && this.ctx.config.padSymbols > 0) {\n for (let p = this.ctx.config.padSymbols - 1; p >= 0; p--) {\n const topPos = (reelPos - (p + 1)) % reels[ridx]!.length\n this.paddingTop[ridx]!.push(reels[ridx]![topPos]!)\n const bottomPos =\n (reelPos + this.ctx.getCurrentGameMode().symbolsPerReel[ridx]! + p) %\n reels[ridx]!.length\n this.paddingBottom[ridx]!.unshift(reels[ridx]![bottomPos]!)\n }\n }\n\n for (\n let row = 0;\n row < this.ctx.getCurrentGameMode().symbolsPerReel[ridx]!;\n row++\n ) {\n const symbol = reels[ridx]![(reelPos + row) % reels[ridx]!.length]\n\n if (!symbol) {\n throw new Error(`Failed to get symbol at pos ${reelPos + row} on reel ${ridx}`)\n }\n\n this.reels[ridx]![row] = symbol\n }\n }\n }\n}\n\ninterface StandaloneBoardOpts {\n ctx: AnySimulationContext\n}\n\n/**\n * Extends GameState. Provides board-related functionality.\n */\nexport class Board<\n TGameModes extends AnyGameModes,\n TSymbols extends AnySymbols,\n TUserState extends AnyUserData,\n> extends GameState<TGameModes, TSymbols, TUserState> {\n board: {\n reels: Reels\n paddingTop: Reels\n paddingBottom: Reels\n anticipation: number[]\n }\n\n constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>) {\n super(opts)\n\n this.board = {\n reels: [],\n paddingTop: [],\n paddingBottom: [],\n anticipation: [],\n }\n }\n\n /**\n * Resets the board to an empty state.\\\n * This is called before drawing a new board.\n */\n resetBoard() {\n this.resetReels()\n }\n\n private makeEmptyReels() {\n return Array.from({ length: this.getCurrentGameMode().reelsAmount }, () => [])\n }\n\n private resetReels() {\n this.board.reels = this.makeEmptyReels()\n this.board.anticipation = Array.from(\n { length: this.getCurrentGameMode().reelsAmount },\n () => 0,\n )\n if (this.config.padSymbols && this.config.padSymbols > 0) {\n this.board.paddingTop = this.makeEmptyReels()\n this.board.paddingBottom = this.makeEmptyReels()\n }\n }\n\n /**\n * Counts how many symbols matching the criteria are on a specific reel.\n */\n countSymbolsOnReel(\n symbolOrProperties: GameSymbol | Record<string, any>,\n reelIndex: number,\n ) {\n let total = 0\n\n for (const symbol of this.board.reels[reelIndex]!) {\n let matches = true\n if (symbolOrProperties instanceof GameSymbol) {\n if (symbol.id !== symbolOrProperties.id) matches = false\n } else {\n for (const [key, value] of Object.entries(symbolOrProperties)) {\n if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {\n matches = false\n break\n }\n }\n }\n if (matches) {\n total++\n }\n }\n\n return total\n }\n\n /**\n * Counts how many symbols matching the criteria are on the board.\n *\n * Passing a GameSymbol will compare by ID, passing a properties object will compare by properties.\n *\n * Returns a tuple where the first element is the total count, and the second element is a record of counts per reel index.\n */\n countSymbolsOnBoard(\n symbolOrProperties: GameSymbol | Record<string, any>,\n ): [number, Record<number, number>] {\n let total = 0\n const onReel: Record<number, number> = {}\n\n for (const [ridx, reel] of this.board.reels.entries()) {\n for (const symbol of reel) {\n let matches = true\n\n if (symbolOrProperties instanceof GameSymbol) {\n if (symbol.id !== symbolOrProperties.id) matches = false\n } else {\n for (const [key, value] of Object.entries(symbolOrProperties)) {\n if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {\n matches = false\n break\n }\n }\n }\n\n if (matches) {\n total++\n if (onReel[ridx] === undefined) {\n onReel[ridx] = 1\n } else {\n onReel[ridx]++\n }\n }\n }\n }\n\n return [total, onReel]\n }\n\n /**\n * Checks if a symbol appears more than once on any reel in the current reel set.\n *\n * Useful to check for \"forbidden\" generations, e.g. 2 scatters on one reel.\n */\n isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol) {\n for (const reel of this.board.reels) {\n let count = 0\n for (const sym of reel) {\n if (sym.id === symbol.id) {\n count++\n }\n if (count > 1) {\n return true\n }\n }\n }\n return false\n }\n\n /**\n * Gets all reel stops (positions) where the specified symbol appears in the current reel set.\\\n * Returns an array of arrays, where each inner array contains the positions for the corresponding reel.\n */\n getReelStopsForSymbol(reels: Reels, symbol: GameSymbol) {\n const reelStops: number[][] = []\n for (let ridx = 0; ridx < reels.length; ridx++) {\n const reel = reels[ridx]!\n const positions: number[] = []\n for (let pos = 0; pos < reel.length; pos++) {\n if (reel[pos]!.id === symbol.id) {\n positions.push(pos)\n }\n }\n reelStops.push(positions)\n }\n return reelStops\n }\n\n /**\n * Combines multiple arrays of reel stops into a single array of reel stops.\\\n */\n combineReelStops(...reelStops: number[][][]) {\n const combined: number[][] = []\n for (let ridx = 0; ridx < this.getCurrentGameMode().reelsAmount; ridx++) {\n combined[ridx] = []\n for (const stops of reelStops) {\n combined[ridx] = combined[ridx]!.concat(stops[ridx]!)\n }\n }\n return combined\n }\n\n /**\n * From a list of reel stops on reels, selects a random stop for a speficied number of random symbols.\n *\n * Mostly useful for placing scatter symbols on the board.\n */\n getRandomReelStops(reels: Reels, reelStops: number[][], amount: number) {\n const reelsAmount = this.getCurrentGameMode().reelsAmount\n const symProbsOnReels: number[] = []\n const stopPositionsForReels: Record<string, number> = {}\n\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n symProbsOnReels.push(reelStops[ridx]!.length / reels[ridx]!.length)\n }\n\n while (Object.keys(stopPositionsForReels).length !== amount) {\n const possibleReels: number[] = []\n for (let i = 0; i < reelsAmount; i++) {\n if (symProbsOnReels[i]! > 0) {\n possibleReels.push(i)\n }\n }\n const possibleProbs = symProbsOnReels.filter((p) => p > 0)\n const weights = Object.fromEntries(\n possibleReels.map((ridx, idx) => [ridx, possibleProbs[idx]!]),\n )\n const chosenReel = weightedRandom(weights, this.state.rng)\n const chosenStop = randomItem(reelStops[Number(chosenReel)]!, this.state.rng)\n symProbsOnReels[Number(chosenReel)] = 0\n stopPositionsForReels[chosenReel] = chosenStop\n }\n\n return stopPositionsForReels\n }\n\n /**\n * Selects a random reel set based on the configured weights of the current result set.\\\n * Returns the reels as arrays of GameSymbols.\n */\n getRandomReelset() {\n const weights = this.state.currentResultSet.reelWeights\n const evalWeights = this.state.currentResultSet.reelWeights.evaluate?.(\n this as Board<any, any, any>,\n )\n\n let reelSetId: string = \"\"\n\n if (evalWeights) {\n reelSetId = weightedRandom(evalWeights, this.state.rng)\n } else {\n reelSetId = weightedRandom(weights[this.state.currentSpinType]!, this.state.rng)\n }\n\n const reelSet = this.getReelsetById(this.state.currentGameMode, reelSetId)\n return reelSet\n }\n\n /**\n * Draws a board using specified reel stops.\n */\n drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>) {\n this.drawBoardMixed(reels, forcedStops)\n }\n\n /**\n * Draws a board using random reel stops.\n */\n drawBoardWithRandomStops(reels: Reels) {\n this.drawBoardMixed(reels)\n }\n\n private drawBoardMixed(reels: Reels, forcedStops?: Record<string, number>) {\n this.resetReels()\n\n const finalReelStops: (number | null)[] = Array.from(\n { length: this.getCurrentGameMode().reelsAmount },\n () => null,\n )\n\n if (forcedStops) {\n // Fill in forced stops\n for (const [r, stopPos] of Object.entries(forcedStops)) {\n const reelIdx = Number(r)\n const symCount = this.getCurrentGameMode().symbolsPerReel[reelIdx]!\n finalReelStops[reelIdx] =\n stopPos - Math.round(this.state.rng.randomFloat(0, symCount - 1))\n if (finalReelStops[reelIdx]! < 0) {\n finalReelStops[reelIdx] = reels[reelIdx]!.length + finalReelStops[reelIdx]!\n }\n }\n }\n\n // Fill in random stops for reels without a forced stop\n for (let i = 0; i < finalReelStops.length; i++) {\n if (finalReelStops[i] === null) {\n finalReelStops[i] = Math.floor(\n this.state.rng.randomFloat(0, reels[i]!.length - 1),\n )\n }\n }\n\n for (let ridx = 0; ridx < this.getCurrentGameMode().reelsAmount; ridx++) {\n const reelPos = finalReelStops[ridx]!\n\n if (this.config.padSymbols && this.config.padSymbols > 0) {\n for (let p = this.config.padSymbols - 1; p >= 0; p--) {\n const topPos = (reelPos - (p + 1)) % reels[ridx]!.length\n this.board.paddingTop[ridx]!.push(reels[ridx]![topPos]!)\n const bottomPos =\n (reelPos + this.getCurrentGameMode().symbolsPerReel[ridx]! + p) %\n reels[ridx]!.length\n this.board.paddingBottom[ridx]!.unshift(reels[ridx]![bottomPos]!)\n }\n }\n\n for (let row = 0; row < this.getCurrentGameMode().symbolsPerReel[ridx]!; row++) {\n const symbol = reels[ridx]![(reelPos + row) % reels[ridx]!.length]\n\n if (!symbol) {\n throw new Error(`Failed to get symbol at pos ${reelPos + row} on reel ${ridx}`)\n }\n\n this.board.reels[ridx]![row] = symbol\n }\n }\n }\n}\n","import { GameSymbol } from \"./GameSymbol\"\nimport { AnySimulationContext, SimulationContext } from \"./Simulation\"\n\nexport class WinType {\n protected payout: number\n protected winCombinations: WinCombination[]\n protected ctx!: AnySimulationContext\n protected readonly wildSymbol?: WildSymbol\n\n constructor(opts?: WinTypeOpts) {\n this.payout = 0\n this.winCombinations = []\n this.wildSymbol = opts?.wildSymbol\n }\n\n /**\n * Sets the simulation context for this WinType instance.\n * \n * This gives the WinType access to the current board.\n */\n context(ctx: SimulationContext<any, any, any>): WinType {\n this.ctx = ctx\n return this\n }\n\n protected ensureContext() {\n if (!this.ctx) {\n throw new Error(\"WinType context is not set. Call context(ctx) first.\")\n }\n }\n\n /**\n * Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.\n */\n evaluateWins() {\n this.ensureContext()\n return this\n }\n\n /**\n * Custom post-processing of wins, e.g. for handling multipliers.\n */\n postProcess(func: PostProcessFn<typeof this.winCombinations>) {\n this.ensureContext()\n const result = func(this, this.ctx!)\n this.payout = result.payout\n this.winCombinations = result.winCombinations\n return this\n }\n\n /**\n * Returns the total payout and detailed win combinations.\n */\n getWins() {\n return {\n payout: this.payout,\n winCombinations: this.winCombinations,\n }\n }\n}\n\nexport interface WinTypeOpts {\n /**\n * Configuration used to identify wild symbols on the board.\\\n * You can either provide a specific `GameSymbol` instance or a set of properties to match against symbols on the board.\n *\n * @example\n * If you have different wild symbols, each with a property `isWild: true`, you can define:\n * ```ts\n * wildSymbol: { isWild: true }\n * ```\n *\n * @example\n * If you have a single wild symbol instance, you can define:\n * ```ts\n * wildSymbol: myWildSymbol\n * ```\n */\n wildSymbol?: WildSymbol\n}\n\nexport type WinCombination = {\n payout: number\n kind: number\n symbols: Array<{\n symbol: GameSymbol\n isWild: boolean\n substitutedFor?: GameSymbol\n reelIndex: number\n posIndex: number\n }>\n}\n\ntype PostProcessFn<TWinCombs extends WinCombination[]> = (\n winType: WinType,\n ctx: AnySimulationContext,\n) => {\n payout: number\n winCombinations: TWinCombs\n}\n\ntype WildSymbol = GameSymbol | Record<string, any>\n","import { GameSymbol } from \"../GameSymbol\"\nimport { SimulationContext } from \"../Simulation\"\nimport { WinCombination, WinType, WinTypeOpts } from \"../WinType\"\n\nexport class LinesWinType extends WinType {\n protected lines: Record<number, number[]>\n declare protected winCombinations: LineWinCombination[]\n declare context: (ctx: SimulationContext<any, any, any>) => LinesWinType\n declare getWins: () => {\n payout: number\n winCombinations: LineWinCombination[]\n }\n\n constructor(opts: LinesWinTypeOpts) {\n super(opts)\n this.lines = opts.lines\n\n if (Object.keys(this.lines).length === 0) {\n throw new Error(\"LinesWinType must have at least one line defined.\")\n }\n }\n\n private validateConfig() {\n const reelsAmount = this.ctx.getCurrentGameMode().reelsAmount\n const symsPerReel = this.ctx.getCurrentGameMode().symbolsPerReel\n\n for (const [lineNum, positions] of Object.entries(this.lines)) {\n if (positions.length !== reelsAmount) {\n throw new Error(\n `Line ${lineNum} has ${positions.length} positions, but the current game mode has ${reelsAmount} reels.`,\n )\n }\n for (let i = 0; i < positions.length; i++) {\n if (positions[i]! < 0 || positions[i]! >= symsPerReel[i]!) {\n throw new Error(\n `Line ${lineNum} has an invalid position ${positions[i]} on reel ${i}. Valid range is 0 to ${\n symsPerReel[i]! - 1\n }.`,\n )\n }\n }\n }\n\n const firstLine = Math.min(...Object.keys(this.lines).map(Number))\n if (firstLine !== 1) {\n throw new Error(\n `Lines must start from 1. Found line ${firstLine} as the first line.`,\n )\n }\n }\n\n private isWild(symbol: GameSymbol) {\n return !!this.wildSymbol && symbol.compare(this.wildSymbol)\n }\n\n evaluateWins() {\n this.ensureContext()\n this.validateConfig()\n\n const lineWins: LineWinCombination[] = []\n let payout = 0\n\n const reels = this.ctx.board.reels\n const reelsAmount = this.ctx.getCurrentGameMode().reelsAmount\n\n for (const [lineNumStr, lineDef] of Object.entries(this.lines)) {\n const lineNum = Number(lineNumStr)\n\n let baseSymbol: GameSymbol | null = null\n let leadingWilds = 0\n const chain: GameSymbol[] = []\n const details: LineWinCombination[\"symbols\"] = []\n\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n const rowIdx = lineDef[ridx]!\n const sym = reels[ridx]![rowIdx]\n if (!sym) throw new Error(\"Encountered an invalid symbol while evaluating wins.\")\n\n const wild = this.isWild(sym)\n\n if (ridx === 0) {\n chain.push(sym)\n details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild })\n if (wild) leadingWilds++\n else baseSymbol = sym\n continue\n }\n\n if (wild) {\n chain.push(sym)\n details.push({\n reelIndex: ridx,\n posIndex: rowIdx,\n symbol: sym,\n isWild: true,\n substitutedFor: baseSymbol || undefined,\n })\n continue\n }\n\n if (!baseSymbol) {\n baseSymbol = sym\n chain.push(sym)\n details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false })\n continue\n }\n\n if (sym.id === baseSymbol.id) {\n chain.push(sym)\n details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false })\n continue\n }\n\n break\n }\n\n if (chain.length === 0) continue\n\n const allWild = chain.every((s) => this.isWild(s))\n const wildRepresentative =\n this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null\n\n const len = chain.length\n let bestPayout = 0\n let bestType: LineWinCombination[\"winType\"] | null = null\n let payingSymbol: GameSymbol | null = null\n\n if (baseSymbol?.pays && baseSymbol.pays[len]) {\n bestPayout = baseSymbol.pays[len]!\n bestType = \"substituted\"\n payingSymbol = baseSymbol\n }\n\n if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {\n const wildPay = wildRepresentative.pays[len]!\n if (wildPay > bestPayout) {\n bestPayout = wildPay\n bestType = \"pure-wild\"\n payingSymbol = wildRepresentative\n }\n }\n\n if (!bestPayout || !bestType || !payingSymbol) continue\n\n const minLen = payingSymbol.pays\n ? Math.min(...Object.keys(payingSymbol.pays).map(Number))\n : Infinity\n\n if (len < minLen) continue\n\n const wildCount = details.filter((d) => d.isWild).length\n const nonWildCount = len - wildCount\n\n lineWins.push({\n lineNumber: lineNum,\n kind: len,\n payout: bestPayout,\n symbol: payingSymbol,\n winType: bestType,\n substitutedBaseSymbol: bestType === \"pure-wild\" ? null : baseSymbol,\n symbols: details,\n stats: { wildCount, nonWildCount, leadingWilds },\n })\n payout += bestPayout\n }\n\n for (const win of lineWins) {\n this.ctx.recordSymbolOccurrence({\n kind: win.kind,\n symbolId: win.symbol.id,\n spinType: this.ctx.state.currentSpinType,\n })\n }\n\n this.payout = payout\n this.winCombinations = lineWins\n\n return this\n }\n}\n\ninterface LinesWinTypeOpts extends WinTypeOpts {\n /**\n * Defines the paylines for the slot game.\n *\n * @example\n * ```ts\n * lines: {\n * 1: [0, 0, 0, 0, 0],\n * 2: [1, 1, 1, 1, 1],\n * 3: [2, 2, 2, 2, 2],\n * }\n * ```\n */\n lines: Record<number, number[]>\n}\n\nexport interface LineWinCombination extends WinCombination {\n lineNumber: number\n symbol: GameSymbol\n winType: \"pure-wild\" | \"substituted\"\n substitutedBaseSymbol: GameSymbol | null\n stats: {\n wildCount: number\n nonWildCount: number\n leadingWilds: number\n }\n}\n","import { WinType } from \"../WinType\"\n\nexport class ClusterWinType extends WinType {}\n","import { WinType } from \"../WinType\"\n\nexport class ManywaysWinType extends WinType {}\n","import assert from \"assert\"\n\nexport class OptimizationConditions {\n protected rtp?: number | \"x\"\n protected avgWin?: number\n protected hitRate?: number | \"x\"\n protected searchRange: number[]\n protected forceSearch: Record<string, string>\n priority: number\n\n constructor(opts: OptimizationConditionsOpts) {\n let { rtp, avgWin, hitRate, searchConditions, priority } = opts\n\n if (rtp == undefined || rtp === \"x\") {\n assert(avgWin !== undefined && hitRate !== undefined, \"If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.\")\n rtp = Math.round((avgWin! / Number(hitRate)) * 100000) / 100000\n }\n\n let noneCount = 0\n for (const val of [rtp, avgWin, hitRate]) {\n if (val === undefined) noneCount++\n }\n assert(noneCount <= 1, \"Invalid combination of optimization conditions.\")\n\n this.searchRange = [-1, -1]\n this.forceSearch = {}\n\n if (typeof searchConditions === \"number\") {\n this.searchRange = [searchConditions, searchConditions]\n }\n if (Array.isArray(searchConditions)) {\n if (searchConditions[0] > searchConditions[1] || searchConditions.length !== 2) {\n throw new Error(\"Invalid searchConditions range.\")\n }\n this.searchRange = searchConditions\n }\n if (typeof searchConditions === \"object\" && !Array.isArray(searchConditions)) {\n this.searchRange = [-1, -1]\n this.forceSearch = searchConditions\n }\n\n this.rtp = rtp\n this.avgWin = avgWin\n this.hitRate = hitRate\n this.priority = priority\n }\n\n getRtp() {\n return this.rtp\n }\n\n getAvgWin() {\n return this.avgWin\n }\n\n getHitRate() {\n return this.hitRate\n }\n\n getSearchRange() {\n return this.searchRange\n }\n\n getForceSearch() {\n return this.forceSearch\n }\n}\n\ninterface OptimizationConditionsOpts {\n /**\n * The desired RTP (0-1)\n */\n rtp?: number | \"x\"\n /**\n * The desired average win (per spin).\n */\n avgWin?: number\n /**\n * The desired hit rate (e.g. `200` to hit 1 in 200 spins).\n */\n hitRate?: number | \"x\"\n /**\n * A way of filtering results by\n *\n * - A number (payout multiplier), e.g. `5000`\n * - Force record value, e.g. `{ \"symbolId\": \"scatter\" }`\n * - A range of numbers, e.g. `[0, 100]` (payout multiplier range)\n */\n searchConditions?: number | Record<string, string> | [number, number]\n /**\n * **Priority matters!**\\\n * Higher priority conditions will be evaluated first.\\\n * After a book matching this condition is found, the book will be removed from the pool\\\n * and can't be used to satisfy other conditions with lower priority.\n * \n * TODO add better explanation\n */\n priority: number\n}\n","export class OptimizationScaling {\n protected config: OptimizationScalingOpts\n\n constructor(opts: OptimizationScalingOpts) {\n this.config = opts\n }\n\n getConfig() {\n return this.config\n }\n}\n\ntype OptimizationScalingOpts = Array<{\n criteria: string\n scaleFactor: number\n winRange: [number, number]\n probability: number\n}>\n","export class OptimizationParameters {\n protected parameters: OptimizationParametersOpts\n\n constructor(opts?: OptimizationParametersOpts) {\n this.parameters = {\n ...OptimizationParameters.DEFAULT_PARAMETERS,\n ...opts,\n }\n }\n\n static DEFAULT_PARAMETERS: OptimizationParametersOpts = {\n numShowPigs: 5000,\n numPigsPerFence: 10000,\n threadsFenceConstruction: 16,\n threadsShowConstruction: 16,\n testSpins: [50, 100, 200],\n testSpinsWeights: [0.3, 0.4, 0.3],\n simulationTrials: 5000,\n graphIndexes: [],\n run1000Batch: false,\n minMeanToMedian: 4,\n maxMeanToMedian: 8,\n pmbRtp: 1.0,\n scoreType: \"rtp\",\n }\n\n getParameters() {\n return this.parameters\n }\n}\n\nexport interface OptimizationParametersOpts {\n readonly numShowPigs: number\n readonly numPigsPerFence: number\n readonly threadsFenceConstruction: number\n readonly threadsShowConstruction: number\n readonly testSpins: number[]\n readonly testSpinsWeights: number[]\n readonly simulationTrials: number\n readonly graphIndexes: number[]\n readonly run1000Batch: false\n readonly minMeanToMedian: number\n readonly maxMeanToMedian: number\n readonly pmbRtp: number\n readonly scoreType: \"rtp\"\n}\n","import fs from \"fs\"\nimport path from \"path\"\nimport assert from \"assert\"\nimport zlib from \"zlib\"\nimport { buildSync } from \"esbuild\"\nimport { createDirIfNotExists, JSONL, printBoard, writeFile } from \"../utils\"\nimport { Board } from \"./Board\"\nimport { GameConfig } from \"./GameConfig\"\nimport { GameModeName } from \"./GameMode\"\nimport { ResultSet } from \"./ResultSet\"\nimport { Wallet } from \"./Wallet\"\nimport { Book } from \"./Book\"\nimport { Worker, isMainThread, parentPort, workerData } from \"worker_threads\"\nimport { RecordItem } from \"./GameState\"\nimport { AnyGameModes, AnySymbols, AnyUserData, CommonGameOptions } from \"../index\"\n\nlet completedSimulations = 0\nconst TEMP_FILENAME = \"__temp_compiled_src_IGNORE.js\"\n\nexport class Simulation {\n protected readonly gameConfigOpts: CommonGameOptions\n readonly gameConfig: GameConfig\n readonly simRunsAmount: Partial<Record<GameModeName, number>>\n private readonly concurrency: number\n private wallet: Wallet\n private library: Map<string, Book>\n readonly records: RecordItem[]\n protected debug = false\n\n constructor(opts: SimulationConfigOpts, gameConfigOpts: CommonGameOptions) {\n this.gameConfig = new GameConfig(gameConfigOpts)\n this.gameConfigOpts = gameConfigOpts\n this.simRunsAmount = opts.simRunsAmount || {}\n this.concurrency = (opts.concurrency || 6) >= 2 ? opts.concurrency || 6 : 2\n this.wallet = new Wallet()\n this.library = new Map()\n this.records = []\n\n const gameModeKeys = Object.keys(this.gameConfig.config.gameModes)\n assert(\n Object.values(this.gameConfig.config.gameModes)\n .map((m) => gameModeKeys.includes(m.name))\n .every((v) => v === true),\n \"Game mode name must match its key in the gameModes object.\",\n )\n\n if (isMainThread) {\n this.preprocessFiles()\n }\n }\n\n async runSimulation(opts: SimulationOpts) {\n const debug = opts.debug || false\n this.debug = debug\n\n const gameModesToSimulate = Object.keys(this.simRunsAmount)\n const configuredGameModes = Object.keys(this.gameConfig.config.gameModes)\n\n if (gameModesToSimulate.length === 0) {\n throw new Error(\"No game modes configured for simulation.\")\n }\n\n this.gameConfig.generateReelsetFiles()\n\n // Code that runs when the user executes the simulations.\n // This spawns individual processes and merges the results afterwards.\n if (isMainThread) {\n const debugDetails: Record<string, Record<string, any>> = {}\n\n for (const mode of gameModesToSimulate) {\n completedSimulations = 0\n this.wallet = new Wallet()\n this.library = new Map()\n\n debugDetails[mode] = {}\n\n console.log(`\\nSimulating game mode: ${mode}`)\n console.time(mode)\n\n const runs = this.simRunsAmount[mode] || 0\n\n if (runs <= 0) continue\n\n if (!configuredGameModes.includes(mode)) {\n throw new Error(\n `Tried to simulate game mode \"${mode}\", but it's not configured in the game config.`,\n )\n }\n\n const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode)\n\n await this.spawnWorkersForGameMode({ mode, simNumsToCriteria })\n\n createDirIfNotExists(\n path.join(\n process.cwd(),\n this.gameConfig.config.outputDir,\n \"optimization_files\",\n ),\n )\n createDirIfNotExists(\n path.join(process.cwd(), this.gameConfig.config.outputDir, \"publish_files\"),\n )\n\n Simulation.writeLookupTableCSV({\n gameMode: mode,\n library: this.library,\n gameConfig: this.gameConfig.config,\n })\n Simulation.writeLookupTableSegmentedCSV({\n gameMode: mode,\n library: this.library,\n gameConfig: this.gameConfig.config,\n })\n Simulation.writeRecords({\n gameMode: mode,\n records: this.records,\n gameConfig: this.gameConfig.config,\n debug: this.debug,\n })\n await Simulation.writeBooksJson({\n gameMode: mode,\n library: this.library,\n gameConfig: this.gameConfig.config,\n })\n Simulation.writeIndexJson({\n gameConfig: this.gameConfig.config,\n })\n\n debugDetails[mode].rtp =\n this.wallet.getCumulativeWins() /\n (runs * this.gameConfig.config.gameModes[mode]!.cost)\n\n debugDetails[mode].wins = this.wallet.getCumulativeWins()\n debugDetails[mode].winsPerSpinType = this.wallet.getCumulativeWinsPerSpinType()\n\n console.timeEnd(mode)\n }\n\n console.log(\"\\n=== SIMULATION SUMMARY ===\")\n console.table(debugDetails)\n }\n\n let desiredSims = 0\n let actualSims = 0\n const criteriaToRetries: Record<string, number> = {}\n\n // Code that runs for individual processes\n if (!isMainThread) {\n const { mode, simStart, simEnd, index } = workerData\n\n const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode)\n\n // Run each simulation until the criteria is met.\n for (let simId = simStart; simId <= simEnd; simId++) {\n if (this.debug) desiredSims++\n\n const criteria = simNumsToCriteria[simId] || \"N/A\"\n const ctx = new SimulationContext(this.gameConfigOpts)\n\n if (!criteriaToRetries[criteria]) {\n criteriaToRetries[criteria] = 0\n }\n\n ctx.runSingleSimulation({ simId, mode, criteria, index })\n\n if (this.debug) {\n criteriaToRetries[criteria] += ctx.actualSims - 1\n actualSims += ctx.actualSims\n }\n }\n\n if (this.debug) {\n console.log(`Desired ${desiredSims}, Actual ${actualSims}`)\n console.log(`Retries per criteria:`, criteriaToRetries)\n }\n\n parentPort?.postMessage({\n type: \"done\",\n workerNum: index,\n })\n }\n }\n\n /**\n * Runs all simulations for a specific game mode.\n */\n async spawnWorkersForGameMode(opts: {\n mode: string\n simNumsToCriteria: Record<number, string>\n }) {\n const { mode, simNumsToCriteria } = opts\n\n const numSims = Object.keys(simNumsToCriteria).length\n const simRangesPerChunk = this.getSimRangesForChunks(numSims, this.concurrency!)\n\n await Promise.all(\n simRangesPerChunk.map(([simStart, simEnd], index) => {\n return this.callWorker({\n basePath: this.gameConfig.config.outputDir,\n mode,\n simStart,\n simEnd,\n index,\n totalSims: numSims,\n })\n }),\n )\n }\n\n async callWorker(opts: {\n basePath: string\n mode: string\n simStart: number\n simEnd: number\n index: number\n totalSims: number\n }) {\n const { mode, simEnd, simStart, basePath, index, totalSims } = opts\n\n function logArrowProgress(current: number, total: number) {\n const percentage = (current / total) * 100\n const progressBarLength = 50\n const filledLength = Math.round((progressBarLength * current) / total)\n const bar = \"█\".repeat(filledLength) + \"-\".repeat(progressBarLength - filledLength)\n process.stdout.write(`\\r[${bar}] ${percentage.toFixed(2)}% (${current}/${total})`)\n if (current === total) {\n process.stdout.write(\"\\n\")\n }\n }\n\n return new Promise((resolve, reject) => {\n const scriptPath = path.join(process.cwd(), basePath, TEMP_FILENAME)\n\n const worker = new Worker(scriptPath, {\n workerData: {\n mode,\n simStart,\n simEnd,\n index,\n },\n })\n\n worker.on(\"message\", (msg) => {\n if (msg.type === \"log\") {\n //console.log(`[Worker ${msg.workerNum}] ${msg.message}`)\n } else if (msg.type === \"complete\") {\n completedSimulations++\n\n if (completedSimulations % 250 === 0) {\n logArrowProgress(completedSimulations, totalSims)\n }\n\n // Write data to global library\n const book = Book.fromSerialized(msg.book)\n this.library.set(book.id.toString(), book)\n this.wallet.mergeSerialized(msg.wallet)\n this.mergeRecords(msg.records)\n } else if (msg.type === \"done\") {\n resolve(true)\n }\n })\n\n worker.on(\"error\", (error) => {\n console.error(\"Error:\", error)\n reject(error)\n })\n\n worker.on(\"exit\", (code) => {\n if (code !== 0) {\n reject(new Error(`Worker stopped with exit code ${code}`))\n }\n })\n })\n }\n\n /**\n * Creates a CSV file in the format \"simulationId,weight,payout\".\n *\n * `weight` defaults to 1.\n */\n private static writeLookupTableCSV(opts: {\n gameMode: string\n library: Map<string, Book>\n gameConfig: GameConfig[\"config\"]\n }) {\n const { gameMode, library, gameConfig } = opts\n\n const rows: string[] = []\n\n for (const [bookId, book] of library.entries()) {\n rows.push(`${book.id},1,${Math.round(book.getPayout())}`)\n }\n\n rows.sort((a, b) => Number(a.split(\",\")[0]) - Number(b.split(\",\")[0]))\n\n let outputFileName = `lookUpTable_${gameMode}.csv`\n let outputFilePath = path.join(gameConfig.outputDir, outputFileName)\n writeFile(outputFilePath, rows.join(\"\\n\"))\n\n outputFileName = `lookUpTable_${gameMode}_0.csv`\n outputFilePath = path.join(gameConfig.outputDir, outputFileName)\n writeFile(outputFilePath, rows.join(\"\\n\"))\n\n return outputFilePath\n }\n\n /**\n * Creates a CSV file in the format \"simulationId,criteria,payoutBase,payoutFreespins\".\n */\n private static writeLookupTableSegmentedCSV(opts: {\n gameMode: string\n library: Map<string, Book>\n gameConfig: GameConfig[\"config\"]\n }) {\n const { gameMode, library, gameConfig } = opts\n\n const rows: string[] = []\n\n for (const [bookId, book] of library.entries()) {\n rows.push(\n `${book.id},${book.criteria},${book.getBasegameWins()},${book.getFreespinsWins()}`,\n )\n }\n\n rows.sort((a, b) => Number(a.split(\",\")[0]) - Number(b.split(\",\")[0]))\n\n const outputFileName = `lookUpTableSegmented_${gameMode}.csv`\n\n const outputFilePath = path.join(gameConfig.outputDir, outputFileName)\n writeFile(outputFilePath, rows.join(\"\\n\"))\n\n return outputFilePath\n }\n\n private static writeRecords(opts: {\n gameMode: string\n records: RecordItem[]\n gameConfig: GameConfig[\"config\"]\n fileNameWithoutExtension?: string\n debug?: boolean\n }) {\n const { gameMode, fileNameWithoutExtension, records, gameConfig, debug } = opts\n\n const outputFileName = fileNameWithoutExtension\n ? `${fileNameWithoutExtension}.json`\n : `force_record_${gameMode}.json`\n\n const outputFilePath = path.join(gameConfig.outputDir, outputFileName)\n writeFile(outputFilePath, JSON.stringify(records, null, 2))\n\n if (debug) Simulation.logSymbolOccurrences(records)\n\n return outputFilePath\n }\n\n private static writeIndexJson(opts: { gameConfig: GameConfig[\"config\"] }) {\n const { gameConfig } = opts\n\n const outputFilePath = path.join(\n process.cwd(),\n gameConfig.outputDir,\n \"publish_files\",\n \"index.json\",\n )\n\n const modes = Object.entries(gameConfig.gameModes).map(([mode, modeConfig]) => ({\n name: mode,\n cost: modeConfig.cost,\n events: `books_${mode}.jsonl.zst`,\n weights: `lookUpTable_${mode}_0.csv`,\n }))\n\n writeFile(outputFilePath, JSON.stringify({ modes }, null, 2))\n }\n\n private static async writeBooksJson(opts: {\n gameMode: string\n library: Map<string, Book>\n gameConfig: GameConfig[\"config\"]\n fileNameWithoutExtension?: string\n }) {\n const { gameMode, fileNameWithoutExtension, library, gameConfig } = opts\n\n const outputFileName = fileNameWithoutExtension\n ? `${fileNameWithoutExtension}.jsonl`\n : `books_${gameMode}.jsonl`\n\n const outputFilePath = path.join(gameConfig.outputDir, outputFileName)\n const books = Array.from(library.values())\n .map((b) => b.serialize())\n .map((b) => ({\n id: b.id,\n payoutMultiplier: b.payout,\n events: b.events,\n }))\n .sort((a, b) => a.id - b.id)\n\n const contents = JSONL.stringify(books)\n\n writeFile(outputFilePath, contents)\n\n const compressedFileName = fileNameWithoutExtension\n ? `${fileNameWithoutExtension}.jsonl.zst`\n : `books_${gameMode}.jsonl.zst`\n\n const compressedFilePath = path.join(\n process.cwd(),\n gameConfig.outputDir,\n \"publish_files\",\n compressedFileName,\n )\n\n fs.rmSync(compressedFilePath, { force: true })\n\n const compressed = zlib.zstdCompressSync(Buffer.from(contents))\n fs.writeFileSync(compressedFilePath, compressed)\n }\n\n private static logSymbolOccurrences(records: RecordItem[]) {\n const validRecords = records.filter(\n (r) =>\n r.search.some((s) => s.name === \"symbolId\") &&\n r.search.some((s) => s.name === \"kind\"),\n )\n\n const structuredRecords = validRecords\n .map((r) => {\n const symbolEntry = r.search.find((s) => s.name === \"symbolId\")\n const kindEntry = r.search.find((s) => s.name === \"kind\")\n const spinTypeEntry = r.search.find((s) => s.name === \"spinType\")\n return {\n symbol: symbolEntry ? symbolEntry.value : \"unknown\",\n kind: kindEntry ? kindEntry.value : \"unknown\",\n spinType: spinTypeEntry ? spinTypeEntry.value : \"unknown\",\n timesTriggered: r.timesTriggered,\n }\n })\n .sort((a, b) => {\n if (a.symbol < b.symbol) return -1\n if (a.symbol > b.symbol) return 1\n if (a.kind < b.kind) return -1\n if (a.kind > b.kind) return 1\n if (a.spinType < b.spinType) return -1\n if (a.spinType > b.spinType) return 1\n return 0\n })\n\n console.table(structuredRecords)\n }\n\n /**\n * Compiles user configured game to JS for use in different Node processes\n */\n private preprocessFiles() {\n const builtFilePath = path.join(this.gameConfig.config.outputDir, TEMP_FILENAME)\n fs.rmSync(builtFilePath, { force: true })\n buildSync({\n entryPoints: [process.cwd()],\n bundle: true,\n platform: \"node\",\n outfile: path.join(this.gameConfig.config.outputDir, TEMP_FILENAME),\n external: [\"esbuild\"],\n })\n }\n\n private getSimRangesForChunks(total: number, chunks: number): [number, number][] {\n const base = Math.floor(total / chunks)\n const remainder = total % chunks\n const result: [number, number][] = []\n\n let current = 1\n\n for (let i = 0; i < chunks; i++) {\n const size = base + (i < remainder ? 1 : 0)\n const start = current\n const end = current + size - 1\n result.push([start, end])\n current = end + 1\n }\n\n return result\n }\n\n private mergeRecords(otherRecords: RecordItem[]) {\n for (const otherRecord of otherRecords) {\n let record = this.records.find((r) => {\n if (r.search.length !== otherRecord.search.length) return false\n for (let i = 0; i < r.search.length; i++) {\n if (r.search[i]!.name !== otherRecord.search[i]!.name) return false\n if (r.search[i]!.value !== otherRecord.search[i]!.value) return false\n }\n return true\n })\n if (!record) {\n record = {\n search: otherRecord.search,\n timesTriggered: 0,\n bookIds: [],\n }\n this.records.push(record)\n }\n record.timesTriggered += otherRecord.timesTriggered\n for (const bookId of otherRecord.bookIds) {\n if (!record.bookIds.includes(bookId)) {\n record.bookIds.push(bookId)\n }\n }\n }\n }\n}\n\nexport type SimulationConfigOpts = {\n /**\n * Object containing the game modes and their respective simulation runs amount.\n */\n simRunsAmount: Partial<Record<GameModeName, number>>\n /**\n * Number of concurrent processes to use for simulations.\n *\n * Default: 6\n */\n concurrency?: number\n}\n\n/**\n * @internal\n */\nexport type AnySimulationContext<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> = SimulationContext<TGameModes, TSymbols, TUserState>\n\n/**\n * @internal\n */\nexport class SimulationContext<\n TGameModes extends AnyGameModes,\n TSymbols extends AnySymbols,\n TUserState extends AnyUserData,\n> extends Board<TGameModes, TSymbols, TUserState> {\n constructor(opts: CommonGameOptions<any, any, TUserState>) {\n super(opts)\n }\n\n actualSims = 0\n\n /**\n * Will run a single simulation until the specified criteria is met.\n */\n runSingleSimulation(opts: {\n simId: number\n mode: string\n criteria: string\n index: number\n }) {\n const { simId, mode, criteria, index } = opts\n\n this.state.currentGameMode = mode\n this.state.currentSimulationId = simId\n this.state.isCriteriaMet = false\n\n const resultSet = this.getResultSetByCriteria(this.state.currentGameMode, criteria)\n\n while (!this.state.isCriteriaMet) {\n this.actualSims++\n this.resetSimulation()\n\n this.state.currentResultSet = resultSet\n this.state.book.criteria = resultSet.criteria\n\n this.handleGameFlow()\n\n if (resultSet.meetsCriteria(this)) {\n this.state.isCriteriaMet = true\n }\n }\n\n this.wallet.confirmWins(this as SimulationContext<any, any, any>)\n\n if (this.state.book.getPayout() >= this.config.maxWinX) {\n this.state.triggeredMaxWin = true\n }\n\n this.record({\n criteria: resultSet.criteria,\n })\n\n this.config.hooks.onSimulationAccepted?.(this)\n\n this.confirmRecords()\n\n parentPort?.postMessage({\n type: \"complete\",\n simId,\n book: this.state.book.serialize(),\n wallet: this.wallet.serialize(),\n records: this.getRecords(),\n })\n }\n\n /**\n * If a simulation does not meet the required criteria, reset the state to run it again.\n *\n * This also runs once before each simulation to ensure a clean state.\n */\n protected resetSimulation() {\n this.resetState()\n this.resetBoard()\n }\n\n /**\n * Contains and executes the entire game logic:\n * - Drawing the board\n * - Evaluating wins\n * - Updating wallet\n * - Handling free spins\n * - Recording events\n *\n * You can customize the game flow by implementing the `onHandleGameFlow` hook in the game configuration.\n */\n protected handleGameFlow() {\n this.config.hooks.onHandleGameFlow(this)\n }\n}\n\nexport interface SimulationOpts {\n debug?: boolean\n}\n","import fs from \"fs\"\nimport path from \"path\"\nimport { GameConfig } from \"../GameConfig\"\nimport { Optimizer, OptimzierGameModeConfig } from \"../optimizer\"\nimport assert from \"assert\"\nimport {\n getAvgWin,\n getLessBetHitrate,\n getMaxWin,\n getMaxwinHitrate,\n getMinWin,\n getNonZeroHitrate,\n getNullHitrate,\n getPayoutWeights,\n getRtp,\n getStandardDeviation,\n getTotalLutWeight,\n getUniquePayouts,\n getVariance,\n parseLookupTable,\n} from \"./utils\"\nimport { writeJsonFile } from \"../../utils\"\nimport { isMainThread } from \"worker_threads\"\n\nexport class Analysis {\n protected readonly gameConfig: GameConfig[\"config\"]\n protected readonly optimizerConfig: OptimzierGameModeConfig\n protected filePaths: Record<string, FilePaths>\n\n constructor(optimizer: Optimizer) {\n this.gameConfig = optimizer.getGameConfig()\n this.optimizerConfig = optimizer.getOptimizerGameModes()\n this.filePaths = {}\n }\n\n async runAnalysis(gameModes: string[]) {\n if (!isMainThread) return // IMPORTANT: Prevent workers from kicking off (multiple) analysis runs\n\n this.filePaths = this.getPathsForModes(gameModes)\n this.getNumberStats(gameModes)\n this.getWinRanges(gameModes)\n //this.getSymbolStats(gameModes)\n console.log(\"Analysis complete. Files written to build directory.\")\n }\n\n private getPathsForModes(gameModes: string[]) {\n const rootPath = process.cwd()\n const paths: Record<string, FilePaths> = {}\n\n for (const modeStr of gameModes) {\n const lut = path.join(\n rootPath,\n this.gameConfig.outputDir,\n `lookUpTable_${modeStr}.csv`,\n )\n const lutSegmented = path.join(\n rootPath,\n this.gameConfig.outputDir,\n `lookUpTableSegmented_${modeStr}.csv`,\n )\n const lutOptimized = path.join(\n rootPath,\n this.gameConfig.outputDir,\n \"publish_files\",\n `lookUpTable_${modeStr}_0.csv`,\n )\n const booksJsonl = path.join(\n rootPath,\n this.gameConfig.outputDir,\n `books_${modeStr}.jsonl`,\n )\n const booksJsonlCompressed = path.join(\n rootPath,\n this.gameConfig.outputDir,\n \"publish_files\",\n `books_${modeStr}.jsonl.zst`,\n )\n\n paths[modeStr] = {\n lut,\n lutSegmented,\n lutOptimized,\n booksJsonl,\n booksJsonlCompressed,\n }\n\n for (const p of Object.values(paths[modeStr])) {\n assert(\n fs.existsSync(p),\n `File \"${p}\" does not exist. Run optimization to auto-create it.`,\n )\n }\n }\n\n return paths\n }\n\n private getNumberStats(gameModes: string[]) {\n const stats: Statistics[] = []\n\n for (const modeStr of gameModes) {\n const mode = this.getGameModeConfig(modeStr)\n\n const lutOptimized = parseLookupTable(\n fs.readFileSync(this.filePaths[modeStr]!.lutOptimized, \"utf-8\"),\n )\n const totalWeight = getTotalLutWeight(lutOptimized)\n const payoutWeights = getPayoutWeights(lutOptimized)\n\n stats.push({\n gameMode: mode.name,\n totalWeight,\n avgWin: getAvgWin(payoutWeights),\n rtp: getRtp(payoutWeights, mode.cost),\n minWin: getMinWin(payoutWeights),\n maxWin: getMaxWin(payoutWeights),\n stdDev: getStandardDeviation(payoutWeights),\n variance: getVariance(payoutWeights),\n nonZeroHitRate: getNonZeroHitrate(payoutWeights),\n nullHitRate: getNullHitrate(payoutWeights),\n maxwinHitRate: getMaxwinHitrate(payoutWeights),\n lessBetHitRate: getLessBetHitrate(payoutWeights, mode.cost),\n uniquePayouts: getUniquePayouts(payoutWeights),\n })\n }\n\n writeJsonFile(\n path.join(process.cwd(), this.gameConfig.outputDir, \"stats_summary.json\"),\n stats,\n )\n }\n\n private getWinRanges(gameModes: string[]) {\n const winRanges: [number, number][] = [\n [0, 0.1],\n [0, 0.99],\n [1, 1.99],\n [2, 2.99],\n [3, 4.99],\n [5, 9.99],\n [10, 19.99],\n [20, 49.99],\n [50, 99.99],\n [100, 199.99],\n [200, 499.99],\n [500, 999.99],\n [1000, 1999.99],\n [2000, 2999.99],\n [3000, 4999.99],\n [5000, 7499.99],\n [7500, 9999.99],\n [10000, 14999.99],\n [15000, 19999.99],\n [20000, 24999.99],\n ]\n\n for (const modeStr of gameModes) {\n const mode = this.getGameModeConfig(modeStr)\n\n const lutOptimized = parseLookupTable(\n fs.readFileSync(this.filePaths[modeStr]!.lutOptimized, \"utf-8\"),\n )\n const totalWeight = getTotalLutWeight(lutOptimized)\n const payoutWeights = getPayoutWeights(lutOptimized)\n }\n }\n\n private getGameModeConfig(mode: string) {\n const config = this.gameConfig.gameModes[mode]\n assert(config, `Game mode \"${mode}\" not found in game config`)\n return config\n }\n}\n\nexport interface AnalysisOpts {\n gameModes: string[]\n}\n\ninterface FilePaths {\n lut: string\n lutSegmented: string\n lutOptimized: string\n booksJsonl: string\n booksJsonlCompressed: string\n}\n\ninterface Statistics {\n gameMode: string\n totalWeight: number\n rtp: number\n avgWin: number\n minWin: number\n maxWin: number\n stdDev: number\n variance: number\n maxwinHitRate: number\n nonZeroHitRate: number\n nullHitRate: number\n lessBetHitRate: number\n uniquePayouts: number\n}\n","export function parseLookupTable(content: string) {\n const lines = content.trim().split(\"\\n\")\n const lut: LookupTable = []\n for (const line of lines) {\n const [indexStr, weightStr, payoutStr] = line.split(\",\")\n const index = parseInt(indexStr!.trim())\n const weight = parseInt(weightStr!.trim())\n const payout = parseFloat(payoutStr!.trim())\n lut.push([index, weight, payout])\n }\n return lut\n}\n\nexport type LookupTable = [number, number, number][]\n\nexport function getTotalLutWeight(lut: LookupTable) {\n return lut.reduce((sum, [, weight]) => sum + weight, 0)\n}\n\nexport function getTotalWeight(payoutWeights: PayoutWeights) {\n return Object.values(payoutWeights).reduce((sum, w) => sum + w, 0)\n}\n\ntype PayoutWeights = Record<number, number>\n\nexport function getPayoutWeights(\n lut: LookupTable,\n opts: { normalize?: boolean } = {},\n): PayoutWeights {\n const { normalize = true } = opts\n const totalWeight = getTotalLutWeight(lut)\n\n let payoutWeights: Record<number, number> = {}\n\n for (const [, weight, p] of lut) {\n const payout = p / 100\n if (payoutWeights[payout] === undefined) {\n payoutWeights[payout] = 0\n }\n payoutWeights[payout] += weight\n }\n\n // Sort by payout value\n payoutWeights = Object.fromEntries(\n Object.entries(payoutWeights).sort(([a], [b]) => parseFloat(a) - parseFloat(b)),\n )\n\n if (normalize) {\n for (const payout in payoutWeights) {\n payoutWeights[payout]! /= totalWeight\n }\n }\n\n return payoutWeights\n}\n\nexport function getNonZeroHitrate(payoutWeights: PayoutWeights) {\n const totalWeight = getTotalWeight(payoutWeights)\n if (Math.min(...Object.keys(payoutWeights).map(Number)) == 0) {\n return totalWeight / (totalWeight - (payoutWeights[0] ?? 0) / totalWeight)\n } else {\n return 1\n }\n}\n\nexport function getNullHitrate(payoutWeights: PayoutWeights) {\n return payoutWeights[0] ?? 0\n}\n\nexport function getMaxwinHitrate(payoutWeights: PayoutWeights) {\n const totalWeight = getTotalWeight(payoutWeights)\n const maxWin = Math.max(...Object.keys(payoutWeights).map(Number))\n const hitRate = (payoutWeights[maxWin] || 0) / totalWeight\n return 1 / hitRate\n}\n\nexport function getUniquePayouts(payoutWeights: PayoutWeights) {\n return Object.keys(payoutWeights).length\n}\n\nexport function getMinWin(payoutWeights: PayoutWeights) {\n const payouts = Object.keys(payoutWeights).map(Number)\n return Math.min(...payouts)\n}\n\nexport function getMaxWin(payoutWeights: PayoutWeights) {\n const payouts = Object.keys(payoutWeights).map(Number)\n return Math.max(...payouts)\n}\n\nexport function getAvgWin(payoutWeights: PayoutWeights) {\n let avgWin = 0\n for (const [payoutStr, weight] of Object.entries(payoutWeights)) {\n const payout = parseFloat(payoutStr)\n avgWin += payout * weight\n }\n return avgWin\n}\n\nexport function getRtp(payoutWeights: PayoutWeights, cost: number) {\n const avgWin = getAvgWin(payoutWeights)\n return avgWin / cost\n}\n\nexport function getStandardDeviation(payoutWeights: PayoutWeights) {\n const variance = getVariance(payoutWeights)\n return Math.sqrt(variance)\n}\n\nexport function getVariance(payoutWeights: PayoutWeights) {\n const totalWeight = getTotalWeight(payoutWeights)\n const avgWin = getAvgWin(payoutWeights)\n let variance = 0\n for (const [payoutStr, weight] of Object.entries(payoutWeights)) {\n const payout = parseFloat(payoutStr)\n variance += Math.pow(payout - avgWin, 2) * (weight / totalWeight)\n }\n return variance\n}\n\nexport function getLessBetHitrate(payoutWeights: PayoutWeights, cost: number) {\n let lessBetWeight = 0\n const totalWeight = getTotalWeight(payoutWeights)\n for (const [payoutStr, weight] of Object.entries(payoutWeights)) {\n const payout = parseFloat(payoutStr)\n if (payout < cost) {\n lessBetWeight += weight\n }\n }\n return lessBetWeight / totalWeight\n}\n","import path from \"path\"\nimport { type Optimizer } from \"../optimizer\"\nimport { writeJsonFile } from \"../../utils\"\n\nexport function makeMathConfig(\n optimizer: Optimizer,\n opts: { writeToFile?: boolean } = {},\n) {\n const game = optimizer.getGameConfig()\n const gameModesCfg = optimizer.getOptimizerGameModes()\n const { writeToFile } = opts\n\n const isDefined = <T>(v: T | undefined): v is T => v !== undefined\n\n const config: MathConfig = {\n game_id: game.id,\n bet_modes: Object.entries(game.gameModes).map(([key, mode]) => ({\n bet_mode: mode.name,\n cost: mode.cost,\n rtp: mode.rtp,\n max_win: game.maxWinX,\n })),\n fences: Object.entries(gameModesCfg).map(([gameModeName, modeCfg]) => ({\n bet_mode: gameModeName,\n fences: Object.entries(modeCfg.conditions)\n .map(([fenceName, fence]) => ({\n name: fenceName,\n avg_win: isDefined(fence.getAvgWin())\n ? fence.getAvgWin()!.toString()\n : undefined,\n hr: isDefined(fence.getHitRate()) ? fence.getHitRate()!.toString() : undefined,\n rtp: isDefined(fence.getRtp()) ? fence.getRtp()!.toString() : undefined,\n identity_condition: {\n search: Object.entries(fence.getForceSearch()).map(([k, v]) => ({\n name: k,\n value: v,\n })),\n win_range_start: fence.getSearchRange()[0]!,\n win_range_end: fence.getSearchRange()[1]!,\n opposite: false,\n },\n priority: fence.priority,\n }))\n .sort((a, b) => b.priority - a.priority),\n })),\n dresses: Object.entries(gameModesCfg).flatMap(([gameModeName, modeCfg]) => ({\n bet_mode: gameModeName,\n dresses: modeCfg.scaling.getConfig().map((s) => ({\n fence: s.criteria,\n scale_factor: s.scaleFactor.toString(),\n identity_condition_win_range: s.winRange,\n prob: s.probability,\n })),\n })),\n }\n\n if (writeToFile) {\n const outPath = path.join(process.cwd(), game.outputDir, \"math_config.json\")\n writeJsonFile(outPath, config)\n }\n\n return config\n}\n\nexport type MathConfig = {\n game_id: string\n bet_modes: Array<{\n bet_mode: string\n cost: number\n rtp: number\n max_win: number\n }>\n fences: Array<{\n bet_mode: string\n fences: Array<Fence>\n }>\n dresses: Array<{\n bet_mode: string\n dresses: Dress[]\n }>\n}\n\ninterface Search {\n name: string\n value: string\n}\n\ninterface IdentityCondition {\n search: Search[]\n opposite: boolean\n win_range_start: number\n win_range_end: number\n}\n\ninterface Fence {\n name: string\n avg_win?: string\n rtp?: string\n hr?: string\n identity_condition: IdentityCondition\n priority: number\n}\n\ninterface Dress {\n fence: string\n scale_factor: string\n identity_condition_win_range: [number, number]\n prob: number\n}\n","import path from \"path\"\nimport { writeFile } from \"../../utils\"\nimport { Optimizer } from \"../optimizer\"\n\nexport function makeSetupFile(optimizer: Optimizer, gameMode: string) {\n const gameConfig = optimizer.getGameConfig()\n const optimizerGameModes = optimizer.getOptimizerGameModes()\n const modeConfig = optimizerGameModes[gameMode]\n\n if (!modeConfig) {\n throw new Error(`Game mode \"${gameMode}\" not found in optimizer configuration.`)\n }\n\n const params = modeConfig.parameters.getParameters()\n\n let content = \"\"\n content += `game_name;${gameConfig.id}\\n`\n content += `bet_type;${gameMode}\\n`\n content += `num_show_pigs;${params.numShowPigs}\\n`\n content += `num_pigs_per_fence;${params.numPigsPerFence}\\n`\n content += `threads_for_fence_construction;${params.threadsFenceConstruction}\\n`\n content += `threads_for_show_construction;${params.threadsShowConstruction}\\n`\n content += `score_type;${params.scoreType}\\n`\n content += `test_spins;${JSON.stringify(params.testSpins)}\\n`\n content += `test_spins_weights;${JSON.stringify(params.testSpinsWeights)}\\n`\n content += `simulation_trials;${params.simulationTrials}\\n`\n content += `graph_indexes;0\\n`\n content += `run_1000_batch;False\\n`\n content += `simulation_trials;${params.simulationTrials}\\n`\n content += `user_game_build_path;${path.join(process.cwd(), gameConfig.outputDir)}\\n`\n content += `pmb_rtp;${params.pmbRtp}\\n`\n\n const outPath = path.join(__dirname, \"./optimizer-rust/src\", \"setup.txt\")\n\n writeFile(outPath, content)\n}\n","import { GameConfig, OptimizationConditions, OptimizationScaling } from \"../../index\"\nimport { GameModeName } from \"../GameMode\"\nimport { OptimizationParameters } from \"./OptimizationParameters\"\nimport { makeMathConfig } from \"../utils/math-config\"\nimport { makeSetupFile } from \"../utils/setup-file\"\nimport { spawn } from \"child_process\"\nimport path from \"path\"\nimport { Analysis } from \"../analysis\"\nimport assert from \"assert\"\nimport { isMainThread } from \"worker_threads\"\nimport { SlotGame } from \"../SlotGame\"\n\nexport class Optimizer {\n protected readonly gameConfig: GameConfig[\"config\"]\n protected readonly gameModes: OptimzierGameModeConfig\n\n constructor(opts: OptimizerOpts) {\n this.gameConfig = opts.game.getConfig().config\n this.gameModes = opts.gameModes\n\n this.verifyConfig()\n }\n\n /**\n * Runs the optimization process, and runs analysis after.\n */\n async runOptimization({ gameModes }: OptimizationOpts) {\n if (!isMainThread) return // IMPORTANT: Prevent workers from kicking off (multiple) optimizations\n\n const mathConfig = makeMathConfig(this, { writeToFile: true })\n\n for (const mode of gameModes) {\n const setupFile = makeSetupFile(this, mode)\n await this.runSingleOptimization()\n }\n console.log(\"Optimization complete. Files written to build directory.\")\n }\n\n private async runSingleOptimization() {\n return await rustProgram()\n }\n\n private verifyConfig() {\n for (const [k, mode] of Object.entries(this.gameModes)) {\n const configMode = this.gameConfig.gameModes[k]\n\n if (!configMode) {\n throw new Error(\n `Game mode \"${mode}\" defined in optimizer config does not exist in the game config.`,\n )\n }\n\n const conditions = Object.keys(mode.conditions)\n const scalings = Object.keys(mode.scaling)\n const parameters = Object.keys(mode.parameters)\n\n for (const condition of conditions) {\n if (!configMode.resultSets.find((r) => r.criteria === condition)) {\n throw new Error(\n `Condition \"${condition}\" defined in optimizer config for game mode \"${k}\" does not exist as criteria in any ResultSet of the same game mode.`,\n )\n }\n }\n\n const criteria = configMode.resultSets.map((r) => r.criteria)\n assert(\n conditions.every((c) => criteria.includes(c)),\n `Not all ResultSet criteria in game mode \"${k}\" are defined as optimization conditions.`,\n )\n\n let gameModeRtp = configMode.rtp\n let paramRtp = 0\n for (const cond of conditions) {\n const paramConfig = mode.conditions[cond]!\n paramRtp += Number(paramConfig.getRtp())\n }\n\n gameModeRtp = Math.round(gameModeRtp * 1000) / 1000\n paramRtp = Math.round(paramRtp * 1000) / 1000\n\n assert(\n gameModeRtp === paramRtp,\n `Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode \"${k}\".`,\n )\n }\n }\n\n getGameConfig() {\n return this.gameConfig\n }\n\n getOptimizerGameModes() {\n return this.gameModes\n }\n}\n\nasync function rustProgram(...args: string[]) {\n return new Promise((resolve, reject) => {\n const task = spawn(\"cargo\", [\"run\", \"--release\", ...args], {\n shell: true,\n cwd: path.join(__dirname, \"./optimizer-rust\"),\n stdio: \"pipe\",\n })\n task.on(\"error\", (error) => {\n console.error(\"Error:\", error)\n reject(error)\n })\n task.on(\"exit\", () => {\n resolve(true)\n })\n task.on(\"close\", () => {\n resolve(true)\n })\n task.stdout.on(\"data\", (data) => {\n console.log(data.toString())\n })\n task.stderr.on(\"data\", (data) => {\n console.log(data.toString())\n })\n task.stdout.on(\"error\", (data) => {\n console.log(data.toString())\n reject(data.toString())\n })\n })\n}\n\nexport interface OptimizationOpts {\n gameModes: string[]\n}\n\nexport interface OptimizerOpts {\n game: SlotGame<any, any, any>\n gameModes: OptimzierGameModeConfig\n}\n\nexport type OptimzierGameModeConfig = Record<\n GameModeName,\n {\n conditions: Record<string, OptimizationConditions>\n scaling: OptimizationScaling\n parameters: OptimizationParameters\n }\n>\n","import { AnyGameModes, AnySymbols, AnyUserData, CommonGameOptions } from \"../index\"\nimport { GameConfig } from \"./GameConfig\"\nimport { Simulation, SimulationConfigOpts, SimulationOpts } from \"./Simulation\"\nimport { Analysis, AnalysisOpts } from \"./analysis\"\nimport { OptimizationOpts, Optimizer, OptimizerOpts } from \"./optimizer\"\n\n/**\n * SlotGame class that encapsulates the game configuration and state.\\\n * Main entry point for the slot game.\n */\nexport class SlotGame<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> {\n private readonly gameConfigOpts: CommonGameOptions<TGameModes, TSymbols, TUserState>\n private simulation?: Simulation\n private optimizer?: Optimizer\n private analyzer?: Analysis\n\n constructor(config: CommonGameOptions<TGameModes, TSymbols, TUserState>) {\n this.gameConfigOpts = config\n }\n\n /**\n * Sets up the simulation configuration.\\\n * Must be called before `runTasks()`.\n */\n configureSimulation(opts: SimulationConfigOpts) {\n this.simulation = new Simulation(opts, this.gameConfigOpts as any)\n }\n\n /**\n * Sets up the optimization configuration.\\\n * Must be called before `runTasks()`.\n */\n configureOptimization(opts: Pick<OptimizerOpts, \"gameModes\">) {\n this.optimizer = new Optimizer({\n game: this,\n gameModes: opts.gameModes,\n })\n }\n\n /**\n * Runs the simulation based on the configured settings.\n */\n private async runSimulation(opts: SimulationOpts = {}) {\n if (!this.simulation) {\n throw new Error(\n \"Simulation is not configured. Do so by calling configureSimulation() first.\",\n )\n }\n\n await this.simulation.runSimulation(opts)\n }\n\n /**\n * Runs the optimization based on the configured settings.\n */\n private async runOptimization(opts: OptimizationOpts) {\n if (!this.optimizer) {\n throw new Error(\n \"Optimization is not configured. Do so by calling configureOptimization() first.\",\n )\n }\n\n await this.optimizer.runOptimization(opts)\n }\n\n /**\n * Runs the analysis based on the configured settings.\n */\n private async runAnalysis(opts: AnalysisOpts) {\n if (!this.optimizer) {\n throw new Error(\n \"Optimization must be configured to run analysis. Do so by calling configureOptimization() first.\",\n )\n }\n this.analyzer = new Analysis(this.optimizer)\n await this.analyzer.runAnalysis(opts.gameModes)\n }\n\n async runTasks(\n opts: {\n doSimulation?: boolean\n doOptimization?: boolean\n doAnalysis?: boolean\n simulationOpts?: SimulationOpts\n optimizationOpts?: OptimizationOpts\n analysisOpts?: AnalysisOpts\n } = {},\n ) {\n if (!opts.doSimulation && !opts.doOptimization && !opts.doAnalysis) {\n console.log(\"No tasks to run. Enable either simulation, optimization or analysis.\")\n }\n\n if (opts.doSimulation) {\n await this.runSimulation(opts.simulationOpts || {})\n }\n\n if (opts.doOptimization) {\n await this.runOptimization(opts.optimizationOpts || { gameModes: [] })\n }\n\n if (opts.doAnalysis) {\n await this.runAnalysis(opts.analysisOpts || { gameModes: [] })\n }\n }\n\n /**\n * Gets the game configuration.\n */\n getConfig() {\n return new GameConfig(this.gameConfigOpts)\n }\n}\n"],"mappings":"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","names":["assert","import_assert","assert","import_fs","fs","path","fs","import_assert","assert","process","process","import_assert","assert","import_fs","import_path","import_assert","import_worker_threads","assert","path","fs","zlib","import_fs","import_path","import_assert","import_worker_threads","path","assert","fs","import_path","path","import_path","path","import_path","import_assert","import_worker_threads","assert","path"]}
|
|
1
|
+
{"version":3,"sources":["../index.ts","../src/constants.ts","../src/game-config/index.ts","../src/simulation/index.ts","../src/result-set/index.ts","../src/service/index.ts","../src/service/rng.ts","../utils.ts","../src/game-state/index.ts","../src/board/index.ts","../src/game-symbol/index.ts","../src/service/board.ts","../src/service/data.ts","../src/service/game.ts","../src/service/wallet.ts","../src/game-context/index.ts","../src/book/index.ts","../src/recorder/index.ts","../src/wallet/index.ts","../src/analysis/index.ts","../src/analysis/utils.ts","../src/optimizer/index.ts","../src/utils/math-config.ts","../src/utils/setup-file.ts","../src/optimizer/OptimizationConditions.ts","../src/optimizer/OptimizationScaling.ts","../src/optimizer/OptimizationParameters.ts","../src/slot-game/index.ts","../src/createSlotGame.ts","../src/game-mode/index.ts","../src/win-types/index.ts","../src/win-types/LinesWinType.ts","../src/win-types/ClusterWinType.ts","../src/win-types/ManywaysWinType.ts","../src/reel-set/GeneratedReelSet.ts","../src/reel-set/index.ts","../src/reel-set/StaticReelSet.ts","../src/board/StandaloneBoard.ts"],"sourcesContent":["export {\n type InferGameType,\n type AnyUserData,\n type AnyGameModes,\n type AnySymbols,\n type GameHooks,\n type SpinType,\n type Reels,\n} from \"./src/types\"\n\nexport { SPIN_TYPE } from \"./src/constants\"\n\nexport {\n createSlotGame,\n defineGameModes,\n defineSymbols,\n defineUserState,\n} from \"./src/createSlotGame\"\n\nexport { GameMode } from \"./src/game-mode\"\nexport { GameSymbol } from \"./src/game-symbol\"\nexport { ResultSet } from \"./src/result-set\"\n\nexport {\n OptimizationConditions,\n OptimizationParameters,\n OptimizationScaling,\n} from \"./src/optimizer\"\n\nexport { type GameContext } from \"./src/game-context\"\n\nexport { LinesWinType } from \"./src/win-types/LinesWinType\"\nexport { ClusterWinType } from \"./src/win-types/ClusterWinType\"\nexport { ManywaysWinType } from \"./src/win-types/ManywaysWinType\"\n\nexport { GeneratedReelSet } from \"./src/reel-set/GeneratedReelSet\"\nexport { StaticReelSet } from \"./src/reel-set/StaticReelSet\"\n\nexport { StandaloneBoard } from \"./src/board/StandaloneBoard\"\n","export const SPIN_TYPE = {\n BASE_GAME: \"basegame\",\n FREE_SPINS: \"freespins\",\n} as const\n","import assert from \"assert\"\nimport { AnyGameModes, AnySymbols, AnyUserData, GameHooks } from \"../types\"\nimport { SPIN_TYPE } from \"../constants\"\n\nexport interface GameConfigOptions<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> {\n /**\n * The unique identifier of the game, used for configuration and identification.\n */\n id: string\n /**\n * The name of the game, used for display purposes.\n */\n name: string\n /**\n * A GameMode is the core structure of a slot, defining the board,\\\n * bet cost, win type, and other properties.\n */\n gameModes: TGameModes\n /**\n * A list of all symbols that will appear on the reels.\n */\n symbols: TSymbols\n /**\n * A mapping from spin type to scatter counts to the number of free spins awarded.\n *\n * @example\n * ```ts\n * scatterToFreespins: {\n * [SPIN_TYPE.BASE_GAME]: {\n * 3: 10,\n * 4: 12,\n * 5: 15,\n * },\n * [SPIN_TYPE.FREE_SPINS]: {\n * 3: 6,\n * 4: 8,\n * 5: 10,\n * },\n * },\n * ```\n */\n scatterToFreespins: Record<string, Record<number, number>>\n /**\n * If set, this will pad the board with symbols on the top and bottom of the reels.\\\n * Useful for teasing symbols right above or below the active board.\n *\n * Default: 1\n */\n padSymbols?: number\n /**\n * The maximum win multiplier of the game, e.g. 5000 for a 5000x max win.\n */\n maxWinX: number\n /**\n * Custom additional state that can be used in game flow logic.\n */\n userState?: TUserState\n /**\n * Hooks are used to inject custom logic at specific points in the game flow.\\\n * Some required hooks must be implemented for certain features to work.\n */\n hooks: GameHooks<TGameModes, TSymbols, TUserState>\n}\n\nexport function createGameConfig<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n>(opts: GameConfigOptions<TGameModes, TSymbols, TUserState>) {\n const symbols = new Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>()\n\n for (const [key, value] of Object.entries(opts.symbols)) {\n assert(value.id === key, `Symbol key \"${key}\" does not match symbol id \"${value.id}\"`)\n symbols.set(key, value as TSymbols[keyof TSymbols])\n }\n\n const getAnticipationTrigger = (spinType: string) => {\n return Math.min(...Object.keys(opts.scatterToFreespins[spinType]!).map(Number)) - 1\n }\n\n return {\n padSymbols: opts.padSymbols || 1,\n userState: opts.userState || ({} as TUserState),\n ...opts,\n symbols,\n anticipationTriggers: {\n [SPIN_TYPE.BASE_GAME]: getAnticipationTrigger(SPIN_TYPE.BASE_GAME),\n [SPIN_TYPE.FREE_SPINS]: getAnticipationTrigger(SPIN_TYPE.FREE_SPINS),\n },\n outputDir: \"__build__\",\n }\n}\n\nexport type GameConfig<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> = Required<Omit<GameConfigOptions<TGameModes, TSymbols, TUserState>, \"symbols\">> & {\n /**\n * A map of all symbols.\n */\n symbols: Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>\n outputDir: string\n /**\n * A mapping of spin types to the number of scatter symbols required to trigger anticipation.\n */\n anticipationTriggers: Record<(typeof SPIN_TYPE)[keyof typeof SPIN_TYPE], number>\n}\n","import fs from \"fs\"\nimport path from \"path\"\nimport assert from \"assert\"\nimport zlib from \"zlib\"\nimport { buildSync } from \"esbuild\"\nimport { Worker, isMainThread, parentPort, workerData } from \"worker_threads\"\nimport { createGameConfig, GameConfigOptions, GameConfig } from \"../game-config\"\nimport { createGameContext, GameContext } from \"../game-context\"\nimport { createDirIfNotExists, JSONL, writeFile } from \"../../utils\"\nimport { SPIN_TYPE } from \"../constants\"\nimport { Book } from \"../book\"\nimport { Recorder, RecordItem } from \"../recorder\"\nimport { Wallet } from \"../wallet\"\nimport { ResultSet } from \"../result-set\"\n\nlet completedSimulations = 0\nconst TEMP_FILENAME = \"__temp_compiled_src_IGNORE.js\"\n\nexport class Simulation {\n readonly gameConfigOpts: GameConfigOptions\n readonly gameConfig: GameConfig\n readonly simRunsAmount: Partial<Record<string, number>>\n readonly concurrency: number\n private debug = false\n private actualSims = 0\n private library: Map<number, Book>\n private recorder: Recorder\n private wallet: Wallet\n\n constructor(opts: SimulationOptions, gameConfigOpts: GameConfigOptions) {\n this.gameConfig = createGameConfig(gameConfigOpts)\n this.gameConfigOpts = gameConfigOpts\n this.simRunsAmount = opts.simRunsAmount || {}\n this.concurrency = (opts.concurrency || 6) >= 2 ? opts.concurrency || 6 : 2\n this.library = new Map()\n this.recorder = new Recorder()\n this.wallet = new Wallet()\n\n const gameModeKeys = Object.keys(this.gameConfig.gameModes)\n assert(\n Object.values(this.gameConfig.gameModes)\n .map((m) => gameModeKeys.includes(m.name))\n .every((v) => v === true),\n \"Game mode name must match its key in the gameModes object.\",\n )\n\n if (isMainThread) {\n this.preprocessFiles()\n }\n }\n\n async runSimulation(opts: SimulationConfigOptions) {\n const debug = opts.debug || false\n this.debug = debug\n\n const gameModesToSimulate = Object.keys(this.simRunsAmount)\n const configuredGameModes = Object.keys(this.gameConfig.gameModes)\n\n if (gameModesToSimulate.length === 0) {\n throw new Error(\"No game modes configured for simulation.\")\n }\n\n this.generateReelsetFiles()\n\n // Code that runs when the user executes the simulations.\n // This spawns individual processes and merges the results afterwards.\n if (isMainThread) {\n const debugDetails: Record<string, Record<string, any>> = {}\n\n for (const mode of gameModesToSimulate) {\n completedSimulations = 0\n this.wallet = new Wallet()\n this.library = new Map()\n this.recorder = new Recorder()\n\n debugDetails[mode] = {}\n\n console.log(`\\nSimulating game mode: ${mode}`)\n console.time(mode)\n\n const runs = this.simRunsAmount[mode] || 0\n\n if (runs <= 0) continue\n\n if (!configuredGameModes.includes(mode)) {\n throw new Error(\n `Tried to simulate game mode \"${mode}\", but it's not configured in the game config.`,\n )\n }\n\n const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode)\n\n await this.spawnWorkersForGameMode({ mode, simNumsToCriteria })\n\n createDirIfNotExists(\n path.join(process.cwd(), this.gameConfig.outputDir, \"optimization_files\"),\n )\n createDirIfNotExists(\n path.join(process.cwd(), this.gameConfig.outputDir, \"publish_files\"),\n )\n\n this.writeLookupTableCSV(mode)\n this.writeLookupTableSegmentedCSV(mode)\n this.writeRecords(mode)\n await this.writeBooksJson(mode)\n this.writeIndexJson()\n\n debugDetails[mode].rtp =\n this.wallet.getCumulativeWins() / (runs * this.gameConfig.gameModes[mode]!.cost)\n\n debugDetails[mode].wins = this.wallet.getCumulativeWins()\n debugDetails[mode].winsPerSpinType = this.wallet.getCumulativeWinsPerSpinType()\n\n console.timeEnd(mode)\n }\n\n console.log(\"\\n=== SIMULATION SUMMARY ===\")\n console.table(debugDetails)\n }\n\n let desiredSims = 0\n let actualSims = 0\n const criteriaToRetries: Record<string, number> = {}\n\n // Code that runs for individual processes\n if (!isMainThread) {\n const { mode, simStart, simEnd, index } = workerData\n\n const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode)\n\n // Run each simulation until the criteria is met.\n for (let simId = simStart; simId <= simEnd; simId++) {\n if (this.debug) desiredSims++\n\n const criteria = simNumsToCriteria[simId] || \"N/A\"\n\n if (!criteriaToRetries[criteria]) {\n criteriaToRetries[criteria] = 0\n }\n\n this.runSingleSimulation({ simId, mode, criteria, index })\n\n if (this.debug) {\n criteriaToRetries[criteria] += this.actualSims - 1\n actualSims += this.actualSims\n }\n }\n\n if (this.debug) {\n console.log(`Desired ${desiredSims}, Actual ${actualSims}`)\n console.log(`Retries per criteria:`, criteriaToRetries)\n }\n\n parentPort?.postMessage({\n type: \"done\",\n workerNum: index,\n })\n }\n }\n\n /**\n * Runs all simulations for a specific game mode.\n */\n async spawnWorkersForGameMode(opts: {\n mode: string\n simNumsToCriteria: Record<number, string>\n }) {\n const { mode, simNumsToCriteria } = opts\n\n const numSims = Object.keys(simNumsToCriteria).length\n const simRangesPerChunk = this.getSimRangesForChunks(numSims, this.concurrency!)\n\n await Promise.all(\n simRangesPerChunk.map(([simStart, simEnd], index) => {\n return this.callWorker({\n basePath: this.gameConfig.outputDir,\n mode,\n simStart,\n simEnd,\n index,\n totalSims: numSims,\n })\n }),\n )\n }\n\n async callWorker(opts: {\n basePath: string\n mode: string\n simStart: number\n simEnd: number\n index: number\n totalSims: number\n }) {\n const { mode, simEnd, simStart, basePath, index, totalSims } = opts\n\n function logArrowProgress(current: number, total: number) {\n const percentage = (current / total) * 100\n const progressBarLength = 50\n const filledLength = Math.round((progressBarLength * current) / total)\n const bar = \"█\".repeat(filledLength) + \"-\".repeat(progressBarLength - filledLength)\n process.stdout.write(`\\r[${bar}] ${percentage.toFixed(2)}% (${current}/${total})`)\n if (current === total) {\n process.stdout.write(\"\\n\")\n }\n }\n\n return new Promise((resolve, reject) => {\n const scriptPath = path.join(process.cwd(), basePath, TEMP_FILENAME)\n\n const worker = new Worker(scriptPath, {\n workerData: {\n mode,\n simStart,\n simEnd,\n index,\n },\n })\n\n worker.on(\"message\", (msg) => {\n if (msg.type === \"log\") {\n //console.log(`[Worker ${msg.workerNum}] ${msg.message}`)\n } else if (msg.type === \"complete\") {\n completedSimulations++\n\n if (completedSimulations % 250 === 0) {\n logArrowProgress(completedSimulations, totalSims)\n }\n\n // Write data to global library\n const book = Book.fromSerialized(msg.book)\n this.library.set(book.id, book)\n this.wallet.mergeSerialized(msg.wallet)\n this.mergeRecords(msg.records)\n } else if (msg.type === \"done\") {\n resolve(true)\n }\n })\n\n worker.on(\"error\", (error) => {\n console.error(\"Error:\", error)\n reject(error)\n })\n\n worker.on(\"exit\", (code) => {\n if (code !== 0) {\n reject(new Error(`Worker stopped with exit code ${code}`))\n }\n })\n })\n }\n\n /**\n * Will run a single simulation until the specified criteria is met.\n */\n runSingleSimulation(opts: {\n simId: number\n mode: string\n criteria: string\n index: number\n }) {\n const { simId, mode, criteria } = opts\n\n const ctx = createGameContext({\n config: this.gameConfig,\n })\n\n ctx.state.currentGameMode = mode\n ctx.state.currentSimulationId = simId\n ctx.state.isCriteriaMet = false\n\n const resultSet = ctx.services.game.getResultSetByCriteria(\n ctx.state.currentGameMode,\n criteria,\n )\n\n while (!ctx.state.isCriteriaMet) {\n this.actualSims++\n this.resetSimulation(ctx)\n\n ctx.state.currentResultSet = resultSet\n\n this.handleGameFlow(ctx)\n\n if (resultSet.meetsCriteria(ctx)) {\n ctx.state.isCriteriaMet = true\n }\n }\n\n ctx.services.wallet._getWallet().writePayoutToBook(ctx)\n ctx.services.wallet._getWallet().confirmWins(ctx)\n\n if (ctx.services.data._getBook().payout >= ctx.config.maxWinX) {\n ctx.state.triggeredMaxWin = true\n }\n\n ctx.services.data.record({\n criteria: resultSet.criteria,\n })\n\n ctx.config.hooks.onSimulationAccepted?.(ctx)\n\n this.confirmRecords(ctx)\n\n parentPort?.postMessage({\n type: \"complete\",\n simId,\n book: ctx.services.data._getBook().serialize(),\n wallet: ctx.services.wallet._getWallet().serialize(),\n records: ctx.services.data._getRecords(),\n })\n }\n\n /**\n * If a simulation does not meet the required criteria, reset the state to run it again.\n *\n * This also runs once before each simulation to ensure a clean state.\n */\n protected resetSimulation(ctx: GameContext) {\n this.resetState(ctx)\n ctx.services.board.resetBoard()\n ctx.services.data._setRecorder(new Recorder())\n ctx.services.wallet._setWallet(new Wallet())\n ctx.services.data._setBook(\n new Book({\n id: ctx.state.currentSimulationId,\n criteria: ctx.state.currentResultSet.criteria,\n }),\n )\n }\n\n protected resetState(ctx: GameContext) {\n ctx.services.rng.setSeedIfDifferent(ctx.state.currentSimulationId)\n ctx.state.currentSpinType = SPIN_TYPE.BASE_GAME\n ctx.state.currentFreespinAmount = 0\n ctx.state.totalFreespinAmount = 0\n ctx.state.triggeredMaxWin = false\n ctx.state.triggeredFreespins = false\n ctx.state.userData = ctx.config.userState || {}\n }\n\n /**\n * Contains and executes the entire game logic:\n * - Drawing the board\n * - Evaluating wins\n * - Updating wallet\n * - Handling free spins\n * - Recording events\n *\n * You can customize the game flow by implementing the `onHandleGameFlow` hook in the game configuration.\n */\n protected handleGameFlow(ctx: GameContext) {\n this.gameConfig.hooks.onHandleGameFlow(ctx)\n }\n\n /**\n * Creates a CSV file in the format \"simulationId,weight,payout\".\n *\n * `weight` defaults to 1.\n */\n private writeLookupTableCSV(gameMode: string) {\n const rows: string[] = []\n\n for (const [bookId, book] of this.library.entries()) {\n rows.push(`${book.id},1,${Math.round(book.payout)}`)\n }\n\n rows.sort((a, b) => Number(a.split(\",\")[0]) - Number(b.split(\",\")[0]))\n\n let outputFileName = `lookUpTable_${gameMode}.csv`\n let outputFilePath = path.join(this.gameConfig.outputDir, outputFileName)\n writeFile(outputFilePath, rows.join(\"\\n\"))\n\n outputFileName = `lookUpTable_${gameMode}_0.csv`\n outputFilePath = path.join(this.gameConfig.outputDir, \"publish_files\", outputFileName)\n writeFile(outputFilePath, rows.join(\"\\n\"))\n\n return outputFilePath\n }\n\n /**\n * Creates a CSV file in the format \"simulationId,criteria,payoutBase,payoutFreespins\".\n */\n private writeLookupTableSegmentedCSV(gameMode: string) {\n const rows: string[] = []\n\n for (const [bookId, book] of this.library.entries()) {\n rows.push(`${book.id},${book.criteria},${book.basegameWins},${book.freespinsWins}`)\n }\n\n rows.sort((a, b) => Number(a.split(\",\")[0]) - Number(b.split(\",\")[0]))\n\n const outputFileName = `lookUpTableSegmented_${gameMode}.csv`\n\n const outputFilePath = path.join(this.gameConfig.outputDir, outputFileName)\n writeFile(outputFilePath, rows.join(\"\\n\"))\n\n return outputFilePath\n }\n\n private writeRecords(gameMode: string) {\n const outputFileName = `force_record_${gameMode}.json`\n const outputFilePath = path.join(this.gameConfig.outputDir, outputFileName)\n writeFile(outputFilePath, JSON.stringify(this.recorder.records, null, 2))\n\n if (this.debug) this.logSymbolOccurrences()\n\n return outputFilePath\n }\n\n private writeIndexJson() {\n const outputFilePath = path.join(\n process.cwd(),\n this.gameConfig.outputDir,\n \"publish_files\",\n \"index.json\",\n )\n\n const modes = Object.entries(this.gameConfig.gameModes).map(([mode, modeConfig]) => ({\n name: mode,\n cost: modeConfig.cost,\n events: `books_${mode}.jsonl.zst`,\n weights: `lookUpTable_${mode}_0.csv`,\n }))\n\n writeFile(outputFilePath, JSON.stringify({ modes }, null, 2))\n }\n\n private async writeBooksJson(gameMode: string) {\n const outputFileName = `books_${gameMode}.jsonl`\n\n const outputFilePath = path.join(this.gameConfig.outputDir, outputFileName)\n const books = Array.from(this.library.values())\n .map((b) => b.serialize())\n .map((b) => ({\n id: b.id,\n payoutMultiplier: b.payout,\n events: b.events,\n }))\n .sort((a, b) => a.id - b.id)\n\n const contents = JSONL.stringify(books)\n\n writeFile(outputFilePath, contents)\n\n const compressedFileName = `books_${gameMode}.jsonl.zst`\n\n const compressedFilePath = path.join(\n process.cwd(),\n this.gameConfig.outputDir,\n \"publish_files\",\n compressedFileName,\n )\n\n fs.rmSync(compressedFilePath, { force: true })\n\n const compressed = zlib.zstdCompressSync(Buffer.from(contents))\n fs.writeFileSync(compressedFilePath, compressed)\n }\n\n private logSymbolOccurrences() {\n const validRecords = this.recorder.records.filter(\n (r) =>\n r.search.some((s) => s.name === \"symbolId\") &&\n r.search.some((s) => s.name === \"kind\"),\n )\n\n const structuredRecords = validRecords\n .map((r) => {\n const symbolEntry = r.search.find((s) => s.name === \"symbolId\")\n const kindEntry = r.search.find((s) => s.name === \"kind\")\n const spinTypeEntry = r.search.find((s) => s.name === \"spinType\")\n return {\n symbol: symbolEntry ? symbolEntry.value : \"unknown\",\n kind: kindEntry ? kindEntry.value : \"unknown\",\n spinType: spinTypeEntry ? spinTypeEntry.value : \"unknown\",\n timesTriggered: r.timesTriggered,\n }\n })\n .sort((a, b) => {\n if (a.symbol < b.symbol) return -1\n if (a.symbol > b.symbol) return 1\n if (a.kind < b.kind) return -1\n if (a.kind > b.kind) return 1\n if (a.spinType < b.spinType) return -1\n if (a.spinType > b.spinType) return 1\n return 0\n })\n\n console.table(structuredRecords)\n }\n\n /**\n * Compiles user configured game to JS for use in different Node processes\n */\n private preprocessFiles() {\n const builtFilePath = path.join(this.gameConfig.outputDir, TEMP_FILENAME)\n fs.rmSync(builtFilePath, { force: true })\n buildSync({\n entryPoints: [process.cwd()],\n bundle: true,\n platform: \"node\",\n outfile: path.join(this.gameConfig.outputDir, TEMP_FILENAME),\n external: [\"esbuild\"],\n })\n }\n\n private getSimRangesForChunks(total: number, chunks: number): [number, number][] {\n const base = Math.floor(total / chunks)\n const remainder = total % chunks\n const result: [number, number][] = []\n\n let current = 1\n\n for (let i = 0; i < chunks; i++) {\n const size = base + (i < remainder ? 1 : 0)\n const start = current\n const end = current + size - 1\n result.push([start, end])\n current = end + 1\n }\n\n return result\n }\n\n private mergeRecords(otherRecords: RecordItem[]) {\n for (const otherRecord of otherRecords) {\n let record = this.recorder.records.find((r) => {\n if (r.search.length !== otherRecord.search.length) return false\n for (let i = 0; i < r.search.length; i++) {\n if (r.search[i]!.name !== otherRecord.search[i]!.name) return false\n if (r.search[i]!.value !== otherRecord.search[i]!.value) return false\n }\n return true\n })\n if (!record) {\n record = {\n search: otherRecord.search,\n timesTriggered: 0,\n bookIds: [],\n }\n this.recorder.records.push(record)\n }\n record.timesTriggered += otherRecord.timesTriggered\n for (const bookId of otherRecord.bookIds) {\n if (!record.bookIds.includes(bookId)) {\n record.bookIds.push(bookId)\n }\n }\n }\n }\n\n /**\n * Generates reelset CSV files for all game modes.\n */\n private generateReelsetFiles() {\n for (const mode of Object.values(this.gameConfig.gameModes)) {\n if (mode.reelSets && mode.reelSets.length > 0) {\n for (const reelSet of Object.values(mode.reelSets)) {\n reelSet.associatedGameModeName = mode.name\n reelSet.generateReels(this)\n }\n } else {\n throw new Error(\n `Game mode \"${mode.name}\" has no reel sets defined. Cannot generate reelset files.`,\n )\n }\n }\n }\n\n /**\n * Confirms all pending records and adds them to the main records list.\n */\n confirmRecords(ctx: GameContext) {\n const recorder = ctx.services.data._getRecorder()\n\n for (const pendingRecord of recorder.pendingRecords) {\n const search = Object.entries(pendingRecord.properties)\n .map(([name, value]) => ({ name, value }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n let record = recorder.records.find((r) => {\n if (r.search.length !== search.length) return false\n for (let i = 0; i < r.search.length; i++) {\n if (r.search[i]!.name !== search[i]!.name) return false\n if (r.search[i]!.value !== search[i]!.value) return false\n }\n return true\n })\n if (!record) {\n record = {\n search,\n timesTriggered: 0,\n bookIds: [],\n }\n recorder.records.push(record)\n }\n record.timesTriggered++\n if (!record.bookIds.includes(pendingRecord.bookId)) {\n record.bookIds.push(pendingRecord.bookId)\n }\n }\n\n recorder.pendingRecords = []\n }\n}\n\nexport type SimulationOptions = {\n /**\n * Object containing the game modes and their respective simulation runs amount.\n */\n simRunsAmount: Partial<Record<string, number>>\n /**\n * Number of concurrent processes to use for simulations.\n *\n * Default: 6\n */\n concurrency?: number\n}\n\nexport type SimulationConfigOptions = {\n debug?: boolean\n}\n","import assert from \"assert\"\nimport { AnyGameModes, AnySymbols, AnyUserData } from \"../types\"\nimport { GameContext } from \"../game-context\"\nimport { Simulation } from \"../simulation\"\nimport { RandomNumberGenerator } from \"../service/rng\"\nimport { copy } from \"../../utils\"\nimport { Wallet } from \"../wallet\"\nimport { SPIN_TYPE } from \"../constants\"\n\nexport class ResultSet<TUserState extends AnyUserData> {\n criteria: string\n quota: number\n multiplier?: number\n reelWeights: ReelWeights<TUserState>\n userData?: Record<string, any>\n forceMaxWin?: boolean\n forceFreespins?: boolean\n evaluate?: (ctx: GameContext<AnyGameModes, AnySymbols, TUserState>) => boolean\n\n constructor(opts: ResultSetOpts<TUserState>) {\n this.criteria = opts.criteria\n this.quota = opts.quota\n this.multiplier = opts.multiplier\n this.reelWeights = opts.reelWeights\n this.userData = opts.userData\n this.forceMaxWin = opts.forceMaxWin\n this.forceFreespins = opts.forceFreespins\n this.evaluate = opts.evaluate\n }\n\n static assignCriteriaToSimulations(ctx: Simulation, gameModeName: string) {\n const rng = new RandomNumberGenerator()\n rng.setSeed(0)\n\n assert(ctx.simRunsAmount, \"Simulation configuration is not set.\")\n\n const simNums = ctx.simRunsAmount[gameModeName]\n const resultSets = ctx.gameConfig.gameModes[gameModeName]?.resultSets\n\n if (!resultSets || resultSets.length === 0) {\n throw new Error(`No ResultSets found for game mode: ${gameModeName}.`)\n }\n\n if (simNums === undefined || simNums <= 0) {\n throw new Error(`No simulations configured for game mode \"${gameModeName}\".`)\n }\n\n const totalQuota = resultSets.reduce((sum, rs) => sum + rs.quota, 0)\n\n const numberOfSimsForCriteria: Record<string, number> = Object.fromEntries(\n resultSets.map((rs) => {\n const normalizedQuota = totalQuota > 0 ? rs.quota / totalQuota : 0\n return [rs.criteria, Math.max(Math.floor(normalizedQuota * simNums), 1)]\n }),\n )\n\n let totalSims = Object.values(numberOfSimsForCriteria).reduce(\n (sum, num) => sum + num,\n 0,\n )\n\n let reduceSims = totalSims > simNums\n\n const criteriaToWeights = Object.fromEntries(\n resultSets.map((rs) => [rs.criteria, rs.quota]),\n )\n\n while (totalSims != simNums) {\n const rs = rng.weightedRandom(criteriaToWeights)\n if (reduceSims && numberOfSimsForCriteria[rs]! > 1) {\n numberOfSimsForCriteria[rs]! -= 1\n } else if (!reduceSims) {\n numberOfSimsForCriteria[rs]! += 1\n }\n\n totalSims = Object.values(numberOfSimsForCriteria).reduce(\n (sum, num) => sum + num,\n 0,\n )\n reduceSims = totalSims > simNums\n }\n\n let allCriteria: string[] = []\n const simNumsToCriteria: Record<number, string> = {}\n\n Object.entries(numberOfSimsForCriteria).forEach(([criteria, num]) => {\n for (let i = 0; i <= num; i++) {\n allCriteria.push(criteria)\n }\n })\n\n allCriteria = rng.shuffle(allCriteria)\n\n for (let i = 1; i <= Math.min(simNums, allCriteria.length); i++) {\n simNumsToCriteria[i] = allCriteria[i]!\n }\n\n return simNumsToCriteria\n }\n\n /**\n * Checks if core criteria is met, e.g. target multiplier or max win.\n */\n meetsCriteria(ctx: GameContext) {\n // @ts-ignore TODO: Fix type errors with AnyTypes\n const customEval = this.evaluate?.(copy(ctx))\n\n const freespinsMet = this.forceFreespins ? ctx.state.triggeredFreespins : true\n\n const wallet = ctx.services.wallet._getWallet()\n\n const multiplierMet =\n this.multiplier !== undefined\n ? wallet.getCurrentWin() === this.multiplier && !this.forceMaxWin\n : wallet.getCurrentWin() > 0 && (!this.forceMaxWin || true)\n\n const maxWinMet = this.forceMaxWin\n ? wallet.getCurrentWin() >= ctx.config.maxWinX\n : true\n\n const coreCriteriaMet = freespinsMet && multiplierMet && maxWinMet\n\n const finalResult =\n customEval !== undefined ? coreCriteriaMet && customEval === true : coreCriteriaMet\n\n if (this.forceMaxWin && maxWinMet) {\n ctx.services.data.record({\n maxwin: true,\n })\n }\n\n return finalResult\n }\n}\n\ninterface ResultSetOpts<TUserState extends AnyUserData> {\n /**\n * A short string to describe the criteria for this ResultSet.\n */\n criteria: string\n /**\n * The quota of spins, out of the total simulations, that must be forced to meet the specified criteria.\\\n * **Float from 0 to 1. Total quota of all ResultSets in a GameMode must be 1.**\n */\n quota: number\n /**\n * The required multiplier for a simulated spin to be accepted.\n */\n multiplier?: number\n /**\n * Configure the weights of the reels in this ResultSet.\n *\n * If you need to support dynamic / special reel weights based on the simulation context,\\\n * you can provide an `evaluate` function that returns the desired weights.\n *\n * If the `evaluate` function returns a falsy value, the usual spin type based weights will be used.\n *\n * @example\n * ```ts\n * new ResultSet({\n * criteria: \"superFreespins\",\n * quota: 0.05,\n * forceFreespins: true,\n * reelWeights: {\n * [SPIN_TYPE.BASE_GAME]: { base1: 1 },\n * [SPIN_TYPE.FREE_SPINS]: { bonus1: 1, bonus2: 2 },\n * evaluate: (ctx) => {\n * if (ctx.state.userData.triggeredSuperFreespins) {\n * return { superbonus: 1 }\n * }\n * }\n * },\n * userData: { forceSuperFreespins: true },\n * }),\n * ```\n */\n reelWeights: ReelWeights<TUserState>\n /**\n * Optional data to use when evaluating the criteria.\\\n * This can be used to pass additional context or parameters needed for the evaluation.\n */\n userData?: Record<string, any>\n /**\n * If set, this will force the game to always trigger a max win.\n */\n forceMaxWin?: boolean\n /**\n * If set, this will force the game to always trigger free spins.\n */\n forceFreespins?: boolean\n /**\n * Custom function to evaluate if the criteria is met.\n *\n * E.g. use this to check for free spins that upgraded to super free spins\\\n * or other arbitrary simulation criteria.\n */\n evaluate?: (ctx: GameContext<AnyGameModes, AnySymbols, TUserState>) => boolean\n}\n\ninterface ReelWeights<TUserState extends AnyUserData> {\n [SPIN_TYPE.BASE_GAME]: Record<string, number>\n [SPIN_TYPE.FREE_SPINS]: Record<string, number>\n evaluate?: (\n ctx: GameContext<AnyGameModes, AnySymbols, TUserState>,\n ) => Record<string, number> | undefined | null | false\n}\n","import { GameContext } from \"../game-context\"\n\nexport class AbstractService {\n /**\n * Function that returns the current game context.\n */\n protected ctx: () => GameContext\n\n constructor(ctx: () => GameContext) {\n this.ctx = ctx\n }\n}\n","import { AbstractService } from \".\"\nimport { GameContext } from \"../game-context\"\nimport { AnyGameModes, AnySymbols, AnyUserData } from \"../types\"\n\nexport class RngService<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> extends AbstractService {\n protected rng = new RandomNumberGenerator()\n\n constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>) {\n // @ts-ignore TODO: Fix type errors with AnyTypes\n super(ctx)\n }\n\n /**\n * Random weighted selection from a set of items.\n */\n weightedRandom = this.rng.weightedRandom.bind(this.rng)\n\n /**\n * Selects a random item from an array.\n */\n randomItem = this.rng.randomItem.bind(this.rng)\n\n /**\n * Shuffles an array.\n */\n shuffle = this.rng.shuffle.bind(this.rng)\n\n /**\n * Generates a random float between two values.\n */\n randomFloat = this.rng.randomFloat.bind(this.rng)\n\n /**\n * Sets the seed for the RNG.\n */\n setSeedIfDifferent = this.rng.setSeedIfDifferent.bind(this.rng)\n}\n\nexport class RandomNumberGenerator {\n mIdum: number\n mIy: number\n mIv: Array<number>\n NTAB: number\n IA: number\n IM: number\n IQ: number\n IR: number\n NDIV: number\n AM: number\n RNMX: number\n\n protected _currentSeed: number = 0\n\n constructor() {\n this.mIdum = 0\n this.mIy = 0\n this.mIv = []\n\n this.NTAB = 32\n this.IA = 16807\n this.IM = 2147483647\n this.IQ = 127773\n this.IR = 2836\n this.NDIV = 1 + (this.IM - 1) / this.NTAB\n this.AM = 1.0 / this.IM\n this.RNMX = 1.0 - 1.2e-7\n }\n\n getCurrentSeed() {\n return this._currentSeed\n }\n\n protected setCurrentSeed(seed: number) {\n this._currentSeed = seed\n }\n\n setSeed(seed: number): void {\n this.mIdum = seed\n this.setCurrentSeed(seed)\n\n if (seed >= 0) {\n this.mIdum = -seed\n }\n\n this.mIy = 0\n }\n\n setSeedIfDifferent(seed: number) {\n if (this.getCurrentSeed() !== seed) {\n this.setSeed(seed)\n }\n }\n\n generateRandomNumber(): number {\n let k: number\n let j: number\n\n if (this.mIdum <= 0 || this.mIy === 0) {\n if (-this.mIdum < 1) {\n this.mIdum = 1\n } else {\n this.mIdum = -this.mIdum\n }\n\n for (j = this.NTAB + 7; j >= 0; j -= 1) {\n k = Math.floor(this.mIdum / this.IQ)\n this.mIdum = Math.floor(this.IA * (this.mIdum - k * this.IQ) - this.IR * k)\n\n if (this.mIdum < 0) {\n this.mIdum += this.IM\n }\n\n if (j < this.NTAB) {\n this.mIv[j] = this.mIdum\n }\n }\n\n ;[this.mIy as any] = this.mIv\n }\n\n k = Math.floor(this.mIdum / this.IQ)\n this.mIdum = Math.floor(this.IA * (this.mIdum - k * this.IQ) - this.IR * k)\n\n if (this.mIdum < 0) {\n this.mIdum += this.IM\n }\n\n j = Math.floor(this.mIy / this.NDIV)\n\n this.mIy = Math.floor(this.mIv[j] as any)\n this.mIv[j] = this.mIdum\n\n return this.mIy\n }\n\n randomFloat(low: number, high: number): number {\n let float: number = this.AM * this.generateRandomNumber()\n\n if (float > this.RNMX) {\n float = this.RNMX\n }\n\n return float * (high - low) + low\n }\n\n weightedRandom<T extends Record<string, number>>(weights: T) {\n const totalWeight = Object.values(weights).reduce(\n (sum: number, weight) => sum + (weight as number),\n 0,\n )\n const randomValue = this.randomFloat(0, 1) * totalWeight\n\n let cumulativeWeight = 0\n for (const [key, weight] of Object.entries(weights)) {\n cumulativeWeight += weight as number\n if (randomValue < cumulativeWeight) {\n return key\n }\n }\n\n throw new Error(\"No item selected in weighted random selection.\")\n }\n\n randomItem<T>(array: T[]) {\n if (array.length === 0) {\n throw new Error(\"Cannot select a random item from an empty array.\")\n }\n const randomIndex = Math.floor(this.randomFloat(0, 1) * array.length)\n return array[randomIndex]!\n }\n\n shuffle<T>(array: T[]): T[] {\n const newArray = [...array]\n let currentIndex = newArray.length,\n randomIndex\n\n while (currentIndex != 0) {\n randomIndex = Math.floor(this.randomFloat(0, 1) * currentIndex)\n currentIndex--\n ;[newArray[currentIndex] as any, newArray[randomIndex] as any] = [\n newArray[randomIndex],\n newArray[currentIndex],\n ]\n }\n\n return newArray\n }\n}\n","import fs from \"fs\"\nimport { BoardService } from \"./src/service/board\"\n\nexport function createDirIfNotExists(dirPath: string): void {\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true })\n }\n}\n\nexport function writeJsonFile(filePath: string, data: object | any[]) {\n try {\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), {\n encoding: \"utf8\",\n })\n } catch (error) {\n throw new Error(`Failed to write JSON file at ${filePath}: ${error}`)\n }\n}\n\nexport function writeFile(filePath: string, data: string) {\n try {\n fs.writeFileSync(filePath, data, { encoding: \"utf8\" })\n } catch (error) {\n throw new Error(`Failed to write file at ${filePath}: ${error}`)\n }\n}\n\n/**\n * Creates a deep copy of an object or array.\n */\nexport function copy<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj))\n}\n\n/**\n * Prints the board to the console in a readable format.\n */\nexport function printBoard(board: BoardService) {\n const fullBoard = board.getBoardReels().map((reel, ridx) => {\n return [...board.getPaddingTop()[ridx]!, ...reel, ...board.getPaddingBottom()[ridx]!]\n })\n\n const rows = Math.max(...fullBoard.map((reel) => reel.length))\n const cellWidth = 4 // inner width of symbol area\n\n const padSymbol = (sym: string) => {\n if (sym.length > cellWidth) sym = sym.slice(0, cellWidth)\n const left = Math.floor((cellWidth - sym.length) / 2)\n const right = cellWidth - sym.length - left\n return \" \".repeat(left) + sym + \" \".repeat(right)\n }\n\n const maxTop = Math.max(...board.getPaddingTop().map((p) => p?.length ?? 0))\n const maxBottom = Math.max(...board.getPaddingBottom().map((p) => p?.length ?? 0))\n const boardStart = maxTop\n const boardEnd = rows - maxBottom - 1\n\n const makeSeparator = () => {\n return fullBoard.map(() => `═${\"═\".repeat(cellWidth)}═ `).join(\"\")\n }\n\n for (let row = 0; row < rows; row++) {\n if (row === boardStart) {\n console.log(makeSeparator()) // top border of board\n }\n\n let top = \"\"\n let mid = \"\"\n let bot = \"\"\n for (let col = 0; col < fullBoard.length; col++) {\n const sym = fullBoard[col]![row]?.id ?? \" \"\n const padded = padSymbol(sym)\n top += `┌${\"─\".repeat(cellWidth)}┐ `\n mid += `│${padded}│ `\n bot += `└${\"─\".repeat(cellWidth)}┘ `\n }\n\n console.log(top)\n console.log(mid)\n console.log(bot)\n\n if (row === boardEnd) {\n console.log(makeSeparator()) // bottom border of board\n }\n }\n}\n\nexport function weightedAverage(dist: Record<number, number>) {\n const keys = Object.keys(dist).map(Number)\n const values = Object.values(dist)\n\n const totalWeight = round(\n values.reduce((a, b) => a + b, 0),\n 6,\n )\n const weightedSum = keys.reduce((sum, key, i) => sum + key * values[i]!, 0)\n\n return weightedSum / totalWeight\n}\n\nexport class JSONL {\n public static stringify(array: object[]): string {\n return array.map((object) => JSON.stringify(object)).join(\"\\n\")\n }\n\n public static parse<T>(jsonl: string): Array<T> {\n return jsonl\n .split(\"\\n\")\n .filter((s) => s !== \"\")\n .map((str) => JSON.parse(str))\n }\n}\n\nexport function round(value: number, decimals: number) {\n return Number(Math.round(Number(value + \"e\" + decimals)) + \"e-\" + decimals)\n}\n","import { AnyUserData, SpinType } from \"../types\"\nimport { SPIN_TYPE } from \"../constants\"\nimport { ResultSet } from \"../result-set\"\n\nexport interface GameStateOptions<TUserState extends AnyUserData> {\n currentSimulationId: number\n /**\n * e.g. \"base\", \"freespins\", etc. (depending on the game config)\n */\n currentGameMode: string\n /**\n * Spin type constant as defined in `SPIN_TYPE`\n */\n currentSpinType: SpinType\n /**\n * The current ResultSet for the active simulation run.\n */\n currentResultSet: ResultSet<any>\n /**\n * Whether the criteria in the ResultSet for the current simulation has been met.\n */\n isCriteriaMet: boolean\n /**\n * Number of freespins remaining in the current freespin round.\n */\n currentFreespinAmount: number\n /**\n * Total amount of freespins awarded during the active simulation.\n */\n totalFreespinAmount: number\n /**\n * Custom user data that can be used in game flow logic.\n */\n userData: TUserState\n /**\n * Whether a max win has been triggered during the active simulation.\n */\n triggeredMaxWin: boolean\n /**\n * Whether freespins have been triggered during the active simulation.\n */\n triggeredFreespins: boolean\n}\n\nexport function createGameState<TUserState extends AnyUserData = AnyUserData>(\n opts?: Partial<GameStateOptions<TUserState>>,\n) {\n return {\n currentSimulationId: opts?.currentSimulationId || 0,\n currentGameMode: opts?.currentGameMode || \"N/A\",\n currentSpinType: opts?.currentSpinType || SPIN_TYPE.BASE_GAME,\n currentResultSet:\n opts?.currentResultSet ||\n new ResultSet({\n criteria: \"N/A\",\n quota: 0,\n reelWeights: {\n [SPIN_TYPE.BASE_GAME]: {},\n [SPIN_TYPE.FREE_SPINS]: {},\n },\n }),\n isCriteriaMet: opts?.isCriteriaMet || false,\n currentFreespinAmount: opts?.currentFreespinAmount || 0,\n totalFreespinAmount: opts?.totalFreespinAmount || 0,\n userData: opts?.userData || ({} as TUserState),\n triggeredMaxWin: opts?.triggeredMaxWin || false,\n triggeredFreespins: opts?.triggeredFreespins || false,\n }\n}\n\nexport type GameState<TUserState extends AnyUserData = AnyUserData> = ReturnType<\n typeof createGameState<TUserState>\n>\n","import assert from \"assert\"\nimport { GameContext } from \"../game-context\"\nimport { Reels } from \"../types\"\nimport { GameSymbol } from \"../game-symbol\"\n\n/**\n * This general board class is designed to function with or without a game context.\n */\nexport class Board {\n /**\n * The current reels on the board.\\\n * Includes only the visible symbols (without padding).\n */\n reels: Reels\n /**\n * The top padding symbols on the board.\\\n * These are the symbols above the visible area.\n */\n paddingTop: Reels\n /**\n * The bottom padding symbols on the board.\\\n * These are the symbols below the visible area.\n */\n paddingBottom: Reels\n /**\n * The anticipation values for each reel on the board.\\\n * Used for triggering anticipation effects.\n */\n anticipation: boolean[]\n lastDrawnReelStops: number[]\n lastUsedReels: Reels\n\n constructor() {\n this.reels = []\n this.paddingTop = []\n this.paddingBottom = []\n this.anticipation = []\n this.lastDrawnReelStops = []\n this.lastUsedReels = []\n }\n\n makeEmptyReels(opts: { ctx: GameContext; reelsAmount?: number }) {\n const length =\n opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount\n\n assert(length, \"Cannot make empty reels without context or reelsAmount.\")\n\n return Array.from({ length }, () => [])\n }\n\n countSymbolsOnReel(\n symbolOrProperties: GameSymbol | Record<string, any>,\n reelIndex: number,\n ) {\n let total = 0\n\n for (const symbol of this.reels[reelIndex]!) {\n let matches = true\n if (symbolOrProperties instanceof GameSymbol) {\n if (symbol.id !== symbolOrProperties.id) matches = false\n } else {\n for (const [key, value] of Object.entries(symbolOrProperties)) {\n if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {\n matches = false\n break\n }\n }\n }\n if (matches) {\n total++\n }\n }\n\n return total\n }\n\n countSymbolsOnBoard(\n symbolOrProperties: GameSymbol | Record<string, any>,\n ): [number, Record<number, number>] {\n let total = 0\n const onReel: Record<number, number> = {}\n\n for (const [ridx, reel] of this.reels.entries()) {\n for (const symbol of reel) {\n let matches = true\n\n if (symbolOrProperties instanceof GameSymbol) {\n if (symbol.id !== symbolOrProperties.id) matches = false\n } else {\n for (const [key, value] of Object.entries(symbolOrProperties)) {\n if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {\n matches = false\n break\n }\n }\n }\n\n if (matches) {\n total++\n if (onReel[ridx] === undefined) {\n onReel[ridx] = 1\n } else {\n onReel[ridx]++\n }\n }\n }\n }\n\n return [total, onReel]\n }\n\n isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol) {\n for (const reel of this.reels) {\n let count = 0\n for (const sym of reel) {\n if (sym.id === symbol.id) {\n count++\n }\n if (count > 1) {\n return true\n }\n }\n }\n return false\n }\n\n getReelStopsForSymbol(reels: Reels, symbol: GameSymbol) {\n const reelStops: number[][] = []\n for (let ridx = 0; ridx < reels.length; ridx++) {\n const reel = reels[ridx]!\n const positions: number[] = []\n for (let pos = 0; pos < reel.length; pos++) {\n if (reel[pos]!.id === symbol.id) {\n positions.push(pos)\n }\n }\n reelStops.push(positions)\n }\n return reelStops\n }\n\n combineReelStops(opts: {\n ctx: GameContext\n reelsAmount?: number\n reelStops: number[][][]\n }) {\n const reelsAmount =\n opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount\n\n assert(reelsAmount, \"Cannot combine reel stops without context or reelsAmount.\")\n\n const combined: number[][] = []\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n combined[ridx] = []\n for (const stops of opts.reelStops) {\n combined[ridx] = combined[ridx]!.concat(stops[ridx]!)\n }\n }\n return combined\n }\n\n getRandomReelStops(opts: {\n ctx: GameContext\n reelsAmount?: number\n reels: Reels\n reelStops: number[][]\n amount: number\n }) {\n const reelsAmount =\n opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount\n\n assert(reelsAmount, \"Cannot get random reel stops without context or reelsAmount.\")\n\n const symProbsOnReels: number[] = []\n const stopPositionsForReels: Record<number, number> = {}\n\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n symProbsOnReels.push(opts.reelStops[ridx]!.length / opts.reels[ridx]!.length)\n }\n\n while (Object.keys(stopPositionsForReels).length !== opts.amount) {\n const possibleReels: number[] = []\n for (let i = 0; i < reelsAmount; i++) {\n if (symProbsOnReels[i]! > 0) {\n possibleReels.push(i)\n }\n }\n const possibleProbs = symProbsOnReels.filter((p) => p > 0)\n const weights = Object.fromEntries(\n possibleReels.map((ridx, idx) => [ridx, possibleProbs[idx]!]),\n )\n const chosenReel = opts.ctx.services.rng.weightedRandom(weights)\n const chosenStop = opts.ctx.services.rng.randomItem(\n opts.reelStops[Number(chosenReel)]!,\n )\n symProbsOnReels[Number(chosenReel)] = 0\n stopPositionsForReels[Number(chosenReel)] = chosenStop\n }\n\n return stopPositionsForReels\n }\n\n getRandomReelset(ctx: GameContext) {\n const weights = ctx.state.currentResultSet.reelWeights\n const evalWeights = ctx.state.currentResultSet.reelWeights.evaluate?.(ctx)\n\n let reelSetId: string = \"\"\n\n if (evalWeights) {\n reelSetId = ctx.services.rng.weightedRandom(evalWeights)\n } else {\n reelSetId = ctx.services.rng.weightedRandom(weights[ctx.state.currentSpinType]!)\n }\n\n const reelSet = ctx.services.game.getReelsetById(ctx.state.currentGameMode, reelSetId)\n\n return reelSet\n }\n\n resetReels(opts: { ctx: GameContext; reelsAmount?: number }) {\n const length =\n opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount\n\n this.reels = this.makeEmptyReels(opts)\n this.anticipation = Array.from({ length }, () => false)\n this.paddingTop = this.makeEmptyReels(opts)\n this.paddingBottom = this.makeEmptyReels(opts)\n }\n\n drawBoardMixed(opts: {\n ctx: GameContext\n reels: Reels\n forcedStops?: Record<string, number>\n reelsAmount?: number\n symbolsPerReel?: number[]\n padSymbols?: number\n }) {\n this.resetReels(opts)\n\n const reelsAmount =\n opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount\n const symbolsPerReel =\n opts.symbolsPerReel ?? opts.ctx.services.game.getCurrentGameMode().symbolsPerReel\n const padSymbols = opts.padSymbols ?? opts.ctx.config.padSymbols\n\n const finalReelStops: (number | null)[] = Array.from(\n { length: reelsAmount },\n () => null,\n )\n\n if (opts.forcedStops) {\n // Fill in forced stops\n for (const [r, stopPos] of Object.entries(opts.forcedStops)) {\n const reelIdx = Number(r)\n\n const symCount = symbolsPerReel[reelIdx]!\n\n finalReelStops[reelIdx] =\n stopPos - Math.round(opts.ctx.services.rng.randomFloat(0, symCount - 1))\n\n if (finalReelStops[reelIdx]! < 0) {\n finalReelStops[reelIdx] = opts.reels[reelIdx]!.length + finalReelStops[reelIdx]!\n }\n }\n }\n\n // Fill in random stops for reels without a forced stop\n for (let i = 0; i < finalReelStops.length; i++) {\n if (finalReelStops[i] === null) {\n finalReelStops[i] = Math.floor(\n opts.ctx.services.rng.randomFloat(0, opts.reels[i]!.length - 1),\n )\n }\n }\n\n this.lastDrawnReelStops = finalReelStops.map((pos) => pos!) as number[]\n this.lastUsedReels = opts.reels\n\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n const reelPos = finalReelStops[ridx]!\n\n for (let p = padSymbols - 1; p >= 0; p--) {\n const topPos = (reelPos - (p + 1)) % opts.reels[ridx]!.length\n this.paddingTop[ridx]!.push(opts.reels[ridx]![topPos]!)\n const bottomPos = (reelPos + symbolsPerReel[ridx]! + p) % opts.reels[ridx]!.length\n this.paddingBottom[ridx]!.unshift(opts.reels[ridx]![bottomPos]!)\n }\n\n for (let row = 0; row < symbolsPerReel[ridx]!; row++) {\n const symbol = opts.reels[ridx]![(reelPos + row) % opts.reels[ridx]!.length]\n\n if (!symbol) {\n throw new Error(`Failed to get symbol at pos ${reelPos + row} on reel ${ridx}`)\n }\n\n this.reels[ridx]![row] = symbol\n }\n }\n }\n\n tumbleBoard(opts: {\n ctx: GameContext\n symbolsToDelete: Array<{ reelIdx: number; rowIdx: number }>\n reelsAmount?: number\n symbolsPerReel?: number[]\n padSymbols?: number\n }) {\n assert(this.lastDrawnReelStops.length > 0, \"Cannot tumble board before drawing it.\")\n\n const reelsAmount =\n opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount\n const symbolsPerReel =\n opts.symbolsPerReel ?? opts.ctx.services.game.getCurrentGameMode().symbolsPerReel\n const padSymbols = opts.padSymbols ?? opts.ctx.config.padSymbols\n\n if (!opts.ctx && !reelsAmount && !symbolsPerReel) {\n throw new Error(\n \"If ctx is not provided, reelsAmount and symbolsPerReel must be given.\",\n )\n }\n\n const reels = this.lastUsedReels\n\n opts.symbolsToDelete.forEach(({ reelIdx, rowIdx }) => {\n this.reels[reelIdx]!.splice(rowIdx, 1)\n })\n\n const newFirstSymbolPositions: Record<number, number> = {}\n\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n // Drop down padding symbols from top\n while (this.reels[ridx]!.length < symbolsPerReel[ridx]!) {\n const padSymbol = this.paddingTop[ridx]!.pop()\n if (padSymbol) {\n this.reels[ridx]!.unshift(padSymbol)\n } else {\n break\n }\n }\n\n const previousStop = this.lastDrawnReelStops[ridx]!\n const stopBeforePad = previousStop - padSymbols - 1\n const symbolsNeeded = symbolsPerReel[ridx]! - this.reels[ridx]!.length\n // Drop rest of symbols\n for (let s = 0; s < symbolsNeeded; s++) {\n const symbolPos = (stopBeforePad - s + reels[ridx]!.length) % reels[ridx]!.length\n const newSymbol = reels[ridx]![symbolPos]\n\n assert(newSymbol, \"Failed to get new symbol for tumbling.\")\n\n this.reels[ridx]!.unshift(newSymbol)\n newFirstSymbolPositions[ridx] = symbolPos\n }\n }\n\n // Add new padding top symbols\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n const firstSymbolPos = newFirstSymbolPositions[ridx]!\n for (let p = 1; p <= padSymbols; p++) {\n const topPos = (firstSymbolPos - p + reels[ridx]!.length) % reels[ridx]!.length\n const padSymbol = reels[ridx]![topPos]\n\n assert(padSymbol, \"Failed to get new padding symbol for tumbling.\")\n\n this.paddingTop[ridx]!.unshift(padSymbol)\n }\n }\n }\n}\n","export class GameSymbol {\n readonly id: string\n readonly pays?: Record<number, number>\n readonly properties: Map<string, any>\n\n constructor(opts: GameSymbolOpts) {\n this.id = opts.id\n this.pays = opts.pays\n this.properties = new Map<string, any>(Object.entries(opts.properties || {}))\n\n if (this.pays && Object.keys(this.pays).length === 0) {\n throw new Error(`GameSymbol \"${this.id}\" must have pays defined.`)\n }\n }\n\n /**\n * Compares this symbol to another symbol or a set of properties.\n */\n compare(symbolOrProperties?: GameSymbol | Record<string, any>) {\n if (!symbolOrProperties) {\n console.warn(\"No symbol or properties provided for comparison.\")\n return false\n }\n if (symbolOrProperties instanceof GameSymbol) {\n return this.id === symbolOrProperties.id\n } else {\n for (const [key, value] of Object.entries(symbolOrProperties)) {\n if (!this.properties.has(key) || this.properties.get(key) !== value) {\n return false\n }\n }\n return true\n }\n }\n}\n\nexport interface GameSymbolOpts {\n /**\n * Unique identifier for the symbol, e.g. \"W\", \"H1\", \"L5\", etc.\n */\n id: string\n /**\n * Paytable for the symbol, where the key is the number of symbols and the value is the payout multiplier.\n */\n pays?: Record<number, number>\n /**\n * Additional properties for the symbol, e.g. `multiplier` or `isWild`.\n *\n * Properties can help identify special symbols.\n * \n * @example\n * If your game has a \"normal\" scatter and a \"super\" scatter, you can define them like this:\n * \n * ```ts\n * properties: {\n * isScatter: true,\n * }\n * ```\n */\n properties?: Record<string, any>\n}\n","import { AbstractService } from \".\"\nimport { GameContext } from \"../game-context\"\nimport { GameSymbol } from \"../game-symbol\"\nimport { AnyGameModes, AnySymbols, AnyUserData, Reels } from \"../types\"\nimport { Board } from \"../board\"\n\nexport class BoardService<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> extends AbstractService {\n private board: Board\n\n constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>) {\n // @ts-ignore TODO: Fix type errors with AnyTypes\n super(ctx)\n\n this.board = new Board()\n }\n\n /**\n * Resets the board to an empty state.\\\n * This is called before drawing a new board.\n */\n resetBoard() {\n this.resetReels()\n this.board.lastDrawnReelStops = []\n }\n\n /**\n * Gets the current reels and symbols on the board.\n */\n getBoardReels() {\n return this.board.reels\n }\n\n getPaddingTop() {\n return this.board.paddingTop\n }\n\n getPaddingBottom() {\n return this.board.paddingBottom\n }\n\n getAnticipation() {\n return this.board.anticipation\n }\n\n private resetReels() {\n this.board.resetReels({\n ctx: this.ctx(),\n })\n }\n\n /**\n * Sets the anticipation value for a specific reel.\n */\n setAnticipationForReel(reelIndex: number, value: boolean) {\n this.board.anticipation[reelIndex] = value\n }\n\n /**\n * Counts how many symbols matching the criteria are on a specific reel.\n */\n countSymbolsOnReel(\n symbolOrProperties: GameSymbol | Record<string, any>,\n reelIndex: number,\n ) {\n return this.board.countSymbolsOnReel(symbolOrProperties, reelIndex)\n }\n\n /**\n * Counts how many symbols matching the criteria are on the board.\n *\n * Passing a GameSymbol will compare by ID, passing a properties object will compare by properties.\n *\n * Returns a tuple where the first element is the total count, and the second element is a record of counts per reel index.\n */\n countSymbolsOnBoard(\n symbolOrProperties: GameSymbol | Record<string, any>,\n ): [number, Record<number, number>] {\n return this.board.countSymbolsOnBoard(symbolOrProperties)\n }\n\n /**\n * Checks if a symbol appears more than once on any reel in the current reel set.\n *\n * Useful to check for \"forbidden\" generations, e.g. 2 scatters on one reel.\n */\n isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol) {\n return this.board.isSymbolOnAnyReelMultipleTimes(symbol)\n }\n\n /**\n * Gets all reel stops (positions) where the specified symbol appears in the current reel set.\\\n * Returns an array of arrays, where each inner array contains the positions for the corresponding reel.\n */\n getReelStopsForSymbol(reels: Reels, symbol: GameSymbol) {\n return this.board.getReelStopsForSymbol(reels, symbol)\n }\n\n /**\n * Combines multiple arrays of reel stops into a single array of reel stops.\\\n */\n combineReelStops(...reelStops: number[][][]) {\n return this.board.combineReelStops({\n ctx: this.ctx(),\n reelStops,\n })\n }\n\n /**\n * From a list of reel stops on reels, selects a random stop for a speficied number of random symbols.\n *\n * Mostly useful for placing scatter symbols on the board.\n */\n getRandomReelStops(reels: Reels, reelStops: number[][], amount: number) {\n return this.board.getRandomReelStops({\n ctx: this.ctx(),\n reels,\n reelStops,\n amount,\n })\n }\n\n /**\n * Selects a random reel set based on the configured weights of the current result set.\\\n * Returns the reels as arrays of GameSymbols.\n */\n getRandomReelset() {\n return this.board.getRandomReelset(this.ctx())\n }\n\n /**\n * Draws a board using specified reel stops.\n */\n drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>) {\n this.drawBoardMixed(reels, forcedStops)\n }\n\n /**\n * Draws a board using random reel stops.\n */\n drawBoardWithRandomStops(reels: Reels) {\n this.drawBoardMixed(reels)\n }\n\n private drawBoardMixed(reels: Reels, forcedStops?: Record<string, number>) {\n this.board.drawBoardMixed({\n ctx: this.ctx(),\n reels,\n forcedStops,\n })\n }\n\n /**\n * Tumbles the board. All given symbols will be deleted and new symbols will fall from the top.\n */\n tumbleBoard(symbolsToDelete: Array<{ reelIdx: number; rowIdx: number }>) {\n this.board.tumbleBoard({\n ctx: this.ctx(),\n symbolsToDelete,\n })\n }\n}\n","import assert from \"assert\"\nimport { AbstractService } from \".\"\nimport { GameContext } from \"../game-context\"\nimport { Recorder } from \"../recorder\"\nimport { AnyGameModes, AnySymbols, AnyUserData, SpinType } from \"../types\"\nimport { Book, BookEvent } from \"../book\"\n\nexport class DataService<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> extends AbstractService {\n private recorder!: Recorder\n private book!: Book\n\n constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>) {\n // @ts-ignore TODO: Fix type errors with AnyTypes\n super(ctx)\n }\n\n private ensureRecorder() {\n assert(this.recorder, \"Recorder not set in DataService. Call setRecorder() first.\")\n }\n\n private ensureBook() {\n assert(this.book, \"Book not set in DataService. Call setBook() first.\")\n }\n\n /**\n * Intended for internal use only.\n */\n _setRecorder(recorder: Recorder) {\n this.recorder = recorder\n }\n\n /**\n * Intended for internal use only.\n */\n _getBook() {\n return this.book\n }\n\n /**\n * Intended for internal use only.\n */\n _setBook(book: Book) {\n this.book = book\n }\n\n /**\n * Intended for internal use only.\n */\n _getRecorder() {\n return this.recorder\n }\n\n /**\n * Intended for internal use only.\n */\n _getRecords() {\n this.ensureRecorder()\n return this.recorder.records\n }\n\n /**\n * Record data for statistical analysis.\n */\n record(data: Record<string, string | number | boolean>) {\n this.ensureRecorder()\n\n this.recorder.pendingRecords.push({\n bookId: this.ctx().state.currentSimulationId,\n properties: Object.fromEntries(\n Object.entries(data).map(([k, v]) => [k, String(v)]),\n ),\n })\n }\n\n /**\n * Records a symbol occurrence for statistical analysis.\n *\n * Calls `ctx.services.data.record()` with the provided data.\n */\n recordSymbolOccurrence(data: {\n kind: number\n symbolId: string\n spinType: SpinType\n [key: string]: any\n }) {\n this.record(data)\n }\n\n /**\n * Adds an event to the book.\n */\n addBookEvent(event: Omit<BookEvent, \"index\">) {\n this.ensureBook()\n this.book.addEvent(event)\n }\n\n /**\n * Intended for internal use only.\n */\n _clearPendingRecords() {\n this.ensureRecorder()\n this.recorder.pendingRecords = []\n }\n}\n","import { AbstractService } from \".\"\nimport { GameContext } from \"../game-context\"\nimport { AnyGameModes, AnySymbols, AnyUserData, SpinType } from \"../types\"\n\nexport class GameService<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> extends AbstractService {\n constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>) {\n // @ts-ignore TODO: Fix type errors with AnyTypes\n super(ctx)\n }\n\n /**\n * Retrieves a reel set by its ID within a specific game mode.\n */\n getReelsetById(gameMode: string, id: string) {\n const reelSet = this.ctx().config.gameModes[gameMode]!.reelSets.find(\n (rs) => rs.id === id,\n )\n if (!reelSet) {\n throw new Error(\n `Reel set with id \"${id}\" not found in game mode \"${gameMode}\". Available reel sets: ${this.ctx()\n .config.gameModes[gameMode]!.reelSets.map((rs) => rs.id)\n .join(\", \")}`,\n )\n }\n return reelSet.reels\n }\n\n /**\n * Retrieves the number of free spins awarded for a given spin type and scatter count.\n */\n getFreeSpinsForScatters(spinType: SpinType, scatterCount: number) {\n const freespinsConfig = this.ctx().config.scatterToFreespins[spinType]\n if (!freespinsConfig) {\n throw new Error(\n `No free spins configuration found for spin type \"${spinType}\". Please check your game configuration.`,\n )\n }\n return freespinsConfig[scatterCount] || 0\n }\n\n /**\n * Retrieves a result set by its criteria within a specific game mode.\n */\n getResultSetByCriteria(mode: string, criteria: string) {\n const gameMode = this.ctx().config.gameModes[mode]\n if (!gameMode) {\n throw new Error(`Game mode \"${mode}\" not found in game config.`)\n }\n const resultSet = gameMode.resultSets.find((rs) => rs.criteria === criteria)\n if (!resultSet) {\n throw new Error(\n `Criteria \"${criteria}\" not found in game mode \"${mode}\". Available criteria: ${gameMode.resultSets\n .map((rs) => rs.criteria)\n .join(\", \")}`,\n )\n }\n return resultSet\n }\n\n /**\n * Returns all configured symbols as an array.\n */\n getSymbolArray() {\n return Array.from(this.ctx().config.symbols).map(([n, v]) => v)\n }\n\n /**\n * Gets the configuration for the current game mode.\n */\n getCurrentGameMode() {\n return this.ctx().config.gameModes[this.ctx().state.currentGameMode]!\n }\n\n /**\n * Ensures the requested number of scatters is valid based on the game configuration.\\\n * Returns a valid number of scatters.\n */\n verifyScatterCount(numScatters: number) {\n const scatterCounts =\n this.ctx().config.scatterToFreespins[this.ctx().state.currentSpinType]\n if (!scatterCounts) {\n throw new Error(\n `No scatter counts defined for spin type \"${this.ctx().state.currentSpinType}\". Please check your game configuration.`,\n )\n }\n const validCounts = Object.keys(scatterCounts).map((key) => parseInt(key, 10))\n if (validCounts.length === 0) {\n throw new Error(\n `No scatter counts defined for spin type \"${this.ctx().state.currentSpinType}\". Please check your game configuration.`,\n )\n }\n if (numScatters < Math.min(...validCounts)) {\n return Math.min(...validCounts)\n }\n if (numScatters > Math.max(...validCounts)) {\n return Math.max(...validCounts)\n }\n return numScatters\n }\n\n /**\n * Increases the freespin count by the specified amount.\n *\n * Also sets `state.triggeredFreespins` to true.\n */\n awardFreespins(amount: number) {\n this.ctx().state.currentFreespinAmount += amount\n this.ctx().state.totalFreespinAmount += amount\n this.ctx().state.triggeredFreespins = true\n }\n}\n","import assert from \"assert\"\nimport { AbstractService } from \".\"\nimport { GameContext } from \"../game-context\"\nimport { AnyGameModes, AnySymbols, AnyUserData, SpinType } from \"../types\"\nimport { Wallet } from \"../wallet\"\n\nexport class WalletService<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> extends AbstractService {\n private wallet!: Wallet\n\n constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>) {\n // @ts-ignore TODO: Fix type errors with AnyTypes\n super(ctx)\n }\n\n private ensureWallet() {\n assert(this.wallet, \"Wallet not set in WalletService. Call setWallet() first.\")\n }\n\n /**\n * Intended for internal use only.\n */\n _getWallet() {\n this.ensureWallet()\n return this.wallet\n }\n\n /**\n * Intended for internal use only.\n */\n _setWallet(wallet: Wallet) {\n this.wallet = wallet\n }\n\n /**\n * Adds the given amount to the wallet state.\n *\n * After calculating the win for a board, call this method to update the wallet state.\\\n * If your game has tumbling mechanics, you should call this method again after every new tumble and win calculation.\n */\n addSpinWin(amount: number) {\n this.ensureWallet()\n this.wallet.addSpinWin(amount)\n }\n\n /**\n * Helps to add tumble wins to the wallet state.\n *\n * This also calls `addSpinWin()` internally, to add the tumble win to the overall spin win.\n */\n addTumbleWin(amount: number) {\n this.ensureWallet()\n this.wallet.addTumbleWin(amount)\n }\n\n /**\n * Confirms the wins of the current spin.\n *\n * Should be called after `addSpinWin()`, and after your tumble events are played out,\\\n * and after a (free) spin is played out to finalize the win.\n */\n confirmSpinWin() {\n this.ensureWallet()\n this.wallet.confirmSpinWin(this.ctx().state.currentSpinType)\n }\n\n /**\n * Gets the total win amount of the current simulation.\n */\n getCurrentWin() {\n this.ensureWallet()\n return this.wallet.getCurrentWin()\n }\n\n /**\n * Gets the current spin win amount of the ongoing spin.\n */\n getCurrentSpinWin() {\n this.ensureWallet()\n return this.wallet.getCurrentSpinWin()\n }\n\n /**\n * Gets the current tumble win amount of the ongoing spin.\n */\n getCurrentTumbleWin() {\n this.ensureWallet()\n return this.wallet.getCurrentTumbleWin()\n }\n}\n","import { GameConfig } from \"../game-config\"\nimport { createGameState, GameState } from \"../game-state\"\nimport { BoardService } from \"../service/board\"\nimport { DataService } from \"../service/data\"\nimport { GameService } from \"../service/game\"\nimport { RngService } from \"../service/rng\"\nimport { WalletService } from \"../service/wallet\"\nimport { AnyGameModes, AnySymbols, AnyUserData } from \"../types\"\n\nexport interface GameContextOptions<\n TGameModes extends AnyGameModes,\n TSymbols extends AnySymbols,\n TUserState extends AnyUserData,\n> {\n config: GameConfig<TGameModes, TSymbols, TUserState>\n state?: Partial<GameState<TUserState>>\n services?: Partial<GameContextServices>\n}\n\nexport function createGameContext<\n TGameModes extends AnyGameModes,\n TSymbols extends AnySymbols,\n TUserState extends AnyUserData,\n>(opts: GameContextOptions<TGameModes, TSymbols, TUserState>) {\n const context = {\n config: opts.config,\n state: createGameState(opts.state),\n services: {} as GameContextServices,\n }\n\n const getContext = () => context\n\n function createServices() {\n return {\n game: new GameService(getContext),\n data: new DataService(getContext),\n board: new BoardService(getContext),\n wallet: new WalletService(getContext),\n rng: new RngService(getContext),\n ...opts.services,\n }\n }\n\n context.services = createServices()\n\n return context\n}\n\nexport type GameContext<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> = {\n /**\n * The static configuration of the game.\n */\n config: GameConfig<TGameModes, TSymbols, TUserState>\n /**\n * Game state holding information about the current simulation.\n */\n state: GameState<TUserState>\n /**\n * Services providing game functionality.\n */\n services: GameContextServices\n}\n\nexport interface GameContextServices {\n /**\n * Service providing common utility functions.\n */\n game: GameService\n /**\n * Service for interacting with the book data or recorder.\n */\n data: DataService\n /**\n * Service managing the game board and reels.\n */\n board: BoardService\n /**\n * Service providing win related functionality.\n */\n wallet: WalletService\n /**\n * Service for seeded random number generation.\n */\n rng: RngService\n}\n","import { SPIN_TYPE } from \"../constants\"\nimport { GameContext } from \"../game-context\"\nimport { Wallet } from \"../wallet\"\n\nexport class Book {\n readonly id: number\n criteria: string = \"N/A\"\n events: BookEvent[] = []\n payout: number = 0\n basegameWins: number = 0\n freespinsWins: number = 0\n\n constructor(opts: BookOpts) {\n this.id = opts.id\n }\n\n /**\n * Intended for internal use only.\n */\n setCriteria(criteria: string) {\n this.criteria = criteria\n }\n\n /**\n * Adds an event to the book.\n */\n addEvent(event: Omit<BookEvent, \"index\">) {\n const index = this.events.length + 1\n this.events.push({ index, ...event })\n }\n\n /**\n * Intended for internal use only.\n */\n serialize() {\n return {\n id: this.id,\n criteria: this.criteria,\n events: this.events,\n payout: this.payout,\n basegameWins: this.basegameWins,\n freespinsWins: this.freespinsWins,\n }\n }\n\n /**\n * Intended for internal use only.\n */\n static fromSerialized(data: ReturnType<Book[\"serialize\"]>) {\n const book = new Book({ id: data.id, criteria: data.criteria })\n book.events = data.events\n book.payout = data.payout\n book.basegameWins = data.basegameWins\n book.freespinsWins = data.freespinsWins\n return book\n }\n}\n\nexport interface BookEvent {\n index: number\n type: string\n data: Record<string, any>\n}\n\ninterface BookOpts {\n id: number\n criteria: string\n}\n","export class Recorder {\n records: RecordItem[]\n pendingRecords: PendingRecord[]\n\n constructor() {\n this.records = []\n this.pendingRecords = []\n }\n}\n\nexport interface PendingRecord {\n bookId: number\n properties: Record<string, string>\n}\n\nexport interface RecordItem {\n search: Array<{ name: string; value: string }>\n timesTriggered: number\n bookIds: number[]\n}\n","import { SPIN_TYPE } from \"../constants\"\nimport { GameContext } from \"../game-context\"\nimport { SpinType } from \"../types\"\n\nexport class Wallet {\n /**\n * Total win amount (as the bet multiplier) from all simulations.\n */\n protected cumulativeWins = 0\n /**\n * Total win amount (as the bet multiplier) per spin type.\n *\n * @example\n * ```ts\n * {\n * basegame: 50,\n * freespins: 100,\n * superfreespins: 200,\n * }\n * ```\n */\n protected cumulativeWinsPerSpinType = {\n [SPIN_TYPE.BASE_GAME]: 0,\n [SPIN_TYPE.FREE_SPINS]: 0,\n }\n /**\n * Current win amount (as the bet multiplier) for the ongoing simulation.\n */\n protected currentWin = 0\n /**\n * Current win amount (as the bet multiplier) for the ongoing simulation per spin type.\n *\n * @example\n * ```ts\n * {\n * basegame: 50,\n * freespins: 100,\n * superfreespins: 200,\n * }\n * ```\n */\n protected currentWinPerSpinType = {\n [SPIN_TYPE.BASE_GAME]: 0,\n [SPIN_TYPE.FREE_SPINS]: 0,\n }\n /**\n * Holds the current win amount for a single (free) spin.\\\n * After each spin, this amount is added to `currentWinPerSpinType` and then reset to zero.\n */\n protected currentSpinWin = 0\n /**\n * Current win amount (as the bet multiplier) for the ongoing tumble sequence.\n */\n protected currentTumbleWin = 0\n\n constructor() {}\n\n /**\n * Updates the win for the current spin.\n *\n * Should be called after each tumble event, if applicable.\\\n * Or generally call this to add wins during a spin.\n *\n * After each (free) spin, this amount should be added to `currentWinPerSpinType` via `confirmSpinWin()`\n */\n addSpinWin(amount: number) {\n this.currentSpinWin += amount\n }\n\n /**\n * Confirms the wins of the current spin.\n *\n * Should be called after `addSpinWin()`, and after your tumble events are played out,\\\n * and after a (free) spin is played out to finalize the win.\n */\n confirmSpinWin(spinType: SpinType) {\n if (!Object.keys(this.currentWinPerSpinType).includes(spinType)) {\n throw new Error(`Spin type \"${spinType}\" does not exist in the wallet.`)\n }\n this.currentWinPerSpinType[spinType]! += this.currentSpinWin\n this.currentWin += this.currentSpinWin\n this.currentSpinWin = 0\n this.currentTumbleWin = 0\n }\n\n /**\n * Returns the accumulated win amount (as the bet multiplier) from all simulations.\n */\n getCumulativeWins() {\n return this.cumulativeWins\n }\n\n /**\n * Returns the accumulated win amount (as the bet multiplier) per spin type from all simulations.\n */\n getCumulativeWinsPerSpinType() {\n return this.cumulativeWinsPerSpinType\n }\n\n /**\n * Returns the current win amount (as the bet multiplier) for the ongoing simulation.\n */\n getCurrentWin() {\n return this.currentWin\n }\n\n /**\n * Returns the current spin win amount (as the bet multiplier) for the ongoing simulation.\n */\n getCurrentSpinWin() {\n return this.currentSpinWin\n }\n\n /**\n * Returns the current tumble win amount (as the bet multiplier) for the ongoing simulation.\n */\n getCurrentTumbleWin() {\n return this.currentTumbleWin\n }\n\n /**\n * Returns the current win amount (as the bet multiplier) per spin type for the ongoing simulation.\n */\n getCurrentWinPerSpinType() {\n return this.currentWinPerSpinType\n }\n\n /**\n * Adds a win to `currentSpinWin` and `currentTumbleWin`.\n *\n * After each (free) spin, this amount should be added to `currentWinPerSpinType` via `confirmSpinWin()`\n */\n addTumbleWin(amount: number) {\n this.currentTumbleWin += amount\n this.addSpinWin(amount)\n }\n\n /**\n * Intended for internal use only.\n * \n * Resets the current win amounts to zero.\n */\n resetCurrentWin() {\n this.currentWin = 0\n this.currentSpinWin = 0\n this.currentTumbleWin = 0\n\n for (const spinType of Object.keys(this.currentWinPerSpinType)) {\n this.currentWinPerSpinType[spinType as SpinType] = 0\n }\n }\n\n /**\n * Intended for internal use only.\n * \n * Adds current wins to cumulative wins and resets current wins to zero.\n */\n confirmWins(ctx: GameContext) {\n function process(number: number) {\n return Math.round(Math.min(number, ctx.config.maxWinX) * 100) / 100\n }\n\n this.currentWin = process(this.currentWin)\n this.cumulativeWins += this.currentWin\n let spinTypeWins = 0\n\n for (const spinType of Object.keys(this.currentWinPerSpinType)) {\n const st = spinType as SpinType\n const spinTypeWin = process(this.currentWinPerSpinType[st])\n this.cumulativeWinsPerSpinType[st]! += spinTypeWin\n spinTypeWins += spinTypeWin\n }\n\n if (process(spinTypeWins) !== this.currentWin) {\n throw new Error(\n `Inconsistent wallet state: currentWin (${this.currentWin}) does not equal spinTypeWins (${spinTypeWins}).`,\n )\n }\n\n this.resetCurrentWin()\n }\n\n /**\n * Intended for internal use only.\n *\n * Transfers the win data from the given wallet to the calling book.\n */\n writePayoutToBook(ctx: GameContext) {\n function process(number: number) {\n return Math.round(Math.min(number, ctx.config.maxWinX) * 100) / 100\n }\n\n const wallet = ctx.services.wallet._getWallet()\n const book = ctx.services.data._getBook()\n\n book.payout = Math.round(process(wallet.getCurrentWin()) * 100)\n book.basegameWins = process(\n wallet.getCurrentWinPerSpinType()[SPIN_TYPE.BASE_GAME] || 0,\n )\n book.freespinsWins = process(\n wallet.getCurrentWinPerSpinType()[SPIN_TYPE.FREE_SPINS] || 0,\n )\n }\n\n /**\n * Intended for internal use only.\n */\n serialize() {\n return {\n cumulativeWins: this.cumulativeWins,\n cumulativeWinsPerSpinType: this.cumulativeWinsPerSpinType,\n currentWin: this.currentWin,\n currentWinPerSpinType: this.currentWinPerSpinType,\n currentSpinWin: this.currentSpinWin,\n currentTumbleWin: this.currentTumbleWin,\n }\n }\n\n /**\n * Intended for internal use only.\n */\n merge(wallet: Wallet) {\n this.cumulativeWins += wallet.getCumulativeWins()\n const otherWinsPerSpinType = wallet.getCumulativeWinsPerSpinType()\n\n for (const spinType of Object.keys(this.cumulativeWinsPerSpinType)) {\n this.cumulativeWinsPerSpinType[spinType as SpinType]! +=\n otherWinsPerSpinType[spinType as SpinType] || 0\n }\n }\n\n /**\n * Intended for internal use only.\n */\n mergeSerialized(data: ReturnType<Wallet[\"serialize\"]>) {\n this.cumulativeWins += data.cumulativeWins\n for (const spinType of Object.keys(this.cumulativeWinsPerSpinType)) {\n this.cumulativeWinsPerSpinType[spinType as SpinType]! +=\n data.cumulativeWinsPerSpinType[spinType as SpinType] || 0\n }\n this.currentWin += data.currentWin\n this.currentSpinWin += data.currentSpinWin\n this.currentTumbleWin += data.currentTumbleWin\n for (const spinType of Object.keys(this.currentWinPerSpinType)) {\n this.currentWinPerSpinType[spinType as SpinType]! +=\n data.currentWinPerSpinType[spinType as SpinType] || 0\n }\n }\n}\n","import fs from \"fs\"\nimport path from \"path\"\nimport { GameConfig } from \"../game-config\"\nimport { Optimizer, OptimzierGameModeConfig } from \"../optimizer\"\nimport assert from \"assert\"\nimport {\n getAvgWin,\n getLessBetHitrate,\n getMaxWin,\n getMaxwinHitrate,\n getMinWin,\n getNonZeroHitrate,\n getNullHitrate,\n getPayoutWeights,\n getRtp,\n getStandardDeviation,\n getTotalLutWeight,\n getUniquePayouts,\n getVariance,\n parseLookupTable,\n} from \"./utils\"\nimport { writeJsonFile } from \"../../utils\"\nimport { isMainThread } from \"worker_threads\"\n\nexport class Analysis {\n protected readonly gameConfig: GameConfig\n protected readonly optimizerConfig: OptimzierGameModeConfig\n protected filePaths: Record<string, FilePaths>\n\n constructor(optimizer: Optimizer) {\n this.gameConfig = optimizer.getGameConfig()\n this.optimizerConfig = optimizer.getOptimizerGameModes()\n this.filePaths = {}\n }\n\n async runAnalysis(gameModes: string[]) {\n if (!isMainThread) return // IMPORTANT: Prevent workers from kicking off (multiple) analysis runs\n\n this.filePaths = this.getPathsForModes(gameModes)\n this.getNumberStats(gameModes)\n this.getWinRanges(gameModes)\n //this.getSymbolStats(gameModes)\n console.log(\"Analysis complete. Files written to build directory.\")\n }\n\n private getPathsForModes(gameModes: string[]) {\n const rootPath = process.cwd()\n const paths: Record<string, FilePaths> = {}\n\n for (const modeStr of gameModes) {\n const lut = path.join(\n rootPath,\n this.gameConfig.outputDir,\n `lookUpTable_${modeStr}.csv`,\n )\n const lutSegmented = path.join(\n rootPath,\n this.gameConfig.outputDir,\n `lookUpTableSegmented_${modeStr}.csv`,\n )\n const lutOptimized = path.join(\n rootPath,\n this.gameConfig.outputDir,\n \"publish_files\",\n `lookUpTable_${modeStr}_0.csv`,\n )\n const booksJsonl = path.join(\n rootPath,\n this.gameConfig.outputDir,\n `books_${modeStr}.jsonl`,\n )\n const booksJsonlCompressed = path.join(\n rootPath,\n this.gameConfig.outputDir,\n \"publish_files\",\n `books_${modeStr}.jsonl.zst`,\n )\n\n paths[modeStr] = {\n lut,\n lutSegmented,\n lutOptimized,\n booksJsonl,\n booksJsonlCompressed,\n }\n\n for (const p of Object.values(paths[modeStr])) {\n assert(\n fs.existsSync(p),\n `File \"${p}\" does not exist. Run optimization to auto-create it.`,\n )\n }\n }\n\n return paths\n }\n\n private getNumberStats(gameModes: string[]) {\n const stats: Statistics[] = []\n\n for (const modeStr of gameModes) {\n const mode = this.getGameModeConfig(modeStr)\n\n const lutOptimized = parseLookupTable(\n fs.readFileSync(this.filePaths[modeStr]!.lutOptimized, \"utf-8\"),\n )\n const totalWeight = getTotalLutWeight(lutOptimized)\n const payoutWeights = getPayoutWeights(lutOptimized)\n\n stats.push({\n gameMode: mode.name,\n totalWeight,\n avgWin: getAvgWin(payoutWeights),\n rtp: getRtp(payoutWeights, mode.cost),\n minWin: getMinWin(payoutWeights),\n maxWin: getMaxWin(payoutWeights),\n stdDev: getStandardDeviation(payoutWeights),\n variance: getVariance(payoutWeights),\n nonZeroHitRate: getNonZeroHitrate(payoutWeights),\n nullHitRate: getNullHitrate(payoutWeights),\n maxwinHitRate: getMaxwinHitrate(payoutWeights),\n lessBetHitRate: getLessBetHitrate(payoutWeights, mode.cost),\n uniquePayouts: getUniquePayouts(payoutWeights),\n })\n }\n\n writeJsonFile(\n path.join(process.cwd(), this.gameConfig.outputDir, \"stats_summary.json\"),\n stats,\n )\n }\n\n private getWinRanges(gameModes: string[]) {\n const winRanges: [number, number][] = [\n [0, 0.1],\n [0, 0.99],\n [1, 1.99],\n [2, 2.99],\n [3, 4.99],\n [5, 9.99],\n [10, 19.99],\n [20, 49.99],\n [50, 99.99],\n [100, 199.99],\n [200, 499.99],\n [500, 999.99],\n [1000, 1999.99],\n [2000, 2999.99],\n [3000, 4999.99],\n [5000, 7499.99],\n [7500, 9999.99],\n [10000, 14999.99],\n [15000, 19999.99],\n [20000, 24999.99],\n ]\n\n for (const modeStr of gameModes) {\n const mode = this.getGameModeConfig(modeStr)\n\n const lutOptimized = parseLookupTable(\n fs.readFileSync(this.filePaths[modeStr]!.lutOptimized, \"utf-8\"),\n )\n const totalWeight = getTotalLutWeight(lutOptimized)\n const payoutWeights = getPayoutWeights(lutOptimized)\n }\n }\n\n private getGameModeConfig(mode: string) {\n const config = this.gameConfig.gameModes[mode]\n assert(config, `Game mode \"${mode}\" not found in game config`)\n return config\n }\n}\n\nexport interface AnalysisOpts {\n gameModes: string[]\n}\n\ninterface FilePaths {\n lut: string\n lutSegmented: string\n lutOptimized: string\n booksJsonl: string\n booksJsonlCompressed: string\n}\n\ninterface Statistics {\n gameMode: string\n totalWeight: number\n rtp: number\n avgWin: number\n minWin: number\n maxWin: number\n stdDev: number\n variance: number\n maxwinHitRate: number\n nonZeroHitRate: number\n nullHitRate: number\n lessBetHitRate: number\n uniquePayouts: number\n}\n","export function parseLookupTable(content: string) {\n const lines = content.trim().split(\"\\n\")\n const lut: LookupTable = []\n for (const line of lines) {\n const [indexStr, weightStr, payoutStr] = line.split(\",\")\n const index = parseInt(indexStr!.trim())\n const weight = parseInt(weightStr!.trim())\n const payout = parseFloat(payoutStr!.trim())\n lut.push([index, weight, payout])\n }\n return lut\n}\n\nexport type LookupTable = [number, number, number][]\n\nexport function getTotalLutWeight(lut: LookupTable) {\n return lut.reduce((sum, [, weight]) => sum + weight, 0)\n}\n\nexport function getTotalWeight(payoutWeights: PayoutWeights) {\n return Object.values(payoutWeights).reduce((sum, w) => sum + w, 0)\n}\n\ntype PayoutWeights = Record<number, number>\n\nexport function getPayoutWeights(\n lut: LookupTable,\n opts: { normalize?: boolean } = {},\n): PayoutWeights {\n const { normalize = true } = opts\n const totalWeight = getTotalLutWeight(lut)\n\n let payoutWeights: Record<number, number> = {}\n\n for (const [, weight, p] of lut) {\n const payout = p / 100\n if (payoutWeights[payout] === undefined) {\n payoutWeights[payout] = 0\n }\n payoutWeights[payout] += weight\n }\n\n // Sort by payout value\n payoutWeights = Object.fromEntries(\n Object.entries(payoutWeights).sort(([a], [b]) => parseFloat(a) - parseFloat(b)),\n )\n\n if (normalize) {\n for (const payout in payoutWeights) {\n payoutWeights[payout]! /= totalWeight\n }\n }\n\n return payoutWeights\n}\n\nexport function getNonZeroHitrate(payoutWeights: PayoutWeights) {\n const totalWeight = getTotalWeight(payoutWeights)\n if (Math.min(...Object.keys(payoutWeights).map(Number)) == 0) {\n return totalWeight / (totalWeight - (payoutWeights[0] ?? 0) / totalWeight)\n } else {\n return 1\n }\n}\n\nexport function getNullHitrate(payoutWeights: PayoutWeights) {\n return payoutWeights[0] ?? 0\n}\n\nexport function getMaxwinHitrate(payoutWeights: PayoutWeights) {\n const totalWeight = getTotalWeight(payoutWeights)\n const maxWin = Math.max(...Object.keys(payoutWeights).map(Number))\n const hitRate = (payoutWeights[maxWin] || 0) / totalWeight\n return 1 / hitRate\n}\n\nexport function getUniquePayouts(payoutWeights: PayoutWeights) {\n return Object.keys(payoutWeights).length\n}\n\nexport function getMinWin(payoutWeights: PayoutWeights) {\n const payouts = Object.keys(payoutWeights).map(Number)\n return Math.min(...payouts)\n}\n\nexport function getMaxWin(payoutWeights: PayoutWeights) {\n const payouts = Object.keys(payoutWeights).map(Number)\n return Math.max(...payouts)\n}\n\nexport function getAvgWin(payoutWeights: PayoutWeights) {\n let avgWin = 0\n for (const [payoutStr, weight] of Object.entries(payoutWeights)) {\n const payout = parseFloat(payoutStr)\n avgWin += payout * weight\n }\n return avgWin\n}\n\nexport function getRtp(payoutWeights: PayoutWeights, cost: number) {\n const avgWin = getAvgWin(payoutWeights)\n return avgWin / cost\n}\n\nexport function getStandardDeviation(payoutWeights: PayoutWeights) {\n const variance = getVariance(payoutWeights)\n return Math.sqrt(variance)\n}\n\nexport function getVariance(payoutWeights: PayoutWeights) {\n const totalWeight = getTotalWeight(payoutWeights)\n const avgWin = getAvgWin(payoutWeights)\n let variance = 0\n for (const [payoutStr, weight] of Object.entries(payoutWeights)) {\n const payout = parseFloat(payoutStr)\n variance += Math.pow(payout - avgWin, 2) * (weight / totalWeight)\n }\n return variance\n}\n\nexport function getLessBetHitrate(payoutWeights: PayoutWeights, cost: number) {\n let lessBetWeight = 0\n const totalWeight = getTotalWeight(payoutWeights)\n for (const [payoutStr, weight] of Object.entries(payoutWeights)) {\n const payout = parseFloat(payoutStr)\n if (payout < cost) {\n lessBetWeight += weight\n }\n }\n return lessBetWeight / totalWeight\n}\n","import path from \"path\"\nimport assert from \"assert\"\nimport { spawn } from \"child_process\"\nimport { isMainThread } from \"worker_threads\"\nimport { makeMathConfig } from \"../utils/math-config\"\nimport { makeSetupFile } from \"../utils/setup-file\"\nimport { OptimizationParameters } from \"./OptimizationParameters\"\nimport { OptimizationConditions } from \"./OptimizationConditions\"\nimport { OptimizationScaling } from \"./OptimizationScaling\"\nimport { SlotGame } from \"../slot-game\"\nimport { GameConfig } from \"../game-config\"\n\nexport class Optimizer {\n protected readonly gameConfig: GameConfig\n protected readonly gameModes: OptimzierGameModeConfig\n\n constructor(opts: OptimizerOpts) {\n this.gameConfig = opts.game.getConfig()\n this.gameModes = opts.gameModes\n this.verifyConfig()\n }\n\n /**\n * Runs the optimization process, and runs analysis after.\n */\n async runOptimization({ gameModes }: OptimizationOpts) {\n if (!isMainThread) return // IMPORTANT: Prevent workers from kicking off (multiple) optimizations\n\n const mathConfig = makeMathConfig(this, { writeToFile: true })\n\n for (const mode of gameModes) {\n const setupFile = makeSetupFile(this, mode)\n await this.runSingleOptimization()\n }\n console.log(\"Optimization complete. Files written to build directory.\")\n }\n\n private async runSingleOptimization() {\n return await rustProgram()\n }\n\n private verifyConfig() {\n for (const [k, mode] of Object.entries(this.gameModes)) {\n const configMode = this.gameConfig.gameModes[k]\n\n if (!configMode) {\n throw new Error(\n `Game mode \"${mode}\" defined in optimizer config does not exist in the game config.`,\n )\n }\n\n const conditions = Object.keys(mode.conditions)\n const scalings = Object.keys(mode.scaling)\n const parameters = Object.keys(mode.parameters)\n\n for (const condition of conditions) {\n if (!configMode.resultSets.find((r) => r.criteria === condition)) {\n throw new Error(\n `Condition \"${condition}\" defined in optimizer config for game mode \"${k}\" does not exist as criteria in any ResultSet of the same game mode.`,\n )\n }\n }\n\n const criteria = configMode.resultSets.map((r) => r.criteria)\n assert(\n conditions.every((c) => criteria.includes(c)),\n `Not all ResultSet criteria in game mode \"${k}\" are defined as optimization conditions.`,\n )\n\n let gameModeRtp = configMode.rtp\n let paramRtp = 0\n for (const cond of conditions) {\n const paramConfig = mode.conditions[cond]!\n paramRtp += Number(paramConfig.getRtp())\n }\n\n gameModeRtp = Math.round(gameModeRtp * 1000) / 1000\n paramRtp = Math.round(paramRtp * 1000) / 1000\n\n assert(\n gameModeRtp === paramRtp,\n `Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode \"${k}\".`,\n )\n }\n }\n\n getGameConfig() {\n return this.gameConfig\n }\n\n getOptimizerGameModes() {\n return this.gameModes\n }\n}\n\nasync function rustProgram(...args: string[]) {\n return new Promise((resolve, reject) => {\n const task = spawn(\"cargo\", [\"run\", \"--release\", ...args], {\n shell: true,\n cwd: path.join(__dirname, \"./optimizer-rust\"),\n stdio: \"pipe\",\n })\n task.on(\"error\", (error) => {\n console.error(\"Error:\", error)\n reject(error)\n })\n task.on(\"exit\", () => {\n resolve(true)\n })\n task.on(\"close\", () => {\n resolve(true)\n })\n task.stdout.on(\"data\", (data) => {\n console.log(data.toString())\n })\n task.stderr.on(\"data\", (data) => {\n console.log(data.toString())\n })\n task.stdout.on(\"error\", (data) => {\n console.log(data.toString())\n reject(data.toString())\n })\n })\n}\n\nexport interface OptimizationOpts {\n gameModes: string[]\n}\n\nexport interface OptimizerOpts {\n game: SlotGame<any, any, any>\n gameModes: OptimzierGameModeConfig\n}\n\nexport type OptimzierGameModeConfig = Record<\n string,\n {\n conditions: Record<string, OptimizationConditions>\n scaling: OptimizationScaling\n parameters: OptimizationParameters\n }\n>\n\nexport { OptimizationConditions } from \"./OptimizationConditions\"\nexport { OptimizationScaling } from \"./OptimizationScaling\"\nexport { OptimizationParameters } from \"./OptimizationParameters\"","import path from \"path\"\nimport { type Optimizer } from \"../optimizer\"\nimport { writeJsonFile } from \"../../utils\"\n\nexport function makeMathConfig(\n optimizer: Optimizer,\n opts: { writeToFile?: boolean } = {},\n) {\n const game = optimizer.getGameConfig()\n const gameModesCfg = optimizer.getOptimizerGameModes()\n const { writeToFile } = opts\n\n const isDefined = <T>(v: T | undefined): v is T => v !== undefined\n\n const config: MathConfig = {\n game_id: game.id,\n bet_modes: Object.entries(game.gameModes).map(([key, mode]) => ({\n bet_mode: mode.name,\n cost: mode.cost,\n rtp: mode.rtp,\n max_win: game.maxWinX,\n })),\n fences: Object.entries(gameModesCfg).map(([gameModeName, modeCfg]) => ({\n bet_mode: gameModeName,\n fences: Object.entries(modeCfg.conditions)\n .map(([fenceName, fence]) => ({\n name: fenceName,\n avg_win: isDefined(fence.getAvgWin())\n ? fence.getAvgWin()!.toString()\n : undefined,\n hr: isDefined(fence.getHitRate()) ? fence.getHitRate()!.toString() : undefined,\n rtp: isDefined(fence.getRtp()) ? fence.getRtp()!.toString() : undefined,\n identity_condition: {\n search: Object.entries(fence.getForceSearch()).map(([k, v]) => ({\n name: k,\n value: v,\n })),\n win_range_start: fence.getSearchRange()[0]!,\n win_range_end: fence.getSearchRange()[1]!,\n opposite: false,\n },\n priority: fence.priority,\n }))\n .sort((a, b) => b.priority - a.priority),\n })),\n dresses: Object.entries(gameModesCfg).flatMap(([gameModeName, modeCfg]) => ({\n bet_mode: gameModeName,\n dresses: modeCfg.scaling.getConfig().map((s) => ({\n fence: s.criteria,\n scale_factor: s.scaleFactor.toString(),\n identity_condition_win_range: s.winRange,\n prob: s.probability,\n })),\n })),\n }\n\n if (writeToFile) {\n const outPath = path.join(process.cwd(), game.outputDir, \"math_config.json\")\n writeJsonFile(outPath, config)\n }\n\n return config\n}\n\nexport type MathConfig = {\n game_id: string\n bet_modes: Array<{\n bet_mode: string\n cost: number\n rtp: number\n max_win: number\n }>\n fences: Array<{\n bet_mode: string\n fences: Array<Fence>\n }>\n dresses: Array<{\n bet_mode: string\n dresses: Dress[]\n }>\n}\n\ninterface Search {\n name: string\n value: string\n}\n\ninterface IdentityCondition {\n search: Search[]\n opposite: boolean\n win_range_start: number\n win_range_end: number\n}\n\ninterface Fence {\n name: string\n avg_win?: string\n rtp?: string\n hr?: string\n identity_condition: IdentityCondition\n priority: number\n}\n\ninterface Dress {\n fence: string\n scale_factor: string\n identity_condition_win_range: [number, number]\n prob: number\n}\n","import path from \"path\"\nimport { writeFile } from \"../../utils\"\nimport { Optimizer } from \"../optimizer\"\n\nexport function makeSetupFile(optimizer: Optimizer, gameMode: string) {\n const gameConfig = optimizer.getGameConfig()\n const optimizerGameModes = optimizer.getOptimizerGameModes()\n const modeConfig = optimizerGameModes[gameMode]\n\n if (!modeConfig) {\n throw new Error(`Game mode \"${gameMode}\" not found in optimizer configuration.`)\n }\n\n const params = modeConfig.parameters.getParameters()\n\n let content = \"\"\n content += `game_name;${gameConfig.id}\\n`\n content += `bet_type;${gameMode}\\n`\n content += `num_show_pigs;${params.numShowPigs}\\n`\n content += `num_pigs_per_fence;${params.numPigsPerFence}\\n`\n content += `threads_for_fence_construction;${params.threadsFenceConstruction}\\n`\n content += `threads_for_show_construction;${params.threadsShowConstruction}\\n`\n content += `score_type;${params.scoreType}\\n`\n content += `test_spins;${JSON.stringify(params.testSpins)}\\n`\n content += `test_spins_weights;${JSON.stringify(params.testSpinsWeights)}\\n`\n content += `simulation_trials;${params.simulationTrials}\\n`\n content += `graph_indexes;0\\n`\n content += `run_1000_batch;False\\n`\n content += `simulation_trials;${params.simulationTrials}\\n`\n content += `user_game_build_path;${path.join(process.cwd(), gameConfig.outputDir)}\\n`\n content += `pmb_rtp;${params.pmbRtp}\\n`\n\n const outPath = path.join(__dirname, \"./optimizer-rust/src\", \"setup.txt\")\n\n writeFile(outPath, content)\n}\n","import assert from \"assert\"\n\nexport class OptimizationConditions {\n protected rtp?: number | \"x\"\n protected avgWin?: number\n protected hitRate?: number | \"x\"\n protected searchRange: number[]\n protected forceSearch: Record<string, string>\n priority: number\n\n constructor(opts: OptimizationConditionsOpts) {\n let { rtp, avgWin, hitRate, searchConditions, priority } = opts\n\n if (rtp == undefined || rtp === \"x\") {\n assert(avgWin !== undefined && hitRate !== undefined, \"If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.\")\n rtp = Math.round((avgWin! / Number(hitRate)) * 100000) / 100000\n }\n\n let noneCount = 0\n for (const val of [rtp, avgWin, hitRate]) {\n if (val === undefined) noneCount++\n }\n assert(noneCount <= 1, \"Invalid combination of optimization conditions.\")\n\n this.searchRange = [-1, -1]\n this.forceSearch = {}\n\n if (typeof searchConditions === \"number\") {\n this.searchRange = [searchConditions, searchConditions]\n }\n if (Array.isArray(searchConditions)) {\n if (searchConditions[0] > searchConditions[1] || searchConditions.length !== 2) {\n throw new Error(\"Invalid searchConditions range.\")\n }\n this.searchRange = searchConditions\n }\n if (typeof searchConditions === \"object\" && !Array.isArray(searchConditions)) {\n this.searchRange = [-1, -1]\n this.forceSearch = searchConditions\n }\n\n this.rtp = rtp\n this.avgWin = avgWin\n this.hitRate = hitRate\n this.priority = priority\n }\n\n getRtp() {\n return this.rtp\n }\n\n getAvgWin() {\n return this.avgWin\n }\n\n getHitRate() {\n return this.hitRate\n }\n\n getSearchRange() {\n return this.searchRange\n }\n\n getForceSearch() {\n return this.forceSearch\n }\n}\n\ninterface OptimizationConditionsOpts {\n /**\n * The desired RTP (0-1)\n */\n rtp?: number | \"x\"\n /**\n * The desired average win (per spin).\n */\n avgWin?: number\n /**\n * The desired hit rate (e.g. `200` to hit 1 in 200 spins).\n */\n hitRate?: number | \"x\"\n /**\n * A way of filtering results by\n *\n * - A number (payout multiplier), e.g. `5000`\n * - Force record value, e.g. `{ \"symbolId\": \"scatter\" }`\n * - A range of numbers, e.g. `[0, 100]` (payout multiplier range)\n */\n searchConditions?: number | Record<string, string> | [number, number]\n /**\n * **Priority matters!**\\\n * Higher priority conditions will be evaluated first.\\\n * After a book matching this condition is found, the book will be removed from the pool\\\n * and can't be used to satisfy other conditions with lower priority.\n * \n * TODO add better explanation\n */\n priority: number\n}\n","export class OptimizationScaling {\n protected config: OptimizationScalingOpts\n\n constructor(opts: OptimizationScalingOpts) {\n this.config = opts\n }\n\n getConfig() {\n return this.config\n }\n}\n\ntype OptimizationScalingOpts = Array<{\n criteria: string\n scaleFactor: number\n winRange: [number, number]\n probability: number\n}>\n","export class OptimizationParameters {\n protected parameters: OptimizationParametersOpts\n\n constructor(opts?: OptimizationParametersOpts) {\n this.parameters = {\n ...OptimizationParameters.DEFAULT_PARAMETERS,\n ...opts,\n }\n }\n\n static DEFAULT_PARAMETERS: OptimizationParametersOpts = {\n numShowPigs: 5000,\n numPigsPerFence: 10000,\n threadsFenceConstruction: 16,\n threadsShowConstruction: 16,\n testSpins: [50, 100, 200],\n testSpinsWeights: [0.3, 0.4, 0.3],\n simulationTrials: 5000,\n graphIndexes: [],\n run1000Batch: false,\n minMeanToMedian: 4,\n maxMeanToMedian: 8,\n pmbRtp: 1.0,\n scoreType: \"rtp\",\n }\n\n getParameters() {\n return this.parameters\n }\n}\n\nexport interface OptimizationParametersOpts {\n readonly numShowPigs: number\n readonly numPigsPerFence: number\n readonly threadsFenceConstruction: number\n readonly threadsShowConstruction: number\n readonly testSpins: number[]\n readonly testSpinsWeights: number[]\n readonly simulationTrials: number\n readonly graphIndexes: number[]\n readonly run1000Batch: false\n readonly minMeanToMedian: number\n readonly maxMeanToMedian: number\n readonly pmbRtp: number\n readonly scoreType: \"rtp\"\n}\n","import { AnyGameModes, AnySymbols, AnyUserData } from \"../types\"\nimport { createGameConfig, GameConfigOptions } from \"../game-config\"\nimport { Simulation, SimulationConfigOptions, SimulationOptions } from \"../simulation\"\nimport { Analysis, AnalysisOpts } from \"../analysis\"\nimport { OptimizationOpts, Optimizer, OptimizerOpts } from \"../optimizer\"\n\n/**\n * SlotGame class that encapsulates the game configuration and state.\\\n * Main entry point for the slot game.\n */\nexport class SlotGame<\n TGameModes extends AnyGameModes = AnyGameModes,\n TSymbols extends AnySymbols = AnySymbols,\n TUserState extends AnyUserData = AnyUserData,\n> {\n private readonly configOpts: GameConfigOptions<TGameModes, TSymbols, TUserState>\n private simulation?: Simulation\n private optimizer?: Optimizer\n private analyzer?: Analysis\n\n constructor(config: GameConfigOptions<TGameModes, TSymbols, TUserState>) {\n this.configOpts = config\n }\n\n /**\n * Sets up the simulation configuration.\\\n * Must be called before `runTasks()`.\n */\n configureSimulation(opts: SimulationOptions) {\n // @ts-ignore TODO: Fix type errors with AnyTypes\n this.simulation = new Simulation(opts, this.configOpts)\n }\n\n /**\n * Sets up the optimization configuration.\\\n * Must be called before `runTasks()`.\n */\n configureOptimization(opts: Pick<OptimizerOpts, \"gameModes\">) {\n this.optimizer = new Optimizer({\n game: this,\n gameModes: opts.gameModes,\n })\n }\n\n /**\n * Runs the simulation based on the configured settings.\n */\n private async runSimulation(opts: SimulationConfigOptions = {}) {\n if (!this.simulation) {\n throw new Error(\n \"Simulation is not configured. Do so by calling configureSimulation() first.\",\n )\n }\n\n await this.simulation.runSimulation(opts)\n }\n\n /**\n * Runs the optimization based on the configured settings.\n */\n private async runOptimization(opts: OptimizationOpts) {\n if (!this.optimizer) {\n throw new Error(\n \"Optimization is not configured. Do so by calling configureOptimization() first.\",\n )\n }\n\n await this.optimizer.runOptimization(opts)\n }\n\n /**\n * Runs the analysis based on the configured settings.\n */\n private async runAnalysis(opts: AnalysisOpts) {\n if (!this.optimizer) {\n throw new Error(\n \"Optimization must be configured to run analysis. Do so by calling configureOptimization() first.\",\n )\n }\n this.analyzer = new Analysis(this.optimizer)\n await this.analyzer.runAnalysis(opts.gameModes)\n }\n\n /**\n * Runs the configured tasks: simulation, optimization, and/or analysis.\n */\n async runTasks(\n opts: {\n doSimulation?: boolean\n doOptimization?: boolean\n doAnalysis?: boolean\n simulationOpts?: SimulationConfigOptions\n optimizationOpts?: OptimizationOpts\n analysisOpts?: AnalysisOpts\n } = {},\n ) {\n if (!opts.doSimulation && !opts.doOptimization && !opts.doAnalysis) {\n console.log(\"No tasks to run. Enable either simulation, optimization or analysis.\")\n }\n\n if (opts.doSimulation) {\n await this.runSimulation(opts.simulationOpts || {})\n }\n\n if (opts.doOptimization) {\n await this.runOptimization(opts.optimizationOpts || { gameModes: [] })\n }\n\n if (opts.doAnalysis) {\n await this.runAnalysis(opts.analysisOpts || { gameModes: [] })\n }\n }\n\n /**\n * Gets the game configuration.\n */\n getConfig() {\n return createGameConfig(this.configOpts)\n }\n}\n","import { GameConfigOptions } from \"./game-config\"\nimport { SlotGame } from \"./slot-game\"\nimport { AnyGameModes, AnySymbols, AnyUserData, InferGameType } from \"./types\"\n\nexport function createSlotGame<TGame>(\n opts: TGame extends InferGameType<infer G, infer S, infer U>\n ? GameConfigOptions<G, S, U>\n : never,\n) {\n return new SlotGame(opts) as TGame\n}\n\nexport const defineUserState = <TUserState extends AnyUserData>(data: TUserState) => data\n\nexport const defineSymbols = <TSymbols extends AnySymbols>(symbols: TSymbols) => symbols\n\nexport const defineGameModes = <TGameModes extends AnyGameModes>(gameModes: TGameModes) =>\n gameModes\n","import assert from \"assert\"\nimport { ResultSet } from \"../result-set\"\nimport { ReelSet } from \"../reel-set\"\n\nexport class GameMode {\n readonly name: string\n readonly reelsAmount: number\n readonly symbolsPerReel: number[]\n readonly cost: number\n readonly rtp: number\n readonly reelSets: ReelSet[]\n readonly resultSets: ResultSet<any>[]\n readonly isBonusBuy: boolean\n\n constructor(opts: GameModeOpts) {\n this.name = opts.name\n this.reelsAmount = opts.reelsAmount\n this.symbolsPerReel = opts.symbolsPerReel\n this.cost = opts.cost\n this.rtp = opts.rtp\n this.reelSets = opts.reelSets\n this.resultSets = opts.resultSets\n this.isBonusBuy = opts.isBonusBuy\n\n assert(this.rtp >= 0.9 && this.rtp <= 0.99, \"RTP must be between 0.9 and 0.99\")\n\n assert(\n this.symbolsPerReel.length === this.reelsAmount,\n \"symbolsPerReel length must match reelsAmount.\",\n )\n\n assert(this.reelSets.length > 0, \"GameMode must have at least one ReelSet defined.\")\n }\n}\n\nexport interface GameModeOpts {\n /**\n * Name of the game mode.\n */\n name: string\n /**\n * Number of reels the board has.\n */\n reelsAmount: number\n /**\n * How many symbols each reel has. Array length must match `reelsAmount`.\\\n * The number at an array index represents the number of symbols on that reel.\n */\n symbolsPerReel: number[]\n /**\n * Cost of the game mode, multiplied by the base bet.\n */\n cost: number\n /**\n * The target RTP of the game.\n */\n rtp: number\n /**\n * Defines (and generates) all reels for the game.\\\n * Which reels are used in a spin is determined by the ResultSet of the current game mode.\n *\n * It is common to have one reel set for the base game and another for free spins.\\\n * Each `ResultSet` can then set the weights of these reel sets to control which\\\n * reel set is used for a specific criteria.\n */\n reelSets: ReelSet[]\n /**\n * A ResultSet defines how often a specific outcome should be generated.\\\n * For example, a ResultSet can be used to force a specific ratio of max wins\\\n * in the simulations to ensure there are different frontend representations.\n */\n resultSets: ResultSet<any>[]\n /**\n * Whether this game mode is a bonus buy.\n */\n isBonusBuy: boolean\n}\n","import { GameContext } from \"../game-context\"\nimport { GameSymbol } from \"../game-symbol\"\nimport { Reels } from \"../types\"\n\nexport class WinType {\n protected payout: number\n protected winCombinations: WinCombination[]\n protected ctx: GameContext\n protected readonly wildSymbol?: WildSymbol\n\n constructor(opts: WinTypeOpts) {\n this.ctx = opts.ctx\n this.payout = 0\n this.winCombinations = []\n this.wildSymbol = opts?.wildSymbol\n }\n\n /**\n * Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.\n */\n evaluateWins(board: Reels) {\n return this\n }\n\n /**\n * Custom post-processing of wins, e.g. for handling multipliers.\n */\n postProcess(func: PostProcessFn<typeof this.winCombinations>) {\n const result = func(this, this.ctx)\n this.payout = result.payout\n this.winCombinations = result.winCombinations\n return this\n }\n\n /**\n * Returns the total payout and detailed win combinations.\n */\n getWins() {\n return {\n payout: this.payout,\n winCombinations: this.winCombinations,\n }\n }\n\n protected isWild(symbol: GameSymbol) {\n return !!this.wildSymbol && symbol.compare(this.wildSymbol)\n }\n}\n\nexport interface WinTypeOpts {\n /**\n * A reference to the game context.\n */\n ctx: GameContext<any, any, any> // TODO: Hacky and stupid. Fix AnyTypes.\n /**\n * Configuration used to identify wild symbols on the board.\\\n * You can either provide a specific `GameSymbol` instance or a set of properties to match against symbols on the board.\n *\n * @example\n * If you have different wild symbols, each with a property `isWild: true`, you can define:\n * ```ts\n * wildSymbol: { isWild: true }\n * ```\n *\n * @example\n * If you have a single wild symbol instance, you can define:\n * ```ts\n * wildSymbol: myWildSymbol\n * ```\n */\n wildSymbol?: WildSymbol\n}\n\nexport type WinCombination = {\n payout: number\n kind: number\n symbols: Array<{\n symbol: GameSymbol\n isWild: boolean\n substitutedFor?: GameSymbol\n reelIndex: number\n posIndex: number\n }>\n}\n\ntype PostProcessFn<TWinCombs extends WinCombination[]> = (\n winType: WinType,\n ctx: GameContext,\n) => {\n payout: number\n winCombinations: TWinCombs\n}\n\ntype WildSymbol = GameSymbol | Record<string, any>\n","import { GameSymbol } from \"../game-symbol\"\nimport { WinCombination, WinType, WinTypeOpts } from \".\"\nimport { Reels } from \"../types\"\n\nexport class LinesWinType extends WinType {\n protected lines: Record<number, number[]>\n declare protected winCombinations: LineWinCombination[]\n declare getWins: () => {\n payout: number\n winCombinations: LineWinCombination[]\n }\n\n constructor(opts: LinesWinTypeOpts) {\n super(opts)\n this.lines = opts.lines\n\n if (Object.keys(this.lines).length === 0) {\n throw new Error(\"LinesWinType must have at least one line defined.\")\n }\n }\n\n private validateConfig() {\n const reelsAmount = this.ctx.services.game.getCurrentGameMode().reelsAmount\n const symsPerReel = this.ctx.services.game.getCurrentGameMode().symbolsPerReel\n\n for (const [lineNum, positions] of Object.entries(this.lines)) {\n if (positions.length !== reelsAmount) {\n throw new Error(\n `Line ${lineNum} has ${positions.length} positions, but the current game mode has ${reelsAmount} reels.`,\n )\n }\n for (let i = 0; i < positions.length; i++) {\n if (positions[i]! < 0 || positions[i]! >= symsPerReel[i]!) {\n throw new Error(\n `Line ${lineNum} has an invalid position ${positions[i]} on reel ${i}. Valid range is 0 to ${\n symsPerReel[i]! - 1\n }.`,\n )\n }\n }\n }\n\n const firstLine = Math.min(...Object.keys(this.lines).map(Number))\n if (firstLine !== 1) {\n throw new Error(\n `Lines must start from 1. Found line ${firstLine} as the first line.`,\n )\n }\n }\n\n /**\n * Calculates wins based on the defined paylines and provided board state.\\\n * Retrieve the results using `getWins()` after.\n */\n evaluateWins(board: Reels) {\n this.validateConfig()\n\n const lineWins: LineWinCombination[] = []\n let payout = 0\n\n const reels = board\n\n for (const [lineNumStr, lineDef] of Object.entries(this.lines)) {\n const lineNum = Number(lineNumStr)\n\n let baseSymbol: GameSymbol | null = null\n let leadingWilds = 0\n const chain: GameSymbol[] = []\n const details: LineWinCombination[\"symbols\"] = []\n\n for (let ridx = 0; ridx < reels.length; ridx++) {\n const rowIdx = lineDef[ridx]!\n const sym = reels[ridx]![rowIdx]\n if (!sym) throw new Error(\"Encountered an invalid symbol while evaluating wins.\")\n\n const wild = this.isWild(sym)\n\n if (ridx === 0) {\n chain.push(sym)\n details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild })\n if (wild) leadingWilds++\n else baseSymbol = sym\n continue\n }\n\n if (wild) {\n chain.push(sym)\n details.push({\n reelIndex: ridx,\n posIndex: rowIdx,\n symbol: sym,\n isWild: true,\n substitutedFor: baseSymbol || undefined,\n })\n continue\n }\n\n if (!baseSymbol) {\n baseSymbol = sym\n chain.push(sym)\n details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false })\n continue\n }\n\n if (sym.id === baseSymbol.id) {\n chain.push(sym)\n details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false })\n continue\n }\n\n break\n }\n\n if (chain.length === 0) continue\n\n const allWild = chain.every((s) => this.isWild(s))\n const wildRepresentative =\n this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null\n\n const len = chain.length\n let bestPayout = 0\n let bestType: LineWinCombination[\"winType\"] | null = null\n let payingSymbol: GameSymbol | null = null\n\n if (baseSymbol?.pays && baseSymbol.pays[len]) {\n bestPayout = baseSymbol.pays[len]!\n bestType = \"substituted\"\n payingSymbol = baseSymbol\n }\n\n if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {\n const wildPay = wildRepresentative.pays[len]!\n if (wildPay > bestPayout) {\n bestPayout = wildPay\n bestType = \"pure-wild\"\n payingSymbol = wildRepresentative\n }\n }\n\n if (!bestPayout || !bestType || !payingSymbol) continue\n\n const minLen = payingSymbol.pays\n ? Math.min(...Object.keys(payingSymbol.pays).map(Number))\n : Infinity\n\n if (len < minLen) continue\n\n const wildCount = details.filter((d) => d.isWild).length\n const nonWildCount = len - wildCount\n\n lineWins.push({\n lineNumber: lineNum,\n kind: len,\n payout: bestPayout,\n symbol: payingSymbol,\n winType: bestType,\n substitutedBaseSymbol: bestType === \"pure-wild\" ? null : baseSymbol,\n symbols: details,\n stats: { wildCount, nonWildCount, leadingWilds },\n })\n payout += bestPayout\n }\n\n for (const win of lineWins) {\n this.ctx.services.data.recordSymbolOccurrence({\n kind: win.kind,\n symbolId: win.symbol.id,\n spinType: this.ctx.state.currentSpinType,\n })\n }\n\n this.payout = payout\n this.winCombinations = lineWins\n\n return this\n }\n}\n\ninterface LinesWinTypeOpts extends WinTypeOpts {\n /**\n * Defines the paylines for the slot game.\n *\n * @example\n * ```ts\n * lines: {\n * 1: [0, 0, 0, 0, 0],\n * 2: [1, 1, 1, 1, 1],\n * 3: [2, 2, 2, 2, 2],\n * }\n * ```\n */\n lines: Record<number, number[]>\n}\n\nexport interface LineWinCombination extends WinCombination {\n lineNumber: number\n symbol: GameSymbol\n winType: \"pure-wild\" | \"substituted\"\n substitutedBaseSymbol: GameSymbol | null\n stats: {\n wildCount: number\n nonWildCount: number\n leadingWilds: number\n }\n}\n","import { WinType } from \".\"\n\nexport class ClusterWinType extends WinType {}\n","import { WinType } from \".\"\n\nexport class ManywaysWinType extends WinType {}\n","import fs from \"fs\"\nimport path from \"path\"\nimport { isMainThread } from \"worker_threads\"\nimport { ReelSet, ReelSetOptions } from \".\"\nimport { GameConfig } from \"../game-config\"\nimport { GameSymbol } from \"../game-symbol\"\nimport { Simulation } from \"../simulation\"\n\n/**\n * This class is responsible for generating reel sets for slot games based on specified configurations.\n *\n * **While it offers a high degree of customization, some configurations may lead to unsolvable scenarios.**\n *\n * If the reel generator is unable to fulfill niche constraints,\\\n * you might need to adjust your configuration, or edit the generated reels manually.\\\n * Setting a different seed may also help.\n */\nexport class GeneratedReelSet extends ReelSet {\n protected readonly symbolWeights: Map<string, number> = new Map()\n protected readonly rowsAmount: number\n protected limitSymbolsToReels?: Record<string, number[]>\n protected readonly spaceBetweenSameSymbols?: number | Record<string, number>\n protected readonly spaceBetweenSymbols?: Record<string, Record<string, number>>\n protected readonly preferStackedSymbols?: number\n protected readonly symbolStacks?: Record<\n string,\n {\n chance: number | Record<string, number>\n min?: number | Record<string, number>\n max?: number | Record<string, number>\n }\n >\n protected readonly symbolQuotas?: Record<string, number | Record<string, number>>\n private overrideExisting: boolean\n\n constructor(opts: GeneratedReelSetOptions) {\n super(opts)\n this.id = opts.id\n this.symbolWeights = new Map(Object.entries(opts.symbolWeights))\n this.rowsAmount = opts.rowsAmount || 250\n\n if (opts.limitSymbolsToReels) this.limitSymbolsToReels = opts.limitSymbolsToReels\n\n this.overrideExisting = opts.overrideExisting || false\n this.spaceBetweenSameSymbols = opts.spaceBetweenSameSymbols\n this.spaceBetweenSymbols = opts.spaceBetweenSymbols\n this.preferStackedSymbols = opts.preferStackedSymbols\n this.symbolStacks = opts.symbolStacks\n this.symbolQuotas = opts.symbolQuotas\n\n if (\n (typeof this.spaceBetweenSameSymbols == \"number\" &&\n (this.spaceBetweenSameSymbols < 1 || this.spaceBetweenSameSymbols > 8)) ||\n (typeof this.spaceBetweenSameSymbols == \"object\" &&\n Object.values(this.spaceBetweenSameSymbols).some((v) => v < 1 || v > 8))\n ) {\n throw new Error(\n `spaceBetweenSameSymbols must be between 1 and 8, got ${this.spaceBetweenSameSymbols}.`,\n )\n }\n\n if (\n Object.values(this.spaceBetweenSymbols || {}).some((o) =>\n Object.values(o).some((v) => v < 1 || v > 8),\n )\n ) {\n throw new Error(\n `spaceBetweenSymbols must be between 1 and 8, got ${this.spaceBetweenSymbols}.`,\n )\n }\n\n if (\n this.preferStackedSymbols &&\n (this.preferStackedSymbols < 0 || this.preferStackedSymbols > 100)\n ) {\n throw new Error(\n `preferStackedSymbols must be between 0 and 100, got ${this.preferStackedSymbols}.`,\n )\n }\n }\n\n private validateConfig(config: GameConfig) {\n this.symbolWeights.forEach((_, symbol) => {\n if (!config.symbols.has(symbol)) {\n throw new Error(\n `Symbol \"${symbol}\" of the reel generator ${this.id} for mode ${this.associatedGameModeName} is not defined in the game config`,\n )\n }\n })\n\n if (this.limitSymbolsToReels && Object.keys(this.limitSymbolsToReels).length == 0) {\n this.limitSymbolsToReels = undefined\n }\n }\n\n private isSymbolAllowedOnReel(symbolId: string, reelIdx: number) {\n if (!this.limitSymbolsToReels) return true\n const allowedReels = this.limitSymbolsToReels[symbolId]\n if (!allowedReels || allowedReels.length === 0) return true\n return allowedReels.includes(reelIdx)\n }\n\n private resolveStacking(symbolId: string, reelIdx: number) {\n const cfg = this.symbolStacks?.[symbolId]\n if (!cfg) return null\n\n const STACKING_MIN = 1\n const STACKING_MAX = 4\n\n const chance =\n typeof cfg.chance === \"number\" ? cfg.chance : (cfg.chance?.[reelIdx] ?? 0)\n if (chance <= 0) return null\n\n let min = typeof cfg.min === \"number\" ? cfg.min : (cfg.min?.[reelIdx] ?? STACKING_MIN)\n let max = typeof cfg.max === \"number\" ? cfg.max : (cfg.max?.[reelIdx] ?? STACKING_MAX)\n\n return { chance, min, max }\n }\n\n private tryPlaceStack(\n reel: Array<GameSymbol | null>,\n config: GameConfig,\n reelIdx: number,\n symbolId: string,\n startIndex: number,\n maxStack: number,\n ) {\n if (!this.isSymbolAllowedOnReel(symbolId, reelIdx)) return 0\n\n let canPlace = 0\n for (let j = 0; j < maxStack; j++) {\n const idx = (startIndex + j) % this.rowsAmount\n if (reel[idx] !== null) break\n canPlace++\n }\n if (canPlace === 0) return 0\n\n const symObj = config.symbols.get(symbolId)\n if (!symObj) {\n throw new Error(\n `Symbol with id \"${symbolId}\" not found in the game config symbols map.`,\n )\n }\n\n for (let j = 0; j < canPlace; j++) {\n const idx = (startIndex + j) % reel.length\n reel[idx] = symObj\n }\n return canPlace\n }\n\n /**\n * Checks if a symbol can be placed at the target index without violating spacing rules.\n */\n private violatesSpacing(\n reel: Array<GameSymbol | null>,\n symbolId: string,\n targetIndex: number,\n ) {\n const circDist = (a: number, b: number) => {\n const diff = Math.abs(a - b)\n return Math.min(diff, this.rowsAmount - diff)\n }\n\n const spacingType = this.spaceBetweenSameSymbols ?? undefined\n const sameSpacing =\n typeof spacingType === \"number\" ? spacingType : (spacingType?.[symbolId] ?? 0)\n\n for (let i = 0; i <= reel.length; i++) {\n const placed = reel[i]\n if (!placed) continue\n\n const dist = circDist(targetIndex, i)\n\n // Same symbol spacing\n if (sameSpacing >= 1 && placed.id === symbolId) {\n if (dist <= sameSpacing) return true\n }\n\n // Cross-symbol spacing\n if (this.spaceBetweenSymbols) {\n const forward = this.spaceBetweenSymbols[symbolId]?.[placed.id] ?? 0\n if (forward >= 1 && dist <= forward) return true\n\n const reverse = this.spaceBetweenSymbols[placed.id]?.[symbolId] ?? 0\n if (reverse >= 1 && dist <= reverse) return true\n }\n }\n\n return false\n }\n\n generateReels({ gameConfig: config }: Simulation) {\n this.validateConfig(config)\n\n const gameMode = config.gameModes[this.associatedGameModeName]\n\n if (!gameMode) {\n throw new Error(\n `Error generating reels for game mode \"${this.associatedGameModeName}\". It's not defined in the game config.`,\n )\n }\n\n const filePath = path.join(\n config.outputDir,\n `reels_${this.associatedGameModeName}-${this.id}.csv`,\n )\n\n const exists = fs.existsSync(filePath)\n\n if (exists && !this.overrideExisting) {\n this.reels = this.parseReelsetCSV(filePath, config)\n return\n }\n\n if (!exists && this.symbolWeights.size === 0) {\n throw new Error(\n `Cannot generate reels for generator \"${this.id}\" of mode \"${this.associatedGameModeName}\" because the symbol weights are empty.`,\n )\n }\n\n const reelsAmount = gameMode.reelsAmount\n const weightsObj = Object.fromEntries(this.symbolWeights)\n\n // Generate initial reels with random symbols\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n const reel: Array<GameSymbol | null> = new Array(this.rowsAmount).fill(null)\n\n const reelQuotas: Record<string, number> = {}\n const quotaCounts: Record<string, number> = {}\n let totalReelsQuota = 0\n\n // Get quotas for this reel, across all symbols\n for (const [sym, quotaConf] of Object.entries(this.symbolQuotas || {})) {\n const q = typeof quotaConf === \"number\" ? quotaConf : quotaConf[ridx]\n if (!q) continue\n reelQuotas[sym] = q\n totalReelsQuota += q\n }\n\n if (totalReelsQuota > 100) {\n throw new Error(\n `Total symbol quotas for reel ${ridx} exceed 100%. Adjust your configuration on reel set \"${this.id}\".`,\n )\n }\n\n if (totalReelsQuota > 0) {\n for (const [sym, quota] of Object.entries(reelQuotas)) {\n const quotaCount = Math.max(1, Math.floor((this.rowsAmount * quota) / 100))\n quotaCounts[sym] = quotaCount\n }\n }\n\n // Place required quotas first (use stacking over spacing, if configured)\n for (const [sym, targetCount] of Object.entries(quotaCounts)) {\n let remaining = targetCount\n let attempts = 0\n\n while (remaining > 0) {\n if (attempts++ > this.rowsAmount * 10) {\n throw new Error(\n `Failed to place ${targetCount} of symbol ${sym} on reel ${ridx} (likely spacing/stacking too strict).`,\n )\n }\n\n const pos = Math.round(this.rng.randomFloat(0, this.rowsAmount - 1))\n const stackCfg = this.resolveStacking(sym, ridx)\n let placed = 0\n\n // Try to place a symbol stack first, if configured\n if (stackCfg && Math.round(this.rng.randomFloat(1, 100)) <= stackCfg.chance) {\n const stackSize = Math.max(\n 0,\n Math.round(this.rng.randomFloat(stackCfg.min, stackCfg.max)),\n )\n const toPlace = Math.min(stackSize, remaining)\n placed = this.tryPlaceStack(reel, config, ridx, sym, pos, toPlace)\n }\n\n // Not enough space, fall back to placing single symbols\n if (\n placed === 0 &&\n reel[pos] === null &&\n this.isSymbolAllowedOnReel(sym, ridx) &&\n !this.violatesSpacing(reel, sym, pos)\n ) {\n reel[pos] = config.symbols.get(sym)!\n placed = 1\n }\n\n remaining -= placed\n }\n }\n\n // Fill the rest of the reel randomly\n for (let r = 0; r < this.rowsAmount; r++) {\n if (reel[r] !== null) continue // already placed quota\n\n let chosenSymbolId = this.rng.weightedRandom(weightsObj)\n\n // If symbolStacks is NOT configured for the next choice, allow \"preferStackedSymbols\" fallback\n const nextHasStackCfg = !!this.resolveStacking(chosenSymbolId, ridx)\n if (!nextHasStackCfg && this.preferStackedSymbols && reel.length > 0) {\n const prevSymbol = r - 1 >= 0 ? reel[r - 1] : reel[reel.length - 1]\n if (\n prevSymbol &&\n Math.round(this.rng.randomFloat(1, 100)) <= this.preferStackedSymbols &&\n (!this.spaceBetweenSameSymbols ||\n !this.violatesSpacing(reel, prevSymbol.id, r))\n ) {\n chosenSymbolId = prevSymbol.id\n }\n }\n\n // Check for stacking preference\n if (this.preferStackedSymbols && reel.length > 0) {\n const prevSymbol = r - 1 >= 0 ? reel[r - 1] : reel[reel.length - 1]\n\n if (\n Math.round(this.rng.randomFloat(1, 100)) <= this.preferStackedSymbols &&\n (!this.spaceBetweenSameSymbols ||\n !this.violatesSpacing(reel, prevSymbol!.id, r))\n ) {\n chosenSymbolId = prevSymbol!.id\n }\n }\n\n // If symbol has stack config, try to place a stack (ignore spacing)\n const stackCfg = this.resolveStacking(chosenSymbolId, ridx)\n if (stackCfg && this.isSymbolAllowedOnReel(chosenSymbolId, ridx)) {\n const roll = Math.round(this.rng.randomFloat(1, 100))\n if (roll <= stackCfg.chance) {\n const desiredSize = Math.max(\n 1,\n Math.round(this.rng.randomFloat(stackCfg.min, stackCfg.max)),\n )\n const placed = this.tryPlaceStack(\n reel,\n config,\n ridx,\n chosenSymbolId,\n r,\n desiredSize,\n )\n if (placed > 0) {\n // advance loop to skip the cells we just filled on this side of the boundary\n // (wrapped cells at the start are already filled and will be skipped when encountered)\n r += placed - 1\n continue\n }\n }\n }\n\n let tries = 0\n const maxTries = 2500\n\n while (\n !this.isSymbolAllowedOnReel(chosenSymbolId, ridx) ||\n this.violatesSpacing(reel, chosenSymbolId, r)\n ) {\n if (++tries > maxTries) {\n throw new Error(\n [\n `Failed to place a symbol on reel ${ridx} at position ${r} after ${maxTries} attempts.\\n`,\n \"Try to change the seed or adjust your configuration.\\n\",\n ].join(\" \"),\n )\n }\n chosenSymbolId = this.rng.weightedRandom(weightsObj)\n\n const hasStackCfg = !!this.resolveStacking(chosenSymbolId, ridx)\n if (!hasStackCfg && this.preferStackedSymbols && reel.length > 0) {\n const prevSymbol = r - 1 >= 0 ? reel[r - 1] : reel[reel.length - 1]\n if (\n prevSymbol &&\n Math.round(this.rng.randomFloat(1, 100)) <= this.preferStackedSymbols &&\n (!this.spaceBetweenSameSymbols ||\n !this.violatesSpacing(reel, prevSymbol.id, r))\n ) {\n chosenSymbolId = prevSymbol.id\n }\n }\n }\n\n const symbol = config.symbols.get(chosenSymbolId)\n\n if (!symbol) {\n throw new Error(\n `Symbol with id \"${chosenSymbolId}\" not found in the game config symbols map.`,\n )\n }\n\n reel[r] = symbol\n }\n\n if (reel.some((s) => s === null)) {\n throw new Error(`Reel ${ridx} has unfilled positions after generation.`)\n }\n\n this.reels.push(reel as GameSymbol[])\n }\n\n // Write the CSV\n const csvRows: string[][] = Array.from({ length: this.rowsAmount }, () =>\n Array.from({ length: reelsAmount }, () => \"\"),\n )\n\n for (let ridx = 0; ridx < reelsAmount; ridx++) {\n for (let r = 0; r < this.rowsAmount; r++) {\n csvRows[r]![ridx] = this.reels[ridx]![r]!.id\n }\n }\n\n const csvString = csvRows.map((row) => row.join(\",\")).join(\"\\n\")\n\n if (isMainThread) {\n fs.writeFileSync(filePath, csvString)\n\n this.reels = this.parseReelsetCSV(filePath, config)\n console.log(\n `Generated reelset ${this.id} for game mode ${this.associatedGameModeName}`,\n )\n }\n }\n}\n\ninterface GeneratedReelSetOptions extends ReelSetOptions {\n /**\n * The weights of the symbols in the reelset.\\\n * This is a mapping of symbol IDs to their respective weights.\n */\n symbolWeights: Record<string, number>\n /**\n * The number of rows in the reelset.\\\n * Default is 250, but can be adjusted as needed.\n */\n rowsAmount?: number\n /**\n * Prevent the same symbol from appearing directly above or below itself.\\\n * This can be a single number for all symbols, or a mapping of symbol IDs to\n * their respective spacing values.\n *\n * Must be 1 or higher, if set.\n *\n * **This is overridden by `symbolStacks`**\n */\n spaceBetweenSameSymbols?: number | Record<string, number>\n /**\n * Prevents specific symbols from appearing within a certain distance of each other.\n *\n * Useful for preventing scatter and super scatter symbols from appearing too close to each other.\n *\n * **This is overridden by `symbolStacks`**\n */\n spaceBetweenSymbols?: Record<string, Record<string, number>>\n /**\n * A percentage value 0-100 that indicates the likelihood of a symbol being stacked.\\\n * A value of 0 means no stacked symbols, while 100 means all symbols are stacked.\n *\n * This is only a preference. Symbols may still not be stacked if\\\n * other restrictions (like `spaceBetweenSameSymbols`) prevent it.\n *\n * **This is overridden by `symbolStacks`**\n */\n preferStackedSymbols?: number\n /**\n * A mapping of symbols to their respective advanced stacking configuration.\n *\n * @example\n * ```ts\n * symbolStacks: {\n * \"W\": {\n * chance: { \"1\": 20, \"2\": 20, \"3\": 20, \"4\": 20 }, // 20% chance to be stacked on reels 2-5\n * min: 2, // At least 2 wilds in a stack\n * max: 4, // At most 4 wilds in a stack\n * }\n * }\n * ```\n */\n symbolStacks?: Record<\n string,\n {\n chance: number | Record<string, number>\n min?: number | Record<string, number>\n max?: number | Record<string, number>\n }\n >\n /**\n * Configures symbols to be limited to specific reels.\\\n * For example, you could configure Scatters to appear only on reels 1, 3 and 5.\n *\n * @example\n * ```ts\n * limitSymbolsToReels: {\n * \"S\": [0, 2, 4], // Remember that reels are 0-indexed.\n * }\n * ```\n */\n limitSymbolsToReels?: Record<string, number[]>\n /**\n * Defines optional quotas for symbols on the reels.\\\n * The quota (1-100%) defines how often a symbol should appear in the reelset, or in a specific reel.\n *\n * This is particularly useful for controlling the frequency of special symbols like scatters or wilds.\n *\n * Reels not provided for a symbol will use the weights from `symbolWeights`.\n *\n * _Any_ small quota will ensure that the symbol appears at least once on the reel.\n *\n * @example\n * ```ts\n * symbolQuotas: {\n * \"S\": 3, // 3% of symbols on each reel will be scatters\n * \"W\": { \"1\": 10, \"2\": 5, \"3\": 3, \"4\": 1 }, // Wilds will appear with different quotas on selected reels\n * }\n * ```\n */\n symbolQuotas?: Record<string, number | Record<string, number>>\n /**\n * If true, existing reels CSV files will be overwritten.\n */\n overrideExisting?: boolean\n /**\n * Optional seed for the RNG to ensure reproducible results.\n *\n * Default seed is `0`.\n *\n * Note: Seeds 0 and 1 produce the same results.\n */\n seed?: number\n}\n\nexport type Reels = GameSymbol[][]\n","import fs from \"fs\"\nimport path from \"path\"\nimport { GameConfig } from \"../game-config\"\nimport { RandomNumberGenerator } from \"../service/rng\"\nimport { Simulation } from \"../simulation\"\nimport { Reels } from \"../types\"\n\nexport class ReelSet {\n id: string\n associatedGameModeName: string\n reels: Reels\n protected rng: RandomNumberGenerator\n\n constructor(opts: ReelSetOptions) {\n this.id = opts.id\n this.associatedGameModeName = \"\"\n this.reels = []\n this.rng = new RandomNumberGenerator()\n this.rng.setSeed(opts.seed ?? 0)\n }\n\n generateReels(simulation: Simulation) {\n throw new Error(\"Not implemented\")\n }\n\n /**\n * Reads a reelset CSV file and returns the reels as arrays of GameSymbols.\n */\n parseReelsetCSV(reelSetPath: string, config: GameConfig) {\n if (!fs.existsSync(reelSetPath)) {\n throw new Error(`Reelset CSV file not found at path: ${reelSetPath}`)\n }\n\n const allowedExtensions = [\".csv\"]\n const ext = path.extname(reelSetPath).toLowerCase()\n if (!allowedExtensions.includes(ext)) {\n throw new Error(\n `Invalid file extension for reelset CSV: ${ext}. Allowed extensions are: ${allowedExtensions.join(\n \", \",\n )}`,\n )\n }\n\n const csvData = fs.readFileSync(reelSetPath, \"utf8\")\n const rows = csvData.split(\"\\n\").filter((line) => line.trim() !== \"\")\n const reels: Reels = Array.from(\n { length: config.gameModes[this.associatedGameModeName]!.reelsAmount },\n () => [],\n )\n\n rows.forEach((row) => {\n const symsInRow = row.split(\",\").map((symbolId) => {\n const symbol = config.symbols.get(symbolId.trim())\n if (!symbol) {\n throw new Error(`Symbol with id \"${symbolId}\" not found in game config.`)\n }\n return symbol\n })\n symsInRow.forEach((symbol, ridx) => {\n if (ridx >= reels.length) {\n throw new Error(\n `Row in reelset CSV has more symbols than expected reels amount (${reels.length})`,\n )\n }\n reels[ridx]!.push(symbol)\n })\n })\n\n const reelLengths = reels.map((r) => r.length)\n const uniqueLengths = new Set(reelLengths)\n if (uniqueLengths.size > 1) {\n throw new Error(\n `Inconsistent reel lengths in reelset CSV at ${reelSetPath}: ${[\n ...uniqueLengths,\n ].join(\", \")}`,\n )\n }\n\n return reels\n }\n}\n\nexport interface ReelSetOptions {\n /**\n * The unique identifier of the reel generator.\\\n * Must be unique per game mode.\n */\n id: string\n /**\n * Optional seed for the RNG to ensure reproducible results.\n *\n * Default seed is `0`.\n *\n * Note: Seeds 0 and 1 produce the same results.\n */\n seed?: number\n}\n","import assert from \"assert\"\nimport { ReelSet, ReelSetOptions } from \".\"\nimport { GameConfig } from \"../game-config\"\nimport { Simulation } from \"../simulation\"\nimport { Reels } from \"../types\"\n\n/**\n * This class is responsible for providing reel sets for slot games based on a static configuration or file.\n */\nexport class StaticReelSet extends ReelSet {\n reels: Reels\n csvPath: string\n private _strReels: string[][]\n\n constructor(opts: StaticReelSetOptions) {\n super(opts)\n\n this.reels = []\n this._strReels = opts.reels || []\n this.csvPath = opts.csvPath || \"\"\n\n assert(\n opts.reels || opts.csvPath,\n `Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`,\n )\n }\n\n private validateConfig(config: GameConfig) {\n this.reels.forEach((reel) => {\n reel.forEach((symbol) => {\n if (!config.symbols.has(symbol.id)) {\n throw new Error(\n `Symbol \"${symbol}\" of the reel set ${this.id} for mode ${this.associatedGameModeName} is not defined in the game config`,\n )\n }\n })\n })\n\n if (this.csvPath && this._strReels.length > 0) {\n throw new Error(\n `Both 'csvPath' and 'reels' are provided for StaticReelSet ${this.id}. Please provide only one.`,\n )\n }\n }\n\n generateReels({ gameConfig: config }: Simulation) {\n this.validateConfig(config)\n\n if (this._strReels.length > 0) {\n this.reels = this._strReels.map((reel) => {\n return reel.map((symbolId) => {\n const symbol = config.symbols.get(symbolId)\n if (!symbol) {\n throw new Error(\n `Symbol \"${symbolId}\" of the reel set ${this.id} for mode ${this.associatedGameModeName} is not defined in the game config`,\n )\n }\n return symbol\n })\n })\n }\n\n if (this.csvPath) {\n this.reels = this.parseReelsetCSV(this.csvPath, config)\n }\n }\n}\n\ninterface StaticReelSetOptions extends ReelSetOptions {\n reels?: string[][]\n csvPath?: string\n}\n","import { GameContext } from \"../game-context\"\nimport { GameSymbol } from \"../game-symbol\"\nimport { Reels } from \"../types\"\nimport { Board } from \".\"\n\nexport class StandaloneBoard {\n private board: Board\n private ctx: GameContext\n private reelsAmount: number\n private symbolsPerReel: number[]\n private padSymbols: number\n\n constructor(opts: StandaloneBoardOptions) {\n this.board = new Board()\n this.ctx = opts.ctx\n this.reelsAmount = opts.reelsAmount\n this.symbolsPerReel = opts.symbolsPerReel\n this.padSymbols = opts.padSymbols\n }\n\n /**\n * Resets the board to an empty state.\\\n * This is called before drawing a new board.\n */\n resetBoard() {\n this.resetReels()\n this.board.lastDrawnReelStops = []\n }\n\n /**\n * Gets the current reels and symbols on the board.\n */\n getBoardReels() {\n return this.board.reels\n }\n\n getPaddingTop() {\n return this.board.paddingTop\n }\n\n getPaddingBottom() {\n return this.board.paddingBottom\n }\n\n private resetReels() {\n this.board.resetReels({\n ctx: this.ctx,\n })\n }\n\n /**\n * Sets the anticipation value for a specific reel.\n */\n setAnticipationForReel(reelIndex: number, value: boolean) {\n this.board.anticipation[reelIndex] = value\n }\n\n /**\n * Counts how many symbols matching the criteria are on a specific reel.\n */\n countSymbolsOnReel(\n symbolOrProperties: GameSymbol | Record<string, any>,\n reelIndex: number,\n ) {\n return this.board.countSymbolsOnReel(symbolOrProperties, reelIndex)\n }\n\n /**\n * Counts how many symbols matching the criteria are on the board.\n *\n * Passing a GameSymbol will compare by ID, passing a properties object will compare by properties.\n *\n * Returns a tuple where the first element is the total count, and the second element is a record of counts per reel index.\n */\n countSymbolsOnBoard(\n symbolOrProperties: GameSymbol | Record<string, any>,\n ): [number, Record<number, number>] {\n return this.board.countSymbolsOnBoard(symbolOrProperties)\n }\n\n /**\n * Checks if a symbol appears more than once on any reel in the current reel set.\n *\n * Useful to check for \"forbidden\" generations, e.g. 2 scatters on one reel.\n */\n isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol) {\n return this.board.isSymbolOnAnyReelMultipleTimes(symbol)\n }\n\n /**\n * Gets all reel stops (positions) where the specified symbol appears in the current reel set.\\\n * Returns an array of arrays, where each inner array contains the positions for the corresponding reel.\n */\n getReelStopsForSymbol(reels: Reels, symbol: GameSymbol) {\n return this.board.getReelStopsForSymbol(reels, symbol)\n }\n\n /**\n * Combines multiple arrays of reel stops into a single array of reel stops.\\\n */\n combineReelStops(...reelStops: number[][][]) {\n return this.board.combineReelStops({\n ctx: this.ctx,\n reelStops,\n })\n }\n\n /**\n * From a list of reel stops on reels, selects a random stop for a speficied number of random symbols.\n *\n * Mostly useful for placing scatter symbols on the board.\n */\n getRandomReelStops(reels: Reels, reelStops: number[][], amount: number) {\n return this.board.getRandomReelStops({\n ctx: this.ctx,\n reels,\n reelsAmount: this.reelsAmount,\n reelStops,\n amount,\n })\n }\n\n /**\n * Selects a random reel set based on the configured weights of the current result set.\\\n * Returns the reels as arrays of GameSymbols.\n */\n getRandomReelset() {\n return this.board.getRandomReelset(this.ctx)\n }\n\n /**\n * Draws a board using specified reel stops.\n */\n drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>) {\n this.drawBoardMixed(reels, forcedStops)\n }\n\n /**\n * Draws a board using random reel stops.\n */\n drawBoardWithRandomStops(reels: Reels) {\n this.drawBoardMixed(reels)\n }\n\n private drawBoardMixed(reels: Reels, forcedStops?: Record<string, number>) {\n this.board.drawBoardMixed({\n ctx: this.ctx,\n reels,\n forcedStops,\n reelsAmount: this.reelsAmount,\n symbolsPerReel: this.symbolsPerReel,\n padSymbols: this.padSymbols,\n })\n }\n\n /**\n * Tumbles the board. All given symbols will be deleted and new symbols will fall from the top.\n */\n tumbleBoard(symbolsToDelete: Array<{ reelIdx: number; rowIdx: number }>) {\n this.board.tumbleBoard({\n ctx: this.ctx,\n symbolsToDelete,\n reelsAmount: this.reelsAmount,\n symbolsPerReel: this.symbolsPerReel,\n padSymbols: this.padSymbols,\n })\n }\n}\n\ninterface StandaloneBoardOptions {\n ctx: GameContext\n reelsAmount: number\n symbolsPerReel: number[]\n padSymbols: number\n}\n"],"mappings":"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","names":["assert","import_fs","import_assert","import_assert","fs","assert","import_assert","assert","import_assert","assert","import_assert","assert","process","assert","path","fs","zlib","import_fs","import_path","import_assert","import_worker_threads","path","assert","fs","import_path","import_assert","import_worker_threads","import_path","path","import_path","path","import_assert","assert","assert","path","import_assert","assert","import_fs","import_path","import_worker_threads","import_fs","import_path","fs","path","path","fs","import_assert","assert"]}
|