@slot-engine/core 0.0.4 → 0.0.5
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 +0 -6
- package/dist/index.d.ts +0 -6
- package/dist/index.js +46 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46 -54
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -42,7 +42,6 @@ var GameConfig = class _GameConfig {
|
|
|
42
42
|
if (mode.reelSets && mode.reelSets.length > 0) {
|
|
43
43
|
for (const reelGenerator of Object.values(mode.reelSets)) {
|
|
44
44
|
reelGenerator.associatedGameModeName = mode.name;
|
|
45
|
-
reelGenerator.outputDir = this.config.outputDir;
|
|
46
45
|
reelGenerator.generateReels(this);
|
|
47
46
|
}
|
|
48
47
|
} else {
|
|
@@ -105,6 +104,7 @@ var GameConfig = class _GameConfig {
|
|
|
105
104
|
};
|
|
106
105
|
|
|
107
106
|
// src/GameMode.ts
|
|
107
|
+
import assert2 from "assert";
|
|
108
108
|
var GameMode = class {
|
|
109
109
|
name;
|
|
110
110
|
reelsAmount;
|
|
@@ -123,14 +123,12 @@ var GameMode = class {
|
|
|
123
123
|
this.reelSets = opts.reelSets;
|
|
124
124
|
this.resultSets = opts.resultSets;
|
|
125
125
|
this.isBonusBuy = opts.isBonusBuy;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
throw new Error("GameMode must have at least one ResultSet defined.");
|
|
133
|
-
}
|
|
126
|
+
assert2(this.rtp >= 0.9 && this.rtp <= 0.99, "RTP must be between 0.9 and 0.99");
|
|
127
|
+
assert2(
|
|
128
|
+
this.symbolsPerReel.length === this.reelsAmount,
|
|
129
|
+
"symbolsPerReel length must match reelsAmount."
|
|
130
|
+
);
|
|
131
|
+
assert2(this.reelSets.length > 0, "GameMode must have at least one ReelSet defined.");
|
|
134
132
|
}
|
|
135
133
|
};
|
|
136
134
|
|
|
@@ -341,7 +339,6 @@ var ReelGenerator = class {
|
|
|
341
339
|
preferStackedSymbols;
|
|
342
340
|
symbolStacks;
|
|
343
341
|
symbolQuotas;
|
|
344
|
-
outputDir = "";
|
|
345
342
|
csvPath = "";
|
|
346
343
|
overrideExisting;
|
|
347
344
|
rng;
|
|
@@ -349,7 +346,6 @@ var ReelGenerator = class {
|
|
|
349
346
|
this.id = opts.id;
|
|
350
347
|
this.symbolWeights = new Map(Object.entries(opts.symbolWeights));
|
|
351
348
|
this.rowsAmount = opts.rowsAmount || 250;
|
|
352
|
-
this.outputDir = opts.outputDir;
|
|
353
349
|
if (opts.limitSymbolsToReels) this.limitSymbolsToReels = opts.limitSymbolsToReels;
|
|
354
350
|
this.overrideExisting = opts.overrideExisting || false;
|
|
355
351
|
this.spaceBetweenSameSymbols = opts.spaceBetweenSameSymbols;
|
|
@@ -378,34 +374,16 @@ var ReelGenerator = class {
|
|
|
378
374
|
this.rng.setSeed(opts.seed ?? 0);
|
|
379
375
|
}
|
|
380
376
|
validateConfig({ config }) {
|
|
381
|
-
|
|
382
|
-
if (!
|
|
377
|
+
this.symbolWeights.forEach((_, symbol) => {
|
|
378
|
+
if (!config.symbols.has(symbol)) {
|
|
383
379
|
throw new Error(
|
|
384
|
-
|
|
385
|
-
`Symbol "${symbol.id}" is not defined in the symbol weights of the reel generator ${this.id} for mode ${this.associatedGameModeName}.`,
|
|
386
|
-
`Please ensure all symbols have weights defined.
|
|
387
|
-
`
|
|
388
|
-
].join(" ")
|
|
380
|
+
`Symbol "${symbol}" of the reel generator ${this.id} for mode ${this.associatedGameModeName} is not defined in the game config`
|
|
389
381
|
);
|
|
390
382
|
}
|
|
391
383
|
});
|
|
392
|
-
for (const [symbolId, weight] of this.symbolWeights.entries()) {
|
|
393
|
-
if (!config.symbols.has(symbolId)) {
|
|
394
|
-
throw new Error(
|
|
395
|
-
[
|
|
396
|
-
`Symbol "${symbolId}" is defined in the reel generator's symbol weights, but does not exist in the game config.`,
|
|
397
|
-
`Please ensure all symbols in the reel generator are defined in the game config.
|
|
398
|
-
`
|
|
399
|
-
].join(" ")
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
384
|
if (this.limitSymbolsToReels && Object.keys(this.limitSymbolsToReels).length == 0) {
|
|
404
385
|
this.limitSymbolsToReels = void 0;
|
|
405
386
|
}
|
|
406
|
-
if (this.outputDir === "") {
|
|
407
|
-
throw new Error("Output directory must be specified for the ReelGenerator.");
|
|
408
|
-
}
|
|
409
387
|
}
|
|
410
388
|
isSymbolAllowedOnReel(symbolId, reelIdx) {
|
|
411
389
|
if (!this.limitSymbolsToReels) return true;
|
|
@@ -485,6 +463,15 @@ var ReelGenerator = class {
|
|
|
485
463
|
);
|
|
486
464
|
this.csvPath = filePath;
|
|
487
465
|
const exists = fs2.existsSync(filePath);
|
|
466
|
+
if (exists && !this.overrideExisting) {
|
|
467
|
+
this.reels = this.parseReelsetCSV(filePath, gameConf);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (!exists && this.symbolWeights.size === 0) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
`Cannot generate reels for generator "${this.id}" of mode "${this.associatedGameModeName}" because the symbol weights are empty.`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
488
475
|
const reelsAmount = gameMode.reelsAmount;
|
|
489
476
|
const weightsObj = Object.fromEntries(this.symbolWeights);
|
|
490
477
|
for (let ridx = 0; ridx < reelsAmount; ridx++) {
|
|
@@ -619,16 +606,12 @@ var ReelGenerator = class {
|
|
|
619
606
|
}
|
|
620
607
|
const csvString = csvRows.map((row) => row.join(",")).join("\n");
|
|
621
608
|
if (isMainThread) {
|
|
622
|
-
createDirIfNotExists(this.outputDir);
|
|
623
609
|
fs2.writeFileSync(filePath, csvString);
|
|
624
610
|
this.reels = this.parseReelsetCSV(filePath, gameConf);
|
|
625
611
|
console.log(
|
|
626
612
|
`Generated reelset ${this.id} for game mode ${this.associatedGameModeName}`
|
|
627
613
|
);
|
|
628
614
|
}
|
|
629
|
-
if (exists) {
|
|
630
|
-
this.reels = this.parseReelsetCSV(filePath, gameConf);
|
|
631
|
-
}
|
|
632
615
|
}
|
|
633
616
|
/**
|
|
634
617
|
* Reads a reelset CSV file and returns the reels as arrays of GameSymbols.
|
|
@@ -652,11 +635,21 @@ var ReelGenerator = class {
|
|
|
652
635
|
reels[ridx].push(symbol);
|
|
653
636
|
});
|
|
654
637
|
});
|
|
638
|
+
const reelLengths = reels.map((r) => r.length);
|
|
639
|
+
const uniqueLengths = new Set(reelLengths);
|
|
640
|
+
if (uniqueLengths.size > 1) {
|
|
641
|
+
throw new Error(
|
|
642
|
+
`Inconsistent reel lengths in reelset CSV at ${reelSetPath}: ${[
|
|
643
|
+
...uniqueLengths
|
|
644
|
+
].join(", ")}`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
655
647
|
return reels;
|
|
656
648
|
}
|
|
657
649
|
};
|
|
658
650
|
|
|
659
651
|
// src/ResultSet.ts
|
|
652
|
+
import assert3 from "assert";
|
|
660
653
|
var ResultSet = class {
|
|
661
654
|
criteria;
|
|
662
655
|
quota;
|
|
@@ -675,16 +668,11 @@ var ResultSet = class {
|
|
|
675
668
|
this.forceMaxWin = opts.forceMaxWin;
|
|
676
669
|
this.forceFreespins = opts.forceFreespins;
|
|
677
670
|
this.evaluate = opts.evaluate;
|
|
678
|
-
if (this.quota < 0 || this.quota > 1) {
|
|
679
|
-
throw new Error(`Quota must be a float between 0 and 1, got ${this.quota}.`);
|
|
680
|
-
}
|
|
681
671
|
}
|
|
682
672
|
static assignCriteriaToSimulations(ctx, gameModeName) {
|
|
683
673
|
const rng = new RandomNumberGenerator();
|
|
684
674
|
rng.setSeed(0);
|
|
685
|
-
|
|
686
|
-
throw new Error("Simulation configuration is not set.");
|
|
687
|
-
}
|
|
675
|
+
assert3(ctx.simRunsAmount, "Simulation configuration is not set.");
|
|
688
676
|
const simNums = ctx.simRunsAmount[gameModeName];
|
|
689
677
|
const resultSets = ctx.gameConfig.config.gameModes[gameModeName]?.resultSets;
|
|
690
678
|
if (!resultSets || resultSets.length === 0) {
|
|
@@ -693,8 +681,12 @@ var ResultSet = class {
|
|
|
693
681
|
if (simNums === void 0 || simNums <= 0) {
|
|
694
682
|
throw new Error(`No simulations configured for game mode "${gameModeName}".`);
|
|
695
683
|
}
|
|
684
|
+
const totalQuota = resultSets.reduce((sum, rs) => sum + rs.quota, 0);
|
|
696
685
|
const numberOfSimsForCriteria = Object.fromEntries(
|
|
697
|
-
resultSets.map((rs) =>
|
|
686
|
+
resultSets.map((rs) => {
|
|
687
|
+
const normalizedQuota = totalQuota > 0 ? rs.quota / totalQuota : 0;
|
|
688
|
+
return [rs.criteria, Math.max(Math.floor(normalizedQuota * simNums), 1)];
|
|
689
|
+
})
|
|
698
690
|
);
|
|
699
691
|
let totalSims = Object.values(numberOfSimsForCriteria).reduce(
|
|
700
692
|
(sum, num) => sum + num,
|
|
@@ -1765,7 +1757,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
1765
1757
|
};
|
|
1766
1758
|
|
|
1767
1759
|
// src/optimizer/OptimizationConditions.ts
|
|
1768
|
-
import
|
|
1760
|
+
import assert4 from "assert";
|
|
1769
1761
|
var OptimizationConditions = class {
|
|
1770
1762
|
rtp;
|
|
1771
1763
|
avgWin;
|
|
@@ -1776,14 +1768,14 @@ var OptimizationConditions = class {
|
|
|
1776
1768
|
constructor(opts) {
|
|
1777
1769
|
let { rtp, avgWin, hitRate, searchConditions, priority } = opts;
|
|
1778
1770
|
if (rtp == void 0 || rtp === "x") {
|
|
1779
|
-
|
|
1771
|
+
assert4(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
|
|
1780
1772
|
rtp = Math.round(avgWin / Number(hitRate) * 1e5) / 1e5;
|
|
1781
1773
|
}
|
|
1782
1774
|
let noneCount = 0;
|
|
1783
1775
|
for (const val of [rtp, avgWin, hitRate]) {
|
|
1784
1776
|
if (val === void 0) noneCount++;
|
|
1785
1777
|
}
|
|
1786
|
-
|
|
1778
|
+
assert4(noneCount <= 1, "Invalid combination of optimization conditions.");
|
|
1787
1779
|
this.searchRange = [-1, -1];
|
|
1788
1780
|
this.forceSearch = {};
|
|
1789
1781
|
if (typeof searchConditions === "number") {
|
|
@@ -1864,7 +1856,7 @@ var OptimizationParameters = class _OptimizationParameters {
|
|
|
1864
1856
|
// src/Simulation.ts
|
|
1865
1857
|
import fs3 from "fs";
|
|
1866
1858
|
import path2 from "path";
|
|
1867
|
-
import
|
|
1859
|
+
import assert5 from "assert";
|
|
1868
1860
|
import zlib from "zlib";
|
|
1869
1861
|
import { buildSync } from "esbuild";
|
|
1870
1862
|
import { Worker, isMainThread as isMainThread2, parentPort, workerData } from "worker_threads";
|
|
@@ -1888,7 +1880,7 @@ var Simulation = class _Simulation {
|
|
|
1888
1880
|
this.library = /* @__PURE__ */ new Map();
|
|
1889
1881
|
this.records = [];
|
|
1890
1882
|
const gameModeKeys = Object.keys(this.gameConfig.config.gameModes);
|
|
1891
|
-
|
|
1883
|
+
assert5(
|
|
1892
1884
|
Object.values(this.gameConfig.config.gameModes).map((m) => gameModeKeys.includes(m.name)).every((v) => v === true),
|
|
1893
1885
|
"Game mode name must match its key in the gameModes object."
|
|
1894
1886
|
);
|
|
@@ -2294,7 +2286,7 @@ var SimulationContext = class extends Board {
|
|
|
2294
2286
|
// src/analysis/index.ts
|
|
2295
2287
|
import fs4 from "fs";
|
|
2296
2288
|
import path3 from "path";
|
|
2297
|
-
import
|
|
2289
|
+
import assert6 from "assert";
|
|
2298
2290
|
|
|
2299
2291
|
// src/analysis/utils.ts
|
|
2300
2292
|
function parseLookupTable(content) {
|
|
@@ -2459,7 +2451,7 @@ var Analysis = class {
|
|
|
2459
2451
|
booksJsonlCompressed
|
|
2460
2452
|
};
|
|
2461
2453
|
for (const p of Object.values(paths[modeStr])) {
|
|
2462
|
-
|
|
2454
|
+
assert6(
|
|
2463
2455
|
fs4.existsSync(p),
|
|
2464
2456
|
`File "${p}" does not exist. Run optimization to auto-create it.`
|
|
2465
2457
|
);
|
|
@@ -2531,7 +2523,7 @@ var Analysis = class {
|
|
|
2531
2523
|
}
|
|
2532
2524
|
getGameModeConfig(mode) {
|
|
2533
2525
|
const config = this.gameConfig.gameModes[mode];
|
|
2534
|
-
|
|
2526
|
+
assert6(config, `Game mode "${mode}" not found in game config`);
|
|
2535
2527
|
return config;
|
|
2536
2528
|
}
|
|
2537
2529
|
};
|
|
@@ -2635,7 +2627,7 @@ function makeSetupFile(optimizer, gameMode) {
|
|
|
2635
2627
|
// src/optimizer/index.ts
|
|
2636
2628
|
import { spawn } from "child_process";
|
|
2637
2629
|
import path6 from "path";
|
|
2638
|
-
import
|
|
2630
|
+
import assert7 from "assert";
|
|
2639
2631
|
import { isMainThread as isMainThread4 } from "worker_threads";
|
|
2640
2632
|
var Optimizer = class {
|
|
2641
2633
|
gameConfig;
|
|
@@ -2679,7 +2671,7 @@ var Optimizer = class {
|
|
|
2679
2671
|
}
|
|
2680
2672
|
}
|
|
2681
2673
|
const criteria = configMode.resultSets.map((r) => r.criteria);
|
|
2682
|
-
|
|
2674
|
+
assert7(
|
|
2683
2675
|
conditions.every((c) => criteria.includes(c)),
|
|
2684
2676
|
`Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
|
|
2685
2677
|
);
|
|
@@ -2691,7 +2683,7 @@ var Optimizer = class {
|
|
|
2691
2683
|
}
|
|
2692
2684
|
gameModeRtp = Math.round(gameModeRtp * 1e3) / 1e3;
|
|
2693
2685
|
paramRtp = Math.round(paramRtp * 1e3) / 1e3;
|
|
2694
|
-
|
|
2686
|
+
assert7(
|
|
2695
2687
|
gameModeRtp === paramRtp,
|
|
2696
2688
|
`Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode "${k}".`
|
|
2697
2689
|
);
|