@omnitronix/bonnys-fortune-game-engine 1.3.1 → 1.3.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 (146) hide show
  1. package/README.md +15 -15
  2. package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +1 -0
  3. package/dist/bonnys-fortune-v1.game-engine.d.ts +36 -0
  4. package/dist/bonnys-fortune-v1.game-engine.js +23 -12
  5. package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
  6. package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +4 -0
  7. package/dist/config/game-logic-config/game-logic-config-loader.d.ts +3 -0
  8. package/dist/config/game-logic-config/game-logic-config.d.ts +2 -0
  9. package/dist/config/game-logic-config/game-logic-config.js +2 -0
  10. package/dist/config/game-logic-config/game-logic-config.js.map +1 -1
  11. package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +4 -0
  12. package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +4 -0
  13. package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +3 -0
  14. package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +5 -0
  15. package/dist/config/reel-strips-config/reel.dto.d.ts +5 -0
  16. package/dist/domain/game-round.types.d.ts +9 -0
  17. package/dist/domain/mappers/reel-strips-config.mapper.d.ts +4 -0
  18. package/dist/domain/reel-strip-option.d.ts +7 -0
  19. package/dist/domain/reel-strips-config.d.ts +9 -0
  20. package/dist/domain/reel.d.ts +8 -0
  21. package/dist/domain/types/cheat-trigger-bonus-command.d.ts +5 -0
  22. package/dist/domain/types/{debug-trigger-bonus-command.js → cheat-trigger-bonus-command.js} +1 -1
  23. package/dist/domain/types/cheat-trigger-bonus-command.js.map +1 -0
  24. package/dist/domain/types/cheat-trigger-bonus-request.dto.d.ts +5 -0
  25. package/dist/domain/types/{debug-trigger-bonus-request.dto.js → cheat-trigger-bonus-request.dto.js} +1 -1
  26. package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +1 -0
  27. package/dist/domain/types/cheat-update-bonus-meter-progress-command.d.ts +5 -0
  28. package/dist/domain/types/{debug-update-bonus-meter-progress-command.js → cheat-update-bonus-meter-progress-command.js} +1 -1
  29. package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +1 -0
  30. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.d.ts +5 -0
  31. package/dist/domain/types/{debug-update-bonus-meter-progress-request.dto.js → cheat-update-bonus-meter-progress-request.dto.js} +1 -1
  32. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +1 -0
  33. package/dist/domain/types/game-spin-command.d.ts +8 -0
  34. package/dist/domain/types/game-spin-input.dto.d.ts +8 -0
  35. package/dist/domain/types/game-spin-output.dto.d.ts +119 -0
  36. package/dist/domain/types/game-symbols.response.dto.d.ts +10 -0
  37. package/dist/domain/types/reel-strip-option.dto.d.ts +4 -0
  38. package/dist/domain/types/reel-strips-config.dto.d.ts +5 -0
  39. package/dist/domain/types/reel.dto.d.ts +5 -0
  40. package/dist/domain/types/start-bonus-round-command.d.ts +8 -0
  41. package/dist/domain/types/start-bonus-round-input.dto.d.ts +10 -0
  42. package/dist/game-engine.interface.d.ts +41 -0
  43. package/dist/helpers/generate-hash.d.ts +1 -0
  44. package/dist/helpers/number-helper.d.ts +23 -0
  45. package/dist/helpers/optional-boolean-mapper.d.ts +1 -0
  46. package/dist/helpers/uuid-helper.d.ts +3 -0
  47. package/dist/helpers/validation-helper.d.ts +14 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/logger/logger.d.ts +22 -0
  51. package/dist/logger/logger.js +4 -5
  52. package/dist/logger/logger.js.map +1 -1
  53. package/dist/logic/bonnys-fortune.game-logic.d.ts +32 -0
  54. package/dist/logic/bonnys-fortune.game-logic.js +15 -11
  55. package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
  56. package/dist/logic/bonnys-fortune.spin-generator.d.ts +19 -0
  57. package/dist/logic/bonnys-fortune.types.d.ts +170 -0
  58. package/dist/logic/bonnys-fortune.win-calculator.d.ts +10 -0
  59. package/dist/logic/game-logic-config.interface.d.ts +180 -0
  60. package/dist/logic/handlers/base-game.handler.d.ts +15 -0
  61. package/dist/logic/handlers/base-game.handler.js +5 -1
  62. package/dist/logic/handlers/base-game.handler.js.map +1 -1
  63. package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +13 -0
  64. package/dist/logic/handlers/free-spins-bonus.handler.d.ts +11 -0
  65. package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +11 -0
  66. package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +15 -0
  67. package/dist/rng/DummyRngClient.d.ts +18 -0
  68. package/dist/rng/DummyRngClient.js +1 -8
  69. package/dist/rng/DummyRngClient.js.map +1 -1
  70. package/dist/rng/rng-client.factory.d.ts +7 -0
  71. package/dist/rng/rng-client.factory.js.map +1 -1
  72. package/dist/rng/rng-client.interface.d.ts +16 -0
  73. package/dist/rng/rng-service.d.ts +26 -0
  74. package/dist/validation/abstract-game-logic-config.validator.d.ts +4 -0
  75. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +131 -0
  76. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js +9 -0
  77. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
  78. package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +5 -0
  79. package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +2 -0
  80. package/dist/validation/game-logic-config-validation.service.d.ts +8 -0
  81. package/dist/validation/reel-strips-config-validation.service.d.ts +5 -0
  82. package/package.json +4 -3
  83. package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +80 -0
  84. package/src/bonnys-fortune-v1.game-engine.ts +314 -0
  85. package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +27 -0
  86. package/src/config/game-logic-config/game-logic-config-loader.ts +3 -0
  87. package/src/config/game-logic-config/game-logic-config.ts +382 -0
  88. package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +43 -0
  89. package/src/config/reel-strips-config/reel-strip-option.dto.ts +13 -0
  90. package/src/config/reel-strips-config/reel-strips-config-loader.ts +9 -0
  91. package/src/config/reel-strips-config/reel-strips-config.dto.ts +16 -0
  92. package/src/config/reel-strips-config/reel.dto.ts +16 -0
  93. package/src/config/reel-strips-config/reels-BASE.csv +52 -0
  94. package/src/config/reel-strips-config/reels-BONUS.csv +52 -0
  95. package/src/domain/game-round.types.ts +10 -0
  96. package/src/domain/mappers/reel-strips-config.mapper.ts +16 -0
  97. package/src/domain/reel-strip-option.ts +15 -0
  98. package/src/domain/reel-strips-config.ts +21 -0
  99. package/src/domain/reel.ts +17 -0
  100. package/src/domain/types/cheat-trigger-bonus-command.ts +5 -0
  101. package/src/domain/types/cheat-trigger-bonus-request.dto.ts +5 -0
  102. package/src/domain/types/cheat-update-bonus-meter-progress-command.ts +6 -0
  103. package/src/domain/types/cheat-update-bonus-meter-progress-request.dto.ts +5 -0
  104. package/src/domain/types/game-spin-command.ts +8 -0
  105. package/src/domain/types/game-spin-input.dto.ts +8 -0
  106. package/src/domain/types/game-spin-output.dto.ts +142 -0
  107. package/src/domain/types/game-symbols.response.dto.ts +11 -0
  108. package/src/domain/types/reel-strip-option.dto.ts +13 -0
  109. package/src/domain/types/reel-strips-config.dto.ts +15 -0
  110. package/src/domain/types/reel.dto.ts +15 -0
  111. package/src/domain/types/start-bonus-round-command.ts +8 -0
  112. package/src/domain/types/start-bonus-round-input.dto.ts +10 -0
  113. package/src/game-engine.interface.ts +59 -0
  114. package/src/helpers/generate-hash.ts +5 -0
  115. package/src/helpers/number-helper.ts +41 -0
  116. package/src/helpers/optional-boolean-mapper.ts +5 -0
  117. package/src/helpers/uuid-helper.ts +7 -0
  118. package/src/helpers/validation-helper.ts +27 -0
  119. package/src/index.ts +3 -0
  120. package/src/logger/logger.ts +178 -0
  121. package/src/logic/bonnys-fortune.game-logic.ts +490 -0
  122. package/src/logic/bonnys-fortune.spin-generator.ts +277 -0
  123. package/src/logic/bonnys-fortune.types.ts +223 -0
  124. package/src/logic/bonnys-fortune.win-calculator.ts +210 -0
  125. package/src/logic/game-logic-config.interface.ts +176 -0
  126. package/src/logic/handlers/base-game.handler.ts +221 -0
  127. package/src/logic/handlers/collect-feature-bonus.handler.ts +301 -0
  128. package/src/logic/handlers/free-spins-bonus.handler.ts +119 -0
  129. package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +118 -0
  130. package/src/logic/handlers/treasure-hunt-bonus.handler.ts +232 -0
  131. package/src/rng/DummyRngClient.ts +108 -0
  132. package/src/rng/rng-client.factory.ts +27 -0
  133. package/src/rng/rng-client.interface.ts +38 -0
  134. package/src/rng/rng-service.ts +130 -0
  135. package/src/validation/abstract-game-logic-config.validator.ts +20 -0
  136. package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +379 -0
  137. package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +8 -0
  138. package/src/validation/custom-decorators/IsNestedIntArray.ts +29 -0
  139. package/src/validation/game-logic-config-validation.service.ts +28 -0
  140. package/src/validation/reel-strips-config-validation.service.ts +29 -0
  141. package/dist/__tests__/comprehensive.test.js +0 -741
  142. package/dist/__tests__/comprehensive.test.js.map +0 -1
  143. package/dist/domain/types/debug-trigger-bonus-command.js.map +0 -1
  144. package/dist/domain/types/debug-trigger-bonus-request.dto.js.map +0 -1
  145. package/dist/domain/types/debug-update-bonus-meter-progress-command.js.map +0 -1
  146. package/dist/domain/types/debug-update-bonus-meter-progress-request.dto.js.map +0 -1
