@slot-engine/core 0.0.10 → 0.1.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 CHANGED
@@ -1,6 +1,3 @@
1
- > [!NOTE]
2
- > This software **is work in progress** and may currently miss features or contain bugs. Feel free to contribute to help improve this project. Breaking changes may occur at any time.
3
-
4
1
  # Slot Engine Core
5
2
 
6
3
  Library for configuring and simulating slot games. Produces output compatible with Stake Engine / Stake RGS.
package/dist/index.d.mts CHANGED
@@ -428,7 +428,11 @@ declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymb
428
428
  /**
429
429
  * Draws a board using specified reel stops.
430
430
  */
431
- drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>): void;
431
+ drawBoardWithForcedStops(opts: {
432
+ reels: Reels;
433
+ forcedStops: Record<string, number>;
434
+ randomOffset?: boolean;
435
+ }): void;
432
436
  /**
433
437
  * Draws a board using random reel stops.
434
438
  */
@@ -440,7 +444,10 @@ declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymb
440
444
  tumbleBoard(symbolsToDelete: Array<{
441
445
  reelIdx: number;
442
446
  rowIdx: number;
443
- }>): void;
447
+ }>): {
448
+ newBoardSymbols: Record<string, GameSymbol[]>;
449
+ newPaddingTopSymbols: Record<string, GameSymbol[]>;
450
+ };
444
451
  }
445
452
 
446
453
  declare class Recorder {
@@ -609,7 +616,7 @@ declare class ReelSet {
609
616
  reels: Reels;
610
617
  protected rng: RandomNumberGenerator;
611
618
  constructor(opts: ReelSetOptions);
612
- generateReels(simulation: Simulation): void;
619
+ generateReels(config: GameConfig): ReelSet;
613
620
  /**
614
621
  * Reads a reelset CSV file and returns the reels as arrays of GameSymbols.
615
622
  */
@@ -687,6 +694,11 @@ interface GameModeOpts {
687
694
 
688
695
  declare class GameService<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> extends AbstractService {
689
696
  constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>);
697
+ /**
698
+ * Intended for internal use only.\
699
+ * Generates reels for all reel sets in the game configuration.
700
+ */
701
+ _generateReels(): void;
690
702
  /**
691
703
  * Retrieves a reel set by its ID within a specific game mode.
692
704
  */
@@ -1344,7 +1356,7 @@ declare class GeneratedReelSet extends ReelSet {
1344
1356
  * Checks if a symbol can be placed at the target index without violating spacing rules.
1345
1357
  */
1346
1358
  private violatesSpacing;
1347
- generateReels({ gameConfig: config }: Simulation): void;
1359
+ generateReels(config: GameConfig): this;
1348
1360
  }
1349
1361
  interface GeneratedReelSetOptions extends ReelSetOptions {
1350
1362
  /**
@@ -1458,7 +1470,7 @@ declare class StaticReelSet extends ReelSet {
1458
1470
  private _strReels;
1459
1471
  constructor(opts: StaticReelSetOptions);
1460
1472
  private validateConfig;
1461
- generateReels({ gameConfig: config }: Simulation): void;
1473
+ generateReels(config: GameConfig): this;
1462
1474
  }
1463
1475
  interface StaticReelSetOptions extends ReelSetOptions {
1464
1476
  reels?: string[][];
@@ -1537,7 +1549,11 @@ declare class StandaloneBoard {
1537
1549
  /**
1538
1550
  * Draws a board using specified reel stops.
1539
1551
  */
1540
- drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>): void;
1552
+ drawBoardWithForcedStops(opts: {
1553
+ reels: Reels;
1554
+ forcedStops: Record<string, number>;
1555
+ randomOffset?: boolean;
1556
+ }): void;
1541
1557
  /**
1542
1558
  * Draws a board using random reel stops.
1543
1559
  */
@@ -1549,7 +1565,10 @@ declare class StandaloneBoard {
1549
1565
  tumbleBoard(symbolsToDelete: Array<{
1550
1566
  reelIdx: number;
1551
1567
  rowIdx: number;
1552
- }>): void;
1568
+ }>): {
1569
+ newBoardSymbols: Record<string, GameSymbol[]>;
1570
+ newPaddingTopSymbols: Record<string, GameSymbol[]>;
1571
+ };
1553
1572
  }
1554
1573
  interface StandaloneBoardOptions {
1555
1574
  ctx: GameContext;
package/dist/index.d.ts CHANGED
@@ -428,7 +428,11 @@ declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymb
428
428
  /**
429
429
  * Draws a board using specified reel stops.
430
430
  */
431
- drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>): void;
431
+ drawBoardWithForcedStops(opts: {
432
+ reels: Reels;
433
+ forcedStops: Record<string, number>;
434
+ randomOffset?: boolean;
435
+ }): void;
432
436
  /**
433
437
  * Draws a board using random reel stops.
434
438
  */
@@ -440,7 +444,10 @@ declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymb
440
444
  tumbleBoard(symbolsToDelete: Array<{
441
445
  reelIdx: number;
442
446
  rowIdx: number;
443
- }>): void;
447
+ }>): {
448
+ newBoardSymbols: Record<string, GameSymbol[]>;
449
+ newPaddingTopSymbols: Record<string, GameSymbol[]>;
450
+ };
444
451
  }
