@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,7 +0,0 @@
1
- import { v4 } from 'uuid';
2
-
3
- export class UuidHelper {
4
- public static generateUuid(): string {
5
- return v4();
6
- }
7
- }
@@ -1,27 +0,0 @@
1
- import { validate, ValidationError } from 'class-validator';
2
-
3
- export class ValidationHelper {
4
- public static readonly validationOptions = {
5
- whitelist: true,
6
- forbidNonWhitelisted: true,
7
- };
8
-
9
- public static readonly validationOptionsWithoutForbidden = {
10
- whitelist: true,
11
- forbidNonWhitelisted: false,
12
- };
13
-
14
- public static validate<T extends object>(dto: T): Promise<ValidationError[]> {
15
- return validate(dto, ValidationHelper.validationOptions);
16
- }
17
-
18
- public static validateWithoutForbidden<T extends object>(
19
- dto: T,
20
- ): Promise<ValidationError[]> {
21
- return validate(dto, ValidationHelper.validationOptionsWithoutForbidden);
22
- }
23
-
24
- public static isPlainObject(value: unknown): value is Record<string, any> {
25
- return typeof value === 'object' && value !== null && !Array.isArray(value);
26
- }
27
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- import 'reflect-metadata';
2
-
3
- export { BonnysFortuneV1GameEngine } from './bonnys-fortune-v1.game-engine';
@@ -1,178 +0,0 @@
1
- import {
2
- Logger as WinstonLogger,
3
- createLogger,
4
- format,
5
- transports,
6
- } from 'winston';
7
-
8
- type LogFormat = 'json' | 'pretty';
9
-
10
- export class Logger {
11
- private readonly logger: WinstonLogger;
12
- private context: string;
13
-
14
- constructor(context: string, logFormat?: LogFormat) {
15
- this.context = context;
16
- const format = logFormat ?? this.detectLogFormat();
17
- this.logger = this.createWinstonLogger(format);
18
- }
19
-
20
- private detectLogFormat(): LogFormat {
21
- const envFormat = process.env.LOG_FORMAT?.toLowerCase();
22
-
23
- switch (envFormat) {
24
- case 'pretty':
25
- return 'pretty';
26
- case 'json':
27
- return 'json';
28
- default:
29
- return 'json';
30
- }
31
- }
32
-
33
- private createWinstonLogger(logFormat: LogFormat): WinstonLogger {
34
- const winstonFormat = this.getLogFormat(logFormat);
35
-
36
- return createLogger({
37
- level: 'info',
38
- format: winstonFormat,
39
- transports: this.getTransports(),
40
- });
41
- }
42
-
43
- private getLogFormat(logFormat: LogFormat) {
44
- return format.combine(format.timestamp(), this.selectFormat(logFormat));
45
- }
46
-
47
- private selectFormat(logFormat: LogFormat) {
48
- switch (logFormat) {
49
- case 'pretty':
50
- return this.getPrettyFormat();
51
- case 'json':
52
- return this.getJsonFormat();
53
- default:
54
- return this.getJsonFormat();
55
- }
56
- }
57
-
58
- private getJsonFormat() {
59
- return format.combine(
60
- format((info) => {
61
- if (this.context) {
62
- info.message = `[${this.context}] ${info.message}`;
63
- }
64
- return info;
65
- })(),
66
- format.json(),
67
- );
68
- }
69
-
70
- private getPrettyFormat() {
71
- return format.combine(
72
- format.colorize(),
73
- format.printf(({ timestamp, level, message, error, ...meta }) => {
74
- const contextStr = this.context ? `[${this.context}] ` : '';
75
- let output = `${timestamp} ${level}: ${contextStr}${message}`;
76
-
77
- if (
78
- error &&
79
- typeof error === 'object' &&
80
- 'name' in error &&
81
- 'message' in error
82
- ) {
83
- output += `\n Error: ${error.name}: ${error.message}`;
84
-
85
- if (error.name === 'DomainException' && 'errorCode' in error) {
86
- output += `\n Internal Code: ${error.errorCode}`;
87
- }
88
-
89
- if ('stack' in error && error.stack) {
90
- output += `\n Stack: ${error.stack}`;
91
- }
92
- }
93
-
94
- const metaKeys = Object.keys(meta).filter(
95
- (key) => key !== 'timestamp' && key !== 'level' && key !== 'message',
96
- );
97
- if (metaKeys.length > 0) {
98
- output += `\n Meta: ${JSON.stringify(meta, null, 2)}`;
99
- }
100
-
101
- return output;
102
- }),
103
- );
104
- }
105
-
106
- private safeStringify(obj: any): string {
107
- try {
108
- return JSON.stringify(obj, (key, value) => {
109
- if (typeof value === 'bigint') {
110
- return value.toString();
111
- }
112
- return value;
113
- });
114
- } catch (error) {
115
- return '[Object cannot be serialized]';
116
- }
117
- }
118
-
119
- private getTransports() {
120
- return [
121
- new transports.Console(),
122
- new transports.File({ filename: 'logs/app.log' }),
123
- ];
124
- }
125
-
126
- log(
127
- message: any,
128
- { level = 'info', ...rest }: { level?: string } & Record<string, any> = {},
129
- ) {
130
- switch (level) {
131
- case 'error':
132
- this.logger.error(message, rest);
133
- break;
134
- case 'warn':
135
- this.logger.warn(message, rest);
136
- break;
137
- case 'debug':
138
- this.logger.debug(message, rest);
139
- break;
140
- case 'verbose':
141
- this.logger.verbose(message, rest);
142
- break;
143
- default:
144
- this.logger.info(message, rest);
145
- break;
146
- }
147
- }
148
-
149
- error(message: any, ...optionalParams: any[]) {
150
- if (optionalParams.length > 0 && optionalParams[0] instanceof Error) {
151
- const error = optionalParams[0];
152
- const errorData: any = {
153
- name: error.name,
154
- message: error.message,
155
- stack: error.stack,
156
- };
157
-
158
- this.logger.error(message, {
159
- error: errorData,
160
- ...optionalParams.slice(1),
161
- });
162
- } else {
163
- this.logger.error(message, ...optionalParams);
164
- }
165
- }
166
-
167
- warn(message: any, ...optionalParams: any[]) {
168
- this.logger.warn(message, ...optionalParams);
169
- }
170
-
171
- debug(message: any, ...optionalParams: any[]) {
172
- this.logger.debug(message, ...optionalParams);
173
- }
174
-
175
- verbose(message: any, ...optionalParams: any[]) {
176
- this.logger.verbose(message, ...optionalParams);
177
- }
178
- }
@@ -1,480 +0,0 @@
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 bonusMeters = { ...privateState.bonusMetersByAmount[betAmount] };
284
-
285
- for (const key of command.payload?.bonusMeters.redraw ?? []) {
286
- const cfg =
287
- gameLogicConfig.baseGame.bonusGameMeters[
288
- key as keyof typeof gameLogicConfig.baseGame.bonusGameMeters
289
- ];
290
- const { value: threshold } = await this.randomThreshold(
291
- cfg.min,
292
- cfg.max,
293
- command.id,
294
- `bonus_meter_redraw_${key}`,
295
- );
296
- const progress = 0;
297
- const level = this.getMeterLevel(
298
- progress,
299
- threshold,
300
- cfg.percentageLevels,
301
- );
302
-
303
- bonusMeters[key] = {
304
- ...bonusMeters[key],
305
- threshold,
306
- progress,
307
- level,
308
- };
309
- }
310
-
311
- privateState.bonusMetersByAmount[betAmount] = bonusMeters;
312
-
313
- return {
314
- publicState: {
315
- ...publicState,
316
- bonusMeters,
317
- },
318
- privateState,
319
- };
320
- }
321
-
322
- default: {
323
- return {
324
- publicState,
325
- privateState,
326
- };
327
- }
328
- }
329
- }
330
-
331
- public cheatTriggerBonus(
332
- publicState: BonnysFortunePublicState,
333
- privateState: BonnysFortunePrivateState,
334
- params: CheatTriggerBonusRequestDto,
335
- ): CheatTriggerBonusResult {
336
- const bonusTrigger: BonusTrigger = {
337
- bonusId: UuidHelper.generateUuid(),
338
- bonusType: params.bonusType,
339
- betAmount: params.betAmount,
340
- };
341
-
342
- privateState.pendingBonuses.push(bonusTrigger);
343
-
344
- return {
345
- publicState,
346
- privateState,
347
- outcome: {
348
- triggeredBonus: bonusTrigger,
349
- },
350
- };
351
- }
352
-
353
- public cheatUpdateBonusMeterProgress(
354
- publicState: BonnysFortunePublicState,
355
- privateState: BonnysFortunePrivateState,
356
- params: CheatUpdateBonusMeterProgressRequestDto,
357
- gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
358
- ): CheatUpdateBonusMeterProgressResult {
359
- const betAmount = publicState.currentBetAmount;
360
-
361
- const currentPrivateMeter =
362
- privateState.bonusMetersByAmount[betAmount][params.bonusType];
363
-
364
- if (!currentPrivateMeter) {
365
- throw new Error(
366
- `Bonus meter ${params.bonusType} not found for bet amount ${betAmount}`,
367
- );
368
- }
369
-
370
- const meterConfig =
371
- gameLogicConfig.baseGame.bonusGameMeters[
372
- params.bonusType as keyof typeof gameLogicConfig.baseGame.bonusGameMeters
373
- ];
374
- if (!meterConfig) {
375
- throw new Error(`Config for bonus meter ${params.bonusType} not found`);
376
- }
377
-
378
- const newLevel = this.getMeterLevel(
379
- params.progress,
380
- currentPrivateMeter.threshold,
381
- meterConfig.percentageLevels,
382
- );
383
-
384
- const updatedMeter: MeterState = {
385
- ...currentPrivateMeter,
386
- progress: params.progress,
387
- level: newLevel,
388
- };
389
-
390
- privateState.bonusMetersByAmount[betAmount] = {
391
- ...privateState.bonusMetersByAmount[betAmount],
392
- [params.bonusType]: updatedMeter,
393
- };
394
-
395
- publicState = {
396
- ...publicState,
397
- bonusMeters: {
398
- ...publicState.bonusMeters,
399
- [params.bonusType]: {
400
- ...publicState.bonusMeters[params.bonusType],
401
- progress: params.progress,
402
- level: newLevel,
403
- },
404
- },
405
- };
406
-
407
- return {
408
- publicState,
409
- privateState,
410
- outcome: {
411
- updatedMeter,
412
- },
413
- };
414
- }
415
-
416
- public getSymbols(
417
- gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
418
- ): GetSymbolsResult {
419
- const symbols = gameLogicConfig.baseGame.symbolMultipliers.map(
420
- (symbol) => ({
421
- symbolId: symbol.symbolId,
422
- name: symbol.name,
423
- multipliers: symbol.multipliers,
424
- isWild: symbol.isWild,
425
- isScatter: symbol.isScatter,
426
- }),
427
- );
428
-
429
- return { symbols };
430
- }
431
-
432
- private shouldRedrawMeter(
433
- bonusType: string,
434
- gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
435
- ): boolean {
436
- if (bonusType === 'collectFeature') {
437
- return false;
438
- }
439
-
440
- return bonusType in gameLogicConfig.baseGame.bonusGameMeters;
441
- }
442
-
443
- private requiresMeterUpdate(
444
- redrawKeys: string[],
445
- gameLogicConfig: BonnysFortuneGameLogicConfigInterface,
446
- ): boolean {
447
- return redrawKeys.some(
448
- (key) => key in gameLogicConfig.baseGame.bonusGameMeters,
449
- );
450
- }
451
-
452
- private getMeterLevel(
453
- progress: number,
454
- threshold: number,
455
- percentageLevels: Record<
456
- string,
457
- { min: number; max: number; value?: number }
458
- >,
459
- ): number {
460
- for (const [, range] of Object.entries(percentageLevels)) {
461
- const minValue = NumberHelper.safeMultiply(range.min, threshold, 4);
462
- const maxValue = NumberHelper.safeMultiply(range.max, threshold, 4);
463
-
464
- if (progress >= minValue && progress < maxValue) {
465
- return range.value ?? 0;
466
- }
467
- }
468
-
469
- return 5;
470
- }
471
-
472
- private async randomThreshold(
473
- min: number,
474
- max: number,
475
- commandId: string,
476
- actionId: string,
477
- ): Promise<{ value: number; seed: number }> {
478
- return this.rngService.getSingleNumber(min, max, commandId, actionId);
479
- }
480
- }