@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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.mts +7 -14
  3. package/dist/index.d.ts +7 -14
  4. package/dist/index.js +68 -90
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +68 -90
  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/dist/lib/zstd.exe +0 -0
  13. package/index.ts +0 -205
  14. package/lib/zstd.exe +0 -0
  15. package/optimizer-rust/Cargo.toml +0 -19
  16. package/optimizer-rust/src/exes.rs +0 -154
  17. package/optimizer-rust/src/main.rs +0 -1659
  18. package/src/Board.ts +0 -527
  19. package/src/Book.ts +0 -83
  20. package/src/GameConfig.ts +0 -148
  21. package/src/GameMode.ts +0 -86
  22. package/src/GameState.ts +0 -272
  23. package/src/GameSymbol.ts +0 -61
  24. package/src/ReelGenerator.ts +0 -589
  25. package/src/ResultSet.ts +0 -207
  26. package/src/Simulation.ts +0 -625
  27. package/src/SlotGame.ts +0 -117
  28. package/src/Wallet.ts +0 -203
  29. package/src/WinType.ts +0 -102
  30. package/src/analysis/index.ts +0 -198
  31. package/src/analysis/utils.ts +0 -128
  32. package/src/optimizer/OptimizationConditions.ts +0 -99
  33. package/src/optimizer/OptimizationParameters.ts +0 -46
  34. package/src/optimizer/OptimizationScaling.ts +0 -18
  35. package/src/optimizer/index.ts +0 -142
  36. package/src/utils/math-config.ts +0 -109
  37. package/src/utils/setup-file.ts +0 -36
  38. package/src/utils/zstd.ts +0 -28
  39. package/src/winTypes/ClusterWinType.ts +0 -3
  40. package/src/winTypes/LinesWinType.ts +0 -208
  41. package/src/winTypes/ManywaysWinType.ts +0 -3
  42. package/tsconfig.json +0 -19
  43. package/utils.ts +0 -270
