@slot-engine/core 0.1.5 → 0.1.6

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.d.mts CHANGED
@@ -365,6 +365,70 @@ interface GameSymbolOpts {
365
365
  properties?: Record<string, any>;
366
366
  }
367
367
 
368
+ declare class WinType {
369
+ protected payout: number;
370
+ protected winCombinations: WinCombination[];
371
+ protected ctx: GameContext;
372
+ protected readonly wildSymbol?: WildSymbol;
373
+ constructor(opts: WinTypeOpts);
374
+ /**
375
+ * Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
376
+ */
377
+ evaluateWins(board: Reels): this;
378
+ /**
379
+ * Custom post-processing of wins, e.g. for handling multipliers.
380
+ */
381
+ postProcess(func: PostProcessFn<typeof this.winCombinations>): this;
382
+ /**
383
+ * Returns the total payout and detailed win combinations.
384
+ */
385
+ getWins(): {
386
+ payout: number;
387
+ winCombinations: WinCombination[];
388
+ };
389
+ protected isWild(symbol: GameSymbol): boolean;
390
+ protected getSymbolPayout(symbol: GameSymbol, count: number): number;
391
+ }
392
+ interface WinTypeOpts {
393
+ /**
394
+ * A reference to the game context.
395
+ */
396
+ ctx: GameContext<any, any, any>;
397
+ /**
398
+ * Configuration used to identify wild symbols on the board.\
399
+ * You can either provide a specific `GameSymbol` instance or a set of properties to match against symbols on the board.
400
+ *
401
+ * @example
402
+ * If you have different wild symbols, each with a property `isWild: true`, you can define:
403
+ * ```ts
404
+ * wildSymbol: { isWild: true }
405
+ * ```
406
+ *
407
+ * @example
408
+ * If you have a single wild symbol instance, you can define:
409
+ * ```ts
410
+ * wildSymbol: myWildSymbol
411
+ * ```
412
+ */
413
+ wildSymbol?: WildSymbol;
414
+ }
415
+ type WinCombination = {
416
+ payout: number;
417
+ kind: number;
418
+ baseSymbol: GameSymbol;
419
+ symbols: Array<{
420
+ symbol: GameSymbol;
421
+ isWild: boolean;
422
+ substitutedFor?: GameSymbol;
423
+ reelIndex: number;
424
+ posIndex: number;
425
+ }>;
426
+ };
427
+ type PostProcessFn<TWinCombs extends WinCombination[]> = (wins: TWinCombs, ctx: GameContext) => {
428
+ winCombinations: TWinCombs;
429
+ };
430
+ type WildSymbol = GameSymbol | Record<string, any>;
431
+
368
432
  declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> extends AbstractService {
369
433
  private board;
370
434
  constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>);
@@ -454,6 +518,17 @@ declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymb
454
518
  newBoardSymbols: Record<string, GameSymbol[]>;
455
519
  newPaddingTopSymbols: Record<string, GameSymbol[]>;
456
520
  };
521
+ /**
522
+ * Dedupes win symbols for tumble.\
523
+ * Returns a list of symbols to remove from the board based on the given win combinations.
524
+ *
525
+ * Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
526
+ * this method ensures that each symbol is only listed once for removal. Otherwise tumbling may break.
527
+ */
528
+ dedupeWinSymbolsForTumble(winCombinations: WinCombination[]): {
529
+ reelIdx: number;
530
+ rowIdx: number;
531
+ }[];
457
532
  }
458
533
 
