@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.d.mts
CHANGED
|
@@ -82,7 +82,6 @@ declare class ReelGenerator {
|
|
|
82
82
|
max?: number | Record<string, number>;
|
|
83
83
|
}>;
|
|
84
84
|
protected readonly symbolQuotas?: Record<string, number | Record<string, number>>;
|
|
85
|
-
outputDir: string;
|
|
86
85
|
csvPath: string;
|
|
87
86
|
overrideExisting: boolean;
|
|
88
87
|
rng: RandomNumberGenerator;
|
|
@@ -117,11 +116,6 @@ interface ReelGeneratorOpts {
|
|
|
117
116
|
* Default is 250, but can be adjusted as needed.
|
|
118
117
|
*/
|
|
119
118
|
rowsAmount?: number;
|
|
120
|
-
/**
|
|
121
|
-
* The directory where the generated reelset files will be saved.\
|
|
122
|
-
* **It's recommended to just use `__dirname`**!
|
|
123
|
-
*/
|
|
124
|
-
outputDir: string;
|
|
125
119
|
/**
|
|
126
120
|
* Prevent the same symbol from appearing directly above or below itself.\
|
|
127
121
|
* This can be a single number for all symbols, or a mapping of symbol IDs to
|
package/dist/index.d.ts
CHANGED
|
@@ -82,7 +82,6 @@ declare class ReelGenerator {
|
|
|
82
82
|
max?: number | Record<string, number>;
|
|
83
83
|
}>;
|
|
84
84
|
protected readonly symbolQuotas?: Record<string, number | Record<string, number>>;
|
|
85
|
-
outputDir: string;
|
|
86
85
|
csvPath: string;
|
|
87
86
|
overrideExisting: boolean;
|
|
88
87
|
rng: RandomNumberGenerator;
|
|
@@ -117,11 +116,6 @@ interface ReelGeneratorOpts {
|
|
|
117
116
|
* Default is 250, but can be adjusted as needed.
|
|
118
117
|
*/
|
|
119
118
|
rowsAmount?: number;
|
|
120
|
-
/**
|
|
121
|
-
* The directory where the generated reelset files will be saved.\
|
|
122
|
-
* **It's recommended to just use `__dirname`**!
|
|
123
|
-
*/
|
|
124
|
-
outputDir: string;
|
|
125
119
|
/**
|
|
126
120
|
* Prevent the same symbol from appearing directly above or below itself.\
|
|
127
121
|
* This can be a single number for all symbols, or a mapping of symbol IDs to
|
package/dist/index.js
CHANGED
|
@@ -96,7 +96,6 @@ var GameConfig = class _GameConfig {
|
|
|
96
96
|
if (mode.reelSets && mode.reelSets.length > 0) {
|
|
97
97
|
for (const reelGenerator of Object.values(mode.reelSets)) {
|
|
98
98
|
reelGenerator.associatedGameModeName = mode.name;
|
|
99
|
-
reelGenerator.outputDir = this.config.outputDir;
|
|
100
99
|
reelGenerator.generateReels(this);
|
|
101
100
|
}
|
|
102
101
|
} else {
|
|
@@ -159,6 +158,7 @@ var GameConfig = class _GameConfig {
|
|
|
159
158
|
};
|
|
160
159
|
|
|
161
160
|
// src/GameMode.ts
|
|
161
|
+
var import_assert2 = __toESM(require("assert"));
|
|
162
162
|
var GameMode = class {
|
|
163
163
|
name;
|
|
164
164
|
reelsAmount;
|
|
@@ -177,14 +177,12 @@ var GameMode = class {
|
|
|
177
177
|
this.reelSets = opts.reelSets;
|
|
178
178
|
this.resultSets = opts.resultSets;
|
|
179
179
|
this.isBonusBuy = opts.isBonusBuy;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
throw new Error("GameMode must have at least one ResultSet defined.");
|
|
187
|
-
}
|
|
180
|
+
(0, import_assert2.default)(this.rtp >= 0.9 && this.rtp <= 0.99, "RTP must be between 0.9 and 0.99");
|
|
181
|
+
(0, import_assert2.default)(
|
|
182
|
+
this.symbolsPerReel.length === this.reelsAmount,
|
|
183
|
+
"symbolsPerReel length must match reelsAmount."
|
|
184
|
+
);
|
|
185
|
+
(0, import_assert2.default)(this.reelSets.length > 0, "GameMode must have at least one ReelSet defined.");
|
|
188
186
|
}
|
|
189
187
|
};
|
|
190
188
|
|
|
@@ -395,7 +393,6 @@ var ReelGenerator = class {
|
|
|
395
393
|
preferStackedSymbols;
|
|
396
394
|
symbolStacks;
|
|
397
395
|
symbolQuotas;
|
|
398
|
-
outputDir = "";
|
|
399
396
|
csvPath = "";
|
|
400
397
|
overrideExisting;
|
|
401
398
|
rng;
|
|
@@ -403,7 +400,6 @@ var ReelGenerator = class {
|
|
|
403
400
|
this.id = opts.id;
|
|
404
401
|
this.symbolWeights = new Map(Object.entries(opts.symbolWeights));
|
|
405
402
|
this.rowsAmount = opts.rowsAmount || 250;
|
|
406
|
-
this.outputDir = opts.outputDir;
|
|
407
403
|
if (opts.limitSymbolsToReels) this.limitSymbolsToReels = opts.limitSymbolsToReels;
|
|
408
404
|
this.overrideExisting = opts.overrideExisting || false;
|
|
409
405
|
this.spaceBetweenSameSymbols = opts.spaceBetweenSameSymbols;
|
|
@@ -432,34 +428,16 @@ var ReelGenerator = class {
|
|
|
432
428
|
this.rng.setSeed(opts.seed ?? 0);
|
|
433
429
|
}
|
|
434
430
|
validateConfig({ config }) {
|
|
435
|
-
|
|
436
|
-
if (!
|
|
431
|
+
this.symbolWeights.forEach((_, symbol) => {
|
|
432
|
+
if (!config.symbols.has(symbol)) {
|
|
437
433
|
throw new Error(
|
|
438
|
-
|
|
439
|
-
`Symbol "${symbol.id}" is not defined in the symbol weights of the reel generator ${this.id} for mode ${this.associatedGameModeName}.`,
|
|
440
|
-
`Please ensure all symbols have weights defined.
|
|
441
|
-
`
|
|
442
|
-
].join(" ")
|
|
434
|
+
`Symbol "${symbol}" of the reel generator ${this.id} for mode ${this.associatedGameModeName} is not defined in the game config`
|
|
443
435
|
);
|
|
444
436
|
}
|
|
445
437
|
});
|
|
446
|
-
for (const [symbolId, weight] of this.symbolWeights.entries()) {
|
|
447
|
-
if (!config.symbols.has(symbolId)) {
|
|
448
|
-
throw new Error(
|
|
449
|
-
[
|
|
450
|
-
`Symbol "${symbolId}" is defined in the reel generator's symbol weights, but does not exist in the game config.`,
|
|
451
|
-
`Please ensure all symbols in the reel generator are defined in the game config.
|
|
452
|
-
`
|
|
453
|
-
].join(" ")
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
438
|
if (this.limitSymbolsToReels && Object.keys(this.limitSymbolsToReels).length == 0) {
|
|
458
439
|
this.limitSymbolsToReels = void 0;
|
|
459
440
|
}
|
|
460
|
-
if (this.outputDir === "") {
|
|
461
|
-
throw new Error("Output directory must be specified for the ReelGenerator.");
|
|
462
|
-
}
|
|
463
441
|
}
|
|
464
442
|
isSymbolAllowedOnReel(symbolId, reelIdx) {
|
|
465
443
|
if (!this.limitSymbolsToReels) return true;
|
|
@@ -539,6 +517,15 @@ var ReelGenerator = class {
|
|
|
539
517
|
);
|
|
540
518
|
this.csvPath = filePath;
|
|
541
519
|
const exists = import_fs2.default.existsSync(filePath);
|
|
520
|
+
if (exists && !this.overrideExisting) {
|
|
521
|
+
this.reels = this.parseReelsetCSV(filePath, gameConf);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (!exists && this.symbolWeights.size === 0) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
`Cannot generate reels for generator "${this.id}" of mode "${this.associatedGameModeName}" because the symbol weights are empty.`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
542
529
|
const reelsAmount = gameMode.reelsAmount;
|
|
543
530
|
const weightsObj = Object.fromEntries(this.symbolWeights);
|
|
544
531
|
for (let ridx = 0; ridx < reelsAmount; ridx++) {
|
|
@@ -673,16 +660,12 @@ var ReelGenerator = class {
|
|
|
673
660
|
}
|
|
674
661
|
const csvString = csvRows.map((row) => row.join(",")).join("\n");
|
|
675
662
|
if (import_worker_threads.isMainThread) {
|
|
676
|
-
createDirIfNotExists(this.outputDir);
|
|
677
663
|
import_fs2.default.writeFileSync(filePath, csvString);
|
|
678
664
|
this.reels = this.parseReelsetCSV(filePath, gameConf);
|
|
679
665
|
console.log(
|
|
680
666
|
`Generated reelset ${this.id} for game mode ${this.associatedGameModeName}`
|
|
681
667
|
);
|
|
682
668
|
}
|
|
683
|
-
if (exists) {
|
|
684
|
-
this.reels = this.parseReelsetCSV(filePath, gameConf);
|
|
685
|
-
}
|
|
686
669
|
}
|
|
687
670
|
/**
|
|
688
671
|
* Reads a reelset CSV file and returns the reels as arrays of GameSymbols.
|
|
@@ -706,11 +689,21 @@ var ReelGenerator = class {
|
|
|
706
689
|
reels[ridx].push(symbol);
|
|
707
690
|
});
|
|
708
691
|
});
|
|
692
|
+
const reelLengths = reels.map((r) => r.length);
|
|
693
|
+
const uniqueLengths = new Set(reelLengths);
|
|
694
|
+
if (uniqueLengths.size > 1) {
|
|
695
|
+
throw new Error(
|
|
696
|
+
`Inconsistent reel lengths in reelset CSV at ${reelSetPath}: ${[
|
|
697
|
+
...uniqueLengths
|
|
698
|
+
].join(", ")}`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
709
701
|
return reels;
|
|
710
702
|
}
|
|
711
703
|
};
|
|
712
704
|
|
|
713
705
|
// src/ResultSet.ts
|
|
706
|
+
var import_assert3 = __toESM(require("assert"));
|
|
714
707
|
var ResultSet = class {
|
|
715
708
|
criteria;
|
|
716
709
|
quota;
|
|
@@ -729,16 +722,11 @@ var ResultSet = class {
|
|
|
729
722
|
this.forceMaxWin = opts.forceMaxWin;
|
|
730
723
|
this.forceFreespins = opts.forceFreespins;
|
|
731
724
|
this.evaluate = opts.evaluate;
|
|
732
|
-
if (this.quota < 0 || this.quota > 1) {
|
|
733
|
-
throw new Error(`Quota must be a float between 0 and 1, got ${this.quota}.`);
|
|
734
|
-
}
|
|
735
725
|
}
|
|
736
726
|
static assignCriteriaToSimulations(ctx, gameModeName) {
|
|
737
727
|
const rng = new RandomNumberGenerator();
|
|
738
728
|
rng.setSeed(0);
|
|
739
|
-
|
|
740
|
-
throw new Error("Simulation configuration is not set.");
|
|
741
|
-
}
|
|
729
|
+
(0, import_assert3.default)(ctx.simRunsAmount, "Simulation configuration is not set.");
|
|
742
730
|
const simNums = ctx.simRunsAmount[gameModeName];
|
|
743
731
|
const resultSets = ctx.gameConfig.config.gameModes[gameModeName]?.resultSets;
|
|
744
732
|
if (!resultSets || resultSets.length === 0) {
|
|
@@ -747,8 +735,12 @@ var ResultSet = class {
|
|
|
747
735
|
if (simNums === void 0 || simNums <= 0) {
|
|
748
736
|
throw new Error(`No simulations configured for game mode "${gameModeName}".`);
|
|
749
737
|
}
|
|
738
|
+
const totalQuota = resultSets.reduce((sum, rs) => sum + rs.quota, 0);
|
|
750
739
|
const numberOfSimsForCriteria = Object.fromEntries(
|
|
751
|
-
resultSets.map((rs) =>
|
|
740
|
+
resultSets.map((rs) => {
|
|
741
|
+
const normalizedQuota = totalQuota > 0 ? rs.quota / totalQuota : 0;
|
|
742
|
+
return [rs.criteria, Math.max(Math.floor(normalizedQuota * simNums), 1)];
|
|
743
|
+
})
|
|
752
744
|
);
|
|
753
745
|
let totalSims = Object.values(numberOfSimsForCriteria).reduce(
|
|
754
746
|
(sum, num) => sum + num,
|
|
@@ -1819,7 +1811,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
1819
1811
|
};
|
|
1820
1812
|
|
|
1821
1813
|
// src/optimizer/OptimizationConditions.ts
|
|
1822
|
-
var
|
|
1814
|
+
var import_assert4 = __toESM(require("assert"));
|
|
1823
1815
|
var OptimizationConditions = class {
|
|
1824
1816
|
rtp;
|
|
1825
1817
|
avgWin;
|
|
@@ -1830,14 +1822,14 @@ var OptimizationConditions = class {
|
|
|
1830
1822
|
constructor(opts) {
|
|
1831
1823
|
let { rtp, avgWin, hitRate, searchConditions, priority } = opts;
|
|
1832
1824
|
if (rtp == void 0 || rtp === "x") {
|
|
1833
|
-
(0,
|
|
1825
|
+
(0, import_assert4.default)(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
|
|
1834
1826
|
rtp = Math.round(avgWin / Number(hitRate) * 1e5) / 1e5;
|
|
1835
1827
|
}
|
|
1836
1828
|
let noneCount = 0;
|
|
1837
1829
|
for (const val of [rtp, avgWin, hitRate]) {
|
|
1838
1830
|
if (val === void 0) noneCount++;
|
|
1839
1831
|
}
|
|
1840
|
-
(0,
|
|
1832
|
+
(0, import_assert4.default)(noneCount <= 1, "Invalid combination of optimization conditions.");
|
|
1841
1833
|
this.searchRange = [-1, -1];
|
|
1842
1834
|
this.forceSearch = {};
|
|
1843
1835
|
if (typeof searchConditions === "number") {
|
|
@@ -1918,7 +1910,7 @@ var OptimizationParameters = class _OptimizationParameters {
|
|
|
1918
1910
|
// src/Simulation.ts
|
|
1919
1911
|
var import_fs3 = __toESM(require("fs"));
|
|
1920
1912
|
var import_path2 = __toESM(require("path"));
|
|
1921
|
-
var
|
|
1913
|
+
var import_assert5 = __toESM(require("assert"));
|
|
1922
1914
|
var import_zlib = __toESM(require("zlib"));
|
|
1923
1915
|
var import_esbuild = require("esbuild");
|
|
1924
1916
|
var import_worker_threads2 = require("worker_threads");
|
|
@@ -1942,7 +1934,7 @@ var Simulation = class _Simulation {
|
|
|
1942
1934
|
this.library = /* @__PURE__ */ new Map();
|
|
1943
1935
|
this.records = [];
|
|
1944
1936
|
const gameModeKeys = Object.keys(this.gameConfig.config.gameModes);
|
|
1945
|
-
(0,
|
|
1937
|
+
(0, import_assert5.default)(
|
|
1946
1938
|
Object.values(this.gameConfig.config.gameModes).map((m) => gameModeKeys.includes(m.name)).every((v) => v === true),
|
|
1947
1939
|
"Game mode name must match its key in the gameModes object."
|
|
1948
1940
|
);
|
|
@@ -2348,7 +2340,7 @@ var SimulationContext = class extends Board {
|
|
|
2348
2340
|
// src/analysis/index.ts
|
|
2349
2341
|
var import_fs4 = __toESM(require("fs"));
|
|
2350
2342
|
var import_path3 = __toESM(require("path"));
|
|
2351
|
-
var
|
|
2343
|
+
var import_assert6 = __toESM(require("assert"));
|
|
2352
2344
|
|
|
2353
2345
|
// src/analysis/utils.ts
|
|
2354
2346
|
function parseLookupTable(content) {
|
|
@@ -2513,7 +2505,7 @@ var Analysis = class {
|
|
|
2513
2505
|
booksJsonlCompressed
|
|
2514
2506
|
};
|
|
2515
2507
|
for (const p of Object.values(paths[modeStr])) {
|
|
2516
|
-
(0,
|
|
2508
|
+
(0, import_assert6.default)(
|
|
2517
2509
|
import_fs4.default.existsSync(p),
|
|
2518
2510
|
`File "${p}" does not exist. Run optimization to auto-create it.`
|
|
2519
2511
|
);
|
|
@@ -2585,7 +2577,7 @@ var Analysis = class {
|
|
|
2585
2577
|
}
|
|
2586
2578
|
getGameModeConfig(mode) {
|
|
2587
2579
|
const config = this.gameConfig.gameModes[mode];
|
|
2588
|
-
(0,
|
|
2580
|
+
(0, import_assert6.default)(config, `Game mode "${mode}" not found in game config`);
|
|
2589
2581
|
return config;
|
|
2590
2582
|
}
|
|
2591
2583
|
};
|
|
@@ -2689,7 +2681,7 @@ function makeSetupFile(optimizer, gameMode) {
|
|
|
2689
2681
|
// src/optimizer/index.ts
|
|
2690
2682
|
var import_child_process = require("child_process");
|
|
2691
2683
|
var import_path6 = __toESM(require("path"));
|
|
2692
|
-
var
|
|
2684
|
+
var import_assert7 = __toESM(require("assert"));
|
|
2693
2685
|
var import_worker_threads4 = require("worker_threads");
|
|
2694
2686
|
var Optimizer = class {
|
|
2695
2687
|
gameConfig;
|
|
@@ -2733,7 +2725,7 @@ var Optimizer = class {
|
|
|
2733
2725
|
}
|
|
2734
2726
|
}
|
|
2735
2727
|
const criteria = configMode.resultSets.map((r) => r.criteria);
|
|
2736
|
-
(0,
|
|
2728
|
+
(0, import_assert7.default)(
|
|
2737
2729
|
conditions.every((c) => criteria.includes(c)),
|
|
2738
2730
|
`Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
|
|
2739
2731
|
);
|
|
@@ -2745,7 +2737,7 @@ var Optimizer = class {
|
|
|
2745
2737
|
}
|
|
2746
2738
|
gameModeRtp = Math.round(gameModeRtp * 1e3) / 1e3;
|
|
2747
2739
|
paramRtp = Math.round(paramRtp * 1e3) / 1e3;
|
|
2748
|
-
(0,
|
|
2740
|
+
(0, import_assert7.default)(
|
|
2749
2741
|
gameModeRtp === paramRtp,
|
|
2750
2742
|
`Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode "${k}".`
|
|
2751
2743
|
);
|