@slot-engine/core 0.1.7 → 0.1.9

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/dist/index.mjs CHANGED
@@ -471,6 +471,11 @@ var Board = class {
471
471
  this.reels[reelIndex] = this.reels[reelIndex] || [];
472
472
  this.reels[reelIndex][rowIndex] = symbol;
473
473
  }
474
+ removeSymbol(reelIndex, rowIndex) {
475
+ if (this.reels[reelIndex]) {
476
+ this.reels[reelIndex].splice(rowIndex, 1);
477
+ }
478
+ }
474
479
  makeEmptyReels(opts) {
475
480
  const length = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
476
481
  assert3(length, "Cannot make empty reels without context or reelsAmount.");
@@ -660,18 +665,35 @@ var Board = class {
660
665
  this.reels[ridx][row] = symbol;
661
666
  }
662
667
  }
668
+ return {
669
+ stopPositions: this.lastDrawnReelStops
670
+ };
663
671
  }
664
672
  tumbleBoard(opts) {
665
673
  assert3(this.lastDrawnReelStops.length > 0, "Cannot tumble board before drawing it.");
666
674
  const reelsAmount = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
667
675
  const symbolsPerReel = opts.symbolsPerReel ?? opts.ctx.services.game.getCurrentGameMode().symbolsPerReel;
668
676
  const padSymbols = opts.padSymbols ?? opts.ctx.config.padSymbols;
677
+ if (opts.startingStops) {
678
+ assert3(
679
+ opts.startingStops.length === reelsAmount,
680
+ "Starting stops length does not match reels amount."
681
+ );
682
+ assert3(opts.reels, "Reels must be provided when using startingStops.");
683
+ }
684
+ if (opts.reels) {
685
+ assert3(opts.startingStops, "Starting stops must be provided when using reels.");
686
+ }
669
687
  if (!opts.ctx && !reelsAmount && !symbolsPerReel) {
670
688
  throw new Error(
671
689
  "If ctx is not provided, reelsAmount and symbolsPerReel must be given."
672
690
  );
673
691
  }
674
- const reels = this.lastUsedReels;
692
+ const reels = opts.reels || this.lastUsedReels;
693
+ assert3(
694
+ reels.length === reelsAmount,
695
+ "Given reels length does not match reels amount."
696
+ );
675
697
  const sortedDeletions = [...opts.symbolsToDelete].sort((a, b) => b.rowIdx - a.rowIdx);
676
698
  sortedDeletions.forEach(({ reelIdx, rowIdx }) => {
677
699
  this.reels[reelIdx].splice(rowIdx, 1);
@@ -681,7 +703,7 @@ var Board = class {
681
703
  const newPaddingTopSymbols = {};
682
704
  for (let ridx = 0; ridx < reelsAmount; ridx++) {
683
705
  while (this.reels[ridx].length < symbolsPerReel[ridx]) {
684
- const padSymbol = this.paddingTop[ridx].pop();
706
+ const padSymbol = this.paddingTop[ridx]?.pop();
685
707
  if (padSymbol) {
686
708
  this.reels[ridx].unshift(padSymbol);
687
709
  if (!newBoardSymbols[ridx]) {
@@ -697,7 +719,16 @@ var Board = class {
697
719
  const symbolsNeeded = symbolsPerReel[ridx] - this.reels[ridx].length;
698
720
  for (let s = 0; s < symbolsNeeded; s++) {
699
721
  const symbolPos = (stopBeforePad - s + reels[ridx].length) % reels[ridx].length;
700
- const newSymbol = reels[ridx][symbolPos];
722
+ let newSymbol = reels[ridx][symbolPos];
723
+ const startStops = opts.startingStops;
724
+ if (startStops) {
725
+ const forcedSym = reels[ridx][startStops?.[ridx]];
726
+ assert3(
727
+ forcedSym,
728
+ `Failed to get forced symbol for tumbling. Tried to get symbol for position ${startStops?.[ridx]} on reel ${ridx}.`
729
+ );
730
+ newSymbol = forcedSym;
731
+ }
701
732
  assert3(newSymbol, "Failed to get new symbol for tumbling.");
702
733
  this.reels[ridx].unshift(newSymbol);
703
734
  newFirstSymbolPositions[ridx] = symbolPos;
@@ -721,9 +752,11 @@ var Board = class {
721
752
  newPaddingTopSymbols[ridx].unshift(padSymbol);
722
753
  }
723
754
  }
724
- this.lastDrawnReelStops = this.lastDrawnReelStops.map((stop, ridx) => {
725
- return newFirstSymbolPositions[ridx] ?? stop;
726
- });
755
+ if (!opts.reels && !opts.startingStops) {
756
+ this.lastDrawnReelStops = this.lastDrawnReelStops.map((stop, ridx) => {
757
+ return newFirstSymbolPositions[ridx] ?? stop;
758
+ });
759
+ }
727
760
  return {
728
761
  newBoardSymbols,
729
762
  newPaddingTopSymbols
@@ -786,6 +819,12 @@ var BoardService = class extends AbstractService {
786
819
  setSymbol(reelIndex, rowIndex, symbol) {
787
820
  this.board.setSymbol(reelIndex, rowIndex, symbol);
788
821
  }
822
+ /**
823
+ * Removes the symbol at the specified reel and row index.
824
+ */
825
+ removeSymbol(reelIndex, rowIndex) {
826
+ this.board.removeSymbol(reelIndex, rowIndex);
827
+ }
789
828
  resetReels() {
790
829
  this.board.resetReels({
791
830
  ctx: this.ctx()
@@ -861,16 +900,16 @@ var BoardService = class extends AbstractService {
861
900
  * Draws a board using specified reel stops.
862
901
  */
863
902
  drawBoardWithForcedStops(opts) {
864
- this.drawBoardMixed(opts.reels, opts.forcedStops, opts.randomOffset);
903
+ return this.drawBoardMixed(opts.reels, opts.forcedStops, opts.randomOffset);
865
904
  }
866
905
  /**
867
906
  * Draws a board using random reel stops.
868
907
  */
869
908
  drawBoardWithRandomStops(reels) {
870
- this.drawBoardMixed(reels);
909
+ return this.drawBoardMixed(reels);
871
910
  }
872
911
  drawBoardMixed(reels, forcedStops, forcedStopsOffset) {
873
- this.board.drawBoardMixed({
912
+ return this.board.drawBoardMixed({
874
913
  ctx: this.ctx(),
875
914
  reels,
876
915
  forcedStops,
@@ -886,6 +925,21 @@ var BoardService = class extends AbstractService {
886
925
  symbolsToDelete
887
926
  });
888
927
  }
928
+ /**
929
+ * **Experimental - May be changed or replaced in the future**
930
+ *
931
+ * Tumbles the board normally like `tumbleBoard`, but allows specifying a different reel set to get symbols from.\
932
+ * Also requires specifying the starting stops from where the symbols will be tumbled.\
933
+ * **This method does not remember the last tumbled position. Use this if you need to do a singular one-off tumble.**
934
+ */
935
+ tumbleBoardAndForget(opts) {
936
+ return this.board.tumbleBoard({
937
+ ctx: this.ctx(),
938
+ symbolsToDelete: opts.symbolsToDelete,
939
+ reels: opts.reels,
940
+ startingStops: opts.forcedStops
941
+ });
942
+ }
889
943
  /**
890
944
  * Dedupes win symbols for tumble.\
891
945
  * Returns a list of symbols to remove from the board based on the given win combinations.
@@ -896,6 +950,26 @@ var BoardService = class extends AbstractService {
896
950
  dedupeWinSymbolsForTumble(winCombinations) {
897
951
  return this.board.dedupeWinSymbolsForTumble(winCombinations);
898
952
  }
953
+ /**
954
+ * Sets the symbolsPerReel for the current game mode.
955
+ *
956
+ * The value will be reset to the original value as set in the game mode config in the next simulation.
957
+ */
958
+ setSymbolsPerReel(symbolsPerReel) {
959
+ this.ctx().config.gameModes[this.ctx().state.currentGameMode]._setSymbolsPerReel(
960
+ symbolsPerReel
961
+ );
962
+ }
963
+ /**
964
+ * Sets the reelsAmount for the current game mode.
965
+ *
966
+ * The value will be reset to the original value as set in the game mode config in the next simulation.
967
+ */
968
+ setReelsAmount(reelsAmount) {
969
+ this.ctx().config.gameModes[this.ctx().state.currentGameMode]._setReelsAmount(
970
+ reelsAmount
971
+ );
972
+ }
899
973
  };
900
974
 
901
975
  // src/service/data.ts
@@ -1800,6 +1874,9 @@ ${error.stack}
1800
1874
  criteria: ctx.state.currentResultSet.criteria
1801
1875
  })
1802
1876
  );
1877
+ Object.values(ctx.config.gameModes).forEach((mode) => {
1878
+ mode._resetTempValues();
1879
+ });
1803
1880
  }
1804
1881
  resetState(ctx) {
1805
1882
  ctx.services.rng.setSeedIfDifferent(ctx.state.currentSimulationId);
@@ -2748,6 +2825,8 @@ var defineGameModes = (gameModes) => gameModes;
2748
2825
  import assert10 from "assert";
2749
2826
  var GameMode = class {
2750
2827
  name;
2828
+ _reelsAmount;
2829
+ _symbolsPerReel;
2751
2830
  reelsAmount;
2752
2831
  symbolsPerReel;
2753
2832
  cost;
@@ -2757,7 +2836,9 @@ var GameMode = class {
2757
2836
  isBonusBuy;
2758
2837
  constructor(opts) {
2759
2838
  this.name = opts.name;
2839
+ this._reelsAmount = opts.reelsAmount;
2760
2840
  this.reelsAmount = opts.reelsAmount;
2841
+ this._symbolsPerReel = opts.symbolsPerReel;
2761
2842
  this.symbolsPerReel = opts.symbolsPerReel;
2762
2843
  this.cost = opts.cost;
2763
2844
  this.rtp = opts.rtp;
@@ -2771,6 +2852,29 @@ var GameMode = class {
2771
2852
  );
2772
2853
  assert10(this.reelSets.length > 0, "GameMode must have at least one ReelSet defined.");
2773
2854
  }
2855
+ /**
2856
+ * Intended for internal use only.
2857
+ */
2858
+ _resetTempValues() {
2859
+ this.reelsAmount = this._reelsAmount;
2860
+ this.symbolsPerReel = this._symbolsPerReel;
2861
+ }
2862
+ /**
2863
+ * Intended for internal use only.
2864
+ */
2865
+ _setSymbolsPerReel(symbolsPerReel) {
2866
+ assert10(
2867
+ symbolsPerReel.length === this._reelsAmount,
2868
+ "symbolsPerReel length must match reelsAmount."
2869
+ );
2870
+ this.symbolsPerReel = symbolsPerReel;
2871
+ }
2872
+ /**
2873
+ * Intended for internal use only.
2874
+ */
2875
+ _setReelsAmount(reelsAmount) {
2876
+ this.reelsAmount = reelsAmount;
2877
+ }
2774
2878
  };
2775
2879
 
2776
2880
  // src/win-types/index.ts
@@ -3394,9 +3498,9 @@ var GeneratedReelSet = class extends ReelSet {
3394
3498
  `Error generating reels for game mode "${this.associatedGameModeName}". It's not defined in the game config.`
3395
3499
  );
3396
3500
  }
3501
+ const outputDir = path7.join(config.rootDir, config.outputDir);
3397
3502
  const filePath = path7.join(
3398
- config.rootDir,
3399
- config.outputDir,
3503
+ outputDir,
3400
3504
  `reels_${this.associatedGameModeName}-${this.id}.csv`
3401
3505
  );
3402
3506
  const exists = fs5.existsSync(filePath);
@@ -3543,12 +3647,13 @@ var GeneratedReelSet = class extends ReelSet {
3543
3647
  }
3544
3648
  const csvString = csvRows.map((row) => row.join(",")).join("\n");
3545
3649
  if (isMainThread5) {
3650
+ createDirIfNotExists(outputDir);
3546
3651
  fs5.writeFileSync(filePath, csvString);
3547
- this.reels = this.parseReelsetCSV(filePath, config);
3548
3652
  console.log(
3549
3653
  `Generated reelset ${this.id} for game mode ${this.associatedGameModeName}`
3550
3654
  );
3551
3655
  }
3656
+ this.reels = this.parseReelsetCSV(filePath, config);
3552
3657
  return this;
3553
3658
  }
3554
3659
  };
@@ -3653,6 +3758,12 @@ var StandaloneBoard = class {
3653
3758
  setSymbol(reelIndex, rowIndex, symbol) {
3654
3759
  this.board.setSymbol(reelIndex, rowIndex, symbol);
3655
3760
  }
3761
+ /**
3762
+ * Removes the symbol at the specified reel and row index.
3763
+ */
3764
+ removeSymbol(reelIndex, rowIndex) {
3765
+ this.board.removeSymbol(reelIndex, rowIndex);
3766
+ }
3656
3767
  resetReels() {
3657
3768
  this.board.resetReels({
3658
3769
  ctx: this.ctx
@@ -3729,16 +3840,16 @@ var StandaloneBoard = class {
3729
3840
  * Draws a board using specified reel stops.
3730
3841
  */
3731
3842
  drawBoardWithForcedStops(opts) {
3732
- this.drawBoardMixed(opts.reels, opts.forcedStops, opts.randomOffset);
3843
+ return this.drawBoardMixed(opts.reels, opts.forcedStops, opts.randomOffset);
3733
3844
  }
3734
3845
  /**
3735
3846
  * Draws a board using random reel stops.
3736
3847
  */
3737
3848
  drawBoardWithRandomStops(reels) {
3738
- this.drawBoardMixed(reels);
3849
+ return this.drawBoardMixed(reels);
3739
3850
  }
3740
3851
  drawBoardMixed(reels, forcedStops, forcedStopsOffset) {
3741
- this.board.drawBoardMixed({
3852
+ return this.board.drawBoardMixed({
3742
3853
  ctx: this.ctx,
3743
3854
  reels,
3744
3855
  forcedStops,
@@ -3760,16 +3871,46 @@ var StandaloneBoard = class {
3760
3871
  padSymbols: this.padSymbols
3761
3872
  });
3762
3873
  }
3874
+ /**
3875
+ * **Experimental - May be changed or replaced in the future**
3876
+ *
3877
+ * Tumbles the board normally like `tumbleBoard`, but allows specifying a different reel set to get symbols from.\
3878
+ * Also requires specifying the starting stops from where the symbols will be tumbled.\
3879
+ * **This method does not remember the last tumbled position. Use this if you need to do a singular one-off tumble.**
3880
+ */
3881
+ tumbleBoardAndForget(opts) {
3882
+ return this.board.tumbleBoard({
3883
+ ctx: this.ctx,
3884
+ symbolsToDelete: opts.symbolsToDelete,
3885
+ reelsAmount: this.reelsAmount,
3886
+ symbolsPerReel: this.symbolsPerReel,
3887
+ padSymbols: this.padSymbols,
3888
+ reels: opts.reels,
3889
+ startingStops: opts.forcedStops
3890
+ });
3891
+ }
3763
3892
  /**
3764
3893
  * Dedupes win symbols for tumble.\
3765
3894
  * Returns a list of symbols to remove from the board based on the given win combinations.
3766
- *
3895
+ *
3767
3896
  * Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
3768
3897
  * this method ensures that each symbol is only listed once for removal. Otherwise tumbling may break.
3769
3898
  */
3770
3899
  dedupeWinSymbolsForTumble(winCombinations) {
3771
3900
  return this.board.dedupeWinSymbolsForTumble(winCombinations);
3772
3901
  }
3902
+ /**
3903
+ * Sets symbolsPerReel.
3904
+ */
3905
+ setSymbolsPerReel(symbolsPerReel) {
3906
+ this.symbolsPerReel = symbolsPerReel;
3907
+ }
3908
+ /**
3909
+ * Sets the reelsAmount.
3910
+ */
3911
+ setReelsAmount(reelsAmount) {
3912
+ this.reelsAmount = reelsAmount;
3913
+ }
3773
3914
  };
3774
3915
  export {
3775
3916
  ClusterWinType,