459
534
  declare class Recorder {
@@ -698,70 +773,6 @@ interface GameModeOpts {
698
773
  isBonusBuy: boolean;
699
774
  }
700
775
 
701
- declare class WinType {
702
- protected payout: number;
703
- protected winCombinations: WinCombination[];
704
- protected ctx: GameContext;
705
- protected readonly wildSymbol?: WildSymbol;
706
- constructor(opts: WinTypeOpts);
707
- /**
708
- * Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
709
- */
710
- evaluateWins(board: Reels): this;
711
- /**
712
- * Custom post-processing of wins, e.g. for handling multipliers.
713
- */
714
- postProcess(func: PostProcessFn<typeof this.winCombinations>): this;
715
- /**
716
- * Returns the total payout and detailed win combinations.
717
- */
718
- getWins(): {
719
- payout: number;
720
- winCombinations: WinCombination[];
721
- };
722
- protected isWild(symbol: GameSymbol): boolean;
723
- protected getSymbolPayout(symbol: GameSymbol, count: number): number;
724
- }
725
- interface WinTypeOpts {
726
- /**
727
- * A reference to the game context.
728
- */
729
- ctx: GameContext<any, any, any>;
730
- /**
731
- * Configuration used to identify wild symbols on the board.\
732
- * You can either provide a specific `GameSymbol` instance or a set of properties to match against symbols on the board.
733
- *
734
- * @example
735
- * If you have different wild symbols, each with a property `isWild: true`, you can define:
736
- * ```ts
737
- * wildSymbol: { isWild: true }
738
- * ```
739
- *
740
- * @example
741
- * If you have a single wild symbol instance, you can define:
742
- * ```ts
743
- * wildSymbol: myWildSymbol
744
- * ```
745
- */
746
- wildSymbol?: WildSymbol;
747
- }
748
- type WinCombination = {
749
- payout: number;
750
- kind: number;
751
- baseSymbol: GameSymbol;
752
- symbols: Array<{
753
- symbol: GameSymbol;
754
- isWild: boolean;
755
- substitutedFor?: GameSymbol;
756
- reelIndex: number;
757
- posIndex: number;
758
- }>;
759
- };
760
- type PostProcessFn<TWinCombs extends WinCombination[]> = (wins: TWinCombs, ctx: GameContext) => {
761
- winCombinations: TWinCombs;
762
- };
763
- type WildSymbol = GameSymbol | Record<string, any>;
764
-
765
776
  declare class GameService<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> extends AbstractService {
766
777
  constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>);
767
778
  /**
@@ -1583,6 +1594,17 @@ declare class StandaloneBoard {
1583
1594
  newBoardSymbols: Record<string, GameSymbol[]>;
1584
1595
  newPaddingTopSymbols: Record<string, GameSymbol[]>;
1585
1596
  };
1597
+ /**
1598
+ * Dedupes win symbols for tumble.\
1599
+ * Returns a list of symbols to remove from the board based on the given win combinations.
1600
+ *
1601
+ * Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
1602
+ * this method ensures that each symbol is only listed once for removal. Otherwise tumbling may break.
1603
+ */
1604
+ dedupeWinSymbolsForTumble(winCombinations: WinCombination[]): {
1605
+ reelIdx: number;
1606
+ rowIdx: number;
1607
+ }[];
1586
1608
  }
1587
1609
  interface StandaloneBoardOptions {
1588
1610
  ctx: GameContext;
package/dist/index.d.ts CHANGED
@@ -365,6 +365,70 @@ interface GameSymbolOpts {
365
365
  properties?: Record<string, any>;
366
366
  }
367
367
 
368
+ declare class WinType {
369
+ protected payout: number;
370
+ protected winCombinations: WinCombination[];
371
+ protected ctx: GameContext;
372
+ protected readonly wildSymbol?: WildSymbol;
373
+ constructor(opts: WinTypeOpts);
374
+ /**
375
+ * Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
376
+ */
377
+ evaluateWins(board: Reels): this;
378
+ /**
379
+ * Custom post-processing of wins, e.g. for handling multipliers.
380
+ */
381
+ postProcess(func: PostProcessFn<typeof this.winCombinations>): this;
382
+ /**
383
+ * Returns the total payout and detailed win combinations.
384
+ */
385
+ getWins(): {
386
+ payout: number;
387
+ winCombinations: WinCombination[];
388
+ };
389
+ protected isWild(symbol: GameSymbol): boolean;
390
+ protected getSymbolPayout(symbol: GameSymbol, count: number): number;
391
+ }
392
+ interface WinTypeOpts {
393
+ /**
394
+ * A reference to the game context.
395
+ */
396
+ ctx: GameContext<any, any, any>;
397
+ /**
398
+ * Configuration used to identify wild symbols on the board.\
399
+ * You can either provide a specific `GameSymbol` instance or a set of properties to match against symbols on the board.
400
+ *
401
+ * @example
402
+ * If you have different wild symbols, each with a property `isWild: true`, you can define:
403
+ * ```ts
404
+ * wildSymbol: { isWild: true }
405
+ * ```
406
+ *
407
+ * @example
408
+ * If you have a single wild symbol instance, you can define:
409
+ * ```ts
410
+ * wildSymbol: myWildSymbol
411
+ * ```
412
+ */
413
+ wildSymbol?: WildSymbol;
414
+ }
415
+ type WinCombination = {
416
+ payout: number;
417
+ kind: number;
418
+ baseSymbol: GameSymbol;
419
+ symbols: Array<{
420
+ symbol: GameSymbol;
421
+ isWild: boolean;
422
+ substitutedFor?: GameSymbol;
423
+ reelIndex: number;
424
+ posIndex: number;
425
+ }>;
426
+ };
427
+ type PostProcessFn<TWinCombs extends WinCombination[]> = (wins: TWinCombs, ctx: GameContext) => {
428
+ winCombinations: TWinCombs;
429
+ };
430
+ type WildSymbol = GameSymbol | Record<string, any>;
431
+
368
432
  declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> extends AbstractService {
369
433
  private board;
370
434
  constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>);
@@ -454,6 +518,17 @@ declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymb
454
518
  newBoardSymbols: Record<string, GameSymbol[]>;
455
519
  newPaddingTopSymbols: Record<string, GameSymbol[]>;
456
520
  };
