@energy8platform/game-engine 0.20.0 → 0.22.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 (93) hide show
  1. package/README.md +121 -0
  2. package/dist/animation.cjs.js +18 -3
  3. package/dist/animation.cjs.js.map +1 -1
  4. package/dist/animation.esm.js +18 -3
  5. package/dist/animation.esm.js.map +1 -1
  6. package/dist/core.cjs.js +18 -3
  7. package/dist/core.cjs.js.map +1 -1
  8. package/dist/core.esm.js +18 -3
  9. package/dist/core.esm.js.map +1 -1
  10. package/dist/host.cjs.js +18 -3
  11. package/dist/host.cjs.js.map +1 -1
  12. package/dist/host.d.ts +3 -5
  13. package/dist/host.esm.js +18 -3
  14. package/dist/host.esm.js.map +1 -1
  15. package/dist/index.cjs.js +18 -2535
  16. package/dist/index.cjs.js.map +1 -1
  17. package/dist/index.d.ts +3 -1020
  18. package/dist/index.esm.js +20 -2525
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/slot.cjs.js +1872 -61
  21. package/dist/slot.cjs.js.map +1 -1
  22. package/dist/slot.d.ts +555 -28
  23. package/dist/slot.esm.js +1857 -62
  24. package/dist/slot.esm.js.map +1 -1
  25. package/dist/vite.cjs.js +0 -5
  26. package/dist/vite.cjs.js.map +1 -1
  27. package/dist/vite.esm.js +0 -5
  28. package/dist/vite.esm.js.map +1 -1
  29. package/package.json +2 -35
  30. package/src/animation/Tween.ts +14 -3
  31. package/src/host/index.ts +0 -1
  32. package/src/host/types.ts +0 -3
  33. package/src/index.ts +0 -28
  34. package/src/slot/anim/easing-map.ts +16 -2
  35. package/src/slot/cascade/TumbleController.ts +230 -0
  36. package/src/slot/config/ReelSystemConfig.ts +559 -0
  37. package/src/slot/config/presets.ts +222 -0
  38. package/src/slot/features/extra.ts +189 -0
  39. package/src/slot/features/index.ts +44 -0
  40. package/src/slot/features/symbols.ts +183 -0
  41. package/src/slot/features/types.ts +136 -0
  42. package/src/slot/features/wilds.ts +151 -0
  43. package/src/slot/grid/ReelGrid.ts +93 -24
  44. package/src/slot/grid/SymbolCell.ts +45 -11
  45. package/src/slot/index.ts +61 -2
  46. package/src/slot/motion/AnticipationController.ts +96 -0
  47. package/src/slot/motion/SpinEngine.ts +384 -0
  48. package/src/slot/system/ReelSystem.ts +295 -0
  49. package/src/vite/index.ts +0 -5
  50. package/dist/react-jsx.cjs.js +0 -3
  51. package/dist/react-jsx.cjs.js.map +0 -1
  52. package/dist/react-jsx.d.ts +0 -602
  53. package/dist/react-jsx.esm.js +0 -2
  54. package/dist/react-jsx.esm.js.map +0 -1
  55. package/dist/react.cjs.js +0 -3787
  56. package/dist/react.cjs.js.map +0 -1
  57. package/dist/react.d.ts +0 -1467
  58. package/dist/react.esm.js +0 -3771
  59. package/dist/react.esm.js.map +0 -1
  60. package/dist/ui.cjs.js +0 -2999
  61. package/dist/ui.cjs.js.map +0 -1
  62. package/dist/ui.d.ts +0 -1116
  63. package/dist/ui.esm.js +0 -2983
  64. package/dist/ui.esm.js.map +0 -1
  65. package/src/react/EngineContext.ts +0 -26
  66. package/src/react/ReactScene.ts +0 -88
  67. package/src/react/applyProps.ts +0 -271
  68. package/src/react/catalogue.ts +0 -17
  69. package/src/react/createPixiRoot.ts +0 -31
  70. package/src/react/extendAll.ts +0 -74
  71. package/src/react/hooks.ts +0 -46
  72. package/src/react/index.ts +0 -29
  73. package/src/react/jsx-runtime.ts +0 -346
  74. package/src/react/reconciler.ts +0 -338
  75. package/src/slot/freeSpins/FreeSpinsSession.ts +0 -40
  76. package/src/state/StateMachine.ts +0 -231
  77. package/src/state/index.ts +0 -1
  78. package/src/ui/BalanceDisplay.ts +0 -159
  79. package/src/ui/Button.ts +0 -304
  80. package/src/ui/FlexContainer.ts +0 -775
  81. package/src/ui/Label.ts +0 -124
  82. package/src/ui/LabelValue.ts +0 -122
  83. package/src/ui/Layout.ts +0 -291
  84. package/src/ui/Modal.ts +0 -170
  85. package/src/ui/Panel.ts +0 -204
  86. package/src/ui/ProgressBar.ts +0 -170
  87. package/src/ui/ScrollContainer.ts +0 -478
  88. package/src/ui/Slider.ts +0 -241
  89. package/src/ui/Toast.ts +0 -150
  90. package/src/ui/Toggle.ts +0 -201
  91. package/src/ui/WinDisplay.ts +0 -145
  92. package/src/ui/index.ts +0 -33
  93. package/src/ui/view.ts +0 -28
