@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.
Files changed (175) hide show
  1. package/README.md +25 -15
  2. package/dist/__tests__/base-game.test.js +255 -0
  3. package/dist/__tests__/base-game.test.js.map +1 -0
  4. package/dist/__tests__/bonnys-fortune-v1.game-engine.test.js +1 -1
  5. package/dist/__tests__/bonus-handlers.test.js +303 -0
  6. package/dist/__tests__/bonus-handlers.test.js.map +1 -0
  7. package/dist/__tests__/bonus-meters.test.js +508 -0
  8. package/dist/__tests__/bonus-meters.test.js.map +1 -0
  9. package/dist/__tests__/collect-feature-handler.test.js +386 -0
  10. package/dist/__tests__/collect-feature-handler.test.js.map +1 -0
  11. package/dist/__tests__/comprehensive.test.js +747 -0
  12. package/dist/__tests__/comprehensive.test.js.map +1 -0
  13. package/dist/__tests__/error-paths.test.js +373 -0
  14. package/dist/__tests__/error-paths.test.js.map +1 -0
  15. package/dist/__tests__/helpers/test-engine-factory.js +268 -0
  16. package/dist/__tests__/helpers/test-engine-factory.js.map +1 -0
  17. package/dist/__tests__/rng-gli19-compliance.test.js +292 -0
  18. package/dist/__tests__/rng-gli19-compliance.test.js.map +1 -0
  19. package/dist/__tests__/rng-security.test.js +383 -0
  20. package/dist/__tests__/rng-security.test.js.map +1 -0
  21. package/dist/__tests__/rtp-simulation.test.js +242 -0
  22. package/dist/__tests__/rtp-simulation.test.js.map +1 -0
  23. package/dist/__tests__/state-transitions.test.js +307 -0
  24. package/dist/__tests__/state-transitions.test.js.map +1 -0
  25. package/dist/__tests__/steering-fortune-handler.test.js +286 -0
  26. package/dist/__tests__/steering-fortune-handler.test.js.map +1 -0
  27. package/dist/__tests__/symbol-distribution.test.js +195 -0
  28. package/dist/__tests__/symbol-distribution.test.js.map +1 -0
  29. package/dist/__tests__/treasure-hunt-handler.test.js +295 -0
  30. package/dist/__tests__/treasure-hunt-handler.test.js.map +1 -0
  31. package/dist/__tests__/win-calculator.test.js +515 -0
  32. package/dist/__tests__/win-calculator.test.js.map +1 -0
  33. package/dist/bonnys-fortune-v1.game-engine.js +14 -25
  34. package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
  35. package/dist/config/game-logic-config/game-logic-config.js +0 -2
  36. package/dist/config/game-logic-config/game-logic-config.js.map +1 -1
  37. package/dist/domain/types/{cheat-trigger-bonus-command.js → debug-trigger-bonus-command.js} +1 -1
  38. package/dist/domain/types/debug-trigger-bonus-command.js.map +1 -0
  39. package/dist/domain/types/{cheat-trigger-bonus-request.dto.js → debug-trigger-bonus-request.dto.js} +1 -1
  40. package/dist/domain/types/debug-trigger-bonus-request.dto.js.map +1 -0
  41. package/dist/domain/types/{cheat-update-bonus-meter-progress-command.js → debug-update-bonus-meter-progress-command.js} +1 -1
  42. package/dist/domain/types/debug-update-bonus-meter-progress-command.js.map +1 -0
  43. package/dist/domain/types/{cheat-update-bonus-meter-progress-request.dto.js → debug-update-bonus-meter-progress-request.dto.js} +1 -1
  44. package/dist/domain/types/debug-update-bonus-meter-progress-request.dto.js.map +1 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/logic/bonnys-fortune.game-logic.js +37 -17
  47. package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
  48. package/dist/logic/handlers/base-game.handler.js +1 -5
  49. package/dist/logic/handlers/base-game.handler.js.map +1 -1
  50. package/dist/rng/DummyRngClient.js +8 -1
  51. package/dist/rng/DummyRngClient.js.map +1 -1
  52. package/dist/rng/rng-client.factory.js.map +1 -1
  53. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js +0 -9
  54. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
  55. package/package.json +6 -6
  56. package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +0 -1
  57. package/dist/bonnys-fortune-v1.game-engine.d.ts +0 -36
  58. package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +0 -4
  59. package/dist/config/game-logic-config/game-logic-config-loader.d.ts +0 -3
  60. package/dist/config/game-logic-config/game-logic-config.d.ts +0 -2
  61. package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +0 -4
  62. package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +0 -4
  63. package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +0 -3
  64. package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +0 -5
  65. package/dist/config/reel-strips-config/reel.dto.d.ts +0 -5
  66. package/dist/domain/game-round.types.d.ts +0 -9
  67. package/dist/domain/mappers/reel-strips-config.mapper.d.ts +0 -4
  68. package/dist/domain/reel-strip-option.d.ts +0 -7
  69. package/dist/domain/reel-strips-config.d.ts +0 -9
  70. package/dist/domain/reel.d.ts +0 -8
  71. package/dist/domain/types/cheat-trigger-bonus-command.d.ts +0 -5
  72. package/dist/domain/types/cheat-trigger-bonus-command.js.map +0 -1
  73. package/dist/domain/types/cheat-trigger-bonus-request.dto.d.ts +0 -5
  74. package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +0 -1
  75. package/dist/domain/types/cheat-update-bonus-meter-progress-command.d.ts +0 -5
  76. package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +0 -1
  77. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.d.ts +0 -5
  78. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +0 -1
  79. package/dist/domain/types/game-spin-command.d.ts +0 -8
  80. package/dist/domain/types/game-spin-input.dto.d.ts +0 -8
  81. package/dist/domain/types/game-spin-output.dto.d.ts +0 -119
  82. package/dist/domain/types/game-symbols.response.dto.d.ts +0 -10
  83. package/dist/domain/types/reel-strip-option.dto.d.ts +0 -4
  84. package/dist/domain/types/reel-strips-config.dto.d.ts +0 -5
  85. package/dist/domain/types/reel.dto.d.ts +0 -5
  86. package/dist/domain/types/start-bonus-round-command.d.ts +0 -8
  87. package/dist/domain/types/start-bonus-round-input.dto.d.ts +0 -10
  88. package/dist/game-engine.interface.d.ts +0 -41
  89. package/dist/helpers/generate-hash.d.ts +0 -1
  90. package/dist/helpers/number-helper.d.ts +0 -23
  91. package/dist/helpers/optional-boolean-mapper.d.ts +0 -1
  92. package/dist/helpers/uuid-helper.d.ts +0 -3
  93. package/dist/helpers/validation-helper.d.ts +0 -14
  94. package/dist/index.d.ts +0 -2
  95. package/dist/logger/logger.d.ts +0 -22
  96. package/dist/logger/logger.js +0 -140
  97. package/dist/logger/logger.js.map +0 -1
  98. package/dist/logic/bonnys-fortune.game-logic.d.ts +0 -32
  99. package/dist/logic/bonnys-fortune.spin-generator.d.ts +0 -19
  100. package/dist/logic/bonnys-fortune.types.d.ts +0 -170
  101. package/dist/logic/bonnys-fortune.win-calculator.d.ts +0 -10
  102. package/dist/logic/game-logic-config.interface.d.ts +0 -180
  103. package/dist/logic/handlers/base-game.handler.d.ts +0 -15
  104. package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +0 -13
  105. package/dist/logic/handlers/free-spins-bonus.handler.d.ts +0 -11
  106. package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +0 -11
  107. package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +0 -15
  108. package/dist/rng/DummyRngClient.d.ts +0 -18
  109. package/dist/rng/rng-client.factory.d.ts +0 -7
  110. package/dist/rng/rng-client.interface.d.ts +0 -16
  111. package/dist/rng/rng-service.d.ts +0 -26
  112. package/dist/validation/abstract-game-logic-config.validator.d.ts +0 -4
  113. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +0 -131
  114. package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +0 -5
  115. package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +0 -2
  116. package/dist/validation/game-logic-config-validation.service.d.ts +0 -8
  117. package/dist/validation/reel-strips-config-validation.service.d.ts +0 -5
  118. package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +0 -80
  119. package/src/bonnys-fortune-v1.game-engine.ts +0 -314
  120. package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +0 -27
  121. package/src/config/game-logic-config/game-logic-config-loader.ts +0 -3
  122. package/src/config/game-logic-config/game-logic-config.ts +0 -382
  123. package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +0 -43
  124. package/src/config/reel-strips-config/reel-strip-option.dto.ts +0 -13
  125. package/src/config/reel-strips-config/reel-strips-config-loader.ts +0 -9
  126. package/src/config/reel-strips-config/reel-strips-config.dto.ts +0 -16
  127. package/src/config/reel-strips-config/reel.dto.ts +0 -16
  128. package/src/config/reel-strips-config/reels-BASE.csv +0 -52
  129. package/src/config/reel-strips-config/reels-BONUS.csv +0 -52
  130. package/src/domain/game-round.types.ts +0 -10
  131. package/src/domain/mappers/reel-strips-config.mapper.ts +0 -16
  132. package/src/domain/reel-strip-option.ts +0 -15
  133. package/src/domain/reel-strips-config.ts +0 -21
  134. package/src/domain/reel.ts +0 -17
  135. package/src/domain/types/cheat-trigger-bonus-command.ts +0 -5
  136. package/src/domain/types/cheat-trigger-bonus-request.dto.ts +0 -5
  137. package/src/domain/types/cheat-update-bonus-meter-progress-command.ts +0 -6
  138. package/src/domain/types/cheat-update-bonus-meter-progress-request.dto.ts +0 -5
  139. package/src/domain/types/game-spin-command.ts +0 -8
  140. package/src/domain/types/game-spin-input.dto.ts +0 -8
  141. package/src/domain/types/game-spin-output.dto.ts +0 -142
  142. package/src/domain/types/game-symbols.response.dto.ts +0 -11
  143. package/src/domain/types/reel-strip-option.dto.ts +0 -13
  144. package/src/domain/types/reel-strips-config.dto.ts +0 -15
  145. package/src/domain/types/reel.dto.ts +0 -15
  146. package/src/domain/types/start-bonus-round-command.ts +0 -8
  147. package/src/domain/types/start-bonus-round-input.dto.ts +0 -10
  148. package/src/game-engine.interface.ts +0 -59
  149. package/src/helpers/generate-hash.ts +0 -5
  150. package/src/helpers/number-helper.ts +0 -41
  151. package/src/helpers/optional-boolean-mapper.ts +0 -5
  152. package/src/helpers/uuid-helper.ts +0 -7
  153. package/src/helpers/validation-helper.ts +0 -27
  154. package/src/index.ts +0 -3
  155. package/src/logger/logger.ts +0 -178
  156. package/src/logic/bonnys-fortune.game-logic.ts +0 -490
  157. package/src/logic/bonnys-fortune.spin-generator.ts +0 -277
  158. package/src/logic/bonnys-fortune.types.ts +0 -223
  159. package/src/logic/bonnys-fortune.win-calculator.ts +0 -210
  160. package/src/logic/game-logic-config.interface.ts +0 -176
  161. package/src/logic/handlers/base-game.handler.ts +0 -221
  162. package/src/logic/handlers/collect-feature-bonus.handler.ts +0 -301
  163. package/src/logic/handlers/free-spins-bonus.handler.ts +0 -119
  164. package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +0 -118
  165. package/src/logic/handlers/treasure-hunt-bonus.handler.ts +0 -232
  166. package/src/rng/DummyRngClient.ts +0 -108
  167. package/src/rng/rng-client.factory.ts +0 -27
  168. package/src/rng/rng-client.interface.ts +0 -38
  169. package/src/rng/rng-service.ts +0 -130
  170. package/src/validation/abstract-game-logic-config.validator.ts +0 -20
  171. package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +0 -379
  172. package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +0 -8
  173. package/src/validation/custom-decorators/IsNestedIntArray.ts +0 -29
  174. package/src/validation/game-logic-config-validation.service.ts +0 -28
  175. package/src/validation/reel-strips-config-validation.service.ts +0 -29
