@slot-engine/core 0.0.1
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/.turbo/turbo-build.log +33 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +7 -0
- package/README.md +8 -0
- package/dist/index.d.mts +1306 -0
- package/dist/index.d.ts +1306 -0
- package/dist/index.js +2929 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2874 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/zstd.exe +0 -0
- package/dist/optimizer-rust/Cargo.toml +19 -0
- package/dist/optimizer-rust/src/exes.rs +154 -0
- package/dist/optimizer-rust/src/main.rs +1659 -0
- package/index.ts +205 -0
- package/lib/zstd.exe +0 -0
- package/optimizer-rust/Cargo.toml +19 -0
- package/optimizer-rust/src/exes.rs +154 -0
- package/optimizer-rust/src/main.rs +1659 -0
- package/package.json +33 -0
- package/src/Board.ts +527 -0
- package/src/Book.ts +83 -0
- package/src/GameConfig.ts +148 -0
- package/src/GameMode.ts +86 -0
- package/src/GameState.ts +272 -0
- package/src/GameSymbol.ts +61 -0
- package/src/ReelGenerator.ts +589 -0
- package/src/ResultSet.ts +207 -0
- package/src/Simulation.ts +625 -0
- package/src/SlotGame.ts +117 -0
- package/src/Wallet.ts +203 -0
- package/src/WinType.ts +102 -0
- package/src/analysis/index.ts +198 -0
- package/src/analysis/utils.ts +128 -0
- package/src/optimizer/OptimizationConditions.ts +99 -0
- package/src/optimizer/OptimizationParameters.ts +46 -0
- package/src/optimizer/OptimizationScaling.ts +18 -0
- package/src/optimizer/index.ts +142 -0
- package/src/utils/math-config.ts +109 -0
- package/src/utils/setup-file.ts +36 -0
- package/src/utils/zstd.ts +28 -0
- package/src/winTypes/ClusterWinType.ts +3 -0
- package/src/winTypes/LinesWinType.ts +208 -0
- package/src/winTypes/ManywaysWinType.ts +3 -0
- package/tsconfig.json +19 -0
- package/utils.ts +270 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1306 @@
|
|
|
1
|
+
declare class GameSymbol {
|
|
2
|
+
id: string;
|
|
3
|
+
pays?: Record<number, number>;
|
|
4
|
+
properties: Map<string, any>;
|
|
5
|
+
constructor(opts: GameSymbolOpts);
|
|
6
|
+
/**
|
|
7
|
+
* Compares this symbol to another symbol or a set of properties.
|
|
8
|
+
*/
|
|
9
|
+
compare(symbolOrProperties?: GameSymbol | Record<string, any>): boolean;
|
|
10
|
+
}
|
|
11
|
+
interface GameSymbolOpts {
|
|
12
|
+
/**
|
|
13
|
+
* Unique identifier for the symbol, e.g. "W", "H1", "L5", etc.
|
|
14
|
+
*/
|
|
15
|
+
id: string;
|
|
16
|
+
/**
|
|
17
|
+
* Paytable for the symbol, where the key is the number of symbols and the value is the payout multiplier.
|
|
18
|
+
*/
|
|
19
|
+
pays?: Record<number, number>;
|
|
20
|
+
/**
|
|
21
|
+
* Additional properties for the symbol, e.g. `multiplier` or `isWild`.
|
|
22
|
+
*
|
|
23
|
+
* Properties can help identify special symbols.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* If your game has a "normal" scatter and a "super" scatter, you can define them like this:
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* properties: {
|
|
30
|
+
* isScatter: true,
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
properties?: Record<string, any>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
declare function weightedRandom<T extends Record<string, number>>(weights: T, rng: RandomNumberGenerator): string;
|
|
38
|
+
declare class RandomNumberGenerator {
|
|
39
|
+
mIdum: number;
|
|
40
|
+
mIy: number;
|
|
41
|
+
mIv: Array<number>;
|
|
42
|
+
NTAB: number;
|
|
43
|
+
IA: number;
|
|
44
|
+
IM: number;
|
|
45
|
+
IQ: number;
|
|
46
|
+
IR: number;
|
|
47
|
+
NDIV: number;
|
|
48
|
+
AM: number;
|
|
49
|
+
RNMX: number;
|
|
50
|
+
protected _currentSeed: number;
|
|
51
|
+
constructor();
|
|
52
|
+
getCurrentSeed(): number;
|
|
53
|
+
protected setCurrentSeed(seed: number): void;
|
|
54
|
+
setSeed(seed: number): void;
|
|
55
|
+
setSeedIfDifferent(seed: number): void;
|
|
56
|
+
generateRandomNumber(): number;
|
|
57
|
+
randomFloat(low: number, high: number): number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* This class is responsible for generating reel sets for slot games based on specified configurations.
|
|
62
|
+
*
|
|
63
|
+
* **While it offers a high degree of customization, some configurations may lead to unsolvable scenarios.**
|
|
64
|
+
*
|
|
65
|
+
* If the reel generator is unable to fulfill niche constraints,\
|
|
66
|
+
* you might need to adjust your configuration, or edit the generated reels manually.\
|
|
67
|
+
* Setting a different seed may also help.
|
|
68
|
+
*/
|
|
69
|
+
declare class ReelGenerator {
|
|
70
|
+
id: string;
|
|
71
|
+
associatedGameModeName: string;
|
|
72
|
+
protected readonly symbolWeights: Map<string, number>;
|
|
73
|
+
protected readonly rowsAmount: number;
|
|
74
|
+
reels: Reels;
|
|
75
|
+
protected limitSymbolsToReels?: Record<string, number[]>;
|
|
76
|
+
protected readonly spaceBetweenSameSymbols?: number | Record<string, number>;
|
|
77
|
+
protected readonly spaceBetweenSymbols?: Record<string, Record<string, number>>;
|
|
78
|
+
protected readonly preferStackedSymbols?: number;
|
|
79
|
+
protected readonly symbolStacks?: Record<string, {
|
|
80
|
+
chance: number | Record<string, number>;
|
|
81
|
+
min?: number | Record<string, number>;
|
|
82
|
+
max?: number | Record<string, number>;
|
|
83
|
+
}>;
|
|
84
|
+
protected readonly symbolQuotas?: Record<string, number | Record<string, number>>;
|
|
85
|
+
outputDir: string;
|
|
86
|
+
csvPath: string;
|
|
87
|
+
overrideExisting: boolean;
|
|
88
|
+
rng: RandomNumberGenerator;
|
|
89
|
+
constructor(opts: ReelGeneratorOpts);
|
|
90
|
+
private validateConfig;
|
|
91
|
+
private isSymbolAllowedOnReel;
|
|
92
|
+
private resolveStacking;
|
|
93
|
+
private tryPlaceStack;
|
|
94
|
+
/**
|
|
95
|
+
* Checks if a symbol can be placed at the target index without violating spacing rules.
|
|
96
|
+
*/
|
|
97
|
+
private violatesSpacing;
|
|
98
|
+
generateReels(gameConf: AnyGameConfig): void;
|
|
99
|
+
/**
|
|
100
|
+
* Reads a reelset CSV file and returns the reels as arrays of GameSymbols.
|
|
101
|
+
*/
|
|
102
|
+
parseReelsetCSV(reelSetPath: string, { config }: GameConfig): Reels;
|
|
103
|
+
}
|
|
104
|
+
interface ReelGeneratorOpts {
|
|
105
|
+
/**
|
|
106
|
+
* The unique identifier of the reel generator.\
|
|
107
|
+
* Must be unique per game mode.
|
|
108
|
+
*/
|
|
109
|
+
id: string;
|
|
110
|
+
/**
|
|
111
|
+
* The weights of the symbols in the reelset.\
|
|
112
|
+
* This is a mapping of symbol IDs to their respective weights.
|
|
113
|
+
*/
|
|
114
|
+
symbolWeights: Record<string, number>;
|
|
115
|
+
/**
|
|
116
|
+
* The number of rows in the reelset.\
|
|
117
|
+
* Default is 250, but can be adjusted as needed.
|
|
118
|
+
*/
|
|
119
|
+
rowsAmount?: number;
|
|
120
|
+
/**
|
|
121
|
+
* The directory where the generated reelset files will be saved.\
|
|
122
|
+
* **It's recommended to just use `__dirname`**!
|
|
123
|
+
*/
|
|
124
|
+
outputDir: string;
|
|
125
|
+
/**
|
|
126
|
+
* Prevent the same symbol from appearing directly above or below itself.\
|
|
127
|
+
* This can be a single number for all symbols, or a mapping of symbol IDs to
|
|
128
|
+
* their respective spacing values.
|
|
129
|
+
*
|
|
130
|
+
* Must be 1 or higher, if set.
|
|
131
|
+
*
|
|
132
|
+
* **This is overridden by `symbolStacks`**
|
|
133
|
+
*/
|
|
134
|
+
spaceBetweenSameSymbols?: number | Record<string, number>;
|
|
135
|
+
/**
|
|
136
|
+
* Prevents specific symbols from appearing within a certain distance of each other.
|
|
137
|
+
*
|
|
138
|
+
* Useful for preventing scatter and super scatter symbols from appearing too close to each other.
|
|
139
|
+
*
|
|
140
|
+
* **This is overridden by `symbolStacks`**
|
|
141
|
+
*/
|
|
142
|
+
spaceBetweenSymbols?: Record<string, Record<string, number>>;
|
|
143
|
+
/**
|
|
144
|
+
* A percentage value 0-100 that indicates the likelihood of a symbol being stacked.\
|
|
145
|
+
* A value of 0 means no stacked symbols, while 100 means all symbols are stacked.
|
|
146
|
+
*
|
|
147
|
+
* This is only a preference. Symbols may still not be stacked if\
|
|
148
|
+
* other restrictions (like `spaceBetweenSameSymbols`) prevent it.
|
|
149
|
+
*
|
|
150
|
+
* **This is overridden by `symbolStacks`**
|
|
151
|
+
*/
|
|
152
|
+
preferStackedSymbols?: number;
|
|
153
|
+
/**
|
|
154
|
+
* A mapping of symbols to their respective advanced stacking configuration.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* symbolStacks: {
|
|
159
|
+
* "W": {
|
|
160
|
+
* chance: { "1": 20, "2": 20, "3": 20, "4": 20 }, // 20% chance to be stacked on reels 2-5
|
|
161
|
+
* min: 2, // At least 2 wilds in a stack
|
|
162
|
+
* max: 4, // At most 4 wilds in a stack
|
|
163
|
+
* }
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
symbolStacks?: Record<string, {
|
|
168
|
+
chance: number | Record<string, number>;
|
|
169
|
+
min?: number | Record<string, number>;
|
|
170
|
+
max?: number | Record<string, number>;
|
|
171
|
+
}>;
|
|
172
|
+
/**
|
|
173
|
+
* Configures symbols to be limited to specific reels.\
|
|
174
|
+
* For example, you could configure Scatters to appear only on reels 1, 3 and 5.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* limitSymbolsToReels: {
|
|
179
|
+
* "S": [0, 2, 4], // Remember that reels are 0-indexed.
|
|
180
|
+
* }
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
limitSymbolsToReels?: Record<string, number[]>;
|
|
184
|
+
/**
|
|
185
|
+
* Defines optional quotas for symbols on the reels.\
|
|
186
|
+
* The quota (1-100%) defines how often a symbol should appear in the reelset, or in a specific reel.
|
|
187
|
+
*
|
|
188
|
+
* This is particularly useful for controlling the frequency of special symbols like scatters or wilds.
|
|
189
|
+
*
|
|
190
|
+
* Reels not provided for a symbol will use the weights from `symbolWeights`.
|
|
191
|
+
*
|
|
192
|
+
* _Any_ small quota will ensure that the symbol appears at least once on the reel.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* symbolQuotas: {
|
|
197
|
+
* "S": 3, // 3% of symbols on each reel will be scatters
|
|
198
|
+
* "W": { "1": 10, "2": 5, "3": 3, "4": 1 }, // Wilds will appear with different quotas on selected reels
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
symbolQuotas?: Record<string, number | Record<string, number>>;
|
|
203
|
+
/**
|
|
204
|
+
* If true, existing reels CSV files will be overwritten.
|
|
205
|
+
*/
|
|
206
|
+
overrideExisting?: boolean;
|
|
207
|
+
/**
|
|
208
|
+
* Optional seed for the RNG to ensure reproducible results.
|
|
209
|
+
*
|
|
210
|
+
* Default seed is `0`.
|
|
211
|
+
*
|
|
212
|
+
* Note: Seeds 0 and 1 produce the same results.
|
|
213
|
+
*/
|
|
214
|
+
seed?: number;
|
|
215
|
+
}
|
|
216
|
+
type Reels = GameSymbol[][];
|
|
217
|
+
|
|
218
|
+
declare class GameMode {
|
|
219
|
+
name: GameModeName;
|
|
220
|
+
reelsAmount: number;
|
|
221
|
+
symbolsPerReel: number[];
|
|
222
|
+
cost: number;
|
|
223
|
+
rtp: number;
|
|
224
|
+
reelSets: ReelGenerator[];
|
|
225
|
+
resultSets: ResultSet<any>[];
|
|
226
|
+
isBonusBuy: boolean;
|
|
227
|
+
constructor(opts: GameModeOpts);
|
|
228
|
+
}
|
|
229
|
+
interface GameModeOpts {
|
|
230
|
+
/**
|
|
231
|
+
* Name of the game mode.
|
|
232
|
+
*/
|
|
233
|
+
name: GameModeName;
|
|
234
|
+
/**
|
|
235
|
+
* Number of reels the board has.
|
|
236
|
+
*/
|
|
237
|
+
reelsAmount: number;
|
|
238
|
+
/**
|
|
239
|
+
* How many symbols each reel has. Array length must match `reelsAmount`.\
|
|
240
|
+
* The number at an array index represents the number of symbols on that reel.
|
|
241
|
+
*/
|
|
242
|
+
symbolsPerReel: number[];
|
|
243
|
+
/**
|
|
244
|
+
* Cost of the game mode, multiplied by the base bet.
|
|
245
|
+
*/
|
|
246
|
+
cost: number;
|
|
247
|
+
/**
|
|
248
|
+
* The target RTP of the game.
|
|
249
|
+
*/
|
|
250
|
+
rtp: number;
|
|
251
|
+
/**
|
|
252
|
+
* Defines and generates all reels for the game.\
|
|
253
|
+
* Which reels are used in a spin is determined by the ResultSet of the current game mode.
|
|
254
|
+
*
|
|
255
|
+
* It is common to have one reel set for the base game and another for free spins.\
|
|
256
|
+
* Each `ResultSet` can then set the weights of these reel sets to control which\
|
|
257
|
+
* reel set is used for a specific criteria.
|
|
258
|
+
*
|
|
259
|
+
* The generator can be adjusted to match the reels to your games needs.
|
|
260
|
+
*/
|
|
261
|
+
reelSets: ReelGenerator[];
|
|
262
|
+
/**
|
|
263
|
+
* A ResultSet defines how often a specific outcome should be generated.\
|
|
264
|
+
* For example, a ResultSet can be used to force a specific ratio of max wins\
|
|
265
|
+
* in the simulations to ensure there are different frontend representations.
|
|
266
|
+
*/
|
|
267
|
+
resultSets: ResultSet<any>[];
|
|
268
|
+
/**
|
|
269
|
+
* Whether this game mode is a bonus buy.
|
|
270
|
+
*/
|
|
271
|
+
isBonusBuy: boolean;
|
|
272
|
+
}
|
|
273
|
+
type GameModeName = "base" | "base-extra-chance-2x" | "base-extra-chance-10x" | "bonus" | string;
|
|
274
|
+
|
|
275
|
+
declare class Simulation {
|
|
276
|
+
protected readonly gameConfigOpts: CommonGameOptions;
|
|
277
|
+
readonly gameConfig: GameConfig;
|
|
278
|
+
readonly simRunsAmount: Partial<Record<GameModeName, number>>;
|
|
279
|
+
private readonly concurrency;
|
|
280
|
+
private wallet;
|
|
281
|
+
private library;
|
|
282
|
+
readonly records: RecordItem[];
|
|
283
|
+
protected debug: boolean;
|
|
284
|
+
constructor(opts: SimulationConfigOpts, gameConfigOpts: CommonGameOptions);
|
|
285
|
+
runSimulation(opts: SimulationOpts): Promise<void>;
|
|
286
|
+
/**
|
|
287
|
+
* Runs all simulations for a specific game mode.
|
|
288
|
+
*/
|
|
289
|
+
spawnWorkersForGameMode(opts: {
|
|
290
|
+
mode: string;
|
|
291
|
+
simNumsToCriteria: Record<number, string>;
|
|
292
|
+
}): Promise<void>;
|
|
293
|
+
callWorker(opts: {
|
|
294
|
+
basePath: string;
|
|
295
|
+
mode: string;
|
|
296
|
+
simStart: number;
|
|
297
|
+
simEnd: number;
|
|
298
|
+
index: number;
|
|
299
|
+
totalSims: number;
|
|
300
|
+
}): Promise<unknown>;
|
|
301
|
+
/**
|
|
302
|
+
* Creates a CSV file in the format "simulationId,weight,payout".
|
|
303
|
+
*
|
|
304
|
+
* `weight` defaults to 1.
|
|
305
|
+
*/
|
|
306
|
+
private static writeLookupTableCSV;
|
|
307
|
+
/**
|
|
308
|
+
* Creates a CSV file in the format "simulationId,criteria,payoutBase,payoutFreespins".
|
|
309
|
+
*/
|
|
310
|
+
private static writeLookupTableSegmentedCSV;
|
|
311
|
+
private static writeRecords;
|
|
312
|
+
private static writeIndexJson;
|
|
313
|
+
private static writeBooksJson;
|
|
314
|
+
private static logSymbolOccurrences;
|
|
315
|
+
/**
|
|
316
|
+
* Compiles user configured game to JS for use in different Node processes
|
|
317
|
+
*/
|
|
318
|
+
private preprocessFiles;
|
|
319
|
+
private getSimRangesForChunks;
|
|
320
|
+
private mergeRecords;
|
|
321
|
+
}
|
|
322
|
+
type SimulationConfigOpts = {
|
|
323
|
+
/**
|
|
324
|
+
* Object containing the game modes and their respective simulation runs amount.
|
|
325
|
+
*/
|
|
326
|
+
simRunsAmount: Partial<Record<GameModeName, number>>;
|
|
327
|
+
/**
|
|
328
|
+
* Number of concurrent processes to use for simulations.
|
|
329
|
+
*
|
|
330
|
+
* Default: 6
|
|
331
|
+
*/
|
|
332
|
+
concurrency?: number;
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* @internal
|
|
336
|
+
*/
|
|
337
|
+
type AnySimulationContext<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> = SimulationContext<TGameModes, TSymbols, TUserState>;
|
|
338
|
+
/**
|
|
339
|
+
* @internal
|
|
340
|
+
*/
|
|
341
|
+
declare class SimulationContext<TGameModes extends AnyGameModes, TSymbols extends AnySymbols, TUserState extends AnyUserData> extends Board<TGameModes, TSymbols, TUserState> {
|
|
342
|
+
constructor(opts: CommonGameOptions<any, any, TUserState>);
|
|
343
|
+
actualSims: number;
|
|
344
|
+
/**
|
|
345
|
+
* Will run a single simulation until the specified criteria is met.
|
|
346
|
+
*/
|
|
347
|
+
runSingleSimulation(opts: {
|
|
348
|
+
simId: number;
|
|
349
|
+
mode: string;
|
|
350
|
+
criteria: string;
|
|
351
|
+
index: number;
|
|
352
|
+
}): void;
|
|
353
|
+
/**
|
|
354
|
+
* If a simulation does not meet the required criteria, reset the state to run it again.
|
|
355
|
+
*
|
|
356
|
+
* This also runs once before each simulation to ensure a clean state.
|
|
357
|
+
*/
|
|
358
|
+
protected resetSimulation(): void;
|
|
359
|
+
/**
|
|
360
|
+
* Contains and executes the entire game logic:
|
|
361
|
+
* - Drawing the board
|
|
362
|
+
* - Evaluating wins
|
|
363
|
+
* - Updating wallet
|
|
364
|
+
* - Handling free spins
|
|
365
|
+
* - Recording events
|
|
366
|
+
*
|
|
367
|
+
* You can customize the game flow by implementing the `onHandleGameFlow` hook in the game configuration.
|
|
368
|
+
*/
|
|
369
|
+
protected handleGameFlow(): void;
|
|
370
|
+
}
|
|
371
|
+
interface SimulationOpts {
|
|
372
|
+
debug?: boolean;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Stores win amounts for simulations.
|
|
377
|
+
*/
|
|
378
|
+
declare class Wallet {
|
|
379
|
+
/**
|
|
380
|
+
* Total win amount (as the bet multiplier) from all simulations.
|
|
381
|
+
*/
|
|
382
|
+
protected cumulativeWins: number;
|
|
383
|
+
/**
|
|
384
|
+
* Total win amount (as the bet multiplier) per spin type.
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```ts
|
|
388
|
+
* {
|
|
389
|
+
* basegame: 50,
|
|
390
|
+
* freespins: 100,
|
|
391
|
+
* superfreespins: 200,
|
|
392
|
+
* }
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
protected cumulativeWinsPerSpinType: {
|
|
396
|
+
basegame: number;
|
|
397
|
+
freespins: number;
|
|
398
|
+
};
|
|
399
|
+
/**
|
|
400
|
+
* Current win amount (as the bet multiplier) for the ongoing simulation.
|
|
401
|
+
*/
|
|
402
|
+
protected currentWin: number;
|
|
403
|
+
/**
|
|
404
|
+
* Current win amount (as the bet multiplier) for the ongoing simulation per spin type.
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* {
|
|
409
|
+
* basegame: 50,
|
|
410
|
+
* freespins: 100,
|
|
411
|
+
* superfreespins: 200,
|
|
412
|
+
* }
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
protected currentWinPerSpinType: {
|
|
416
|
+
basegame: number;
|
|
417
|
+
freespins: number;
|
|
418
|
+
};
|
|
419
|
+
/**
|
|
420
|
+
* Holds the current win amount for a single (free) spin.\
|
|
421
|
+
* After each spin, this amount is added to `currentWinPerSpinType` and then reset to zero.
|
|
422
|
+
*/
|
|
423
|
+
protected currentSpinWin: number;
|
|
424
|
+
/**
|
|
425
|
+
* Current win amount (as the bet multiplier) for the ongoing tumble sequence.
|
|
426
|
+
*/
|
|
427
|
+
protected currentTumbleWin: number;
|
|
428
|
+
constructor();
|
|
429
|
+
/**
|
|
430
|
+
* Updates the win for the current spin.
|
|
431
|
+
*
|
|
432
|
+
* Should be called after each tumble event, if applicable.\
|
|
433
|
+
* Or generally call this to add wins during a spin.
|
|
434
|
+
*
|
|
435
|
+
* After each (free) spin, this amount should be added to `currentWinPerSpinType` via `confirmSpinWin()`
|
|
436
|
+
*/
|
|
437
|
+
addSpinWin(amount: number): void;
|
|
438
|
+
/**
|
|
439
|
+
* Assigns a win amount to the given spin type.
|
|
440
|
+
*
|
|
441
|
+
* Should be called after `addSpinWin()`, and after your tumble events are played out,\
|
|
442
|
+
* and after a (free) spin is played out to finalize the win.
|
|
443
|
+
*/
|
|
444
|
+
confirmSpinWin(spinType: SpinType): void;
|
|
445
|
+
/**
|
|
446
|
+
* Returns the accumulated win amount (as the bet multiplier) from all simulations.
|
|
447
|
+
*/
|
|
448
|
+
getCumulativeWins(): number;
|
|
449
|
+
/**
|
|
450
|
+
* Returns the accumulated win amount (as the bet multiplier) per spin type from all simulations.
|
|
451
|
+
*/
|
|
452
|
+
getCumulativeWinsPerSpinType(): {
|
|
453
|
+
basegame: number;
|
|
454
|
+
freespins: number;
|
|
455
|
+
};
|
|
456
|
+
/**
|
|
457
|
+
* Returns the current win amount (as the bet multiplier) for the ongoing simulation.
|
|
458
|
+
*/
|
|
459
|
+
getCurrentWin(): number;
|
|
460
|
+
/**
|
|
461
|
+
* Returns the current win amount (as the bet multiplier) per spin type for the ongoing simulation.
|
|
462
|
+
*/
|
|
463
|
+
getCurrentWinPerSpinType(): {
|
|
464
|
+
basegame: number;
|
|
465
|
+
freespins: number;
|
|
466
|
+
};
|
|
467
|
+
/**
|
|
468
|
+
* Adds a win to `currentSpinWin` and `currentTumbleWin`.
|
|
469
|
+
*
|
|
470
|
+
* After each (free) spin, this amount should be added to `currentWinPerSpinType` via `confirmSpinWin()`
|
|
471
|
+
*/
|
|
472
|
+
addTumbleWin(amount: number): void;
|
|
473
|
+
/**
|
|
474
|
+
* Resets the current win amounts to zero.
|
|
475
|
+
*/
|
|
476
|
+
resetCurrentWin(): void;
|
|
477
|
+
/**
|
|
478
|
+
* Adds current wins to cumulative wins and resets current wins to zero.
|
|
479
|
+
*/
|
|
480
|
+
confirmWins(ctx: AnySimulationContext): void;
|
|
481
|
+
serialize(): {
|
|
482
|
+
cumulativeWins: number;
|
|
483
|
+
cumulativeWinsPerSpinType: {
|
|
484
|
+
basegame: number;
|
|
485
|
+
freespins: number;
|
|
486
|
+
};
|
|
487
|
+
currentWin: number;
|
|
488
|
+
currentWinPerSpinType: {
|
|
489
|
+
basegame: number;
|
|
490
|
+
freespins: number;
|
|
491
|
+
};
|
|
492
|
+
currentSpinWin: number;
|
|
493
|
+
currentTumbleWin: number;
|
|
494
|
+
};
|
|
495
|
+
merge(wallet: Wallet): void;
|
|
496
|
+
mergeSerialized(data: ReturnType<Wallet["serialize"]>): void;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
declare class Book {
|
|
500
|
+
id: number;
|
|
501
|
+
criteria: string;
|
|
502
|
+
protected events: BookEvent[];
|
|
503
|
+
protected payout: number;
|
|
504
|
+
protected basegameWins: number;
|
|
505
|
+
protected freespinsWins: number;
|
|
506
|
+
constructor(opts: BookOpts);
|
|
507
|
+
/**
|
|
508
|
+
* Adds an event to the book.
|
|
509
|
+
*/
|
|
510
|
+
addEvent(event: Omit<BookEvent, "index">): void;
|
|
511
|
+
/**
|
|
512
|
+
* Transfers the win data from the wallet to the book.
|
|
513
|
+
*/
|
|
514
|
+
writePayout(ctx: AnySimulationContext): void;
|
|
515
|
+
getPayout(): number;
|
|
516
|
+
getBasegameWins(): number;
|
|
517
|
+
getFreespinsWins(): number;
|
|
518
|
+
serialize(): {
|
|
519
|
+
id: number;
|
|
520
|
+
criteria: string;
|
|
521
|
+
events: BookEvent[];
|
|
522
|
+
payout: number;
|
|
523
|
+
basegameWins: number;
|
|
524
|
+
freespinsWins: number;
|
|
525
|
+
};
|
|
526
|
+
static fromSerialized(data: ReturnType<Book["serialize"]>): Book;
|
|
527
|
+
}
|
|
528
|
+
interface BookEvent {
|
|
529
|
+
index: number;
|
|
530
|
+
type: string;
|
|
531
|
+
data: Record<string, any>;
|
|
532
|
+
}
|
|
533
|
+
interface BookOpts {
|
|
534
|
+
id: number;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* The GameState manages the current state of the game.
|
|
539
|
+
*/
|
|
540
|
+
declare class GameState<TGameModes extends AnyGameModes, TSymbols extends AnySymbols, TUserState extends AnyUserData> extends GameConfig<TGameModes, TSymbols, TUserState> {
|
|
541
|
+
state: {
|
|
542
|
+
currentSimulationId: number;
|
|
543
|
+
/**
|
|
544
|
+
* e.g. "base", "freespins", etc. (depending on the game config)
|
|
545
|
+
*/
|
|
546
|
+
currentGameMode: string;
|
|
547
|
+
/**
|
|
548
|
+
* Spin type constant as defined in `GameConfig.SPIN_TYPE`
|
|
549
|
+
*/
|
|
550
|
+
currentSpinType: SpinType;
|
|
551
|
+
/**
|
|
552
|
+
* The current ResultSet for the active simulation run.
|
|
553
|
+
*/
|
|
554
|
+
currentResultSet: ResultSet<any>;
|
|
555
|
+
/**
|
|
556
|
+
* Whether the criteria in the ResultSet for the current simulation has been met.
|
|
557
|
+
*/
|
|
558
|
+
isCriteriaMet: boolean;
|
|
559
|
+
/**
|
|
560
|
+
* Number of freespins remaining in the current freespin round.
|
|
561
|
+
*/
|
|
562
|
+
currentFreespinAmount: number;
|
|
563
|
+
/**
|
|
564
|
+
* Total amount of freespins awarded during the active simulation.
|
|
565
|
+
*/
|
|
566
|
+
totalFreespinAmount: number;
|
|
567
|
+
/**
|
|
568
|
+
* A library of all completed books, indexed by their ID.
|
|
569
|
+
*/
|
|
570
|
+
library: Map<string, Book>;
|
|
571
|
+
/**
|
|
572
|
+
* The current book being recorded.
|
|
573
|
+
*/
|
|
574
|
+
book: Book;
|
|
575
|
+
/**
|
|
576
|
+
* Seeded random number generator instance for the current simulation.
|
|
577
|
+
*/
|
|
578
|
+
rng: RandomNumberGenerator;
|
|
579
|
+
/**
|
|
580
|
+
* Custom user data that can be used in game flow logic.
|
|
581
|
+
*/
|
|
582
|
+
userData: TUserState;
|
|
583
|
+
/**
|
|
584
|
+
* Whether a max win has been triggered during the active simulation.
|
|
585
|
+
*/
|
|
586
|
+
triggeredMaxWin: boolean;
|
|
587
|
+
/**
|
|
588
|
+
* Whether freespins have been triggered during the active simulation.
|
|
589
|
+
*/
|
|
590
|
+
triggeredFreespins: boolean;
|
|
591
|
+
};
|
|
592
|
+
/**
|
|
593
|
+
* The wallet stores win data for the current and all simulations, respectively.
|
|
594
|
+
*/
|
|
595
|
+
wallet: Wallet;
|
|
596
|
+
/**
|
|
597
|
+
* Recorder for statistical analysis (e.g. symbol occurrences, etc.).
|
|
598
|
+
*/
|
|
599
|
+
private recorder;
|
|
600
|
+
constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>);
|
|
601
|
+
/**
|
|
602
|
+
* Gets the configuration for the current game mode.
|
|
603
|
+
*/
|
|
604
|
+
getCurrentGameMode(): GameMode;
|
|
605
|
+
resetState(): void;
|
|
606
|
+
/**
|
|
607
|
+
* Checks if a max win is reached by comparing `wallet.currentWin` to `config.maxWin`.
|
|
608
|
+
*
|
|
609
|
+
* Should be called after `wallet.confirmSpinWin()`.
|
|
610
|
+
*/
|
|
611
|
+
isMaxWinTriggered(): void;
|
|
612
|
+
/**
|
|
613
|
+
* Empties the list of pending records in the recorder.
|
|
614
|
+
*/
|
|
615
|
+
clearPendingRecords(): void;
|
|
616
|
+
/**
|
|
617
|
+
* Confirms all pending records and adds them to the main records list.
|
|
618
|
+
*/
|
|
619
|
+
confirmRecords(): void;
|
|
620
|
+
/**
|
|
621
|
+
* Record data for statistical analysis.
|
|
622
|
+
*/
|
|
623
|
+
record(data: Record<string, string | number | boolean>): void;
|
|
624
|
+
/**
|
|
625
|
+
* Records a symbol occurrence for statistical analysis.
|
|
626
|
+
*
|
|
627
|
+
* Calls `this.record()` with the provided data.
|
|
628
|
+
*/
|
|
629
|
+
recordSymbolOccurrence(data: {
|
|
630
|
+
kind: number;
|
|
631
|
+
symbolId: string;
|
|
632
|
+
spinType: SpinType;
|
|
633
|
+
[key: string]: any;
|
|
634
|
+
}): void;
|
|
635
|
+
/**
|
|
636
|
+
* Gets all confirmed records.
|
|
637
|
+
*/
|
|
638
|
+
getRecords(): RecordItem[];
|
|
639
|
+
/**
|
|
640
|
+
* Moves the current book to the library and resets the current book.
|
|
641
|
+
*/
|
|
642
|
+
moveBookToLibrary(): void;
|
|
643
|
+
/**
|
|
644
|
+
* Increases the freespin count by the specified amount.
|
|
645
|
+
*
|
|
646
|
+
* Also sets `state.triggeredFreespins` to true.
|
|
647
|
+
*/
|
|
648
|
+
awardFreespins(amount: number): void;
|
|
649
|
+
/**
|
|
650
|
+
* Ensures the requested number of scatters is valid based on the game configuration.\
|
|
651
|
+
* Returns a valid number of scatters.
|
|
652
|
+
*/
|
|
653
|
+
verifyScatterCount(numScatters: number): number;
|
|
654
|
+
}
|
|
655
|
+
interface RecordItem {
|
|
656
|
+
search: Array<{
|
|
657
|
+
name: string;
|
|
658
|
+
value: string;
|
|
659
|
+
}>;
|
|
660
|
+
timesTriggered: number;
|
|
661
|
+
bookIds: number[];
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* A version of the Board class that is disconnected from the actual game state\
|
|
666
|
+
* and operates on a copy of the game context.
|
|
667
|
+
*
|
|
668
|
+
* Can be used in custom game logic where you need to evaluate an additional board or reels independent of the main board,\
|
|
669
|
+
* similar to the top and bottom reels of the game "San Quentin".
|
|
670
|
+
*/
|
|
671
|
+
declare class StandaloneBoard {
|
|
672
|
+
protected reels: Reels;
|
|
673
|
+
protected paddingTop: Reels;
|
|
674
|
+
protected paddingBottom: Reels;
|
|
675
|
+
protected anticipation: number[];
|
|
676
|
+
protected ctx: AnySimulationContext;
|
|
677
|
+
constructor(opts: StandaloneBoardOpts);
|
|
678
|
+
/**
|
|
679
|
+
* Updates the context used by this board instance.
|
|
680
|
+
*/
|
|
681
|
+
context(ctx: AnySimulationContext): void;
|
|
682
|
+
/**
|
|
683
|
+
* Resets the board to an empty state.\
|
|
684
|
+
* This is called before drawing a new board.
|
|
685
|
+
*/
|
|
686
|
+
resetBoard(): void;
|
|
687
|
+
private makeEmptyReels;
|
|
688
|
+
private resetReels;
|
|
689
|
+
/**
|
|
690
|
+
* Counts how many symbols matching the criteria are on a specific reel.
|
|
691
|
+
*/
|
|
692
|
+
countSymbolsOnReel(symbolOrProperties: GameSymbol | Record<string, any>, reelIndex: number): number;
|
|
693
|
+
/**
|
|
694
|
+
* Counts how many symbols matching the criteria are on the board.
|
|
695
|
+
*
|
|
696
|
+
* Passing a GameSymbol will compare by ID, passing a properties object will compare by properties.
|
|
697
|
+
*
|
|
698
|
+
* Returns a tuple where the first element is the total count, and the second element is a record of counts per reel index.
|
|
699
|
+
*/
|
|
700
|
+
countSymbolsOnBoard(symbolOrProperties: GameSymbol | Record<string, any>): [number, Record<number, number>];
|
|
701
|
+
/**
|
|
702
|
+
* Checks if a symbol appears more than once on any reel in the current reel set.
|
|
703
|
+
*
|
|
704
|
+
* Useful to check for "forbidden" generations, e.g. 2 scatters on one reel.
|
|
705
|
+
*/
|
|
706
|
+
isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol): boolean;
|
|
707
|
+
/**
|
|
708
|
+
* Draws a board using specified reel stops.
|
|
709
|
+
*/
|
|
710
|
+
drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>): void;
|
|
711
|
+
/**
|
|
712
|
+
* Draws a board using random reel stops.
|
|
713
|
+
*/
|
|
714
|
+
drawBoardWithRandomStops(reels: Reels): void;
|
|
715
|
+
private drawBoardMixed;
|
|
716
|
+
}
|
|
717
|
+
interface StandaloneBoardOpts {
|
|
718
|
+
ctx: AnySimulationContext;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Extends GameState. Provides board-related functionality.
|
|
722
|
+
*/
|
|
723
|
+
declare class Board<TGameModes extends AnyGameModes, TSymbols extends AnySymbols, TUserState extends AnyUserData> extends GameState<TGameModes, TSymbols, TUserState> {
|
|
724
|
+
board: {
|
|
725
|
+
reels: Reels;
|
|
726
|
+
paddingTop: Reels;
|
|
727
|
+
paddingBottom: Reels;
|
|
728
|
+
anticipation: number[];
|
|
729
|
+
};
|
|
730
|
+
constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>);
|
|
731
|
+
/**
|
|
732
|
+
* Resets the board to an empty state.\
|
|
733
|
+
* This is called before drawing a new board.
|
|
734
|
+
*/
|
|
735
|
+
resetBoard(): void;
|
|
736
|
+
private makeEmptyReels;
|
|
737
|
+
private resetReels;
|
|
738
|
+
/**
|
|
739
|
+
* Counts how many symbols matching the criteria are on a specific reel.
|
|
740
|
+
*/
|
|
741
|
+
countSymbolsOnReel(symbolOrProperties: GameSymbol | Record<string, any>, reelIndex: number): number;
|
|
742
|
+
/**
|
|
743
|
+
* Counts how many symbols matching the criteria are on the board.
|
|
744
|
+
*
|
|
745
|
+
* Passing a GameSymbol will compare by ID, passing a properties object will compare by properties.
|
|
746
|
+
*
|
|
747
|
+
* Returns a tuple where the first element is the total count, and the second element is a record of counts per reel index.
|
|
748
|
+
*/
|
|
749
|
+
countSymbolsOnBoard(symbolOrProperties: GameSymbol | Record<string, any>): [number, Record<number, number>];
|
|
750
|
+
/**
|
|
751
|
+
* Checks if a symbol appears more than once on any reel in the current reel set.
|
|
752
|
+
*
|
|
753
|
+
* Useful to check for "forbidden" generations, e.g. 2 scatters on one reel.
|
|
754
|
+
*/
|
|
755
|
+
isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol): boolean;
|
|
756
|
+
/**
|
|
757
|
+
* Gets all reel stops (positions) where the specified symbol appears in the current reel set.\
|
|
758
|
+
* Returns an array of arrays, where each inner array contains the positions for the corresponding reel.
|
|
759
|
+
*/
|
|
760
|
+
getReelStopsForSymbol(reels: Reels, symbol: GameSymbol): number[][];
|
|
761
|
+
/**
|
|
762
|
+
* Combines multiple arrays of reel stops into a single array of reel stops.\
|
|
763
|
+
*/
|
|
764
|
+
combineReelStops(...reelStops: number[][][]): number[][];
|
|
765
|
+
/**
|
|
766
|
+
* From a list of reel stops on reels, selects a random stop for a speficied number of random symbols.
|
|
767
|
+
*
|
|
768
|
+
* Mostly useful for placing scatter symbols on the board.
|
|
769
|
+
*/
|
|
770
|
+
getRandomReelStops(reels: Reels, reelStops: number[][], amount: number): Record<string, number>;
|
|
771
|
+
/**
|
|
772
|
+
* Selects a random reelset based on the configured weights for the current game mode.\
|
|
773
|
+
* Returns the reels as arrays of GameSymbols.
|
|
774
|
+
*/
|
|
775
|
+
getRandomReelset(): Reels;
|
|
776
|
+
/**
|
|
777
|
+
* Draws a board using specified reel stops.
|
|
778
|
+
*/
|
|
779
|
+
drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>): void;
|
|
780
|
+
/**
|
|
781
|
+
* Draws a board using random reel stops.
|
|
782
|
+
*/
|
|
783
|
+
drawBoardWithRandomStops(reels: Reels): void;
|
|
784
|
+
private drawBoardMixed;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
declare class ResultSet<TUserState extends AnyUserData> {
|
|
788
|
+
criteria: string;
|
|
789
|
+
quota: number;
|
|
790
|
+
multiplier?: number;
|
|
791
|
+
reelWeights: ReelWeights<TUserState>;
|
|
792
|
+
userData?: Record<string, any>;
|
|
793
|
+
forceMaxWin?: boolean;
|
|
794
|
+
forceFreespins?: boolean;
|
|
795
|
+
evaluate?: (ctx: AnySimulationContext<any, any, TUserState>) => boolean;
|
|
796
|
+
constructor(opts: ResultSetOpts<TUserState>);
|
|
797
|
+
static assignCriteriaToSimulations(ctx: Simulation, gameModeName: string): Record<number, string>;
|
|
798
|
+
/**
|
|
799
|
+
* Checks if core criteria is met, e.g. target multiplier or max win.
|
|
800
|
+
*/
|
|
801
|
+
meetsCriteria(ctx: AnySimulationContext<any, any, TUserState>): boolean;
|
|
802
|
+
}
|
|
803
|
+
interface ResultSetOpts<TUserState extends AnyUserData> {
|
|
804
|
+
/**
|
|
805
|
+
* A short string to describe the criteria for this ResultSet.
|
|
806
|
+
*/
|
|
807
|
+
criteria: string;
|
|
808
|
+
/**
|
|
809
|
+
* The quota of spins, out of the total simulations, that must be forced to meet the specified criteria.\
|
|
810
|
+
* **Float from 0 to 1. Total quota of all ResultSets in a GameMode must be 1.**
|
|
811
|
+
*/
|
|
812
|
+
quota: number;
|
|
813
|
+
/**
|
|
814
|
+
* The required multiplier for a simulated spin to be accepted.
|
|
815
|
+
*/
|
|
816
|
+
multiplier?: number;
|
|
817
|
+
/**
|
|
818
|
+
* Configure the weights of the reels in this ResultSet.
|
|
819
|
+
*
|
|
820
|
+
* If you need to support dynamic / special reel weights based on the simulation context,\
|
|
821
|
+
* you can provide an `evaluate` function that returns the desired weights.
|
|
822
|
+
*
|
|
823
|
+
* If the `evaluate` function returns a falsy value, the usual spin type based weights will be used.
|
|
824
|
+
*
|
|
825
|
+
* @example
|
|
826
|
+
* ```ts
|
|
827
|
+
* new ResultSet({
|
|
828
|
+
* criteria: "superFreespins",
|
|
829
|
+
* quota: 0.05,
|
|
830
|
+
* forceFreespins: true,
|
|
831
|
+
* reelWeights: {
|
|
832
|
+
* [GameConfig.SPIN_TYPE.BASE_GAME]: { base1: 1 },
|
|
833
|
+
* [GameConfig.SPIN_TYPE.FREE_SPINS]: { bonus1: 1, bonus2: 2 },
|
|
834
|
+
* evaluate: (ctx) => {
|
|
835
|
+
* if (ctx.state.userData.triggeredSuperFreespins) {
|
|
836
|
+
* return { superbonus: 1 }
|
|
837
|
+
* }
|
|
838
|
+
* }
|
|
839
|
+
* },
|
|
840
|
+
* userData: { forceSuperFreespins: true },
|
|
841
|
+
* }),
|
|
842
|
+
* ```
|
|
843
|
+
*/
|
|
844
|
+
reelWeights: ReelWeights<TUserState>;
|
|
845
|
+
/**
|
|
846
|
+
* Optional data to use when evaluating the criteria.\
|
|
847
|
+
* This can be used to pass additional context or parameters needed for the evaluation.
|
|
848
|
+
*/
|
|
849
|
+
userData?: Record<string, any>;
|
|
850
|
+
/**
|
|
851
|
+
* If set, this will force the game to always trigger a max win.
|
|
852
|
+
*/
|
|
853
|
+
forceMaxWin?: boolean;
|
|
854
|
+
/**
|
|
855
|
+
* If set, this will force the game to always trigger free spins.
|
|
856
|
+
*/
|
|
857
|
+
forceFreespins?: boolean;
|
|
858
|
+
/**
|
|
859
|
+
* Custom function to evaluate if the criteria is met.
|
|
860
|
+
*
|
|
861
|
+
* E.g. use this to check for free spins that upgraded to super free spins\
|
|
862
|
+
* or other arbitrary simulation criteria.
|
|
863
|
+
*/
|
|
864
|
+
evaluate?: (ctx: EvaluationContext<TUserState>) => boolean;
|
|
865
|
+
}
|
|
866
|
+
interface ReelWeights<TUserState extends AnyUserData> {
|
|
867
|
+
[GameConfig.SPIN_TYPE.BASE_GAME]: Record<string, number>;
|
|
868
|
+
[GameConfig.SPIN_TYPE.FREE_SPINS]: Record<string, number>;
|
|
869
|
+
evaluate?: (ctx: EvaluationContext<TUserState>) => Record<string, number> | undefined | null | false;
|
|
870
|
+
}
|
|
871
|
+
type EvaluationContext<TUserState extends AnyUserData> = Board<any, any, TUserState>;
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Static configuration for a slot game.\
|
|
875
|
+
* This shouldn't change during gameplay.
|
|
876
|
+
*/
|
|
877
|
+
declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> {
|
|
878
|
+
readonly config: {
|
|
879
|
+
readonly id: string;
|
|
880
|
+
readonly name: string;
|
|
881
|
+
readonly providerNumber: number;
|
|
882
|
+
readonly gameModes: Record<GameModeName, GameMode>;
|
|
883
|
+
readonly symbols: Map<TSymbols[number]["id"], TSymbols[number]>;
|
|
884
|
+
readonly padSymbols?: number;
|
|
885
|
+
readonly scatterToFreespins: Record<string, Record<number, number>>;
|
|
886
|
+
readonly anticipationTriggers: Record<SpinType, number>;
|
|
887
|
+
readonly maxWinX: number;
|
|
888
|
+
readonly outputDir: string;
|
|
889
|
+
readonly hooks: GameHooks<TGameModes, TSymbols, TUserState>;
|
|
890
|
+
readonly userState?: TUserState;
|
|
891
|
+
};
|
|
892
|
+
constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>);
|
|
893
|
+
/**
|
|
894
|
+
* Generates reelset CSV files for all game modes.
|
|
895
|
+
*/
|
|
896
|
+
generateReelsetFiles(): void;
|
|
897
|
+
/**
|
|
898
|
+
* Retrieves a reel set by its ID within a specific game mode.
|
|
899
|
+
*/
|
|
900
|
+
getReelsetById(gameMode: string, id: string): ReelGenerator;
|
|
901
|
+
/**
|
|
902
|
+
* Retrieves the number of free spins awarded for a given spin type and scatter count.
|
|
903
|
+
*/
|
|
904
|
+
getFreeSpinsForScatters(spinType: SpinType, scatterCount: number): number;
|
|
905
|
+
/**
|
|
906
|
+
* Retrieves a result set by its criteria within a specific game mode.
|
|
907
|
+
*/
|
|
908
|
+
getGameModeCriteria(mode: string, criteria: string): ResultSet<any>;
|
|
909
|
+
/**
|
|
910
|
+
* Returns all configured symbols as an array.
|
|
911
|
+
*/
|
|
912
|
+
getSymbolArray(): TSymbols[number][];
|
|
913
|
+
static SPIN_TYPE: {
|
|
914
|
+
readonly BASE_GAME: "basegame";
|
|
915
|
+
readonly FREE_SPINS: "freespins";
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
type SpinType = (typeof GameConfig.SPIN_TYPE)[keyof typeof GameConfig.SPIN_TYPE];
|
|
919
|
+
type AnyGameConfig = GameConfig<any, any, any>;
|
|
920
|
+
|
|
921
|
+
declare class WinType {
|
|
922
|
+
protected payout: number;
|
|
923
|
+
protected winCombinations: WinCombination[];
|
|
924
|
+
protected ctx: AnySimulationContext;
|
|
925
|
+
protected readonly wildSymbol?: WildSymbol;
|
|
926
|
+
constructor(opts?: WinTypeOpts);
|
|
927
|
+
/**
|
|
928
|
+
* Sets the simulation context for this WinType instance.
|
|
929
|
+
*
|
|
930
|
+
* This gives the WinType access to the current board.
|
|
931
|
+
*/
|
|
932
|
+
context(ctx: AnySimulationContext): WinType;
|
|
933
|
+
protected ensureContext(): void;
|
|
934
|
+
/**
|
|
935
|
+
* Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
|
|
936
|
+
*/
|
|
937
|
+
evaluateWins(): this;
|
|
938
|
+
/**
|
|
939
|
+
* Custom post-processing of wins, e.g. for handling multipliers.
|
|
940
|
+
*/
|
|
941
|
+
postProcess(func: PostProcessFn<typeof this.winCombinations>): this;
|
|
942
|
+
/**
|
|
943
|
+
* Returns the total payout and detailed win combinations.
|
|
944
|
+
*/
|
|
945
|
+
getWins(): {
|
|
946
|
+
payout: number;
|
|
947
|
+
winCombinations: WinCombination[];
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
interface WinTypeOpts {
|
|
951
|
+
/**
|
|
952
|
+
* Configuration used to identify wild symbols on the board.\
|
|
953
|
+
* You can either provide a specific `GameSymbol` instance or a set of properties to match against symbols on the board.
|
|
954
|
+
*
|
|
955
|
+
* @example
|
|
956
|
+
* If you have different wild symbols, each with a property `isWild: true`, you can define:
|
|
957
|
+
* ```ts
|
|
958
|
+
* wildSymbol: { isWild: true }
|
|
959
|
+
* ```
|
|
960
|
+
*
|
|
961
|
+
* @example
|
|
962
|
+
* If you have a single wild symbol instance, you can define:
|
|
963
|
+
* ```ts
|
|
964
|
+
* wildSymbol: myWildSymbol
|
|
965
|
+
* ```
|
|
966
|
+
*/
|
|
967
|
+
wildSymbol?: WildSymbol;
|
|
968
|
+
}
|
|
969
|
+
type WinCombination = {
|
|
970
|
+
payout: number;
|
|
971
|
+
kind: number;
|
|
972
|
+
symbols: Array<{
|
|
973
|
+
symbol: GameSymbol;
|
|
974
|
+
isWild: boolean;
|
|
975
|
+
substitutedFor?: GameSymbol;
|
|
976
|
+
reelIndex: number;
|
|
977
|
+
posIndex: number;
|
|
978
|
+
}>;
|
|
979
|
+
};
|
|
980
|
+
type PostProcessFn<TWinCombs extends WinCombination[]> = (winType: WinType, ctx: AnySimulationContext) => {
|
|
981
|
+
payout: number;
|
|
982
|
+
winCombinations: TWinCombs;
|
|
983
|
+
};
|
|
984
|
+
type WildSymbol = GameSymbol | Record<string, any>;
|
|
985
|
+
|
|
986
|
+
declare class LinesWinType extends WinType {
|
|
987
|
+
protected lines: Record<number, number[]>;
|
|
988
|
+
protected winCombinations: LineWinCombination[];
|
|
989
|
+
context: (ctx: AnySimulationContext) => LinesWinType;
|
|
990
|
+
getWins: () => {
|
|
991
|
+
payout: number;
|
|
992
|
+
winCombinations: LineWinCombination[];
|
|
993
|
+
};
|
|
994
|
+
constructor(opts: LinesWinTypeOpts);
|
|
995
|
+
private validateConfig;
|
|
996
|
+
private isWild;
|
|
997
|
+
evaluateWins(): this;
|
|
998
|
+
}
|
|
999
|
+
interface LinesWinTypeOpts extends WinTypeOpts {
|
|
1000
|
+
/**
|
|
1001
|
+
* Defines the paylines for the slot game.
|
|
1002
|
+
*
|
|
1003
|
+
* @example
|
|
1004
|
+
* ```ts
|
|
1005
|
+
* lines: {
|
|
1006
|
+
* 1: [0, 0, 0, 0, 0],
|
|
1007
|
+
* 2: [1, 1, 1, 1, 1],
|
|
1008
|
+
* 3: [2, 2, 2, 2, 2],
|
|
1009
|
+
* }
|
|
1010
|
+
* ```
|
|
1011
|
+
*/
|
|
1012
|
+
lines: Record<number, number[]>;
|
|
1013
|
+
}
|
|
1014
|
+
interface LineWinCombination extends WinCombination {
|
|
1015
|
+
lineNumber: number;
|
|
1016
|
+
symbol: GameSymbol;
|
|
1017
|
+
winType: "pure-wild" | "substituted";
|
|
1018
|
+
substitutedBaseSymbol: GameSymbol | null;
|
|
1019
|
+
stats: {
|
|
1020
|
+
wildCount: number;
|
|
1021
|
+
nonWildCount: number;
|
|
1022
|
+
leadingWilds: number;
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
declare class ClusterWinType extends WinType {
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
declare class ManywaysWinType extends WinType {
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
declare class OptimizationConditions {
|
|
1033
|
+
protected rtp?: number | "x";
|
|
1034
|
+
protected avgWin?: number;
|
|
1035
|
+
protected hitRate?: number | "x";
|
|
1036
|
+
protected searchRange: number[];
|
|
1037
|
+
protected forceSearch: Record<string, string>;
|
|
1038
|
+
priority: number;
|
|
1039
|
+
constructor(opts: OptimizationConditionsOpts);
|
|
1040
|
+
getRtp(): number | "x" | undefined;
|
|
1041
|
+
getAvgWin(): number | undefined;
|
|
1042
|
+
getHitRate(): number | "x" | undefined;
|
|
1043
|
+
getSearchRange(): number[];
|
|
1044
|
+
getForceSearch(): Record<string, string>;
|
|
1045
|
+
}
|
|
1046
|
+
interface OptimizationConditionsOpts {
|
|
1047
|
+
/**
|
|
1048
|
+
* The desired RTP (0-1)
|
|
1049
|
+
*/
|
|
1050
|
+
rtp?: number | "x";
|
|
1051
|
+
/**
|
|
1052
|
+
* The desired average win (per spin).
|
|
1053
|
+
*/
|
|
1054
|
+
avgWin?: number;
|
|
1055
|
+
/**
|
|
1056
|
+
* The desired hit rate (e.g. `200` to hit 1 in 200 spins).
|
|
1057
|
+
*/
|
|
1058
|
+
hitRate?: number | "x";
|
|
1059
|
+
/**
|
|
1060
|
+
* A way of filtering results by
|
|
1061
|
+
*
|
|
1062
|
+
* - A number (payout multiplier), e.g. `5000`
|
|
1063
|
+
* - Force record value, e.g. `{ "symbolId": "scatter" }`
|
|
1064
|
+
* - A range of numbers, e.g. `[0, 100]` (payout multiplier range)
|
|
1065
|
+
*/
|
|
1066
|
+
searchConditions?: number | Record<string, string> | [number, number];
|
|
1067
|
+
/**
|
|
1068
|
+
* **Priority matters!**\
|
|
1069
|
+
* Higher priority conditions will be evaluated first.\
|
|
1070
|
+
* After a book matching this condition is found, the book will be removed from the pool\
|
|
1071
|
+
* and can't be used to satisfy other conditions with lower priority.
|
|
1072
|
+
*
|
|
1073
|
+
* TODO add better explanation
|
|
1074
|
+
*/
|
|
1075
|
+
priority: number;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
declare class OptimizationScaling {
|
|
1079
|
+
protected config: OptimizationScalingOpts;
|
|
1080
|
+
constructor(opts: OptimizationScalingOpts);
|
|
1081
|
+
getConfig(): OptimizationScalingOpts;
|
|
1082
|
+
}
|
|
1083
|
+
type OptimizationScalingOpts = Array<{
|
|
1084
|
+
criteria: string;
|
|
1085
|
+
scaleFactor: number;
|
|
1086
|
+
winRange: [number, number];
|
|
1087
|
+
probability: number;
|
|
1088
|
+
}>;
|
|
1089
|
+
|
|
1090
|
+
declare class OptimizationParameters {
|
|
1091
|
+
protected parameters: OptimizationParametersOpts;
|
|
1092
|
+
constructor(opts?: OptimizationParametersOpts);
|
|
1093
|
+
static DEFAULT_PARAMETERS: OptimizationParametersOpts;
|
|
1094
|
+
getParameters(): OptimizationParametersOpts;
|
|
1095
|
+
}
|
|
1096
|
+
interface OptimizationParametersOpts {
|
|
1097
|
+
readonly numShowPigs: number;
|
|
1098
|
+
readonly numPigsPerFence: number;
|
|
1099
|
+
readonly threadsFenceConstruction: number;
|
|
1100
|
+
readonly threadsShowConstruction: number;
|
|
1101
|
+
readonly testSpins: number[];
|
|
1102
|
+
readonly testSpinsWeights: number[];
|
|
1103
|
+
readonly simulationTrials: number;
|
|
1104
|
+
readonly graphIndexes: number[];
|
|
1105
|
+
readonly run1000Batch: false;
|
|
1106
|
+
readonly minMeanToMedian: number;
|
|
1107
|
+
readonly maxMeanToMedian: number;
|
|
1108
|
+
readonly pmbRtp: number;
|
|
1109
|
+
readonly scoreType: "rtp";
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
interface OptimizationOpts {
|
|
1113
|
+
gameModes: string[];
|
|
1114
|
+
}
|
|
1115
|
+
interface OptimizerOpts {
|
|
1116
|
+
game: SlotGame<any, any, any>;
|
|
1117
|
+
gameModes: OptimzierGameModeConfig;
|
|
1118
|
+
}
|
|
1119
|
+
type OptimzierGameModeConfig = Record<GameModeName, {
|
|
1120
|
+
conditions: Record<string, OptimizationConditions>;
|
|
1121
|
+
scaling: OptimizationScaling;
|
|
1122
|
+
parameters: OptimizationParameters;
|
|
1123
|
+
}>;
|
|
1124
|
+
|
|
1125
|
+
interface AnalysisOpts {
|
|
1126
|
+
gameModes: string[];
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* SlotGame class that encapsulates the game configuration and state.\
|
|
1131
|
+
* Main entry point for the slot game.
|
|
1132
|
+
*/
|
|
1133
|
+
declare class SlotGame<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> {
|
|
1134
|
+
private readonly gameConfigOpts;
|
|
1135
|
+
private simulation?;
|
|
1136
|
+
private optimizer?;
|
|
1137
|
+
private analyzer?;
|
|
1138
|
+
constructor(config: CommonGameOptions<TGameModes, TSymbols, TUserState>);
|
|
1139
|
+
/**
|
|
1140
|
+
* Sets up the simulation configuration.\
|
|
1141
|
+
* Must be called before `runTasks()`.
|
|
1142
|
+
*/
|
|
1143
|
+
configureSimulation(opts: SimulationConfigOpts): void;
|
|
1144
|
+
/**
|
|
1145
|
+
* Sets up the optimization configuration.\
|
|
1146
|
+
* Must be called before `runTasks()`.
|
|
1147
|
+
*/
|
|
1148
|
+
configureOptimization(opts: Pick<OptimizerOpts, "gameModes">): void;
|
|
1149
|
+
/**
|
|
1150
|
+
* Runs the simulation based on the configured settings.
|
|
1151
|
+
*/
|
|
1152
|
+
private runSimulation;
|
|
1153
|
+
/**
|
|
1154
|
+
* Runs the optimization based on the configured settings.
|
|
1155
|
+
*/
|
|
1156
|
+
private runOptimization;
|
|
1157
|
+
/**
|
|
1158
|
+
* Runs the analysis based on the configured settings.
|
|
1159
|
+
*/
|
|
1160
|
+
private runAnalysis;
|
|
1161
|
+
runTasks(opts?: {
|
|
1162
|
+
debug?: boolean;
|
|
1163
|
+
doSimulation?: boolean;
|
|
1164
|
+
doOptimization?: boolean;
|
|
1165
|
+
doAnalysis?: boolean;
|
|
1166
|
+
simulationOpts?: SimulationOpts;
|
|
1167
|
+
optimizationOpts?: OptimizationOpts;
|
|
1168
|
+
analysisOpts?: AnalysisOpts;
|
|
1169
|
+
}): Promise<void>;
|
|
1170
|
+
/**
|
|
1171
|
+
* Gets the game configuration.
|
|
1172
|
+
*/
|
|
1173
|
+
getConfig(): GameConfig<TGameModes, TSymbols, TUserState>;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* @internal
|
|
1178
|
+
*/
|
|
1179
|
+
interface CommonGameOptions<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> {
|
|
1180
|
+
/**
|
|
1181
|
+
* The unique identifier of the game, used for configuration and identification.
|
|
1182
|
+
*/
|
|
1183
|
+
id: string;
|
|
1184
|
+
/**
|
|
1185
|
+
* The name of the game, used for display purposes.
|
|
1186
|
+
*/
|
|
1187
|
+
name: string;
|
|
1188
|
+
/**
|
|
1189
|
+
* An arbitrary number to identify the provider of this game.
|
|
1190
|
+
*/
|
|
1191
|
+
providerNumber: number;
|
|
1192
|
+
/**
|
|
1193
|
+
* A GameMode is the core structure of a slot, defining the board,\
|
|
1194
|
+
* bet cost, win type, and other properties.
|
|
1195
|
+
*
|
|
1196
|
+
* One-off mechanisms can also be injected into the core game logic from here.
|
|
1197
|
+
*/
|
|
1198
|
+
gameModes: Record<GameModeName, GameMode>;
|
|
1199
|
+
/**
|
|
1200
|
+
* A list of all symbols that will appear on the reels.
|
|
1201
|
+
*/
|
|
1202
|
+
symbols: GameSymbol[];
|
|
1203
|
+
/**
|
|
1204
|
+
* A mapping from spin type to scatter counts to the number of free spins awarded.
|
|
1205
|
+
*
|
|
1206
|
+
* @example
|
|
1207
|
+
* ```ts
|
|
1208
|
+
* scatterToFreespins: {
|
|
1209
|
+
* [GameConfig.CONSTANTS.BASE_GAME]: {
|
|
1210
|
+
* 3: 10,
|
|
1211
|
+
* 4: 12,
|
|
1212
|
+
* 5: 15,
|
|
1213
|
+
* },
|
|
1214
|
+
* [GameConfig.CONSTANTS.FREE_SPINS]: {
|
|
1215
|
+
* 3: 6,
|
|
1216
|
+
* 4: 8,
|
|
1217
|
+
* 5: 10,
|
|
1218
|
+
* },
|
|
1219
|
+
* },
|
|
1220
|
+
* ```
|
|
1221
|
+
*/
|
|
1222
|
+
scatterToFreespins: Record<string, Record<number, number>>;
|
|
1223
|
+
/**
|
|
1224
|
+
* If set, this will pad the board with symbols on the top and bottom of the reels.\
|
|
1225
|
+
* Useful for teasing symbols right above or below the active board.
|
|
1226
|
+
*
|
|
1227
|
+
* Default: 1
|
|
1228
|
+
*/
|
|
1229
|
+
padSymbols?: number;
|
|
1230
|
+
/**
|
|
1231
|
+
* The maximum win multiplier of the game, e.g. 5000 for a 5000x max win.
|
|
1232
|
+
*/
|
|
1233
|
+
maxWinX: number;
|
|
1234
|
+
/**
|
|
1235
|
+
* Hooks are used to inject custom logic at specific points in the game flow.\
|
|
1236
|
+
* Some required hooks must be implemented for certain features to work.
|
|
1237
|
+
*/
|
|
1238
|
+
hooks: GameHooks<TGameModes, TSymbols, TUserState>;
|
|
1239
|
+
/**
|
|
1240
|
+
* Custom additional state that can be used in game flow logic.
|
|
1241
|
+
*/
|
|
1242
|
+
userState?: TUserState;
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* @internal
|
|
1246
|
+
*/
|
|
1247
|
+
type AnyUserData = Record<string, any>;
|
|
1248
|
+
/**
|
|
1249
|
+
* @internal
|
|
1250
|
+
*/
|
|
1251
|
+
type AnyGameModes = Record<string, GameMode>;
|
|
1252
|
+
/**
|
|
1253
|
+
* @internal
|
|
1254
|
+
*/
|
|
1255
|
+
type AnySymbols = GameSymbol[];
|
|
1256
|
+
/**
|
|
1257
|
+
* @internal
|
|
1258
|
+
*/
|
|
1259
|
+
interface GameHooks<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> {
|
|
1260
|
+
/**
|
|
1261
|
+
* This hook is called after the simulation state is prepared for a spin,\
|
|
1262
|
+
* and the core is ready to handle the game flow.
|
|
1263
|
+
*
|
|
1264
|
+
* **The developer is responsible for implementing the entire game flow here, including:**
|
|
1265
|
+
* - Drawing the board
|
|
1266
|
+
* - Evaluating wins
|
|
1267
|
+
* - Tumbling mechanics
|
|
1268
|
+
* - Updating wallet
|
|
1269
|
+
* - Handling free spins
|
|
1270
|
+
* - Recording events
|
|
1271
|
+
* - ... and everything in between.
|
|
1272
|
+
*
|
|
1273
|
+
* You can access the config and state from the context.
|
|
1274
|
+
*
|
|
1275
|
+
* The game flow is not built into the core, because it can vary greatly between different games.\
|
|
1276
|
+
* This hook provides the flexibility to implement any game flow you need.
|
|
1277
|
+
*/
|
|
1278
|
+
onHandleGameFlow: (ctx: SimulationContext<TGameModes, TSymbols, TUserState>) => void;
|
|
1279
|
+
/**
|
|
1280
|
+
* This hook is called whenever a simulation is accepted, i.e. when the criteria of the current ResultSet is met.
|
|
1281
|
+
*/
|
|
1282
|
+
onSimulationAccepted?: (ctx: SimulationContext<TGameModes, TSymbols, TUserState>) => void;
|
|
1283
|
+
}
|
|
1284
|
+
type InferUserState<T> = T extends SlotGame<infer U> ? U : never;
|
|
1285
|
+
type HookContext<T> = T extends SlotGame<infer G, infer S, infer U> ? SimulationContext<G, S, U> : never;
|
|
1286
|
+
|
|
1287
|
+
type InferGameType<TGameModes extends AnyGameModes, TSymbols extends AnySymbols, TUserState extends AnyUserData> = SlotGame<TGameModes, TSymbols, TUserState>;
|
|
1288
|
+
interface CreateSlotGameOpts<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> {
|
|
1289
|
+
id: CommonGameOptions["id"];
|
|
1290
|
+
name: CommonGameOptions["name"];
|
|
1291
|
+
providerNumber: CommonGameOptions["providerNumber"];
|
|
1292
|
+
gameModes: TGameModes;
|
|
1293
|
+
symbols: TSymbols;
|
|
1294
|
+
scatterToFreespins: CommonGameOptions["scatterToFreespins"];
|
|
1295
|
+
padSymbols?: CommonGameOptions["padSymbols"];
|
|
1296
|
+
maxWinX: CommonGameOptions["maxWinX"];
|
|
1297
|
+
userState?: TUserState;
|
|
1298
|
+
hooks: CommonGameOptions<TGameModes, TSymbols, TUserState>["hooks"];
|
|
1299
|
+
}
|
|
1300
|
+
declare function createSlotGame<TGame>(opts: TGame extends InferGameType<infer G, infer S, infer U> ? CreateSlotGameOpts<G, S, U> : never): TGame;
|
|
1301
|
+
declare const defineUserState: <TUserState extends AnyUserData>(data: TUserState) => TUserState;
|
|
1302
|
+
declare const defineSymbols: <TSymbol extends GameSymbol>(symbols: TSymbol[]) => TSymbol[];
|
|
1303
|
+
declare const defineGameModes: <TGameModes extends AnyGameModes>(gameModes: TGameModes) => TGameModes;
|
|
1304
|
+
declare const defineReelSets: <TSymbols extends AnySymbols>(reelSets: ReelGenerator[]) => ReelGenerator[];
|
|
1305
|
+
|
|
1306
|
+
export { type AnyGameModes, type AnySymbols, type AnyUserData, ClusterWinType, type CommonGameOptions, type CreateSlotGameOpts, type EvaluationContext, GameConfig, type GameHooks, GameMode, GameSymbol, type HookContext, type InferGameType, type InferUserState, LinesWinType, ManywaysWinType, OptimizationConditions, OptimizationParameters, OptimizationScaling, ReelGenerator, type Reels, ResultSet, StandaloneBoard, WinType, createSlotGame, defineGameModes, defineReelSets, defineSymbols, defineUserState, weightedRandom };
|