@slot-engine/core 0.0.2 → 0.0.3
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 +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +61 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +61 -85
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/lib/zstd.exe +0 -0
package/dist/index.d.mts
CHANGED
|
@@ -879,7 +879,7 @@ declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbol
|
|
|
879
879
|
readonly id: string;
|
|
880
880
|
readonly name: string;
|
|
881
881
|
readonly gameModes: Record<GameModeName, GameMode>;
|
|
882
|
-
readonly symbols: Map<TSymbols
|
|
882
|
+
readonly symbols: Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>;
|
|
883
883
|
readonly padSymbols?: number;
|
|
884
884
|
readonly scatterToFreespins: Record<string, Record<number, number>>;
|
|
885
885
|
readonly anticipationTriggers: Record<SpinType, number>;
|
|
@@ -908,7 +908,7 @@ declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbol
|
|
|
908
908
|
/**
|
|
909
909
|
* Returns all configured symbols as an array.
|
|
910
910
|
*/
|
|
911
|
-
getSymbolArray(): TSymbols[
|
|
911
|
+
getSymbolArray(): TSymbols[keyof TSymbols][];
|
|
912
912
|
static SPIN_TYPE: {
|
|
913
913
|
readonly BASE_GAME: "basegame";
|
|
914
914
|
readonly FREE_SPINS: "freespins";
|
|
@@ -928,7 +928,7 @@ declare class WinType {
|
|
|
928
928
|
*
|
|
929
929
|
* This gives the WinType access to the current board.
|
|
930
930
|
*/
|
|
931
|
-
context(ctx:
|
|
931
|
+
context(ctx: SimulationContext<any, any, any>): WinType;
|
|
932
932
|
protected ensureContext(): void;
|
|
933
933
|
/**
|
|
934
934
|
* Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
|
|
@@ -985,7 +985,7 @@ type WildSymbol = GameSymbol | Record<string, any>;
|
|
|
985
985
|
declare class LinesWinType extends WinType {
|
|
986
986
|
protected lines: Record<number, number[]>;
|
|
987
987
|
protected winCombinations: LineWinCombination[];
|
|
988
|
-
context: (ctx:
|
|
988
|
+
context: (ctx: SimulationContext<any, any, any>) => LinesWinType;
|
|
989
989
|
getWins: () => {
|
|
990
990
|
payout: number;
|
|
991
991
|
winCombinations: LineWinCombination[];
|
|
@@ -1193,7 +1193,7 @@ interface CommonGameOptions<TGameModes extends AnyGameModes = AnyGameModes, TSym
|
|
|
1193
1193
|
/**
|
|
1194
1194
|
* A list of all symbols that will appear on the reels.
|
|
1195
1195
|
*/
|
|
1196
|
-
symbols:
|
|
1196
|
+
symbols: TSymbols;
|
|
1197
1197
|
/**
|
|
1198
1198
|
* A mapping from spin type to scatter counts to the number of free spins awarded.
|
|
1199
1199
|
*
|
|
@@ -1246,7 +1246,7 @@ type AnyGameModes = Record<string, GameMode>;
|
|
|
1246
1246
|
/**
|
|
1247
1247
|
* @internal
|
|
1248
1248
|
*/
|
|
1249
|
-
type AnySymbols = GameSymbol
|
|
1249
|
+
type AnySymbols = Record<string, GameSymbol>;
|
|
1250
1250
|
/**
|
|
1251
1251
|
* @internal
|
|
1252
1252
|
*/
|
|
@@ -1292,7 +1292,7 @@ interface CreateSlotGameOpts<TGameModes extends AnyGameModes = AnyGameModes, TSy
|
|
|
1292
1292
|
}
|
|
1293
1293
|
declare function createSlotGame<TGame>(opts: TGame extends InferGameType<infer G, infer S, infer U> ? CreateSlotGameOpts<G, S, U> : never): TGame;
|
|
1294
1294
|
declare const defineUserState: <TUserState extends AnyUserData>(data: TUserState) => TUserState;
|
|
1295
|
-
declare const defineSymbols: <
|
|
1295
|
+
declare const defineSymbols: <TSymbols extends AnySymbols>(symbols: TSymbols) => TSymbols;
|
|
1296
1296
|
declare const defineGameModes: <TGameModes extends AnyGameModes>(gameModes: TGameModes) => TGameModes;
|
|
1297
1297
|
declare const defineReelSets: <TSymbols extends AnySymbols>(reelSets: ReelGenerator[]) => ReelGenerator[];
|
|
1298
1298
|
|
package/dist/index.d.ts
CHANGED
|
@@ -879,7 +879,7 @@ declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbol
|
|
|
879
879
|
readonly id: string;
|
|
880
880
|
readonly name: string;
|
|
881
881
|
readonly gameModes: Record<GameModeName, GameMode>;
|
|
882
|
-
readonly symbols: Map<TSymbols
|
|
882
|
+
readonly symbols: Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>;
|
|
883
883
|
readonly padSymbols?: number;
|
|
884
884
|
readonly scatterToFreespins: Record<string, Record<number, number>>;
|
|
885
885
|
readonly anticipationTriggers: Record<SpinType, number>;
|
|
@@ -908,7 +908,7 @@ declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbol
|
|
|
908
908
|
/**
|
|
909
909
|
* Returns all configured symbols as an array.
|
|
910
910
|
*/
|
|
911
|
-
getSymbolArray(): TSymbols[
|
|
911
|
+
getSymbolArray(): TSymbols[keyof TSymbols][];
|
|
912
912
|
static SPIN_TYPE: {
|
|
913
913
|
readonly BASE_GAME: "basegame";
|
|
914
914
|
readonly FREE_SPINS: "freespins";
|
|
@@ -928,7 +928,7 @@ declare class WinType {
|
|
|
928
928
|
*
|
|
929
929
|
* This gives the WinType access to the current board.
|
|
930
930
|
*/
|
|
931
|
-
context(ctx:
|
|
931
|
+
context(ctx: SimulationContext<any, any, any>): WinType;
|
|
932
932
|
protected ensureContext(): void;
|
|
933
933
|
/**
|
|
934
934
|
* Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
|
|
@@ -985,7 +985,7 @@ type WildSymbol = GameSymbol | Record<string, any>;
|
|
|
985
985
|
declare class LinesWinType extends WinType {
|
|
986
986
|
protected lines: Record<number, number[]>;
|
|
987
987
|
protected winCombinations: LineWinCombination[];
|
|
988
|
-
context: (ctx:
|
|
988
|
+
context: (ctx: SimulationContext<any, any, any>) => LinesWinType;
|
|
989
989
|
getWins: () => {
|
|
990
990
|
payout: number;
|
|
991
991
|
winCombinations: LineWinCombination[];
|
|
@@ -1193,7 +1193,7 @@ interface CommonGameOptions<TGameModes extends AnyGameModes = AnyGameModes, TSym
|
|
|
1193
1193
|
/**
|
|
1194
1194
|
* A list of all symbols that will appear on the reels.
|
|
1195
1195
|
*/
|
|
1196
|
-
symbols:
|
|
1196
|
+
symbols: TSymbols;
|
|
1197
1197
|
/**
|
|
1198
1198
|
* A mapping from spin type to scatter counts to the number of free spins awarded.
|
|
1199
1199
|
*
|
|
@@ -1246,7 +1246,7 @@ type AnyGameModes = Record<string, GameMode>;
|
|
|
1246
1246
|
/**
|
|
1247
1247
|
* @internal
|
|
1248
1248
|
*/
|
|
1249
|
-
type AnySymbols = GameSymbol
|
|
1249
|
+
type AnySymbols = Record<string, GameSymbol>;
|
|
1250
1250
|
/**
|
|
1251
1251
|
* @internal
|
|
1252
1252
|
*/
|
|
@@ -1292,7 +1292,7 @@ interface CreateSlotGameOpts<TGameModes extends AnyGameModes = AnyGameModes, TSy
|
|
|
1292
1292
|
}
|
|
1293
1293
|
declare function createSlotGame<TGame>(opts: TGame extends InferGameType<infer G, infer S, infer U> ? CreateSlotGameOpts<G, S, U> : never): TGame;
|
|
1294
1294
|
declare const defineUserState: <TUserState extends AnyUserData>(data: TUserState) => TUserState;
|
|
1295
|
-
declare const defineSymbols: <
|
|
1295
|
+
declare const defineSymbols: <TSymbols extends AnySymbols>(symbols: TSymbols) => TSymbols;
|
|
1296
1296
|
declare const defineGameModes: <TGameModes extends AnyGameModes>(gameModes: TGameModes) => TGameModes;
|
|
1297
1297
|
declare const defineReelSets: <TSymbols extends AnySymbols>(reelSets: ReelGenerator[]) => ReelGenerator[];
|
|
1298
1298
|
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,7 @@ __export(index_exports, {
|
|
|
53
53
|
module.exports = __toCommonJS(index_exports);
|
|
54
54
|
|
|
55
55
|
// src/GameConfig.ts
|
|
56
|
+
var import_assert = __toESM(require("assert"));
|
|
56
57
|
var GameConfig = class _GameConfig {
|
|
57
58
|
config;
|
|
58
59
|
constructor(opts) {
|
|
@@ -76,14 +77,12 @@ var GameConfig = class _GameConfig {
|
|
|
76
77
|
userState: opts.userState,
|
|
77
78
|
outputDir: "__build__"
|
|
78
79
|
};
|
|
79
|
-
for (const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
);
|
|
86
|
-
}
|
|
80
|
+
for (const [key, value] of Object.entries(opts.symbols)) {
|
|
81
|
+
(0, import_assert.default)(
|
|
82
|
+
value.id === key,
|
|
83
|
+
`Symbol key "${key}" does not match symbol id "${value.id}"`
|
|
84
|
+
);
|
|
85
|
+
this.config.symbols.set(key, value);
|
|
87
86
|
}
|
|
88
87
|
function getAnticipationTrigger(spinType) {
|
|
89
88
|
return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
|
|
@@ -1827,7 +1826,7 @@ var ManywaysWinType = class extends WinType {
|
|
|
1827
1826
|
};
|
|
1828
1827
|
|
|
1829
1828
|
// src/optimizer/OptimizationConditions.ts
|
|
1830
|
-
var
|
|
1829
|
+
var import_assert2 = __toESM(require("assert"));
|
|
1831
1830
|
var OptimizationConditions = class {
|
|
1832
1831
|
rtp;
|
|
1833
1832
|
avgWin;
|
|
@@ -1838,14 +1837,14 @@ var OptimizationConditions = class {
|
|
|
1838
1837
|
constructor(opts) {
|
|
1839
1838
|
let { rtp, avgWin, hitRate, searchConditions, priority } = opts;
|
|
1840
1839
|
if (rtp == void 0 || rtp === "x") {
|
|
1841
|
-
(0,
|
|
1840
|
+
(0, import_assert2.default)(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
|
|
1842
1841
|
rtp = Math.round(avgWin / Number(hitRate) * 1e5) / 1e5;
|
|
1843
1842
|
}
|
|
1844
1843
|
let noneCount = 0;
|
|
1845
1844
|
for (const val of [rtp, avgWin, hitRate]) {
|
|
1846
1845
|
if (val === void 0) noneCount++;
|
|
1847
1846
|
}
|
|
1848
|
-
(0,
|
|
1847
|
+
(0, import_assert2.default)(noneCount <= 1, "Invalid combination of optimization conditions.");
|
|
1849
1848
|
this.searchRange = [-1, -1];
|
|
1850
1849
|
this.forceSearch = {};
|
|
1851
1850
|
if (typeof searchConditions === "number") {
|
|
@@ -1925,41 +1924,11 @@ var OptimizationParameters = class _OptimizationParameters {
|
|
|
1925
1924
|
|
|
1926
1925
|
// src/Simulation.ts
|
|
1927
1926
|
var import_fs3 = __toESM(require("fs"));
|
|
1928
|
-
var
|
|
1929
|
-
var
|
|
1927
|
+
var import_path2 = __toESM(require("path"));
|
|
1928
|
+
var import_assert3 = __toESM(require("assert"));
|
|
1929
|
+
var import_zlib = __toESM(require("zlib"));
|
|
1930
1930
|
var import_esbuild = require("esbuild");
|
|
1931
1931
|
var import_worker_threads2 = require("worker_threads");
|
|
1932
|
-
|
|
1933
|
-
// src/utils/zstd.ts
|
|
1934
|
-
var import_path2 = __toESM(require("path"));
|
|
1935
|
-
var import_child_process = require("child_process");
|
|
1936
|
-
async function zstd(...args) {
|
|
1937
|
-
return new Promise((resolve, reject) => {
|
|
1938
|
-
const task = (0, import_child_process.spawn)(import_path2.default.join(__dirname, "./lib/zstd.exe"), args);
|
|
1939
|
-
task.on("error", (error) => {
|
|
1940
|
-
console.error("Error:", error);
|
|
1941
|
-
reject(error);
|
|
1942
|
-
});
|
|
1943
|
-
task.on("exit", () => {
|
|
1944
|
-
resolve(true);
|
|
1945
|
-
});
|
|
1946
|
-
task.on("close", () => {
|
|
1947
|
-
resolve(true);
|
|
1948
|
-
});
|
|
1949
|
-
task.stdout.on("data", (data) => {
|
|
1950
|
-
console.log(data.toString());
|
|
1951
|
-
});
|
|
1952
|
-
task.stderr.on("data", (data) => {
|
|
1953
|
-
console.log(data.toString());
|
|
1954
|
-
});
|
|
1955
|
-
task.stdout.on("error", (data) => {
|
|
1956
|
-
console.log(data.toString());
|
|
1957
|
-
reject(data.toString());
|
|
1958
|
-
});
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
// src/Simulation.ts
|
|
1963
1932
|
var completedSimulations = 0;
|
|
1964
1933
|
var TEMP_FILENAME = "__temp_compiled_src_IGNORE.js";
|
|
1965
1934
|
var Simulation = class _Simulation {
|
|
@@ -1980,7 +1949,7 @@ var Simulation = class _Simulation {
|
|
|
1980
1949
|
this.library = /* @__PURE__ */ new Map();
|
|
1981
1950
|
this.records = [];
|
|
1982
1951
|
const gameModeKeys = Object.keys(this.gameConfig.config.gameModes);
|
|
1983
|
-
(0,
|
|
1952
|
+
(0, import_assert3.default)(
|
|
1984
1953
|
Object.values(this.gameConfig.config.gameModes).map((m) => gameModeKeys.includes(m.name)).every((v) => v === true),
|
|
1985
1954
|
"Game mode name must match its key in the gameModes object."
|
|
1986
1955
|
);
|
|
@@ -2017,14 +1986,14 @@ Simulating game mode: ${mode}`);
|
|
|
2017
1986
|
const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
|
|
2018
1987
|
await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
|
|
2019
1988
|
createDirIfNotExists(
|
|
2020
|
-
|
|
1989
|
+
import_path2.default.join(
|
|
2021
1990
|
process.cwd(),
|
|
2022
1991
|
this.gameConfig.config.outputDir,
|
|
2023
1992
|
"optimization_files"
|
|
2024
1993
|
)
|
|
2025
1994
|
);
|
|
2026
1995
|
createDirIfNotExists(
|
|
2027
|
-
|
|
1996
|
+
import_path2.default.join(process.cwd(), this.gameConfig.config.outputDir, "publish_files")
|
|
2028
1997
|
);
|
|
2029
1998
|
_Simulation.writeLookupTableCSV({
|
|
2030
1999
|
gameMode: mode,
|
|
@@ -2120,7 +2089,7 @@ Simulating game mode: ${mode}`);
|
|
|
2120
2089
|
}
|
|
2121
2090
|
}
|
|
2122
2091
|
return new Promise((resolve, reject) => {
|
|
2123
|
-
const scriptPath =
|
|
2092
|
+
const scriptPath = import_path2.default.join(process.cwd(), basePath, TEMP_FILENAME);
|
|
2124
2093
|
const worker = new import_worker_threads2.Worker(scriptPath, {
|
|
2125
2094
|
workerData: {
|
|
2126
2095
|
mode,
|
|
@@ -2168,10 +2137,10 @@ Simulating game mode: ${mode}`);
|
|
|
2168
2137
|
}
|
|
2169
2138
|
rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
|
|
2170
2139
|
let outputFileName = `lookUpTable_${gameMode}.csv`;
|
|
2171
|
-
let outputFilePath =
|
|
2140
|
+
let outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
|
|
2172
2141
|
writeFile(outputFilePath, rows.join("\n"));
|
|
2173
2142
|
outputFileName = `lookUpTable_${gameMode}_0.csv`;
|
|
2174
|
-
outputFilePath =
|
|
2143
|
+
outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
|
|
2175
2144
|
writeFile(outputFilePath, rows.join("\n"));
|
|
2176
2145
|
return outputFilePath;
|
|
2177
2146
|
}
|
|
@@ -2188,21 +2157,21 @@ Simulating game mode: ${mode}`);
|
|
|
2188
2157
|
}
|
|
2189
2158
|
rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
|
|
2190
2159
|
const outputFileName = `lookUpTableSegmented_${gameMode}.csv`;
|
|
2191
|
-
const outputFilePath =
|
|
2160
|
+
const outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
|
|
2192
2161
|
writeFile(outputFilePath, rows.join("\n"));
|
|
2193
2162
|
return outputFilePath;
|
|
2194
2163
|
}
|
|
2195
2164
|
static writeRecords(opts) {
|
|
2196
2165
|
const { gameMode, fileNameWithoutExtension, records, gameConfig, debug } = opts;
|
|
2197
2166
|
const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.json` : `force_record_${gameMode}.json`;
|
|
2198
|
-
const outputFilePath =
|
|
2167
|
+
const outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
|
|
2199
2168
|
writeFile(outputFilePath, JSON.stringify(records, null, 2));
|
|
2200
2169
|
if (debug) _Simulation.logSymbolOccurrences(records);
|
|
2201
2170
|
return outputFilePath;
|
|
2202
2171
|
}
|
|
2203
2172
|
static writeIndexJson(opts) {
|
|
2204
2173
|
const { gameConfig } = opts;
|
|
2205
|
-
const outputFilePath =
|
|
2174
|
+
const outputFilePath = import_path2.default.join(
|
|
2206
2175
|
process.cwd(),
|
|
2207
2176
|
gameConfig.outputDir,
|
|
2208
2177
|
"publish_files",
|
|
@@ -2219,7 +2188,7 @@ Simulating game mode: ${mode}`);
|
|
|
2219
2188
|
static async writeBooksJson(opts) {
|
|
2220
2189
|
const { gameMode, fileNameWithoutExtension, library, gameConfig } = opts;
|
|
2221
2190
|
const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl` : `books_${gameMode}.jsonl`;
|
|
2222
|
-
const outputFilePath =
|
|
2191
|
+
const outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
|
|
2223
2192
|
const books = Array.from(library.values()).map((b) => b.serialize()).map((b) => ({
|
|
2224
2193
|
id: b.id,
|
|
2225
2194
|
payoutMultiplier: b.payout,
|
|
@@ -2228,14 +2197,15 @@ Simulating game mode: ${mode}`);
|
|
|
2228
2197
|
const contents = JSONL.stringify(books);
|
|
2229
2198
|
writeFile(outputFilePath, contents);
|
|
2230
2199
|
const compressedFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl.zst` : `books_${gameMode}.jsonl.zst`;
|
|
2231
|
-
const compressedFilePath =
|
|
2200
|
+
const compressedFilePath = import_path2.default.join(
|
|
2232
2201
|
process.cwd(),
|
|
2233
2202
|
gameConfig.outputDir,
|
|
2234
2203
|
"publish_files",
|
|
2235
2204
|
compressedFileName
|
|
2236
2205
|
);
|
|
2237
2206
|
import_fs3.default.rmSync(compressedFilePath, { force: true });
|
|
2238
|
-
|
|
2207
|
+
const compressed = import_zlib.default.zstdCompressSync(Buffer.from(contents));
|
|
2208
|
+
import_fs3.default.writeFileSync(compressedFilePath, compressed);
|
|
2239
2209
|
}
|
|
2240
2210
|
static logSymbolOccurrences(records) {
|
|
2241
2211
|
const validRecords = records.filter(
|
|
@@ -2266,14 +2236,14 @@ Simulating game mode: ${mode}`);
|
|
|
2266
2236
|
* Compiles user configured game to JS for use in different Node processes
|
|
2267
2237
|
*/
|
|
2268
2238
|
preprocessFiles() {
|
|
2269
|
-
const builtFilePath =
|
|
2239
|
+
const builtFilePath = import_path2.default.join(this.gameConfig.config.outputDir, TEMP_FILENAME);
|
|
2270
2240
|
import_fs3.default.rmSync(builtFilePath, { force: true });
|
|
2271
2241
|
(0, import_esbuild.buildSync)({
|
|
2272
2242
|
entryPoints: [process.cwd()],
|
|
2273
2243
|
bundle: true,
|
|
2274
2244
|
platform: "node",
|
|
2275
|
-
outfile:
|
|
2276
|
-
external: ["esbuild"
|
|
2245
|
+
outfile: import_path2.default.join(this.gameConfig.config.outputDir, TEMP_FILENAME),
|
|
2246
|
+
external: ["esbuild"]
|
|
2277
2247
|
});
|
|
2278
2248
|
}
|
|
2279
2249
|
getSimRangesForChunks(total, chunks) {
|
|
@@ -2381,8 +2351,8 @@ var SimulationContext = class extends Board {
|
|
|
2381
2351
|
|
|
2382
2352
|
// src/analysis/index.ts
|
|
2383
2353
|
var import_fs4 = __toESM(require("fs"));
|
|
2384
|
-
var
|
|
2385
|
-
var
|
|
2354
|
+
var import_path3 = __toESM(require("path"));
|
|
2355
|
+
var import_assert4 = __toESM(require("assert"));
|
|
2386
2356
|
|
|
2387
2357
|
// src/analysis/utils.ts
|
|
2388
2358
|
function parseLookupTable(content) {
|
|
@@ -2426,8 +2396,11 @@ function getPayoutWeights(lut, opts = {}) {
|
|
|
2426
2396
|
}
|
|
2427
2397
|
function getNonZeroHitrate(payoutWeights) {
|
|
2428
2398
|
const totalWeight = getTotalWeight(payoutWeights);
|
|
2429
|
-
|
|
2430
|
-
|
|
2399
|
+
if (Math.min(...Object.keys(payoutWeights).map(Number)) == 0) {
|
|
2400
|
+
return totalWeight / (totalWeight - (payoutWeights[0] ?? 0) / totalWeight);
|
|
2401
|
+
} else {
|
|
2402
|
+
return 1;
|
|
2403
|
+
}
|
|
2431
2404
|
}
|
|
2432
2405
|
function getNullHitrate(payoutWeights) {
|
|
2433
2406
|
return payoutWeights[0] ?? 0;
|
|
@@ -2488,6 +2461,7 @@ function getLessBetHitrate(payoutWeights, cost) {
|
|
|
2488
2461
|
}
|
|
2489
2462
|
|
|
2490
2463
|
// src/analysis/index.ts
|
|
2464
|
+
var import_worker_threads3 = require("worker_threads");
|
|
2491
2465
|
var Analysis = class {
|
|
2492
2466
|
gameConfig;
|
|
2493
2467
|
optimizerConfig;
|
|
@@ -2498,6 +2472,7 @@ var Analysis = class {
|
|
|
2498
2472
|
this.filePaths = {};
|
|
2499
2473
|
}
|
|
2500
2474
|
async runAnalysis(gameModes) {
|
|
2475
|
+
if (!import_worker_threads3.isMainThread) return;
|
|
2501
2476
|
this.filePaths = this.getPathsForModes(gameModes);
|
|
2502
2477
|
this.getNumberStats(gameModes);
|
|
2503
2478
|
this.getWinRanges(gameModes);
|
|
@@ -2507,28 +2482,28 @@ var Analysis = class {
|
|
|
2507
2482
|
const rootPath = process.cwd();
|
|
2508
2483
|
const paths = {};
|
|
2509
2484
|
for (const modeStr of gameModes) {
|
|
2510
|
-
const lut =
|
|
2485
|
+
const lut = import_path3.default.join(
|
|
2511
2486
|
rootPath,
|
|
2512
2487
|
this.gameConfig.outputDir,
|
|
2513
2488
|
`lookUpTable_${modeStr}.csv`
|
|
2514
2489
|
);
|
|
2515
|
-
const lutSegmented =
|
|
2490
|
+
const lutSegmented = import_path3.default.join(
|
|
2516
2491
|
rootPath,
|
|
2517
2492
|
this.gameConfig.outputDir,
|
|
2518
2493
|
`lookUpTableSegmented_${modeStr}.csv`
|
|
2519
2494
|
);
|
|
2520
|
-
const lutOptimized =
|
|
2495
|
+
const lutOptimized = import_path3.default.join(
|
|
2521
2496
|
rootPath,
|
|
2522
2497
|
this.gameConfig.outputDir,
|
|
2523
2498
|
"publish_files",
|
|
2524
2499
|
`lookUpTable_${modeStr}_0.csv`
|
|
2525
2500
|
);
|
|
2526
|
-
const booksJsonl =
|
|
2501
|
+
const booksJsonl = import_path3.default.join(
|
|
2527
2502
|
rootPath,
|
|
2528
2503
|
this.gameConfig.outputDir,
|
|
2529
2504
|
`books_${modeStr}.jsonl`
|
|
2530
2505
|
);
|
|
2531
|
-
const booksJsonlCompressed =
|
|
2506
|
+
const booksJsonlCompressed = import_path3.default.join(
|
|
2532
2507
|
rootPath,
|
|
2533
2508
|
this.gameConfig.outputDir,
|
|
2534
2509
|
"publish_files",
|
|
@@ -2542,7 +2517,7 @@ var Analysis = class {
|
|
|
2542
2517
|
booksJsonlCompressed
|
|
2543
2518
|
};
|
|
2544
2519
|
for (const p of Object.values(paths[modeStr])) {
|
|
2545
|
-
(0,
|
|
2520
|
+
(0, import_assert4.default)(
|
|
2546
2521
|
import_fs4.default.existsSync(p),
|
|
2547
2522
|
`File "${p}" does not exist. Run optimization to auto-create it.`
|
|
2548
2523
|
);
|
|
@@ -2576,7 +2551,7 @@ var Analysis = class {
|
|
|
2576
2551
|
});
|
|
2577
2552
|
}
|
|
2578
2553
|
writeJsonFile(
|
|
2579
|
-
|
|
2554
|
+
import_path3.default.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
|
|
2580
2555
|
stats
|
|
2581
2556
|
);
|
|
2582
2557
|
}
|
|
@@ -2614,13 +2589,13 @@ var Analysis = class {
|
|
|
2614
2589
|
}
|
|
2615
2590
|
getGameModeConfig(mode) {
|
|
2616
2591
|
const config = this.gameConfig.gameModes[mode];
|
|
2617
|
-
(0,
|
|
2592
|
+
(0, import_assert4.default)(config, `Game mode "${mode}" not found in game config`);
|
|
2618
2593
|
return config;
|
|
2619
2594
|
}
|
|
2620
2595
|
};
|
|
2621
2596
|
|
|
2622
2597
|
// src/utils/math-config.ts
|
|
2623
|
-
var
|
|
2598
|
+
var import_path4 = __toESM(require("path"));
|
|
2624
2599
|
function makeMathConfig(optimizer, opts = {}) {
|
|
2625
2600
|
const game = optimizer.getGameConfig();
|
|
2626
2601
|
const gameModesCfg = optimizer.getOptimizerGameModes();
|
|
@@ -2664,14 +2639,14 @@ function makeMathConfig(optimizer, opts = {}) {
|
|
|
2664
2639
|
}))
|
|
2665
2640
|
};
|
|
2666
2641
|
if (writeToFile) {
|
|
2667
|
-
const outPath =
|
|
2642
|
+
const outPath = import_path4.default.join(process.cwd(), game.outputDir, "math_config.json");
|
|
2668
2643
|
writeJsonFile(outPath, config);
|
|
2669
2644
|
}
|
|
2670
2645
|
return config;
|
|
2671
2646
|
}
|
|
2672
2647
|
|
|
2673
2648
|
// src/utils/setup-file.ts
|
|
2674
|
-
var
|
|
2649
|
+
var import_path5 = __toESM(require("path"));
|
|
2675
2650
|
function makeSetupFile(optimizer, gameMode) {
|
|
2676
2651
|
const gameConfig = optimizer.getGameConfig();
|
|
2677
2652
|
const optimizerGameModes = optimizer.getOptimizerGameModes();
|
|
@@ -2707,19 +2682,19 @@ function makeSetupFile(optimizer, gameMode) {
|
|
|
2707
2682
|
`;
|
|
2708
2683
|
content += `simulation_trials;${params.simulationTrials}
|
|
2709
2684
|
`;
|
|
2710
|
-
content += `user_game_build_path;${
|
|
2685
|
+
content += `user_game_build_path;${import_path5.default.join(process.cwd(), gameConfig.outputDir)}
|
|
2711
2686
|
`;
|
|
2712
2687
|
content += `pmb_rtp;${params.pmbRtp}
|
|
2713
2688
|
`;
|
|
2714
|
-
const outPath =
|
|
2689
|
+
const outPath = import_path5.default.join(__dirname, "./optimizer-rust/src", "setup.txt");
|
|
2715
2690
|
writeFile(outPath, content);
|
|
2716
2691
|
}
|
|
2717
2692
|
|
|
2718
2693
|
// src/optimizer/index.ts
|
|
2719
|
-
var
|
|
2720
|
-
var
|
|
2721
|
-
var
|
|
2722
|
-
var
|
|
2694
|
+
var import_child_process = require("child_process");
|
|
2695
|
+
var import_path6 = __toESM(require("path"));
|
|
2696
|
+
var import_assert5 = __toESM(require("assert"));
|
|
2697
|
+
var import_worker_threads4 = require("worker_threads");
|
|
2723
2698
|
var Optimizer = class {
|
|
2724
2699
|
gameConfig;
|
|
2725
2700
|
gameModes;
|
|
@@ -2732,12 +2707,13 @@ var Optimizer = class {
|
|
|
2732
2707
|
* Runs the optimization process, and runs analysis after.
|
|
2733
2708
|
*/
|
|
2734
2709
|
async runOptimization({ gameModes }) {
|
|
2735
|
-
if (!
|
|
2710
|
+
if (!import_worker_threads4.isMainThread) return;
|
|
2736
2711
|
const mathConfig = makeMathConfig(this, { writeToFile: true });
|
|
2737
2712
|
for (const mode of gameModes) {
|
|
2738
2713
|
const setupFile = makeSetupFile(this, mode);
|
|
2739
2714
|
await this.runSingleOptimization();
|
|
2740
2715
|
}
|
|
2716
|
+
console.log("Optimization complete. Files written to build directory.");
|
|
2741
2717
|
}
|
|
2742
2718
|
async runSingleOptimization() {
|
|
2743
2719
|
return await rustProgram();
|
|
@@ -2761,7 +2737,7 @@ var Optimizer = class {
|
|
|
2761
2737
|
}
|
|
2762
2738
|
}
|
|
2763
2739
|
const criteria = configMode.resultSets.map((r) => r.criteria);
|
|
2764
|
-
(0,
|
|
2740
|
+
(0, import_assert5.default)(
|
|
2765
2741
|
conditions.every((c) => criteria.includes(c)),
|
|
2766
2742
|
`Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
|
|
2767
2743
|
);
|
|
@@ -2773,7 +2749,7 @@ var Optimizer = class {
|
|
|
2773
2749
|
}
|
|
2774
2750
|
gameModeRtp = Math.round(gameModeRtp * 1e3) / 1e3;
|
|
2775
2751
|
paramRtp = Math.round(paramRtp * 1e3) / 1e3;
|
|
2776
|
-
(0,
|
|
2752
|
+
(0, import_assert5.default)(
|
|
2777
2753
|
gameModeRtp === paramRtp,
|
|
2778
2754
|
`Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode "${k}".`
|
|
2779
2755
|
);
|
|
@@ -2788,9 +2764,9 @@ var Optimizer = class {
|
|
|
2788
2764
|
};
|
|
2789
2765
|
async function rustProgram(...args) {
|
|
2790
2766
|
return new Promise((resolve, reject) => {
|
|
2791
|
-
const task = (0,
|
|
2767
|
+
const task = (0, import_child_process.spawn)("cargo", ["run", "--release", ...args], {
|
|
2792
2768
|
shell: true,
|
|
2793
|
-
cwd:
|
|
2769
|
+
cwd: import_path6.default.join(__dirname, "./optimizer-rust"),
|
|
2794
2770
|
stdio: "pipe"
|
|
2795
2771
|
});
|
|
2796
2772
|
task.on("error", (error) => {
|