@slot-engine/core 0.1.1 → 0.1.2
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 +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +120 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +120 -39
- package/dist/index.mjs.map +1 -1
- package/dist/optimizer-rust/src/exes.rs +2 -0
- package/dist/optimizer-rust/src/main.rs +12 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -24,7 +24,8 @@ function createGameConfig(opts) {
|
|
|
24
24
|
[SPIN_TYPE.BASE_GAME]: getAnticipationTrigger(SPIN_TYPE.BASE_GAME),
|
|
25
25
|
[SPIN_TYPE.FREE_SPINS]: getAnticipationTrigger(SPIN_TYPE.FREE_SPINS)
|
|
26
26
|
},
|
|
27
|
-
outputDir: "__build__"
|
|
27
|
+
outputDir: "__build__",
|
|
28
|
+
rootDir: opts.rootDir || process.cwd()
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -1148,6 +1149,7 @@ var Book = class _Book {
|
|
|
1148
1149
|
freespinsWins = 0;
|
|
1149
1150
|
constructor(opts) {
|
|
1150
1151
|
this.id = opts.id;
|
|
1152
|
+
this.criteria = opts.criteria;
|
|
1151
1153
|
}
|
|
1152
1154
|
/**
|
|
1153
1155
|
* Intended for internal use only.
|
|
@@ -1469,10 +1471,14 @@ Simulating game mode: ${mode}`);
|
|
|
1469
1471
|
const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
|
|
1470
1472
|
await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
|
|
1471
1473
|
createDirIfNotExists(
|
|
1472
|
-
path.join(
|
|
1474
|
+
path.join(
|
|
1475
|
+
this.gameConfig.rootDir,
|
|
1476
|
+
this.gameConfig.outputDir,
|
|
1477
|
+
"optimization_files"
|
|
1478
|
+
)
|
|
1473
1479
|
);
|
|
1474
1480
|
createDirIfNotExists(
|
|
1475
|
-
path.join(
|
|
1481
|
+
path.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "publish_files")
|
|
1476
1482
|
);
|
|
1477
1483
|
this.writeLookupTableCSV(mode);
|
|
1478
1484
|
this.writeLookupTableSegmentedCSV(mode);
|
|
@@ -1525,7 +1531,7 @@ Simulating game mode: ${mode}`);
|
|
|
1525
1531
|
await Promise.all(
|
|
1526
1532
|
simRangesPerChunk.map(([simStart, simEnd], index) => {
|
|
1527
1533
|
return this.callWorker({
|
|
1528
|
-
basePath: this.gameConfig.outputDir,
|
|
1534
|
+
basePath: path.join(this.gameConfig.rootDir, this.gameConfig.outputDir),
|
|
1529
1535
|
mode,
|
|
1530
1536
|
simStart,
|
|
1531
1537
|
simEnd,
|
|
@@ -1548,7 +1554,7 @@ Simulating game mode: ${mode}`);
|
|
|
1548
1554
|
}
|
|
1549
1555
|
}
|
|
1550
1556
|
return new Promise((resolve, reject) => {
|
|
1551
|
-
const scriptPath = path.join(
|
|
1557
|
+
const scriptPath = path.join(basePath, TEMP_FILENAME);
|
|
1552
1558
|
const worker = new Worker(scriptPath, {
|
|
1553
1559
|
workerData: {
|
|
1554
1560
|
mode,
|
|
@@ -1598,10 +1604,10 @@ Simulating game mode: ${mode}`);
|
|
|
1598
1604
|
ctx.state.currentGameMode,
|
|
1599
1605
|
criteria
|
|
1600
1606
|
);
|
|
1607
|
+
ctx.state.currentResultSet = resultSet;
|
|
1601
1608
|
while (!ctx.state.isCriteriaMet) {
|
|
1602
1609
|
this.actualSims++;
|
|
1603
1610
|
this.resetSimulation(ctx);
|
|
1604
|
-
ctx.state.currentResultSet = resultSet;
|
|
1605
1611
|
this.handleGameFlow(ctx);
|
|
1606
1612
|
if (resultSet.meetsCriteria(ctx)) {
|
|
1607
1613
|
ctx.state.isCriteriaMet = true;
|
|
@@ -1676,10 +1682,19 @@ Simulating game mode: ${mode}`);
|
|
|
1676
1682
|
}
|
|
1677
1683
|
rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
|
|
1678
1684
|
let outputFileName = `lookUpTable_${gameMode}.csv`;
|
|
1679
|
-
let outputFilePath = path.join(
|
|
1685
|
+
let outputFilePath = path.join(
|
|
1686
|
+
this.gameConfig.rootDir,
|
|
1687
|
+
this.gameConfig.outputDir,
|
|
1688
|
+
outputFileName
|
|
1689
|
+
);
|
|
1680
1690
|
writeFile(outputFilePath, rows.join("\n"));
|
|
1681
1691
|
outputFileName = `lookUpTable_${gameMode}_0.csv`;
|
|
1682
|
-
outputFilePath = path.join(
|
|
1692
|
+
outputFilePath = path.join(
|
|
1693
|
+
this.gameConfig.rootDir,
|
|
1694
|
+
this.gameConfig.outputDir,
|
|
1695
|
+
"publish_files",
|
|
1696
|
+
outputFileName
|
|
1697
|
+
);
|
|
1683
1698
|
writeFile(outputFilePath, rows.join("\n"));
|
|
1684
1699
|
return outputFilePath;
|
|
1685
1700
|
}
|
|
@@ -1693,20 +1708,28 @@ Simulating game mode: ${mode}`);
|
|
|
1693
1708
|
}
|
|
1694
1709
|
rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
|
|
1695
1710
|
const outputFileName = `lookUpTableSegmented_${gameMode}.csv`;
|
|
1696
|
-
const outputFilePath = path.join(
|
|
1711
|
+
const outputFilePath = path.join(
|
|
1712
|
+
this.gameConfig.rootDir,
|
|
1713
|
+
this.gameConfig.outputDir,
|
|
1714
|
+
outputFileName
|
|
1715
|
+
);
|
|
1697
1716
|
writeFile(outputFilePath, rows.join("\n"));
|
|
1698
1717
|
return outputFilePath;
|
|
1699
1718
|
}
|
|
1700
1719
|
writeRecords(gameMode) {
|
|
1701
1720
|
const outputFileName = `force_record_${gameMode}.json`;
|
|
1702
|
-
const outputFilePath = path.join(
|
|
1721
|
+
const outputFilePath = path.join(
|
|
1722
|
+
this.gameConfig.rootDir,
|
|
1723
|
+
this.gameConfig.outputDir,
|
|
1724
|
+
outputFileName
|
|
1725
|
+
);
|
|
1703
1726
|
writeFile(outputFilePath, JSON.stringify(this.recorder.records, null, 2));
|
|
1704
1727
|
if (this.debug) this.logSymbolOccurrences();
|
|
1705
1728
|
return outputFilePath;
|
|
1706
1729
|
}
|
|
1707
1730
|
writeIndexJson() {
|
|
1708
1731
|
const outputFilePath = path.join(
|
|
1709
|
-
|
|
1732
|
+
this.gameConfig.rootDir,
|
|
1710
1733
|
this.gameConfig.outputDir,
|
|
1711
1734
|
"publish_files",
|
|
1712
1735
|
"index.json"
|
|
@@ -1725,7 +1748,11 @@ Simulating game mode: ${mode}`);
|
|
|
1725
1748
|
}
|
|
1726
1749
|
async writeBooksJson(gameMode) {
|
|
1727
1750
|
const outputFileName = `books_${gameMode}.jsonl`;
|
|
1728
|
-
const outputFilePath = path.join(
|
|
1751
|
+
const outputFilePath = path.join(
|
|
1752
|
+
this.gameConfig.rootDir,
|
|
1753
|
+
this.gameConfig.outputDir,
|
|
1754
|
+
outputFileName
|
|
1755
|
+
);
|
|
1729
1756
|
const books = Array.from(this.library.values()).map((b) => b.serialize()).map((b) => ({
|
|
1730
1757
|
id: b.id,
|
|
1731
1758
|
payoutMultiplier: b.payout,
|
|
@@ -1735,7 +1762,7 @@ Simulating game mode: ${mode}`);
|
|
|
1735
1762
|
writeFile(outputFilePath, contents);
|
|
1736
1763
|
const compressedFileName = `books_${gameMode}.jsonl.zst`;
|
|
1737
1764
|
const compressedFilePath = path.join(
|
|
1738
|
-
|
|
1765
|
+
this.gameConfig.rootDir,
|
|
1739
1766
|
this.gameConfig.outputDir,
|
|
1740
1767
|
"publish_files",
|
|
1741
1768
|
compressedFileName
|
|
@@ -1773,13 +1800,21 @@ Simulating game mode: ${mode}`);
|
|
|
1773
1800
|
* Compiles user configured game to JS for use in different Node processes
|
|
1774
1801
|
*/
|
|
1775
1802
|
preprocessFiles() {
|
|
1776
|
-
const builtFilePath = path.join(
|
|
1803
|
+
const builtFilePath = path.join(
|
|
1804
|
+
this.gameConfig.rootDir,
|
|
1805
|
+
this.gameConfig.outputDir,
|
|
1806
|
+
TEMP_FILENAME
|
|
1807
|
+
);
|
|
1777
1808
|
fs2.rmSync(builtFilePath, { force: true });
|
|
1778
1809
|
buildSync({
|
|
1779
|
-
entryPoints: [
|
|
1810
|
+
entryPoints: [this.gameConfig.rootDir],
|
|
1780
1811
|
bundle: true,
|
|
1781
1812
|
platform: "node",
|
|
1782
|
-
outfile: path.join(
|
|
1813
|
+
outfile: path.join(
|
|
1814
|
+
this.gameConfig.rootDir,
|
|
1815
|
+
this.gameConfig.outputDir,
|
|
1816
|
+
TEMP_FILENAME
|
|
1817
|
+
),
|
|
1783
1818
|
external: ["esbuild"]
|
|
1784
1819
|
});
|
|
1785
1820
|
}
|
|
@@ -1890,6 +1925,18 @@ function parseLookupTable(content) {
|
|
|
1890
1925
|
}
|
|
1891
1926
|
return lut;
|
|
1892
1927
|
}
|
|
1928
|
+
function parseLookupTableSegmented(content) {
|
|
1929
|
+
const lines = content.trim().split("\n");
|
|
1930
|
+
const lut = [];
|
|
1931
|
+
for (const line of lines) {
|
|
1932
|
+
const [indexStr, criteria, weightStr, payoutStr] = line.split(",");
|
|
1933
|
+
const index = parseInt(indexStr.trim());
|
|
1934
|
+
const weight = parseInt(weightStr.trim());
|
|
1935
|
+
const payout = parseFloat(payoutStr.trim());
|
|
1936
|
+
lut.push([index, criteria, weight, payout]);
|
|
1937
|
+
}
|
|
1938
|
+
return lut;
|
|
1939
|
+
}
|
|
1893
1940
|
function getTotalLutWeight(lut) {
|
|
1894
1941
|
return lut.reduce((sum, [, weight]) => sum + weight, 0);
|
|
1895
1942
|
}
|
|
@@ -2002,7 +2049,7 @@ var Analysis = class {
|
|
|
2002
2049
|
console.log("Analysis complete. Files written to build directory.");
|
|
2003
2050
|
}
|
|
2004
2051
|
getPathsForModes(gameModes) {
|
|
2005
|
-
const rootPath =
|
|
2052
|
+
const rootPath = this.gameConfig.rootDir;
|
|
2006
2053
|
const paths = {};
|
|
2007
2054
|
for (const modeStr of gameModes) {
|
|
2008
2055
|
const lut = path2.join(
|
|
@@ -2074,7 +2121,7 @@ var Analysis = class {
|
|
|
2074
2121
|
});
|
|
2075
2122
|
}
|
|
2076
2123
|
writeJsonFile(
|
|
2077
|
-
path2.join(
|
|
2124
|
+
path2.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "stats_summary.json"),
|
|
2078
2125
|
stats
|
|
2079
2126
|
);
|
|
2080
2127
|
}
|
|
@@ -2103,35 +2150,76 @@ var Analysis = class {
|
|
|
2103
2150
|
];
|
|
2104
2151
|
const payoutRanges = {};
|
|
2105
2152
|
for (const modeStr of gameModes) {
|
|
2106
|
-
payoutRanges[modeStr] = {};
|
|
2153
|
+
payoutRanges[modeStr] = { overall: {}, criteria: {} };
|
|
2107
2154
|
const lutOptimized = parseLookupTable(
|
|
2108
2155
|
fs3.readFileSync(this.filePaths[modeStr].lutOptimized, "utf-8")
|
|
2109
2156
|
);
|
|
2157
|
+
const lutSegmented = parseLookupTableSegmented(
|
|
2158
|
+
fs3.readFileSync(this.filePaths[modeStr].lutSegmented, "utf-8")
|
|
2159
|
+
);
|
|
2110
2160
|
lutOptimized.forEach(([, , p]) => {
|
|
2111
2161
|
const payout = p / 100;
|
|
2112
2162
|
for (const [min, max] of winRanges) {
|
|
2113
2163
|
if (payout >= min && payout <= max) {
|
|
2114
2164
|
const rangeKey = `${min}-${max}`;
|
|
2115
|
-
if (!payoutRanges[modeStr][rangeKey]) {
|
|
2116
|
-
payoutRanges[modeStr][rangeKey] = 0;
|
|
2165
|
+
if (!payoutRanges[modeStr].overall[rangeKey]) {
|
|
2166
|
+
payoutRanges[modeStr].overall[rangeKey] = 0;
|
|
2167
|
+
}
|
|
2168
|
+
payoutRanges[modeStr].overall[rangeKey] += 1;
|
|
2169
|
+
break;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
});
|
|
2173
|
+
lutSegmented.forEach(([, criteria, bp, fsp]) => {
|
|
2174
|
+
const basePayout = bp / 100;
|
|
2175
|
+
const freeSpinPayout = fsp / 100;
|
|
2176
|
+
const payout = basePayout + freeSpinPayout;
|
|
2177
|
+
for (const [min, max] of winRanges) {
|
|
2178
|
+
if (payout >= min && payout <= max) {
|
|
2179
|
+
const rangeKey = `${min}-${max}`;
|
|
2180
|
+
if (!payoutRanges[modeStr].overall[rangeKey]) {
|
|
2181
|
+
payoutRanges[modeStr].overall[rangeKey] = 0;
|
|
2182
|
+
}
|
|
2183
|
+
payoutRanges[modeStr].overall[rangeKey] += 1;
|
|
2184
|
+
if (!payoutRanges[modeStr].criteria[criteria]) {
|
|
2185
|
+
payoutRanges[modeStr].criteria[criteria] = {};
|
|
2117
2186
|
}
|
|
2118
|
-
payoutRanges[modeStr][rangeKey]
|
|
2187
|
+
if (!payoutRanges[modeStr].criteria[criteria][rangeKey]) {
|
|
2188
|
+
payoutRanges[modeStr].criteria[criteria][rangeKey] = 0;
|
|
2189
|
+
}
|
|
2190
|
+
payoutRanges[modeStr].criteria[criteria][rangeKey] += 1;
|
|
2119
2191
|
break;
|
|
2120
2192
|
}
|
|
2121
2193
|
}
|
|
2122
2194
|
});
|
|
2123
|
-
const
|
|
2124
|
-
Object.keys(payoutRanges[modeStr]).sort((a, b) => {
|
|
2195
|
+
const orderedOverall = {};
|
|
2196
|
+
Object.keys(payoutRanges[modeStr].overall).sort((a, b) => {
|
|
2125
2197
|
const [aMin] = a.split("-").map(Number);
|
|
2126
2198
|
const [bMin] = b.split("-").map(Number);
|
|
2127
2199
|
return aMin - bMin;
|
|
2128
2200
|
}).forEach((key) => {
|
|
2129
|
-
|
|
2201
|
+
orderedOverall[key] = payoutRanges[modeStr].overall[key];
|
|
2202
|
+
});
|
|
2203
|
+
const orderedCriteria = {};
|
|
2204
|
+
Object.keys(payoutRanges[modeStr].criteria).forEach((crit) => {
|
|
2205
|
+
const critMap = payoutRanges[modeStr].criteria[crit];
|
|
2206
|
+
const orderedCritMap = {};
|
|
2207
|
+
Object.keys(critMap).sort((a, b) => {
|
|
2208
|
+
const [aMin] = a.split("-").map(Number);
|
|
2209
|
+
const [bMin] = b.split("-").map(Number);
|
|
2210
|
+
return aMin - bMin;
|
|
2211
|
+
}).forEach((key) => {
|
|
2212
|
+
orderedCritMap[key] = critMap[key];
|
|
2213
|
+
});
|
|
2214
|
+
orderedCriteria[crit] = orderedCritMap;
|
|
2130
2215
|
});
|
|
2131
|
-
payoutRanges[modeStr] =
|
|
2216
|
+
payoutRanges[modeStr] = {
|
|
2217
|
+
overall: orderedOverall,
|
|
2218
|
+
criteria: {}
|
|
2219
|
+
};
|
|
2132
2220
|
}
|
|
2133
2221
|
writeJsonFile(
|
|
2134
|
-
path2.join(
|
|
2222
|
+
path2.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "stats_payouts.json"),
|
|
2135
2223
|
payoutRanges
|
|
2136
2224
|
);
|
|
2137
2225
|
}
|
|
@@ -2193,7 +2281,7 @@ function makeMathConfig(optimizer, opts = {}) {
|
|
|
2193
2281
|
}))
|
|
2194
2282
|
};
|
|
2195
2283
|
if (writeToFile) {
|
|
2196
|
-
const outPath = path3.join(
|
|
2284
|
+
const outPath = path3.join(game.rootDir, game.outputDir, "math_config.json");
|
|
2197
2285
|
writeJsonFile(outPath, config);
|
|
2198
2286
|
}
|
|
2199
2287
|
return config;
|
|
@@ -2236,7 +2324,7 @@ function makeSetupFile(optimizer, gameMode) {
|
|
|
2236
2324
|
`;
|
|
2237
2325
|
content += `simulation_trials;${params.simulationTrials}
|
|
2238
2326
|
`;
|
|
2239
|
-
content += `user_game_build_path;${path4.join(
|
|
2327
|
+
content += `user_game_build_path;${path4.join(gameConfig.rootDir, gameConfig.outputDir)}
|
|
2240
2328
|
`;
|
|
2241
2329
|
content += `pmb_rtp;${params.pmbRtp}
|
|
2242
2330
|
`;
|
|
@@ -2410,9 +2498,9 @@ var Optimizer = class {
|
|
|
2410
2498
|
}
|
|
2411
2499
|
};
|
|
2412
2500
|
async function rustProgram(...args) {
|
|
2501
|
+
console.log("Starting Rust optimizer. This may take a while...");
|
|
2413
2502
|
return new Promise((resolve, reject) => {
|
|
2414
|
-
const task = spawn("cargo", ["run", "--release", ...args], {
|
|
2415
|
-
shell: true,
|
|
2503
|
+
const task = spawn("cargo", ["run", "-q", "--release", ...args], {
|
|
2416
2504
|
cwd: path5.join(__dirname, "./optimizer-rust"),
|
|
2417
2505
|
stdio: "pipe"
|
|
2418
2506
|
});
|
|
@@ -2878,8 +2966,6 @@ var ClusterWinType = class extends WinType {
|
|
|
2878
2966
|
|
|
2879
2967
|
// src/win-types/ManywaysWinType.ts
|
|
2880
2968
|
var ManywaysWinType = class extends WinType {
|
|
2881
|
-
_checked = [];
|
|
2882
|
-
_checkedWilds = [];
|
|
2883
2969
|
constructor(opts) {
|
|
2884
2970
|
super(opts);
|
|
2885
2971
|
}
|
|
@@ -2977,12 +3063,6 @@ var ManywaysWinType = class extends WinType {
|
|
|
2977
3063
|
getWayLength(symbolList) {
|
|
2978
3064
|
return Math.max(...Object.keys(symbolList).map((k) => parseInt(k, 10))) + 1;
|
|
2979
3065
|
}
|
|
2980
|
-
isChecked(ridx, sidx) {
|
|
2981
|
-
return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
|
|
2982
|
-
}
|
|
2983
|
-
isCheckedWild(ridx, sidx) {
|
|
2984
|
-
return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
|
|
2985
|
-
}
|
|
2986
3066
|
};
|
|
2987
3067
|
|
|
2988
3068
|
// src/reel-set/GeneratedReelSet.ts
|
|
@@ -3186,6 +3266,7 @@ var GeneratedReelSet = class extends ReelSet {
|
|
|
3186
3266
|
);
|
|
3187
3267
|
}
|
|
3188
3268
|
const filePath = path7.join(
|
|
3269
|
+
config.rootDir,
|
|
3189
3270
|
config.outputDir,
|
|
3190
3271
|
`reels_${this.associatedGameModeName}-${this.id}.csv`
|
|
3191
3272
|
);
|