@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.mjs CHANGED
@@ -13,7 +13,7 @@ function createGameConfig(opts) {
13
13
  symbols.set(key, value);
14
14
  }
15
15
  const getAnticipationTrigger = (spinType) => {
16
- return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
16
+ return Math.min(...Object.keys(opts.scatterToFreespins[spinType] || {}).map(Number)) - 1;
17
17
  };
18
18
  return {
19
19
  padSymbols: opts.padSymbols || 1,
@@ -349,6 +349,16 @@ function createGameState(opts) {
349
349
  };
350
350
  }
351
351
 
352
+ // src/recorder/index.ts
353
+ var Recorder = class {
354
+ records;
355
+ pendingRecords;
356
+ constructor() {
357
+ this.records = [];
358
+ this.pendingRecords = [];
359
+ }
360
+ };
361
+
352
362
  // src/board/index.ts
353
363
  import assert3 from "assert";
354
364
 
@@ -384,6 +394,16 @@ var GameSymbol = class _GameSymbol {
384
394
  return true;
385
395
  }
386
396
  }
397
+ /**
398
+ * Creates a clone of this GameSymbol.
399
+ */
400
+ clone() {
401
+ return new _GameSymbol({
402
+ id: this.id,
403
+ pays: this.pays ? { ...this.pays } : void 0,
404
+ properties: Object.fromEntries(this.properties)
405
+ });
406
+ }
387
407
  };
388
408
 
389
409
  // src/board/index.ts
@@ -418,6 +438,13 @@ var Board = class {
418
438
  this.lastDrawnReelStops = [];
419
439
  this.lastUsedReels = [];
420
440
  }
