@slot-engine/core 0.0.2 → 0.0.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 +58 -66
- package/dist/index.d.ts +58 -66
- package/dist/index.js +73 -101
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +73 -101
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/lib/zstd.exe +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/GameConfig.ts
|
|
2
|
+
import assert from "assert";
|
|
2
3
|
var GameConfig = class _GameConfig {
|
|
3
4
|
config;
|
|
4
5
|
constructor(opts) {
|
|
@@ -22,14 +23,12 @@ var GameConfig = class _GameConfig {
|
|
|
22
23
|
userState: opts.userState,
|
|
23
24
|
outputDir: "__build__"
|
|
24
25
|
};
|
|
25
|
-
for (const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
);
|
|
32
|
-
}
|
|
26
|
+
for (const [key, value] of Object.entries(opts.symbols)) {
|
|
27
|
+
assert(
|
|
28
|
+
value.id === key,
|
|
29
|
+
`Symbol key "${key}" does not match symbol id "${value.id}"`
|
|
30
|
+
);
|
|
31
|
+
this.config.symbols.set(key, value);
|
|
33
32
|
}
|
|
34
33
|
function getAnticipationTrigger(spinType) {
|
|
35
34
|
return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
|
|
@@ -63,7 +62,7 @@ var GameConfig = class _GameConfig {
|
|
|
63
62
|
`Reel set with id "${id}" not found in game mode "${gameMode}". Available reel sets: ${this.config.gameModes[gameMode].reelSets.map((rs) => rs.id).join(", ")}`
|
|
64
63
|
);
|
|
65
64
|
}
|
|
66
|
-
return reelSet;
|
|
65
|
+
return reelSet.reels;
|
|
67
66
|
}
|
|
68
67
|
/**
|
|
69
68
|
* Retrieves the number of free spins awarded for a given spin type and scatter count.
|
|
@@ -80,7 +79,7 @@ var GameConfig = class _GameConfig {
|
|
|
80
79
|
/**
|
|
81
80
|
* Retrieves a result set by its criteria within a specific game mode.
|
|
82
81
|
*/
|
|
83
|
-
|
|
82
|
+
getResultSetByCriteria(mode, criteria) {
|
|
84
83
|
const gameMode = this.config.gameModes[mode];
|
|
85
84
|
if (!gameMode) {
|
|
86
85
|
throw new Error(`Game mode "${mode}" not found in game config.`);
|
|
@@ -1050,13 +1049,6 @@ var GameState = class extends GameConfig {
|
|
|
1050
1049
|
this.clearPendingRecords();
|
|
1051
1050
|
this.state.userData = this.config.userState || {};
|
|
1052
1051
|
}
|
|
1053
|
-
/**
|
|
1054
|
-
* Checks if a max win is reached by comparing `wallet.currentWin` to `config.maxWin`.
|
|
1055
|
-
*
|
|
1056
|
-
* Should be called after `wallet.confirmSpinWin()`.
|
|
1057
|
-
*/
|
|
1058
|
-
isMaxWinTriggered() {
|
|
1059
|
-
}
|
|
1060
1052
|
/**
|
|
1061
1053
|
* Empties the list of pending records in the recorder.
|
|
1062
1054
|
*/
|
|
@@ -1502,7 +1494,7 @@ var Board = class extends GameState {
|
|
|
1502
1494
|
return stopPositionsForReels;
|
|
1503
1495
|
}
|
|
1504
1496
|
/**
|
|
1505
|
-
* Selects a random
|
|
1497
|
+
* Selects a random reel set based on the configured weights of the current result set.\
|
|
1506
1498
|
* Returns the reels as arrays of GameSymbols.
|
|
1507
1499
|
*/
|
|
1508
1500
|
getRandomReelset() {
|
|
@@ -1517,7 +1509,7 @@ var Board = class extends GameState {
|
|
|
1517
1509
|
reelSetId = weightedRandom(weights[this.state.currentSpinType], this.state.rng);
|
|
1518
1510
|
}
|
|
1519
1511
|
const reelSet = this.getReelsetById(this.state.currentGameMode, reelSetId);
|
|
1520
|
-
return reelSet
|
|
1512
|
+
return reelSet;
|
|
1521
1513
|
}
|
|
1522
1514
|
/**
|
|
1523
1515
|
* Draws a board using specified reel stops.
|
|
@@ -1773,7 +1765,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
1773
1765
|
};
|
|
1774
1766
|
|
|
1775
1767
|
// src/optimizer/OptimizationConditions.ts
|
|
1776
|
-
import
|
|
1768
|
+
import assert2 from "assert";
|
|
1777
1769
|
var OptimizationConditions = class {
|
|
1778
1770
|
rtp;
|
|
1779
1771
|
avgWin;
|
|
@@ -1784,14 +1776,14 @@ var OptimizationConditions = class {
|
|
|
1784
1776
|
constructor(opts) {
|
|
1785
1777
|
let { rtp, avgWin, hitRate, searchConditions, priority } = opts;
|
|
1786
1778
|
if (rtp == void 0 || rtp === "x") {
|
|
1787
|
-
|
|
1779
|
+
assert2(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
|
|
1788
1780
|
rtp = Math.round(avgWin / Number(hitRate) * 1e5) / 1e5;
|
|
1789
1781
|
}
|
|
1790
1782
|
let noneCount = 0;
|
|
1791
1783
|
for (const val of [rtp, avgWin, hitRate]) {
|
|
1792
1784
|
if (val === void 0) noneCount++;
|
|
1793
1785
|
}
|
|
1794
|
-
|
|
1786
|
+
assert2(noneCount <= 1, "Invalid combination of optimization conditions.");
|
|
1795
1787
|
this.searchRange = [-1, -1];
|
|
1796
1788
|
this.forceSearch = {};
|
|
1797
1789
|
if (typeof searchConditions === "number") {
|
|
@@ -1871,41 +1863,11 @@ var OptimizationParameters = class _OptimizationParameters {
|
|
|
1871
1863
|
|
|
1872
1864
|
// src/Simulation.ts
|
|
1873
1865
|
import fs3 from "fs";
|
|
1874
|
-
import
|
|
1875
|
-
import
|
|
1866
|
+
import path2 from "path";
|
|
1867
|
+
import assert3 from "assert";
|
|
1868
|
+
import zlib from "zlib";
|
|
1876
1869
|
import { buildSync } from "esbuild";
|
|
1877
1870
|
import { Worker, isMainThread as isMainThread2, parentPort, workerData } from "worker_threads";
|
|
1878
|
-
|
|
1879
|
-
// src/utils/zstd.ts
|
|
1880
|
-
import path2 from "path";
|
|
1881
|
-
import { spawn } from "child_process";
|
|
1882
|
-
async function zstd(...args) {
|
|
1883
|
-
return new Promise((resolve, reject) => {
|
|
1884
|
-
const task = spawn(path2.join(__dirname, "./lib/zstd.exe"), args);
|
|
1885
|
-
task.on("error", (error) => {
|
|
1886
|
-
console.error("Error:", error);
|
|
1887
|
-
reject(error);
|
|
1888
|
-
});
|
|
1889
|
-
task.on("exit", () => {
|
|
1890
|
-
resolve(true);
|
|
1891
|
-
});
|
|
1892
|
-
task.on("close", () => {
|
|
1893
|
-
resolve(true);
|
|
1894
|
-
});
|
|
1895
|
-
task.stdout.on("data", (data) => {
|
|
1896
|
-
console.log(data.toString());
|
|
1897
|
-
});
|
|
1898
|
-
task.stderr.on("data", (data) => {
|
|
1899
|
-
console.log(data.toString());
|
|
1900
|
-
});
|
|
1901
|
-
task.stdout.on("error", (data) => {
|
|
1902
|
-
console.log(data.toString());
|
|
1903
|
-
reject(data.toString());
|
|
1904
|
-
});
|
|
1905
|
-
});
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
// src/Simulation.ts
|
|
1909
1871
|
var completedSimulations = 0;
|
|
1910
1872
|
var TEMP_FILENAME = "__temp_compiled_src_IGNORE.js";
|
|
1911
1873
|
var Simulation = class _Simulation {
|
|
@@ -1926,7 +1888,7 @@ var Simulation = class _Simulation {
|
|
|
1926
1888
|
this.library = /* @__PURE__ */ new Map();
|
|
1927
1889
|
this.records = [];
|
|
1928
1890
|
const gameModeKeys = Object.keys(this.gameConfig.config.gameModes);
|
|
1929
|
-
|
|
1891
|
+
assert3(
|
|
1930
1892
|
Object.values(this.gameConfig.config.gameModes).map((m) => gameModeKeys.includes(m.name)).every((v) => v === true),
|
|
1931
1893
|
"Game mode name must match its key in the gameModes object."
|
|
1932
1894
|
);
|
|
@@ -1963,14 +1925,14 @@ Simulating game mode: ${mode}`);
|
|
|
1963
1925
|
const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
|
|
1964
1926
|
await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
|
|
1965
1927
|
createDirIfNotExists(
|
|
1966
|
-
|
|
1928
|
+
path2.join(
|
|
1967
1929
|
process.cwd(),
|
|
1968
1930
|
this.gameConfig.config.outputDir,
|
|
1969
1931
|
"optimization_files"
|
|
1970
1932
|
)
|
|
1971
1933
|
);
|
|
1972
1934
|
createDirIfNotExists(
|
|
1973
|
-
|
|
1935
|
+
path2.join(process.cwd(), this.gameConfig.config.outputDir, "publish_files")
|
|
1974
1936
|
);
|
|
1975
1937
|
_Simulation.writeLookupTableCSV({
|
|
1976
1938
|
gameMode: mode,
|
|
@@ -2066,7 +2028,7 @@ Simulating game mode: ${mode}`);
|
|
|
2066
2028
|
}
|
|
2067
2029
|
}
|
|
2068
2030
|
return new Promise((resolve, reject) => {
|
|
2069
|
-
const scriptPath =
|
|
2031
|
+
const scriptPath = path2.join(process.cwd(), basePath, TEMP_FILENAME);
|
|
2070
2032
|
const worker = new Worker(scriptPath, {
|
|
2071
2033
|
workerData: {
|
|
2072
2034
|
mode,
|
|
@@ -2114,10 +2076,10 @@ Simulating game mode: ${mode}`);
|
|
|
2114
2076
|
}
|
|
2115
2077
|
rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
|
|
2116
2078
|
let outputFileName = `lookUpTable_${gameMode}.csv`;
|
|
2117
|
-
let outputFilePath =
|
|
2079
|
+
let outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
|
|
2118
2080
|
writeFile(outputFilePath, rows.join("\n"));
|
|
2119
2081
|
outputFileName = `lookUpTable_${gameMode}_0.csv`;
|
|
2120
|
-
outputFilePath =
|
|
2082
|
+
outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
|
|
2121
2083
|
writeFile(outputFilePath, rows.join("\n"));
|
|
2122
2084
|
return outputFilePath;
|
|
2123
2085
|
}
|
|
@@ -2134,21 +2096,21 @@ Simulating game mode: ${mode}`);
|
|
|
2134
2096
|
}
|
|
2135
2097
|
rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
|
|
2136
2098
|
const outputFileName = `lookUpTableSegmented_${gameMode}.csv`;
|
|
2137
|
-
const outputFilePath =
|
|
2099
|
+
const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
|
|
2138
2100
|
writeFile(outputFilePath, rows.join("\n"));
|
|
2139
2101
|
return outputFilePath;
|
|
2140
2102
|
}
|
|
2141
2103
|
static writeRecords(opts) {
|
|
2142
2104
|
const { gameMode, fileNameWithoutExtension, records, gameConfig, debug } = opts;
|
|
2143
2105
|
const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.json` : `force_record_${gameMode}.json`;
|
|
2144
|
-
const outputFilePath =
|
|
2106
|
+
const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
|
|
2145
2107
|
writeFile(outputFilePath, JSON.stringify(records, null, 2));
|
|
2146
2108
|
if (debug) _Simulation.logSymbolOccurrences(records);
|
|
2147
2109
|
return outputFilePath;
|
|
2148
2110
|
}
|
|
2149
2111
|
static writeIndexJson(opts) {
|
|
2150
2112
|
const { gameConfig } = opts;
|
|
2151
|
-
const outputFilePath =
|
|
2113
|
+
const outputFilePath = path2.join(
|
|
2152
2114
|
process.cwd(),
|
|
2153
2115
|
gameConfig.outputDir,
|
|
2154
2116
|
"publish_files",
|
|
@@ -2165,7 +2127,7 @@ Simulating game mode: ${mode}`);
|
|
|
2165
2127
|
static async writeBooksJson(opts) {
|
|
2166
2128
|
const { gameMode, fileNameWithoutExtension, library, gameConfig } = opts;
|
|
2167
2129
|
const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl` : `books_${gameMode}.jsonl`;
|
|
2168
|
-
const outputFilePath =
|
|
2130
|
+
const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
|
|
2169
2131
|
const books = Array.from(library.values()).map((b) => b.serialize()).map((b) => ({
|
|
2170
2132
|
id: b.id,
|
|
2171
2133
|
payoutMultiplier: b.payout,
|
|
@@ -2174,14 +2136,15 @@ Simulating game mode: ${mode}`);
|
|
|
2174
2136
|
const contents = JSONL.stringify(books);
|
|
2175
2137
|
writeFile(outputFilePath, contents);
|
|
2176
2138
|
const compressedFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl.zst` : `books_${gameMode}.jsonl.zst`;
|
|
2177
|
-
const compressedFilePath =
|
|
2139
|
+
const compressedFilePath = path2.join(
|
|
2178
2140
|
process.cwd(),
|
|
2179
2141
|
gameConfig.outputDir,
|
|
2180
2142
|
"publish_files",
|
|
2181
2143
|
compressedFileName
|
|
2182
2144
|
);
|
|
2183
2145
|
fs3.rmSync(compressedFilePath, { force: true });
|
|
2184
|
-
|
|
2146
|
+
const compressed = zlib.zstdCompressSync(Buffer.from(contents));
|
|
2147
|
+
fs3.writeFileSync(compressedFilePath, compressed);
|
|
2185
2148
|
}
|
|
2186
2149
|
static logSymbolOccurrences(records) {
|
|
2187
2150
|
const validRecords = records.filter(
|
|
@@ -2212,14 +2175,14 @@ Simulating game mode: ${mode}`);
|
|
|
2212
2175
|
* Compiles user configured game to JS for use in different Node processes
|
|
2213
2176
|
*/
|
|
2214
2177
|
preprocessFiles() {
|
|
2215
|
-
const builtFilePath =
|
|
2178
|
+
const builtFilePath = path2.join(this.gameConfig.config.outputDir, TEMP_FILENAME);
|
|
2216
2179
|
fs3.rmSync(builtFilePath, { force: true });
|
|
2217
2180
|
buildSync({
|
|
2218
2181
|
entryPoints: [process.cwd()],
|
|
2219
2182
|
bundle: true,
|
|
2220
2183
|
platform: "node",
|
|
2221
|
-
outfile:
|
|
2222
|
-
external: ["esbuild"
|
|
2184
|
+
outfile: path2.join(this.gameConfig.config.outputDir, TEMP_FILENAME),
|
|
2185
|
+
external: ["esbuild"]
|
|
2223
2186
|
});
|
|
2224
2187
|
}
|
|
2225
2188
|
getSimRangesForChunks(total, chunks) {
|
|
@@ -2276,22 +2239,25 @@ var SimulationContext = class extends Board {
|
|
|
2276
2239
|
this.state.currentGameMode = mode;
|
|
2277
2240
|
this.state.currentSimulationId = simId;
|
|
2278
2241
|
this.state.isCriteriaMet = false;
|
|
2242
|
+
const resultSet = this.getResultSetByCriteria(this.state.currentGameMode, criteria);
|
|
2279
2243
|
while (!this.state.isCriteriaMet) {
|
|
2280
2244
|
this.actualSims++;
|
|
2281
2245
|
this.resetSimulation();
|
|
2282
|
-
const resultSet = this.getGameModeCriteria(this.state.currentGameMode, criteria);
|
|
2283
2246
|
this.state.currentResultSet = resultSet;
|
|
2284
2247
|
this.state.book.criteria = resultSet.criteria;
|
|
2285
2248
|
this.handleGameFlow();
|
|
2286
2249
|
if (resultSet.meetsCriteria(this)) {
|
|
2287
2250
|
this.state.isCriteriaMet = true;
|
|
2288
|
-
this.config.hooks.onSimulationAccepted?.(this);
|
|
2289
|
-
this.record({
|
|
2290
|
-
criteria: resultSet.criteria
|
|
2291
|
-
});
|
|
2292
2251
|
}
|
|
2293
2252
|
}
|
|
2294
2253
|
this.wallet.confirmWins(this);
|
|
2254
|
+
if (this.state.book.getPayout() >= this.config.maxWinX) {
|
|
2255
|
+
this.state.triggeredMaxWin = true;
|
|
2256
|
+
}
|
|
2257
|
+
this.record({
|
|
2258
|
+
criteria: resultSet.criteria
|
|
2259
|
+
});
|
|
2260
|
+
this.config.hooks.onSimulationAccepted?.(this);
|
|
2295
2261
|
this.confirmRecords();
|
|
2296
2262
|
parentPort?.postMessage({
|
|
2297
2263
|
type: "complete",
|
|
@@ -2327,8 +2293,8 @@ var SimulationContext = class extends Board {
|
|
|
2327
2293
|
|
|
2328
2294
|
// src/analysis/index.ts
|
|
2329
2295
|
import fs4 from "fs";
|
|
2330
|
-
import
|
|
2331
|
-
import
|
|
2296
|
+
import path3 from "path";
|
|
2297
|
+
import assert4 from "assert";
|
|
2332
2298
|
|
|
2333
2299
|
// src/analysis/utils.ts
|
|
2334
2300
|
function parseLookupTable(content) {
|
|
@@ -2372,8 +2338,11 @@ function getPayoutWeights(lut, opts = {}) {
|
|
|
2372
2338
|
}
|
|
2373
2339
|
function getNonZeroHitrate(payoutWeights) {
|
|
2374
2340
|
const totalWeight = getTotalWeight(payoutWeights);
|
|
2375
|
-
|
|
2376
|
-
|
|
2341
|
+
if (Math.min(...Object.keys(payoutWeights).map(Number)) == 0) {
|
|
2342
|
+
return totalWeight / (totalWeight - (payoutWeights[0] ?? 0) / totalWeight);
|
|
2343
|
+
} else {
|
|
2344
|
+
return 1;
|
|
2345
|
+
}
|
|
2377
2346
|
}
|
|
2378
2347
|
function getNullHitrate(payoutWeights) {
|
|
2379
2348
|
return payoutWeights[0] ?? 0;
|
|
@@ -2434,6 +2403,7 @@ function getLessBetHitrate(payoutWeights, cost) {
|
|
|
2434
2403
|
}
|
|
2435
2404
|
|
|
2436
2405
|
// src/analysis/index.ts
|
|
2406
|
+
import { isMainThread as isMainThread3 } from "worker_threads";
|
|
2437
2407
|
var Analysis = class {
|
|
2438
2408
|
gameConfig;
|
|
2439
2409
|
optimizerConfig;
|
|
@@ -2444,6 +2414,7 @@ var Analysis = class {
|
|
|
2444
2414
|
this.filePaths = {};
|
|
2445
2415
|
}
|
|
2446
2416
|
async runAnalysis(gameModes) {
|
|
2417
|
+
if (!isMainThread3) return;
|
|
2447
2418
|
this.filePaths = this.getPathsForModes(gameModes);
|
|
2448
2419
|
this.getNumberStats(gameModes);
|
|
2449
2420
|
this.getWinRanges(gameModes);
|
|
@@ -2453,28 +2424,28 @@ var Analysis = class {
|
|
|
2453
2424
|
const rootPath = process.cwd();
|
|
2454
2425
|
const paths = {};
|
|
2455
2426
|
for (const modeStr of gameModes) {
|
|
2456
|
-
const lut =
|
|
2427
|
+
const lut = path3.join(
|
|
2457
2428
|
rootPath,
|
|
2458
2429
|
this.gameConfig.outputDir,
|
|
2459
2430
|
`lookUpTable_${modeStr}.csv`
|
|
2460
2431
|
);
|
|
2461
|
-
const lutSegmented =
|
|
2432
|
+
const lutSegmented = path3.join(
|
|
2462
2433
|
rootPath,
|
|
2463
2434
|
this.gameConfig.outputDir,
|
|
2464
2435
|
`lookUpTableSegmented_${modeStr}.csv`
|
|
2465
2436
|
);
|
|
2466
|
-
const lutOptimized =
|
|
2437
|
+
const lutOptimized = path3.join(
|
|
2467
2438
|
rootPath,
|
|
2468
2439
|
this.gameConfig.outputDir,
|
|
2469
2440
|
"publish_files",
|
|
2470
2441
|
`lookUpTable_${modeStr}_0.csv`
|
|
2471
2442
|
);
|
|
2472
|
-
const booksJsonl =
|
|
2443
|
+
const booksJsonl = path3.join(
|
|
2473
2444
|
rootPath,
|
|
2474
2445
|
this.gameConfig.outputDir,
|
|
2475
2446
|
`books_${modeStr}.jsonl`
|
|
2476
2447
|
);
|
|
2477
|
-
const booksJsonlCompressed =
|
|
2448
|
+
const booksJsonlCompressed = path3.join(
|
|
2478
2449
|
rootPath,
|
|
2479
2450
|
this.gameConfig.outputDir,
|
|
2480
2451
|
"publish_files",
|
|
@@ -2488,7 +2459,7 @@ var Analysis = class {
|
|
|
2488
2459
|
booksJsonlCompressed
|
|
2489
2460
|
};
|
|
2490
2461
|
for (const p of Object.values(paths[modeStr])) {
|
|
2491
|
-
|
|
2462
|
+
assert4(
|
|
2492
2463
|
fs4.existsSync(p),
|
|
2493
2464
|
`File "${p}" does not exist. Run optimization to auto-create it.`
|
|
2494
2465
|
);
|
|
@@ -2522,7 +2493,7 @@ var Analysis = class {
|
|
|
2522
2493
|
});
|
|
2523
2494
|
}
|
|
2524
2495
|
writeJsonFile(
|
|
2525
|
-
|
|
2496
|
+
path3.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
|
|
2526
2497
|
stats
|
|
2527
2498
|
);
|
|
2528
2499
|
}
|
|
@@ -2560,13 +2531,13 @@ var Analysis = class {
|
|
|
2560
2531
|
}
|
|
2561
2532
|
getGameModeConfig(mode) {
|
|
2562
2533
|
const config = this.gameConfig.gameModes[mode];
|
|
2563
|
-
|
|
2534
|
+
assert4(config, `Game mode "${mode}" not found in game config`);
|
|
2564
2535
|
return config;
|
|
2565
2536
|
}
|
|
2566
2537
|
};
|
|
2567
2538
|
|
|
2568
2539
|
// src/utils/math-config.ts
|
|
2569
|
-
import
|
|
2540
|
+
import path4 from "path";
|
|
2570
2541
|
function makeMathConfig(optimizer, opts = {}) {
|
|
2571
2542
|
const game = optimizer.getGameConfig();
|
|
2572
2543
|
const gameModesCfg = optimizer.getOptimizerGameModes();
|
|
@@ -2610,14 +2581,14 @@ function makeMathConfig(optimizer, opts = {}) {
|
|
|
2610
2581
|
}))
|
|
2611
2582
|
};
|
|
2612
2583
|
if (writeToFile) {
|
|
2613
|
-
const outPath =
|
|
2584
|
+
const outPath = path4.join(process.cwd(), game.outputDir, "math_config.json");
|
|
2614
2585
|
writeJsonFile(outPath, config);
|
|
2615
2586
|
}
|
|
2616
2587
|
return config;
|
|
2617
2588
|
}
|
|
2618
2589
|
|
|
2619
2590
|
// src/utils/setup-file.ts
|
|
2620
|
-
import
|
|
2591
|
+
import path5 from "path";
|
|
2621
2592
|
function makeSetupFile(optimizer, gameMode) {
|
|
2622
2593
|
const gameConfig = optimizer.getGameConfig();
|
|
2623
2594
|
const optimizerGameModes = optimizer.getOptimizerGameModes();
|
|
@@ -2653,19 +2624,19 @@ function makeSetupFile(optimizer, gameMode) {
|
|
|
2653
2624
|
`;
|
|
2654
2625
|
content += `simulation_trials;${params.simulationTrials}
|
|
2655
2626
|
`;
|
|
2656
|
-
content += `user_game_build_path;${
|
|
2627
|
+
content += `user_game_build_path;${path5.join(process.cwd(), gameConfig.outputDir)}
|
|
2657
2628
|
`;
|
|
2658
2629
|
content += `pmb_rtp;${params.pmbRtp}
|
|
2659
2630
|
`;
|
|
2660
|
-
const outPath =
|
|
2631
|
+
const outPath = path5.join(__dirname, "./optimizer-rust/src", "setup.txt");
|
|
2661
2632
|
writeFile(outPath, content);
|
|
2662
2633
|
}
|
|
2663
2634
|
|
|
2664
2635
|
// src/optimizer/index.ts
|
|
2665
|
-
import { spawn
|
|
2666
|
-
import
|
|
2667
|
-
import
|
|
2668
|
-
import { isMainThread as
|
|
2636
|
+
import { spawn } from "child_process";
|
|
2637
|
+
import path6 from "path";
|
|
2638
|
+
import assert5 from "assert";
|
|
2639
|
+
import { isMainThread as isMainThread4 } from "worker_threads";
|
|
2669
2640
|
var Optimizer = class {
|
|
2670
2641
|
gameConfig;
|
|
2671
2642
|
gameModes;
|
|
@@ -2678,12 +2649,13 @@ var Optimizer = class {
|
|
|
2678
2649
|
* Runs the optimization process, and runs analysis after.
|
|
2679
2650
|
*/
|
|
2680
2651
|
async runOptimization({ gameModes }) {
|
|
2681
|
-
if (!
|
|
2652
|
+
if (!isMainThread4) return;
|
|
2682
2653
|
const mathConfig = makeMathConfig(this, { writeToFile: true });
|
|
2683
2654
|
for (const mode of gameModes) {
|
|
2684
2655
|
const setupFile = makeSetupFile(this, mode);
|
|
2685
2656
|
await this.runSingleOptimization();
|
|
2686
2657
|
}
|
|
2658
|
+
console.log("Optimization complete. Files written to build directory.");
|
|
2687
2659
|
}
|
|
2688
2660
|
async runSingleOptimization() {
|
|
2689
2661
|
return await rustProgram();
|
|
@@ -2707,7 +2679,7 @@ var Optimizer = class {
|
|
|
2707
2679
|
}
|
|
2708
2680
|
}
|
|
2709
2681
|
const criteria = configMode.resultSets.map((r) => r.criteria);
|
|
2710
|
-
|
|
2682
|
+
assert5(
|
|
2711
2683
|
conditions.every((c) => criteria.includes(c)),
|
|
2712
2684
|
`Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
|
|
2713
2685
|
);
|
|
@@ -2719,7 +2691,7 @@ var Optimizer = class {
|
|
|
2719
2691
|
}
|
|
2720
2692
|
gameModeRtp = Math.round(gameModeRtp * 1e3) / 1e3;
|
|
2721
2693
|
paramRtp = Math.round(paramRtp * 1e3) / 1e3;
|
|
2722
|
-
|
|
2694
|
+
assert5(
|
|
2723
2695
|
gameModeRtp === paramRtp,
|
|
2724
2696
|
`Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode "${k}".`
|
|
2725
2697
|
);
|
|
@@ -2734,9 +2706,9 @@ var Optimizer = class {
|
|
|
2734
2706
|
};
|
|
2735
2707
|
async function rustProgram(...args) {
|
|
2736
2708
|
return new Promise((resolve, reject) => {
|
|
2737
|
-
const task =
|
|
2709
|
+
const task = spawn("cargo", ["run", "--release", ...args], {
|
|
2738
2710
|
shell: true,
|
|
2739
|
-
cwd:
|
|
2711
|
+
cwd: path6.join(__dirname, "./optimizer-rust"),
|
|
2740
2712
|
stdio: "pipe"
|
|
2741
2713
|
});
|
|
2742
2714
|
task.on("error", (error) => {
|