@slot-engine/core 0.0.8 → 0.0.10

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.js CHANGED
@@ -65,7 +65,7 @@ function createGameConfig(opts) {
65
65
  symbols.set(key, value);
66
66
  }
67
67
  const getAnticipationTrigger = (spinType) => {
68
- return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
68
+ return Math.min(...Object.keys(opts.scatterToFreespins[spinType] || {}).map(Number)) - 1;
69
69
  };
70
70
  return {
71
71
  padSymbols: opts.padSymbols || 1,
@@ -401,6 +401,16 @@ function createGameState(opts) {
401
401
  };
402
402
  }
403
403
 
404
+ // src/recorder/index.ts
405
+ var Recorder = class {
406
+ records;
407
+ pendingRecords;
408
+ constructor() {
409
+ this.records = [];
410
+ this.pendingRecords = [];
411
+ }
412
+ };
413
+
404
414
  // src/board/index.ts
405
415
  var import_assert3 = __toESM(require("assert"));
406
416
 
@@ -436,6 +446,16 @@ var GameSymbol = class _GameSymbol {
436
446
  return true;
437
447
  }
438
448
  }
449
+ /**
450
+ * Creates a clone of this GameSymbol.
451
+ */
452
+ clone() {
453
+ return new _GameSymbol({
454
+ id: this.id,
455
+ pays: this.pays ? { ...this.pays } : void 0,
456
+ properties: Object.fromEntries(this.properties)
457
+ });
458
+ }
439
459
  };
440
460
 
441
461
  // src/board/index.ts
@@ -470,6 +490,13 @@ var Board = class {
470
490
  this.lastDrawnReelStops = [];
471
491
  this.lastUsedReels = [];
472
492
  }
