@slot-engine/core 0.0.1 → 0.0.3
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/README.md +1 -1
- package/dist/index.d.mts +7 -14
- package/dist/index.d.ts +7 -14
- package/dist/index.js +68 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +68 -90
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
- package/.turbo/turbo-build.log +0 -33
- package/.turbo/turbo-typecheck.log +0 -4
- package/CHANGELOG.md +0 -7
- package/dist/lib/zstd.exe +0 -0
- package/index.ts +0 -205
- package/lib/zstd.exe +0 -0
- package/optimizer-rust/Cargo.toml +0 -19
- package/optimizer-rust/src/exes.rs +0 -154
- package/optimizer-rust/src/main.rs +0 -1659
- package/src/Board.ts +0 -527
- package/src/Book.ts +0 -83
- package/src/GameConfig.ts +0 -148
- package/src/GameMode.ts +0 -86
- package/src/GameState.ts +0 -272
- package/src/GameSymbol.ts +0 -61
- package/src/ReelGenerator.ts +0 -589
- package/src/ResultSet.ts +0 -207
- package/src/Simulation.ts +0 -625
- package/src/SlotGame.ts +0 -117
- package/src/Wallet.ts +0 -203
- package/src/WinType.ts +0 -102
- package/src/analysis/index.ts +0 -198
- package/src/analysis/utils.ts +0 -128
- package/src/optimizer/OptimizationConditions.ts +0 -99
- package/src/optimizer/OptimizationParameters.ts +0 -46
- package/src/optimizer/OptimizationScaling.ts +0 -18
- package/src/optimizer/index.ts +0 -142
- package/src/utils/math-config.ts +0 -109
- package/src/utils/setup-file.ts +0 -36
- package/src/utils/zstd.ts +0 -28
- package/src/winTypes/ClusterWinType.ts +0 -3
- package/src/winTypes/LinesWinType.ts +0 -208
- package/src/winTypes/ManywaysWinType.ts +0 -3
- package/tsconfig.json +0 -19
- package/utils.ts +0 -270
package/src/Board.ts
DELETED
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
import { AnyGameModes, AnySymbols, AnyUserData, CommonGameOptions } from "../index"
|
|
2
|
-
import { randomItem, weightedRandom } from "../utils"
|
|
3
|
-
import { GameState } from "./GameState"
|
|
4
|
-
import { GameSymbol } from "./GameSymbol"
|
|
5
|
-
import { Reels } from "./ReelGenerator"
|
|
6
|
-
import { AnySimulationContext } from "./Simulation"
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A version of the Board class that is disconnected from the actual game state\
|
|
10
|
-
* and operates on a copy of the game context.
|
|
11
|
-
*
|
|
12
|
-
* Can be used in custom game logic where you need to evaluate an additional board or reels independent of the main board,\
|
|
13
|
-
* similar to the top and bottom reels of the game "San Quentin".
|
|
14
|
-
*/
|
|
15
|
-
export class StandaloneBoard {
|
|
16
|
-
protected reels: Reels
|
|
17
|
-
protected paddingTop: Reels
|
|
18
|
-
protected paddingBottom: Reels
|
|
19
|
-
protected anticipation: number[]
|
|
20
|
-
protected ctx: AnySimulationContext
|
|
21
|
-
|
|
22
|
-
constructor(opts: StandaloneBoardOpts) {
|
|
23
|
-
this.reels = []
|
|
24
|
-
this.paddingTop = []
|
|
25
|
-
this.paddingBottom = []
|
|
26
|
-
this.anticipation = []
|
|
27
|
-
this.ctx = opts.ctx
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Updates the context used by this board instance.
|
|
32
|
-
*/
|
|
33
|
-
context(ctx: AnySimulationContext) {
|
|
34
|
-
this.ctx = ctx
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Resets the board to an empty state.\
|
|
39
|
-
* This is called before drawing a new board.
|
|
40
|
-
*/
|
|
41
|
-
resetBoard() {
|
|
42
|
-
this.resetReels()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private makeEmptyReels() {
|
|
46
|
-
return Array.from({ length: this.ctx.getCurrentGameMode().reelsAmount }, () => [])
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private resetReels() {
|
|
50
|
-
this.reels = this.makeEmptyReels()
|
|
51
|
-
this.anticipation = Array.from(
|
|
52
|
-
{ length: this.ctx.getCurrentGameMode().reelsAmount },
|
|
53
|
-
() => 0,
|
|
54
|
-
)
|
|
55
|
-
if (this.ctx.config.padSymbols && this.ctx.config.padSymbols > 0) {
|
|
56
|
-
this.paddingTop = this.makeEmptyReels()
|
|
57
|
-
this.paddingBottom = this.makeEmptyReels()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Counts how many symbols matching the criteria are on a specific reel.
|
|
63
|
-
*/
|
|
64
|
-
countSymbolsOnReel(
|
|
65
|
-
symbolOrProperties: GameSymbol | Record<string, any>,
|
|
66
|
-
reelIndex: number,
|
|
67
|
-
) {
|
|
68
|
-
let total = 0
|
|
69
|
-
|
|
70
|
-
for (const symbol of this.reels[reelIndex]!) {
|
|
71
|
-
let matches = true
|
|
72
|
-
if (symbolOrProperties instanceof GameSymbol) {
|
|
73
|
-
if (symbol.id !== symbolOrProperties.id) matches = false
|
|
74
|
-
} else {
|
|
75
|
-
for (const [key, value] of Object.entries(symbolOrProperties)) {
|
|
76
|
-
if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
|
|
77
|
-
matches = false
|
|
78
|
-
break
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (matches) {
|
|
83
|
-
total++
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return total
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Counts how many symbols matching the criteria are on the board.
|
|
92
|
-
*
|
|
93
|
-
* Passing a GameSymbol will compare by ID, passing a properties object will compare by properties.
|
|
94
|
-
*
|
|
95
|
-
* Returns a tuple where the first element is the total count, and the second element is a record of counts per reel index.
|
|
96
|
-
*/
|
|
97
|
-
countSymbolsOnBoard(
|
|
98
|
-
symbolOrProperties: GameSymbol | Record<string, any>,
|
|
99
|
-
): [number, Record<number, number>] {
|
|
100
|
-
let total = 0
|
|
101
|
-
const onReel: Record<number, number> = {}
|
|
102
|
-
|
|
103
|
-
for (const [ridx, reel] of this.reels.entries()) {
|
|
104
|
-
for (const symbol of reel) {
|
|
105
|
-
let matches = true
|
|
106
|
-
|
|
107
|
-
if (symbolOrProperties instanceof GameSymbol) {
|
|
108
|
-
if (symbol.id !== symbolOrProperties.id) matches = false
|
|
109
|
-
} else {
|
|
110
|
-
for (const [key, value] of Object.entries(symbolOrProperties)) {
|
|
111
|
-
if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
|
|
112
|
-
matches = false
|
|
113
|
-
break
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (matches) {
|
|
119
|
-
total++
|
|
120
|
-
if (onReel[ridx] === undefined) {
|
|
121
|
-
onReel[ridx] = 1
|
|
122
|
-
} else {
|
|
123
|
-
onReel[ridx]++
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return [total, onReel]
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Checks if a symbol appears more than once on any reel in the current reel set.
|
|
134
|
-
*
|
|
135
|
-
* Useful to check for "forbidden" generations, e.g. 2 scatters on one reel.
|
|
136
|
-
*/
|
|
137
|
-
isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol) {
|
|
138
|
-
for (const reel of this.reels) {
|
|
139
|
-
let count = 0
|
|
140
|
-
for (const sym of reel) {
|
|
141
|
-
if (sym.id === symbol.id) {
|
|
142
|
-
count++
|
|
143
|
-
}
|
|
144
|
-
if (count > 1) {
|
|
145
|
-
return true
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return false
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Draws a board using specified reel stops.
|
|
154
|
-
*/
|
|
155
|
-
drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>) {
|
|
156
|
-
this.drawBoardMixed(reels, forcedStops)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Draws a board using random reel stops.
|
|
161
|
-
*/
|
|
162
|
-
drawBoardWithRandomStops(reels: Reels) {
|
|
163
|
-
this.drawBoardMixed(reels)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private drawBoardMixed(reels: Reels, forcedStops?: Record<string, number>) {
|
|
167
|
-
this.resetReels()
|
|
168
|
-
|
|
169
|
-
const finalReelStops: (number | null)[] = Array.from(
|
|
170
|
-
{ length: this.ctx.getCurrentGameMode().reelsAmount },
|
|
171
|
-
() => null,
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
if (forcedStops) {
|
|
175
|
-
// Fill in forced stops
|
|
176
|
-
for (const [r, stopPos] of Object.entries(forcedStops)) {
|
|
177
|
-
const reelIdx = Number(r)
|
|
178
|
-
const symCount = this.ctx.getCurrentGameMode().symbolsPerReel[reelIdx]!
|
|
179
|
-
finalReelStops[reelIdx] =
|
|
180
|
-
stopPos - Math.round(this.ctx.state.rng.randomFloat(0, symCount - 1))
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Fill in random stops for reels without a forced stop
|
|
185
|
-
for (let i = 0; i < finalReelStops.length; i++) {
|
|
186
|
-
if (finalReelStops[i] === null) {
|
|
187
|
-
finalReelStops[i] = Math.floor(
|
|
188
|
-
this.ctx.state.rng.randomFloat(0, reels[i]!.length - 1),
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
for (let ridx = 0; ridx < this.ctx.getCurrentGameMode().reelsAmount; ridx++) {
|
|
194
|
-
const reelPos = finalReelStops[ridx]!
|
|
195
|
-
|
|
196
|
-
if (this.ctx.config.padSymbols && this.ctx.config.padSymbols > 0) {
|
|
197
|
-
for (let p = this.ctx.config.padSymbols - 1; p >= 0; p--) {
|
|
198
|
-
const topPos = (reelPos - (p + 1)) % reels[ridx]!.length
|
|
199
|
-
this.paddingTop[ridx]!.push(reels[ridx]![topPos]!)
|
|
200
|
-
const bottomPos =
|
|
201
|
-
(reelPos + this.ctx.getCurrentGameMode().symbolsPerReel[ridx]! + p) %
|
|
202
|
-
reels[ridx]!.length
|
|
203
|
-
this.paddingBottom[ridx]!.unshift(reels[ridx]![bottomPos]!)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
for (
|
|
208
|
-
let row = 0;
|
|
209
|
-
row < this.ctx.getCurrentGameMode().symbolsPerReel[ridx]!;
|
|
210
|
-
row++
|
|
211
|
-
) {
|
|
212
|
-
const symbol = reels[ridx]![(reelPos + row) % reels[ridx]!.length]
|
|
213
|
-
|
|
214
|
-
if (!symbol) {
|
|
215
|
-
throw new Error(`Failed to get symbol at pos ${reelPos + row} on reel ${ridx}`)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
this.reels[ridx]![row] = symbol
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
interface StandaloneBoardOpts {
|
|
225
|
-
ctx: AnySimulationContext
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Extends GameState. Provides board-related functionality.
|
|
230
|
-
*/
|
|
231
|
-
export class Board<
|
|
232
|
-
TGameModes extends AnyGameModes,
|
|
233
|
-
TSymbols extends AnySymbols,
|
|
234
|
-
TUserState extends AnyUserData,
|
|
235
|
-
> extends GameState<TGameModes, TSymbols, TUserState> {
|
|
236
|
-
board: {
|
|
237
|
-
reels: Reels
|
|
238
|
-
paddingTop: Reels
|
|
239
|
-
paddingBottom: Reels
|
|
240
|
-
anticipation: number[]
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>) {
|
|
244
|
-
super(opts)
|
|
245
|
-
|
|
246
|
-
this.board = {
|
|
247
|
-
reels: [],
|
|
248
|
-
paddingTop: [],
|
|
249
|
-
paddingBottom: [],
|
|
250
|
-
anticipation: [],
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Resets the board to an empty state.\
|
|
256
|
-
* This is called before drawing a new board.
|
|
257
|
-
*/
|
|
258
|
-
resetBoard() {
|
|
259
|
-
this.resetReels()
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private makeEmptyReels() {
|
|
263
|
-
return Array.from({ length: this.getCurrentGameMode().reelsAmount }, () => [])
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private resetReels() {
|
|
267
|
-
this.board.reels = this.makeEmptyReels()
|
|
268
|
-
this.board.anticipation = Array.from(
|
|
269
|
-
{ length: this.getCurrentGameMode().reelsAmount },
|
|
270
|
-
() => 0,
|
|
271
|
-
)
|
|
272
|
-
if (this.config.padSymbols && this.config.padSymbols > 0) {
|
|
273
|
-
this.board.paddingTop = this.makeEmptyReels()
|
|
274
|
-
this.board.paddingBottom = this.makeEmptyReels()
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Counts how many symbols matching the criteria are on a specific reel.
|
|
280
|
-
*/
|
|
281
|
-
countSymbolsOnReel(
|
|
282
|
-
symbolOrProperties: GameSymbol | Record<string, any>,
|
|
283
|
-
reelIndex: number,
|
|
284
|
-
) {
|
|
285
|
-
let total = 0
|
|
286
|
-
|
|
287
|
-
for (const symbol of this.board.reels[reelIndex]!) {
|
|
288
|
-
let matches = true
|
|
289
|
-
if (symbolOrProperties instanceof GameSymbol) {
|
|
290
|
-
if (symbol.id !== symbolOrProperties.id) matches = false
|
|
291
|
-
} else {
|
|
292
|
-
for (const [key, value] of Object.entries(symbolOrProperties)) {
|
|
293
|
-
if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
|
|
294
|
-
matches = false
|
|
295
|
-
break
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (matches) {
|
|
300
|
-
total++
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return total
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Counts how many symbols matching the criteria are on the board.
|
|
309
|
-
*
|
|
310
|
-
* Passing a GameSymbol will compare by ID, passing a properties object will compare by properties.
|
|
311
|
-
*
|
|
312
|
-
* Returns a tuple where the first element is the total count, and the second element is a record of counts per reel index.
|
|
313
|
-
*/
|
|
314
|
-
countSymbolsOnBoard(
|
|
315
|
-
symbolOrProperties: GameSymbol | Record<string, any>,
|
|
316
|
-
): [number, Record<number, number>] {
|
|
317
|
-
let total = 0
|
|
318
|
-
const onReel: Record<number, number> = {}
|
|
319
|
-
|
|
320
|
-
for (const [ridx, reel] of this.board.reels.entries()) {
|
|
321
|
-
for (const symbol of reel) {
|
|
322
|
-
let matches = true
|
|
323
|
-
|
|
324
|
-
if (symbolOrProperties instanceof GameSymbol) {
|
|
325
|
-
if (symbol.id !== symbolOrProperties.id) matches = false
|
|
326
|
-
} else {
|
|
327
|
-
for (const [key, value] of Object.entries(symbolOrProperties)) {
|
|
328
|
-
if (!symbol.properties.has(key) || symbol.properties.get(key) !== value) {
|
|
329
|
-
matches = false
|
|
330
|
-
break
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (matches) {
|
|
336
|
-
total++
|
|
337
|
-
if (onReel[ridx] === undefined) {
|
|
338
|
-
onReel[ridx] = 1
|
|
339
|
-
} else {
|
|
340
|
-
onReel[ridx]++
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return [total, onReel]
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Checks if a symbol appears more than once on any reel in the current reel set.
|
|
351
|
-
*
|
|
352
|
-
* Useful to check for "forbidden" generations, e.g. 2 scatters on one reel.
|
|
353
|
-
*/
|
|
354
|
-
isSymbolOnAnyReelMultipleTimes(symbol: GameSymbol) {
|
|
355
|
-
for (const reel of this.board.reels) {
|
|
356
|
-
let count = 0
|
|
357
|
-
for (const sym of reel) {
|
|
358
|
-
if (sym.id === symbol.id) {
|
|
359
|
-
count++
|
|
360
|
-
}
|
|
361
|
-
if (count > 1) {
|
|
362
|
-
return true
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return false
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Gets all reel stops (positions) where the specified symbol appears in the current reel set.\
|
|
371
|
-
* Returns an array of arrays, where each inner array contains the positions for the corresponding reel.
|
|
372
|
-
*/
|
|
373
|
-
getReelStopsForSymbol(reels: Reels, symbol: GameSymbol) {
|
|
374
|
-
const reelStops: number[][] = []
|
|
375
|
-
for (let ridx = 0; ridx < reels.length; ridx++) {
|
|
376
|
-
const reel = reels[ridx]!
|
|
377
|
-
const positions: number[] = []
|
|
378
|
-
for (let pos = 0; pos < reel.length; pos++) {
|
|
379
|
-
if (reel[pos]!.id === symbol.id) {
|
|
380
|
-
positions.push(pos)
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
reelStops.push(positions)
|
|
384
|
-
}
|
|
385
|
-
return reelStops
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Combines multiple arrays of reel stops into a single array of reel stops.\
|
|
390
|
-
*/
|
|
391
|
-
combineReelStops(...reelStops: number[][][]) {
|
|
392
|
-
const combined: number[][] = []
|
|
393
|
-
for (let ridx = 0; ridx < this.getCurrentGameMode().reelsAmount; ridx++) {
|
|
394
|
-
combined[ridx] = []
|
|
395
|
-
for (const stops of reelStops) {
|
|
396
|
-
combined[ridx] = combined[ridx]!.concat(stops[ridx]!)
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return combined
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* From a list of reel stops on reels, selects a random stop for a speficied number of random symbols.
|
|
404
|
-
*
|
|
405
|
-
* Mostly useful for placing scatter symbols on the board.
|
|
406
|
-
*/
|
|
407
|
-
getRandomReelStops(reels: Reels, reelStops: number[][], amount: number) {
|
|
408
|
-
const reelsAmount = this.getCurrentGameMode().reelsAmount
|
|
409
|
-
const symProbsOnReels: number[] = []
|
|
410
|
-
const stopPositionsForReels: Record<string, number> = {}
|
|
411
|
-
|
|
412
|
-
for (let ridx = 0; ridx < reelsAmount; ridx++) {
|
|
413
|
-
symProbsOnReels.push(reelStops[ridx]!.length / reels[ridx]!.length)
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
while (Object.keys(stopPositionsForReels).length !== amount) {
|
|
417
|
-
const possibleReels: number[] = []
|
|
418
|
-
for (let i = 0; i < reelsAmount; i++) {
|
|
419
|
-
if (symProbsOnReels[i]! > 0) {
|
|
420
|
-
possibleReels.push(i)
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
const possibleProbs = symProbsOnReels.filter((p) => p > 0)
|
|
424
|
-
const weights = Object.fromEntries(
|
|
425
|
-
possibleReels.map((ridx, idx) => [ridx, possibleProbs[idx]!]),
|
|
426
|
-
)
|
|
427
|
-
const chosenReel = weightedRandom(weights, this.state.rng)
|
|
428
|
-
const chosenStop = randomItem(reelStops[Number(chosenReel)]!, this.state.rng)
|
|
429
|
-
symProbsOnReels[Number(chosenReel)] = 0
|
|
430
|
-
stopPositionsForReels[chosenReel] = chosenStop
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return stopPositionsForReels
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Selects a random reelset based on the configured weights for the current game mode.\
|
|
438
|
-
* Returns the reels as arrays of GameSymbols.
|
|
439
|
-
*/
|
|
440
|
-
getRandomReelset() {
|
|
441
|
-
const weights = this.state.currentResultSet.reelWeights
|
|
442
|
-
const evalWeights = this.state.currentResultSet.reelWeights.evaluate?.(
|
|
443
|
-
this as Board<any, any, any>,
|
|
444
|
-
)
|
|
445
|
-
|
|
446
|
-
let reelSetId: string = ""
|
|
447
|
-
|
|
448
|
-
if (evalWeights) {
|
|
449
|
-
reelSetId = weightedRandom(evalWeights, this.state.rng)
|
|
450
|
-
} else {
|
|
451
|
-
reelSetId = weightedRandom(weights[this.state.currentSpinType]!, this.state.rng)
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const reelSet = this.getReelsetById(this.state.currentGameMode, reelSetId)
|
|
455
|
-
return reelSet.reels
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Draws a board using specified reel stops.
|
|
460
|
-
*/
|
|
461
|
-
drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>) {
|
|
462
|
-
this.drawBoardMixed(reels, forcedStops)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Draws a board using random reel stops.
|
|
467
|
-
*/
|
|
468
|
-
drawBoardWithRandomStops(reels: Reels) {
|
|
469
|
-
this.drawBoardMixed(reels)
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
private drawBoardMixed(reels: Reels, forcedStops?: Record<string, number>) {
|
|
473
|
-
this.resetReels()
|
|
474
|
-
|
|
475
|
-
const finalReelStops: (number | null)[] = Array.from(
|
|
476
|
-
{ length: this.getCurrentGameMode().reelsAmount },
|
|
477
|
-
() => null,
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
if (forcedStops) {
|
|
481
|
-
// Fill in forced stops
|
|
482
|
-
for (const [r, stopPos] of Object.entries(forcedStops)) {
|
|
483
|
-
const reelIdx = Number(r)
|
|
484
|
-
const symCount = this.getCurrentGameMode().symbolsPerReel[reelIdx]!
|
|
485
|
-
finalReelStops[reelIdx] =
|
|
486
|
-
stopPos - Math.round(this.state.rng.randomFloat(0, symCount - 1))
|
|
487
|
-
if (finalReelStops[reelIdx]! < 0) {
|
|
488
|
-
finalReelStops[reelIdx] = reels[reelIdx]!.length + finalReelStops[reelIdx]!
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Fill in random stops for reels without a forced stop
|
|
494
|
-
for (let i = 0; i < finalReelStops.length; i++) {
|
|
495
|
-
if (finalReelStops[i] === null) {
|
|
496
|
-
finalReelStops[i] = Math.floor(
|
|
497
|
-
this.state.rng.randomFloat(0, reels[i]!.length - 1),
|
|
498
|
-
)
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
for (let ridx = 0; ridx < this.getCurrentGameMode().reelsAmount; ridx++) {
|
|
503
|
-
const reelPos = finalReelStops[ridx]!
|
|
504
|
-
|
|
505
|
-
if (this.config.padSymbols && this.config.padSymbols > 0) {
|
|
506
|
-
for (let p = this.config.padSymbols - 1; p >= 0; p--) {
|
|
507
|
-
const topPos = (reelPos - (p + 1)) % reels[ridx]!.length
|
|
508
|
-
this.board.paddingTop[ridx]!.push(reels[ridx]![topPos]!)
|
|
509
|
-
const bottomPos =
|
|
510
|
-
(reelPos + this.getCurrentGameMode().symbolsPerReel[ridx]! + p) %
|
|
511
|
-
reels[ridx]!.length
|
|
512
|
-
this.board.paddingBottom[ridx]!.unshift(reels[ridx]![bottomPos]!)
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
for (let row = 0; row < this.getCurrentGameMode().symbolsPerReel[ridx]!; row++) {
|
|
517
|
-
const symbol = reels[ridx]![(reelPos + row) % reels[ridx]!.length]
|
|
518
|
-
|
|
519
|
-
if (!symbol) {
|
|
520
|
-
throw new Error(`Failed to get symbol at pos ${reelPos + row} on reel ${ridx}`)
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
this.board.reels[ridx]![row] = symbol
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
package/src/Book.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { GameConfig } from "./GameConfig"
|
|
2
|
-
import { AnySimulationContext } from "./Simulation"
|
|
3
|
-
|
|
4
|
-
export class Book {
|
|
5
|
-
id: number
|
|
6
|
-
criteria: string = "N/A"
|
|
7
|
-
protected events: BookEvent[] = []
|
|
8
|
-
protected payout: number = 0
|
|
9
|
-
protected basegameWins: number = 0
|
|
10
|
-
protected freespinsWins: number = 0
|
|
11
|
-
|
|
12
|
-
constructor(opts: BookOpts) {
|
|
13
|
-
this.id = opts.id
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Adds an event to the book.
|
|
18
|
-
*/
|
|
19
|
-
addEvent(event: Omit<BookEvent, "index">) {
|
|
20
|
-
const index = this.events.length + 1
|
|
21
|
-
this.events.push({ index, ...event })
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Transfers the win data from the wallet to the book.
|
|
26
|
-
*/
|
|
27
|
-
writePayout(ctx: AnySimulationContext) {
|
|
28
|
-
function process(number: number) {
|
|
29
|
-
return Math.round(Math.min(number, ctx.config.maxWinX) * 100) / 100
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
this.payout = Math.round(process(ctx.wallet.getCurrentWin()) * 100)
|
|
33
|
-
this.basegameWins = process(
|
|
34
|
-
ctx.wallet.getCurrentWinPerSpinType()[GameConfig.SPIN_TYPE.BASE_GAME] || 0,
|
|
35
|
-
)
|
|
36
|
-
this.freespinsWins = process(
|
|
37
|
-
ctx.wallet.getCurrentWinPerSpinType()[GameConfig.SPIN_TYPE.FREE_SPINS] || 0,
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
getPayout() {
|
|
42
|
-
return this.payout
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
getBasegameWins() {
|
|
46
|
-
return this.basegameWins
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
getFreespinsWins() {
|
|
50
|
-
return this.freespinsWins
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
serialize() {
|
|
54
|
-
return {
|
|
55
|
-
id: this.id,
|
|
56
|
-
criteria: this.criteria,
|
|
57
|
-
events: this.events,
|
|
58
|
-
payout: this.payout,
|
|
59
|
-
basegameWins: this.basegameWins,
|
|
60
|
-
freespinsWins: this.freespinsWins,
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static fromSerialized(data: ReturnType<Book["serialize"]>) {
|
|
65
|
-
const book = new Book({ id: data.id })
|
|
66
|
-
book.criteria = data.criteria
|
|
67
|
-
book.events = data.events
|
|
68
|
-
book.payout = data.payout
|
|
69
|
-
book.basegameWins = data.basegameWins
|
|
70
|
-
book.freespinsWins = data.freespinsWins
|
|
71
|
-
return book
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface BookEvent {
|
|
76
|
-
index: number
|
|
77
|
-
type: string
|
|
78
|
-
data: Record<string, any>
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
interface BookOpts {
|
|
82
|
-
id: number
|
|
83
|
-
}
|