@slot-engine/core 0.0.1 → 0.0.2
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 +0 -7
- package/dist/index.d.ts +0 -7
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -7
- 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/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/SlotGame.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { AnyGameModes, AnySymbols, AnyUserData, CommonGameOptions } from "../index"
|
|
2
|
-
import { GameConfig } from "./GameConfig"
|
|
3
|
-
import { Simulation, SimulationConfigOpts, SimulationOpts } from "./Simulation"
|
|
4
|
-
import { Analysis, AnalysisOpts } from "./analysis"
|
|
5
|
-
import { OptimizationOpts, Optimizer, OptimizerOpts } from "./optimizer"
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* SlotGame class that encapsulates the game configuration and state.\
|
|
9
|
-
* Main entry point for the slot game.
|
|
10
|
-
*/
|
|
11
|
-
export class SlotGame<
|
|
12
|
-
TGameModes extends AnyGameModes = AnyGameModes,
|
|
13
|
-
TSymbols extends AnySymbols = AnySymbols,
|
|
14
|
-
TUserState extends AnyUserData = AnyUserData,
|
|
15
|
-
> {
|
|
16
|
-
private readonly gameConfigOpts: CommonGameOptions<TGameModes, TSymbols, TUserState>
|
|
17
|
-
private simulation?: Simulation
|
|
18
|
-
private optimizer?: Optimizer
|
|
19
|
-
private analyzer?: Analysis
|
|
20
|
-
|
|
21
|
-
constructor(config: CommonGameOptions<TGameModes, TSymbols, TUserState>) {
|
|
22
|
-
this.gameConfigOpts = config
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Sets up the simulation configuration.\
|
|
27
|
-
* Must be called before `runTasks()`.
|
|
28
|
-
*/
|
|
29
|
-
configureSimulation(opts: SimulationConfigOpts) {
|
|
30
|
-
this.simulation = new Simulation(opts, this.gameConfigOpts as any)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Sets up the optimization configuration.\
|
|
35
|
-
* Must be called before `runTasks()`.
|
|
36
|
-
*/
|
|
37
|
-
configureOptimization(opts: Pick<OptimizerOpts, "gameModes">) {
|
|
38
|
-
this.optimizer = new Optimizer({
|
|
39
|
-
game: this,
|
|
40
|
-
gameModes: opts.gameModes,
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Runs the simulation based on the configured settings.
|
|
46
|
-
*/
|
|
47
|
-
private async runSimulation(opts: SimulationOpts = {}) {
|
|
48
|
-
if (!this.simulation) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
"Simulation is not configured. Do so by calling configureSimulation() first.",
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await this.simulation.runSimulation(opts)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Runs the optimization based on the configured settings.
|
|
59
|
-
*/
|
|
60
|
-
private async runOptimization(opts: OptimizationOpts) {
|
|
61
|
-
if (!this.optimizer) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
"Optimization is not configured. Do so by calling configureOptimization() first.",
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
await this.optimizer.runOptimization(opts)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Runs the analysis based on the configured settings.
|
|
72
|
-
*/
|
|
73
|
-
private async runAnalysis(opts: AnalysisOpts) {
|
|
74
|
-
if (!this.optimizer) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
"Optimization must be configured to run analysis. Do so by calling configureOptimization() first.",
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
this.analyzer = new Analysis(this.optimizer)
|
|
80
|
-
await this.analyzer.runAnalysis(opts.gameModes)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async runTasks(
|
|
84
|
-
opts: {
|
|
85
|
-
debug?: boolean
|
|
86
|
-
doSimulation?: boolean
|
|
87
|
-
doOptimization?: boolean
|
|
88
|
-
doAnalysis?: boolean
|
|
89
|
-
simulationOpts?: SimulationOpts
|
|
90
|
-
optimizationOpts?: OptimizationOpts
|
|
91
|
-
analysisOpts?: AnalysisOpts
|
|
92
|
-
} = {},
|
|
93
|
-
) {
|
|
94
|
-
if (!opts.doSimulation && !opts.doOptimization && !opts.doAnalysis) {
|
|
95
|
-
console.log("No tasks to run. Enable either simulation, optimization or analysis.")
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (opts.doSimulation) {
|
|
99
|
-
await this.runSimulation({ debug: opts.debug })
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (opts.doOptimization) {
|
|
103
|
-
await this.runOptimization(opts.optimizationOpts || { gameModes: [] })
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (opts.doAnalysis) {
|
|
107
|
-
await this.runAnalysis(opts.analysisOpts || { gameModes: [] })
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Gets the game configuration.
|
|
113
|
-
*/
|
|
114
|
-
getConfig() {
|
|
115
|
-
return new GameConfig(this.gameConfigOpts)
|
|
116
|
-
}
|
|
117
|
-
}
|
package/src/Wallet.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { GameConfig, SpinType } from "./GameConfig"
|
|
2
|
-
import { AnySimulationContext } from "./Simulation"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Stores win amounts for simulations.
|
|
6
|
-
*/
|
|
7
|
-
export class Wallet {
|
|
8
|
-
/**
|
|
9
|
-
* Total win amount (as the bet multiplier) from all simulations.
|
|
10
|
-
*/
|
|
11
|
-
protected cumulativeWins = 0
|
|
12
|
-
/**
|
|
13
|
-
* Total win amount (as the bet multiplier) per spin type.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```ts
|
|
17
|
-
* {
|
|
18
|
-
* basegame: 50,
|
|
19
|
-
* freespins: 100,
|
|
20
|
-
* superfreespins: 200,
|
|
21
|
-
* }
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
protected cumulativeWinsPerSpinType = {
|
|
25
|
-
[GameConfig.SPIN_TYPE.BASE_GAME]: 0,
|
|
26
|
-
[GameConfig.SPIN_TYPE.FREE_SPINS]: 0,
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Current win amount (as the bet multiplier) for the ongoing simulation.
|
|
30
|
-
*/
|
|
31
|
-
protected currentWin = 0
|
|
32
|
-
/**
|
|
33
|
-
* Current win amount (as the bet multiplier) for the ongoing simulation per spin type.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```ts
|
|
37
|
-
* {
|
|
38
|
-
* basegame: 50,
|
|
39
|
-
* freespins: 100,
|
|
40
|
-
* superfreespins: 200,
|
|
41
|
-
* }
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
protected currentWinPerSpinType = {
|
|
45
|
-
[GameConfig.SPIN_TYPE.BASE_GAME]: 0,
|
|
46
|
-
[GameConfig.SPIN_TYPE.FREE_SPINS]: 0,
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Holds the current win amount for a single (free) spin.\
|
|
50
|
-
* After each spin, this amount is added to `currentWinPerSpinType` and then reset to zero.
|
|
51
|
-
*/
|
|
52
|
-
protected currentSpinWin = 0
|
|
53
|
-
/**
|
|
54
|
-
* Current win amount (as the bet multiplier) for the ongoing tumble sequence.
|
|
55
|
-
*/
|
|
56
|
-
protected currentTumbleWin = 0
|
|
57
|
-
|
|
58
|
-
constructor() {}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Updates the win for the current spin.
|
|
62
|
-
*
|
|
63
|
-
* Should be called after each tumble event, if applicable.\
|
|
64
|
-
* Or generally call this to add wins during a spin.
|
|
65
|
-
*
|
|
66
|
-
* After each (free) spin, this amount should be added to `currentWinPerSpinType` via `confirmSpinWin()`
|
|
67
|
-
*/
|
|
68
|
-
addSpinWin(amount: number) {
|
|
69
|
-
this.currentSpinWin += amount
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Assigns a win amount to the given spin type.
|
|
74
|
-
*
|
|
75
|
-
* Should be called after `addSpinWin()`, and after your tumble events are played out,\
|
|
76
|
-
* and after a (free) spin is played out to finalize the win.
|
|
77
|
-
*/
|
|
78
|
-
confirmSpinWin(spinType: SpinType) {
|
|
79
|
-
if (!Object.keys(this.currentWinPerSpinType).includes(spinType)) {
|
|
80
|
-
throw new Error(`Spin type "${spinType}" does not exist in the wallet.`)
|
|
81
|
-
}
|
|
82
|
-
this.currentWinPerSpinType[spinType]! += this.currentSpinWin
|
|
83
|
-
this.currentWin += this.currentSpinWin
|
|
84
|
-
this.currentSpinWin = 0
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Returns the accumulated win amount (as the bet multiplier) from all simulations.
|
|
89
|
-
*/
|
|
90
|
-
getCumulativeWins() {
|
|
91
|
-
return this.cumulativeWins
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Returns the accumulated win amount (as the bet multiplier) per spin type from all simulations.
|
|
96
|
-
*/
|
|
97
|
-
getCumulativeWinsPerSpinType() {
|
|
98
|
-
return this.cumulativeWinsPerSpinType
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Returns the current win amount (as the bet multiplier) for the ongoing simulation.
|
|
103
|
-
*/
|
|
104
|
-
getCurrentWin() {
|
|
105
|
-
return this.currentWin
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Returns the current win amount (as the bet multiplier) per spin type for the ongoing simulation.
|
|
110
|
-
*/
|
|
111
|
-
getCurrentWinPerSpinType() {
|
|
112
|
-
return this.currentWinPerSpinType
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Adds a win to `currentSpinWin` and `currentTumbleWin`.
|
|
117
|
-
*
|
|
118
|
-
* After each (free) spin, this amount should be added to `currentWinPerSpinType` via `confirmSpinWin()`
|
|
119
|
-
*/
|
|
120
|
-
addTumbleWin(amount: number) {
|
|
121
|
-
this.currentTumbleWin += amount
|
|
122
|
-
this.addSpinWin(amount)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Resets the current win amounts to zero.
|
|
127
|
-
*/
|
|
128
|
-
resetCurrentWin() {
|
|
129
|
-
this.currentWin = 0
|
|
130
|
-
this.currentSpinWin = 0
|
|
131
|
-
this.currentTumbleWin = 0
|
|
132
|
-
|
|
133
|
-
for (const spinType of Object.keys(this.currentWinPerSpinType)) {
|
|
134
|
-
this.currentWinPerSpinType[spinType as SpinType] = 0
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Adds current wins to cumulative wins and resets current wins to zero.
|
|
140
|
-
*/
|
|
141
|
-
confirmWins(ctx: AnySimulationContext) {
|
|
142
|
-
function process(number: number) {
|
|
143
|
-
return Math.round(Math.min(number, ctx.config.maxWinX) * 100) / 100
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
ctx.state.book.writePayout(ctx)
|
|
147
|
-
|
|
148
|
-
this.currentWin = process(this.currentWin)
|
|
149
|
-
this.cumulativeWins += this.currentWin
|
|
150
|
-
let spinTypeWins = 0
|
|
151
|
-
|
|
152
|
-
for (const spinType of Object.keys(this.currentWinPerSpinType)) {
|
|
153
|
-
const st = spinType as SpinType
|
|
154
|
-
const spinTypeWin = process(this.currentWinPerSpinType[st])
|
|
155
|
-
this.cumulativeWinsPerSpinType[st]! += spinTypeWin
|
|
156
|
-
spinTypeWins += spinTypeWin
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (process(spinTypeWins) !== this.currentWin) {
|
|
160
|
-
throw new Error(
|
|
161
|
-
`Inconsistent wallet state: currentWin (${this.currentWin}) does not equal spinTypeWins (${spinTypeWins}).`,
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this.resetCurrentWin()
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
serialize() {
|
|
169
|
-
return {
|
|
170
|
-
cumulativeWins: this.cumulativeWins,
|
|
171
|
-
cumulativeWinsPerSpinType: this.cumulativeWinsPerSpinType,
|
|
172
|
-
currentWin: this.currentWin,
|
|
173
|
-
currentWinPerSpinType: this.currentWinPerSpinType,
|
|
174
|
-
currentSpinWin: this.currentSpinWin,
|
|
175
|
-
currentTumbleWin: this.currentTumbleWin,
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
merge(wallet: Wallet) {
|
|
180
|
-
this.cumulativeWins += wallet.getCumulativeWins()
|
|
181
|
-
const otherWinsPerSpinType = wallet.getCumulativeWinsPerSpinType()
|
|
182
|
-
|
|
183
|
-
for (const spinType of Object.keys(this.cumulativeWinsPerSpinType)) {
|
|
184
|
-
this.cumulativeWinsPerSpinType[spinType as SpinType]! +=
|
|
185
|
-
otherWinsPerSpinType[spinType as SpinType] || 0
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
mergeSerialized(data: ReturnType<Wallet["serialize"]>) {
|
|
190
|
-
this.cumulativeWins += data.cumulativeWins
|
|
191
|
-
for (const spinType of Object.keys(this.cumulativeWinsPerSpinType)) {
|
|
192
|
-
this.cumulativeWinsPerSpinType[spinType as SpinType]! +=
|
|
193
|
-
data.cumulativeWinsPerSpinType[spinType as SpinType] || 0
|
|
194
|
-
}
|
|
195
|
-
this.currentWin += data.currentWin
|
|
196
|
-
this.currentSpinWin += data.currentSpinWin
|
|
197
|
-
this.currentTumbleWin += data.currentTumbleWin
|
|
198
|
-
for (const spinType of Object.keys(this.currentWinPerSpinType)) {
|
|
199
|
-
this.currentWinPerSpinType[spinType as SpinType]! +=
|
|
200
|
-
data.currentWinPerSpinType[spinType as SpinType] || 0
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
package/src/WinType.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { GameSymbol } from "./GameSymbol"
|
|
2
|
-
import { AnySimulationContext } from "./Simulation"
|
|
3
|
-
|
|
4
|
-
export class WinType {
|
|
5
|
-
protected payout: number
|
|
6
|
-
protected winCombinations: WinCombination[]
|
|
7
|
-
protected ctx!: AnySimulationContext
|
|
8
|
-
protected readonly wildSymbol?: WildSymbol
|
|
9
|
-
|
|
10
|
-
constructor(opts?: WinTypeOpts) {
|
|
11
|
-
this.payout = 0
|
|
12
|
-
this.winCombinations = []
|
|
13
|
-
this.wildSymbol = opts?.wildSymbol
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Sets the simulation context for this WinType instance.
|
|
18
|
-
*
|
|
19
|
-
* This gives the WinType access to the current board.
|
|
20
|
-
*/
|
|
21
|
-
context(ctx: AnySimulationContext): WinType {
|
|
22
|
-
this.ctx = ctx
|
|
23
|
-
return this
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
protected ensureContext() {
|
|
27
|
-
if (!this.ctx) {
|
|
28
|
-
throw new Error("WinType context is not set. Call context(ctx) first.")
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
|
|
34
|
-
*/
|
|
35
|
-
evaluateWins() {
|
|
36
|
-
this.ensureContext()
|
|
37
|
-
return this
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Custom post-processing of wins, e.g. for handling multipliers.
|
|
42
|
-
*/
|
|
43
|
-
postProcess(func: PostProcessFn<typeof this.winCombinations>) {
|
|
44
|
-
this.ensureContext()
|
|
45
|
-
const result = func(this, this.ctx!)
|
|
46
|
-
this.payout = result.payout
|
|
47
|
-
this.winCombinations = result.winCombinations
|
|
48
|
-
return this
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Returns the total payout and detailed win combinations.
|
|
53
|
-
*/
|
|
54
|
-
getWins() {
|
|
55
|
-
return {
|
|
56
|
-
payout: this.payout,
|
|
57
|
-
winCombinations: this.winCombinations,
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface WinTypeOpts {
|
|
63
|
-
/**
|
|
64
|
-
* Configuration used to identify wild symbols on the board.\
|
|
65
|
-
* You can either provide a specific `GameSymbol` instance or a set of properties to match against symbols on the board.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* If you have different wild symbols, each with a property `isWild: true`, you can define:
|
|
69
|
-
* ```ts
|
|
70
|
-
* wildSymbol: { isWild: true }
|
|
71
|
-
* ```
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* If you have a single wild symbol instance, you can define:
|
|
75
|
-
* ```ts
|
|
76
|
-
* wildSymbol: myWildSymbol
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
wildSymbol?: WildSymbol
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export type WinCombination = {
|
|
83
|
-
payout: number
|
|
84
|
-
kind: number
|
|
85
|
-
symbols: Array<{
|
|
86
|
-
symbol: GameSymbol
|
|
87
|
-
isWild: boolean
|
|
88
|
-
substitutedFor?: GameSymbol
|
|
89
|
-
reelIndex: number
|
|
90
|
-
posIndex: number
|
|
91
|
-
}>
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
type PostProcessFn<TWinCombs extends WinCombination[]> = (
|
|
95
|
-
winType: WinType,
|
|
96
|
-
ctx: AnySimulationContext,
|
|
97
|
-
) => {
|
|
98
|
-
payout: number
|
|
99
|
-
winCombinations: TWinCombs
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
type WildSymbol = GameSymbol | Record<string, any>
|
package/src/analysis/index.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import fs from "fs"
|
|
2
|
-
import path from "path"
|
|
3
|
-
import { GameConfig } from "../GameConfig"
|
|
4
|
-
import { Optimizer, OptimzierGameModeConfig } from "../optimizer"
|
|
5
|
-
import assert from "assert"
|
|
6
|
-
import {
|
|
7
|
-
getAvgWin,
|
|
8
|
-
getLessBetHitrate,
|
|
9
|
-
getMaxWin,
|
|
10
|
-
getMaxwinHitrate,
|
|
11
|
-
getMinWin,
|
|
12
|
-
getNonZeroHitrate,
|
|
13
|
-
getNullHitrate,
|
|
14
|
-
getPayoutWeights,
|
|
15
|
-
getRtp,
|
|
16
|
-
getStandardDeviation,
|
|
17
|
-
getTotalLutWeight,
|
|
18
|
-
getUniquePayouts,
|
|
19
|
-
getVariance,
|
|
20
|
-
parseLookupTable,
|
|
21
|
-
} from "./utils"
|
|
22
|
-
import { writeJsonFile } from "../../utils"
|
|
23
|
-
|
|
24
|
-
export class Analysis {
|
|
25
|
-
protected readonly gameConfig: GameConfig["config"]
|
|
26
|
-
protected readonly optimizerConfig: OptimzierGameModeConfig
|
|
27
|
-
protected filePaths: Record<string, FilePaths>
|
|
28
|
-
|
|
29
|
-
constructor(optimizer: Optimizer) {
|
|
30
|
-
this.gameConfig = optimizer.getGameConfig()
|
|
31
|
-
this.optimizerConfig = optimizer.getOptimizerGameModes()
|
|
32
|
-
this.filePaths = {}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async runAnalysis(gameModes: string[]) {
|
|
36
|
-
this.filePaths = this.getPathsForModes(gameModes)
|
|
37
|
-
this.getNumberStats(gameModes)
|
|
38
|
-
this.getWinRanges(gameModes)
|
|
39
|
-
//this.getSymbolStats(gameModes)
|
|
40
|
-
console.log("Analysis complete. Files written to build directory.")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private getPathsForModes(gameModes: string[]) {
|
|
44
|
-
const rootPath = process.cwd()
|
|
45
|
-
const paths: Record<string, FilePaths> = {}
|
|
46
|
-
|
|
47
|
-
for (const modeStr of gameModes) {
|
|
48
|
-
const lut = path.join(
|
|
49
|
-
rootPath,
|
|
50
|
-
this.gameConfig.outputDir,
|
|
51
|
-
`lookUpTable_${modeStr}.csv`,
|
|
52
|
-
)
|
|
53
|
-
const lutSegmented = path.join(
|
|
54
|
-
rootPath,
|
|
55
|
-
this.gameConfig.outputDir,
|
|
56
|
-
`lookUpTableSegmented_${modeStr}.csv`,
|
|
57
|
-
)
|
|
58
|
-
const lutOptimized = path.join(
|
|
59
|
-
rootPath,
|
|
60
|
-
this.gameConfig.outputDir,
|
|
61
|
-
"publish_files",
|
|
62
|
-
`lookUpTable_${modeStr}_0.csv`,
|
|
63
|
-
)
|
|
64
|
-
const booksJsonl = path.join(
|
|
65
|
-
rootPath,
|
|
66
|
-
this.gameConfig.outputDir,
|
|
67
|
-
`books_${modeStr}.jsonl`,
|
|
68
|
-
)
|
|
69
|
-
const booksJsonlCompressed = path.join(
|
|
70
|
-
rootPath,
|
|
71
|
-
this.gameConfig.outputDir,
|
|
72
|
-
"publish_files",
|
|
73
|
-
`books_${modeStr}.jsonl.zst`,
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
paths[modeStr] = {
|
|
77
|
-
lut,
|
|
78
|
-
lutSegmented,
|
|
79
|
-
lutOptimized,
|
|
80
|
-
booksJsonl,
|
|
81
|
-
booksJsonlCompressed,
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
for (const p of Object.values(paths[modeStr])) {
|
|
85
|
-
assert(
|
|
86
|
-
fs.existsSync(p),
|
|
87
|
-
`File "${p}" does not exist. Run optimization to auto-create it.`,
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return paths
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
private getNumberStats(gameModes: string[]) {
|
|
96
|
-
const stats: Statistics[] = []
|
|
97
|
-
|
|
98
|
-
for (const modeStr of gameModes) {
|
|
99
|
-
const mode = this.getGameModeConfig(modeStr)
|
|
100
|
-
|
|
101
|
-
const lutOptimized = parseLookupTable(
|
|
102
|
-
fs.readFileSync(this.filePaths[modeStr]!.lutOptimized, "utf-8"),
|
|
103
|
-
)
|
|
104
|
-
const totalWeight = getTotalLutWeight(lutOptimized)
|
|
105
|
-
const payoutWeights = getPayoutWeights(lutOptimized)
|
|
106
|
-
|
|
107
|
-
stats.push({
|
|
108
|
-
gameMode: mode.name,
|
|
109
|
-
totalWeight,
|
|
110
|
-
avgWin: getAvgWin(payoutWeights),
|
|
111
|
-
rtp: getRtp(payoutWeights, mode.cost),
|
|
112
|
-
minWin: getMinWin(payoutWeights),
|
|
113
|
-
maxWin: getMaxWin(payoutWeights),
|
|
114
|
-
stdDev: getStandardDeviation(payoutWeights),
|
|
115
|
-
variance: getVariance(payoutWeights),
|
|
116
|
-
nonZeroHitRate: getNonZeroHitrate(payoutWeights),
|
|
117
|
-
nullHitRate: getNullHitrate(payoutWeights),
|
|
118
|
-
maxwinHitRate: getMaxwinHitrate(payoutWeights),
|
|
119
|
-
lessBetHitRate: getLessBetHitrate(payoutWeights, mode.cost),
|
|
120
|
-
uniquePayouts: getUniquePayouts(payoutWeights),
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
writeJsonFile(
|
|
125
|
-
path.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
|
|
126
|
-
stats,
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private getWinRanges(gameModes: string[]) {
|
|
131
|
-
const winRanges: [number, number][] = [
|
|
132
|
-
[0, 0.1],
|
|
133
|
-
[0, 0.99],
|
|
134
|
-
[1, 1.99],
|
|
135
|
-
[2, 2.99],
|
|
136
|
-
[3, 4.99],
|
|
137
|
-
[5, 9.99],
|
|
138
|
-
[10, 19.99],
|
|
139
|
-
[20, 49.99],
|
|
140
|
-
[50, 99.99],
|
|
141
|
-
[100, 199.99],
|
|
142
|
-
[200, 499.99],
|
|
143
|
-
[500, 999.99],
|
|
144
|
-
[1000, 1999.99],
|
|
145
|
-
[2000, 2999.99],
|
|
146
|
-
[3000, 4999.99],
|
|
147
|
-
[5000, 7499.99],
|
|
148
|
-
[7500, 9999.99],
|
|
149
|
-
[10000, 14999.99],
|
|
150
|
-
[15000, 19999.99],
|
|
151
|
-
[20000, 24999.99],
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
for (const modeStr of gameModes) {
|
|
155
|
-
const mode = this.getGameModeConfig(modeStr)
|
|
156
|
-
|
|
157
|
-
const lutOptimized = parseLookupTable(
|
|
158
|
-
fs.readFileSync(this.filePaths[modeStr]!.lutOptimized, "utf-8"),
|
|
159
|
-
)
|
|
160
|
-
const totalWeight = getTotalLutWeight(lutOptimized)
|
|
161
|
-
const payoutWeights = getPayoutWeights(lutOptimized)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private getGameModeConfig(mode: string) {
|
|
166
|
-
const config = this.gameConfig.gameModes[mode]
|
|
167
|
-
assert(config, `Game mode "${mode}" not found in game config`)
|
|
168
|
-
return config
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export interface AnalysisOpts {
|
|
173
|
-
gameModes: string[]
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
interface FilePaths {
|
|
177
|
-
lut: string
|
|
178
|
-
lutSegmented: string
|
|
179
|
-
lutOptimized: string
|
|
180
|
-
booksJsonl: string
|
|
181
|
-
booksJsonlCompressed: string
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
interface Statistics {
|
|
185
|
-
gameMode: string
|
|
186
|
-
totalWeight: number
|
|
187
|
-
rtp: number
|
|
188
|
-
avgWin: number
|
|
189
|
-
minWin: number
|
|
190
|
-
maxWin: number
|
|
191
|
-
stdDev: number
|
|
192
|
-
variance: number
|
|
193
|
-
maxwinHitRate: number
|
|
194
|
-
nonZeroHitRate: number
|
|
195
|
-
nullHitRate: number
|
|
196
|
-
lessBetHitRate: number
|
|
197
|
-
uniquePayouts: number
|
|
198
|
-
}
|