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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) 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 +4 -2
  14. package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
  15. package/dist/rng/DummyRngClient.js +8 -1
  16. package/dist/rng/DummyRngClient.js.map +1 -1
  17. package/dist/rng/rng-client.factory.js +1 -2
  18. package/dist/rng/rng-client.factory.js.map +1 -1
  19. package/package.json +3 -4
  20. package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +0 -1
  21. package/dist/bonnys-fortune-v1.game-engine.d.ts +0 -32
  22. package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +0 -4
  23. package/dist/config/game-logic-config/game-logic-config-loader.d.ts +0 -3
  24. package/dist/config/game-logic-config/game-logic-config.d.ts +0 -2
  25. package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +0 -4
  26. package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +0 -4
  27. package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +0 -3
  28. package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +0 -5
  29. package/dist/config/reel-strips-config/reel.dto.d.ts +0 -5
  30. package/dist/domain/game-round.types.d.ts +0 -9
  31. package/dist/domain/mappers/reel-strips-config.mapper.d.ts +0 -4
  32. package/dist/domain/reel-strip-option.d.ts +0 -7
  33. package/dist/domain/reel-strips-config.d.ts +0 -9
  34. package/dist/domain/reel.d.ts +0 -8
  35. package/dist/domain/types/cheat-trigger-bonus-command.d.ts +0 -5
  36. package/dist/domain/types/cheat-trigger-bonus-command.js.map +0 -1
  37. package/dist/domain/types/cheat-trigger-bonus-request.dto.d.ts +0 -5
  38. package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +0 -1
  39. package/dist/domain/types/cheat-update-bonus-meter-progress-command.d.ts +0 -5
  40. package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +0 -1
  41. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.d.ts +0 -5
  42. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +0 -1
  43. package/dist/domain/types/game-spin-command.d.ts +0 -8
  44. package/dist/domain/types/game-spin-input.dto.d.ts +0 -8
  45. package/dist/domain/types/game-spin-output.dto.d.ts +0 -119
  46. package/dist/domain/types/game-symbols.response.dto.d.ts +0 -10
  47. package/dist/domain/types/reel-strip-option.dto.d.ts +0 -4
  48. package/dist/domain/types/reel-strips-config.dto.d.ts +0 -5
  49. package/dist/domain/types/reel.dto.d.ts +0 -5
  50. package/dist/domain/types/start-bonus-round-command.d.ts +0 -8
  51. package/dist/domain/types/start-bonus-round-input.dto.d.ts +0 -10
  52. package/dist/game-engine.interface.d.ts +0 -41
  53. package/dist/helpers/generate-hash.d.ts +0 -1
  54. package/dist/helpers/number-helper.d.ts +0 -23
  55. package/dist/helpers/optional-boolean-mapper.d.ts +0 -1
  56. package/dist/helpers/uuid-helper.d.ts +0 -3
  57. package/dist/helpers/validation-helper.d.ts +0 -14
  58. package/dist/index.d.ts +0 -2
  59. package/dist/logger/logger.d.ts +0 -22
  60. package/dist/logic/bonnys-fortune.game-logic.d.ts +0 -32
  61. package/dist/logic/bonnys-fortune.spin-generator.d.ts +0 -19
  62. package/dist/logic/bonnys-fortune.types.d.ts +0 -170
  63. package/dist/logic/bonnys-fortune.win-calculator.d.ts +0 -10
  64. package/dist/logic/game-logic-config.interface.d.ts +0 -178
  65. package/dist/logic/handlers/base-game.handler.d.ts +0 -15
  66. package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +0 -13
  67. package/dist/logic/handlers/free-spins-bonus.handler.d.ts +0 -11
  68. package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +0 -11
  69. package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +0 -15
  70. package/dist/rng/DummyRngClient.d.ts +0 -18
  71. package/dist/rng/rng-client.factory.d.ts +0 -7
  72. package/dist/rng/rng-client.interface.d.ts +0 -16
  73. package/dist/rng/rng-service.d.ts +0 -26
  74. package/dist/validation/abstract-game-logic-config.validator.d.ts +0 -4
  75. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +0 -129
  76. package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +0 -5
  77. package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +0 -2
  78. package/dist/validation/game-logic-config-validation.service.d.ts +0 -8
  79. package/dist/validation/reel-strips-config-validation.service.d.ts +0 -5
  80. package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +0 -80
  81. package/src/bonnys-fortune-v1.game-engine.ts +0 -285
  82. package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +0 -27
  83. package/src/config/game-logic-config/game-logic-config-loader.ts +0 -3
  84. package/src/config/game-logic-config/game-logic-config.ts +0 -380
  85. package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +0 -43
  86. package/src/config/reel-strips-config/reel-strip-option.dto.ts +0 -13
  87. package/src/config/reel-strips-config/reel-strips-config-loader.ts +0 -9
  88. package/src/config/reel-strips-config/reel-strips-config.dto.ts +0 -16
  89. package/src/config/reel-strips-config/reel.dto.ts +0 -16
  90. package/src/config/reel-strips-config/reels-BASE.csv +0 -52
  91. package/src/config/reel-strips-config/reels-BONUS.csv +0 -52
  92. package/src/domain/game-round.types.ts +0 -10
  93. package/src/domain/mappers/reel-strips-config.mapper.ts +0 -16
  94. package/src/domain/reel-strip-option.ts +0 -15
  95. package/src/domain/reel-strips-config.ts +0 -21
  96. package/src/domain/reel.ts +0 -17
  97. package/src/domain/types/cheat-trigger-bonus-command.ts +0 -5
  98. package/src/domain/types/cheat-trigger-bonus-request.dto.ts +0 -5
  99. package/src/domain/types/cheat-update-bonus-meter-progress-command.ts +0 -6
  100. package/src/domain/types/cheat-update-bonus-meter-progress-request.dto.ts +0 -5
  101. package/src/domain/types/game-spin-command.ts +0 -8
  102. package/src/domain/types/game-spin-input.dto.ts +0 -8
  103. package/src/domain/types/game-spin-output.dto.ts +0 -142
  104. package/src/domain/types/game-symbols.response.dto.ts +0 -11
  105. package/src/domain/types/reel-strip-option.dto.ts +0 -13
  106. package/src/domain/types/reel-strips-config.dto.ts +0 -15
  107. package/src/domain/types/reel.dto.ts +0 -15
  108. package/src/domain/types/start-bonus-round-command.ts +0 -8
  109. package/src/domain/types/start-bonus-round-input.dto.ts +0 -10
  110. package/src/game-engine.interface.ts +0 -59
  111. package/src/helpers/generate-hash.ts +0 -5
  112. package/src/helpers/number-helper.ts +0 -41
  113. package/src/helpers/optional-boolean-mapper.ts +0 -5
  114. package/src/helpers/uuid-helper.ts +0 -7
  115. package/src/helpers/validation-helper.ts +0 -27
  116. package/src/index.ts +0 -3
  117. package/src/logger/logger.ts +0 -178
  118. package/src/logic/bonnys-fortune.game-logic.ts +0 -480
  119. package/src/logic/bonnys-fortune.spin-generator.ts +0 -277
  120. package/src/logic/bonnys-fortune.types.ts +0 -223
  121. package/src/logic/bonnys-fortune.win-calculator.ts +0 -210
  122. package/src/logic/game-logic-config.interface.ts +0 -174
  123. package/src/logic/handlers/base-game.handler.ts +0 -213
  124. package/src/logic/handlers/collect-feature-bonus.handler.ts +0 -301
  125. package/src/logic/handlers/free-spins-bonus.handler.ts +0 -119
  126. package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +0 -118
  127. package/src/logic/handlers/treasure-hunt-bonus.handler.ts +0 -232
  128. package/src/rng/DummyRngClient.ts +0 -108
  129. package/src/rng/rng-client.factory.ts +0 -27
  130. package/src/rng/rng-client.interface.ts +0 -38
  131. package/src/rng/rng-service.ts +0 -130
  132. package/src/validation/abstract-game-logic-config.validator.ts +0 -20
  133. package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +0 -371
  134. package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +0 -8
  135. package/src/validation/custom-decorators/IsNestedIntArray.ts +0 -29
  136. package/src/validation/game-logic-config-validation.service.ts +0 -28
  137. 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
- }