@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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.mts +0 -7
  3. package/dist/index.d.ts +0 -7
  4. package/dist/index.js +9 -7
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +9 -7
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +5 -1
  9. package/.turbo/turbo-build.log +0 -33
  10. package/.turbo/turbo-typecheck.log +0 -4
  11. package/CHANGELOG.md +0 -7
  12. package/index.ts +0 -205
  13. package/lib/zstd.exe +0 -0
  14. package/optimizer-rust/Cargo.toml +0 -19
  15. package/optimizer-rust/src/exes.rs +0 -154
  16. package/optimizer-rust/src/main.rs +0 -1659
  17. package/src/Board.ts +0 -527
  18. package/src/Book.ts +0 -83
  19. package/src/GameConfig.ts +0 -148
  20. package/src/GameMode.ts +0 -86
  21. package/src/GameState.ts +0 -272
  22. package/src/GameSymbol.ts +0 -61
  23. package/src/ReelGenerator.ts +0 -589
  24. package/src/ResultSet.ts +0 -207
  25. package/src/Simulation.ts +0 -625
  26. package/src/SlotGame.ts +0 -117
  27. package/src/Wallet.ts +0 -203
  28. package/src/WinType.ts +0 -102
  29. package/src/analysis/index.ts +0 -198
  30. package/src/analysis/utils.ts +0 -128
  31. package/src/optimizer/OptimizationConditions.ts +0 -99
  32. package/src/optimizer/OptimizationParameters.ts +0 -46
  33. package/src/optimizer/OptimizationScaling.ts +0 -18
  34. package/src/optimizer/index.ts +0 -142
  35. package/src/utils/math-config.ts +0 -109
  36. package/src/utils/setup-file.ts +0 -36
  37. package/src/utils/zstd.ts +0 -28
  38. package/src/winTypes/ClusterWinType.ts +0 -3
  39. package/src/winTypes/LinesWinType.ts +0 -208
  40. package/src/winTypes/ManywaysWinType.ts +0 -3
  41. package/tsconfig.json +0 -19
  42. 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>
@@ -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
- }