441
+ getSymbol(reelIndex, rowIndex) {
442
+ return this.reels[reelIndex]?.[rowIndex];
443
+ }
444
+ setSymbol(reelIndex, rowIndex, symbol) {
445
+ this.reels[reelIndex] = this.reels[reelIndex] || [];
446
+ this.reels[reelIndex][rowIndex] = symbol;
447
+ }
421
448
  makeEmptyReels(opts) {
422
449
  const length = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
423
450
  assert3(length, "Cannot make empty reels without context or reelsAmount.");
@@ -680,6 +707,18 @@ var BoardService = class extends AbstractService {
680
707
  getAnticipation() {
681
708
  return this.board.anticipation;
682
709
  }
710
+ /**
711
+ * Gets the symbol at the specified reel and row index.
712
+ */
713
+ getSymbol(reelIndex, rowIndex) {
714
+ return this.board.getSymbol(reelIndex, rowIndex);
715
+ }
716
+ /**
717
+ * Sets the symbol at the specified reel and row index.
718
+ */
719
+ setSymbol(reelIndex, rowIndex, symbol) {
720
+ this.board.setSymbol(reelIndex, rowIndex, symbol);
721
+ }
683
722
  resetReels() {
684
723
  this.board.resetReels({
685
724
  ctx: this.ctx()
@@ -1105,16 +1144,6 @@ var Book = class _Book {
1105
1144
  }
1106
1145
  };
1107
1146
 
1108
- // src/recorder/index.ts
1109
- var Recorder = class {
1110
- records;
1111
- pendingRecords;
1112
- constructor() {
1113
- this.records = [];
1114
- this.pendingRecords = [];
1115
- }
1116
- };
1117
-
1118
1147
  // src/wallet/index.ts
1119
1148
  var Wallet = class {
1120
1149
  /**
@@ -2028,14 +2057,39 @@ var Analysis = class {
2028
2057
  [15e3, 19999.99],
2029
2058
  [2e4, 24999.99]
2030
2059
  ];
2060
+ const payoutRanges = {};
2031
2061
  for (const modeStr of gameModes) {
2032
- const mode = this.getGameModeConfig(modeStr);
2062
+ payoutRanges[modeStr] = {};
2033
2063
  const lutOptimized = parseLookupTable(
2034
2064
  fs3.readFileSync(this.filePaths[modeStr].lutOptimized, "utf-8")
2035
2065
  );
2036
- const totalWeight = getTotalLutWeight(lutOptimized);
2037
- const payoutWeights = getPayoutWeights(lutOptimized);
2066
+ lutOptimized.forEach(([, , p]) => {
2067
+ const payout = p / 100;
2068
+ for (const [min, max] of winRanges) {
2069
+ if (payout >= min && payout <= max) {
2070
+ const rangeKey = `${min}-${max}`;
2071
+ if (!payoutRanges[modeStr][rangeKey]) {
2072
+ payoutRanges[modeStr][rangeKey] = 0;
2073
+ }
2074
+ payoutRanges[modeStr][rangeKey] += 1;
2075
+ break;
2076
+ }
2077
+ }
2078
+ });
2079
+ const orderedRanges = {};
2080
+ Object.keys(payoutRanges[modeStr]).sort((a, b) => {
2081
+ const [aMin] = a.split("-").map(Number);
2082
+ const [bMin] = b.split("-").map(Number);
2083
+ return aMin - bMin;
2084
+ }).forEach((key) => {
2085
+ orderedRanges[key] = payoutRanges[modeStr][key];
2086
+ });
2087
+ payoutRanges[modeStr] = orderedRanges;
2038
2088
  }
2089
+ writeJsonFile(
2090
+ path2.join(process.cwd(), this.gameConfig.outputDir, "stats_payouts.json"),
2091
+ payoutRanges
2092
+ );
2039
2093
  }
2040
2094
  getGameModeConfig(mode) {
2041
2095
  const config = this.gameConfig.gameModes[mode];
@@ -2502,9 +2556,20 @@ var WinType = class {
2502
2556
  isWild(symbol) {
2503
2557
  return !!this.wildSymbol && symbol.compare(this.wildSymbol);
2504
2558
  }
2559
+ getSymbolPayout(symbol, count) {
2560
+ if (!symbol.pays) return 0;
2561
+ let clusterSize = 0;
2562
+ const sizes = Object.keys(symbol.pays).map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
2563
+ for (const size of sizes) {
2564
+ if (size > count) break;
2565
+ clusterSize = size;
2566
+ }
2567
+ return symbol.pays[clusterSize] || 0;
2568
+ }
2505
2569
  };
2506
2570
 
2507
2571
  // src/win-types/LinesWinType.ts
2572
+ import assert11 from "assert";
2508
2573
  var LinesWinType = class extends WinType {
2509
2574
  lines;
2510
2575
  constructor(opts) {
@@ -2545,106 +2610,335 @@ var LinesWinType = class extends WinType {
2545
2610
  evaluateWins(board) {
2546
2611
  this.validateConfig();
2547
2612
  const lineWins = [];
2548
- let payout = 0;
2549
2613
  const reels = board;
2550
- for (const [lineNumStr, lineDef] of Object.entries(this.lines)) {
2614
+ for (const [lineNumStr, line] of Object.entries(this.lines)) {
2551
2615
  const lineNum = Number(lineNumStr);
2552
- let baseSymbol = null;
2553
- let leadingWilds = 0;
2554
- const chain = [];
2555
- const details = [];
2556
- for (let ridx = 0; ridx < reels.length; ridx++) {
2557
- const rowIdx = lineDef[ridx];
2558
- const sym = reels[ridx][rowIdx];
2559
- if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.");
2560
- const wild = this.isWild(sym);
2561
- if (ridx === 0) {
2562
- chain.push(sym);
2563
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild });
2564
- if (wild) leadingWilds++;
2565
- else baseSymbol = sym;
2566
- continue;
2567
- }
2568
- if (wild) {
2569
- chain.push(sym);
2570
- details.push({
2571
- reelIndex: ridx,
2572
- posIndex: rowIdx,
2573
- symbol: sym,
2574
- isWild: true,
2575
- substitutedFor: baseSymbol || void 0
2576
- });
2577
- continue;
2578
- }
2616
+ let baseSymbol;
2617
+ const potentialWinLine = [];
2618
+ const potentialWildLine = [];
2619
+ for (const [ridx, reel] of reels.entries()) {
2620
+ const sidx = line[ridx];
2621
+ const thisSymbol = reel[sidx];
2579
2622
  if (!baseSymbol) {
2580
- baseSymbol = sym;
2581
- chain.push(sym);
2582
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false });
2623
+ baseSymbol = thisSymbol;
2624
+ }
2625
+ assert11(baseSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
2626
+ assert11(thisSymbol, `No symbol found at line ${lineNum}, reel ${ridx}`);
2627
+ if (potentialWinLine.length == 0) {
2628
+ if (this.isWild(thisSymbol)) {
2629
+ potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2630
+ }
2631
+ potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2583
2632
  continue;
2584
2633
  }
2585
- if (sym.id === baseSymbol.id) {
2586
- chain.push(sym);
2587
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: false });
2634
+ if (this.isWild(baseSymbol)) {
2635
+ if (this.isWild(thisSymbol)) {
2636
+ potentialWildLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2637
+ } else {
2638
+ baseSymbol = thisSymbol;
2639
+ }
2640
+ potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2588
2641
  continue;
2589
2642
  }
2590
- break;
2591
- }
2592
- if (chain.length === 0) continue;
2593
- const allWild = chain.every((s) => this.isWild(s));
2594
- const wildRepresentative = this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null;
2595
- const len = chain.length;
2596
- let bestPayout = 0;
2597
- let bestType = null;
2598
- let payingSymbol = null;
2599
- if (baseSymbol?.pays && baseSymbol.pays[len]) {
2600
- bestPayout = baseSymbol.pays[len];
2601
- bestType = "substituted";
2602
- payingSymbol = baseSymbol;
2603
- }
2604
- if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
2605
- const wildPay = wildRepresentative.pays[len];
2606
- if (wildPay > bestPayout) {
2607
- bestPayout = wildPay;
2608
- bestType = "pure-wild";
2609
- payingSymbol = wildRepresentative;
2643
+ if (baseSymbol.compare(thisSymbol) || this.isWild(thisSymbol)) {
2644
+ potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2610
2645
  }
2611
2646
  }
2612
- if (!bestPayout || !bestType || !payingSymbol) continue;
2613
- const minLen = payingSymbol.pays ? Math.min(...Object.keys(payingSymbol.pays).map(Number)) : Infinity;
2614
- if (len < minLen) continue;
2615
- const wildCount = details.filter((d) => d.isWild).length;
2616
- const nonWildCount = len - wildCount;
2617
- lineWins.push({
2647
+ const minSymLine = Math.min(
2648
+ ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
2649
+ );
2650
+ if (potentialWinLine.length < minSymLine) continue;
2651
+ const linePayout = this.getLinePayout(potentialWinLine);
2652
+ const wildLinePayout = this.getLinePayout(potentialWildLine);
2653
+ let finalLine = {
2654
+ kind: potentialWinLine.length,
2655
+ baseSymbol,
2656
+ symbols: potentialWinLine.map((s) => ({
2657
+ symbol: s.symbol,
2658
+ isWild: this.isWild(s.symbol),
2659
+ reelIndex: s.reel,
2660
+ posIndex: s.row
2661
+ })),
2618
2662
  lineNumber: lineNum,
2619
- kind: len,
2620
- payout: bestPayout,
2621
- symbol: payingSymbol,
2622
- winType: bestType,
2623
- substitutedBaseSymbol: bestType === "pure-wild" ? null : baseSymbol,
2624
- symbols: details,
2625
- stats: { wildCount, nonWildCount, leadingWilds }
2626
- });
2627
- payout += bestPayout;
2663
+ payout: linePayout
2664
+ };
2665
+ if (wildLinePayout > linePayout) {
2666
+ baseSymbol = potentialWildLine[0]?.symbol;
2667
+ finalLine = {
2668
+ kind: potentialWildLine.length,
2669
+ baseSymbol,
2670
+ symbols: potentialWildLine.map((s) => ({
2671
+ symbol: s.symbol,
2672
+ isWild: this.isWild(s.symbol),
2673
+ reelIndex: s.reel,
2674
+ posIndex: s.row
2675
+ })),
2676
+ lineNumber: lineNum,
2677
+ payout: wildLinePayout
2678
+ };
2679
+ }
2680
+ lineWins.push(finalLine);
2628
2681
  }
2629
2682
  for (const win of lineWins) {
2630
2683
  this.ctx.services.data.recordSymbolOccurrence({
2631
2684
  kind: win.kind,
2632
- symbolId: win.symbol.id,
2685
+ symbolId: win.baseSymbol.id,
2633
2686
  spinType: this.ctx.state.currentSpinType
2634
2687
  });
2635
2688
  }
2636
- this.payout = payout;
2689
+ this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
2637
2690
  this.winCombinations = lineWins;
2638
2691
  return this;
2639
2692
  }
2693
+ getLinePayout(line) {
2694
+ if (line.length === 0) return 0;
2695
+ let baseSymbol = line.find((s) => !this.isWild(s.symbol))?.symbol;
2696
+ if (!baseSymbol) baseSymbol = line[0].symbol;
2697
+ const kind = line.length;
2698
+ const payout = this.getSymbolPayout(baseSymbol, kind);
2699
+ return payout;
2700
+ }
2640
2701
  };
2641
2702
 
2642
2703
  // src/win-types/ClusterWinType.ts
2643
2704
  var ClusterWinType = class extends WinType {
2705
+ _checked = [];
2706
+ _checkedWilds = [];
2707
+ _currentBoard = [];
2708
+ constructor(opts) {
2709
+ super(opts);
2710
+ }
2711
+ validateConfig() {
2712
+ }
2713
+ /**
2714
+ * Calculates wins based on symbol cluster size and provided board state.\
2715
+ * Retrieve the results using `getWins()` after.
2716
+ */
2717
+ evaluateWins(board) {
2718
+ this.validateConfig();
2719
+ this._checked = [];
2720
+ this._currentBoard = board;
2721
+ const clusterWins = [];
2722
+ const potentialClusters = [];
2723
+ for (const [ridx, reel] of board.entries()) {
2724
+ for (const [sidx, symbol] of reel.entries()) {
2725
+ this._checkedWilds = [];
2726
+ if (this.isWild(symbol)) continue;
2727
+ if (this.isChecked(ridx, sidx)) {
2728
+ continue;
2729
+ }
2730
+ const thisSymbol = { reel: ridx, row: sidx, symbol };
2731
+ this._checked.push(thisSymbol);
2732
+ const neighbors = this.getNeighbors(ridx, sidx);
2733
+ const matchingSymbols = this.evaluateCluster(symbol, neighbors);
2734
+ if (matchingSymbols.size >= 1) {
2735
+ potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
2736
+ }
2737
+ }
2738
+ }
2739
+ for (const [ridx, reel] of board.entries()) {
2740
+ for (const [sidx, symbol] of reel.entries()) {
2741
+ this._checkedWilds = [];
2742
+ if (!this.isWild(symbol)) continue;
2743
+ if (this.isChecked(ridx, sidx)) {
2744
+ continue;
2745
+ }
2746
+ const thisSymbol = { reel: ridx, row: sidx, symbol };
2747
+ this._checked.push(thisSymbol);
2748
+ const neighbors = this.getNeighbors(ridx, sidx);
2749
+ const matchingSymbols = this.evaluateCluster(symbol, neighbors);
2750
+ if (matchingSymbols.size >= 1) {
2751
+ potentialClusters.push([thisSymbol, ...matchingSymbols.values()]);
2752
+ }
2753
+ }
2754
+ }
2755
+ potentialClusters.forEach((cluster) => {
2756
+ const kind = cluster.length;
2757
+ let baseSymbol = cluster.find((s) => !this.isWild(s.symbol))?.symbol;
2758
+ if (!baseSymbol) baseSymbol = cluster[0].symbol;
2759
+ const payout = this.getSymbolPayout(baseSymbol, kind);
2760
+ if (!baseSymbol.pays || Object.keys(baseSymbol.pays).length === 0) {
2761
+ return;
2762
+ }
2763
+ clusterWins.push({
2764
+ payout,
2765
+ kind,
2766
+ baseSymbol,
2767
+ symbols: cluster.map((s) => ({
2768
+ symbol: s.symbol,
2769
+ isWild: this.isWild(s.symbol),
2770
+ reelIndex: s.reel,
2771
+ posIndex: s.row
2772
+ }))
2773
+ });
2774
+ });
2775
+ for (const win of clusterWins) {
2776
+ this.ctx.services.data.recordSymbolOccurrence({
2777
+ kind: win.kind,
2778
+ symbolId: win.baseSymbol.id,
2779
+ spinType: this.ctx.state.currentSpinType
2780
+ });
2781
+ }
2782
+ this.payout = clusterWins.reduce((sum, c) => sum + c.payout, 0);
2783
+ this.winCombinations = clusterWins;
2784
+ return this;
2785
+ }
2786
+ getNeighbors(ridx, sidx) {
2787
+ const board = this._currentBoard;
2788
+ const neighbors = [];
2789
+ const potentialNeighbors = [
2790
+ [ridx - 1, sidx],
2791
+ [ridx + 1, sidx],
2792
+ [ridx, sidx - 1],
2793
+ [ridx, sidx + 1]
2794
+ ];
2795
+ potentialNeighbors.forEach(([nridx, nsidx]) => {
2796
+ if (board[nridx] && board[nridx][nsidx]) {
2797
+ neighbors.push({ reel: nridx, row: nsidx, symbol: board[nridx][nsidx] });
2798
+ }
2799
+ });
2800
+ return neighbors;
2801
+ }
2802
+ evaluateCluster(rootSymbol, neighbors) {
2803
+ const matchingSymbols = /* @__PURE__ */ new Map();
2804
+ neighbors.forEach((neighbor) => {
2805
+ const { reel, row, symbol } = neighbor;
2806
+ if (this.isChecked(reel, row)) return;
2807
+ if (this.isCheckedWild(reel, row)) return;
2808
+ if (this.isWild(symbol) || symbol.compare(rootSymbol)) {
2809
+ const key = `${reel}-${row}`;
2810
+ matchingSymbols.set(key, { reel, row, symbol });
2811
+ if (symbol.compare(rootSymbol)) {
2812
+ this._checked.push(neighbor);
2813
+ }
2814
+ if (this.isWild(symbol)) {
2815
+ this._checkedWilds.push(neighbor);
2816
+ }
2817
+ const neighbors2 = this.getNeighbors(reel, row);
2818
+ const nestedMatches = this.evaluateCluster(rootSymbol, neighbors2);
2819
+ nestedMatches.forEach((nsym) => {
2820
+ const nkey = `${nsym.reel}-${nsym.row}`;
2821
+ matchingSymbols.set(nkey, nsym);
2822
+ });
2823
+ }
2824
+ });
2825
+ return matchingSymbols;
2826
+ }
2827
+ isChecked(ridx, sidx) {
2828
+ return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
2829
+ }
2830
+ isCheckedWild(ridx, sidx) {
2831
+ return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
2832
+ }
2644
2833
  };
2645
2834
 
2646
2835
  // src/win-types/ManywaysWinType.ts
2647
2836
  var ManywaysWinType = class extends WinType {
2837
+ _checked = [];
2838
+ _checkedWilds = [];
2839
+ constructor(opts) {
2840
+ super(opts);
2841
+ }
2842
+ validateConfig() {
2843
+ }
2844
+ /**
2845
+ * Calculates wins based on the defined paylines and provided board state.\
2846
+ * Retrieve the results using `getWins()` after.
2847
+ */
2848
+ evaluateWins(board) {
2849
+ this.validateConfig();
2850
+ const waysWins = [];
2851
+ const reels = board;
2852
+ const possibleWaysWins = /* @__PURE__ */ new Map();
2853
+ const candidateSymbols = /* @__PURE__ */ new Map();
2854
+ let searchReelIdx = 0;
2855
+ let searchActive = true;
2856
+ while (searchActive && searchReelIdx < reels.length) {
2857
+ const reel = reels[searchReelIdx];
2858
+ let hasWild = false;
2859
+ for (const symbol of reel) {
2860
+ candidateSymbols.set(symbol.id, symbol);
2861
+ if (this.isWild(symbol)) {
2862
+ hasWild = true;
2863
+ }
2864
+ }
2865
+ if (!hasWild) {
2866
+ searchActive = false;
2867
+ }
2868
+ searchReelIdx++;
2869
+ }
2870
+ for (const baseSymbol of candidateSymbols.values()) {
2871
+ let symbolList = {};
2872
+ let isInterrupted = false;
2873
+ for (const [ridx, reel] of reels.entries()) {
2874
+ if (isInterrupted) break;
2875
+ for (const [sidx, symbol] of reel.entries()) {
2876
+ const isMatch = baseSymbol.compare(symbol) || this.isWild(symbol);
2877
+ if (isMatch) {
2878
+ if (!symbolList[ridx]) {
2879
+ symbolList[ridx] = [];
2880
+ }
2881
+ symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
2882
+ }
2883
+ }
2884
+ if (!symbolList[ridx]) {
2885
+ isInterrupted = true;
2886
+ break;
2887
+ }
2888
+ }
2889
+ const minSymLine = Math.min(
2890
+ ...Object.keys(baseSymbol.pays || {}).map((k) => parseInt(k, 10))
2891
+ );
2892
+ const wayLength = this.getWayLength(symbolList);
2893
+ if (wayLength >= minSymLine) {
2894
+ possibleWaysWins.set(baseSymbol.id, symbolList);
2895
+ }
2896
+ }
2897
+ for (const [baseSymbolId, symbolList] of possibleWaysWins.entries()) {
2898
+ const wayLength = this.getWayLength(symbolList);
2899
+ let baseSymbol = Object.values(symbolList).flatMap((l) => l.map((s) => s)).find((s) => !this.isWild(s.symbol))?.symbol;
2900
+ if (!baseSymbol) baseSymbol = symbolList[0][0].symbol;
2901
+ const singleWayPayout = this.getSymbolPayout(baseSymbol, wayLength);
2902
+ const totalWays = Object.values(symbolList).reduce(
2903
+ (ways, syms) => ways * syms.length,
2904
+ 1
2905
+ );
2906
+ const totalPayout = singleWayPayout * totalWays;
2907
+ waysWins.push({
2908
+ kind: wayLength,
2909
+ baseSymbol,
2910
+ symbols: Object.values(symbolList).flatMap(
2911
+ (reel) => reel.map((s) => ({
2912
+ symbol: s.symbol,
2913
+ isWild: this.isWild(s.symbol),
2914
+ reelIndex: s.reel,
2915
+ posIndex: s.row
2916
+ }))
2917
+ ),
2918
+ ways: totalWays,
2919
+ payout: totalPayout
2920
+ });
2921
+ }
2922
+ for (const win of waysWins) {
2923
+ this.ctx.services.data.recordSymbolOccurrence({
2924
+ kind: win.kind,
2925
+ symbolId: win.baseSymbol.id,
2926
+ spinType: this.ctx.state.currentSpinType
2927
+ });
2928
+ }
2929
+ this.payout = waysWins.reduce((sum, l) => sum + l.payout, 0);
2930
+ this.winCombinations = waysWins;
2931
+ return this;
2932
+ }
2933
+ getWayLength(symbolList) {
2934
+ return Math.max(...Object.keys(symbolList).map((k) => parseInt(k, 10))) + 1;
2935
+ }
2936
+ isChecked(ridx, sidx) {
2937
+ return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
2938
+ }
2939
+ isCheckedWild(ridx, sidx) {
2940
+ return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
2941
+ }
2648
2942
  };
2649
2943
 
2650
2944
  // src/reel-set/GeneratedReelSet.ts
@@ -3005,7 +3299,7 @@ var GeneratedReelSet = class extends ReelSet {
3005
3299
  };
3006
3300
 
3007
3301
  // src/reel-set/StaticReelSet.ts
3008
- import assert11 from "assert";
3302
+ import assert12 from "assert";
3009
3303
  var StaticReelSet = class extends ReelSet {
3010
3304
  reels;
3011
3305
  csvPath;
@@ -3015,7 +3309,7 @@ var StaticReelSet = class extends ReelSet {
3015
3309
  this.reels = [];
3016
3310
  this._strReels = opts.reels || [];
3017
3311
  this.csvPath = opts.csvPath || "";
3018
- assert11(
3312
+ assert12(
3019
3313
  opts.reels || opts.csvPath,
3020
3314
  `Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`
3021
3315
  );
@@ -3091,6 +3385,18 @@ var StandaloneBoard = class {
3091
3385
  getPaddingBottom() {
3092
3386
  return this.board.paddingBottom;
3093
3387
  }
3388
+ /**
3389
+ * Gets the symbol at the specified reel and row index.
3390
+ */
3391
+ getSymbol(reelIndex, rowIndex) {
3392
+ return this.board.getSymbol(reelIndex, rowIndex);
3393
+ }
3394
+ /**
3395
+ * Sets the symbol at the specified reel and row index.
3396
+ */
3397
+ setSymbol(reelIndex, rowIndex, symbol) {
3398
+ this.board.setSymbol(reelIndex, rowIndex, symbol);
3399
+ }
3094
3400
  resetReels() {
3095
3401
  this.board.resetReels({
3096
3402
  ctx: this.ctx