445
452
 
446
453
  declare class Recorder {
@@ -609,7 +616,7 @@ declare class ReelSet {
609
616
  reels: Reels;
610
617
  protected rng: RandomNumberGenerator;
611
618
  constructor(opts: ReelSetOptions);
612
- generateReels(simulation: Simulation): void;
619
+ generateReels(config: GameConfig): ReelSet;
613
620
  /**
614
621
  * Reads a reelset CSV file and returns the reels as arrays of GameSymbols.
615
622
  */
@@ -687,6 +694,11 @@ interface GameModeOpts {
687
694
 
688
695
  declare class GameService<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> extends AbstractService {
689
696
  constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>);
697
+ /**
698
+ * Intended for internal use only.\
699
+ * Generates reels for all reel sets in the game configuration.
700
+ */
701
+ _generateReels(): void;
690
702
  /**
691
703
  * Retrieves a reel set by its ID within a specific game mode.
692
704
  */
@@ -1344,7 +1356,7 @@ declare class GeneratedReelSet extends ReelSet {
1344
1356
  * Checks if a symbol can be placed at the target index without violating spacing rules.
1345
1357
  */
1346
1358
  private violatesSpacing;
1347
- generateReels({ gameConfig: config }: Simulation): void;
1359
+ generateReels(config: GameConfig): this;
1348
1360
  }
1349
1361
  interface GeneratedReelSetOptions extends ReelSetOptions {
1350
1362
  /**
@@ -1458,7 +1470,7 @@ declare class StaticReelSet extends ReelSet {
1458
1470
  private _strReels;
1459
1471
  constructor(opts: StaticReelSetOptions);
1460
1472
  private validateConfig;
1461
- generateReels({ gameConfig: config }: Simulation): void;
1473
+ generateReels(config: GameConfig): this;
1462
1474
  }
1463
1475
  interface StaticReelSetOptions extends ReelSetOptions {
1464
1476
  reels?: string[][];
@@ -1537,7 +1549,11 @@ declare class StandaloneBoard {
1537
1549
  /**
1538
1550
  * Draws a board using specified reel stops.
1539
1551
  */
1540
- drawBoardWithForcedStops(reels: Reels, forcedStops: Record<string, number>): void;
1552
+ drawBoardWithForcedStops(opts: {
1553
+ reels: Reels;
1554
+ forcedStops: Record<string, number>;
1555
+ randomOffset?: boolean;
1556
+ }): void;
1541
1557
  /**
1542
1558
  * Draws a board using random reel stops.
1543
1559
  */
@@ -1549,7 +1565,10 @@ declare class StandaloneBoard {
1549
1565
  tumbleBoard(symbolsToDelete: Array<{
1550
1566
  reelIdx: number;
1551
1567
  rowIdx: number;
1552
- }>): void;
1568
+ }>): {
1569
+ newBoardSymbols: Record<string, GameSymbol[]>;
1570
+ newPaddingTopSymbols: Record<string, GameSymbol[]>;
1571
+ };
1553
1572
  }
1554
1573
  interface StandaloneBoardOptions {
1555
1574
  ctx: GameContext;
package/dist/index.js CHANGED
@@ -650,7 +650,11 @@ var Board = class {
650
650
  for (const [r, stopPos] of Object.entries(opts.forcedStops)) {
651
651
  const reelIdx = Number(r);
652
652
  const symCount = symbolsPerReel[reelIdx];
653
- finalReelStops[reelIdx] = stopPos - Math.round(opts.ctx.services.rng.randomFloat(0, symCount - 1));
653
+ if (opts.forcedStopsOffset !== false) {
654
+ finalReelStops[reelIdx] = stopPos - Math.round(opts.ctx.services.rng.randomFloat(0, symCount - 1));
655
+ } else {
656
+ finalReelStops[reelIdx] = stopPos;
657
+ }
654
658
  if (finalReelStops[reelIdx] < 0) {
655
659
  finalReelStops[reelIdx] = opts.reels[reelIdx].length + finalReelStops[reelIdx];
656
660
  }
@@ -693,15 +697,22 @@ var Board = class {
693
697
  );
694
698
  }
695
699
  const reels = this.lastUsedReels;
696
- opts.symbolsToDelete.forEach(({ reelIdx, rowIdx }) => {
700
+ const sortedDeletions = [...opts.symbolsToDelete].sort((a, b) => b.rowIdx - a.rowIdx);
701
+ sortedDeletions.forEach(({ reelIdx, rowIdx }) => {
697
702
  this.reels[reelIdx].splice(rowIdx, 1);
698
703
  });
699
704
  const newFirstSymbolPositions = {};
705
+ const newBoardSymbols = {};
706
+ const newPaddingTopSymbols = {};
700
707
  for (let ridx = 0; ridx < reelsAmount; ridx++) {
701
708
  while (this.reels[ridx].length < symbolsPerReel[ridx]) {
702
709
  const padSymbol = this.paddingTop[ridx].pop();
703
710
  if (padSymbol) {
704
711
  this.reels[ridx].unshift(padSymbol);
712
+ if (!newBoardSymbols[ridx]) {
713
+ newBoardSymbols[ridx] = [];
714
+ }
715
+ newBoardSymbols[ridx].unshift(padSymbol);
705
716
  } else {
706
717
  break;
707
718
  }
@@ -715,17 +726,30 @@ var Board = class {
715
726
  (0, import_assert3.default)(newSymbol, "Failed to get new symbol for tumbling.");
716
727
  this.reels[ridx].unshift(newSymbol);
717
728
  newFirstSymbolPositions[ridx] = symbolPos;
729
+ if (!newBoardSymbols[ridx]) {
730
+ newBoardSymbols[ridx] = [];
731
+ }
732
+ newBoardSymbols[ridx].unshift(newSymbol);
718
733
  }
719
734
  }
720
735
  for (let ridx = 0; ridx < reelsAmount; ridx++) {
721
736
  const firstSymbolPos = newFirstSymbolPositions[ridx];
737
+ if (firstSymbolPos === void 0) continue;
722
738
  for (let p = 1; p <= padSymbols; p++) {
723
739
  const topPos = (firstSymbolPos - p + reels[ridx].length) % reels[ridx].length;
724
740
  const padSymbol = reels[ridx][topPos];
725
741
  (0, import_assert3.default)(padSymbol, "Failed to get new padding symbol for tumbling.");
726
742
  this.paddingTop[ridx].unshift(padSymbol);
743
+ if (!newPaddingTopSymbols[ridx]) {
744
+ newPaddingTopSymbols[ridx] = [];
745
+ }
746
+ newPaddingTopSymbols[ridx].unshift(padSymbol);
727
747
  }
728
748
  }
749
+ return {
750
+ newBoardSymbols,
751
+ newPaddingTopSymbols
752
+ };
729
753
  }
730
754
  };
731
755
 
@@ -845,8 +869,8 @@ var BoardService = class extends AbstractService {
845
869
  /**
846
870
  * Draws a board using specified reel stops.
847
871
  */
848
- drawBoardWithForcedStops(reels, forcedStops) {
849
- this.drawBoardMixed(reels, forcedStops);
872
+ drawBoardWithForcedStops(opts) {
873
+ this.drawBoardMixed(opts.reels, opts.forcedStops, opts.randomOffset);
850
874
  }
851
875
  /**
852
876
  * Draws a board using random reel stops.
@@ -854,18 +878,19 @@ var BoardService = class extends AbstractService {
854
878
  drawBoardWithRandomStops(reels) {
855
879
  this.drawBoardMixed(reels);
856
880
  }
857
- drawBoardMixed(reels, forcedStops) {
881
+ drawBoardMixed(reels, forcedStops, forcedStopsOffset) {
858
882
  this.board.drawBoardMixed({
859
883
  ctx: this.ctx(),
860
884
  reels,
861
- forcedStops
885
+ forcedStops,
886
+ forcedStopsOffset
862
887
  });
863
888
  }
864
889
  /**
865
890
  * Tumbles the board. All given symbols will be deleted and new symbols will fall from the top.
866
891
  */
867
892
  tumbleBoard(symbolsToDelete) {
868
- this.board.tumbleBoard({
893
+ return this.board.tumbleBoard({
869
894
  ctx: this.ctx(),
870
895
  symbolsToDelete
871
896
  });
@@ -958,6 +983,25 @@ var GameService = class extends AbstractService {
958
983
  constructor(ctx) {
959
984
  super(ctx);
960
985
  }
986
+ /**
987
+ * Intended for internal use only.\
988
+ * Generates reels for all reel sets in the game configuration.
989
+ */
990
+ _generateReels() {
991
+ const config = this.ctx().config;
992
+ for (const mode of Object.values(config.gameModes)) {
993
+ if (mode.reelSets && mode.reelSets.length > 0) {
994
+ for (const reelSet of Object.values(mode.reelSets)) {
995
+ reelSet.associatedGameModeName = mode.name;
996
+ reelSet.generateReels(config);
997
+ }
998
+ } else {
999
+ throw new Error(
1000
+ `Game mode "${mode.name}" has no reel sets defined. Cannot generate reelset files.`
1001
+ );
1002
+ }
1003
+ }
1004
+ }
961
1005
  /**
962
1006
  * Retrieves a reel set by its ID within a specific game mode.
963
1007
  */
@@ -1839,7 +1883,7 @@ Simulating game mode: ${mode}`);
1839
1883
  if (mode.reelSets && mode.reelSets.length > 0) {
1840
1884
  for (const reelSet of Object.values(mode.reelSets)) {
1841
1885
  reelSet.associatedGameModeName = mode.name;
1842
- reelSet.generateReels(this);
1886
+ reelSet.generateReels(this.gameConfig);
1843
1887
  }
1844
1888
  } else {
1845
1889
  throw new Error(
@@ -3013,7 +3057,7 @@ var ReelSet = class {
3013
3057
  this.rng = new RandomNumberGenerator();
3014
3058
  this.rng.setSeed(opts.seed ?? 0);
3015
3059
  }
3016
- generateReels(simulation) {
3060
+ generateReels(config) {
3017
3061
  throw new Error("Not implemented");
3018
3062
  }
3019
3063
  /**
@@ -3185,7 +3229,7 @@ var GeneratedReelSet = class extends ReelSet {
3185
3229
  }
3186
3230
  return false;
3187
3231
  }
3188
- generateReels({ gameConfig: config }) {
3232
+ generateReels(config) {
3189
3233
  this.validateConfig(config);
3190
3234
  const gameMode = config.gameModes[this.associatedGameModeName];
3191
3235
  if (!gameMode) {
@@ -3200,7 +3244,7 @@ var GeneratedReelSet = class extends ReelSet {
3200
3244
  const exists = import_fs5.default.existsSync(filePath);
3201
3245
  if (exists && !this.overrideExisting) {
3202
3246
  this.reels = this.parseReelsetCSV(filePath, config);
3203
- return;
3247
+ return this;
3204
3248
  }
3205
3249
  if (!exists && this.symbolWeights.size === 0) {
3206
3250
  throw new Error(
@@ -3347,6 +3391,7 @@ var GeneratedReelSet = class extends ReelSet {
3347
3391
  `Generated reelset ${this.id} for game mode ${this.associatedGameModeName}`
3348
3392
  );
3349
3393
  }
3394
+ return this;
3350
3395
  }
3351
3396
  };
3352
3397
 
@@ -3382,7 +3427,7 @@ var StaticReelSet = class extends ReelSet {
3382
3427
  );
3383
3428
  }
3384
3429
  }
3385
- generateReels({ gameConfig: config }) {
3430
+ generateReels(config) {
3386
3431
  this.validateConfig(config);
3387
3432
  if (this._strReels.length > 0) {
3388
3433
  this.reels = this._strReels.map((reel) => {
@@ -3400,6 +3445,7 @@ var StaticReelSet = class extends ReelSet {
3400
3445
  if (this.csvPath) {
3401
3446
  this.reels = this.parseReelsetCSV(this.csvPath, config);
3402
3447
  }
3448
+ return this;
3403
3449
  }
3404
3450
  };
3405
3451
 
@@ -3524,8 +3570,8 @@ var StandaloneBoard = class {
3524
3570
  /**
3525
3571
  * Draws a board using specified reel stops.
3526
3572
  */
3527
- drawBoardWithForcedStops(reels, forcedStops) {
3528
- this.drawBoardMixed(reels, forcedStops);
3573
+ drawBoardWithForcedStops(opts) {
3574
+ this.drawBoardMixed(opts.reels, opts.forcedStops, opts.randomOffset);
3529
3575
  }
3530
3576
  /**
3531
3577
  * Draws a board using random reel stops.
@@ -3533,11 +3579,12 @@ var StandaloneBoard = class {
3533
3579
  drawBoardWithRandomStops(reels) {
3534
3580
  this.drawBoardMixed(reels);
3535
3581
  }
3536
- drawBoardMixed(reels, forcedStops) {
3582
+ drawBoardMixed(reels, forcedStops, forcedStopsOffset) {
3537
3583
  this.board.drawBoardMixed({
3538
3584
  ctx: this.ctx,
3539
3585
  reels,
3540
3586
  forcedStops,
3587
+ forcedStopsOffset,
3541
3588
  reelsAmount: this.reelsAmount,
3542
3589
  symbolsPerReel: this.symbolsPerReel,
3543
3590
  padSymbols: this.padSymbols
@@ -3547,7 +3594,7 @@ var StandaloneBoard = class {
3547
3594
  * Tumbles the board. All given symbols will be deleted and new symbols will fall from the top.
3548
3595
  */
3549
3596
  tumbleBoard(symbolsToDelete) {
3550
- this.board.tumbleBoard({
3597
+ return this.board.tumbleBoard({
3551
3598
  ctx: this.ctx,
3552
3599
  symbolsToDelete,
3553
3600
  reelsAmount: this.reelsAmount,