@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
@@ -1,208 +0,0 @@
1
- import { GameSymbol } from "../GameSymbol"
2
- import { AnySimulationContext } from "../Simulation"
3
- import { WinCombination, WinType, WinTypeOpts } from "../WinType"
4
-
5
- export class LinesWinType extends WinType {
6
- protected lines: Record<number, number[]>
7
- declare protected winCombinations: LineWinCombination[]
8
- declare context: (ctx: AnySimulationContext) => LinesWinType
9
- declare getWins: () => {
10
- payout: number
11
- winCombinations: LineWinCombination[]
12
- }
13
-
14
- constructor(opts: LinesWinTypeOpts) {
15
- super(opts)
16
- this.lines = opts.lines
17
-
18
- if (Object.keys(this.lines).length === 0) {
19
- throw new Error("LinesWinType must have at least one line defined.")
20
- }
21
- }
22
-
23
- private validateConfig() {
24
- const reelsAmount = this.ctx.getCurrentGameMode().reelsAmount
25
- const symsPerReel = this.ctx.getCurrentGameMode().symbolsPerReel
26
-
27
- for (const [lineNum, positions] of Object.entries(this.lines)) {
28
- if (positions.length !== reelsAmount) {
29
- throw new Error(
30
- `Line ${lineNum} has ${positions.length} positions, but the current game mode has ${reelsAmount} reels.`,
31
- )
32
- }
33
- for (let i = 0; i < positions.length; i++) {
34
- if (positions[i]! < 0 || positions[i]! >= symsPerReel[i]!) {
35
- throw new Error(
36
- `Line ${lineNum} has an invalid position ${positions[i]} on reel ${i}. Valid range is 0 to ${
37
- symsPerReel[i]! - 1
38
- }.`,
39
- )
40
- }
41
- }
42
- }
43
-
44
- const firstLine = Math.min(...Object.keys(this.lines).map(Number))
45
- if (firstLine !== 1) {
46
- throw new Error(
47
- `Lines must start from 1. Found line ${firstLine} as the first line.`,
48
- )
49
- }
50
- }
51
-
52
- private isWild(symbol: GameSymbol) {
53
- return !!this.wildSymbol && symbol.compare(this.wildSymbol)
54
- }
55
-
56
- evaluateWins() {
57
- this.ensureContext()
58
- this.validateConfig()
59
-
60
- const lineWins: LineWinCombination[] = []
61
- let payout = 0
62
-
63
- const reels = this.ctx.board.reels
64
- const reelsAmount = this.ctx.getCurrentGameMode().reelsAmount
65
-
66
- for (const [lineNumStr, lineDef] of Object.entries(this.lines)) {
67
- const lineNum = Number(lineNumStr)
68
-
69
- let baseSymbol: GameSymbol | null = null
70
- let leadingWilds = 0
71
- const chain: GameSymbol[] = []
72
- const details: LineWinCombination["symbols"] = []
73
-
74
- for (let ridx = 0; ridx < reelsAmount; ridx++) {
75
- const rowIdx = lineDef[ridx]!
76
- const sym = reels[ridx]![rowIdx]
77
- if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.")
78
-
79
- const wild = this.isWild(sym)
80
-
81
- if (ridx === 0) {
82
- chain.push(sym)
83
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild })
84
- if (wild) leadingWilds++
85
- else baseSymbol = sym
86
- continue
87
- }
88
-
89
- if (wild) {
90
- chain.push(sym)
91
- details.push({
92
- reelIndex: ridx,
93
- posIndex: rowIdx,
94
- symbol: sym,
95
- isWild: true,
96
- substitutedFor: baseSymbol || undefined,
97
- })
98
- continue
99
- }
100
-
101
- if (!baseSymbol) {
102
- baseSymbol = sym
103
- chain.push(sym)
104
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false })
105
- continue
106
- }
107
-
108
- if (sym.id === baseSymbol.id) {
109
- chain.push(sym)
110
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false })
111
- continue
112
- }
113
-
114
- break
115
- }
116
-
117
- if (chain.length === 0) continue
118
-
119
- const allWild = chain.every((s) => this.isWild(s))
120
- const wildRepresentative =
121
- this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null
122
-
123
- const len = chain.length
124
- let bestPayout = 0
125
- let bestType: LineWinCombination["winType"] | null = null
126
- let payingSymbol: GameSymbol | null = null
127
-
128
- if (baseSymbol?.pays && baseSymbol.pays[len]) {
129
- bestPayout = baseSymbol.pays[len]!
130
- bestType = "substituted"
131
- payingSymbol = baseSymbol
132
- }
133
-
134
- if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
135
- const wildPay = wildRepresentative.pays[len]!
136
- if (wildPay > bestPayout) {
137
- bestPayout = wildPay
138
- bestType = "pure-wild"
139
- payingSymbol = wildRepresentative
140
- }
141
- }
142
-
143
- if (!bestPayout || !bestType || !payingSymbol) continue
144
-
145
- const minLen = payingSymbol.pays
146
- ? Math.min(...Object.keys(payingSymbol.pays).map(Number))
147
- : Infinity
148
-
149
- if (len < minLen) continue
150
-
151
- const wildCount = details.filter((d) => d.isWild).length
152
- const nonWildCount = len - wildCount
153
-
154
- lineWins.push({
155
- lineNumber: lineNum,
156
- kind: len,
157
- payout: bestPayout,
158
- symbol: payingSymbol,
159
- winType: bestType,
160
- substitutedBaseSymbol: bestType === "pure-wild" ? null : baseSymbol,
161
- symbols: details,
162
- stats: { wildCount, nonWildCount, leadingWilds },
163
- })
164
- payout += bestPayout
165
- }
166
-
167
- for (const win of lineWins) {
168
- this.ctx.recordSymbolOccurrence({
169
- kind: win.kind,
170
- symbolId: win.symbol.id,
171
- spinType: this.ctx.state.currentSpinType,
172
- })
173
- }
174
-
175
- this.payout = payout
176
- this.winCombinations = lineWins
177
-
178
- return this
179
- }
180
- }
181
-
182
- interface LinesWinTypeOpts extends WinTypeOpts {
183
- /**
184
- * Defines the paylines for the slot game.
185
- *
186
- * @example
187
- * ```ts
188
- * lines: {
189
- * 1: [0, 0, 0, 0, 0],
190
- * 2: [1, 1, 1, 1, 1],
191
- * 3: [2, 2, 2, 2, 2],
192
- * }
193
- * ```
194
- */
195
- lines: Record<number, number[]>
196
- }
197
-
198
- export interface LineWinCombination extends WinCombination {
199
- lineNumber: number
200
- symbol: GameSymbol
201
- winType: "pure-wild" | "substituted"
202
- substitutedBaseSymbol: GameSymbol | null
203
- stats: {
204
- wildCount: number
205
- nonWildCount: number
206
- leadingWilds: number
207
- }
208
- }
@@ -1,3 +0,0 @@
1
- import { WinType } from "../WinType"
2
-
3
- export class ManywaysWinType extends WinType {}
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "compilerOptions": {
4
- "declaration": true,
5
- "declarationMap": true,
6
- "esModuleInterop": true,
7
- "incremental": false,
8
- "isolatedModules": true,
9
- "lib": ["es2022", "DOM", "DOM.Iterable"],
10
- "module": "NodeNext",
11
- "moduleDetection": "force",
12
- "moduleResolution": "NodeNext",
13
- "noUncheckedIndexedAccess": true,
14
- "resolveJsonModule": true,
15
- "skipLibCheck": true,
16
- "strict": true,
17
- "target": "ES2022"
18
- }
19
- }
package/utils.ts DELETED
@@ -1,270 +0,0 @@
1
- import fs from "fs"
2
- import { Board } from "./src/Board"
3
-
4
- export function weightedRandom<T extends Record<string, number>>(
5
- weights: T,
6
- rng: RandomNumberGenerator,
7
- ) {
8
- const totalWeight = Object.values(weights).reduce(
9
- (sum: number, weight) => sum + (weight as number),
10
- 0,
11
- )
12
- const randomValue = rng.randomFloat(0, 1) * totalWeight
13
-
14
- let cumulativeWeight = 0
15
- for (const [key, weight] of Object.entries(weights)) {
16
- cumulativeWeight += weight as number
17
- if (randomValue < cumulativeWeight) {
18
- return key
19
- }
20
- }
21
-
22
- throw new Error("No item selected in weighted random selection.")
23
- }
24
-
25
- export function randomItem<T>(array: T[], rng: RandomNumberGenerator) {
26
- if (array.length === 0) {
27
- throw new Error("Cannot select a random item from an empty array.")
28
- }
29
- const randomIndex = Math.floor(rng.randomFloat(0, 1) * array.length)
30
- return array[randomIndex]!
31
- }
32
-
33
- export function createDirIfNotExists(dirPath: string): void {
34
- if (!fs.existsSync(dirPath)) {
35
- fs.mkdirSync(dirPath, { recursive: true })
36
- }
37
- }
38
-
39
- export function shuffle<T>(array: T[], rng: RandomNumberGenerator): T[] {
40
- const newArray = [...array]
41
- let currentIndex = newArray.length,
42
- randomIndex
43
-
44
- while (currentIndex != 0) {
45
- randomIndex = Math.floor(rng.randomFloat(0, 1) * currentIndex)
46
- currentIndex--
47
- ;[newArray[currentIndex] as any, newArray[randomIndex] as any] = [
48
- newArray[randomIndex],
49
- newArray[currentIndex],
50
- ]
51
- }
52
-
53
- return newArray
54
- }
55
-
56
- export function writeJsonFile(filePath: string, data: object | any[]) {
57
- try {
58
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2), {
59
- encoding: "utf8",
60
- })
61
- } catch (error) {
62
- throw new Error(`Failed to write JSON file at ${filePath}: ${error}`)
63
- }
64
- }
65
-
66
- export function writeFile(filePath: string, data: string) {
67
- try {
68
- fs.writeFileSync(filePath, data, { encoding: "utf8" })
69
- } catch (error) {
70
- throw new Error(`Failed to write file at ${filePath}: ${error}`)
71
- }
72
- }
73
-
74
- export class RandomNumberGenerator {
75
- mIdum: number
76
- mIy: number
77
- mIv: Array<number>
78
- NTAB: number
79
- IA: number
80
- IM: number
81
- IQ: number
82
- IR: number
83
- NDIV: number
84
- AM: number
85
- RNMX: number
86
-
87
- protected _currentSeed: number = 0
88
-
89
- constructor() {
90
- this.mIdum = 0
91
- this.mIy = 0
92
- this.mIv = []
93
-
94
- this.NTAB = 32
95
- this.IA = 16807
96
- this.IM = 2147483647
97
- this.IQ = 127773
98
- this.IR = 2836
99
- this.NDIV = 1 + (this.IM - 1) / this.NTAB
100
- this.AM = 1.0 / this.IM
101
- this.RNMX = 1.0 - 1.2e-7
102
- }
103
-
104
- getCurrentSeed() {
105
- return this._currentSeed
106
- }
107
-
108
- protected setCurrentSeed(seed: number) {
109
- this._currentSeed = seed
110
- }
111
-
112
- setSeed(seed: number): void {
113
- this.mIdum = seed
114
- this.setCurrentSeed(seed)
115
-
116
- if (seed >= 0) {
117
- this.mIdum = -seed
118
- }
119
-
120
- this.mIy = 0
121
- }
122
-
123
- setSeedIfDifferent(seed: number) {
124
- if (this.getCurrentSeed() !== seed) {
125
- this.setSeed(seed)
126
- }
127
- }
128
-
129
- generateRandomNumber(): number {
130
- let k: number
131
- let j: number
132
-
133
- if (this.mIdum <= 0 || this.mIy === 0) {
134
- if (-this.mIdum < 1) {
135
- this.mIdum = 1
136
- } else {
137
- this.mIdum = -this.mIdum
138
- }
139
-
140
- for (j = this.NTAB + 7; j >= 0; j -= 1) {
141
- k = Math.floor(this.mIdum / this.IQ)
142
- this.mIdum = Math.floor(this.IA * (this.mIdum - k * this.IQ) - this.IR * k)
143
-
144
- if (this.mIdum < 0) {
145
- this.mIdum += this.IM
146
- }
147
-
148
- if (j < this.NTAB) {
149
- this.mIv[j] = this.mIdum
150
- }
151
- }
152
-
153
- ;[this.mIy as any] = this.mIv
154
- }
155
-
156
- k = Math.floor(this.mIdum / this.IQ)
157
- this.mIdum = Math.floor(this.IA * (this.mIdum - k * this.IQ) - this.IR * k)
158
-
159
- if (this.mIdum < 0) {
160
- this.mIdum += this.IM
161
- }
162
-
163
- j = Math.floor(this.mIy / this.NDIV)
164
-
165
- this.mIy = Math.floor(this.mIv[j] as any)
166
- this.mIv[j] = this.mIdum
167
-
168
- return this.mIy
169
- }
170
-
171
- randomFloat(low: number, high: number): number {
172
- let float: number = this.AM * this.generateRandomNumber()
173
-
174
- if (float > this.RNMX) {
175
- float = this.RNMX
176
- }
177
-
178
- return float * (high - low) + low
179
- }
180
- }
181
-
182
- /**
183
- * Creates a deep copy of an object or array.
184
- */
185
- export function copy<T>(obj: T): T {
186
- return JSON.parse(JSON.stringify(obj))
187
- }
188
-
189
- /**
190
- * Prints the board to the console in a readable format.
191
- */
192
- export function printBoard({ board, config }: Board<any, any, any>) {
193
- const fullBoard = board.reels.map((reel, ridx) => {
194
- if (config.padSymbols && config.padSymbols > 0) {
195
- return [...board.paddingTop[ridx]!, ...reel, ...board.paddingBottom[ridx]!]
196
- }
197
- return reel
198
- })
199
-
200
- const rows = Math.max(...fullBoard.map((reel) => reel.length))
201
- const cellWidth = 4 // inner width of symbol area
202
-
203
- const padSymbol = (sym: string) => {
204
- if (sym.length > cellWidth) sym = sym.slice(0, cellWidth)
205
- const left = Math.floor((cellWidth - sym.length) / 2)
206
- const right = cellWidth - sym.length - left
207
- return " ".repeat(left) + sym + " ".repeat(right)
208
- }
209
-
210
- const maxTop = Math.max(...board.paddingTop.map((p) => p?.length ?? 0))
211
- const maxBottom = Math.max(...board.paddingBottom.map((p) => p?.length ?? 0))
212
- const boardStart = maxTop
213
- const boardEnd = rows - maxBottom - 1
214
-
215
- const makeSeparator = () => {
216
- return fullBoard.map(() => `═${"═".repeat(cellWidth)}═ `).join("")
217
- }
218
-
219
- for (let row = 0; row < rows; row++) {
220
- if (row === boardStart) {
221
- console.log(makeSeparator()) // top border of board
222
- }
223
-
224
- let top = ""
225
- let mid = ""
226
- let bot = ""
227
- for (let col = 0; col < fullBoard.length; col++) {
228
- const sym = fullBoard[col]![row]?.id ?? " "
229
- const padded = padSymbol(sym)
230
- top += `┌${"─".repeat(cellWidth)}┐ `
231
- mid += `│${padded}│ `
232
- bot += `└${"─".repeat(cellWidth)}┘ `
233
- }
234
-
235
- console.log(top)
236
- console.log(mid)
237
- console.log(bot)
238
-
239
- if (row === boardEnd) {
240
- console.log(makeSeparator()) // bottom border of board
241
- }
242
- }
243
- }
244
-
245
- export function weightedAverage(dist: Record<number, number>) {
246
- const keys = Object.keys(dist).map(Number)
247
- const values = Object.values(dist)
248
-
249
- const totalWeight = round(values.reduce((a, b) => a + b, 0), 6)
250
- const weightedSum = keys.reduce((sum, key, i) => sum + key * values[i]!, 0)
251
-
252
- return weightedSum / totalWeight
253
- }
254
-
255
- export class JSONL {
256
- public static stringify(array: object[]): string {
257
- return array.map((object) => JSON.stringify(object)).join("\n")
258
- }
259
-
260
- public static parse<T>(jsonl: string): Array<T> {
261
- return jsonl
262
- .split("\n")
263
- .filter((s) => s !== "")
264
- .map((str) => JSON.parse(str))
265
- }
266
- }
267
-
268
- export function round(value: number, decimals: number) {
269
- return Number(Math.round(Number(value + "e" + decimals)) + "e-" + decimals)
270
- }