@@ -0,0 +1,295 @@
1
+ // packages/game-engine/src/slot/system/ReelSystem.ts
2
+ //
3
+ // The configurable reel system facade. `createReelSystem({ resolve, config })` builds a grid and
4
+ // wires the spin engine, anticipation controller, tumble/cascade controller and the special-feature
5
+ // registry — all driven by one `ReelSystemConfig`. Presentation only: it draws boards you feed it.
6
+
7
+ import { Container } from 'pixi.js';
8
+ import { Tween } from '../../animation';
9
+ import { ReelGrid } from '../grid/ReelGrid';
10
+ import type { CellData } from '../grid/SymbolCell';
11
+ import type { SymbolResolver } from '../grid/SymbolView';
12
+ import { SpinEngine, type SpinData, type SpinRunOpts } from '../motion/SpinEngine';
13
+ import { AnticipationController } from '../motion/AnticipationController';
14
+ import { TumbleController, type TumbleStep } from '../cascade/TumbleController';
15
+ import { FEATURES, FEATURE_LIST, type FeatureContext, type ReelFeature } from '../features';
16
+ import {
17
+ DEFAULT_REEL_CONFIG,
18
+ effectiveRowsPerReel,
19
+ mergeReelConfig,
20
+ resolveReelConfig,
21
+ waysCount,
22
+ type DeepPartial,
23
+ type FeatureKey,
24
+ type ReelSystemConfig,
25
+ } from '../config/ReelSystemConfig';
26
+
27
+ export interface CreateReelSystemOptions {
28
+ resolve: SymbolResolver;
29
+ config?: DeepPartial<ReelSystemConfig>;
30
+ /** Initial board (board[col][row]); defaults to empty cells. */
31
+ board?: CellData[][];
32
+ log?: (msg: string) => void;
33
+ /** Custom feature modules to register alongside the 13 built-ins. */
34
+ features?: ReelFeature[];
35
+ }
36
+
37
+ export interface ReelSystem {
38
+ /** Root container — add this to your scene. */
39
+ readonly view: Container;
40
+ readonly grid: ReelGrid;
41
+ /** Overlay layer above the grid (masked off) — draw bespoke rings/labels/sprites here. */
42
+ readonly fx: Container;
43
+ readonly config: ReelSystemConfig;
44
+ /** Current board (board[col][row]). */
45
+ readonly board: CellData[][];
46
+ /** Ways-to-win for the current grid (ways/megaways evaluation). */
47
+ readonly ways: number;
48
+ setBoard(board: CellData[][]): void;
49
+ /** Merge a partial config; rebuilds the grid when geometry changes. */
50
+ update(partial: DeepPartial<ReelSystemConfig>): void;
51
+ /** Replace the whole config. */
52
+ setConfig(config: ReelSystemConfig): void;
53
+ spin(target: CellData[][], opts?: SpinRunOpts): Promise<void>;
54
+ /** Run a cascade chain. With `freeSpins` + `cascade.multiplier.persistInFreeSpins`, the multiplier carries over instead of resetting. */
55
+ cascade(steps: TumbleStep[], opts?: { turbo?: boolean; freeSpins?: boolean }): Promise<void>;
56
+ /** Current running cascade multiplier. */
57
+ readonly multiplier: number;
58
+ /** Register a custom feature (or override a built-in by reusing its key). */
59
+ registerFeature(feature: ReelFeature): void;
60
+ /** All registered features (built-ins + custom) in canonical order. */
61
+ features(): ReelFeature[];
62
+ /** Enabled feature modules (built-ins + custom). */
63
+ enabledFeatures(): ReelFeature[];
64
+ /** Run a feature by key — built-in `FeatureKey` or a custom feature's key. */
65
+ runFeature(key: FeatureKey | string, opts?: { freeSpins?: boolean }): Promise<void>;
66
+ /** Build a FeatureContext for driving a feature/bespoke animation yourself. */
67
+ featureContext(opts?: { freeSpins?: boolean }): FeatureContext;
68
+ resize(cellSize: number): void;
69
+ skip(): void;
70
+ destroy(): void;
71
+ }
72
+
73
+ /** Drop a `rowsPerReel` whose length no longer matches `cols` so geometry/ways stay consistent. */
74
+ function normalizeGrid(cfg: ReelSystemConfig): ReelSystemConfig {
75
+ if (cfg.grid.rowsPerReel && cfg.grid.rowsPerReel.length !== cfg.grid.cols) {
76
+ const next = { ...cfg, grid: { ...cfg.grid } };
77
+ delete next.grid.rowsPerReel;
78
+ return next;
79
+ }
80
+ return cfg;
81
+ }
82
+
83
+ export function createReelSystem(opts: CreateReelSystemOptions): ReelSystem {
84
+ let config = normalizeGrid(resolveReelConfig(opts.config));
85
+ const resolve = opts.resolve;
86
+ const log = opts.log;
87
+
88
+ const view = new Container();
89
+ let grid!: ReelGrid;
90
+ let fx!: Container;
91
+ let spin!: SpinEngine;
92
+ let anticipation!: AnticipationController;
93
+ let tumble!: TumbleController;
94
+ let board: CellData[][] = opts.board ?? emptyBoard(config);
95
+ // custom features keyed by id; built-ins live in FEATURES/FEATURE_LIST
96
+ const custom = new Map<string, ReelFeature>();
97
+ for (const f of opts.features ?? []) custom.set(f.key, f);
98
+ const allFeatures = (): ReelFeature[] => {
99
+ const seen = new Set<string>();
100
+ const out: ReelFeature[] = [];
101
+ for (const f of [...FEATURE_LIST, ...custom.values()]) {
102
+ const eff = custom.get(f.key) ?? f; // a custom feature overrides a built-in with the same key
103
+ if (seen.has(eff.key)) continue;
104
+ seen.add(eff.key);
105
+ out.push(eff);
106
+ }
107
+ return out;
108
+ };
109
+ const findFeature = (key: string): ReelFeature | undefined =>
110
+ custom.get(key) ?? (FEATURES as Record<string, ReelFeature>)[key];
111
+
112
+ function buildGrid(): void {
113
+ if (grid) {
114
+ spin?.skip();
115
+ tumble?.skip();
116
+ for (const child of fx?.children.slice() ?? []) Tween.killTweensOf(child);
117
+ grid.destroy({ children: true });
118
+ }
119
+ grid = new ReelGrid({
120
+ cols: config.grid.cols,
121
+ rows: config.grid.rows,
122
+ rowsPerReel: config.grid.rowsPerReel ?? effectiveRowsPerReel(config.grid),
123
+ cellSize: config.grid.cellSize,
124
+ gap: config.grid.gap,
125
+ resolve,
126
+ frameStyle: config.grid.frameStyle,
127
+ mask: config.grid.mask,
128
+ decoration: config.grid.decoration?.padding
129
+ ? { padding: config.grid.decoration.padding }
130
+ : undefined,
131
+ });
132
+ fx = new Container();
133
+ grid.addChild(fx);
134
+ view.addChild(grid);
135
+ spin = new SpinEngine(grid, resolve, config.motion, config.win);
136
+ anticipation = new AnticipationController(config.anticipation);
137
+ tumble = new TumbleController(grid, config.cascade, config.win);
138
+ grid.setGrid(board);
139
+ }
140
+
141
+ function geometryChanged(next: ReelSystemConfig): boolean {
142
+ const a = config.grid,
143
+ b = next.grid;
144
+ return (
145
+ a.cols !== b.cols ||
146
+ a.rows !== b.rows ||
147
+ a.cellSize !== b.cellSize ||
148
+ a.gap !== b.gap ||
149
+ a.mask !== b.mask ||
150
+ JSON.stringify(a.rowsPerReel) !== JSON.stringify(b.rowsPerReel) ||
151
+ (a.decoration?.padding ?? 0) !== (b.decoration?.padding ?? 0)
152
+ );
153
+ }
154
+
155
+ function ctx(freeSpins?: boolean): FeatureContext {
156
+ return { grid, resolve, cfg: config, fx, board, freeSpins, log };
157
+ }
158
+
159
+ buildGrid();
160
+
161
+ const api: ReelSystem = {
162
+ view,
163
+ get grid() {
164
+ return grid;
165
+ },
166
+ get fx() {
167
+ return fx;
168
+ },
169
+ get config() {
170
+ return config;
171
+ },
172
+ get board() {
173
+ return board;
174
+ },
175
+ get ways() {
176
+ return waysCount(config.grid);
177
+ },
178
+
179
+ setBoard(next) {
180
+ board = next;
181
+ // grow the board to the grid shape so feature code can index safely
182
+ grid.setGrid(next);
183
+ },
184
+
185
+ setConfig(next) {
186
+ const norm = normalizeGrid(next);
187
+ const rebuild = geometryChanged(norm);
188
+ config = norm;
189
+ if (rebuild) buildGrid();
190
+ else {
191
+ spin.setConfig(config.motion);
192
+ spin.setWin(config.win);
193
+ anticipation.setConfig(config.anticipation);
194
+ tumble.setConfig(config.cascade);
195
+ tumble.setWin(config.win);
196
+ }
197
+ },
198
+
199
+ update(partial) {
200
+ api.setConfig(mergeReelConfig(config, partial));
201
+ },
202
+
203
+ async spin(target, runOpts) {
204
+ const data: SpinData = { targetGrid: target };
205
+ const decision = anticipation.decide(target);
206
+ let resetZoom: (() => Promise<void>) | null = null;
207
+ if (decision.active) {
208
+ log?.(`Anticipation on reels [${decision.reels.join(', ')}]`);
209
+ resetZoom = await anticipation.zoomIn(grid);
210
+ }
211
+ await spin.run(data, {
212
+ ...runOpts,
213
+ anticipateReels: decision.active ? decision.reels : undefined,
214
+ anticipateSlowdown: decision.slowdown,
215
+ anticipateHoldMs: decision.holdMs,
216
+ });
217
+ if (resetZoom) await resetZoom();
218
+ board = target;
219
+ },
220
+
221
+ get multiplier() {
222
+ return tumble.multiplier;
223
+ },
224
+
225
+ async cascade(steps, cOpts) {
226
+ // keep the multiplier climbing across free-spins when configured; otherwise reset per spin
227
+ const persist = config.cascade.multiplier.persistInFreeSpins && !!cOpts?.freeSpins;
228
+ if (!persist) tumble.resetMultiplier();
229
+ for (let i = 0; i < steps.length; i++) {
230
+ await tumble.step(steps[i], i, cOpts);
231
+ board = steps[i].settledGrid;
232
+ }
233
+ if (config.cascade.multiplier.enabled) log?.(`Cascade multiplier ×${tumble.multiplier}`);
234
+ },
235
+
236
+ registerFeature(feature) {
237
+ custom.set(feature.key, feature);
238
+ },
239
+
240
+ features() {
241
+ return allFeatures();
242
+ },
243
+
244
+ enabledFeatures() {
245
+ return allFeatures().filter((f) => f.enabled(config));
246
+ },
247
+
248
+ featureContext(fOpts) {
249
+ return ctx(fOpts?.freeSpins);
250
+ },
251
+
252
+ async runFeature(key, fOpts) {
253
+ const feature = findFeature(key);
254
+ if (!feature) {
255
+ log?.(`Unknown feature "${key}"`);
256
+ return;
257
+ }
258
+ if (!feature.enabled(config)) {
259
+ log?.(`${feature.label} is disabled`);
260
+ return;
261
+ }
262
+ await feature.demo(ctx(fOpts?.freeSpins));
263
+ },
264
+
265
+ resize(cellSize) {
266
+ config = mergeReelConfig(config, { grid: { cellSize } } as DeepPartial<ReelSystemConfig>);
267
+ grid.resize(cellSize);
268
+ },
269
+
270
+ skip() {
271
+ spin.skip();
272
+ tumble.skip();
273
+ // kill in-flight overlay tweens (labels/rings) so a rebuild never animates destroyed nodes
274
+ for (const child of fx.children.slice()) Tween.killTweensOf(child);
275
+ fx.removeChildren().forEach((c) => c.destroy());
276
+ },
277
+
278
+ destroy() {
279
+ api.skip();
280
+ grid.destroy({ children: true });
281
+ view.destroy({ children: true });
282
+ },
283
+ };
284
+
285
+ return api;
286
+ }
287
+
288
+ function emptyBoard(cfg: ReelSystemConfig): CellData[][] {
289
+ const rows = effectiveRowsPerReel(cfg.grid);
290
+ return Array.from({ length: cfg.grid.cols }, (_, c) =>
291
+ Array.from({ length: rows[c] }, () => ({ symbol: null as string | null })),
292
+ );
293
+ }
294
+
295
+ export { DEFAULT_REEL_CONFIG };
package/src/vite/index.ts CHANGED
@@ -89,9 +89,6 @@ export function defineGameConfig(config: GameConfig = {}): UserConfig {
89
89
  resolve: {
90
90
  dedupe: [
91
91
  'pixi.js',
92
- 'react',
93
- 'react-dom',
94
- 'react-reconciler',
95
92
  ],
96
93
  ...userVite.resolve,
97
94
  },
@@ -99,8 +96,6 @@ export function defineGameConfig(config: GameConfig = {}): UserConfig {
99
96
  optimizeDeps: {
100
97
  include: [
101
98
  'pixi.js',
102
- 'react',
103
- 'react-dom',
104
99
  ],
105
100
  exclude: [
106
101
  'fengari',
@@ -1,3 +0,0 @@
1
- 'use strict';
2
-
3
- //# sourceMappingURL=react-jsx.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"react-jsx.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}