@slot-engine/core 0.2.2 → 0.2.4
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 +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +138 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +138 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -64,6 +64,7 @@ type PermanentFilePaths = {
|
|
|
64
64
|
simulationSummary: string;
|
|
65
65
|
statsPayouts: string;
|
|
66
66
|
statsSummary: string;
|
|
67
|
+
statsRecords: string;
|
|
67
68
|
};
|
|
68
69
|
type TemporaryFilePaths = {
|
|
69
70
|
tempBooks: (mode: string, i: number) => string;
|
|
@@ -558,6 +559,10 @@ declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymb
|
|
|
558
559
|
* Removes the symbol at the specified reel and row index.
|
|
559
560
|
*/
|
|
560
561
|
removeSymbol(reelIndex: number, rowIndex: number): void;
|
|
562
|
+
/**
|
|
563
|
+
* Updates properties of the symbol at the specified reel and row index.
|
|
564
|
+
*/
|
|
565
|
+
updateSymbol(reelIndex: number, rowIndex: number, properties: Record<string, any>): void;
|
|
561
566
|
private resetReels;
|
|
562
567
|
/**
|
|
563
568
|
* Sets the anticipation value for a specific reel.
|
|
@@ -781,7 +786,6 @@ declare class DataService<TGameModes extends AnyGameModes = AnyGameModes, TSymbo
|
|
|
781
786
|
recordSymbolOccurrence(data: {
|
|
782
787
|
kind: number;
|
|
783
788
|
symbolId: string;
|
|
784
|
-
spinType: SpinType;
|
|
785
789
|
[key: string]: any;
|
|
786
790
|
}): void;
|
|
787
791
|
/**
|
|
@@ -1242,7 +1246,40 @@ interface PayoutStatistics {
|
|
|
1242
1246
|
};
|
|
1243
1247
|
}
|
|
1244
1248
|
interface AnalysisOpts {
|
|
1249
|
+
/**
|
|
1250
|
+
* Which game modes to analyze.
|
|
1251
|
+
*/
|
|
1245
1252
|
gameModes: string[];
|
|
1253
|
+
/**
|
|
1254
|
+
* Configure which recorded properties to analyze.
|
|
1255
|
+
* This will provide you with hit rates for the specified groupings.
|
|
1256
|
+
* Each entry defines a grouping strategy for statistics.
|
|
1257
|
+
*
|
|
1258
|
+
* @example
|
|
1259
|
+
* ```ts
|
|
1260
|
+
* recordStats: [
|
|
1261
|
+
* { groupBy: ["symbolId", "kind", "spinType"] }, // All win combinations
|
|
1262
|
+
* { groupBy: ["symbolId", "kind"], filter: { spinType: "basegame" } }, // Base game win combinations only
|
|
1263
|
+
* { groupBy: ["criteria"] }, // Hit rate by result set criteria
|
|
1264
|
+
* ]
|
|
1265
|
+
* ```
|
|
1266
|
+
*/
|
|
1267
|
+
recordStats?: RecordStatsConfig[];
|
|
1268
|
+
}
|
|
1269
|
+
interface RecordStatsConfig {
|
|
1270
|
+
/**
|
|
1271
|
+
* Properties to group by from the recorded search entries.\
|
|
1272
|
+
* E.g. `["symbolId", "kind", "spinType"]` for symbol hit rates.
|
|
1273
|
+
*/
|
|
1274
|
+
groupBy: string[];
|
|
1275
|
+
/**
|
|
1276
|
+
* Optional filter to only include records matching these values.
|
|
1277
|
+
*/
|
|
1278
|
+
filter?: Record<string, string>;
|
|
1279
|
+
/**
|
|
1280
|
+
* Optional custom name for this stats group in the output.
|
|
1281
|
+
*/
|
|
1282
|
+
name?: string;
|
|
1246
1283
|
}
|
|
1247
1284
|
interface Statistics {
|
|
1248
1285
|
gameMode: string;
|
|
@@ -1554,7 +1591,9 @@ declare class ManywaysWinType extends WinType {
|
|
|
1554
1591
|
* Calculates wins based on the defined paylines and provided board state.\
|
|
1555
1592
|
* Retrieve the results using `getWins()` after.
|
|
1556
1593
|
*/
|
|
1557
|
-
evaluateWins(board: Reels
|
|
1594
|
+
evaluateWins(board: Reels, opts?: {
|
|
1595
|
+
jumpGaps?: boolean;
|
|
1596
|
+
}): this;
|
|
1558
1597
|
private getWayLength;
|
|
1559
1598
|
}
|
|
1560
1599
|
interface ManywaysWinTypeOpts extends WinTypeOpts {
|
|
@@ -1748,6 +1787,10 @@ declare class StandaloneBoard {
|
|
|
1748
1787
|
* Removes the symbol at the specified reel and row index.
|
|
1749
1788
|
*/
|
|
1750
1789
|
removeSymbol(reelIndex: number, rowIndex: number): void;
|
|
1790
|
+
/**
|
|
1791
|
+
* Updates properties of the symbol at the specified reel and row index.
|
|
1792
|
+
*/
|
|
1793
|
+
updateSymbol(reelIndex: number, rowIndex: number, properties: Record<string, any>): void;
|
|
1751
1794
|
private resetReels;
|
|
1752
1795
|
/**
|
|
1753
1796
|
* Sets the anticipation value for a specific reel.
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ type PermanentFilePaths = {
|
|
|
64
64
|
simulationSummary: string;
|
|
65
65
|
statsPayouts: string;
|
|
66
66
|
statsSummary: string;
|
|
67
|
+
statsRecords: string;
|
|
67
68
|
};
|
|
68
69
|
type TemporaryFilePaths = {
|
|
69
70
|
tempBooks: (mode: string, i: number) => string;
|
|
@@ -558,6 +559,10 @@ declare class BoardService<TGameModes extends AnyGameModes = AnyGameModes, TSymb
|
|
|
558
559
|
* Removes the symbol at the specified reel and row index.
|
|
559
560
|
*/
|
|
560
561
|
removeSymbol(reelIndex: number, rowIndex: number): void;
|
|
562
|
+
/**
|
|
563
|
+
* Updates properties of the symbol at the specified reel and row index.
|
|
564
|
+
*/
|
|
565
|
+
updateSymbol(reelIndex: number, rowIndex: number, properties: Record<string, any>): void;
|
|
561
566
|
private resetReels;
|
|
562
567
|
/**
|
|
563
568
|
* Sets the anticipation value for a specific reel.
|
|
@@ -781,7 +786,6 @@ declare class DataService<TGameModes extends AnyGameModes = AnyGameModes, TSymbo
|
|
|
781
786
|
recordSymbolOccurrence(data: {
|
|
782
787
|
kind: number;
|
|
783
788
|
symbolId: string;
|
|
784
|
-
spinType: SpinType;
|
|
785
789
|
[key: string]: any;
|
|
786
790
|
}): void;
|
|
787
791
|
/**
|
|
@@ -1242,7 +1246,40 @@ interface PayoutStatistics {
|
|
|
1242
1246
|
};
|
|
1243
1247
|
}
|
|
1244
1248
|
interface AnalysisOpts {
|
|
1249
|
+
/**
|
|
1250
|
+
* Which game modes to analyze.
|
|
1251
|
+
*/
|
|
1245
1252
|
gameModes: string[];
|
|
1253
|
+
/**
|
|
1254
|
+
* Configure which recorded properties to analyze.
|
|
1255
|
+
* This will provide you with hit rates for the specified groupings.
|
|
1256
|
+
* Each entry defines a grouping strategy for statistics.
|
|
1257
|
+
*
|
|
1258
|
+
* @example
|
|
1259
|
+
* ```ts
|
|
1260
|
+
* recordStats: [
|
|
1261
|
+
* { groupBy: ["symbolId", "kind", "spinType"] }, // All win combinations
|
|
1262
|
+
* { groupBy: ["symbolId", "kind"], filter: { spinType: "basegame" } }, // Base game win combinations only
|
|
1263
|
+
* { groupBy: ["criteria"] }, // Hit rate by result set criteria
|
|
1264
|
+
* ]
|
|
1265
|
+
* ```
|
|
1266
|
+
*/
|
|
1267
|
+
recordStats?: RecordStatsConfig[];
|
|
1268
|
+
}
|
|
1269
|
+
interface RecordStatsConfig {
|
|
1270
|
+
/**
|
|
1271
|
+
* Properties to group by from the recorded search entries.\
|
|
1272
|
+
* E.g. `["symbolId", "kind", "spinType"]` for symbol hit rates.
|
|
1273
|
+
*/
|
|
1274
|
+
groupBy: string[];
|
|
1275
|
+
/**
|
|
1276
|
+
* Optional filter to only include records matching these values.
|
|
1277
|
+
*/
|
|
1278
|
+
filter?: Record<string, string>;
|
|
1279
|
+
/**
|
|
1280
|
+
* Optional custom name for this stats group in the output.
|
|
1281
|
+
*/
|
|
1282
|
+
name?: string;
|
|
1246
1283
|
}
|
|
1247
1284
|
interface Statistics {
|
|
1248
1285
|
gameMode: string;
|
|
@@ -1554,7 +1591,9 @@ declare class ManywaysWinType extends WinType {
|
|
|
1554
1591
|
* Calculates wins based on the defined paylines and provided board state.\
|
|
1555
1592
|
* Retrieve the results using `getWins()` after.
|
|
1556
1593
|
*/
|
|
1557
|
-
evaluateWins(board: Reels
|
|
1594
|
+
evaluateWins(board: Reels, opts?: {
|
|
1595
|
+
jumpGaps?: boolean;
|
|
1596
|
+
}): this;
|
|
1558
1597
|
private getWayLength;
|
|
1559
1598
|
}
|
|
1560
1599
|
interface ManywaysWinTypeOpts extends WinTypeOpts {
|
|
@@ -1748,6 +1787,10 @@ declare class StandaloneBoard {
|
|
|
1748
1787
|
* Removes the symbol at the specified reel and row index.
|
|
1749
1788
|
*/
|
|
1750
1789
|
removeSymbol(reelIndex: number, rowIndex: number): void;
|
|
1790
|
+
/**
|
|
1791
|
+
* Updates properties of the symbol at the specified reel and row index.
|
|
1792
|
+
*/
|
|
1793
|
+
updateSymbol(reelIndex: number, rowIndex: number, properties: Record<string, any>): void;
|
|
1751
1794
|
private resetReels;
|
|
1752
1795
|
/**
|
|
1753
1796
|
* Sets the anticipation value for a specific reel.
|
package/dist/index.js
CHANGED
|
@@ -92,7 +92,8 @@ function createPermanentFilePaths(basePath) {
|
|
|
92
92
|
publishFiles: import_path.default.join(basePath, "publish_files"),
|
|
93
93
|
simulationSummary: import_path.default.join(basePath, "simulation_summary.json"),
|
|
94
94
|
statsPayouts: import_path.default.join(basePath, "stats_payouts.json"),
|
|
95
|
-
statsSummary: import_path.default.join(basePath, "stats_summary.json")
|
|
95
|
+
statsSummary: import_path.default.join(basePath, "stats_summary.json"),
|
|
96
|
+
statsRecords: import_path.default.join(basePath, "stats_records.json")
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
99
|
function createTemporaryFilePaths(basePath, tempFolder) {
|
|
@@ -510,6 +511,14 @@ var Board = class {
|
|
|
510
511
|
this.reels[reelIndex].splice(rowIndex, 1);
|
|
511
512
|
}
|
|
512
513
|
}
|
|
514
|
+
updateSymbol(reelIndex, rowIndex, properties) {
|
|
515
|
+
const symbol = this.getSymbol(reelIndex, rowIndex);
|
|
516
|
+
if (symbol) {
|
|
517
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
518
|
+
symbol.properties.set(key, value);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
513
522
|
makeEmptyReels(opts) {
|
|
514
523
|
const length = opts.reelsAmount ?? opts.ctx.services.game.getCurrentGameMode().reelsAmount;
|
|
515
524
|
(0, import_assert3.default)(length, "Cannot make empty reels without context or reelsAmount.");
|
|
@@ -879,6 +888,12 @@ var BoardService = class extends AbstractService {
|
|
|
879
888
|
removeSymbol(reelIndex, rowIndex) {
|
|
880
889
|
this.board.removeSymbol(reelIndex, rowIndex);
|
|
881
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* Updates properties of the symbol at the specified reel and row index.
|
|
893
|
+
*/
|
|
894
|
+
updateSymbol(reelIndex, rowIndex, properties) {
|
|
895
|
+
this.board.updateSymbol(reelIndex, rowIndex, properties);
|
|
896
|
+
}
|
|
882
897
|
resetReels() {
|
|
883
898
|
this.board.resetReels({
|
|
884
899
|
ctx: this.ctx()
|
|
@@ -1087,7 +1102,10 @@ var DataService = class extends AbstractService {
|
|
|
1087
1102
|
* Calls `ctx.services.data.record()` with the provided data.
|
|
1088
1103
|
*/
|
|
1089
1104
|
recordSymbolOccurrence(data) {
|
|
1090
|
-
this.record(
|
|
1105
|
+
this.record({
|
|
1106
|
+
...data,
|
|
1107
|
+
spinType: this.ctx().state.currentSpinType
|
|
1108
|
+
});
|
|
1091
1109
|
}
|
|
1092
1110
|
/**
|
|
1093
1111
|
* Adds an event to the book.
|
|
@@ -3109,15 +3127,21 @@ function getLessBetHitrate(payoutWeights, cost) {
|
|
|
3109
3127
|
|
|
3110
3128
|
// src/analysis/index.ts
|
|
3111
3129
|
var import_worker_threads3 = require("worker_threads");
|
|
3130
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
3112
3131
|
var Analysis = class {
|
|
3113
3132
|
game;
|
|
3114
3133
|
constructor(game) {
|
|
3115
3134
|
this.game = game;
|
|
3116
3135
|
}
|
|
3117
|
-
async runAnalysis(
|
|
3136
|
+
async runAnalysis(opts) {
|
|
3137
|
+
const { gameModes, recordStats = [] } = opts;
|
|
3118
3138
|
if (!import_worker_threads3.isMainThread) return;
|
|
3139
|
+
console.log(import_chalk3.default.gray("Starting analysis..."));
|
|
3119
3140
|
this.getNumberStats(gameModes);
|
|
3120
3141
|
this.getWinRanges(gameModes);
|
|
3142
|
+
if (recordStats.length > 0) {
|
|
3143
|
+
this.getRecordStats(gameModes, recordStats);
|
|
3144
|
+
}
|
|
3121
3145
|
console.log("Analysis complete. Files written to build directory.");
|
|
3122
3146
|
}
|
|
3123
3147
|
getNumberStats(gameModes) {
|
|
@@ -3294,6 +3318,85 @@ var Analysis = class {
|
|
|
3294
3318
|
}
|
|
3295
3319
|
writeJsonFile(meta.paths.statsPayouts, payoutRanges);
|
|
3296
3320
|
}
|
|
3321
|
+
getRecordStats(gameModes, recordStatsConfig) {
|
|
3322
|
+
const meta = this.game.getMetadata();
|
|
3323
|
+
const allStats = [];
|
|
3324
|
+
for (const modeStr of gameModes) {
|
|
3325
|
+
const lutOptimized = parseLookupTable(
|
|
3326
|
+
import_fs4.default.readFileSync(meta.paths.lookupTablePublish(modeStr), "utf-8")
|
|
3327
|
+
);
|
|
3328
|
+
const totalWeight = getTotalLutWeight(lutOptimized);
|
|
3329
|
+
const weightMap = /* @__PURE__ */ new Map();
|
|
3330
|
+
lutOptimized.forEach(([bookId, weight]) => {
|
|
3331
|
+
weightMap.set(bookId, weight);
|
|
3332
|
+
});
|
|
3333
|
+
const forceRecordsPath = meta.paths.forceRecords(modeStr);
|
|
3334
|
+
if (!import_fs4.default.existsSync(forceRecordsPath)) continue;
|
|
3335
|
+
const forceRecords = JSON.parse(
|
|
3336
|
+
import_fs4.default.readFileSync(forceRecordsPath, "utf-8")
|
|
3337
|
+
);
|
|
3338
|
+
const modeStats = {
|
|
3339
|
+
gameMode: modeStr,
|
|
3340
|
+
groups: []
|
|
3341
|
+
};
|
|
3342
|
+
for (const config of recordStatsConfig) {
|
|
3343
|
+
const groupName = config.name || config.groupBy.join("_");
|
|
3344
|
+
const aggregated = /* @__PURE__ */ new Map();
|
|
3345
|
+
for (const record of forceRecords) {
|
|
3346
|
+
const searchMap = new Map(record.search.map((s) => [s.name, s.value]));
|
|
3347
|
+
if (config.filter) {
|
|
3348
|
+
let matches = true;
|
|
3349
|
+
for (const [key2, value] of Object.entries(config.filter)) {
|
|
3350
|
+
if (searchMap.get(key2) !== value) {
|
|
3351
|
+
matches = false;
|
|
3352
|
+
break;
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
if (!matches) continue;
|
|
3356
|
+
}
|
|
3357
|
+
const hasAllProps = config.groupBy.every((prop) => searchMap.has(prop));
|
|
3358
|
+
if (!hasAllProps) continue;
|
|
3359
|
+
const key = config.groupBy.map((prop) => searchMap.get(prop)).join("|");
|
|
3360
|
+
const properties = Object.fromEntries(
|
|
3361
|
+
config.groupBy.map((prop) => [prop, searchMap.get(prop)])
|
|
3362
|
+
);
|
|
3363
|
+
let totalWeight2 = 0;
|
|
3364
|
+
for (const bookId of record.bookIds) {
|
|
3365
|
+
totalWeight2 += weightMap.get(bookId) ?? 0;
|
|
3366
|
+
}
|
|
3367
|
+
const existing = aggregated.get(key);
|
|
3368
|
+
if (existing) {
|
|
3369
|
+
existing.count += record.timesTriggered;
|
|
3370
|
+
existing.totalWeight += totalWeight2;
|
|
3371
|
+
} else {
|
|
3372
|
+
aggregated.set(key, {
|
|
3373
|
+
properties,
|
|
3374
|
+
count: record.timesTriggered,
|
|
3375
|
+
totalWeight: totalWeight2
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
const items = Array.from(aggregated.entries()).map(([key, data]) => {
|
|
3380
|
+
const hitRate = round(totalWeight / data.totalWeight, 4);
|
|
3381
|
+
return {
|
|
3382
|
+
key,
|
|
3383
|
+
properties: data.properties,
|
|
3384
|
+
count: data.count,
|
|
3385
|
+
hitRateString: `1 in ${Math.round(hitRate).toLocaleString()}`,
|
|
3386
|
+
hitRate
|
|
3387
|
+
};
|
|
3388
|
+
}).sort((a, b) => a.hitRate - b.hitRate);
|
|
3389
|
+
modeStats.groups.push({
|
|
3390
|
+
name: groupName,
|
|
3391
|
+
groupBy: config.groupBy,
|
|
3392
|
+
filter: config.filter,
|
|
3393
|
+
items
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
allStats.push(modeStats);
|
|
3397
|
+
}
|
|
3398
|
+
writeJsonFile(meta.paths.statsRecords, allStats);
|
|
3399
|
+
}
|
|
3297
3400
|
getGameModeConfig(mode) {
|
|
3298
3401
|
const config = this.game.getConfig().gameModes[mode];
|
|
3299
3402
|
(0, import_assert6.default)(config, `Game mode "${mode}" not found in game config`);
|
|
@@ -3658,7 +3761,7 @@ var SlotGame = class _SlotGame {
|
|
|
3658
3761
|
*/
|
|
3659
3762
|
runAnalysis(opts) {
|
|
3660
3763
|
this.analyzer = new Analysis(this);
|
|
3661
|
-
this.analyzer.runAnalysis(opts
|
|
3764
|
+
this.analyzer.runAnalysis(opts);
|
|
3662
3765
|
}
|
|
3663
3766
|
/**
|
|
3664
3767
|
* Runs the configured tasks: simulation, optimization, and/or analysis.
|
|
@@ -4104,27 +4207,36 @@ var ManywaysWinType = class extends WinType {
|
|
|
4104
4207
|
* Calculates wins based on the defined paylines and provided board state.\
|
|
4105
4208
|
* Retrieve the results using `getWins()` after.
|
|
4106
4209
|
*/
|
|
4107
|
-
evaluateWins(board) {
|
|
4210
|
+
evaluateWins(board, opts = {}) {
|
|
4108
4211
|
this.validateConfig();
|
|
4212
|
+
const { jumpGaps = false } = opts;
|
|
4109
4213
|
const waysWins = [];
|
|
4110
4214
|
const reels = board;
|
|
4111
4215
|
const possibleWaysWins = /* @__PURE__ */ new Map();
|
|
4112
4216
|
const candidateSymbols = /* @__PURE__ */ new Map();
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
let hasWild = false;
|
|
4118
|
-
for (const symbol of reel) {
|
|
4119
|
-
candidateSymbols.set(symbol.id, symbol);
|
|
4120
|
-
if (this.isWild(symbol)) {
|
|
4121
|
-
hasWild = true;
|
|
4217
|
+
if (jumpGaps) {
|
|
4218
|
+
for (const reel of reels) {
|
|
4219
|
+
for (const symbol of reel) {
|
|
4220
|
+
candidateSymbols.set(symbol.id, symbol);
|
|
4122
4221
|
}
|
|
4123
4222
|
}
|
|
4124
|
-
|
|
4125
|
-
|
|
4223
|
+
} else {
|
|
4224
|
+
let searchReelIdx = 0;
|
|
4225
|
+
let searchActive = true;
|
|
4226
|
+
while (searchActive && searchReelIdx < reels.length) {
|
|
4227
|
+
const reel = reels[searchReelIdx];
|
|
4228
|
+
let hasWild = false;
|
|
4229
|
+
for (const symbol of reel) {
|
|
4230
|
+
candidateSymbols.set(symbol.id, symbol);
|
|
4231
|
+
if (this.isWild(symbol)) {
|
|
4232
|
+
hasWild = true;
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
if (!hasWild) {
|
|
4236
|
+
searchActive = false;
|
|
4237
|
+
}
|
|
4238
|
+
searchReelIdx++;
|
|
4126
4239
|
}
|
|
4127
|
-
searchReelIdx++;
|
|
4128
4240
|
}
|
|
4129
4241
|
for (const baseSymbol of candidateSymbols.values()) {
|
|
4130
4242
|
let symbolList = {};
|
|
@@ -4140,7 +4252,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
4140
4252
|
symbolList[ridx].push({ reel: ridx, row: sidx, symbol });
|
|
4141
4253
|
}
|
|
4142
4254
|
}
|
|
4143
|
-
if (!symbolList[ridx]) {
|
|
4255
|
+
if (!symbolList[ridx] && !jumpGaps) {
|
|
4144
4256
|
isInterrupted = true;
|
|
4145
4257
|
break;
|
|
4146
4258
|
}
|
|
@@ -4156,7 +4268,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
4156
4268
|
for (const [baseSymbolId, symbolList] of possibleWaysWins.entries()) {
|
|
4157
4269
|
const wayLength = this.getWayLength(symbolList);
|
|
4158
4270
|
let baseSymbol = Object.values(symbolList).flatMap((l) => l.map((s) => s)).find((s) => !this.isWild(s.symbol))?.symbol;
|
|
4159
|
-
if (!baseSymbol) baseSymbol = symbolList[0][0].symbol;
|
|
4271
|
+
if (!baseSymbol) baseSymbol = symbolList[Object.keys(symbolList)[0]][0].symbol;
|
|
4160
4272
|
const singleWayPayout = this.getSymbolPayout(baseSymbol, wayLength);
|
|
4161
4273
|
const totalWays = Object.values(symbolList).reduce(
|
|
4162
4274
|
(ways, syms) => ways * syms.length,
|
|
@@ -4190,7 +4302,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
4190
4302
|
return this;
|
|
4191
4303
|
}
|
|
4192
4304
|
getWayLength(symbolList) {
|
|
4193
|
-
return
|
|
4305
|
+
return Object.keys(symbolList).length;
|
|
4194
4306
|
}
|
|
4195
4307
|
};
|
|
4196
4308
|
|
|
@@ -4652,6 +4764,12 @@ var StandaloneBoard = class {
|
|
|
4652
4764
|
removeSymbol(reelIndex, rowIndex) {
|
|
4653
4765
|
this.board.removeSymbol(reelIndex, rowIndex);
|
|
4654
4766
|
}
|
|
4767
|
+
/**
|
|
4768
|
+
* Updates properties of the symbol at the specified reel and row index.
|
|
4769
|
+
*/
|
|
4770
|
+
updateSymbol(reelIndex, rowIndex, properties) {
|
|
4771
|
+
this.board.updateSymbol(reelIndex, rowIndex, properties);
|
|
4772
|
+
}
|
|
4655
4773
|
resetReels() {
|
|
4656
4774
|
this.board.resetReels({
|
|
4657
4775
|
ctx: this.ctx
|