@@ -0,0 +1,490 @@
1
+ import {
2
+ BonnysFortuneBaseGameSpinResult,
3
+ BonnysFortunePrivateState,
4
+ BonnysFortunePublicState,
5
+ BonusTrigger,
6
+ CheatTriggerBonusResult,
7
+ CheatUpdateBonusMeterProgressResult,
8
+ GetSymbolsResult,
9
+ MeterState,
10
+ MetersState,
11
+ SpinResultResponse,
12
+ StartBonusRoundResult,
13
+ SpinType,
14
+ } from './bonnys-fortune.types';
15
+
16
+ import { BaseGameHandler } from './handlers/base-game.handler';
17
+ import {
18
+ BonnysFortuneGameLogicConfigInterface,
19
+ BonnysFortuneSessionStateInterface,
20
+ } from './game-logic-config.interface';
21
+ import { BonnysFortuneSpinGenerator } from './bonnys-fortune.spin-generator';
22
+ import { CheatTriggerBonusRequestDto } from '../domain/types/cheat-trigger-bonus-request.dto';
23
+ import { CheatUpdateBonusMeterProgressRequestDto } from '../domain/types/cheat-update-bonus-meter-progress-request.dto';
24
+ import { CollectFeatureBonusHandler } from './handlers/collect-feature-bonus.handler';
25
+ import { FreeSpinsBonusHandler } from './handlers/free-spins-bonus.handler';
26
+ import { GameActionCommand } from '../game-engine.interface';
27
+ import { GameSpinInputDto } from '../domain/types/game-spin-input.dto';
28
+ import { NumberHelper } from '../helpers/number-helper';
29
+ import { ReelStripsConfig } from '../domain/reel-strips-config';
30
+ import { RngService } from '../rng/rng-service';
31
+ import { StartBonusRoundInputDto } from '../domain/types/start-bonus-round-input.dto';
32
+ import { SteeringToTheFortuneBonusHandler } from './handlers/steering-to-the-fortune-bonus.handler';
33
+ import { TreasureHuntBonusHandler } from './handlers/treasure-hunt-bonus.handler';
34
+ import { UuidHelper } from '../helpers/uuid-helper';
35
+
36
+ export class BonnysFortuneGameLogic {
37
+ constructor(private readonly rngService: RngService) {}
38
+
39
+ public async initSessionState(
40
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
41
+ reelStripsConfig: ReelStripsConfig,
42
+ gameConfig: { betAmountThresholds: number[]; defaultBetAmount: number },
43
+ commandId: string,
44
+ ): Promise<BonnysFortuneSessionStateInterface> {
45
+ const bonusMetersByAmount: Record<number, MetersState> = {};
46
+
47
+ for (const betAmount of gameConfig.betAmountThresholds) {
48
+ bonusMetersByAmount[betAmount] = await this.drawBonusMeters(
49
+ gameLogicConfig,
50
+ commandId,
51
+ betAmount,
52
+ );
53
+ }
54
+
55
+ const initialBetAmount =
56
+ gameConfig.defaultBetAmount ?? gameConfig.betAmountThresholds[0];
57
+
58
+ return {
59
+ publicState: {
60
+ currentBetAmount: initialBetAmount,
61
+ bonusMeters: bonusMetersByAmount[initialBetAmount],
62
+ },
63
+ privateState: {
64
+ bonusMetersByAmount,
65
+ screen: await new BonnysFortuneSpinGenerator(
66
+ this.rngService,
67
+ ).generateBaseScreen(
68
+ gameLogicConfig,
69
+ reelStripsConfig,
70
+ commandId,
71
+ 'init_screen',
72
+ ),
73
+ nextSpinType: SpinType.BASE_GAME_SPIN,
74
+ pendingBonuses: [],
75
+ },
76
+ };
77
+ }
78
+
79
+ public async spin(
80
+ params: GameSpinInputDto,
81
+ publicState: BonnysFortunePublicState,
82
+ privateState: BonnysFortunePrivateState,
83
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
84
+ reelStripsConfig: ReelStripsConfig,
85
+ commandId: string,
86
+ ): Promise<SpinResultResponse<BonnysFortuneBaseGameSpinResult>> {
87
+ return new BaseGameHandler(this.rngService).handleSpin(
88
+ params,
89
+ publicState,
90
+ privateState,
91
+ gameLogicConfig,
92
+ reelStripsConfig,
93
+ commandId,
94
+ );
95
+ }
96
+
97
+ public async startBonusRound(
98
+ params: StartBonusRoundInputDto,
99
+ publicState: BonnysFortunePublicState,
100
+ privateState: BonnysFortunePrivateState,
101
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
102
+ reelStripsConfig: ReelStripsConfig,
103
+ commandId: string,
104
+ ): Promise<StartBonusRoundResult> {
105
+ if (!privateState.pendingBonuses?.length) {
106
+ throw new Error('No pending bonuses to start.');
107
+ }
108
+
109
+ const [activeBonus] = privateState.pendingBonuses;
110
+
111
+ if (
112
+ activeBonus.bonusId !== params.bonusId ||
113
+ activeBonus.bonusType !== params.bonusType
114
+ ) {
115
+ throw new Error(`Invalid bonus start request: ${params.bonusType}`);
116
+ }
117
+
118
+ if (this.shouldRedrawMeter(activeBonus.bonusType, gameLogicConfig)) {
119
+ const updatedStates = await this.updateSessionState(
120
+ publicState,
121
+ privateState,
122
+ gameLogicConfig,
123
+ {
124
+ id: commandId,
125
+ type: 'START_BONUS_ROUND',
126
+ payload: {
127
+ bonusMeters: {
128
+ redraw: [activeBonus.bonusType],
129
+ },
130
+ },
131
+ },
132
+ );
133
+ publicState = updatedStates.publicState;
134
+ privateState = updatedStates.privateState;
135
+ }
136
+
137
+ const updatedPrivateState: BonnysFortunePrivateState = {
138
+ ...privateState,
139
+ pendingBonuses: privateState.pendingBonuses.filter(
140
+ (b) => b.bonusId !== activeBonus.bonusId,
141
+ ),
142
+ activeBonus,
143
+ };
144
+
145
+ switch (params.bonusType) {
146
+ case 'bonusGame1': {
147
+ const bonusHandlerResult = await new TreasureHuntBonusHandler(
148
+ this.rngService,
149
+ ).generateFullBonusRound(
150
+ { ...params, betAmount: activeBonus.betAmount },
151
+ publicState,
152
+ {
153
+ ...updatedPrivateState,
154
+ nextSpinType: SpinType.BONUS_GAME_1_SPIN,
155
+ },
156
+ gameLogicConfig,
157
+ commandId,
158
+ );
159
+
160
+ return {
161
+ ...bonusHandlerResult,
162
+ activeBonus,
163
+ };
164
+ }
165
+
166
+ case 'bonusGame2': {
167
+ const bonusHandlerResult = await new FreeSpinsBonusHandler(
168
+ this.rngService,
169
+ ).generateFullBonusRound(
170
+ { ...params, betAmount: activeBonus.betAmount },
171
+ publicState,
172
+ {
173
+ ...updatedPrivateState,
174
+ nextSpinType: SpinType.BONUS_GAME_2_SPIN,
175
+ },
176
+ gameLogicConfig,
177
+ reelStripsConfig,
178
+ commandId,
179
+ );
180
+
181
+ return {
182
+ ...bonusHandlerResult,
183
+ activeBonus,
184
+ };
185
+ }
186
+ case 'bonusGame3': {
187
+ const bonusHandlerResult = await new SteeringToTheFortuneBonusHandler(
188
+ this.rngService,
189
+ ).generateFullBonusRound(
190
+ { ...params, betAmount: activeBonus.betAmount },
191
+ publicState,
192
+ {
193
+ ...updatedPrivateState,
194
+ nextSpinType: SpinType.BONUS_GAME_3_SPIN,
195
+ },
196
+ gameLogicConfig,
197
+ commandId,
198
+ );
199
+
200
+ return {
201
+ ...bonusHandlerResult,
202
+ activeBonus,
203
+ };
204
+ }
205
+ case 'collectFeature': {
206
+ const bonusHandlerResult = await new CollectFeatureBonusHandler(
207
+ this.rngService,
208
+ ).generateFullBonusRound(
209
+ { ...params, betAmount: activeBonus.betAmount },
210
+ publicState,
211
+ {
212
+ ...updatedPrivateState,
213
+ nextSpinType: SpinType.COLLECT_FEATURE_SPIN,
214
+ },
215
+ gameLogicConfig,
216
+ commandId,
217
+ );
218
+
219
+ return {
220
+ ...bonusHandlerResult,
221
+ activeBonus,
222
+ };
223
+ }
224
+ }
225
+ }
226
+
227
+ private async drawBonusMeters(
228
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
229
+ commandId: string,
230
+ betAmount: number,
231
+ ): Promise<MetersState> {
232
+ const bonusMeters: MetersState = {};
233
+
234
+ for (const [key, cfg] of Object.entries(
235
+ gameLogicConfig.baseGame.bonusGameMeters,
236
+ )) {
237
+ const { value: threshold } = await this.randomThreshold(
238
+ cfg.min,
239
+ cfg.max,
240
+ commandId,
241
+ `bonus_meter_${betAmount}_${key}`,
242
+ );
243
+ const progress = 0;
244
+ const level = this.getMeterLevel(
245
+ progress,
246
+ threshold,
247
+ cfg.percentageLevels,
248
+ );
249
+
250
+ bonusMeters[key] = {
251
+ symbolName: cfg.symbol,
252
+ symbolId:
253
+ gameLogicConfig.baseGame.symbolMultipliers.find(
254
+ (s) => s.name === cfg.symbol,
255
+ )?.symbolId ?? 0,
256
+ threshold,
257
+ progress,
258
+ level,
259
+ };
260
+ }
261
+
262
+ return bonusMeters;
263
+ }
264
+
265
+ public async updateSessionState(
266
+ publicState: BonnysFortunePublicState,
267
+ privateState: BonnysFortunePrivateState,
268
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
269
+ command: GameActionCommand<{ bonusMeters: { redraw: string[] } }>,
270
+ ): Promise<BonnysFortuneSessionStateInterface> {
271
+ switch (command.type) {
272
+ case 'START_BONUS_ROUND': {
273
+ if (
274
+ !this.requiresMeterUpdate(
275
+ command.payload?.bonusMeters.redraw ?? [],
276
+ gameLogicConfig,
277
+ )
278
+ ) {
279
+ return { publicState, privateState };
280
+ }
281
+
282
+ const betAmount = publicState.currentBetAmount;
283
+ const bonusMetersForBetAmount = privateState.bonusMetersByAmount[betAmount];
284
+
285
+ if (!bonusMetersForBetAmount) {
286
+ throw new Error(
287
+ `Bet amount ${betAmount} is not in allowed thresholds`,
288
+ );
289
+ }
290
+
291
+ for (const key of command.payload?.bonusMeters.redraw ?? []) {
292
+ const cfg =
293
+ gameLogicConfig.baseGame.bonusGameMeters[
294
+ key as keyof typeof gameLogicConfig.baseGame.bonusGameMeters
295
+ ];
296
+ const { value: threshold } = await this.randomThreshold(
297
+ cfg.min,
298
+ cfg.max,
299
+ command.id,
300
+ `bonus_meter_redraw_${key}`,
301
+ );
302
+ const progress = 0;
303
+ const level = this.getMeterLevel(
304
+ progress,
305
+ threshold,
306
+ cfg.percentageLevels,
307
+ );
308
+
309
+ bonusMetersForBetAmount[key] = {
310
+ ...bonusMetersForBetAmount[key],
311
+ threshold,
312
+ progress,
313
+ level,
314
+ };
315
+ }
316
+
317
+ return {
318
+ publicState: {
319
+ ...publicState,
320
+ bonusMeters: bonusMetersForBetAmount,
321
+ },
322
+ privateState,
323
+ };
324
+ }
325
+
326
+ default: {
327
+ return {
328
+ publicState,
329
+ privateState,
330
+ };
331
+ }
332
+ }
333
+ }
334
+
335
+ public cheatTriggerBonus(
336
+ publicState: BonnysFortunePublicState,
337
+ privateState: BonnysFortunePrivateState,
338
+ params: CheatTriggerBonusRequestDto,
339
+ ): CheatTriggerBonusResult {
340
+ const bonusTrigger: BonusTrigger = {
341
+ bonusId: UuidHelper.generateUuid(),
342
+ bonusType: params.bonusType,
343
+ betAmount: params.betAmount,
344
+ };
345
+
346
+ privateState.pendingBonuses.push(bonusTrigger);
347
+
348
+ return {
349
+ publicState,
350
+ privateState,
351
+ outcome: {
352
+ triggeredBonus: bonusTrigger,
353
+ },
354
+ };
355
+ }
356
+
357
+ public cheatUpdateBonusMeterProgress(
358
+ publicState: BonnysFortunePublicState,
359
+ privateState: BonnysFortunePrivateState,
360
+ params: CheatUpdateBonusMeterProgressRequestDto,
361
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
362
+ ): CheatUpdateBonusMeterProgressResult {
363
+ const betAmount = publicState.currentBetAmount;
364
+
365
+ const bonusMetersForBetAmount = privateState.bonusMetersByAmount[betAmount];
366
+ if (!bonusMetersForBetAmount) {
367
+ throw new Error(
368
+ `Bet amount ${betAmount} is not in allowed thresholds`,
369
+ );
370
+ }
371
+
372
+ const currentPrivateMeter = bonusMetersForBetAmount[params.bonusType];
373
+
374
+ if (!currentPrivateMeter) {
375
+ throw new Error(
376
+ `Bonus meter ${params.bonusType} not found for bet amount ${betAmount}`,
377
+ );
378
+ }
379
+
380
+ const meterConfig =
381
+ gameLogicConfig.baseGame.bonusGameMeters[
382
+ params.bonusType as keyof typeof gameLogicConfig.baseGame.bonusGameMeters
383
+ ];
384
+ if (!meterConfig) {
385
+ throw new Error(`Config for bonus meter ${params.bonusType} not found`);
386
+ }
387
+
388
+ const newLevel = this.getMeterLevel(
389
+ params.progress,
390
+ currentPrivateMeter.threshold,
391
+ meterConfig.percentageLevels,
392
+ );
393
+
394
+ const updatedMeter: MeterState = {
395
+ ...currentPrivateMeter,
396
+ progress: params.progress,
397
+ level: newLevel,
398
+ };
399
+
400
+ privateState.bonusMetersByAmount[betAmount] = {
401
+ ...bonusMetersForBetAmount,
402
+ [params.bonusType]: updatedMeter,
403
+ };
404
+
405
+ publicState = {
406
+ ...publicState,
407
+ bonusMeters: {
408
+ ...publicState.bonusMeters,
409
+ [params.bonusType]: {
410
+ ...publicState.bonusMeters[params.bonusType],
411
+ progress: params.progress,
412
+ level: newLevel,
413
+ },
414
+ },
415
+ };
416
+
417
+ return {
418
+ publicState,
419
+ privateState,
420
+ outcome: {
421
+ updatedMeter,
422
+ },
423
+ };
424
+ }
425
+
426
+ public getSymbols(
427
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
428
+ ): GetSymbolsResult {
429
+ const symbols = gameLogicConfig.baseGame.symbolMultipliers.map(
430
+ (symbol) => ({
431
+ symbolId: symbol.symbolId,
432
+ name: symbol.name,
433
+ multipliers: symbol.multipliers,
434
+ isWild: symbol.isWild,
435
+ isScatter: symbol.isScatter,
436
+ }),
437
+ );
438
+
439
+ return { symbols };
440
+ }
441
+
442
+ private shouldRedrawMeter(
443
+ bonusType: string,
444
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
445
+ ): boolean {
446
+ if (bonusType === 'collectFeature') {
447
+ return false;
448
+ }
449
+
450
+ return bonusType in gameLogicConfig.baseGame.bonusGameMeters;
451
+ }
452
+
453
+ private requiresMeterUpdate(
454
+ redrawKeys: string[],
455
+ gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
456
+ ): boolean {
457
+ return redrawKeys.some(
458
+ (key) => key in gameLogicConfig.baseGame.bonusGameMeters,
459
+ );
460
+ }
461
+
462
+ private getMeterLevel(
463
+ progress: number,
464
+ threshold: number,
465
+ percentageLevels: Record<
466
+ string,
467
+ { min: number; max: number; value?: number }
468
+ >,
469
+ ): number {
470
+ for (const [, range] of Object.entries(percentageLevels)) {
471
+ const minValue = NumberHelper.safeMultiply(range.min, threshold, 4);
472
+ const maxValue = NumberHelper.safeMultiply(range.max, threshold, 4);
473
+
474
+ if (progress >= minValue && progress < maxValue) {
475
+ return range.value ?? 0;
476
+ }
477
+ }
478
+
479
+ return 5;
480
+ }
481
+
482
+ private async randomThreshold(
483
+ min: number,
484
+ max: number,
485
+ commandId: string,
486
+ actionId: string,
487
+ ): Promise<{ value: number; seed: number }> {
488
+ return this.rngService.getSingleNumber(min, max, commandId, actionId);
489
+ }
490
+ }