@omnitronix/bonnys-fortune-game-engine 1.2.0 → 1.2.1

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 (136) hide show
  1. package/dist/__tests__/comprehensive.test.js +741 -0
  2. package/dist/__tests__/comprehensive.test.js.map +1 -0
  3. package/dist/bonnys-fortune-v1.game-engine.js +12 -7
  4. package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
  5. package/dist/domain/types/{cheat-trigger-bonus-command.js → debug-trigger-bonus-command.js} +1 -1
  6. package/dist/domain/types/debug-trigger-bonus-command.js.map +1 -0
  7. package/dist/domain/types/{cheat-trigger-bonus-request.dto.js → debug-trigger-bonus-request.dto.js} +1 -1
  8. package/dist/domain/types/debug-trigger-bonus-request.dto.js.map +1 -0
  9. package/dist/domain/types/{cheat-update-bonus-meter-progress-command.js → debug-update-bonus-meter-progress-command.js} +1 -1
  10. package/dist/domain/types/debug-update-bonus-meter-progress-command.js.map +1 -0
  11. package/dist/domain/types/{cheat-update-bonus-meter-progress-request.dto.js → debug-update-bonus-meter-progress-request.dto.js} +1 -1
  12. package/dist/domain/types/debug-update-bonus-meter-progress-request.dto.js.map +1 -0
  13. package/dist/logic/bonnys-fortune.game-logic.js +2 -2
  14. package/dist/rng/DummyRngClient.js +8 -1
  15. package/dist/rng/DummyRngClient.js.map +1 -1
  16. package/dist/rng/rng-client.factory.js +1 -2
  17. package/dist/rng/rng-client.factory.js.map +1 -1
  18. package/package.json +3 -4
  19. package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +0 -1
  20. package/dist/bonnys-fortune-v1.game-engine.d.ts +0 -32
  21. package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +0 -4
  22. package/dist/config/game-logic-config/game-logic-config-loader.d.ts +0 -3
  23. package/dist/config/game-logic-config/game-logic-config.d.ts +0 -2
  24. package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +0 -4
  25. package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +0 -4
  26. package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +0 -3
  27. package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +0 -5
  28. package/dist/config/reel-strips-config/reel.dto.d.ts +0 -5
  29. package/dist/domain/game-round.types.d.ts +0 -9
  30. package/dist/domain/mappers/reel-strips-config.mapper.d.ts +0 -4
  31. package/dist/domain/reel-strip-option.d.ts +0 -7
  32. package/dist/domain/reel-strips-config.d.ts +0 -9
  33. package/dist/domain/reel.d.ts +0 -8
  34. package/dist/domain/types/cheat-trigger-bonus-command.d.ts +0 -5
  35. package/dist/domain/types/cheat-trigger-bonus-command.js.map +0 -1
  36. package/dist/domain/types/cheat-trigger-bonus-request.dto.d.ts +0 -5
  37. package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +0 -1
  38. package/dist/domain/types/cheat-update-bonus-meter-progress-command.d.ts +0 -5
  39. package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +0 -1
  40. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.d.ts +0 -5
  41. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +0 -1
  42. package/dist/domain/types/game-spin-command.d.ts +0 -8
  43. package/dist/domain/types/game-spin-input.dto.d.ts +0 -8
  44. package/dist/domain/types/game-spin-output.dto.d.ts +0 -119
  45. package/dist/domain/types/game-symbols.response.dto.d.ts +0 -10
  46. package/dist/domain/types/reel-strip-option.dto.d.ts +0 -4
  47. package/dist/domain/types/reel-strips-config.dto.d.ts +0 -5
  48. package/dist/domain/types/reel.dto.d.ts +0 -5
  49. package/dist/domain/types/start-bonus-round-command.d.ts +0 -8
  50. package/dist/domain/types/start-bonus-round-input.dto.d.ts +0 -10
  51. package/dist/game-engine.interface.d.ts +0 -41
  52. package/dist/helpers/generate-hash.d.ts +0 -1
  53. package/dist/helpers/number-helper.d.ts +0 -23
  54. package/dist/helpers/optional-boolean-mapper.d.ts +0 -1
  55. package/dist/helpers/uuid-helper.d.ts +0 -3
  56. package/dist/helpers/validation-helper.d.ts +0 -14
  57. package/dist/index.d.ts +0 -2
  58. package/dist/logger/logger.d.ts +0 -22
  59. package/dist/logic/bonnys-fortune.game-logic.d.ts +0 -32
  60. package/dist/logic/bonnys-fortune.spin-generator.d.ts +0 -19
  61. package/dist/logic/bonnys-fortune.types.d.ts +0 -170
  62. package/dist/logic/bonnys-fortune.win-calculator.d.ts +0 -10
  63. package/dist/logic/game-logic-config.interface.d.ts +0 -178
  64. package/dist/logic/handlers/base-game.handler.d.ts +0 -15
  65. package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +0 -13
  66. package/dist/logic/handlers/free-spins-bonus.handler.d.ts +0 -11
  67. package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +0 -11
  68. package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +0 -15
  69. package/dist/rng/DummyRngClient.d.ts +0 -18
  70. package/dist/rng/rng-client.factory.d.ts +0 -7
  71. package/dist/rng/rng-client.interface.d.ts +0 -16
  72. package/dist/rng/rng-service.d.ts +0 -26
  73. package/dist/validation/abstract-game-logic-config.validator.d.ts +0 -4
  74. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +0 -129
  75. package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +0 -5
  76. package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +0 -2
  77. package/dist/validation/game-logic-config-validation.service.d.ts +0 -8
  78. package/dist/validation/reel-strips-config-validation.service.d.ts +0 -5
  79. package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +0 -80
  80. package/src/bonnys-fortune-v1.game-engine.ts +0 -285
  81. package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +0 -27
  82. package/src/config/game-logic-config/game-logic-config-loader.ts +0 -3
  83. package/src/config/game-logic-config/game-logic-config.ts +0 -380
  84. package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +0 -43
  85. package/src/config/reel-strips-config/reel-strip-option.dto.ts +0 -13
  86. package/src/config/reel-strips-config/reel-strips-config-loader.ts +0 -9
  87. package/src/config/reel-strips-config/reel-strips-config.dto.ts +0 -16
  88. package/src/config/reel-strips-config/reel.dto.ts +0 -16
  89. package/src/config/reel-strips-config/reels-BASE.csv +0 -52
  90. package/src/config/reel-strips-config/reels-BONUS.csv +0 -52
  91. package/src/domain/game-round.types.ts +0 -10
  92. package/src/domain/mappers/reel-strips-config.mapper.ts +0 -16
  93. package/src/domain/reel-strip-option.ts +0 -15
  94. package/src/domain/reel-strips-config.ts +0 -21
  95. package/src/domain/reel.ts +0 -17
  96. package/src/domain/types/cheat-trigger-bonus-command.ts +0 -5
  97. package/src/domain/types/cheat-trigger-bonus-request.dto.ts +0 -5
  98. package/src/domain/types/cheat-update-bonus-meter-progress-command.ts +0 -6
  99. package/src/domain/types/cheat-update-bonus-meter-progress-request.dto.ts +0 -5
  100. package/src/domain/types/game-spin-command.ts +0 -8
  101. package/src/domain/types/game-spin-input.dto.ts +0 -8
  102. package/src/domain/types/game-spin-output.dto.ts +0 -142
  103. package/src/domain/types/game-symbols.response.dto.ts +0 -11
  104. package/src/domain/types/reel-strip-option.dto.ts +0 -13
  105. package/src/domain/types/reel-strips-config.dto.ts +0 -15
  106. package/src/domain/types/reel.dto.ts +0 -15
  107. package/src/domain/types/start-bonus-round-command.ts +0 -8
  108. package/src/domain/types/start-bonus-round-input.dto.ts +0 -10
  109. package/src/game-engine.interface.ts +0 -59
  110. package/src/helpers/generate-hash.ts +0 -5
  111. package/src/helpers/number-helper.ts +0 -41
  112. package/src/helpers/optional-boolean-mapper.ts +0 -5
  113. package/src/helpers/uuid-helper.ts +0 -7
  114. package/src/helpers/validation-helper.ts +0 -27
  115. package/src/index.ts +0 -3
  116. package/src/logger/logger.ts +0 -178
  117. package/src/logic/bonnys-fortune.game-logic.ts +0 -480
  118. package/src/logic/bonnys-fortune.spin-generator.ts +0 -277
  119. package/src/logic/bonnys-fortune.types.ts +0 -223
  120. package/src/logic/bonnys-fortune.win-calculator.ts +0 -210
  121. package/src/logic/game-logic-config.interface.ts +0 -174
  122. package/src/logic/handlers/base-game.handler.ts +0 -213
  123. package/src/logic/handlers/collect-feature-bonus.handler.ts +0 -301
  124. package/src/logic/handlers/free-spins-bonus.handler.ts +0 -119
  125. package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +0 -118
  126. package/src/logic/handlers/treasure-hunt-bonus.handler.ts +0 -232
  127. package/src/rng/DummyRngClient.ts +0 -108
  128. package/src/rng/rng-client.factory.ts +0 -27
  129. package/src/rng/rng-client.interface.ts +0 -38
  130. package/src/rng/rng-service.ts +0 -130
  131. package/src/validation/abstract-game-logic-config.validator.ts +0 -20
  132. package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +0 -371
  133. package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +0 -8
  134. package/src/validation/custom-decorators/IsNestedIntArray.ts +0 -29
  135. package/src/validation/game-logic-config-validation.service.ts +0 -28
  136. package/src/validation/reel-strips-config-validation.service.ts +0 -29
