@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/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
|
-
}
|