package/src/GameConfig.ts DELETED
@@ -1,148 +0,0 @@
1
- import { AnyGameModes, AnySymbols, AnyUserData, CommonGameOptions, GameHooks } from "../index"
2
- import { GameMode, GameModeName } from "./GameMode"
3
- import { GameSymbol } from "./GameSymbol"
4
-
5
- /**
6
- * Static configuration for a slot game.\
7
- * This shouldn't change during gameplay.
8
- */
9
- export class GameConfig<
10
- TGameModes extends AnyGameModes = AnyGameModes,
11
- TSymbols extends AnySymbols = AnySymbols,
12
- TUserState extends AnyUserData = AnyUserData,
13
- > {
14
- readonly config: {
15
- readonly id: string
16
- readonly name: string
17
- readonly providerNumber: number
18
- readonly gameModes: Record<GameModeName, GameMode>
19
- readonly symbols: Map<TSymbols[number]["id"], TSymbols[number]>
20
- readonly padSymbols?: number
21
- readonly scatterToFreespins: Record<string, Record<number, number>>
22
- readonly anticipationTriggers: Record<SpinType, number>
23
- readonly maxWinX: number
24
- readonly outputDir: string
25
- readonly hooks: GameHooks<TGameModes, TSymbols, TUserState>
26
- readonly userState?: TUserState
27
- }
28
-
29
- constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>) {
30
- this.config = {
31
- id: opts.id,
32
- name: opts.name,
33
- providerNumber: opts.providerNumber,
34
- gameModes: opts.gameModes,
35
- symbols: new Map<string, GameSymbol>(),
36
- padSymbols: opts.padSymbols || 0,
37
- scatterToFreespins: opts.scatterToFreespins,
38
- anticipationTriggers: {
39
- [GameConfig.SPIN_TYPE.BASE_GAME]: getAnticipationTrigger(
40
- GameConfig.SPIN_TYPE.BASE_GAME,
41
- ),
42
- [GameConfig.SPIN_TYPE.FREE_SPINS]: getAnticipationTrigger(
43
- GameConfig.SPIN_TYPE.FREE_SPINS,
44
- ),
45
- },
46
- maxWinX: opts.maxWinX,
47
- hooks: opts.hooks,
48
- userState: opts.userState,
49
- outputDir: "__build__",
50
- }
51
-
52
- for (const symbol of opts.symbols) {
53
- if (!this.config.symbols.has(symbol.id)) {
54
- this.config.symbols.set(symbol.id, symbol)
55
- } else {
56
- console.warn(
57
- `Symbol with id "${symbol.id}" already exists in the game config. Skipping duplicate. This is probably not intentional.`,
58
- )
59
- }
60
- }
61
-
62
- function getAnticipationTrigger(spinType: string) {
63
- return Math.min(...Object.keys(opts.scatterToFreespins[spinType]!).map(Number)) - 1
64
- }
65
- }
66
-
67
- /**
68
- * Generates reelset CSV files for all game modes.
69
- */
70
- generateReelsetFiles() {
71
- for (const mode of Object.values(this.config.gameModes)) {
72
- if (mode.reelSets && mode.reelSets.length > 0) {
73
- for (const reelGenerator of Object.values(mode.reelSets)) {
74
- reelGenerator.associatedGameModeName = mode.name
75
- reelGenerator.outputDir = this.config.outputDir
76
- reelGenerator.generateReels(this)
77
- }
78
- } else {
79
- throw new Error(
80
- `Game mode "${mode.name}" has no reel sets defined. Cannot generate reelset files.`,
81
- )
82
- }
83
- }
84
- }
85
-
86
- /**
87
- * Retrieves a reel set by its ID within a specific game mode.
88
- */
89
- getReelsetById(gameMode: string, id: string) {
90
- const reelSet = this.config.gameModes[gameMode]!.reelSets.find((rs) => rs.id === id)
91
- if (!reelSet) {
92
- throw new Error(
93
- `Reel set with id "${id}" not found in game mode "${gameMode}". Available reel sets: ${this.config.gameModes[
94
- gameMode
95
- ]!.reelSets.map((rs) => rs.id).join(", ")}`,
96
- )
97
- }
98
- return reelSet
99
- }
100
-
101
- /**
102
- * Retrieves the number of free spins awarded for a given spin type and scatter count.
103
- */
104
- getFreeSpinsForScatters(spinType: SpinType, scatterCount: number) {
105
- const freespinsConfig = this.config.scatterToFreespins[spinType]
106
- if (!freespinsConfig) {
107
- throw new Error(
108
- `No free spins configuration found for spin type "${spinType}". Please check your game configuration.`,
109
- )
110
- }
111
- return freespinsConfig[scatterCount] || 0
112
- }
113
-
114
- /**
115
- * Retrieves a result set by its criteria within a specific game mode.
116
- */
117
- getGameModeCriteria(mode: string, criteria: string) {
118
- const gameMode = this.config.gameModes[mode]
119
- if (!gameMode) {
120
- throw new Error(`Game mode "${mode}" not found in game config.`)
121
- }
122
- const resultSet = gameMode.resultSets.find((rs) => rs.criteria === criteria)
123
- if (!resultSet) {
124
- throw new Error(
125
- `Criteria "${criteria}" not found in game mode "${mode}". Available criteria: ${gameMode.resultSets
126
- .map((rs) => rs.criteria)
127
- .join(", ")}`,
128
- )
129
- }
130
- return resultSet
131
- }
132
-
133
- /**
134
- * Returns all configured symbols as an array.
135
- */
136
- getSymbolArray() {
137
- return Array.from(this.config.symbols).map(([n, v]) => v)
138
- }
139
-
140
- static SPIN_TYPE = {
141
- BASE_GAME: "basegame",
142
- FREE_SPINS: "freespins",
143
- } as const
144
- }
145
-
146
- export type SpinType = (typeof GameConfig.SPIN_TYPE)[keyof typeof GameConfig.SPIN_TYPE]
147
-
148
- export type AnyGameConfig = GameConfig<any, any, any>
package/src/GameMode.ts DELETED
@@ -1,86 +0,0 @@
1
- import { type ReelGenerator } from "./ReelGenerator"
2
- import { type ResultSet } from "./ResultSet"
3
-
4
- export class GameMode {
5
- name: GameModeName
6
- reelsAmount: number
7
- symbolsPerReel: number[]
8
- cost: number
9
- rtp: number
10
- reelSets: ReelGenerator[]
11
- resultSets: ResultSet<any>[]
12
- isBonusBuy: boolean
13
-
14
- constructor(opts: GameModeOpts) {
15
- this.name = opts.name
16
- this.reelsAmount = opts.reelsAmount
17
- this.symbolsPerReel = opts.symbolsPerReel
18
- this.cost = opts.cost
19
- this.rtp = opts.rtp
20
- this.reelSets = opts.reelSets
21
- this.resultSets = opts.resultSets
22
- this.isBonusBuy = opts.isBonusBuy
23
-
24
- if (this.symbolsPerReel.length !== this.reelsAmount) {
25
- throw new Error(
26
- `symbolsPerReel length (${this.symbolsPerReel.length}) must match reelsAmount (${this.reelsAmount}).`,
27
- )
28
- }
29
-
30
- if (this.resultSets.length == 0) {
31
- throw new Error("GameMode must have at least one ResultSet defined.")
32
- }
33
- }
34
- }
35
-
36
- export interface GameModeOpts {
37
- /**
38
- * Name of the game mode.
39
- */
40
- name: GameModeName
41
- /**
42
- * Number of reels the board has.
43
- */
44
- reelsAmount: number
45
- /**
46
- * How many symbols each reel has. Array length must match `reelsAmount`.\
47
- * The number at an array index represents the number of symbols on that reel.
48
- */
49
- symbolsPerReel: number[]
50
- /**
51
- * Cost of the game mode, multiplied by the base bet.
52
- */
53
- cost: number
54
- /**
55
- * The target RTP of the game.
56
- */
57
- rtp: number
58
- /**
59
- * Defines and generates all reels for the game.\
60
- * Which reels are used in a spin is determined by the ResultSet of the current game mode.
61
- *
62
- * It is common to have one reel set for the base game and another for free spins.\
63
- * Each `ResultSet` can then set the weights of these reel sets to control which\
64
- * reel set is used for a specific criteria.
65
- *
66
- * The generator can be adjusted to match the reels to your games needs.
67
- */
68
- reelSets: ReelGenerator[]
69
- /**
70
- * A ResultSet defines how often a specific outcome should be generated.\
71
- * For example, a ResultSet can be used to force a specific ratio of max wins\
72
- * in the simulations to ensure there are different frontend representations.
73
- */
74
- resultSets: ResultSet<any>[]
75
- /**
76
- * Whether this game mode is a bonus buy.
77
- */
78
- isBonusBuy: boolean
79
- }
80
-
81
- export type GameModeName =
82
- | "base"
83
- | "base-extra-chance-2x"
84
- | "base-extra-chance-10x"
85
- | "bonus"
86
- | string
package/src/GameState.ts DELETED
@@ -1,272 +0,0 @@
1
- import { GameConfig, SpinType } from "./GameConfig"
2
- import { Wallet } from "./Wallet"
3
- import { Book } from "./Book"
4
- import { ResultSet } from "./ResultSet"
5
- import { RandomNumberGenerator } from "../utils"
6
- import { AnyGameModes, AnySymbols, AnyUserData, CommonGameOptions } from "../index"
7
-
8
- /**
9
- * The GameState manages the current state of the game.
10
- */
11
- export class GameState<
12
- TGameModes extends AnyGameModes,
13
- TSymbols extends AnySymbols,
14
- TUserState extends AnyUserData,
15
- > extends GameConfig<TGameModes, TSymbols, TUserState> {
16
- state: {
17
- currentSimulationId: number
18
- /**
19
- * e.g. "base", "freespins", etc. (depending on the game config)
20
- */
21
- currentGameMode: string
22
- /**
23
- * Spin type constant as defined in `GameConfig.SPIN_TYPE`
24
- */
25
- currentSpinType: SpinType
26
- /**
27
- * The current ResultSet for the active simulation run.
28
- */
29
- currentResultSet: ResultSet<any>
30
- /**
31
- * Whether the criteria in the ResultSet for the current simulation has been met.
32
- */
33
- isCriteriaMet: boolean
34
- /**
35
- * Number of freespins remaining in the current freespin round.
36
- */
37
- currentFreespinAmount: number
38
- /**
39
- * Total amount of freespins awarded during the active simulation.
40
- */
41
- totalFreespinAmount: number
42
- /**
43
- * A library of all completed books, indexed by their ID.
44
- */
45
- library: Map<string, Book>
46
- /**
47
- * The current book being recorded.
48
- */
49
- book: Book
50
- /**
51
- * Seeded random number generator instance for the current simulation.
52
- */
53
- rng: RandomNumberGenerator
54
- /**
55
- * Custom user data that can be used in game flow logic.
56
- */
57
- userData: TUserState
58
- /**
59
- * Whether a max win has been triggered during the active simulation.
60
- */
61
- triggeredMaxWin: boolean
62
- /**
63
- * Whether freespins have been triggered during the active simulation.
64
- */
65
- triggeredFreespins: boolean
66
- }
67
-
68
- /**
69
- * The wallet stores win data for the current and all simulations, respectively.
70
- */
71
- wallet: Wallet
72
-
73
- /**
74
- * Recorder for statistical analysis (e.g. symbol occurrences, etc.).
75
- */
76
- private recorder: {
77
- pendingRecords: PendingRecord[]
78
- readonly records: RecordItem[]
79
- }
80
-
81
- constructor(opts: CommonGameOptions<TGameModes, TSymbols, TUserState>) {
82
- super(opts)
83
-
84
- this.state = {
85
- currentSpinType: GameConfig.SPIN_TYPE.BASE_GAME,
86
- library: new Map(),
87
- book: new Book({ id: 0 }),
88
- currentGameMode: "N/A",
89
- currentSimulationId: 0,
90
- isCriteriaMet: false,
91
- currentFreespinAmount: 0,
92
- totalFreespinAmount: 0,
93
- rng: new RandomNumberGenerator(),
94
- userData: opts.userState || ({} as TUserState),
95
- triggeredMaxWin: false,
96
- triggeredFreespins: false,
97
- // This is a placeholder ResultSet to avoid null checks elsewhere.
98
- currentResultSet: new ResultSet({
99
- criteria: "N/A",
100
- quota: 0,
101
- reelWeights: {
102
- [GameConfig.SPIN_TYPE.BASE_GAME]: {},
103
- [GameConfig.SPIN_TYPE.FREE_SPINS]: {},
104
- },
105
- }),
106
- }
107
-
108
- this.wallet = new Wallet()
109
-
110
- this.recorder = {
111
- pendingRecords: [],
112
- records: [],
113
- }
114
- }
115
-
116
- /**
117
- * Gets the configuration for the current game mode.
118
- */
119
- getCurrentGameMode() {
120
- return this.config.gameModes[this.state.currentGameMode]!
121
- }
122
-
123
- resetState() {
124
- this.state.rng.setSeedIfDifferent(this.state.currentSimulationId)
125
- this.state.book = new Book({ id: this.state.currentSimulationId })
126
- this.state.currentSpinType = GameConfig.SPIN_TYPE.BASE_GAME
127
- this.state.currentFreespinAmount = 0
128
- this.state.totalFreespinAmount = 0
129
- this.state.triggeredMaxWin = false
130
- this.state.triggeredFreespins = false
131
- this.wallet.resetCurrentWin()
132
- this.clearPendingRecords()
133
- this.state.userData = this.config.userState || ({} as TUserState)
134
- }
135
-
136
- /**
137
- * Checks if a max win is reached by comparing `wallet.currentWin` to `config.maxWin`.
138
- *
139
- * Should be called after `wallet.confirmSpinWin()`.
140
- */
141
- isMaxWinTriggered() {}
142
-
143
- /**
144
- * Empties the list of pending records in the recorder.
145
- */
146
- clearPendingRecords() {
147
- this.recorder.pendingRecords = []
148
- }
149
-
150
- /**
151
- * Confirms all pending records and adds them to the main records list.
152
- */
153
- confirmRecords() {
154
- for (const pendingRecord of this.recorder.pendingRecords) {
155
- const search = Object.entries(pendingRecord.properties)
156
- .map(([name, value]) => ({ name, value }))
157
- .sort((a, b) => a.name.localeCompare(b.name))
158
-
159
- let record = this.recorder.records.find((r) => {
160
- if (r.search.length !== search.length) return false
161
- for (let i = 0; i < r.search.length; i++) {
162
- if (r.search[i]!.name !== search[i]!.name) return false
163
- if (r.search[i]!.value !== search[i]!.value) return false
164
- }
165
- return true
166
- })
167
- if (!record) {
168
- record = {
169
- search,
170
- timesTriggered: 0,
171
- bookIds: [],
172
- }
173
- this.recorder.records.push(record)
174
- }
175
- record.timesTriggered++
176
- if (!record.bookIds.includes(pendingRecord.bookId)) {
177
- record.bookIds.push(pendingRecord.bookId)
178
- }
179
- }
180
-
181
- this.clearPendingRecords()
182
- }
183
-
184
- /**
185
- * Record data for statistical analysis.
186
- */
187
- record(data: Record<string, string | number | boolean>) {
188
- this.recorder.pendingRecords.push({
189
- bookId: this.state.currentSimulationId,
190
- properties: Object.fromEntries(
191
- Object.entries(data).map(([k, v]) => [k, String(v)]),
192
- ),
193
- })
194
- }
195
-
196
- /**
197
- * Records a symbol occurrence for statistical analysis.
198
- *
199
- * Calls `this.record()` with the provided data.
200
- */
201
- recordSymbolOccurrence(data: {
202
- kind: number
203
- symbolId: string
204
- spinType: SpinType
205
- [key: string]: any
206
- }) {
207
- this.record(data)
208
- }
209
-
210
- /**
211
- * Gets all confirmed records.
212
- */
213
- getRecords() {
214
- return this.recorder.records
215
- }
216
-
217
- /**
218
- * Moves the current book to the library and resets the current book.
219
- */
220
- moveBookToLibrary() {
221
- this.state.library.set(this.state.book.id.toString(), this.state.book)
222
- this.state.book = new Book({ id: 0 })
223
- }
224
-
225
- /**
226
- * Increases the freespin count by the specified amount.
227
- *
228
- * Also sets `state.triggeredFreespins` to true.
229
- */
230
- awardFreespins(amount: number) {
231
- this.state.currentFreespinAmount += amount
232
- this.state.totalFreespinAmount += amount
233
- this.state.triggeredFreespins = true
234
- }
235
-
236
- /**
237
- * Ensures the requested number of scatters is valid based on the game configuration.\
238
- * Returns a valid number of scatters.
239
- */
240
- verifyScatterCount(numScatters: number) {
241
- const scatterCounts = this.config.scatterToFreespins[this.state.currentSpinType]
242
- if (!scatterCounts) {
243
- throw new Error(
244
- `No scatter counts defined for spin type "${this.state.currentSpinType}". Please check your game configuration.`,
245
- )
246
- }
247
- const validCounts = Object.keys(scatterCounts).map((key) => parseInt(key, 10))
248
- if (validCounts.length === 0) {
249
- throw new Error(
250
- `No scatter counts defined for spin type "${this.state.currentSpinType}". Please check your game configuration.`,
251
- )
252
- }
253
- if (numScatters < Math.min(...validCounts)) {
254
- return Math.min(...validCounts)
255
- }
256
- if (numScatters > Math.max(...validCounts)) {
257
- return Math.max(...validCounts)
258
- }
259
- return numScatters
260
- }
261
- }
262
-
263
- interface PendingRecord {
264
- bookId: number
265
- properties: Record<string, string>
266
- }
267
-
268
- export interface RecordItem {
269
- search: Array<{ name: string; value: string }>
270
- timesTriggered: number
271
- bookIds: number[]
272
- }
package/src/GameSymbol.ts DELETED
@@ -1,61 +0,0 @@
1
- export class GameSymbol {
2
- id: string
3
- pays?: Record<number, number>
4
- properties: Map<string, any>
5
-
6
- constructor(opts: GameSymbolOpts) {
7
- this.id = opts.id
8
- this.pays = opts.pays
9
- this.properties = new Map<string, any>(Object.entries(opts.properties || {}))
10
-
11
- if (this.pays && Object.keys(this.pays).length === 0) {
12
- throw new Error(`GameSymbol "${this.id}" must have pays defined.`)
13
- }
14
- }
15
-
16
- /**
17
- * Compares this symbol to another symbol or a set of properties.
18
- */
19
- compare(symbolOrProperties?: GameSymbol | Record<string, any>) {
20
- if (!symbolOrProperties) {
21
- console.warn("No symbol or properties provided for comparison.")
22
- return false
23
- }
24
- if (symbolOrProperties instanceof GameSymbol) {
25
- return this.id === symbolOrProperties.id
26
- } else {
27
- for (const [key, value] of Object.entries(symbolOrProperties)) {
28
- if (!this.properties.has(key) || this.properties.get(key) !== value) {
29
- return false
30
- }
31
- }
32
- return true
33
- }
34
- }
35
- }
36
-
37
- export interface GameSymbolOpts {
38
- /**
39
- * Unique identifier for the symbol, e.g. "W", "H1", "L5", etc.
40
- */
41
- id: string
42
- /**
43
- * Paytable for the symbol, where the key is the number of symbols and the value is the payout multiplier.
44
- */
45
- pays?: Record<number, number>
46
- /**
47
- * Additional properties for the symbol, e.g. `multiplier` or `isWild`.
48
- *
49
- * Properties can help identify special symbols.
50
- *
51
- * @example
52
- * If your game has a "normal" scatter and a "super" scatter, you can define them like this:
53
- *
54
- * ```ts
55
- * properties: {
56
- * isScatter: true,
57
- * }
58
- * ```
59
- */
60
- properties?: Record<string, any>
61
- }