@slot-engine/core 0.0.9 → 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
  /**
@@ -2579,9 +2608,20 @@ var WinType = class {
2579
2608
  isWild(symbol) {
2580
2609
  return !!this.wildSymbol && symbol.compare(this.wildSymbol);
2581
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
+ }
2582
2621
  };
2583
2622
 
2584
2623
  // src/win-types/LinesWinType.ts
2624
+ var import_assert11 = __toESM(require("assert"));
2585
2625
  var LinesWinType = class extends WinType {
2586
2626
  lines;
2587
2627
  constructor(opts) {
@@ -2622,106 +2662,335 @@ var LinesWinType = class extends WinType {
2622
2662
  evaluateWins(board) {
2623
2663
  this.validateConfig();
2624
2664
  const lineWins = [];
2625
- let payout = 0;
2626
2665
  const reels = board;
2627
- for (const [lineNumStr, lineDef] of Object.entries(this.lines)) {
2666
+ for (const [lineNumStr, line] of Object.entries(this.lines)) {
2628
2667
  const lineNum = Number(lineNumStr);
2629
- let baseSymbol = null;
2630
- let leadingWilds = 0;
2631
- const chain = [];
2632
- const details = [];
2633
- for (let ridx = 0; ridx < reels.length; ridx++) {
2634
- const rowIdx = lineDef[ridx];
2635
- const sym = reels[ridx][rowIdx];
2636
- if (!sym) throw new Error("Encountered an invalid symbol while evaluating wins.");
2637
- const wild = this.isWild(sym);
2638
- if (ridx === 0) {
2639
- chain.push(sym);
2640
- details.push({ reelIndex: ridx, posIndex: rowIdx, symbol: sym, isWild: wild });
2641
- if (wild) leadingWilds++;
2642
- else baseSymbol = sym;
2643
- continue;
2644
- }
2645
- if (wild) {
2646
- chain.push(sym);
2647
- details.push({
2648
- reelIndex: ridx,
2649
- posIndex: rowIdx,
2650
- symbol: sym,
2651
- isWild: true,
2652
- substitutedFor: baseSymbol || void 0
2653
- });
2654
- continue;
2655
- }
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];
2656
2674
  if (!baseSymbol) {
2657
- baseSymbol = sym;
2658
- chain.push(sym);
2659
- 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 });
2660
2684
  continue;
2661
2685
  }
2662
- if (sym.id === baseSymbol.id) {
2663
- chain.push(sym);
2664
- 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 });
2665
2693
  continue;
2666
2694
  }
2667
- break;
2668
- }
2669
- if (chain.length === 0) continue;
2670
- const allWild = chain.every((s) => this.isWild(s));
2671
- const wildRepresentative = this.wildSymbol instanceof GameSymbol ? this.wildSymbol : null;
2672
- const len = chain.length;
2673
- let bestPayout = 0;
2674
- let bestType = null;
2675
- let payingSymbol = null;
2676
- if (baseSymbol?.pays && baseSymbol.pays[len]) {
2677
- bestPayout = baseSymbol.pays[len];
2678
- bestType = "substituted";
2679
- payingSymbol = baseSymbol;
2680
- }
2681
- if (allWild && wildRepresentative?.pays && wildRepresentative.pays[len]) {
2682
- const wildPay = wildRepresentative.pays[len];
2683
- if (wildPay > bestPayout) {
2684
- bestPayout = wildPay;
2685
- bestType = "pure-wild";
2686
- payingSymbol = wildRepresentative;
2695
+ if (baseSymbol.compare(thisSymbol) || this.isWild(thisSymbol)) {
2696
+ potentialWinLine.push({ reel: ridx, row: sidx, symbol: thisSymbol });
2687
2697
  }
2688
2698
  }
2689
- if (!bestPayout || !bestType || !payingSymbol) continue;
2690
- const minLen = payingSymbol.pays ? Math.min(...Object.keys(payingSymbol.pays).map(Number)) : Infinity;
2691
- if (len < minLen) continue;
2692
- const wildCount = details.filter((d) => d.isWild).length;
2693
- const nonWildCount = len - wildCount;
2694
- 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
+ })),
2695
2714
  lineNumber: lineNum,
2696
- kind: len,
2697
- payout: bestPayout,
2698
- symbol: payingSymbol,
2699
- winType: bestType,
2700
- substitutedBaseSymbol: bestType === "pure-wild" ? null : baseSymbol,
2701
- symbols: details,
2702
- stats: { wildCount, nonWildCount, leadingWilds }
2703
- });
2704
- 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);
2705
2733
  }
2706
2734
  for (const win of lineWins) {
2707
2735
  this.ctx.services.data.recordSymbolOccurrence({
2708
2736
  kind: win.kind,
2709
- symbolId: win.symbol.id,
2737
+ symbolId: win.baseSymbol.id,
2710
2738
  spinType: this.ctx.state.currentSpinType
2711
2739
  });
2712
2740
  }
2713
- this.payout = payout;
2741
+ this.payout = lineWins.reduce((sum, l) => sum + l.payout, 0);
2714
2742
  this.winCombinations = lineWins;
2715
2743
  return this;
2716
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
+ }
2717
2753
  };
2718
2754
 
2719
2755
  // src/win-types/ClusterWinType.ts
2720
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
+ }
2721
2885
  };
2722
2886
 
2723
2887
  // src/win-types/ManywaysWinType.ts
2724
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
+ }
2725
2994
  };
2726
2995
 
2727
2996
  // src/reel-set/GeneratedReelSet.ts
@@ -3082,7 +3351,7 @@ var GeneratedReelSet = class extends ReelSet {
3082
3351
  };
3083
3352
 
3084
3353
  // src/reel-set/StaticReelSet.ts
3085
- var import_assert11 = __toESM(require("assert"));
3354
+ var import_assert12 = __toESM(require("assert"));
3086
3355
  var StaticReelSet = class extends ReelSet {
3087
3356
  reels;
3088
3357
  csvPath;
@@ -3092,7 +3361,7 @@ var StaticReelSet = class extends ReelSet {
3092
3361
  this.reels = [];
3093
3362
  this._strReels = opts.reels || [];
3094
3363
  this.csvPath = opts.csvPath || "";
3095
- (0, import_assert11.default)(
3364
+ (0, import_assert12.default)(
3096
3365
  opts.reels || opts.csvPath,
3097
3366
  `Either 'reels' or 'csvPath' must be provided for StaticReelSet ${this.id}`
3098
3367
  );
@@ -3168,6 +3437,18 @@ var StandaloneBoard = class {
3168
3437
  getPaddingBottom() {
3169
3438
  return this.board.paddingBottom;
3170
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
+ }
3171
3452
  resetReels() {
3172
3453
  this.board.resetReels({
3173
3454
  ctx: this.ctx