@@ -1,174 +0,0 @@
1
- import {
2
- BonnysFortunePrivateState,
3
- BonnysFortunePublicState,
4
- } from './bonnys-fortune.types';
5
-
6
- // ---------- Root Config ----------
7
- export interface BonnysFortuneGameLogicConfigInterface {
8
- baseGame: BonnysFortuneGameLogicConfigBaseGame;
9
- bonusGames: BonusGamesConfig;
10
- scatterGame: ScatterGameWrapper;
11
- }
12
-
13
- export interface BonnysFortuneSessionStateInterface {
14
- publicState: BonnysFortunePublicState;
15
- privateState: BonnysFortunePrivateState;
16
- }
17
-
18
- // ---------- Base Game ----------
19
- export interface BonnysFortuneGameLogicConfigBaseGame {
20
- symbolMultipliers: SymbolMultiplier[];
21
- symbolDefinitions: SymbolDefinitions;
22
- baseGameConfig: BaseGameConfig;
23
- paidHSymbolWeights: Record<string, number>;
24
- paidHSymbolReelWeights: Record<string, [number, number]>;
25
- paidScatterReelWeights: Record<string, [number, number]>;
26
- bonusGameMeters: Record<BonusGameId, BonusGameMeter>;
27
- }
28
-
29
- export type BonusGameId = 'bonusGame1' | 'bonusGame2' | 'bonusGame3';
30
-
31
- export interface SymbolMultiplier {
32
- symbolId: number;
33
- name: string;
34
- multipliers: Record<'x3' | 'x4' | 'x5', number>;
35
- isWild: boolean;
36
- isScatter: boolean;
37
- }
38
-
39
- export interface SymbolDefinitions {
40
- baseSymbols: { minIndex: number; maxIndex: number };
41
- specialSymbols: Record<string, number>;
42
- collectFeatureSymbols: {
43
- multipliers: Record<string, number>;
44
- values: Record<string, number>;
45
- };
46
- }
47
-
48
- export interface BaseGameConfig {
49
- layout: { rows: number; reels: number };
50
- winConfig: { minLength: number; maxLength: number };
51
- baseBet: number;
52
- reelsOptions: {
53
- numberOfOptions: number;
54
- optionWeights: number[];
55
- };
56
- }
57
-
58
- export interface BonusGameMeter {
59
- symbol: string;
60
- min: number;
61
- max: number;
62
- percentageLevels: {
63
- level1: { min: number; max: number; value: number };
64
- level2: { min: number; max: number; value: number };
65
- level3: { min: number; max: number; value: number };
66
- level4: { min: number; max: number; value: number };
67
- level5: { min: number; max: number; value: number };
68
- };
69
- }
70
-
71
- // ---------- Bonus Games Configs ----------
72
-
73
- export interface BonusGamesConfig {
74
- bonusGame1: TreasureHuntBonusGameConfig;
75
- bonusGame2: FreeSpinBonusGameConfig;
76
- bonusGame3: SteeringToTheFortuneBonusGameConfig;
77
- }
78
-
79
- export interface TreasureHuntBonusGameConfig {
80
- type: 'treasure_hunt';
81
- potMeter: BonusGameId;
82
- config: TreasureHuntBonusConfig;
83
- }
84
-
85
- export interface TreasureHuntBonusConfig {
86
- symbolsCount: number;
87
- stepsCount: number;
88
- jackpotMultipliers: {
89
- mini: number;
90
- minor: number;
91
- major: number;
92
- grand: number;
93
- };
94
- jackpotStartCounts: {
95
- mini: number;
96
- minor: number;
97
- major: number;
98
- grand: number;
99
- };
100
- jackpotScreenCounts: {
101
- mini: number;
102
- minor: number;
103
- major: number;
104
- grand: number;
105
- };
106
- winningWeights: {
107
- none: number;
108
- mini: number;
109
- minor: number;
110
- major: number;
111
- grand: number;
112
- };
113
- }
114
-
115
- export interface FreeSpinBonusGameConfig {
116
- type: 'free_spins';
117
- potMeter: BonusGameId;
118
- config: {
119
- spinTypes: SpinTypeConfig[];
120
- };
121
- }
122
-
123
- export interface SpinTypeConfig {
124
- layout: { rows: number; reels: number };
125
- spins: number;
126
- multiplier: number;
127
- weight: number;
128
- reelsOptionsWeights: number[];
129
- }
130
-
131
- export interface SteeringToTheFortuneBonusGameConfig {
132
- type: 'steering_to_the_fortune';
133
- potMeter: BonusGameId;
134
- config: SteeringToTheFortuneConfig;
135
- }
136
-
137
- export interface SteeringToTheFortuneConfig {
138
- wheelResults: {
139
- type: 'MULTIPLIER' | 'BONUS';
140
- value?: number;
141
- bonusType?: BonusGameId | 'collectFeature';
142
- weight: number;
143
- }[];
144
- }
145
-
146
- // ---------- Scatter Game ----------
147
- export interface ScatterGameWrapper {
148
- config: ScatterGameConfig;
149
- }
150
-
151
- export interface ScatterGameConfig {
152
- scatterMinNumber: number;
153
- scatterSpins: number;
154
- scatterCheckpoints: ScatterCheckpoint[];
155
- scatterSymbols: ScatterSymbol[];
156
- scatterSymbolSpins: WeightOnly[];
157
- scatterSymbolSlash: WeightOnly[];
158
- }
159
-
160
- export interface ScatterCheckpoint {
161
- spin: number;
162
- multiMinValue: number;
163
- valueMinValue: number;
164
- }
165
-
166
- export interface ScatterSymbol {
167
- symbolId: string;
168
- value: number;
169
- weight: number;
170
- }
171
-
172
- export interface WeightOnly {
173
- weight: number;
174
- }
@@ -1,213 +0,0 @@
1
- import {
2
- BonnysFortuneBaseGameSpinResult,
3
- BonnysFortunePrivateState,
4
- BonnysFortunePublicState,
5
- BonusTrigger,
6
- MetersState,
7
- SpinResultResponse,
8
- SpinType,
9
- } from '../bonnys-fortune.types';
10
-
11
- import { BonnysFortuneGameLogicConfigInterface } from '../game-logic-config.interface';
12
- import { BonnysFortuneSpinGenerator } from '../bonnys-fortune.spin-generator';
13
- import { BonnysFortuneWinCalculator } from '../bonnys-fortune.win-calculator';
14
- import { GameSpinInputDto } from '../../domain/types/game-spin-input.dto';
15
- import { NumberHelper } from '../../helpers/number-helper';
16
- import { ReelStripsConfig } from '../../domain/reel-strips-config';
17
- import { RngService } from '../../rng/rng-service';
18
- import { UuidHelper } from '../../helpers/uuid-helper';
19
-
20
- export class BaseGameHandler {
21
- constructor(private readonly rngService: RngService) {}
22
-
23
- public async handleSpin(
24
- params: GameSpinInputDto,
25
- publicState: BonnysFortunePublicState,
26
- privateState: BonnysFortunePrivateState,
27
- config: BonnysFortuneGameLogicConfigInterface,
28
- reelStripsConfig: ReelStripsConfig,
29
- commandId: string,
30
- ): Promise<SpinResultResponse<BonnysFortuneBaseGameSpinResult>> {
31
- if (privateState.nextSpinType !== SpinType.BASE_GAME_SPIN) {
32
- throw new Error(
33
- `Cannot execute base spin while a bonus spin (${privateState.nextSpinType}) is expected`,
34
- );
35
- }
36
-
37
- let screen = await new BonnysFortuneSpinGenerator(
38
- this.rngService,
39
- ).generateBaseScreen(
40
- config,
41
- reelStripsConfig,
42
- commandId,
43
- 'base_spin_screen',
44
- );
45
-
46
- screen = await new BonnysFortuneSpinGenerator(
47
- this.rngService,
48
- ).applyBonusSymbols(screen, config, commandId, 'base_spin_bonus_symbols');
49
-
50
- const updatedBonusMeters = this.updateBonusMeters(
51
- screen,
52
- privateState.bonusMetersByAmount[params.betAmount],
53
- config,
54
- );
55
-
56
- privateState.bonusMetersByAmount[params.betAmount] = updatedBonusMeters;
57
-
58
- const scatterSymbolId =
59
- config.baseGame.symbolDefinitions.specialSymbols.SCATTER;
60
-
61
- const betStake = this.getBetStake(params, config);
62
-
63
- const triggeredBonus = this.findTriggeredBonus(
64
- updatedBonusMeters,
65
- params.betAmount,
66
- screen,
67
- scatterSymbolId,
68
- );
69
-
70
- const nextSpinType = triggeredBonus
71
- ? getSpinTypeForBonus(triggeredBonus.bonusType)
72
- : SpinType.BASE_GAME_SPIN;
73
-
74
- const winResult = BonnysFortuneWinCalculator.calculateWins(
75
- screen,
76
- config,
77
- betStake,
78
- 1,
79
- SpinType.BASE_GAME_SPIN,
80
- );
81
-
82
- return {
83
- result: {
84
- spinType: SpinType.BASE_GAME_SPIN,
85
- screen,
86
- winResult,
87
- playerWinning: NumberHelper.toFixedNumber(winResult.totalWin, 2),
88
- nextSpinType,
89
- triggeredBonus,
90
- },
91
- publicState: {
92
- currentBetAmount: params.betAmount,
93
- bonusMeters: updatedBonusMeters,
94
- },
95
- privateState: {
96
- ...privateState,
97
- nextSpinType,
98
- pendingBonuses: triggeredBonus ? [triggeredBonus] : [],
99
- },
100
- };
101
- }
102
-
103
- private updateBonusMeters(
104
- screen: number[][],
105
- bonusMeters: MetersState,
106
- config?: BonnysFortuneGameLogicConfigInterface,
107
- ): MetersState {
108
- const symbolsInScreen = screen.flat();
109
-
110
- return Object.fromEntries(
111
- Object.entries(bonusMeters).map(([key, bonusMeter]) => {
112
- const count = symbolsInScreen.filter(
113
- (sym) => sym === bonusMeter.symbolId,
114
- ).length;
115
-
116
- const newProgress = NumberHelper.safeSum(bonusMeter.progress, count, 0);
117
-
118
- const cfg = config?.baseGame.bonusGameMeters[key as keyof typeof config.baseGame.bonusGameMeters];
119
- const percentageLevels = cfg?.percentageLevels ?? {};
120
- const level = this.getMeterLevel(
121
- newProgress,
122
- bonusMeter.threshold,
123
- percentageLevels,
124
- );
125
-
126
- return [key, { ...bonusMeter, progress: newProgress, level }];
127
- }),
128
- );
129
- }
130
-
131
- private findTriggeredBonus(
132
- bonusMeters: MetersState,
133
- betAmount: number,
134
- screen?: number[][],
135
- scatterSymbolId?: number,
136
- ): BonusTrigger | undefined {
137
- const triggeredPot = Object.entries(bonusMeters).find(
138
- ([, bonusMeter]) => bonusMeter.progress >= bonusMeter.threshold,
139
- );
140
- if (triggeredPot) {
141
- const [bonusType] = triggeredPot;
142
- return {
143
- bonusId: UuidHelper.generateUuid(),
144
- bonusType,
145
- betAmount: NumberHelper.toFixedNumber(betAmount, 2),
146
- };
147
- }
148
-
149
- if (screen && scatterSymbolId !== undefined) {
150
- const flatScreen = screen.flat();
151
- const scatterCount = flatScreen.filter(
152
- (sym) => sym === scatterSymbolId,
153
- ).length;
154
-
155
- if (scatterCount >= 3) {
156
- return {
157
- bonusId: UuidHelper.generateUuid(),
158
- bonusType: 'collectFeature',
159
- betAmount: NumberHelper.toFixedNumber(betAmount, 2),
160
- };
161
- }
162
- }
163
-
164
- return undefined;
165
- }
166
-
167
- private getMeterLevel(
168
- progress: number,
169
- threshold: number,
170
- percentageLevels: Record<
171
- string,
172
- { min: number; max: number; value: number }
173
- >,
174
- ): number {
175
- for (const [, range] of Object.entries(percentageLevels)) {
176
- const minValue = NumberHelper.safeMultiply(range.min, threshold, 4);
177
- const maxValue = NumberHelper.safeMultiply(range.max, threshold, 4);
178
-
179
- if (progress >= minValue && progress < maxValue) {
180
- return range.value;
181
- }
182
- }
183
-
184
- return 5;
185
- }
186
-
187
- private getBetStake(
188
- params: GameSpinInputDto,
189
- config: BonnysFortuneGameLogicConfigInterface,
190
- ): number {
191
- const baseBet = config.baseGame.baseGameConfig.baseBet;
192
- return NumberHelper.safeDivide(
193
- params.betAmount,
194
- params.creditsPerLine === baseBet ? params.creditsPerLine : baseBet,
195
- 3,
196
- );
197
- }
198
- }
199
-
200
- export function getSpinTypeForBonus(bonusType: string): SpinType {
201
- switch (bonusType) {
202
- case 'bonusGame1':
203
- return SpinType.BONUS_GAME_1_SPIN;
204
- case 'bonusGame2':
205
- return SpinType.BONUS_GAME_2_SPIN;
206
- case 'bonusGame3':
207
- return SpinType.BONUS_GAME_3_SPIN;
208
- case 'collectFeature':
209
- return SpinType.COLLECT_FEATURE_SPIN;
210
- default:
211
- return SpinType.BASE_GAME_SPIN;
212
- }
213
- }
@@ -1,301 +0,0 @@
1
- import {
2
- BonnysFortunePrivateState,
3
- BonnysFortunePublicState,
4
- BonusRoundResult,
5
- CollectFeatureSpinResult,
6
- SpinType,
7
- } from '../bonnys-fortune.types';
8
-
9
- import { BonnysFortuneGameLogicConfigInterface } from '../game-logic-config.interface';
10
- import { GameSpinInputDto } from '../../domain/types/game-spin-input.dto';
11
- import { NumberHelper } from '../../helpers/number-helper';
12
- import { RngService } from '../../rng/rng-service';
13
-
14
- interface ScatterSymbol {
15
- symbolId: string;
16
- value: number;
17
- weight: number;
18
- }
19
-
20
- export class CollectFeatureBonusHandler {
21
- constructor(private rngService: RngService) {}
22
-
23
- public async generateFullBonusRound(
24
- params: GameSpinInputDto,
25
- publicState: BonnysFortunePublicState,
26
- privateState: BonnysFortunePrivateState,
27
- config: BonnysFortuneGameLogicConfigInterface,
28
- commandId: string,
29
- ): Promise<BonusRoundResult<CollectFeatureSpinResult>> {
30
- const betStake = this.getBetStake(params, config);
31
- const scatterConfig = config.scatterGame.config;
32
- const results = [];
33
-
34
- const initialPublicState = { ...publicState };
35
- const initialPrivateState = { ...privateState };
36
-
37
- let spinsLeft = scatterConfig.scatterSpins;
38
- let totalValues = 0;
39
- let totalMultipliers = 1;
40
- let totalWin = 0;
41
-
42
- const checkpoints = await Promise.all(
43
- scatterConfig.scatterCheckpoints.map(async (cp, index) => {
44
- const actionId = `checkpoint_${index}`;
45
- const result = await this.rngService.getSingleNumber(
46
- 0,
47
- 1,
48
- commandId,
49
- actionId,
50
- );
51
- return cp.spin - 1 + result.value;
52
- }),
53
- );
54
-
55
- for (
56
- let spinIndex = 0;
57
- spinIndex < scatterConfig.scatterSpins;
58
- spinIndex++
59
- ) {
60
- const { result, updatedState, updatedPublicState, updatedPrivateState } =
61
- await this.executeSingleSpin(
62
- spinIndex,
63
- spinsLeft,
64
- totalValues,
65
- totalMultipliers,
66
- totalWin,
67
- betStake,
68
- scatterConfig,
69
- checkpoints,
70
- publicState,
71
- privateState,
72
- commandId,
73
- );
74
-
75
- results.push({
76
- result,
77
- publicState: updatedPublicState,
78
- privateState: updatedPrivateState,
79
- });
80
-
81
- totalValues = updatedState.totalValues;
82
- totalMultipliers = updatedState.totalMultipliers;
83
- totalWin = updatedState.totalWin;
84
- spinsLeft = updatedState.spinsLeft;
85
- publicState = updatedPublicState;
86
- privateState = updatedPrivateState;
87
-
88
- if (spinsLeft <= 0) break;
89
- }
90
-
91
- return {
92
- results,
93
- publicState: initialPublicState,
94
- privateState: initialPrivateState,
95
- };
96
- }
97
-
98
- private async executeSingleSpin(
99
- spinIndex: number,
100
- spinsLeft: number,
101
- totalValues: number,
102
- totalMultipliers: number,
103
- totalWin: number,
104
- betStake: number,
105
- scatterConfig: any,
106
- checkpoints: number[],
107
- publicState: BonnysFortunePublicState,
108
- privateState: BonnysFortunePrivateState,
109
- commandId: string,
110
- ): Promise<{
111
- result: CollectFeatureSpinResult;
112
- updatedState: {
113
- spinsLeft: number;
114
- totalValues: number;
115
- totalMultipliers: number;
116
- totalWin: number;
117
- };
118
- updatedPublicState: BonnysFortunePublicState;
119
- updatedPrivateState: BonnysFortunePrivateState;
120
- }> {
121
- const symbolCount = await this.weightedPick(
122
- scatterConfig.scatterSymbolSpins,
123
- commandId,
124
- `spin_${spinIndex}_symbol_count`,
125
- );
126
-
127
- const symbols: string[] = [];
128
- let multiCount = 0;
129
- let valueCount = 0;
130
-
131
- for (let i = 0; i < symbolCount; i++) {
132
- const symbol = await this.weightedPickSymbol(
133
- scatterConfig.scatterSymbols,
134
- commandId,
135
- `spin_${spinIndex}_symbol_${i}`,
136
- );
137
- symbols.push(symbol.symbolId);
138
- if (symbol.symbolId.startsWith('CM')) multiCount++;
139
- else valueCount++;
140
- }
141
-
142
- if (checkpoints.includes(spinsLeft)) {
143
- const cpIndex = checkpoints.indexOf(spinsLeft);
144
- const cp = scatterConfig.scatterCheckpoints[cpIndex];
145
- if (
146
- (totalMultipliers < cp.multiMinValue && multiCount === 0) ||
147
- (totalValues < cp.valueMinValue && valueCount === 0)
148
- ) {
149
- symbols[0] = 'CV1';
150
- }
151
- }
152
-
153
- const slashedCount = Math.min(
154
- await this.weightedPick(
155
- scatterConfig.scatterSymbolSlash,
156
- commandId,
157
- `spin_${spinIndex}_slashed_count`,
158
- ),
159
- symbolCount,
160
- );
161
- const slashed: boolean[] = Array(symbolCount).fill(false);
162
- for (let i = 0; i < slashedCount; i++) slashed[i] = true;
163
-
164
- let currentValues = 0;
165
- let currentMultipliers = 0;
166
-
167
- for (let i = 0; i < slashed.length; i++) {
168
- if (slashed[i]) {
169
- const symbol = scatterConfig.scatterSymbols.find(
170
- (s: ScatterSymbol) => s.symbolId === symbols[i],
171
- );
172
- if (!symbol) continue;
173
- if (symbols[i].startsWith('CM'))
174
- currentMultipliers = NumberHelper.safeSum(
175
- currentMultipliers,
176
- symbol.value,
177
- 2,
178
- );
179
- else
180
- currentValues = NumberHelper.safeSum(currentValues, symbol.value, 2);
181
- }
182
- }
183
-
184
- const newTotalValues = NumberHelper.safeSum(totalValues, currentValues, 2);
185
- const newTotalMultipliers = NumberHelper.safeSum(
186
- totalMultipliers,
187
- currentMultipliers,
188
- 2,
189
- );
190
- const newTotalWin = NumberHelper.safeMultiply(
191
- NumberHelper.safeMultiply(newTotalValues, newTotalMultipliers, 2),
192
- betStake,
193
- 2,
194
- );
195
- const playerWinning = NumberHelper.safeSum(newTotalWin, -totalWin, 2);
196
-
197
- spinsLeft -= 1;
198
-
199
- const result: CollectFeatureSpinResult = {
200
- spinIndex,
201
- spinType: SpinType.COLLECT_FEATURE_SPIN,
202
- symbols,
203
- slashed,
204
- currentValues,
205
- currentMultipliers,
206
- totalValues: newTotalValues,
207
- totalMultipliers: newTotalMultipliers,
208
- playerWinning,
209
- totalWin: newTotalWin,
210
- spinsLeft,
211
- nextSpinType:
212
- spinsLeft > 0 ? SpinType.COLLECT_FEATURE_SPIN : SpinType.BASE_GAME_SPIN,
213
- };
214
-
215
- const updatedPublicState: BonnysFortunePublicState = {
216
- ...publicState,
217
- };
218
-
219
- const updatedPrivateState: BonnysFortunePrivateState = {
220
- ...privateState,
221
- nextSpinType:
222
- spinsLeft > 0 ? SpinType.COLLECT_FEATURE_SPIN : SpinType.BASE_GAME_SPIN,
223
- activeBonus: spinsLeft > 0 ? privateState.activeBonus : undefined,
224
- };
225
-
226
- return {
227
- result,
228
- updatedState: {
229
- spinsLeft,
230
- totalValues: newTotalValues,
231
- totalMultipliers: newTotalMultipliers,
232
- totalWin: newTotalWin,
233
- },
234
- updatedPublicState,
235
- updatedPrivateState,
236
- };
237
- }
238
-
239
- private async weightedPick(
240
- items: Array<{ count?: number; weight: number }>,
241
- commandId: string,
242
- actionId: string,
243
- ): Promise<number> {
244
- const sum = items.reduce(
245
- (acc, item) => NumberHelper.safeSum(acc, item.weight, 2),
246
- 0,
247
- );
248
-
249
- let { value: draw } = await this.rngService.getSingleNumber(
250
- 0,
251
- sum - 1,
252
- commandId,
253
- actionId,
254
- );
255
-
256
- for (const item of items) {
257
- if (draw < item.weight) {
258
- return item.count || items.indexOf(item) + 1;
259
- }
260
- draw -= item.weight;
261
- }
262
- return 1;
263
- }
264
-
265
- private async weightedPickSymbol(
266
- symbols: ScatterSymbol[],
267
- commandId: string,
268
- actionId: string,
269
- ): Promise<ScatterSymbol> {
270
- const sum = symbols.reduce(
271
- (acc, s) => NumberHelper.safeSum(acc, s.weight, 2),
272
- 0,
273
- );
274
-
275
- const result = await this.rngService.getSingleNumber(
276
- 0,
277
- sum - 1,
278
- commandId,
279
- actionId,
280
- );
281
- let drawValue = result.value;
282
-
283
- for (const s of symbols) {
284
- if (drawValue < s.weight) return s;
285
- drawValue -= s.weight;
286
- }
287
- return symbols[0];
288
- }
289
-
290
- private getBetStake(
291
- params: GameSpinInputDto,
292
- config: BonnysFortuneGameLogicConfigInterface,
293
- ): number {
294
- const baseBet = config.baseGame.baseGameConfig.baseBet;
295
- return NumberHelper.safeDivide(
296
- params.betAmount,
297
- params.creditsPerLine === baseBet ? params.creditsPerLine : baseBet,
298
- 3,
299
- );
300
- }
301
- }