521
+ /**
522
+ * Dedupes win symbols for tumble.\
523
+ * Returns a list of symbols to remove from the board based on the given win combinations.
524
+ *
525
+ * Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
526
+ * this method ensures that each symbol is only listed once for removal. Otherwise tumbling may break.
527
+ */
528
+ dedupeWinSymbolsForTumble(winCombinations: WinCombination[]): {
529
+ reelIdx: number;
530
+ rowIdx: number;
531
+ }[];
457
532
  }
458
533
 
459
534
  declare class Recorder {
@@ -698,70 +773,6 @@ interface GameModeOpts {
698
773
  isBonusBuy: boolean;
699
774
  }
700
775
 
701
- declare class WinType {
702
- protected payout: number;
703
- protected winCombinations: WinCombination[];
704
- protected ctx: GameContext;
705
- protected readonly wildSymbol?: WildSymbol;
706
- constructor(opts: WinTypeOpts);
707
- /**
708
- * Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
709
- */
710
- evaluateWins(board: Reels): this;
711
- /**
712
- * Custom post-processing of wins, e.g. for handling multipliers.
713
- */
714
- postProcess(func: PostProcessFn<typeof this.winCombinations>): this;
715
- /**
716
- * Returns the total payout and detailed win combinations.
717
- */
718
- getWins(): {
719
- payout: number;
720
- winCombinations: WinCombination[];
721
- };
722
- protected isWild(symbol: GameSymbol): boolean;
723
- protected getSymbolPayout(symbol: GameSymbol, count: number): number;
724
- }
725
- interface WinTypeOpts {
726
- /**
727
- * A reference to the game context.
728
- */
729
- ctx: GameContext<any, any, any>;
730
- /**
731
- * Configuration used to identify wild symbols on the board.\
732
- * You can either provide a specific `GameSymbol` instance or a set of properties to match against symbols on the board.
733
- *
734
- * @example
735
- * If you have different wild symbols, each with a property `isWild: true`, you can define:
736
- * ```ts
737
- * wildSymbol: { isWild: true }
738
- * ```
739
- *
740
- * @example
741
- * If you have a single wild symbol instance, you can define:
742
- * ```ts
743
- * wildSymbol: myWildSymbol
744
- * ```
745
- */
746
- wildSymbol?: WildSymbol;
747
- }
748
- type WinCombination = {
749
- payout: number;
750
- kind: number;
751
- baseSymbol: GameSymbol;
752
- symbols: Array<{
753
- symbol: GameSymbol;
754
- isWild: boolean;
755
- substitutedFor?: GameSymbol;
756
- reelIndex: number;
757
- posIndex: number;
758
- }>;
759
- };
760
- type PostProcessFn<TWinCombs extends WinCombination[]> = (wins: TWinCombs, ctx: GameContext) => {
761
- winCombinations: TWinCombs;
762
- };
763
- type WildSymbol = GameSymbol | Record<string, any>;
764
-
765
776
  declare class GameService<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> extends AbstractService {
766
777
  constructor(ctx: () => GameContext<TGameModes, TSymbols, TUserState>);
767
778
  /**
@@ -1583,6 +1594,17 @@ declare class StandaloneBoard {
1583
1594
  newBoardSymbols: Record<string, GameSymbol[]>;
1584
1595
  newPaddingTopSymbols: Record<string, GameSymbol[]>;
1585
1596
  };
1597
+ /**
1598
+ * Dedupes win symbols for tumble.\
1599
+ * Returns a list of symbols to remove from the board based on the given win combinations.
1600
+ *
1601
+ * Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
1602
+ * this method ensures that each symbol is only listed once for removal. Otherwise tumbling may break.
1603
+ */
1604
+ dedupeWinSymbolsForTumble(winCombinations: WinCombination[]): {
1605
+ reelIdx: number;
1606
+ rowIdx: number;
1607
+ }[];
1586
1608
  }
1587
1609
  interface StandaloneBoardOptions {
1588
1610
  ctx: GameContext;
package/dist/index.js CHANGED
@@ -697,14 +697,15 @@ var Board = class {
697
697
  this.lastUsedReels = opts.reels;
698
698
  for (let ridx = 0; ridx < reelsAmount; ridx++) {
699
699
  const reelPos = finalReelStops[ridx];
700
+ const reelLength = opts.reels[ridx].length;
700
701
  for (let p = padSymbols - 1; p >= 0; p--) {
701
- const topPos = (reelPos - (p + 1)) % opts.reels[ridx].length;
702
+ const topPos = ((reelPos - (p + 1)) % reelLength + reelLength) % reelLength;
702
703
  this.paddingTop[ridx].push(opts.reels[ridx][topPos]);
703
- const bottomPos = (reelPos + symbolsPerReel[ridx] + p) % opts.reels[ridx].length;
704
+ const bottomPos = (reelPos + symbolsPerReel[ridx] + p) % reelLength;
704
705
  this.paddingBottom[ridx].unshift(opts.reels[ridx][bottomPos]);
705
706
  }
706
707
  for (let row = 0; row < symbolsPerReel[ridx]; row++) {
707
- const symbol = opts.reels[ridx][(reelPos + row) % opts.reels[ridx].length];
708
+ const symbol = opts.reels[ridx][(reelPos + row) % reelLength];
708
709
  if (!symbol) {
709
710
  throw new Error(`Failed to get symbol at pos ${reelPos + row} on reel ${ridx}`);
710
711
  }
@@ -780,6 +781,19 @@ var Board = class {
780
781
  newPaddingTopSymbols
781
782
  };
782
783
  }
784
+ dedupeWinSymbolsForTumble(winCombinations) {
785
+ const symbolsMap = /* @__PURE__ */ new Map();
786
+ winCombinations.forEach((wc) => {
787
+ wc.symbols.forEach((s) => {
788
+ symbolsMap.set(`${s.reelIndex},${s.posIndex}`, {
789
+ reelIdx: s.reelIndex,
790
+ rowIdx: s.posIndex
791
+ });
792
+ });
793
+ });
794
+ const symbolsToRemove = Array.from(symbolsMap.values());
795
+ return symbolsToRemove;
796
+ }
783
797
  };
784
798
 
785
799
  // src/service/board.ts
@@ -924,6 +938,16 @@ var BoardService = class extends AbstractService {
924
938
  symbolsToDelete
925
939
  });
926
940
  }
941
+ /**
942
+ * Dedupes win symbols for tumble.\
943
+ * Returns a list of symbols to remove from the board based on the given win combinations.
944
+ *
945
+ * Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
946
+ * this method ensures that each symbol is only listed once for removal. Otherwise tumbling may break.
947
+ */
948
+ dedupeWinSymbolsForTumble(winCombinations) {
949
+ return this.board.dedupeWinSymbolsForTumble(winCombinations);
950
+ }
927
951
  };
