@omnitronix/bonnys-fortune-game-engine 1.0.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.
- package/README.md +680 -0
- package/dist/__tests__/bonnys-fortune-v1.game-engine.test.js +71 -0
- package/dist/__tests__/bonnys-fortune-v1.game-engine.test.js.map +1 -0
- package/dist/bonnys-fortune-v1.game-engine.js +173 -0
- package/dist/bonnys-fortune-v1.game-engine.js.map +1 -0
- package/dist/config/game-logic-config/file-system.game-logic-config-loader.js +57 -0
- package/dist/config/game-logic-config/file-system.game-logic-config-loader.js.map +1 -0
- package/dist/config/game-logic-config/game-logic-config-loader.js +3 -0
- package/dist/config/game-logic-config/game-logic-config-loader.js.map +1 -0
- package/dist/config/game-logic-config/game-logic-config.js +386 -0
- package/dist/config/game-logic-config/game-logic-config.js.map +1 -0
- package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.js +34 -0
- package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.js.map +1 -0
- package/dist/config/reel-strips-config/reel-strip-option.dto.js +27 -0
- package/dist/config/reel-strips-config/reel-strip-option.dto.js.map +1 -0
- package/dist/config/reel-strips-config/reel-strips-config-loader.js +3 -0
- package/dist/config/reel-strips-config/reel-strips-config-loader.js.map +1 -0
- package/dist/config/reel-strips-config/reel-strips-config.dto.js +29 -0
- package/dist/config/reel-strips-config/reel-strips-config.dto.js.map +1 -0
- package/dist/config/reel-strips-config/reel.dto.js +29 -0
- package/dist/config/reel-strips-config/reel.dto.js.map +1 -0
- package/dist/config/reel-strips-config/reels-BASE.csv +52 -0
- package/dist/config/reel-strips-config/reels-BONUS.csv +52 -0
- package/dist/domain/game-logic-configs/bonnys-fortune-game-logic-config.domain.js +134 -0
- package/dist/domain/game-logic-configs/bonnys-fortune-game-logic-config.domain.js.map +1 -0
- package/dist/domain/game-round.types.js +9 -0
- package/dist/domain/game-round.types.js.map +1 -0
- package/dist/domain/mappers/base/game-logic-config-mapper.dispatcher.js +18 -0
- package/dist/domain/mappers/base/game-logic-config-mapper.dispatcher.js.map +1 -0
- package/dist/domain/mappers/base/game-logic-config-mapper.interface.js +3 -0
- package/dist/domain/mappers/base/game-logic-config-mapper.interface.js.map +1 -0
- package/dist/domain/mappers/game-logic-configs/bonnys-fortune-game-logic-config.mapper.js +11 -0
- package/dist/domain/mappers/game-logic-configs/bonnys-fortune-game-logic-config.mapper.js.map +1 -0
- package/dist/domain/mappers/reel-strips-config.mapper.js +17 -0
- package/dist/domain/mappers/reel-strips-config.mapper.js.map +1 -0
- package/dist/domain/reel-strip-option.js +19 -0
- package/dist/domain/reel-strip-option.js.map +1 -0
- package/dist/domain/reel-strips-config.js +22 -0
- package/dist/domain/reel-strips-config.js.map +1 -0
- package/dist/domain/reel.js +19 -0
- package/dist/domain/reel.js.map +1 -0
- package/dist/domain/types/cheat-trigger-bonus-command.js +3 -0
- package/dist/domain/types/cheat-trigger-bonus-command.js.map +1 -0
- package/dist/domain/types/cheat-trigger-bonus-request.dto.js +3 -0
- package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +1 -0
- package/dist/domain/types/cheat-update-bonus-meter-progress-command.js +3 -0
- package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +1 -0
- package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js +3 -0
- package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +1 -0
- package/dist/domain/types/game-spin-command.js +3 -0
- package/dist/domain/types/game-spin-command.js.map +1 -0
- package/dist/domain/types/game-spin-input.dto.js +3 -0
- package/dist/domain/types/game-spin-input.dto.js.map +1 -0
- package/dist/domain/types/game-spin-output.dto.js +17 -0
- package/dist/domain/types/game-spin-output.dto.js.map +1 -0
- package/dist/domain/types/game-symbols.response.dto.js +7 -0
- package/dist/domain/types/game-symbols.response.dto.js.map +1 -0
- package/dist/domain/types/reel-strip-option.dto.js +27 -0
- package/dist/domain/types/reel-strip-option.dto.js.map +1 -0
- package/dist/domain/types/reel-strips-config.dto.js +28 -0
- package/dist/domain/types/reel-strips-config.dto.js.map +1 -0
- package/dist/domain/types/reel.dto.js +28 -0
- package/dist/domain/types/reel.dto.js.map +1 -0
- package/dist/domain/types/start-bonus-round-command.js +3 -0
- package/dist/domain/types/start-bonus-round-command.js.map +1 -0
- package/dist/domain/types/start-bonus-round-input.dto.js +3 -0
- package/dist/domain/types/start-bonus-round-input.dto.js.map +1 -0
- package/dist/game-engine.interface.js +3 -0
- package/dist/game-engine.interface.js.map +1 -0
- package/dist/helpers/generate-hash.js +9 -0
- package/dist/helpers/generate-hash.js.map +1 -0
- package/dist/helpers/number-helper.js +43 -0
- package/dist/helpers/number-helper.js.map +1 -0
- package/dist/helpers/optional-boolean-mapper.js +9 -0
- package/dist/helpers/optional-boolean-mapper.js.map +1 -0
- package/dist/helpers/uuid-helper.js +11 -0
- package/dist/helpers/uuid-helper.js.map +1 -0
- package/dist/helpers/validation-helper.js +25 -0
- package/dist/helpers/validation-helper.js.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/logger.js +140 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/logic/bonnys-fortune.game-logic.js +242 -0
- package/dist/logic/bonnys-fortune.game-logic.js.map +1 -0
- package/dist/logic/bonnys-fortune.spin-generator.js +88 -0
- package/dist/logic/bonnys-fortune.spin-generator.js.map +1 -0
- package/dist/logic/bonnys-fortune.types.js +18 -0
- package/dist/logic/bonnys-fortune.types.js.map +1 -0
- package/dist/logic/bonnys-fortune.win-calculator.js +130 -0
- package/dist/logic/bonnys-fortune.win-calculator.js.map +1 -0
- package/dist/logic/game-logic-config.interface.js +12 -0
- package/dist/logic/game-logic-config.interface.js.map +1 -0
- package/dist/logic/handlers/base-game.handler.js +109 -0
- package/dist/logic/handlers/base-game.handler.js.map +1 -0
- package/dist/logic/handlers/collect-feature-bonus.handler.js +151 -0
- package/dist/logic/handlers/collect-feature-bonus.handler.js.map +1 -0
- package/dist/logic/handlers/free-spins-bonus.handler.js +68 -0
- package/dist/logic/handlers/free-spins-bonus.handler.js.map +1 -0
- package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.js +74 -0
- package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.js.map +1 -0
- package/dist/logic/handlers/treasure-hunt-bonus.handler.js +130 -0
- package/dist/logic/handlers/treasure-hunt-bonus.handler.js.map +1 -0
- package/dist/rng/DummyRngClient.js +89 -0
- package/dist/rng/DummyRngClient.js.map +1 -0
- package/dist/rng/rng-client.factory.js +23 -0
- package/dist/rng/rng-client.factory.js.map +1 -0
- package/dist/rng/rng-client.interface.js +3 -0
- package/dist/rng/rng-client.interface.js.map +1 -0
- package/dist/rng/rng-service.js +78 -0
- package/dist/rng/rng-service.js.map +1 -0
- package/dist/validation/abstract-game-logic-config.validator.js +18 -0
- package/dist/validation/abstract-game-logic-config.validator.js.map +1 -0
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js +545 -0
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -0
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.js +12 -0
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.js.map +1 -0
- package/dist/validation/custom-decorators/IsNestedIntArray.js +26 -0
- package/dist/validation/custom-decorators/IsNestedIntArray.js.map +1 -0
- package/dist/validation/game-logic-config-validation.service.js +20 -0
- package/dist/validation/game-logic-config-validation.service.js.map +1 -0
- package/dist/validation/reel-strips-config-validation.service.js +26 -0
- package/dist/validation/reel-strips-config-validation.service.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
# Bonny's Fortune Game Engine
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Pure business logic library for the Bonny's Fortune slot game. A framework-agnostic, TypeScript-based game engine that handles all core game mechanics, state management, and bonus game logic.
|
|
8
|
+
|
|
9
|
+
## What is Bonny's Fortune Game Engine?
|
|
10
|
+
|
|
11
|
+
The Bonny's Fortune Game Engine is an isolated, framework-agnostic library containing pure business logic for the Bonny's Fortune slot game. It implements a command-based architecture that separates game logic from presentation and infrastructure concerns.
|
|
12
|
+
|
|
13
|
+
### Core Characteristics
|
|
14
|
+
|
|
15
|
+
- **Pure Business Logic**: Contains only game rules, calculations, and state transitions
|
|
16
|
+
- **Framework Agnostic**: No dependencies on specific UI frameworks or web servers
|
|
17
|
+
- **Command-Based Architecture**: All interactions through well-defined commands
|
|
18
|
+
- **State Separation**: Distinguishes between public state (visible to players) and private state (server-side only)
|
|
19
|
+
- **RNG Integration**: Supports both external RNG services and local dummy RNG for testing
|
|
20
|
+
- **Audit Trail**: Complete RNG outcome tracking for compliance and debugging
|
|
21
|
+
|
|
22
|
+
### Game Specifications
|
|
23
|
+
|
|
24
|
+
- **RTP**: 96%
|
|
25
|
+
- **Game Type**: Slot
|
|
26
|
+
- **Version**: 1.0.0
|
|
27
|
+
- **Layout**: 5 reels × 3 rows
|
|
28
|
+
- **Win Evaluation**: 243 ways to win
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Command-Based Architecture**: 6 different command types for all game operations
|
|
33
|
+
- **State Management**: Public/private state separation with type safety
|
|
34
|
+
- **RNG Integration**: Pluggable RNG clients with audit trail support
|
|
35
|
+
- **Configuration Validation**: Uses class-validator for runtime type checking
|
|
36
|
+
- **Multiple Bonus Games**: 4 unique bonus game mechanics
|
|
37
|
+
- **TypeScript Support**: Complete type definitions for all interfaces
|
|
38
|
+
- **Comprehensive Testing**: Full test suite with Jest and coverage reporting
|
|
39
|
+
|
|
40
|
+
## Architecture Overview
|
|
41
|
+
|
|
42
|
+
The game engine follows a modular architecture with clear separation of concerns:
|
|
43
|
+
|
|
44
|
+
### Core Components
|
|
45
|
+
|
|
46
|
+
- **Game Engine** (`BonnysFortuneV1GameEngine`): Main entry point and command processor
|
|
47
|
+
- **Game Logic** (`BonnysFortuneGameLogic`): Core game rules and state transitions
|
|
48
|
+
- **Handlers**: Specialized handlers for different game modes:
|
|
49
|
+
- `BaseGameHandler`: Base game spin logic
|
|
50
|
+
- `TreasureHuntBonusHandler`: Pick-style bonus game
|
|
51
|
+
- `FreeSpinsBonusHandler`: Free spins with multipliers
|
|
52
|
+
- `SteeringToTheFortuneBonusHandler`: Wheel of fortune bonus
|
|
53
|
+
- `CollectFeatureBonusHandler`: Multi-spin collect feature
|
|
54
|
+
- **RNG Service**: Random number generation with pluggable clients
|
|
55
|
+
- **Win Calculator**: Payline/ways evaluation and win computation
|
|
56
|
+
- **Validators**: Configuration and input validation using class-validator
|
|
57
|
+
- **Config Loaders**: File system-based configuration loading
|
|
58
|
+
|
|
59
|
+
### Directory Structure
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
src/
|
|
63
|
+
├── bonnys-fortune-v1.game-engine.ts # Main game engine
|
|
64
|
+
├── logic/ # Game logic and handlers
|
|
65
|
+
├── config/ # Configuration loaders
|
|
66
|
+
├── validation/ # Config validators
|
|
67
|
+
├── rng/ # RNG clients and service
|
|
68
|
+
├── domain/ # Types and DTOs
|
|
69
|
+
└── helpers/ # Utility functions
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install @omnitronix/bonnys-fortune-game-engine
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> **Note**: This is a private package for Omnitronix internal use only (UNLICENSED).
|
|
79
|
+
|
|
80
|
+
## Environment Variables
|
|
81
|
+
|
|
82
|
+
The game engine supports various RNG configurations through environment variables:
|
|
83
|
+
|
|
84
|
+
| Variable | Description | Values | Default |
|
|
85
|
+
|----------|-------------|--------|---------|
|
|
86
|
+
| `RNG_CLIENT_MODE` | RNG client mode | `local` (DummyRngClient), `external` (HttpRngClient) | `external` |
|
|
87
|
+
| `RNG_CLIENT_URL` | External RNG service HTTP URL | URL string | - |
|
|
88
|
+
| `RNG_CLIENT_GRPC_URL` | External RNG service gRPC URL | URL string | - |
|
|
89
|
+
| `RNG_CLIENT_METHOD` | RNG communication method | `grpc`, `rest` | - |
|
|
90
|
+
| `RNG_CLIENT_POOL_SIZE` | RNG ring buffer pool size | Number | `2000` |
|
|
91
|
+
|
|
92
|
+
### Environment Examples
|
|
93
|
+
|
|
94
|
+
**Local Development** (uses dummy RNG):
|
|
95
|
+
```bash
|
|
96
|
+
RNG_CLIENT_MODE=local
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Production Environment** (external RNG via gRPC):
|
|
100
|
+
```bash
|
|
101
|
+
RNG_CLIENT_MODE=external
|
|
102
|
+
RNG_CLIENT_METHOD=grpc
|
|
103
|
+
RNG_CLIENT_GRPC_URL=grpc://rng-prod.omnitronix.com:50051
|
|
104
|
+
RNG_CLIENT_POOL_SIZE=5000
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Quick Start
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { BonnysFortuneV1GameEngine } from '@omnitronix/bonnys-fortune-game-engine';
|
|
111
|
+
|
|
112
|
+
// Initialize game engine
|
|
113
|
+
const gameEngine = new BonnysFortuneV1GameEngine();
|
|
114
|
+
|
|
115
|
+
// Get game info
|
|
116
|
+
const info = gameEngine.getGameEngineInfo();
|
|
117
|
+
// { gameCode: 'bonnys-fortune', version: '1.0.0', rtp: 96, gameType: 'slot' }
|
|
118
|
+
|
|
119
|
+
// Initialize session
|
|
120
|
+
const initCommand = {
|
|
121
|
+
id: 'cmd-init-123',
|
|
122
|
+
type: 'INIT_SESSION_STATE',
|
|
123
|
+
payload: {
|
|
124
|
+
betAmountThresholds: [10, 25, 50, 100],
|
|
125
|
+
defaultBetAmount: 10
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const initResult = await gameEngine.processCommand({}, {}, initCommand);
|
|
130
|
+
// Returns: { success, publicState, privateState, outcome, rngOutcome }
|
|
131
|
+
|
|
132
|
+
// Process spin
|
|
133
|
+
const spinCommand = {
|
|
134
|
+
id: 'cmd-spin-456',
|
|
135
|
+
type: 'SPIN',
|
|
136
|
+
payload: {
|
|
137
|
+
sessionId: 'session-123',
|
|
138
|
+
betAmount: 10,
|
|
139
|
+
gameCode: 'bonnys-fortune',
|
|
140
|
+
gameVersion: '1.0.0',
|
|
141
|
+
lines: 10,
|
|
142
|
+
creditsPerLine: 1
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const spinResult = await gameEngine.processCommand(
|
|
147
|
+
initResult.publicState,
|
|
148
|
+
initResult.privateState,
|
|
149
|
+
spinCommand
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Reference
|
|
154
|
+
|
|
155
|
+
### Main Class: `BonnysFortuneV1GameEngine`
|
|
156
|
+
|
|
157
|
+
#### Constructor
|
|
158
|
+
```typescript
|
|
159
|
+
new BonnysFortuneV1GameEngine()
|
|
160
|
+
```
|
|
161
|
+
- Initializes game engine with configuration
|
|
162
|
+
- Auto-loads game configuration and reel strips from file system
|
|
163
|
+
- Sets up RNG client based on environment variables
|
|
164
|
+
|
|
165
|
+
#### Methods
|
|
166
|
+
|
|
167
|
+
**`getGameEngineInfo(): GameEngineInfo`**
|
|
168
|
+
Returns game metadata including code, version, RTP, and game type.
|
|
169
|
+
|
|
170
|
+
**`processCommand(publicState, privateState, command): Promise<CommandProcessingResult>`**
|
|
171
|
+
Main command processor that handles all game operations.
|
|
172
|
+
|
|
173
|
+
### Commands
|
|
174
|
+
|
|
175
|
+
The game engine supports 6 different command types:
|
|
176
|
+
|
|
177
|
+
#### 1. INIT_SESSION_STATE
|
|
178
|
+
|
|
179
|
+
**Purpose**: Initialize game session state with bonus meters
|
|
180
|
+
|
|
181
|
+
**Payload**:
|
|
182
|
+
```typescript
|
|
183
|
+
{
|
|
184
|
+
betAmountThresholds: number[];
|
|
185
|
+
defaultBetAmount: number;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Returns**: Initial public and private state
|
|
190
|
+
|
|
191
|
+
#### 2. SPIN
|
|
192
|
+
|
|
193
|
+
**Purpose**: Execute base game spin
|
|
194
|
+
|
|
195
|
+
**Payload**:
|
|
196
|
+
```typescript
|
|
197
|
+
{
|
|
198
|
+
sessionId: string;
|
|
199
|
+
betAmount: number;
|
|
200
|
+
gameCode: string;
|
|
201
|
+
gameVersion: string;
|
|
202
|
+
lines?: number;
|
|
203
|
+
creditsPerLine?: number;
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Returns**: Spin result with screen, wins, and potential bonus triggers
|
|
208
|
+
|
|
209
|
+
#### 3. START_BONUS_ROUND
|
|
210
|
+
|
|
211
|
+
**Purpose**: Start triggered bonus round
|
|
212
|
+
|
|
213
|
+
**Payload**:
|
|
214
|
+
```typescript
|
|
215
|
+
{
|
|
216
|
+
sessionId: string;
|
|
217
|
+
bonusType: string;
|
|
218
|
+
betAmount: number;
|
|
219
|
+
gameCode: string;
|
|
220
|
+
gameVersion: string;
|
|
221
|
+
lines?: number;
|
|
222
|
+
creditsPerLine?: number;
|
|
223
|
+
bonusId: string;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Returns**: Complete bonus round results
|
|
228
|
+
|
|
229
|
+
#### 4. UPDATE_SESSION_STATE
|
|
230
|
+
|
|
231
|
+
**Purpose**: Update session state (e.g., redraw bonus meters after bonus completion)
|
|
232
|
+
|
|
233
|
+
**Payload**:
|
|
234
|
+
```typescript
|
|
235
|
+
{
|
|
236
|
+
bonusMeters: {
|
|
237
|
+
redraw: string[];
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Returns**: Updated state
|
|
243
|
+
|
|
244
|
+
#### 5. CHEAT_TRIGGER_BONUS (Testing/Admin)
|
|
245
|
+
|
|
246
|
+
**Purpose**: Force trigger specific bonus game
|
|
247
|
+
|
|
248
|
+
**Payload**: `CheatTriggerBonusCommand`
|
|
249
|
+
|
|
250
|
+
**Returns**: State with pending bonus
|
|
251
|
+
|
|
252
|
+
#### 6. CHEAT_UPDATE_BONUS_METER_PROGRESS (Testing/Admin)
|
|
253
|
+
|
|
254
|
+
**Purpose**: Manually set bonus meter progress
|
|
255
|
+
|
|
256
|
+
**Payload**: `CheatUpdateBonusMeterProgressCommand`
|
|
257
|
+
|
|
258
|
+
**Returns**: Updated meter state
|
|
259
|
+
|
|
260
|
+
### State Types
|
|
261
|
+
|
|
262
|
+
#### PublicState (visible to player)
|
|
263
|
+
```typescript
|
|
264
|
+
{
|
|
265
|
+
currentBetAmount: number;
|
|
266
|
+
bonusMeters: {
|
|
267
|
+
[bonusGameId: string]: {
|
|
268
|
+
symbolName: string;
|
|
269
|
+
symbolId: number;
|
|
270
|
+
threshold: number;
|
|
271
|
+
progress: number;
|
|
272
|
+
level: number;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### PrivateState (server-side only)
|
|
279
|
+
```typescript
|
|
280
|
+
{
|
|
281
|
+
screen?: number[][];
|
|
282
|
+
nextSpinType: SpinType;
|
|
283
|
+
pendingBonuses: BonusTrigger[];
|
|
284
|
+
activeBonus?: BonusTrigger;
|
|
285
|
+
bonusMetersByAmount: Record<number, MetersState>;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### CommandProcessingResult
|
|
290
|
+
```typescript
|
|
291
|
+
{
|
|
292
|
+
success: boolean;
|
|
293
|
+
publicState: BonnysFortunePublicState;
|
|
294
|
+
privateState: BonnysFortunePrivateState;
|
|
295
|
+
outcome?: any; // Spin/bonus result
|
|
296
|
+
message?: string;
|
|
297
|
+
rngOutcome?: RngOutcome; // RNG audit trail
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Game Mechanics
|
|
302
|
+
|
|
303
|
+
### Base Game
|
|
304
|
+
|
|
305
|
+
- **Layout**: 5 reels × 3 rows
|
|
306
|
+
- **Win Evaluation**: 243 ways to win
|
|
307
|
+
- **Symbols**: 10 base symbols (H1-H3 high, M1-M3 medium, L1-L4 low) + Wild + Scatter
|
|
308
|
+
- **Symbol Multipliers**: Range from 2x (L4) to 500x (H1) for 5-of-a-kind
|
|
309
|
+
- **Bet Levels**: [10, 25, 50, 100, 500, 1000, 2000]
|
|
310
|
+
- **RTP**: 96%
|
|
311
|
+
|
|
312
|
+
### Bonus Meters System
|
|
313
|
+
|
|
314
|
+
Progressive meters track collection of specific high-value symbols (H1, H2, H3):
|
|
315
|
+
|
|
316
|
+
- Each meter has dynamic threshold (min-max range)
|
|
317
|
+
- Progress increases when symbols land on reels
|
|
318
|
+
- 5 progress levels (0-25%, 25-65%, 65-80%, 80-100%, 100%+)
|
|
319
|
+
- Separate meters per bet amount
|
|
320
|
+
- Triggers corresponding bonus game when threshold reached
|
|
321
|
+
|
|
322
|
+
### Bonus Game 1: Treasure Hunt
|
|
323
|
+
|
|
324
|
+
**Type**: Pick-style bonus
|
|
325
|
+
|
|
326
|
+
**Mechanics**:
|
|
327
|
+
- 25 symbols arranged in grid
|
|
328
|
+
- 6 steps/picks to reveal prizes
|
|
329
|
+
- 4 jackpot levels: Mini (20x), Minor (50x), Major (200x), Grand (2000x)
|
|
330
|
+
- Each pick reveals multiplier or jackpot
|
|
331
|
+
- Jackpot triggered when specific count revealed (Mini: 10, Minor: 7, Major: 5, Grand: 3)
|
|
332
|
+
- Starting counts configurable per jackpot level
|
|
333
|
+
|
|
334
|
+
**Trigger**: Bonus Meter 1 (H1 symbol collection) reaches threshold
|
|
335
|
+
|
|
336
|
+
**Weighted Selection**: Different probabilities for each prize tier
|
|
337
|
+
|
|
338
|
+
### Bonus Game 2: Free Spins
|
|
339
|
+
|
|
340
|
+
**Type**: Free spins with multipliers
|
|
341
|
+
|
|
342
|
+
**Mechanics**:
|
|
343
|
+
- 6 spin type variants with different configurations
|
|
344
|
+
- Variable layouts: 5×3, 5×4, or 5×5 reels
|
|
345
|
+
- Variable spin counts: 4 or 8 spins
|
|
346
|
+
- Win multipliers: 1x to 20x
|
|
347
|
+
- Each variant has weight for random selection
|
|
348
|
+
- All wins multiplied by selected multiplier
|
|
349
|
+
- Uses alternative reel strips (bonus reels)
|
|
350
|
+
|
|
351
|
+
**Trigger**: Bonus Meter 2 (H2 symbol collection) reaches threshold
|
|
352
|
+
|
|
353
|
+
**Reel Options**: 4 reel strip options with configurable weights [1, 1, 4, 4]
|
|
354
|
+
|
|
355
|
+
### Bonus Game 3: Steering to the Fortune (Wheel)
|
|
356
|
+
|
|
357
|
+
**Type**: Wheel of fortune / prize wheel
|
|
358
|
+
|
|
359
|
+
**Mechanics**:
|
|
360
|
+
- Spin wheel once
|
|
361
|
+
- 10 possible outcomes with weighted selection
|
|
362
|
+
- Prize types:
|
|
363
|
+
- **MULTIPLIER**: Direct win multipliers (5x, 8x, 10x, 15x, 25x, 40x, 80x)
|
|
364
|
+
- **BONUS**: Triggers another bonus game (collectFeature, bonusGame1, bonusGame2)
|
|
365
|
+
- Multiplier applied to bet amount for wins
|
|
366
|
+
- Bonus prizes trigger immediate transition to other bonus games
|
|
367
|
+
|
|
368
|
+
**Trigger**: Bonus Meter 3 (H3 symbol collection) reaches threshold
|
|
369
|
+
|
|
370
|
+
**Weights**: Lower multipliers more common, higher multipliers rare, bonus triggers rare
|
|
371
|
+
|
|
372
|
+
### Collect Feature (Scatter Bonus)
|
|
373
|
+
|
|
374
|
+
**Type**: Multi-spin collect feature
|
|
375
|
+
|
|
376
|
+
**Mechanics**:
|
|
377
|
+
- Triggered by 3+ scatter symbols in base game
|
|
378
|
+
- 6 spins total
|
|
379
|
+
- Each spin reveals 1-3 special symbols
|
|
380
|
+
- Two symbol types:
|
|
381
|
+
- **Multiplier symbols** (CM1: 1x, CM2: 2x, CM3: 3x) - multiply total value
|
|
382
|
+
- **Value symbols** (CV1: 1, CV2: 2, CV3: 5) - add to total value
|
|
383
|
+
- Symbols can be "slashed" (disabled) based on weighted probability
|
|
384
|
+
- Checkpoints at specific spins require minimum multipliers/values (Spin 3: 0 multi, 1 value; Spin 2: 1 multi, 2 values)
|
|
385
|
+
- Final win = total values × total multipliers × bet stake
|
|
386
|
+
- Spins continue until all 6 complete
|
|
387
|
+
|
|
388
|
+
**Trigger**: 3 or more scatter symbols land anywhere on reels during base game
|
|
389
|
+
|
|
390
|
+
**Symbol Weights**: Values more common than multipliers, higher values rarer
|
|
391
|
+
|
|
392
|
+
### Special Features
|
|
393
|
+
|
|
394
|
+
- **Wild Symbol**: Substitutes for all symbols except scatter
|
|
395
|
+
- **Progressive Meters**: Meter progress persists across spins until triggered
|
|
396
|
+
- **Bonus Chaining**: Steering to Fortune can trigger other bonuses
|
|
397
|
+
- **State Transitions**: Game tracks next expected spin type (base game vs bonus)
|
|
398
|
+
|
|
399
|
+
## Integration Examples
|
|
400
|
+
|
|
401
|
+
### Example 1: Basic RGS Integration
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import 'reflect-metadata';
|
|
405
|
+
import { BonnysFortuneV1GameEngine } from '@omnitronix/bonnys-fortune-game-engine';
|
|
406
|
+
|
|
407
|
+
class GameSessionService {
|
|
408
|
+
private gameEngine: BonnysFortuneV1GameEngine;
|
|
409
|
+
|
|
410
|
+
constructor() {
|
|
411
|
+
this.gameEngine = new BonnysFortuneV1GameEngine();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async createSession(userId: string, betAmount: number) {
|
|
415
|
+
const initCommand = {
|
|
416
|
+
id: `init-${userId}-${Date.now()}`,
|
|
417
|
+
type: 'INIT_SESSION_STATE',
|
|
418
|
+
payload: {
|
|
419
|
+
betAmountThresholds: [10, 25, 50, 100, 500, 1000, 2000],
|
|
420
|
+
defaultBetAmount: betAmount
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const result = await this.gameEngine.processCommand({}, {}, initCommand);
|
|
425
|
+
|
|
426
|
+
// Store result.publicState in database (visible to player)
|
|
427
|
+
// Store result.privateState securely (server-side only)
|
|
428
|
+
// Store result.rngOutcome for audit trail
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
sessionId: userId,
|
|
432
|
+
publicState: result.publicState,
|
|
433
|
+
// Never send privateState to client!
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async processSpin(sessionId: string, publicState: any, privateState: any, betAmount: number) {
|
|
438
|
+
const spinCommand = {
|
|
439
|
+
id: `spin-${sessionId}-${Date.now()}`,
|
|
440
|
+
type: 'SPIN',
|
|
441
|
+
payload: {
|
|
442
|
+
sessionId,
|
|
443
|
+
betAmount,
|
|
444
|
+
gameCode: 'bonnys-fortune',
|
|
445
|
+
gameVersion: '1.0.0',
|
|
446
|
+
lines: 10,
|
|
447
|
+
creditsPerLine: betAmount / 10
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const result = await this.gameEngine.processCommand(
|
|
452
|
+
publicState,
|
|
453
|
+
privateState,
|
|
454
|
+
spinCommand
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
// Update states in database
|
|
458
|
+
// Log RNG outcome for compliance
|
|
459
|
+
// Return outcome to client
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
outcome: result.outcome,
|
|
463
|
+
publicState: result.publicState,
|
|
464
|
+
rngOutcome: result.rngOutcome, // For audit
|
|
465
|
+
// privateState stays on server
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Example 2: Handling Bonus Triggers
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
async handleSpinResult(spinResult: any) {
|
|
475
|
+
const { outcome, publicState, privateState } = spinResult;
|
|
476
|
+
|
|
477
|
+
// Check if bonus was triggered
|
|
478
|
+
if (outcome.result.triggeredBonus) {
|
|
479
|
+
const bonus = outcome.result.triggeredBonus;
|
|
480
|
+
|
|
481
|
+
// Store pending bonus in session
|
|
482
|
+
// Notify player
|
|
483
|
+
// Wait for player to start bonus
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
type: 'BONUS_TRIGGERED',
|
|
487
|
+
bonusType: bonus.bonusType,
|
|
488
|
+
bonusId: bonus.bonusId,
|
|
489
|
+
message: 'Bonus game triggered! Click to start.',
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Check if we should start pending bonus
|
|
494
|
+
if (privateState.pendingBonuses?.length > 0) {
|
|
495
|
+
const pendingBonus = privateState.pendingBonuses[0];
|
|
496
|
+
|
|
497
|
+
const bonusCommand = {
|
|
498
|
+
id: `bonus-${Date.now()}`,
|
|
499
|
+
type: 'START_BONUS_ROUND',
|
|
500
|
+
payload: {
|
|
501
|
+
sessionId: spinResult.sessionId,
|
|
502
|
+
bonusType: pendingBonus.bonusType,
|
|
503
|
+
betAmount: pendingBonus.betAmount,
|
|
504
|
+
gameCode: 'bonnys-fortune',
|
|
505
|
+
gameVersion: '1.0.0',
|
|
506
|
+
bonusId: pendingBonus.bonusId
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const bonusResult = await this.gameEngine.processCommand(
|
|
511
|
+
publicState,
|
|
512
|
+
privateState,
|
|
513
|
+
bonusCommand
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
type: 'BONUS_COMPLETED',
|
|
518
|
+
results: bonusResult.outcome.results, // All bonus spins
|
|
519
|
+
totalWin: bonusResult.outcome.results.reduce(
|
|
520
|
+
(sum, r) => sum + r.result.playerWinning, 0
|
|
521
|
+
),
|
|
522
|
+
publicState: bonusResult.publicState,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
type: 'REGULAR_SPIN',
|
|
528
|
+
...outcome
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Example 3: RNG Configuration for Different Environments
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
// Local development - use dummy RNG
|
|
537
|
+
// .env.development
|
|
538
|
+
RNG_CLIENT_MODE=local
|
|
539
|
+
|
|
540
|
+
// Staging - use external RNG service
|
|
541
|
+
// .env.staging
|
|
542
|
+
RNG_CLIENT_MODE=external
|
|
543
|
+
RNG_CLIENT_METHOD=rest
|
|
544
|
+
RNG_CLIENT_URL=https://rng-staging.omnitronix.com
|
|
545
|
+
RNG_CLIENT_POOL_SIZE=2000
|
|
546
|
+
|
|
547
|
+
// Production - use external RNG with gRPC
|
|
548
|
+
// .env.production
|
|
549
|
+
RNG_CLIENT_MODE=external
|
|
550
|
+
RNG_CLIENT_METHOD=grpc
|
|
551
|
+
RNG_CLIENT_GRPC_URL=grpc://rng-prod.omnitronix.com:50051
|
|
552
|
+
RNG_CLIENT_POOL_SIZE=5000
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Example 4: Testing with Cheat Commands
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
async function testBonusGame() {
|
|
559
|
+
const gameEngine = new BonnysFortuneV1GameEngine();
|
|
560
|
+
|
|
561
|
+
// Initialize session
|
|
562
|
+
const initResult = await gameEngine.processCommand({}, {}, {
|
|
563
|
+
id: 'test-init',
|
|
564
|
+
type: 'INIT_SESSION_STATE',
|
|
565
|
+
payload: { betAmountThresholds: [10], defaultBetAmount: 10 }
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Force trigger treasure hunt bonus
|
|
569
|
+
const cheatResult = await gameEngine.processCommand(
|
|
570
|
+
initResult.publicState,
|
|
571
|
+
initResult.privateState,
|
|
572
|
+
{
|
|
573
|
+
id: 'test-cheat',
|
|
574
|
+
type: 'CHEAT_TRIGGER_BONUS',
|
|
575
|
+
payload: {
|
|
576
|
+
sessionId: 'test-session',
|
|
577
|
+
bonusType: 'bonusGame1',
|
|
578
|
+
betAmount: 10
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
// Now start the bonus
|
|
584
|
+
const bonusResult = await gameEngine.processCommand(
|
|
585
|
+
cheatResult.publicState,
|
|
586
|
+
cheatResult.privateState,
|
|
587
|
+
{
|
|
588
|
+
id: 'test-bonus-start',
|
|
589
|
+
type: 'START_BONUS_ROUND',
|
|
590
|
+
payload: {
|
|
591
|
+
sessionId: 'test-session',
|
|
592
|
+
bonusType: 'bonusGame1',
|
|
593
|
+
betAmount: 10,
|
|
594
|
+
gameCode: 'bonnys-fortune',
|
|
595
|
+
gameVersion: '1.0.0',
|
|
596
|
+
bonusId: cheatResult.privateState.pendingBonuses[0].bonusId
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
console.log('Bonus results:', bonusResult.outcome);
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
## Development
|
|
606
|
+
|
|
607
|
+
### Running Tests
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
# Run all tests
|
|
611
|
+
npm test
|
|
612
|
+
|
|
613
|
+
# Run tests in watch mode
|
|
614
|
+
npm run test:watch
|
|
615
|
+
|
|
616
|
+
# Run with coverage
|
|
617
|
+
npm run test:coverage
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Tests use local RNG (`RNG_CLIENT_MODE=local`) automatically via `jest.setup.js`.
|
|
621
|
+
|
|
622
|
+
### Building
|
|
623
|
+
|
|
624
|
+
```bash
|
|
625
|
+
# Clean build
|
|
626
|
+
npm run clean
|
|
627
|
+
npm run build
|
|
628
|
+
|
|
629
|
+
# Development mode (watch)
|
|
630
|
+
npm run dev
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Linting
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
# Check for issues
|
|
637
|
+
npm run lint
|
|
638
|
+
|
|
639
|
+
# Auto-fix issues
|
|
640
|
+
npm run lint:fix
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## Configuration
|
|
644
|
+
|
|
645
|
+
Game configuration located in `src/config/game-logic-config/game-logic-config.ts`:
|
|
646
|
+
- Symbol definitions and multipliers
|
|
647
|
+
- Bonus game settings
|
|
648
|
+
- Meter thresholds
|
|
649
|
+
- Bet levels
|
|
650
|
+
- RTP configuration
|
|
651
|
+
|
|
652
|
+
Reel strips in `src/config/reel-strips-config/`:
|
|
653
|
+
- `reels-BASE.csv` - Base game reel strips
|
|
654
|
+
- `reels-BONUS.csv` - Bonus game 2 (free spins) reel strips
|
|
655
|
+
|
|
656
|
+
Configuration validated on startup using class-validator.
|
|
657
|
+
|
|
658
|
+
## Type Exports
|
|
659
|
+
|
|
660
|
+
The package exports all necessary types for TypeScript integration:
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
import {
|
|
664
|
+
BonnysFortuneV1GameEngine,
|
|
665
|
+
GameEngineInfo,
|
|
666
|
+
CommandProcessingResult,
|
|
667
|
+
GameActionCommand,
|
|
668
|
+
BonnysFortunePublicState,
|
|
669
|
+
BonnysFortunePrivateState,
|
|
670
|
+
// ... additional types as needed
|
|
671
|
+
} from '@omnitronix/bonnys-fortune-game-engine';
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
## License
|
|
675
|
+
|
|
676
|
+
UNLICENSED - Internal use only for Omnitronix
|
|
677
|
+
|
|
678
|
+
## Support
|
|
679
|
+
|
|
680
|
+
For questions or issues, contact the Omnitronix development team.
|