@@ -1,176 +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
- defaultBetAmountThresholds: number[];
12
- defaultBetAmount: number;
13
- }
14
-
15
- export interface BonnysFortuneSessionStateInterface {
16
- publicState: BonnysFortunePublicState;
17
- privateState: BonnysFortunePrivateState;
18
- }
19
-
20
- // ---------- Base Game ----------
21
- export interface BonnysFortuneGameLogicConfigBaseGame {
22
- symbolMultipliers: SymbolMultiplier[];
23
- symbolDefinitions: SymbolDefinitions;
24
- baseGameConfig: BaseGameConfig;
25
- paidHSymbolWeights: Record<string, number>;
26
- paidHSymbolReelWeights: Record<string, [number, number]>;
27
- paidScatterReelWeights: Record<string, [number, number]>;
28
- bonusGameMeters: Record<BonusGameId, BonusGameMeter>;
29
- }
30
-
31
- export type BonusGameId = 'bonusGame1' | 'bonusGame2' | 'bonusGame3';
32
-
33
- export interface SymbolMultiplier {
34
- symbolId: number;
35
- name: string;
36
- multipliers: Record<'x3' | 'x4' | 'x5', number>;
37
- isWild: boolean;
38
- isScatter: boolean;
39
- }
40
-
41
- export interface SymbolDefinitions {
42
- baseSymbols: { minIndex: number; maxIndex: number };
43
- specialSymbols: Record<string, number>;
44
- collectFeatureSymbols: {
45
- multipliers: Record<string, number>;
46
- values: Record<string, number>;
47
- };
48
- }
49
-
50
- export interface BaseGameConfig {
51
- layout: { rows: number; reels: number };
52
- winConfig: { minLength: number; maxLength: number };
53
- baseBet: number;
54
- reelsOptions: {
55
- numberOfOptions: number;
56
- optionWeights: number[];
57
- };
58
- }
59
-
60
- export interface BonusGameMeter {
61
- symbol: string;
62
- min: number;
63
- max: number;
64
- percentageLevels: {
65
- level1: { min: number; max: number; value: number };
66
- level2: { min: number; max: number; value: number };
67
- level3: { min: number; max: number; value: number };
68
- level4: { min: number; max: number; value: number };
69
- level5: { min: number; max: number; value: number };
70
- };
71
- }
72
-
73
- // ---------- Bonus Games Configs ----------
74
-
75
- export interface BonusGamesConfig {
76
- bonusGame1: TreasureHuntBonusGameConfig;
77
- bonusGame2: FreeSpinBonusGameConfig;
78
- bonusGame3: SteeringToTheFortuneBonusGameConfig;
79
- }
80
-
81
- export interface TreasureHuntBonusGameConfig {
82
- type: 'treasure_hunt';
83
- potMeter: BonusGameId;
84
- config: TreasureHuntBonusConfig;
85
- }
86
-
87
- export interface TreasureHuntBonusConfig {
88
- symbolsCount: number;
89
- stepsCount: number;
90
- jackpotMultipliers: {
91
- mini: number;
92
- minor: number;
93
- major: number;
94
- grand: number;
95
- };
96
- jackpotStartCounts: {
97
- mini: number;
98
- minor: number;
99
- major: number;
100
- grand: number;
101
- };
102
- jackpotScreenCounts: {
103
- mini: number;
104
- minor: number;
105
- major: number;
106
- grand: number;
107
- };
108
- winningWeights: {
109
- none: number;
110
- mini: number;
111
- minor: number;
112
- major: number;
113
- grand: number;
114
- };
115
- }
116
-
117
- export interface FreeSpinBonusGameConfig {
118
- type: 'free_spins';
119
- potMeter: BonusGameId;
120
- config: {
121
- spinTypes: SpinTypeConfig[];
122
- };
123
- }
124
-
125
- export interface SpinTypeConfig {
126
- layout: { rows: number; reels: number };
127
- spins: number;
128
- multiplier: number;
129
- weight: number;
130
- reelsOptionsWeights: number[];
131
- }
132
-
133
- export interface SteeringToTheFortuneBonusGameConfig {
134
- type: 'steering_to_the_fortune';
135
- potMeter: BonusGameId;
136
- config: SteeringToTheFortuneConfig;
137
- }
138
-
139
- export interface SteeringToTheFortuneConfig {
140
- wheelResults: {
141
- type: 'MULTIPLIER' | 'BONUS';
142
- value?: number;
143
- bonusType?: BonusGameId | 'collectFeature';
144
- weight: number;
145
- }[];
146
- }
147
-
148
- // ---------- Scatter Game ----------
149
- export interface ScatterGameWrapper {
150
- config: ScatterGameConfig;
151
- }
152
-
153
- export interface ScatterGameConfig {
154
- scatterMinNumber: number;
155
- scatterSpins: number;
156
- scatterCheckpoints: ScatterCheckpoint[];
157
- scatterSymbols: ScatterSymbol[];
158
- scatterSymbolSpins: WeightOnly[];
159
- scatterSymbolSlash: WeightOnly[];
160
- }
161
-
162
- export interface ScatterCheckpoint {
163
- spin: number;
164
- multiMinValue: number;
165
- valueMinValue: number;
166
- }
167
-
168
- export interface ScatterSymbol {
169
- symbolId: string;
170
- value: number;
171
- weight: number;
172
- }
173
-
174
- export interface WeightOnly {
175
- weight: number;
176
- }
@@ -1,221 +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 bonusMetersForBetAmount = privateState.bonusMetersByAmount[params.betAmount];
51
-
52
- if (!bonusMetersForBetAmount) {
53
- throw new Error(
54
- `Bet amount ${params.betAmount} is not in allowed thresholds`,
55
- );
56
- }
57
-
58
- const updatedBonusMeters = this.updateBonusMeters(
59
- screen,
60
- bonusMetersForBetAmount,
61
- config,
62
- );
63
-
64
- privateState.bonusMetersByAmount[params.betAmount] = updatedBonusMeters;
65
-
66
- const scatterSymbolId =
67
- config.baseGame.symbolDefinitions.specialSymbols.SCATTER;
68
-
69
- const betStake = this.getBetStake(params, config);
70
-
71
- const triggeredBonus = this.findTriggeredBonus(
72
- updatedBonusMeters,
73
- params.betAmount,
74
- screen,
75
- scatterSymbolId,
76
- );
77
-
78
- const nextSpinType = triggeredBonus
79
- ? getSpinTypeForBonus(triggeredBonus.bonusType)
80
- : SpinType.BASE_GAME_SPIN;
81
-
82
- const winResult = BonnysFortuneWinCalculator.calculateWins(
83
- screen,
84
- config,
85
- betStake,
86
- 1,
87
- SpinType.BASE_GAME_SPIN,
88
- );
89
-
90
- return {
91
- result: {
92
- spinType: SpinType.BASE_GAME_SPIN,
93
- screen,
94
- winResult,
95
- playerWinning: NumberHelper.toFixedNumber(winResult.totalWin, 2),
96
- nextSpinType,
97
- triggeredBonus,
98
- },
99
- publicState: {
100
- currentBetAmount: params.betAmount,
101
- bonusMeters: updatedBonusMeters,
102
- },
103
- privateState: {
104
- ...privateState,
105
- nextSpinType,
106
- pendingBonuses: triggeredBonus ? [triggeredBonus] : [],
107
- },
108
- };
109
- }
110
-
111
- private updateBonusMeters(
112
- screen: number[][],
113
- bonusMeters: MetersState,
114
- config?: BonnysFortuneGameLogicConfigInterface,
115
- ): MetersState {
116
- const symbolsInScreen = screen.flat();
117
-
118
- return Object.fromEntries(
119
- Object.entries(bonusMeters).map(([key, bonusMeter]) => {
120
- const count = symbolsInScreen.filter(
121
- (sym) => sym === bonusMeter.symbolId,
122
- ).length;
123
-
124
- const newProgress = NumberHelper.safeSum(bonusMeter.progress, count, 0);
125
-
126
- const cfg = config?.baseGame.bonusGameMeters[key as keyof typeof config.baseGame.bonusGameMeters];
127
- const percentageLevels = cfg?.percentageLevels ?? {};
128
- const level = this.getMeterLevel(
129
- newProgress,
130
- bonusMeter.threshold,
131
- percentageLevels,
132
- );
133
-
134
- return [key, { ...bonusMeter, progress: newProgress, level }];
135
- }),
136
- );
137
- }
138
-
139
- private findTriggeredBonus(
140
- bonusMeters: MetersState,
141
- betAmount: number,
142
- screen?: number[][],
143
- scatterSymbolId?: number,
144
- ): BonusTrigger | undefined {
145
- const triggeredPot = Object.entries(bonusMeters).find(
146
- ([, bonusMeter]) => bonusMeter.progress >= bonusMeter.threshold,
147
- );
148
- if (triggeredPot) {
149
- const [bonusType] = triggeredPot;
150
- return {
151
- bonusId: UuidHelper.generateUuid(),
152
- bonusType,
153
- betAmount: NumberHelper.toFixedNumber(betAmount, 2),
154
- };
155
- }
156
-
157
- if (screen && scatterSymbolId !== undefined) {
158
- const flatScreen = screen.flat();
159
- const scatterCount = flatScreen.filter(
160
- (sym) => sym === scatterSymbolId,
161
- ).length;
162
-
163
- if (scatterCount >= 3) {
164
- return {
165
- bonusId: UuidHelper.generateUuid(),
166
- bonusType: 'collectFeature',
167
- betAmount: NumberHelper.toFixedNumber(betAmount, 2),
168
- };
169
- }
170
- }
171
-
172
- return undefined;
173
- }
174
-
175
- private getMeterLevel(
176
- progress: number,
177
- threshold: number,
178
- percentageLevels: Record<
179
- string,
180
- { min: number; max: number; value: number }
181
- >,
182
- ): number {
183
- for (const [, range] of Object.entries(percentageLevels)) {
184
- const minValue = NumberHelper.safeMultiply(range.min, threshold, 4);
185
- const maxValue = NumberHelper.safeMultiply(range.max, threshold, 4);
186
-
187
- if (progress >= minValue && progress < maxValue) {
188
- return range.value;
189
- }
190
- }
191
-
192
- return 5;
193
- }
194
-
195
- private getBetStake(
196
- params: GameSpinInputDto,
197
- config: BonnysFortuneGameLogicConfigInterface,
198
- ): number {
199
- const baseBet = config.baseGame.baseGameConfig.baseBet;
200
- return NumberHelper.safeDivide(
201
- params.betAmount,
202
- params.creditsPerLine === baseBet ? params.creditsPerLine : baseBet,
203
- 3,
204
- );
205
- }
206
- }
207
-
208
- export function getSpinTypeForBonus(bonusType: string): SpinType {
209
- switch (bonusType) {
210
- case 'bonusGame1':
211
- return SpinType.BONUS_GAME_1_SPIN;
212
- case 'bonusGame2':
213
- return SpinType.BONUS_GAME_2_SPIN;
214
- case 'bonusGame3':
215
- return SpinType.BONUS_GAME_3_SPIN;
216
- case 'collectFeature':
217
- return SpinType.COLLECT_FEATURE_SPIN;
218
- default:
219
- return SpinType.BASE_GAME_SPIN;
220
- }
221
- }
@@ -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
- }