@omnitronix/bonnys-fortune-game-engine 1.3.2 → 1.5.0
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 +25 -15
- package/dist/__tests__/base-game.test.js +255 -0
- package/dist/__tests__/base-game.test.js.map +1 -0
- package/dist/__tests__/bonnys-fortune-v1.game-engine.test.js +1 -1
- package/dist/__tests__/bonus-handlers.test.js +303 -0
- package/dist/__tests__/bonus-handlers.test.js.map +1 -0
- package/dist/__tests__/bonus-meters.test.js +508 -0
- package/dist/__tests__/bonus-meters.test.js.map +1 -0
- package/dist/__tests__/collect-feature-handler.test.js +386 -0
- package/dist/__tests__/collect-feature-handler.test.js.map +1 -0
- package/dist/__tests__/comprehensive.test.js +747 -0
- package/dist/__tests__/comprehensive.test.js.map +1 -0
- package/dist/__tests__/error-paths.test.js +373 -0
- package/dist/__tests__/error-paths.test.js.map +1 -0
- package/dist/__tests__/helpers/test-engine-factory.js +268 -0
- package/dist/__tests__/helpers/test-engine-factory.js.map +1 -0
- package/dist/__tests__/rng-gli19-compliance.test.js +292 -0
- package/dist/__tests__/rng-gli19-compliance.test.js.map +1 -0
- package/dist/__tests__/rng-security.test.js +383 -0
- package/dist/__tests__/rng-security.test.js.map +1 -0
- package/dist/__tests__/rtp-simulation.test.js +242 -0
- package/dist/__tests__/rtp-simulation.test.js.map +1 -0
- package/dist/__tests__/state-transitions.test.js +307 -0
- package/dist/__tests__/state-transitions.test.js.map +1 -0
- package/dist/__tests__/steering-fortune-handler.test.js +286 -0
- package/dist/__tests__/steering-fortune-handler.test.js.map +1 -0
- package/dist/__tests__/symbol-distribution.test.js +195 -0
- package/dist/__tests__/symbol-distribution.test.js.map +1 -0
- package/dist/__tests__/treasure-hunt-handler.test.js +295 -0
- package/dist/__tests__/treasure-hunt-handler.test.js.map +1 -0
- package/dist/__tests__/win-calculator.test.js +515 -0
- package/dist/__tests__/win-calculator.test.js.map +1 -0
- package/dist/bonnys-fortune-v1.game-engine.js +14 -25
- package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
- package/dist/config/game-logic-config/game-logic-config.js +0 -2
- package/dist/config/game-logic-config/game-logic-config.js.map +1 -1
- package/dist/domain/types/{cheat-trigger-bonus-command.js → debug-trigger-bonus-command.js} +1 -1
- package/dist/domain/types/debug-trigger-bonus-command.js.map +1 -0
- package/dist/domain/types/{cheat-trigger-bonus-request.dto.js → debug-trigger-bonus-request.dto.js} +1 -1
- package/dist/domain/types/debug-trigger-bonus-request.dto.js.map +1 -0
- package/dist/domain/types/{cheat-update-bonus-meter-progress-command.js → debug-update-bonus-meter-progress-command.js} +1 -1
- package/dist/domain/types/debug-update-bonus-meter-progress-command.js.map +1 -0
- package/dist/domain/types/{cheat-update-bonus-meter-progress-request.dto.js → debug-update-bonus-meter-progress-request.dto.js} +1 -1
- package/dist/domain/types/debug-update-bonus-meter-progress-request.dto.js.map +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logic/bonnys-fortune.game-logic.js +37 -17
- package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
- package/dist/logic/handlers/base-game.handler.js +1 -5
- package/dist/logic/handlers/base-game.handler.js.map +1 -1
- package/dist/rng/DummyRngClient.js +8 -1
- package/dist/rng/DummyRngClient.js.map +1 -1
- package/dist/rng/rng-client.factory.js.map +1 -1
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js +0 -9
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
- package/package.json +6 -6
- package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +0 -1
- package/dist/bonnys-fortune-v1.game-engine.d.ts +0 -36
- package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +0 -4
- package/dist/config/game-logic-config/game-logic-config-loader.d.ts +0 -3
- package/dist/config/game-logic-config/game-logic-config.d.ts +0 -2
- package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +0 -4
- package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +0 -4
- package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +0 -3
- package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +0 -5
- package/dist/config/reel-strips-config/reel.dto.d.ts +0 -5
- package/dist/domain/game-round.types.d.ts +0 -9
- package/dist/domain/mappers/reel-strips-config.mapper.d.ts +0 -4
- package/dist/domain/reel-strip-option.d.ts +0 -7
- package/dist/domain/reel-strips-config.d.ts +0 -9
- package/dist/domain/reel.d.ts +0 -8
- package/dist/domain/types/cheat-trigger-bonus-command.d.ts +0 -5
- package/dist/domain/types/cheat-trigger-bonus-command.js.map +0 -1
- package/dist/domain/types/cheat-trigger-bonus-request.dto.d.ts +0 -5
- package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +0 -1
- package/dist/domain/types/cheat-update-bonus-meter-progress-command.d.ts +0 -5
- package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +0 -1
- package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.d.ts +0 -5
- package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +0 -1
- package/dist/domain/types/game-spin-command.d.ts +0 -8
- package/dist/domain/types/game-spin-input.dto.d.ts +0 -8
- package/dist/domain/types/game-spin-output.dto.d.ts +0 -119
- package/dist/domain/types/game-symbols.response.dto.d.ts +0 -10
- package/dist/domain/types/reel-strip-option.dto.d.ts +0 -4
- package/dist/domain/types/reel-strips-config.dto.d.ts +0 -5
- package/dist/domain/types/reel.dto.d.ts +0 -5
- package/dist/domain/types/start-bonus-round-command.d.ts +0 -8
- package/dist/domain/types/start-bonus-round-input.dto.d.ts +0 -10
- package/dist/game-engine.interface.d.ts +0 -41
- package/dist/helpers/generate-hash.d.ts +0 -1
- package/dist/helpers/number-helper.d.ts +0 -23
- package/dist/helpers/optional-boolean-mapper.d.ts +0 -1
- package/dist/helpers/uuid-helper.d.ts +0 -3
- package/dist/helpers/validation-helper.d.ts +0 -14
- package/dist/index.d.ts +0 -2
- package/dist/logger/logger.d.ts +0 -22
- package/dist/logger/logger.js +0 -140
- package/dist/logger/logger.js.map +0 -1
- package/dist/logic/bonnys-fortune.game-logic.d.ts +0 -32
- package/dist/logic/bonnys-fortune.spin-generator.d.ts +0 -19
- package/dist/logic/bonnys-fortune.types.d.ts +0 -170
- package/dist/logic/bonnys-fortune.win-calculator.d.ts +0 -10
- package/dist/logic/game-logic-config.interface.d.ts +0 -180
- package/dist/logic/handlers/base-game.handler.d.ts +0 -15
- package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +0 -13
- package/dist/logic/handlers/free-spins-bonus.handler.d.ts +0 -11
- package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +0 -11
- package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +0 -15
- package/dist/rng/DummyRngClient.d.ts +0 -18
- package/dist/rng/rng-client.factory.d.ts +0 -7
- package/dist/rng/rng-client.interface.d.ts +0 -16
- package/dist/rng/rng-service.d.ts +0 -26
- package/dist/validation/abstract-game-logic-config.validator.d.ts +0 -4
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +0 -131
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +0 -5
- package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +0 -2
- package/dist/validation/game-logic-config-validation.service.d.ts +0 -8
- package/dist/validation/reel-strips-config-validation.service.d.ts +0 -5
- package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +0 -80
- package/src/bonnys-fortune-v1.game-engine.ts +0 -314
- package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +0 -27
- package/src/config/game-logic-config/game-logic-config-loader.ts +0 -3
- package/src/config/game-logic-config/game-logic-config.ts +0 -382
- package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +0 -43
- package/src/config/reel-strips-config/reel-strip-option.dto.ts +0 -13
- package/src/config/reel-strips-config/reel-strips-config-loader.ts +0 -9
- package/src/config/reel-strips-config/reel-strips-config.dto.ts +0 -16
- package/src/config/reel-strips-config/reel.dto.ts +0 -16
- package/src/config/reel-strips-config/reels-BASE.csv +0 -52
- package/src/config/reel-strips-config/reels-BONUS.csv +0 -52
- package/src/domain/game-round.types.ts +0 -10
- package/src/domain/mappers/reel-strips-config.mapper.ts +0 -16
- package/src/domain/reel-strip-option.ts +0 -15
- package/src/domain/reel-strips-config.ts +0 -21
- package/src/domain/reel.ts +0 -17
- package/src/domain/types/cheat-trigger-bonus-command.ts +0 -5
- package/src/domain/types/cheat-trigger-bonus-request.dto.ts +0 -5
- package/src/domain/types/cheat-update-bonus-meter-progress-command.ts +0 -6
- package/src/domain/types/cheat-update-bonus-meter-progress-request.dto.ts +0 -5
- package/src/domain/types/game-spin-command.ts +0 -8
- package/src/domain/types/game-spin-input.dto.ts +0 -8
- package/src/domain/types/game-spin-output.dto.ts +0 -142
- package/src/domain/types/game-symbols.response.dto.ts +0 -11
- package/src/domain/types/reel-strip-option.dto.ts +0 -13
- package/src/domain/types/reel-strips-config.dto.ts +0 -15
- package/src/domain/types/reel.dto.ts +0 -15
- package/src/domain/types/start-bonus-round-command.ts +0 -8
- package/src/domain/types/start-bonus-round-input.dto.ts +0 -10
- package/src/game-engine.interface.ts +0 -59
- package/src/helpers/generate-hash.ts +0 -5
- package/src/helpers/number-helper.ts +0 -41
- package/src/helpers/optional-boolean-mapper.ts +0 -5
- package/src/helpers/uuid-helper.ts +0 -7
- package/src/helpers/validation-helper.ts +0 -27
- package/src/index.ts +0 -3
- package/src/logger/logger.ts +0 -178
- package/src/logic/bonnys-fortune.game-logic.ts +0 -490
- package/src/logic/bonnys-fortune.spin-generator.ts +0 -277
- package/src/logic/bonnys-fortune.types.ts +0 -223
- package/src/logic/bonnys-fortune.win-calculator.ts +0 -210
- package/src/logic/game-logic-config.interface.ts +0 -176
- package/src/logic/handlers/base-game.handler.ts +0 -221
- package/src/logic/handlers/collect-feature-bonus.handler.ts +0 -301
- package/src/logic/handlers/free-spins-bonus.handler.ts +0 -119
- package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +0 -118
- package/src/logic/handlers/treasure-hunt-bonus.handler.ts +0 -232
- package/src/rng/DummyRngClient.ts +0 -108
- package/src/rng/rng-client.factory.ts +0 -27
- package/src/rng/rng-client.interface.ts +0 -38
- package/src/rng/rng-service.ts +0 -130
- package/src/validation/abstract-game-logic-config.validator.ts +0 -20
- package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +0 -379
- package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +0 -8
- package/src/validation/custom-decorators/IsNestedIntArray.ts +0 -29
- package/src/validation/game-logic-config-validation.service.ts +0 -28
- package/src/validation/reel-strips-config-validation.service.ts +0 -29
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
import { BonnysFortuneGameLogicConfigInterface } from './game-logic-config.interface';
|
|
2
|
-
import { Reel } from '../domain/reel';
|
|
3
|
-
import { ReelStripsConfig } from '../domain/reel-strips-config';
|
|
4
|
-
import { RngService } from '../rng/rng-service';
|
|
5
|
-
|
|
6
|
-
export class BonnysFortuneSpinGenerator {
|
|
7
|
-
constructor(private readonly rngService: RngService) {}
|
|
8
|
-
|
|
9
|
-
public async generateScreenWithLayout(
|
|
10
|
-
reelStripsConfig: ReelStripsConfig,
|
|
11
|
-
layout: { reels: number; rows: number },
|
|
12
|
-
optionWeights: number[],
|
|
13
|
-
commandId: string,
|
|
14
|
-
actionId: string,
|
|
15
|
-
): Promise<number[][]> {
|
|
16
|
-
const { reels, rows } = layout;
|
|
17
|
-
|
|
18
|
-
const selectedOptionIndex = await this.selectReelOption(
|
|
19
|
-
optionWeights,
|
|
20
|
-
commandId,
|
|
21
|
-
`${actionId}_reel_option_selection`,
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
return Promise.all(
|
|
25
|
-
Array.from({ length: reels }, async (_, reelIndex) =>
|
|
26
|
-
this.drawReelColumn(
|
|
27
|
-
reelStripsConfig.getReel(reelIndex),
|
|
28
|
-
rows,
|
|
29
|
-
selectedOptionIndex,
|
|
30
|
-
commandId,
|
|
31
|
-
actionId,
|
|
32
|
-
reelIndex,
|
|
33
|
-
),
|
|
34
|
-
),
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
public async generateBaseScreen(
|
|
39
|
-
config: BonnysFortuneGameLogicConfigInterface,
|
|
40
|
-
reelStripsConfig: ReelStripsConfig,
|
|
41
|
-
commandId: string,
|
|
42
|
-
actionId: string,
|
|
43
|
-
): Promise<number[][]> {
|
|
44
|
-
const { reels, rows } = config.baseGame.baseGameConfig.layout;
|
|
45
|
-
const optionWeights =
|
|
46
|
-
config.baseGame.baseGameConfig.reelsOptions.optionWeights;
|
|
47
|
-
|
|
48
|
-
const selectedOptionIndex = await this.selectReelOption(
|
|
49
|
-
optionWeights,
|
|
50
|
-
commandId,
|
|
51
|
-
`${actionId}_reel_option_selection`,
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
return Promise.all(
|
|
55
|
-
Array.from({ length: reels }, async (_, reelIndex) =>
|
|
56
|
-
this.drawReelColumn(
|
|
57
|
-
reelStripsConfig.getReel(reelIndex),
|
|
58
|
-
rows,
|
|
59
|
-
selectedOptionIndex,
|
|
60
|
-
commandId,
|
|
61
|
-
actionId,
|
|
62
|
-
reelIndex,
|
|
63
|
-
),
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private async selectReelOption(
|
|
69
|
-
optionWeights: number[],
|
|
70
|
-
commandId: string,
|
|
71
|
-
actionId: string,
|
|
72
|
-
): Promise<number> {
|
|
73
|
-
const total = optionWeights.reduce((acc, w) => acc + w, 0);
|
|
74
|
-
const result = await this.rngService.getSingleNumber(
|
|
75
|
-
0,
|
|
76
|
-
total - 1,
|
|
77
|
-
commandId,
|
|
78
|
-
actionId,
|
|
79
|
-
);
|
|
80
|
-
const rand = result.value;
|
|
81
|
-
let cumulative = 0;
|
|
82
|
-
for (let i = 0; i < optionWeights.length; i++) {
|
|
83
|
-
cumulative += optionWeights[i];
|
|
84
|
-
if (rand < cumulative) {
|
|
85
|
-
return i;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return optionWeights.length - 1;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private async drawReelColumn(
|
|
92
|
-
reel: Reel,
|
|
93
|
-
rowsCount: number,
|
|
94
|
-
optionIndex: number,
|
|
95
|
-
commandId: string,
|
|
96
|
-
actionId: string,
|
|
97
|
-
reelIndex: number,
|
|
98
|
-
): Promise<number[]> {
|
|
99
|
-
const stopActionId = `${actionId}_reel_${reelIndex}_stop`;
|
|
100
|
-
const option = reel.getOption(optionIndex);
|
|
101
|
-
|
|
102
|
-
const resultStop = await this.rngService.getSingleNumber(
|
|
103
|
-
0,
|
|
104
|
-
option.length - 1,
|
|
105
|
-
commandId,
|
|
106
|
-
stopActionId,
|
|
107
|
-
);
|
|
108
|
-
const stopPos = resultStop.value;
|
|
109
|
-
|
|
110
|
-
return Array.from({ length: rowsCount }, (_, row) =>
|
|
111
|
-
option.getSymbolAt(stopPos + row),
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
public async applyBonusSymbols(
|
|
116
|
-
screen: number[][],
|
|
117
|
-
config: BonnysFortuneGameLogicConfigInterface,
|
|
118
|
-
commandId: string,
|
|
119
|
-
actionId: string,
|
|
120
|
-
): Promise<number[][]> {
|
|
121
|
-
const screenCopy = screen.map((col) => [...col]);
|
|
122
|
-
const reelsCount = config.baseGame.baseGameConfig.layout.reels;
|
|
123
|
-
|
|
124
|
-
const hWeights = [
|
|
125
|
-
config.baseGame.paidHSymbolWeights.None,
|
|
126
|
-
config.baseGame.paidHSymbolWeights.H1,
|
|
127
|
-
config.baseGame.paidHSymbolWeights.H2,
|
|
128
|
-
config.baseGame.paidHSymbolWeights.H3,
|
|
129
|
-
];
|
|
130
|
-
|
|
131
|
-
const pickedHIndex = await this.getRandomWeightedIndex(
|
|
132
|
-
hWeights,
|
|
133
|
-
commandId,
|
|
134
|
-
`${actionId}_h_symbol_selection`,
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
if (pickedHIndex > 0) {
|
|
138
|
-
const hName = `H${pickedHIndex}` as 'H1' | 'H2' | 'H3';
|
|
139
|
-
const hId =
|
|
140
|
-
config.baseGame.symbolMultipliers.find((s) => s.name === hName)
|
|
141
|
-
?.symbolId ?? 0;
|
|
142
|
-
await this.placeHSymbols(
|
|
143
|
-
screenCopy,
|
|
144
|
-
reelsCount,
|
|
145
|
-
config.baseGame.paidHSymbolReelWeights,
|
|
146
|
-
hName,
|
|
147
|
-
hId,
|
|
148
|
-
commandId,
|
|
149
|
-
`${actionId}_h_symbols`,
|
|
150
|
-
);
|
|
151
|
-
} else {
|
|
152
|
-
await this.placeScatterSymbols(
|
|
153
|
-
screenCopy,
|
|
154
|
-
reelsCount,
|
|
155
|
-
config.baseGame.paidScatterReelWeights,
|
|
156
|
-
config.baseGame.symbolDefinitions.specialSymbols.SCATTER,
|
|
157
|
-
commandId,
|
|
158
|
-
`${actionId}_scatter_symbols`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return screenCopy;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private async placeHSymbols(
|
|
166
|
-
screen: number[][],
|
|
167
|
-
reelsCount: number,
|
|
168
|
-
reelWeights: Record<string, number[]>,
|
|
169
|
-
symbolName: 'H1' | 'H2' | 'H3',
|
|
170
|
-
symbolId: number,
|
|
171
|
-
commandId: string,
|
|
172
|
-
actionId: string,
|
|
173
|
-
): Promise<void> {
|
|
174
|
-
const result = await this.rngService.getSingleNumber(
|
|
175
|
-
0,
|
|
176
|
-
reelsCount - 1,
|
|
177
|
-
commandId,
|
|
178
|
-
`${actionId}_guaranteed_reel`,
|
|
179
|
-
);
|
|
180
|
-
const guaranteedReel = result.value;
|
|
181
|
-
await this.placeSymbolRandomlyOnReel(
|
|
182
|
-
screen,
|
|
183
|
-
guaranteedReel,
|
|
184
|
-
symbolId,
|
|
185
|
-
commandId,
|
|
186
|
-
`${actionId}_guaranteed_placement`,
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
for (let reel = 0; reel < reelsCount; reel++) {
|
|
190
|
-
if (reel === guaranteedReel) continue;
|
|
191
|
-
const weights = reelWeights[symbolName];
|
|
192
|
-
if (
|
|
193
|
-
weights &&
|
|
194
|
-
(await this.getRandomWeightedIndex(
|
|
195
|
-
weights,
|
|
196
|
-
commandId,
|
|
197
|
-
`${actionId}_reel_${reel}_weighted`,
|
|
198
|
-
)) === 0
|
|
199
|
-
) {
|
|
200
|
-
await this.placeSymbolRandomlyOnReel(
|
|
201
|
-
screen,
|
|
202
|
-
reel,
|
|
203
|
-
symbolId,
|
|
204
|
-
commandId,
|
|
205
|
-
`${actionId}_reel_${reel}_placement`,
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private async placeScatterSymbols(
|
|
212
|
-
screen: number[][],
|
|
213
|
-
reelsCount: number,
|
|
214
|
-
scatterWeights: Record<string, number[]>,
|
|
215
|
-
scatterId: number,
|
|
216
|
-
commandId: string,
|
|
217
|
-
actionId: string,
|
|
218
|
-
): Promise<void> {
|
|
219
|
-
for (let reel = 0; reel < reelsCount; reel++) {
|
|
220
|
-
const weights =
|
|
221
|
-
scatterWeights[`reel${reel + 1}` as keyof typeof scatterWeights];
|
|
222
|
-
if (
|
|
223
|
-
weights &&
|
|
224
|
-
(await this.getRandomWeightedIndex(
|
|
225
|
-
weights,
|
|
226
|
-
commandId,
|
|
227
|
-
`${actionId}_scatter_reel_${reel}`,
|
|
228
|
-
)) === 0
|
|
229
|
-
) {
|
|
230
|
-
await this.placeSymbolRandomlyOnReel(
|
|
231
|
-
screen,
|
|
232
|
-
reel,
|
|
233
|
-
scatterId,
|
|
234
|
-
commandId,
|
|
235
|
-
`${actionId}_scatter_placement_${reel}`,
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
private async placeSymbolRandomlyOnReel(
|
|
242
|
-
screen: number[][],
|
|
243
|
-
reel: number,
|
|
244
|
-
symbolId: number,
|
|
245
|
-
commandId: string,
|
|
246
|
-
actionId: string,
|
|
247
|
-
): Promise<void> {
|
|
248
|
-
const result = await this.rngService.getSingleNumber(
|
|
249
|
-
0,
|
|
250
|
-
screen[reel].length - 1,
|
|
251
|
-
commandId,
|
|
252
|
-
actionId,
|
|
253
|
-
);
|
|
254
|
-
screen[reel][result.value] = symbolId;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private async getRandomWeightedIndex(
|
|
258
|
-
weights: number[],
|
|
259
|
-
commandId: string,
|
|
260
|
-
actionId: string,
|
|
261
|
-
): Promise<number> {
|
|
262
|
-
const total = weights.reduce((acc, w) => acc + w, 0);
|
|
263
|
-
const result = await this.rngService.getSingleNumber(
|
|
264
|
-
0,
|
|
265
|
-
total - 1,
|
|
266
|
-
commandId,
|
|
267
|
-
actionId,
|
|
268
|
-
);
|
|
269
|
-
const rand = result.value;
|
|
270
|
-
let cumulative = 0;
|
|
271
|
-
for (let i = 0; i < weights.length; i++) {
|
|
272
|
-
cumulative += weights[i];
|
|
273
|
-
if (rand < cumulative) return i;
|
|
274
|
-
}
|
|
275
|
-
return weights.length - 1;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
export interface WinLine {
|
|
2
|
-
symbol: number;
|
|
3
|
-
consecutiveReels: number;
|
|
4
|
-
ways: number;
|
|
5
|
-
multiplier: number;
|
|
6
|
-
win: number;
|
|
7
|
-
positions: [number, number][];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface MeterState {
|
|
11
|
-
symbolName: string;
|
|
12
|
-
symbolId: number;
|
|
13
|
-
threshold: number;
|
|
14
|
-
progress: number;
|
|
15
|
-
level: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type MetersState = Record<string, MeterState>;
|
|
19
|
-
|
|
20
|
-
export interface BonnysFortunePublicState {
|
|
21
|
-
currentBetAmount: number;
|
|
22
|
-
bonusMeters: MetersState;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface BonnysFortunePrivateState {
|
|
26
|
-
screen?: number[][];
|
|
27
|
-
nextSpinType: SpinType;
|
|
28
|
-
pendingBonuses: BonusTrigger[];
|
|
29
|
-
activeBonus?: BonusTrigger;
|
|
30
|
-
bonusMetersByAmount: Record<number, MetersState>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface BonusTrigger {
|
|
34
|
-
bonusId: string;
|
|
35
|
-
bonusType: string;
|
|
36
|
-
betAmount: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface SpinResult {
|
|
40
|
-
spinType: string;
|
|
41
|
-
playerWinning: number;
|
|
42
|
-
nextSpinType: SpinType | string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface SpinResultResponse<T extends SpinResult> {
|
|
46
|
-
result: T;
|
|
47
|
-
publicState: BonnysFortunePublicState;
|
|
48
|
-
privateState: BonnysFortunePrivateState;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface BonnysFortuneBaseGameSpinResult extends SpinResult {
|
|
52
|
-
spinType: SpinType.BASE_GAME_SPIN;
|
|
53
|
-
screen: number[][];
|
|
54
|
-
winResult: {
|
|
55
|
-
totalWin: number;
|
|
56
|
-
winningLines: WinLine[];
|
|
57
|
-
};
|
|
58
|
-
triggeredBonus?: BonusTrigger;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface TreasureHuntSpinResult extends SpinResult {
|
|
62
|
-
spinType: SpinType;
|
|
63
|
-
selection: string;
|
|
64
|
-
multiplier: number;
|
|
65
|
-
multiplierCounts: Record<string, number>;
|
|
66
|
-
winningMultiplier: number | null;
|
|
67
|
-
winningCount: number;
|
|
68
|
-
totalMultiplier: number;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface FreeSpinResult {
|
|
72
|
-
spinType: SpinType.BONUS_GAME_2_SPIN;
|
|
73
|
-
screen: number[][];
|
|
74
|
-
winResult: {
|
|
75
|
-
totalWin: number;
|
|
76
|
-
winningLines: WinLine[];
|
|
77
|
-
};
|
|
78
|
-
playerWinning: number;
|
|
79
|
-
nextSpinType: SpinType;
|
|
80
|
-
multiplier: number;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface SteeringToTheFortuneResult {
|
|
84
|
-
spinType: SpinType;
|
|
85
|
-
playerWinning: number;
|
|
86
|
-
nextSpinType: SpinType;
|
|
87
|
-
prizeType: string;
|
|
88
|
-
prizeValue: number | string;
|
|
89
|
-
totalMultiplier: number;
|
|
90
|
-
triggeredBonus?: BonusTrigger;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export interface CollectFeatureSpinResult {
|
|
94
|
-
spinIndex: number;
|
|
95
|
-
spinType: SpinType;
|
|
96
|
-
symbols: string[];
|
|
97
|
-
slashed: boolean[];
|
|
98
|
-
currentValues: number;
|
|
99
|
-
currentMultipliers: number;
|
|
100
|
-
totalValues: number;
|
|
101
|
-
totalMultipliers: number;
|
|
102
|
-
playerWinning: number;
|
|
103
|
-
totalWin: number;
|
|
104
|
-
spinsLeft: number;
|
|
105
|
-
nextSpinType: SpinType;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export interface BonusRoundResult<TSpin extends SpinResult> {
|
|
109
|
-
results: SpinResultResponse<TSpin>[];
|
|
110
|
-
publicState: BonnysFortunePublicState;
|
|
111
|
-
privateState: BonnysFortunePrivateState;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface BonusRoundResultWithActiveBonus<TSpin extends SpinResult>
|
|
115
|
-
extends BonusRoundResult<TSpin> {
|
|
116
|
-
activeBonus: BonusTrigger;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export type BonnysFortuneGameLogicSpinResult =
|
|
120
|
-
| SpinResultResponse<BonnysFortuneBaseGameSpinResult>
|
|
121
|
-
| SpinResultResponse<FreeSpinResult>
|
|
122
|
-
| SpinResultResponse<TreasureHuntSpinResult>
|
|
123
|
-
| SpinResultResponse<SteeringToTheFortuneResult>
|
|
124
|
-
| SpinResultResponse<CollectFeatureSpinResult>;
|
|
125
|
-
|
|
126
|
-
export type BonnysFortuneBonusGameLogicSpinResult =
|
|
127
|
-
| BonusRoundResult<FreeSpinResult>
|
|
128
|
-
| BonusRoundResult<TreasureHuntSpinResult>
|
|
129
|
-
| BonusRoundResult<SteeringToTheFortuneResult>
|
|
130
|
-
| BonusRoundResult<CollectFeatureSpinResult>;
|
|
131
|
-
|
|
132
|
-
export type StartBonusRoundResult =
|
|
133
|
-
| BonusRoundResultWithActiveBonus<FreeSpinResult>
|
|
134
|
-
| BonusRoundResultWithActiveBonus<TreasureHuntSpinResult>
|
|
135
|
-
| BonusRoundResultWithActiveBonus<SteeringToTheFortuneResult>
|
|
136
|
-
| BonusRoundResultWithActiveBonus<CollectFeatureSpinResult>;
|
|
137
|
-
|
|
138
|
-
export enum SpinType {
|
|
139
|
-
BASE_GAME_SPIN = 'BASE_GAME_SPIN',
|
|
140
|
-
BONUS_GAME_1_SPIN = 'BONUS_GAME_1_SPIN',
|
|
141
|
-
BONUS_GAME_2_SPIN = 'BONUS_GAME_2_SPIN',
|
|
142
|
-
BONUS_GAME_3_SPIN = 'BONUS_GAME_3_SPIN',
|
|
143
|
-
COLLECT_FEATURE_SPIN = 'COLLECT_FEATURE_SPIN',
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export enum PrizeType {
|
|
147
|
-
MULTIPLIER = 'MULTIPLIER',
|
|
148
|
-
BONUS = 'BONUS',
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export interface ExtractedConfigData {
|
|
152
|
-
baseMin: number;
|
|
153
|
-
baseMax: number;
|
|
154
|
-
WILD: number;
|
|
155
|
-
reels: number;
|
|
156
|
-
rows: number;
|
|
157
|
-
minLength: number;
|
|
158
|
-
maxLength: number;
|
|
159
|
-
multipliers: Map<number, Record<string, number>>;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export type SymbolsCountMatrix = number[][];
|
|
163
|
-
|
|
164
|
-
export type SymbolPositionsMatrix = boolean[][][];
|
|
165
|
-
|
|
166
|
-
export interface SymbolAnalysisResult {
|
|
167
|
-
symbolsCount: SymbolsCountMatrix;
|
|
168
|
-
symbolPositions: SymbolPositionsMatrix;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export interface FindWinningLinesParams {
|
|
172
|
-
symbolsCount: SymbolsCountMatrix;
|
|
173
|
-
symbolPositions: SymbolPositionsMatrix;
|
|
174
|
-
configData: ExtractedConfigData;
|
|
175
|
-
betStake: number;
|
|
176
|
-
currentMultiplier: number;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export interface CalculateWinsResult {
|
|
180
|
-
totalWin: number;
|
|
181
|
-
winningLines: WinLine[];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export interface CheatTriggerBonusOutcome {
|
|
185
|
-
triggeredBonus: BonusTrigger;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export interface CheatUpdateBonusMeterProgressOutcome {
|
|
189
|
-
updatedMeter: MeterState;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export interface CheatTriggerBonusResult {
|
|
193
|
-
publicState: BonnysFortunePublicState;
|
|
194
|
-
privateState: BonnysFortunePrivateState;
|
|
195
|
-
outcome: CheatTriggerBonusOutcome;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export interface CheatUpdateBonusMeterProgressResult {
|
|
199
|
-
publicState: BonnysFortunePublicState;
|
|
200
|
-
privateState: BonnysFortunePrivateState;
|
|
201
|
-
outcome: CheatUpdateBonusMeterProgressOutcome;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export interface GetSymbolsOutcome {
|
|
205
|
-
symbolId: number;
|
|
206
|
-
name: string;
|
|
207
|
-
multipliers: Record<'x3' | 'x4' | 'x5', number>;
|
|
208
|
-
isWild: boolean | undefined;
|
|
209
|
-
isScatter: boolean | undefined;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export interface GetSymbolsResult {
|
|
213
|
-
symbols: GetSymbolsOutcome[];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export type BonnysFortuneOutcome =
|
|
217
|
-
| BonnysFortuneGameLogicSpinResult
|
|
218
|
-
| BonnysFortuneBonusGameLogicSpinResult
|
|
219
|
-
| SymbolsCountMatrix
|
|
220
|
-
| CheatTriggerBonusOutcome
|
|
221
|
-
| CheatUpdateBonusMeterProgressOutcome
|
|
222
|
-
| GetSymbolsOutcome
|
|
223
|
-
| null;
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CalculateWinsResult,
|
|
3
|
-
ExtractedConfigData,
|
|
4
|
-
FindWinningLinesParams,
|
|
5
|
-
SpinType,
|
|
6
|
-
SymbolAnalysisResult,
|
|
7
|
-
SymbolPositionsMatrix,
|
|
8
|
-
SymbolsCountMatrix,
|
|
9
|
-
WinLine,
|
|
10
|
-
} from './bonnys-fortune.types';
|
|
11
|
-
|
|
12
|
-
import { BonnysFortuneGameLogicConfigInterface } from './game-logic-config.interface';
|
|
13
|
-
import { NumberHelper } from '../helpers/number-helper';
|
|
14
|
-
|
|
15
|
-
export class BonnysFortuneWinCalculator {
|
|
16
|
-
public static calculateWins(
|
|
17
|
-
screen: number[][],
|
|
18
|
-
config: BonnysFortuneGameLogicConfigInterface,
|
|
19
|
-
betStake: number,
|
|
20
|
-
currentMultiplier: number,
|
|
21
|
-
spinType: SpinType,
|
|
22
|
-
spinTypeIndex?: number, // Index of spin type in bonus game 2 config
|
|
23
|
-
): CalculateWinsResult {
|
|
24
|
-
if (!this.isEligibleSpin(spinType))
|
|
25
|
-
return { totalWin: 0, winningLines: [] };
|
|
26
|
-
|
|
27
|
-
const configData = this.buildConfigData(config, spinType, spinTypeIndex);
|
|
28
|
-
const { symbolsCount, symbolPositions } = this.analyzeScreenSymbols(
|
|
29
|
-
screen,
|
|
30
|
-
configData,
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
return this.findWinningLines({
|
|
34
|
-
symbolsCount,
|
|
35
|
-
symbolPositions,
|
|
36
|
-
configData,
|
|
37
|
-
betStake,
|
|
38
|
-
currentMultiplier,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private static isEligibleSpin(spinType: SpinType): boolean {
|
|
43
|
-
return (
|
|
44
|
-
spinType === SpinType.BASE_GAME_SPIN ||
|
|
45
|
-
spinType === SpinType.BONUS_GAME_2_SPIN
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private static buildConfigData(
|
|
50
|
-
config: BonnysFortuneGameLogicConfigInterface,
|
|
51
|
-
spinType: SpinType,
|
|
52
|
-
spinTypeIndex?: number,
|
|
53
|
-
): ExtractedConfigData {
|
|
54
|
-
const { minIndex: baseMin, maxIndex: baseMax } =
|
|
55
|
-
config.baseGame.symbolDefinitions.baseSymbols;
|
|
56
|
-
const WILD = config.baseGame.symbolDefinitions.specialSymbols.WILD;
|
|
57
|
-
|
|
58
|
-
let rows: number, reels: number, minLength: number, maxLength: number;
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
spinType === SpinType.BONUS_GAME_2_SPIN &&
|
|
62
|
-
spinTypeIndex !== undefined
|
|
63
|
-
) {
|
|
64
|
-
const spinConfig =
|
|
65
|
-
config.bonusGames.bonusGame2.config.spinTypes[spinTypeIndex];
|
|
66
|
-
rows = spinConfig.layout.rows;
|
|
67
|
-
reels = spinConfig.reelsOptionsWeights.length;
|
|
68
|
-
minLength = config.baseGame.baseGameConfig.winConfig.minLength;
|
|
69
|
-
maxLength = config.baseGame.baseGameConfig.winConfig.maxLength;
|
|
70
|
-
} else {
|
|
71
|
-
rows = config.baseGame.baseGameConfig.layout.rows;
|
|
72
|
-
reels = config.baseGame.baseGameConfig.layout.reels;
|
|
73
|
-
minLength = config.baseGame.baseGameConfig.winConfig.minLength;
|
|
74
|
-
maxLength = config.baseGame.baseGameConfig.winConfig.maxLength;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const multipliers = new Map<number, Record<string, number>>();
|
|
78
|
-
for (const symbolConfig of config.baseGame.symbolMultipliers) {
|
|
79
|
-
multipliers.set(symbolConfig.symbolId, symbolConfig.multipliers || {});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
baseMin,
|
|
84
|
-
baseMax,
|
|
85
|
-
WILD,
|
|
86
|
-
reels,
|
|
87
|
-
rows,
|
|
88
|
-
minLength,
|
|
89
|
-
maxLength,
|
|
90
|
-
multipliers,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private static analyzeScreenSymbols(
|
|
95
|
-
screen: number[][],
|
|
96
|
-
configData: ExtractedConfigData,
|
|
97
|
-
): SymbolAnalysisResult {
|
|
98
|
-
const { baseMin, baseMax, WILD, reels, rows } = configData;
|
|
99
|
-
|
|
100
|
-
const symbolsCount: SymbolsCountMatrix = Array.from(
|
|
101
|
-
{ length: baseMax - baseMin + 1 },
|
|
102
|
-
() => Array(reels).fill(0),
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const symbolPositions: SymbolPositionsMatrix = Array.from(
|
|
106
|
-
{ length: baseMax - baseMin + 1 },
|
|
107
|
-
() => Array.from({ length: reels }, () => Array(rows).fill(false)),
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
for (let reel = 0; reel < reels; reel++) {
|
|
111
|
-
for (let row = 0; row < rows; row++) {
|
|
112
|
-
const symbol = screen[reel][row];
|
|
113
|
-
if (symbol >= baseMin && symbol <= baseMax) {
|
|
114
|
-
const index = symbol - baseMin;
|
|
115
|
-
symbolsCount[index][reel]++;
|
|
116
|
-
symbolPositions[index][reel][row] = true;
|
|
117
|
-
} else if (symbol === WILD) {
|
|
118
|
-
this.markWildPositions(
|
|
119
|
-
baseMin,
|
|
120
|
-
baseMax,
|
|
121
|
-
reel,
|
|
122
|
-
row,
|
|
123
|
-
symbolsCount,
|
|
124
|
-
symbolPositions,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return { symbolsCount, symbolPositions };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private static markWildPositions(
|
|
134
|
-
baseMin: number,
|
|
135
|
-
baseMax: number,
|
|
136
|
-
reel: number,
|
|
137
|
-
row: number,
|
|
138
|
-
symbolsCount: SymbolsCountMatrix,
|
|
139
|
-
symbolPositions: SymbolPositionsMatrix,
|
|
140
|
-
): void {
|
|
141
|
-
for (let baseSymbol = baseMin; baseSymbol <= baseMax; baseSymbol++) {
|
|
142
|
-
const index = baseSymbol - baseMin;
|
|
143
|
-
symbolsCount[index][reel]++;
|
|
144
|
-
symbolPositions[index][reel][row] = true;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private static findWinningLines({
|
|
149
|
-
symbolsCount,
|
|
150
|
-
symbolPositions,
|
|
151
|
-
configData,
|
|
152
|
-
betStake,
|
|
153
|
-
currentMultiplier,
|
|
154
|
-
}: FindWinningLinesParams): CalculateWinsResult {
|
|
155
|
-
const { baseMin, baseMax, reels, rows, minLength, maxLength, multipliers } =
|
|
156
|
-
configData;
|
|
157
|
-
|
|
158
|
-
let totalWin = 0;
|
|
159
|
-
const winningLines: WinLine[] = [];
|
|
160
|
-
|
|
161
|
-
for (let symbol = baseMin; symbol <= baseMax; symbol++) {
|
|
162
|
-
const idx = symbol - baseMin;
|
|
163
|
-
let ways = 1;
|
|
164
|
-
let consecutiveReels = 0;
|
|
165
|
-
const positions: [number, number][] = [];
|
|
166
|
-
|
|
167
|
-
for (let reel = 0; reel < reels; reel++) {
|
|
168
|
-
const countOnReel = symbolsCount[idx][reel];
|
|
169
|
-
if (countOnReel > 0) {
|
|
170
|
-
consecutiveReels++;
|
|
171
|
-
ways = NumberHelper.safeMultiply(ways, countOnReel, 6);
|
|
172
|
-
for (let row = 0; row < rows; row++) {
|
|
173
|
-
if (symbolPositions[idx][reel][row]) positions.push([reel, row]);
|
|
174
|
-
}
|
|
175
|
-
} else break;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (consecutiveReels >= minLength && consecutiveReels <= maxLength) {
|
|
179
|
-
const multiplier =
|
|
180
|
-
multipliers.get(symbol)?.[`x${consecutiveReels}`] ?? 0;
|
|
181
|
-
if (multiplier > 0) {
|
|
182
|
-
let winAmount = ways;
|
|
183
|
-
winAmount = NumberHelper.safeMultiply(winAmount, multiplier, 6);
|
|
184
|
-
winAmount = NumberHelper.safeMultiply(winAmount, betStake, 6);
|
|
185
|
-
winAmount = NumberHelper.safeMultiply(
|
|
186
|
-
winAmount,
|
|
187
|
-
currentMultiplier,
|
|
188
|
-
6,
|
|
189
|
-
);
|
|
190
|
-
winAmount = NumberHelper.toFixedNumber(winAmount, 2);
|
|
191
|
-
|
|
192
|
-
totalWin = NumberHelper.safeSum(totalWin, winAmount, 6);
|
|
193
|
-
|
|
194
|
-
winningLines.push({
|
|
195
|
-
symbol,
|
|
196
|
-
consecutiveReels,
|
|
197
|
-
ways,
|
|
198
|
-
multiplier,
|
|
199
|
-
win: winAmount,
|
|
200
|
-
positions,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
totalWin = NumberHelper.toFixedNumber(totalWin, 2);
|
|
207
|
-
|
|
208
|
-
return { totalWin, winningLines };
|
|
209
|
-
}
|
|
210
|
-
}
|