493
+ getSymbol(reelIndex, rowIndex) {
494
+ return this.reels[reelIndex]?.[rowIndex];
495
+ }
496
+ setSymbol(reelIndex, rowIndex, symbol) {
497
+ this.reels[reelIndex] = this.reels[reelIndex] || [];
498
+ this.reels[reelIndex][rowIndex] = symbol;
499
+ }
473
500
  makeEmptyReels(opts) {
474
501
  const length = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
475
502
  (0, import_assert3.default)(length, "Cannot make empty reels without context or reelsAmount.");
@@ -732,6 +759,18 @@ var BoardService = class extends AbstractService {
732
759
  getAnticipation() {
733
760
  return this.board.anticipation;
734
761
  }
762
+ /**
763
+ * Gets the symbol at the specified reel and row index.
764
+ */
765
+ getSymbol(reelIndex, rowIndex) {
766
+ return this.board.getSymbol(reelIndex, rowIndex);
767
+ }
768
+ /**
769
+ * Sets the symbol at the specified reel and row index.
770
+ */
771
+ setSymbol(reelIndex, rowIndex, symbol) {
772
+ this.board.setSymbol(reelIndex, rowIndex, symbol);
773
+ }
735
774
  resetReels() {
736
775
  this.board.resetReels({
737
776
  ctx: this.ctx()
@@ -1157,16 +1196,6 @@ var Book = class _Book {
1157
1196
  }
1158
1197
  };
1159
1198
 
1160
- // src/recorder/index.ts
1161
- var Recorder = class {
1162
- records;
1163
- pendingRecords;
1164
- constructor() {
1165
- this.records = [];
1166
- this.pendingRecords = [];
1167
- }
1168
- };
1169
-
1170
1199
  // src/wallet/index.ts
1171
1200
  var Wallet = class {
1172
1201
  /**
@@ -2080,14 +2109,39 @@ var Analysis = class {
2080
2109
  [15e3, 19999.99],
2081
2110
  [2e4, 24999.99]
2082
2111
  ];
2112
+ const payoutRanges = {};
2083
2113
  for (const modeStr of gameModes) {
2084
- const mode = this.getGameModeConfig(modeStr);
2114
+ payoutRanges[modeStr] = {};
2085
2115
  const lutOptimized = parseLookupTable(
2086
2116
  import_fs3.default.readFileSync(this.filePaths[modeStr].lutOptimized, "utf-8")
2087
2117
  );
2088
- const totalWeight = getTotalLutWeight(lutOptimized);
2089
- const payoutWeights = getPayoutWeights(lutOptimized);
2118
+ lutOptimized.forEach(([, , p]) => {
2119
+ const payout = p / 100;
2120
+ for (const [min, max] of winRanges) {
2121
+ if (payout >= min && payout <= max) {
2122
+ const rangeKey = `${min}-${max}`;
2123
+ if (!payoutRanges[modeStr][rangeKey]) {
2124
+ payoutRanges[modeStr][rangeKey] = 0;
2125
+ }
2126
+ payoutRanges[modeStr][rangeKey] += 1;
2127
+ break;
2128
+ }
2129
+ }
2130
+ });
2131
+ const orderedRanges = {};
2132
+ Object.keys(payoutRanges[modeStr]).sort((a, b) => {
2133
+ const [aMin] = a.split("-").map(Number);
2134
+ const [bMin] = b.split("-").map(Number);
2135
+ return aMin - bMin;
2136
+ }).forEach((key) => {
2137
+ orderedRanges[key] = payoutRanges[modeStr][key];
2138
+ });
2139
+ payoutRanges[modeStr] = orderedRanges;
2090
2140
  }
2141
+ writeJsonFile(
2142
+ import_path2.default.join(process.cwd(), this.gameConfig.outputDir, "stats_payouts.json"),
2143
+ payoutRanges
2144
+ );
2091
2145
  }
2092
2146
  getGameModeConfig(mode) {
2093
2147
  const config = this.gameConfig.gameModes[mode];
@@ -2554,9 +2608,20 @@ var WinType = class {
2554
2608
  isWild(symbol) {
2555
2609
  return !!this.wildSymbol && symbol.compare(this.wildSymbol);
2556
2610
  }
2611
+ getSymbolPayout(symbol, count) {
2612
+ if (!symbol.pays) return 0;
2613
+ let clusterSize = 0;
2614
+ const sizes = Object.keys(symbol.pays).map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
2615
+ for (const size of sizes) {
2616
+ if (size > count) break;
2617
+ clusterSize = size;
2618
+ }
2619
+ return symbol.pays[clusterSize] || 0;
2620
+ }
2557
2621
  };
2558
2622
 
2559
2623
  // src/win-types/LinesWinType.ts
2624
+ var import_assert11 = __toESM(require("assert"));
2560
2625
  var LinesWinType = class extends WinType {
2561
2626
  lines;
2562
2627
  constructor(opts) {
@@ -2597,106 +2662,335 @@ var LinesWinType = class extends WinType {
2597
2662
  evaluateWins(board) {
2598
2663
  this.validateConfig();
2599
2664
  const lineWins = [];
2600
- let payout = 0;
2601
2665
  const reels = board;
2602
- for (const [lineNumStr, lineDef] of Object.entries(this.lines)) {
2666
+ for (const [lineNumStr, line] of Object.entries(this.lines)) {
2603
2667
  const lineNum = Number(lineNumStr);
2604
- let baseSymbol = null;
2605
- let leadingWilds = 0;
2606
- const chain = [];
2607
- const details = [];
2608
- for (let ridx = 0; ridx < reels.length; ridx++) {
2609
- const rowIdx = lineDef[ridx];
2610
- const sym = reels[ridx][rowIdx];
2611
- if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.");
2612
- const wild = this.isWild(sym);
2613
- if (ridx === 0) {
2614
- chain.push(sym);
2615
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild });
2616
- if (wild) leadingWilds++;
2617
- else baseSymbol = sym;
2618
- continue;
2619
- }
2620
- if (wild) {
2621
- chain.push(sym);
2622
- details.push({
2623
- reelIndex: ridx,
2624
- posIndex: rowIdx,
2625
- symbol: sym,
2626
- isWild: true,
2627
- substitutedFor: baseSymbol || void 0
2628
- });
2629
- continue;
2630
- }
2668
+ let baseSymbol;
2669
+ const potentialWinLine = [];
2670
+ const potentialWildLine = [];
2671
+ for (const [ridx, reel] of reels.entries()) {
2672
+ const sidx = line[ridx];
2673
+ const thisSymbol = reel[sidx];
2631
2674
  if (!baseSymbol) {
2632
- baseSymbol = sym;
2633
- chain.push(sym);
2634
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false });
2675
+ baseSymbol = thisSymbol;
2676
+ }
2677
+ (0, import_assert11.default)(baseSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
2678
+ (0, import_assert11.default)(thisSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
2679
+ if (potentialWinLine.length == 0) {
2680
+ if (this.isWild(thisSymbol)) {
2681
+ potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2682
+ }
2683
+ potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2635
2684
  continue;
2636
2685
  }
2637
- if (sym.id === baseSymbol.id) {
2638
- chain.push(sym);
2639
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false });
2686
+ if (this.isWild(baseSymbol)) {
2687
+ if (this.isWild(thisSymbol)) {
2688
+ potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2689
+ } else {
2690
+ baseSymbol = thisSymbol;
2691
+ }
2692
+ potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2640
2693
  continue;
2641
2694
  }
2642
- break;
2643
- }
2644
- if (chain.length === 0) continue;
2645
- const allWild = chain.every((s) => this.isWild(s));
2646
- const wildRepresentative = this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null;
2647
- const len = chain.length;
2648
- let bestPayout = 0;
2649
- let bestType = null;
2650
- let payingSymbol = null;
2651
- if (baseSymbol?.pays && baseSymbol.pays[len]) {
2652
- bestPayout = baseSymbol.pays[len];
2653
- bestType = "substituted";
2654
- payingSymbol = baseSymbol;
2655
- }
2656
- if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
2657
- const wildPay = wildRepresentative.pays[len];
2658
- if (wildPay > bestPayout) {
2659
- bestPayout = wildPay;
2660
- bestType = "pure-wild";
2661
- payingSymbol = wildRepresentative;
2695
+ if (baseSymbol.compare(thisSymbol) || this.isWild(thisSymbol)) {
2696
+ potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2662
2697
  }
2663
2698
  }
2664
- if (!bestPayout || !bestType || !payingSymbol) continue;
2665
- const minLen = payingSymbol.pays ? Math.min(...Object.keys(payingSymbol.pays).map(Number)) : Infinity;
2666
- if (len < minLen) continue;
2667
- const wildCount = details.filter((d) => d.isWild).length;
2668
- const nonWildCount = len - wildCount;
2669
- lineWins.push({
2699
+ const minSymLine = Math.min(
2700
+ ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
2701
+ );
2702
+ if (potentialWinLine.length < minSymLine) continue;
2703
+ const linePayout = this.getLinePayout(potentialWinLine);
2704
+ const wildLinePayout = this.getLinePayout(potentialWildLine);
2705
+ let finalLine = {
2706
+ kind: potentialWinLine.length,
2707
+ baseSymbol,
2708
+ symbols: potentialWinLine.map((s) => ({
2709
+ symbol: s.symbol,
2710
+ isWild: this.isWild(s.symbol),
2711
+ reelIndex: s.reel,
2712
+ posIndex: s.row
2713
+ })),
2670
2714
  lineNumber: lineNum,
2671
- kind: len,
2672
- payout: bestPayout,
2673
- symbol: payingSymbol,
2674
- winType: bestType,
2675
- substitutedBaseSymbol: bestType === "pure-wild" ? null : baseSymbol,
2676
- symbols: details,
2677
- stats: { wildCount, nonWildCount, leadingWilds }
2678
- });
2679
- payout += bestPayout;
2715
+ payout: linePayout
2716
+ };
2717
+ if (wildLinePayout > linePayout) {
2718
+ baseSymbol = potentialWildLine[0]?.symbol;
2719
+ finalLine = {
2720
+ kind: potentialWildLine.length,
2721
+ baseSymbol,
2722
+ symbols: potentialWildLine.map((s) => ({
2723
+ symbol: s.symbol,
2724
+ isWild: this.isWild(s.symbol),
2725
+ reelIndex: s.reel,
2726
+ posIndex: s.row
2727
+ })),
2728
+ lineNumber: lineNum,
2729
+ payout: wildLinePayout
2730
+ };
2731
+ }
2732
+ lineWins.push(finalLine);
2680
2733
  }
2681
2734
  for (const win of lineWins) {
2682
2735
  this.ctx.services.data.recordSymbolOccurrence({
2683
2736
  kind: win.kind,
2684
- symbolId: win.symbol.id,
2737
+ symbolId: win.baseSymbol.id,
2685
2738
  spinType: this.ctx.state.currentSpinType
2686
2739
  });
2687
2740
  }
2688
- this.payout = payout;
2741
+ this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
2689
2742
  this.winCombinations = lineWins;
2690
2743
  return this;
2691
2744
  }
2745
+ getLinePayout(line) {
2746
+ if (line.length === 0) return 0;
2747
+ let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
2748
+ if (!baseSymbol) baseSymbol = line[0].symbol;
2749
+ const kind = line.length;
2750
+ const payout = this.getSymbolPayout(baseSymbol, kind);
2751
+ return payout;
2752
+ }
2692
2753
  };
2693
2754
 
2694
2755
  // src/win-types/ClusterWinType.ts
2695
2756
  var ClusterWinType = class extends WinType {
2757
+ _checked = [];
2758
+ _checkedWilds = [];
2759
+ _currentBoard = [];
2760
+ constructor(opts) {
2761
+ super(opts);
2762
+ }
2763
+ validateConfig() {
2764
+ }
2765
+ /**
2766
+ * Calculates wins based on symbol cluster size and provided board state.\
2767
+ * Retrieve the results using `getWins()` after.
2768
+ */
2769
+ evaluateWins(board) {
2770
+ this.validateConfig();
2771
+ this._checked = [];
2772
+ this._currentBoard = board;
2773
+ const clusterWins = [];
2774
+ const potentialClusters = [];
2775
+ for (const [ridx, reel] of board.entries()) {
2776
+ for (const [sidx, symbol] of reel.entries()) {
2777
+ this._checkedWilds = [];
2778
+ if (this.isWild(symbol)) continue;
2779
+ if (this.isChecked(ridx, sidx)) {
2780
+ continue;
2781
+ }
2782
+ const thisSymbol = { reel: ridx, row: sidx, symbol };
2783
+ this._checked.push(thisSymbol);
2784
+ const neighbors = this.getNeighbors(ridx, sidx);
2785
+ const matchingSymbols = this.evaluateCluster(symbol, neighbors);
2786
+ if (matchingSymbols.size >= 1) {
2787
+ potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
2788
+ }
2789
+ }
2790
+ }
2791
+ for (const [ridx, reel] of board.entries()) {
2792
+ for (const [sidx, symbol] of reel.entries()) {
2793
+ this._checkedWilds = [];
2794
+ if (!this.isWild(symbol)) continue;
2795
+ if (this.isChecked(ridx, sidx)) {
2796
+ continue;
2797
+ }
2798
+ const thisSymbol = { reel: ridx, row: sidx, symbol };
2799
+ this._checked.push(thisSymbol);
2800
+ const neighbors = this.getNeighbors(ridx, sidx);
2801
+ const matchingSymbols = this.evaluateCluster(symbol, neighbors);
2802
+ if (matchingSymbols.size >= 1) {
2803
+ potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
2804
+ }
2805
+ }
2806
+ }
2807
+ potentialClusters.forEach((cluster) => {
2808
+ const kind = cluster.length;
2809
+ let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
2810
+ if (!baseSymbol) baseSymbol = cluster[0].symbol;
2811
+ const payout = this.getSymbolPayout(baseSymbol, kind);
2812
+ if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
2813
+ return;
2814
+ }
2815
+ clusterWins.push({
2816
+ payout,
2817
+ kind,
2818
+ baseSymbol,
2819
+ symbols: cluster.map((s) => ({
2820
+ symbol: s.symbol,
2821
+ isWild: this.isWild(s.symbol),
2822
+ reelIndex: s.reel,
2823
+ posIndex: s.row
2824
+ }))
2825
+ });
2826
+ });
2827
+ for (const win of clusterWins) {
2828
+ this.ctx.services.data.recordSymbolOccurrence({
2829
+ kind: win.kind,
2830
+ symbolId: win.baseSymbol.id,
2831
+ spinType: this.ctx.state.currentSpinType
2832
+ });
2833
+ }
2834
+ this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
2835
+ this.winCombinations = clusterWins;
2836
+ return this;
2837
+ }
2838
+ getNeighbors(ridx, sidx) {
2839
+ const board = this._currentBoard;
2840
+ const neighbors = [];
2841
+ const potentialNeighbors = [
2842
+ [ridx - 1, sidx],
2843
+ [ridx + 1, sidx],
2844
+ [ridx, sidx - 1],
2845
+ [ridx, sidx + 1]
2846
+ ];
2847
+ potentialNeighbors.forEach(([nridx, nsidx]) => {
2848
+ if (board[nridx] && board[nridx][nsidx]) {
2849
+ neighbors.push({ reel: nridx, row: nsidx, symbol: board[nridx][nsidx] });
2850
+ }
2851
+ });
2852
+ return neighbors;
2853
+ }
2854
+ evaluateCluster(rootSymbol, neighbors) {
2855
+ const matchingSymbols = /* @__PURE__ */ new Map();
2856
+ neighbors.forEach((neighbor) => {
2857
+ const { reel, row, symbol } = neighbor;
2858
+ if (this.isChecked(reel, row)) return;
2859
+ if (this.isCheckedWild(reel, row)) return;
2860
+ if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
2861
+ const key = `${reel}-${row}`;
2862
+ matchingSymbols.set(key, { reel, row, symbol });
2863
+ if (symbol.compare(rootSymbol)) {
2864
+ this._checked.push(neighbor);
2865
+ }
2866
+ if (this.isWild(symbol)) {
2867
+ this._checkedWilds.push(neighbor);
2868
+ }
2869
+ const neighbors2 = this.getNeighbors(reel, row);
2870
+ const nestedMatches = this.evaluateCluster(rootSymbol, neighbors2);
2871
+ nestedMatches.forEach((nsym) => {
2872
+ const nkey = `${nsym.reel}-${nsym.row}`;
2873
+ matchingSymbols.set(nkey, nsym);
2874
+ });
2875
+ }
2876
+ });
2877
+ return matchingSymbols;
2878
+ }
2879
+ isChecked(ridx, sidx) {
2880
+ return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
2881
+ }
2882
+ isCheckedWild(ridx, sidx) {
2883
+ return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
2884
+ }
2696
2885
  };
2697
2886
 
2698
2887
  // src/win-types/ManywaysWinType.ts
2699
2888
  var ManywaysWinType = class extends WinType {
2889
+ _checked = [];
2890
+ _checkedWilds = [];
2891
+ constructor(opts) {
2892
+ super(opts);
2893
+ }
2894
+ validateConfig() {
2895
+ }
2896
+ /**
2897
+ * Calculates wins based on the defined paylines and provided board state.\
2898
+ * Retrieve the results using `getWins()` after.
2899
+ */
2900
+ evaluateWins(board) {
2901
+ this.validateConfig();
2902
+ const waysWins = [];
2903
+ const reels = board;
2904
+ const possibleWaysWins = /* @__PURE__ */ new Map();
2905
+ const candidateSymbols = /* @__PURE__ */ new Map();
2906
+ let searchReelIdx = 0;
2907
+ let searchActive = true;
2908
+ while (searchActive && searchReelIdx < reels.length) {
2909
+ const reel = reels[searchReelIdx];
2910
+ let hasWild = false;
2911
+ for (const symbol of reel) {
2912
+ candidateSymbols.set(symbol.id, symbol);
2913
+ if (this.isWild(symbol)) {
2914
+ hasWild = true;
2915
+ }
2916
+ }
2917
+ if (!hasWild) {
2918
+ searchActive = false;
2919
+ }
2920
+ searchReelIdx++;
2921
+ }
2922
+ for (const baseSymbol of candidateSymbols.values()) {
2923
+ let symbolList = {};
2924
+ let isInterrupted = false;
2925
+ for (const [ridx, reel] of reels.entries()) {
2926
+ if (isInterrupted) break;
2927
+ for (const [sidx, symbol] of reel.entries()) {
2928
+ const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
2929
+ if (isMatch) {
2930
+ if (!symbolList[ridx]) {
2931
+ symbolList[ridx] = [];
2932
+ }
2933
+ symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
2934
+ }
2935
+ }
2936
+ if (!symbolList[ridx]) {
2937
+ isInterrupted = true;
2938
+ break;
2939
+ }
2940
+ }
2941
+ const minSymLine = Math.min(
2942
+ ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
2943
+ );
2944
+ const wayLength = this.getWayLength(symbolList);
2945
+ if (wayLength >= minSymLine) {
2946
+ possibleWaysWins.set(baseSymbol.id, symbolList);
2947
+ }
2948
+ }
2949
+ for (const [baseSymbolId, symbolList] of possibleWaysWins.entries()) {
2950
+ const wayLength = this.getWayLength(symbolList);
2951
+ let baseSymbol = Object.values(symbolList).flatMap((l) => l.map((s) => s)).find((s) => !this.isWild(s.symbol))?.symbol;
2952
+ if (!baseSymbol) baseSymbol = symbolList[0][0].symbol;
2953
+ const singleWayPayout = this.getSymbolPayout(baseSymbol, wayLength);
2954
+ const totalWays = Object.values(symbolList).reduce(
2955
+ (ways, syms) => ways * syms.length,
2956
+ 1
2957
+ );
2958
+ const totalPayout = singleWayPayout * totalWays;
2959
+ waysWins.push({
2960
+ kind: wayLength,
2961
+ baseSymbol,
2962
+ symbols: Object.values(symbolList).flatMap(
2963
+ (reel) => reel.map((s) => ({
2964
+ symbol: s.symbol,
2965
+ isWild: this.isWild(s.symbol),
2966
+ reelIndex: s.reel,
2967
+ posIndex: s.row
2968
+ }))
2969
+ ),
2970
+ ways: totalWays,
2971
+ payout: totalPayout
2972
+ });
2973
+ }
2974
+ for (const win of waysWins) {
2975
+ this.ctx.services.data.recordSymbolOccurrence({
2976
+ kind: win.kind,
2977
+ symbolId: win.baseSymbol.id,
2978
+ spinType: this.ctx.state.currentSpinType
2979
+ });
2980
+ }
2981
+ this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
2982
+ this.winCombinations = waysWins;
2983
+ return this;
2984
+ }
2985
+ getWayLength(symbolList) {
2986
+ return Math.max(...Object.keys(symbolList).map((k) => parseInt(k, 10))) + 1;
2987
+ }
2988
+ isChecked(ridx, sidx) {
2989
+ return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
2990
+ }
2991
+ isCheckedWild(ridx, sidx) {
2992
+ return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
2993
+ }
2700
2994
  };
2701
2995
 
2702
2996
  // src/reel-set/GeneratedReelSet.ts
@@ -3057,7 +3351,7 @@ var GeneratedReelSet = class extends ReelSet {
3057
3351
  };
3058
3352
 
3059
3353
  // src/reel-set/StaticReelSet.ts
3060
- var import_assert11 = __toESM(require("assert"));
3354
+ var import_assert12 = __toESM(require("assert"));
3061
3355
  var StaticReelSet = class extends ReelSet {
3062
3356
  reels;
3063
3357
  csvPath;
@@ -3067,7 +3361,7 @@ var StaticReelSet = class extends ReelSet {
3067
3361
  this.reels = [];
3068
3362
  this._strReels = opts.reels || [];
3069
3363
  this.csvPath = opts.csvPath || "";
3070
- (0, import_assert11.default)(
3364
+ (0, import_assert12.default)(
3071
3365
  opts.reels || opts.csvPath,
3072
3366
  `Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`
3073
3367
  );
@@ -3143,6 +3437,18 @@ var StandaloneBoard = class {
3143
3437
  getPaddingBottom() {
3144
3438
  return this.board.paddingBottom;
3145
3439
  }
3440
+ /**
3441
+ * Gets the symbol at the specified reel and row index.
3442
+ */
3443
+ getSymbol(reelIndex, rowIndex) {
3444
+ return this.board.getSymbol(reelIndex, rowIndex);
3445
+ }
3446
+ /**
3447
+ * Sets the symbol at the specified reel and row index.
3448
+ */
3449
+ setSymbol(reelIndex, rowIndex, symbol) {
3450
+ this.board.setSymbol(reelIndex, rowIndex, symbol);
3451
+ }
3146
3452
  resetReels() {
3147
3453
  this.board.resetReels({
3148
3454
  ctx: this.ctx