928
952
 
929
953
  // src/service/data.ts
@@ -2322,28 +2346,12 @@ var Analysis = class {
2322
2346
  const payoutRanges = {};
2323
2347
  for (const modeStr of gameModes) {
2324
2348
  payoutRanges[modeStr] = { overall: {}, criteria: {} };
2325
- const lutOptimized = parseLookupTable(
2326
- import_fs3.default.readFileSync(this.filePaths[modeStr].lutOptimized, "utf-8")
2327
- );
2328
2349
  const lutSegmented = parseLookupTableSegmented(
2329
2350
  import_fs3.default.readFileSync(this.filePaths[modeStr].lutSegmented, "utf-8")
2330
2351
  );
2331
- lutOptimized.forEach(([, , p]) => {
2332
- const payout = p / 100;
2333
- for (const [min, max] of winRanges) {
2334
- if (payout >= min && payout <= max) {
2335
- const rangeKey = `${min}-${max}`;
2336
- if (!payoutRanges[modeStr].overall[rangeKey]) {
2337
- payoutRanges[modeStr].overall[rangeKey] = 0;
2338
- }
2339
- payoutRanges[modeStr].overall[rangeKey] += 1;
2340
- break;
2341
- }
2342
- }
2343
- });
2344
2352
  lutSegmented.forEach(([, criteria, bp, fsp]) => {
2345
- const basePayout = bp / 100;
2346
- const freeSpinPayout = fsp / 100;
2353
+ const basePayout = bp;
2354
+ const freeSpinPayout = fsp;
2347
2355
  const payout = basePayout + freeSpinPayout;
2348
2356
  for (const [min, max] of winRanges) {
2349
2357
  if (payout >= min && payout <= max) {
@@ -3800,6 +3808,16 @@ var StandaloneBoard = class {
3800
3808
  padSymbols: this.padSymbols
3801
3809
  });
3802
3810
  }
3811
+ /**
3812
+ * Dedupes win symbols for tumble.\
3813
+ * Returns a list of symbols to remove from the board based on the given win combinations.
3814
+ *
3815
+ * Since it may be possible that multiple win combinations include the same symbol (e.g. Wilds),\
3816
+ * this method ensures that each symbol is only listed once for removal. Otherwise tumbling may break.
3817
+ */
3818
+ dedupeWinSymbolsForTumble(winCombinations) {
3819
+ return this.board.dedupeWinSymbolsForTumble(winCombinations);
3820
+ }
3803
3821
  };
3804
3822
  // Annotate the CommonJS export names for ESM import in node:
3805
3823
  0 && (module.exports = {