@energy8platform/platform-core 0.24.6 → 0.25.0
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/game-spec.cjs.js +209 -0
- package/dist/game-spec.cjs.js.map +1 -0
- package/dist/game-spec.d.ts +164 -0
- package/dist/game-spec.esm.js +198 -0
- package/dist/game-spec.esm.js.map +1 -0
- package/dist/index.cjs.js +57 -0
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.esm.js +57 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +5 -2
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +9 -0
- package/dist/lua.esm.js +5 -2
- package/dist/lua.esm.js.map +1 -1
- package/dist/shell.cjs.js +35 -0
- package/dist/shell.cjs.js.map +1 -1
- package/dist/shell.d.ts +16 -1
- package/dist/shell.esm.js +35 -1
- package/dist/shell.esm.js.map +1 -1
- package/dist/simulation.cjs.js +40 -22
- package/dist/simulation.cjs.js.map +1 -1
- package/dist/simulation.d.ts +35 -2
- package/dist/simulation.esm.js +39 -23
- package/dist/simulation.esm.js.map +1 -1
- package/dist/slot-result.cjs.js +17 -0
- package/dist/slot-result.cjs.js.map +1 -0
- package/dist/slot-result.d.ts +26 -0
- package/dist/slot-result.esm.js +14 -0
- package/dist/slot-result.esm.js.map +1 -0
- package/package.json +12 -1
- package/scripts/gen-version.mjs +21 -0
- package/src/PlatformSession.ts +28 -0
- package/src/game-spec/defineGame.ts +16 -0
- package/src/game-spec/derive.ts +135 -0
- package/src/game-spec/export.ts +17 -0
- package/src/game-spec/index.ts +6 -0
- package/src/game-spec/types.ts +81 -0
- package/src/game-spec/validate.ts +49 -0
- package/src/lua/LuaEngine.ts +5 -2
- package/src/lua/types.ts +8 -0
- package/src/shell/GameShell.ts +19 -1
- package/src/shell/components/GameInfo.ts +13 -0
- package/src/shell/index.ts +1 -0
- package/src/shell/shell.css.ts +2 -0
- package/src/shell/types.ts +3 -0
- package/src/shell/version.ts +3 -0
- package/src/simulation/NativeSimulationRunner.ts +62 -26
- package/src/simulation/index.ts +3 -0
- package/src/slot-result/coerce.ts +11 -0
- package/src/slot-result/index.ts +2 -0
- package/src/slot-result/types.ts +19 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class GameSpecError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'GameSpecError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function validateSpec(spec) {
|
|
10
|
+
if (!spec.id || spec.id.trim() === '')
|
|
11
|
+
throw new GameSpecError('spec.id is required');
|
|
12
|
+
if (spec.maxWin <= 0)
|
|
13
|
+
throw new GameSpecError('spec.maxWin must be > 0');
|
|
14
|
+
if (spec.grid.cols <= 0 || spec.grid.rows <= 0)
|
|
15
|
+
throw new GameSpecError('spec.grid dimensions must be > 0');
|
|
16
|
+
if (!spec.betLevels.length)
|
|
17
|
+
throw new GameSpecError('spec.betLevels must be non-empty');
|
|
18
|
+
for (let i = 1; i < spec.betLevels.length; i++) {
|
|
19
|
+
if (spec.betLevels[i] <= spec.betLevels[i - 1]) {
|
|
20
|
+
throw new GameSpecError('spec.betLevels must be strictly ascending');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const ids = new Set();
|
|
24
|
+
for (const sym of spec.symbols) {
|
|
25
|
+
if (ids.has(sym.id))
|
|
26
|
+
throw new GameSpecError(`duplicate symbol id: ${sym.id}`);
|
|
27
|
+
ids.add(sym.id);
|
|
28
|
+
if (sym.pay) {
|
|
29
|
+
for (const [count, mult] of Object.entries(sym.pay)) {
|
|
30
|
+
if (Number(count) <= 0 || !Number.isInteger(Number(count))) {
|
|
31
|
+
throw new GameSpecError(`symbol ${sym.id} pay key must be a positive integer: ${count}`);
|
|
32
|
+
}
|
|
33
|
+
if (mult <= 0)
|
|
34
|
+
throw new GameSpecError(`symbol ${sym.id} pay[${count}] must be > 0`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const actionKeys = new Set(Object.keys(spec.actions));
|
|
39
|
+
for (const [key, action] of Object.entries(spec.actions)) {
|
|
40
|
+
if (action.cost !== undefined && action.cost <= 0) {
|
|
41
|
+
throw new GameSpecError(`action ${key} cost must be > 0`);
|
|
42
|
+
}
|
|
43
|
+
for (const t of action.transitions ?? []) {
|
|
44
|
+
for (const next of t.next_actions) {
|
|
45
|
+
if (!actionKeys.has(next)) {
|
|
46
|
+
throw new GameSpecError(`action ${key} transition next_actions references unknown action: ${next}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function freeActionKey(spec) {
|
|
54
|
+
return Object.keys(spec.actions).find((k) => spec.actions[k].role === 'free');
|
|
55
|
+
}
|
|
56
|
+
function defaultStage(role) {
|
|
57
|
+
return role === 'free' ? 'free_spins' : 'base_game';
|
|
58
|
+
}
|
|
59
|
+
function defaultTransitions(role, freeKey, actionKey) {
|
|
60
|
+
if (role === 'free') {
|
|
61
|
+
if (!freeKey)
|
|
62
|
+
return [];
|
|
63
|
+
return [
|
|
64
|
+
{ condition: 'retrigger_spins > 0', add_spins_var: 'retrigger_spins', next_actions: [freeKey] },
|
|
65
|
+
{ condition: 'always', next_actions: [freeKey] },
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
// base | buy — always include an "always" fallback so the engine can route back
|
|
69
|
+
const transitions = [];
|
|
70
|
+
if (freeKey) {
|
|
71
|
+
transitions.push({
|
|
72
|
+
condition: 'free_spins_awarded > 0',
|
|
73
|
+
creates_session: true,
|
|
74
|
+
next_actions: [freeKey],
|
|
75
|
+
session_config: { total_spins_var: 'free_spins_awarded' },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
transitions.push({ condition: 'always', next_actions: [actionKey] });
|
|
79
|
+
return transitions;
|
|
80
|
+
}
|
|
81
|
+
function toActionDefinition(key, action, freeKey) {
|
|
82
|
+
const role = action.role ?? 'base';
|
|
83
|
+
const transitions = action.transitions ?? defaultTransitions(role, freeKey, key);
|
|
84
|
+
if (role === 'free') {
|
|
85
|
+
return {
|
|
86
|
+
stage: action.stage ?? defaultStage(role),
|
|
87
|
+
debit: 'none',
|
|
88
|
+
credit: 'defer',
|
|
89
|
+
requires_session: true,
|
|
90
|
+
transitions,
|
|
91
|
+
...(action.feature ? { feature_data: action.feature } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
stage: action.stage ?? defaultStage(role),
|
|
96
|
+
debit: 'bet',
|
|
97
|
+
cost_multiplier: action.cost ?? 1,
|
|
98
|
+
credit: role === 'buy' ? 'none' : 'win',
|
|
99
|
+
transitions,
|
|
100
|
+
...(action.feature ? { feature_data: action.feature } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function toGameDefinition(spec) {
|
|
104
|
+
const freeKey = freeActionKey(spec);
|
|
105
|
+
const actions = {};
|
|
106
|
+
for (const [key, action] of Object.entries(spec.actions)) {
|
|
107
|
+
actions[key] = toActionDefinition(key, action, freeKey);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
id: spec.id,
|
|
111
|
+
type: 'SLOT',
|
|
112
|
+
actions,
|
|
113
|
+
bet_levels: [...spec.betLevels],
|
|
114
|
+
max_win: { multiplier: spec.maxWin },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function luaTable(record) {
|
|
118
|
+
const parts = Object.entries(record).map(([k, v]) => `[${k}]=${v}`);
|
|
119
|
+
return `{${parts.join(', ')}}`;
|
|
120
|
+
}
|
|
121
|
+
function toLuaPrelude(spec) {
|
|
122
|
+
const lines = ['-- AUTO-GENERATED from game.spec.ts — do not edit'];
|
|
123
|
+
lines.push(`SPEC = { cols = ${spec.grid.cols}, rows = ${spec.grid.rows}, max_win = ${spec.maxWin} }`);
|
|
124
|
+
const symNames = spec.symbols.map((s) => `"${s.id}"`).join(', ');
|
|
125
|
+
lines.push(`SYMBOLS = { ${symNames} }`);
|
|
126
|
+
const symIndex = spec.symbols.map((s, i) => `${s.id}=${i + 1}`).join(', ');
|
|
127
|
+
lines.push(`SYM = { ${symIndex} }`);
|
|
128
|
+
const payEntries = spec.symbols
|
|
129
|
+
.filter((s) => s.pay)
|
|
130
|
+
.map((s) => ` ${s.id} = ${luaTable(s.pay)}`);
|
|
131
|
+
lines.push(`PAYTABLE = {\n${payEntries.join(',\n')}\n}`);
|
|
132
|
+
const valEntries = spec.symbols
|
|
133
|
+
.filter((s) => s.value !== undefined)
|
|
134
|
+
.map((s) => ` ${s.id} = ${Array.isArray(s.value) ? `{${s.value.join(', ')}}` : s.value}`);
|
|
135
|
+
if (valEntries.length)
|
|
136
|
+
lines.push(`VALUES = {\n${valEntries.join(',\n')}\n}`);
|
|
137
|
+
return lines.join('\n') + '\n';
|
|
138
|
+
}
|
|
139
|
+
function toModeMap(spec) {
|
|
140
|
+
const map = {};
|
|
141
|
+
for (const [key, action] of Object.entries(spec.actions)) {
|
|
142
|
+
const role = action.role ?? 'base';
|
|
143
|
+
if (role === 'free')
|
|
144
|
+
continue;
|
|
145
|
+
const mode = action.mode ?? (role === 'base' ? 'BASE' : key.toUpperCase());
|
|
146
|
+
map[key] = mode;
|
|
147
|
+
}
|
|
148
|
+
return map;
|
|
149
|
+
}
|
|
150
|
+
function toMathModes(spec) {
|
|
151
|
+
const modes = [];
|
|
152
|
+
for (const [key, action] of Object.entries(spec.actions)) {
|
|
153
|
+
const role = action.role ?? 'base';
|
|
154
|
+
if (role === 'free')
|
|
155
|
+
continue;
|
|
156
|
+
const mode = action.mode ?? (role === 'base' ? 'BASE' : key.toUpperCase());
|
|
157
|
+
modes.push({
|
|
158
|
+
action: key,
|
|
159
|
+
mode,
|
|
160
|
+
costMultiplier: action.cost ?? 1,
|
|
161
|
+
...(typeof action.rtp === 'number' ? { rtp: action.rtp } : {}),
|
|
162
|
+
maxWin: action.maxWin ?? spec.maxWin,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return modes;
|
|
166
|
+
}
|
|
167
|
+
function toPaytableView(spec) {
|
|
168
|
+
return {
|
|
169
|
+
symbols: spec.symbols
|
|
170
|
+
.filter((s) => s.pay)
|
|
171
|
+
.map((s) => ({ id: s.id, name: s.name ?? s.id, kind: s.kind, pay: { ...s.pay } })),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function defineGame(spec) {
|
|
176
|
+
validateSpec(spec);
|
|
177
|
+
return {
|
|
178
|
+
spec,
|
|
179
|
+
gameDefinition: toGameDefinition(spec),
|
|
180
|
+
luaPrelude: toLuaPrelude(spec),
|
|
181
|
+
modeMap: toModeMap(spec),
|
|
182
|
+
mathModes: toMathModes(spec),
|
|
183
|
+
paytable: toPaytableView(spec),
|
|
184
|
+
symbols: spec.symbols,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildLuaScript(model, logicLua) {
|
|
189
|
+
return model.luaPrelude + '\n' + logicLua;
|
|
190
|
+
}
|
|
191
|
+
function exportGame(spec, opts) {
|
|
192
|
+
const model = defineGame(spec);
|
|
193
|
+
return {
|
|
194
|
+
'gameDefinition.json': JSON.stringify(model.gameDefinition, null, 2),
|
|
195
|
+
'script.lua': buildLuaScript(model, opts.logicLua),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
exports.GameSpecError = GameSpecError;
|
|
200
|
+
exports.buildLuaScript = buildLuaScript;
|
|
201
|
+
exports.defineGame = defineGame;
|
|
202
|
+
exports.exportGame = exportGame;
|
|
203
|
+
exports.toGameDefinition = toGameDefinition;
|
|
204
|
+
exports.toLuaPrelude = toLuaPrelude;
|
|
205
|
+
exports.toMathModes = toMathModes;
|
|
206
|
+
exports.toModeMap = toModeMap;
|
|
207
|
+
exports.toPaytableView = toPaytableView;
|
|
208
|
+
exports.validateSpec = validateSpec;
|
|
209
|
+
//# sourceMappingURL=game-spec.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-spec.cjs.js","sources":["../src/game-spec/validate.ts","../src/game-spec/derive.ts","../src/game-spec/defineGame.ts","../src/game-spec/export.ts"],"sourcesContent":[null,null,null,null],"names":[],"mappings":";;AAEM,MAAO,aAAc,SAAQ,KAAK,CAAA;AACtC,IAAA,WAAA,CAAY,OAAe,EAAA;QACzB,KAAK,CAAC,OAAO,CAAC;AACd,QAAA,IAAI,CAAC,IAAI,GAAG,eAAe;IAC7B;AACD;AAEK,SAAU,YAAY,CAAC,IAAc,EAAA;AACzC,IAAA,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE;AAAE,QAAA,MAAM,IAAI,aAAa,CAAC,qBAAqB,CAAC;AACrF,IAAA,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;AAAE,QAAA,MAAM,IAAI,aAAa,CAAC,yBAAyB,CAAC;AACxE,IAAA,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AAAE,QAAA,MAAM,IAAI,aAAa,CAAC,kCAAkC,CAAC;AAE3G,IAAA,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM;AAAE,QAAA,MAAM,IAAI,aAAa,CAAC,kCAAkC,CAAC;AACvF,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AAC9C,QAAA,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE;AAC9C,YAAA,MAAM,IAAI,aAAa,CAAC,2CAA2C,CAAC;QACtE;IACF;AAEA,IAAA,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU;AAC7B,IAAA,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE;AAC9B,QAAA,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,aAAa,CAAC,CAAA,qBAAA,EAAwB,GAAG,CAAC,EAAE,CAAA,CAAE,CAAC;AAC9E,QAAA,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;AACf,QAAA,IAAI,GAAG,CAAC,GAAG,EAAE;AACX,YAAA,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACnD,gBAAA,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE;oBAC1D,MAAM,IAAI,aAAa,CAAC,CAAA,OAAA,EAAU,GAAG,CAAC,EAAE,CAAA,qCAAA,EAAwC,KAAK,CAAA,CAAE,CAAC;gBAC1F;gBACA,IAAI,IAAI,IAAI,CAAC;oBAAE,MAAM,IAAI,aAAa,CAAC,CAAA,OAAA,EAAU,GAAG,CAAC,EAAE,CAAA,KAAA,EAAQ,KAAK,CAAA,aAAA,CAAe,CAAC;YACtF;QACF;IACF;AAEA,IAAA,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACrD,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;AACxD,QAAA,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,EAAE;AACjD,YAAA,MAAM,IAAI,aAAa,CAAC,UAAU,GAAG,CAAA,iBAAA,CAAmB,CAAC;QAC3D;QACA,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE;AACxC,YAAA,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,YAAY,EAAE;gBACjC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;oBACzB,MAAM,IAAI,aAAa,CAAC,CAAA,OAAA,EAAU,GAAG,CAAA,oDAAA,EAAuD,IAAI,CAAA,CAAE,CAAC;gBACrG;YACF;QACF;IACF;AACF;;AC5CA,SAAS,aAAa,CAAC,IAAc,EAAA;IACnC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;AAC/E;AAEA,SAAS,YAAY,CAAC,IAAgB,EAAA;IACpC,OAAO,IAAI,KAAK,MAAM,GAAG,YAAY,GAAG,WAAW;AACrD;AAEA,SAAS,kBAAkB,CAAC,IAAgB,EAAE,OAA2B,EAAE,SAAiB,EAAA;AAC1F,IAAA,IAAI,IAAI,KAAK,MAAM,EAAE;AACnB,QAAA,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,EAAE;QACvB,OAAO;AACL,YAAA,EAAE,SAAS,EAAE,qBAAqB,EAAE,aAAa,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC,OAAO,CAAC,EAAE;YAC/F,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,OAAO,CAAC,EAAE;SACjD;IACH;;IAEA,MAAM,WAAW,GAAqB,EAAE;IACxC,IAAI,OAAO,EAAE;QACX,WAAW,CAAC,IAAI,CAAC;AACf,YAAA,SAAS,EAAE,wBAAwB;AACnC,YAAA,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,CAAC,OAAO,CAAC;AACvB,YAAA,cAAc,EAAE,EAAE,eAAe,EAAE,oBAAoB,EAAE;AAC1D,SAAA,CAAC;IACJ;AACA,IAAA,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;AACpE,IAAA,OAAO,WAAW;AACpB;AAEA,SAAS,kBAAkB,CAAC,GAAW,EAAE,MAAkB,EAAE,OAA2B,EAAA;AACtF,IAAA,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM;AAClC,IAAA,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC;AAChF,IAAA,IAAI,IAAI,KAAK,MAAM,EAAE;QACnB,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC,IAAI,CAAC;AACzC,YAAA,KAAK,EAAE,MAAM;AACb,YAAA,MAAM,EAAE,OAAO;AACf,YAAA,gBAAgB,EAAE,IAAI;YACtB,WAAW;AACX,YAAA,IAAI,MAAM,CAAC,OAAO,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC;SAC5D;IACH;IACA,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC,IAAI,CAAC;AACzC,QAAA,KAAK,EAAE,KAAK;AACZ,QAAA,eAAe,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC;QACjC,MAAM,EAAE,IAAI,KAAK,KAAK,GAAG,MAAM,GAAG,KAAK;QACvC,WAAW;AACX,QAAA,IAAI,MAAM,CAAC,OAAO,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC;KAC5D;AACH;AAEM,SAAU,gBAAgB,CAAC,IAAc,EAAA;AAC7C,IAAA,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC;IACnC,MAAM,OAAO,GAAqC,EAAE;AACpD,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;AACxD,QAAA,OAAO,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC;IACzD;IACA,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;AACX,QAAA,IAAI,EAAE,MAAM;QACZ,OAAO;AACP,QAAA,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;AAC/B,QAAA,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE;KACrC;AACH;AAEA,SAAS,QAAQ,CAAC,MAA8B,EAAA;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAA,CAAA,EAAI,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,CAAE,CAAC;IACnE,OAAO,CAAA,CAAA,EAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;AAChC;AAEM,SAAU,YAAY,CAAC,IAAc,EAAA;AACzC,IAAA,MAAM,KAAK,GAAa,CAAC,mDAAmD,CAAC;IAC7E,KAAK,CAAC,IAAI,CAAC,CAAA,gBAAA,EAAmB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA,SAAA,EAAY,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA,YAAA,EAAe,IAAI,CAAC,MAAM,CAAA,EAAA,CAAI,CAAC;IAErG,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAA,CAAA,EAAI,CAAC,CAAC,EAAE,CAAA,CAAA,CAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;AAChE,IAAA,KAAK,CAAC,IAAI,CAAC,eAAe,QAAQ,CAAA,EAAA,CAAI,CAAC;AAEvC,IAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAA,EAAG,CAAC,CAAC,EAAE,CAAA,CAAA,EAAI,CAAC,GAAG,CAAC,CAAA,CAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;AAC1E,IAAA,KAAK,CAAC,IAAI,CAAC,WAAW,QAAQ,CAAA,EAAA,CAAI,CAAC;AAEnC,IAAA,MAAM,UAAU,GAAG,IAAI,CAAC;SACrB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG;AACnB,SAAA,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,EAAE,CAAA,GAAA,EAAM,QAAQ,CAAC,CAAC,CAAC,GAA6B,CAAC,CAAA,CAAE,CAAC;AACzE,IAAA,KAAK,CAAC,IAAI,CAAC,CAAA,cAAA,EAAiB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA,GAAA,CAAK,CAAC;AAExD,IAAA,MAAM,UAAU,GAAG,IAAI,CAAC;SACrB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,SAAS;AACnC,SAAA,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,EAAE,CAAA,GAAA,EAAM,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAA,CAAG,GAAG,CAAC,CAAC,KAAK,CAAA,CAAE,CAAC;IAC5F,IAAI,UAAU,CAAC,MAAM;AAAE,QAAA,KAAK,CAAC,IAAI,CAAC,CAAA,YAAA,EAAe,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA,GAAA,CAAK,CAAC;IAE7E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI;AAChC;AAEM,SAAU,SAAS,CAAC,IAAc,EAAA;IACtC,MAAM,GAAG,GAA2B,EAAE;AACtC,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;AACxD,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM;QAClC,IAAI,IAAI,KAAK,MAAM;YAAE;QACrB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,IAAI,KAAK,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;AAC1E,QAAA,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI;IACjB;AACA,IAAA,OAAO,GAAG;AACZ;AAEM,SAAU,WAAW,CAAC,IAAc,EAAA;IACxC,MAAM,KAAK,GAAmB,EAAE;AAChC,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;AACxD,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM;QAClC,IAAI,IAAI,KAAK,MAAM;YAAE;QACrB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,IAAI,KAAK,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAC1E,KAAK,CAAC,IAAI,CAAC;AACT,YAAA,MAAM,EAAE,GAAG;YACX,IAAI;AACJ,YAAA,cAAc,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC;YAChC,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;AAC9D,YAAA,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM;AACrC,SAAA,CAAC;IACJ;AACA,IAAA,OAAO,KAAK;AACd;AAEM,SAAU,cAAc,CAAC,IAAc,EAAA;IAC3C,OAAO;QACL,OAAO,EAAE,IAAI,CAAC;aACX,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG;AACnB,aAAA,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,GAAI,CAAC,CAAC,GAA8B,EAAE,EAAE,CAAC,CAAC;KACjH;AACH;;AClIM,SAAU,UAAU,CAAC,IAAc,EAAA;IACvC,YAAY,CAAC,IAAI,CAAC;IAClB,OAAO;QACL,IAAI;AACJ,QAAA,cAAc,EAAE,gBAAgB,CAAC,IAAI,CAAC;AACtC,QAAA,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC;AAC9B,QAAA,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC;AACxB,QAAA,SAAS,EAAE,WAAW,CAAC,IAAI,CAAC;AAC5B,QAAA,QAAQ,EAAE,cAAc,CAAC,IAAI,CAAC;QAC9B,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB;AACH;;ACZM,SAAU,cAAc,CAAC,KAAgB,EAAE,QAAgB,EAAA;AAC/D,IAAA,OAAO,KAAK,CAAC,UAAU,GAAG,IAAI,GAAG,QAAQ;AAC3C;AAEM,SAAU,UAAU,CACxB,IAAc,EACd,IAA0B,EAAA;AAE1B,IAAA,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC;IAC9B,OAAO;AACL,QAAA,qBAAqB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,YAAY,EAAE,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC;KACnD;AACH;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
interface GameDefinition {
|
|
2
|
+
id: string;
|
|
3
|
+
type: 'SLOT' | 'TABLE';
|
|
4
|
+
actions: Record<string, ActionDefinition>;
|
|
5
|
+
bet_levels?: number[] | BetLevelsConfig;
|
|
6
|
+
max_win?: MaxWinConfig;
|
|
7
|
+
persistent_state?: PersistentStateConfig;
|
|
8
|
+
/**
|
|
9
|
+
* Session expiry duration as a Go-style duration string ("24h", "2h", "5ms").
|
|
10
|
+
* Mirrors the platform's GameDefinition.SessionTTL — defaults to 24h on
|
|
11
|
+
* the server when absent. Used by DevBridge to surface SESSION_EXPIRED.
|
|
12
|
+
*/
|
|
13
|
+
session_ttl?: string;
|
|
14
|
+
}
|
|
15
|
+
interface BetLevelsConfig {
|
|
16
|
+
levels?: number[];
|
|
17
|
+
min?: number;
|
|
18
|
+
max?: number;
|
|
19
|
+
}
|
|
20
|
+
interface MaxWinConfig {
|
|
21
|
+
multiplier?: number;
|
|
22
|
+
fixed?: number;
|
|
23
|
+
}
|
|
24
|
+
interface PersistentStateConfig {
|
|
25
|
+
vars: string[];
|
|
26
|
+
exposed_vars: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* v5 action contract — cost_multiplier and feature_data live on the action
|
|
30
|
+
* itself. Removed in v5: top-level `buy_bonus`/`ante_bet` blocks, debit modes
|
|
31
|
+
* `'buy_bonus_cost'`/`'ante_bet_cost'`, action.buy_bonus_mode, params.ante_bet/
|
|
32
|
+
* params.buy_bonus flags. Lua reads action context via `state.action` and
|
|
33
|
+
* `state.action_config = { cost_multiplier, feature_data }`.
|
|
34
|
+
*/
|
|
35
|
+
interface ActionDefinition {
|
|
36
|
+
stage: string;
|
|
37
|
+
/** Either 'bet' (debit = bet × cost_multiplier) or 'none'/empty (no debit). */
|
|
38
|
+
debit: 'bet' | 'none';
|
|
39
|
+
/** Multiplier on `bet` when debit==='bet'. Defaults to 1.0. */
|
|
40
|
+
cost_multiplier?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Opaque action-specific configuration exposed to Lua as
|
|
43
|
+
* `state.action_config.feature_data`. Common keys: `scatter_distribution`
|
|
44
|
+
* for buy-bonus actions, forced symbol counts, etc.
|
|
45
|
+
*/
|
|
46
|
+
feature_data?: Record<string, unknown>;
|
|
47
|
+
credit?: 'win' | 'none' | 'defer';
|
|
48
|
+
requires_session?: boolean;
|
|
49
|
+
transitions: TransitionRule[];
|
|
50
|
+
input_schema?: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
interface TransitionRule {
|
|
53
|
+
condition: string;
|
|
54
|
+
creates_session?: boolean;
|
|
55
|
+
complete_session?: boolean;
|
|
56
|
+
credit_override?: 'defer';
|
|
57
|
+
next_actions: string[];
|
|
58
|
+
session_config?: SessionConfig;
|
|
59
|
+
add_spins_var?: string;
|
|
60
|
+
}
|
|
61
|
+
interface SessionConfig {
|
|
62
|
+
total_spins_var: string;
|
|
63
|
+
persistent_vars?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type SymbolKind = 'high' | 'mid' | 'low' | 'wild' | 'scatter' | 'multiplier' | 'custom';
|
|
67
|
+
type ActionRole = 'base' | 'feature' | 'buy' | 'free';
|
|
68
|
+
interface SymbolSpec {
|
|
69
|
+
id: string;
|
|
70
|
+
name?: string;
|
|
71
|
+
kind: SymbolKind;
|
|
72
|
+
pay?: Record<number, number>;
|
|
73
|
+
/** Multiplier-symbol x-value(s) (e.g. 100, or [2,3,5]). */
|
|
74
|
+
value?: number | number[];
|
|
75
|
+
/** Arbitrary per-symbol config (tier tables, behavior flags). */
|
|
76
|
+
meta?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
interface ActionSpec {
|
|
79
|
+
role?: ActionRole;
|
|
80
|
+
stage?: string;
|
|
81
|
+
cost?: number;
|
|
82
|
+
mode?: string;
|
|
83
|
+
feature?: Record<string, unknown>;
|
|
84
|
+
/** Shell display for buy/feature actions (SSOT). */
|
|
85
|
+
title?: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
/** Target RTP for THIS mode (0..1), e.g. 0.96. Single source of truth: seeds the math
|
|
88
|
+
* pipeline's `targetRTP` AND the Game Info per-mode table. `math.config` keeps only optimizer
|
|
89
|
+
* tuning (CV / hit-rate / nRowsOut / tolerances), never the RTP. */
|
|
90
|
+
rtp?: number;
|
|
91
|
+
/** Max win for THIS mode as a bet-multiplier; defaults to the game-level `spec.maxWin`. Seeds the
|
|
92
|
+
* curate cap (`capMaxWin`) for the mode AND the Game Info per-mode "Max Win" cell. */
|
|
93
|
+
maxWin?: number;
|
|
94
|
+
transitions?: TransitionRule[];
|
|
95
|
+
}
|
|
96
|
+
interface GameSpec {
|
|
97
|
+
id: string;
|
|
98
|
+
type: 'slot';
|
|
99
|
+
grid: {
|
|
100
|
+
cols: number;
|
|
101
|
+
rows: number;
|
|
102
|
+
};
|
|
103
|
+
betLevels: number[];
|
|
104
|
+
defaultBet?: number;
|
|
105
|
+
maxWin: number;
|
|
106
|
+
currency?: string;
|
|
107
|
+
symbols: SymbolSpec[];
|
|
108
|
+
actions: Record<string, ActionSpec>;
|
|
109
|
+
/** Open hint for codegen/UI: 'cascade' | 'cluster' | 'ways' | 'lines' | … */
|
|
110
|
+
mechanic?: string;
|
|
111
|
+
/** Game-level escape hatch. */
|
|
112
|
+
meta?: Record<string, unknown>;
|
|
113
|
+
}
|
|
114
|
+
interface MathModeSpec {
|
|
115
|
+
action: string;
|
|
116
|
+
mode: string;
|
|
117
|
+
costMultiplier: number;
|
|
118
|
+
/** Target RTP for the mode (from `ActionSpec.rtp`), if declared — seeds the math pipeline. */
|
|
119
|
+
rtp?: number;
|
|
120
|
+
/** Max win (bet-multiplier) for the mode: `ActionSpec.maxWin` ?? game-level `spec.maxWin`. */
|
|
121
|
+
maxWin: number;
|
|
122
|
+
}
|
|
123
|
+
interface PaytableEntry {
|
|
124
|
+
id: string;
|
|
125
|
+
name: string;
|
|
126
|
+
kind: SymbolKind;
|
|
127
|
+
pay: Record<number, number>;
|
|
128
|
+
}
|
|
129
|
+
interface PaytableView {
|
|
130
|
+
symbols: PaytableEntry[];
|
|
131
|
+
}
|
|
132
|
+
interface GameModel {
|
|
133
|
+
spec: GameSpec;
|
|
134
|
+
gameDefinition: GameDefinition;
|
|
135
|
+
luaPrelude: string;
|
|
136
|
+
modeMap: Record<string, string>;
|
|
137
|
+
mathModes: MathModeSpec[];
|
|
138
|
+
paytable: PaytableView;
|
|
139
|
+
symbols: SymbolSpec[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
declare class GameSpecError extends Error {
|
|
143
|
+
constructor(message: string);
|
|
144
|
+
}
|
|
145
|
+
declare function validateSpec(spec: GameSpec): void;
|
|
146
|
+
|
|
147
|
+
declare function toGameDefinition(spec: GameSpec): GameDefinition;
|
|
148
|
+
declare function toLuaPrelude(spec: GameSpec): string;
|
|
149
|
+
declare function toModeMap(spec: GameSpec): Record<string, string>;
|
|
150
|
+
declare function toMathModes(spec: GameSpec): MathModeSpec[];
|
|
151
|
+
declare function toPaytableView(spec: GameSpec): PaytableView;
|
|
152
|
+
|
|
153
|
+
declare function defineGame(spec: GameSpec): GameModel;
|
|
154
|
+
|
|
155
|
+
declare function buildLuaScript(model: GameModel, logicLua: string): string;
|
|
156
|
+
declare function exportGame(spec: GameSpec, opts: {
|
|
157
|
+
logicLua: string;
|
|
158
|
+
}): {
|
|
159
|
+
'gameDefinition.json': string;
|
|
160
|
+
'script.lua': string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export { GameSpecError, buildLuaScript, defineGame, exportGame, toGameDefinition, toLuaPrelude, toMathModes, toModeMap, toPaytableView, validateSpec };
|
|
164
|
+
export type { ActionDefinition, ActionRole, ActionSpec, GameDefinition, GameModel, GameSpec, MathModeSpec, MaxWinConfig, PaytableEntry, PaytableView, SymbolKind, SymbolSpec, TransitionRule };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
class GameSpecError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'GameSpecError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
function validateSpec(spec) {
|
|
8
|
+
if (!spec.id || spec.id.trim() === '')
|
|
9
|
+
throw new GameSpecError('spec.id is required');
|
|
10
|
+
if (spec.maxWin <= 0)
|
|
11
|
+
throw new GameSpecError('spec.maxWin must be > 0');
|
|
12
|
+
if (spec.grid.cols <= 0 || spec.grid.rows <= 0)
|
|
13
|
+
throw new GameSpecError('spec.grid dimensions must be > 0');
|
|
14
|
+
if (!spec.betLevels.length)
|
|
15
|
+
throw new GameSpecError('spec.betLevels must be non-empty');
|
|
16
|
+
for (let i = 1; i < spec.betLevels.length; i++) {
|
|
17
|
+
if (spec.betLevels[i] <= spec.betLevels[i - 1]) {
|
|
18
|
+
throw new GameSpecError('spec.betLevels must be strictly ascending');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const ids = new Set();
|
|
22
|
+
for (const sym of spec.symbols) {
|
|
23
|
+
if (ids.has(sym.id))
|
|
24
|
+
throw new GameSpecError(`duplicate symbol id: ${sym.id}`);
|
|
25
|
+
ids.add(sym.id);
|
|
26
|
+
if (sym.pay) {
|
|
27
|
+
for (const [count, mult] of Object.entries(sym.pay)) {
|
|
28
|
+
if (Number(count) <= 0 || !Number.isInteger(Number(count))) {
|
|
29
|
+
throw new GameSpecError(`symbol ${sym.id} pay key must be a positive integer: ${count}`);
|
|
30
|
+
}
|
|
31
|
+
if (mult <= 0)
|
|
32
|
+
throw new GameSpecError(`symbol ${sym.id} pay[${count}] must be > 0`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const actionKeys = new Set(Object.keys(spec.actions));
|
|
37
|
+
for (const [key, action] of Object.entries(spec.actions)) {
|
|
38
|
+
if (action.cost !== undefined && action.cost <= 0) {
|
|
39
|
+
throw new GameSpecError(`action ${key} cost must be > 0`);
|
|
40
|
+
}
|
|
41
|
+
for (const t of action.transitions ?? []) {
|
|
42
|
+
for (const next of t.next_actions) {
|
|
43
|
+
if (!actionKeys.has(next)) {
|
|
44
|
+
throw new GameSpecError(`action ${key} transition next_actions references unknown action: ${next}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function freeActionKey(spec) {
|
|
52
|
+
return Object.keys(spec.actions).find((k) => spec.actions[k].role === 'free');
|
|
53
|
+
}
|
|
54
|
+
function defaultStage(role) {
|
|
55
|
+
return role === 'free' ? 'free_spins' : 'base_game';
|
|
56
|
+
}
|
|
57
|
+
function defaultTransitions(role, freeKey, actionKey) {
|
|
58
|
+
if (role === 'free') {
|
|
59
|
+
if (!freeKey)
|
|
60
|
+
return [];
|
|
61
|
+
return [
|
|
62
|
+
{ condition: 'retrigger_spins > 0', add_spins_var: 'retrigger_spins', next_actions: [freeKey] },
|
|
63
|
+
{ condition: 'always', next_actions: [freeKey] },
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
// base | buy — always include an "always" fallback so the engine can route back
|
|
67
|
+
const transitions = [];
|
|
68
|
+
if (freeKey) {
|
|
69
|
+
transitions.push({
|
|
70
|
+
condition: 'free_spins_awarded > 0',
|
|
71
|
+
creates_session: true,
|
|
72
|
+
next_actions: [freeKey],
|
|
73
|
+
session_config: { total_spins_var: 'free_spins_awarded' },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
transitions.push({ condition: 'always', next_actions: [actionKey] });
|
|
77
|
+
return transitions;
|
|
78
|
+
}
|
|
79
|
+
function toActionDefinition(key, action, freeKey) {
|
|
80
|
+
const role = action.role ?? 'base';
|
|
81
|
+
const transitions = action.transitions ?? defaultTransitions(role, freeKey, key);
|
|
82
|
+
if (role === 'free') {
|
|
83
|
+
return {
|
|
84
|
+
stage: action.stage ?? defaultStage(role),
|
|
85
|
+
debit: 'none',
|
|
86
|
+
credit: 'defer',
|
|
87
|
+
requires_session: true,
|
|
88
|
+
transitions,
|
|
89
|
+
...(action.feature ? { feature_data: action.feature } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
stage: action.stage ?? defaultStage(role),
|
|
94
|
+
debit: 'bet',
|
|
95
|
+
cost_multiplier: action.cost ?? 1,
|
|
96
|
+
credit: role === 'buy' ? 'none' : 'win',
|
|
97
|
+
transitions,
|
|
98
|
+
...(action.feature ? { feature_data: action.feature } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function toGameDefinition(spec) {
|
|
102
|
+
const freeKey = freeActionKey(spec);
|
|
103
|
+
const actions = {};
|
|
104
|
+
for (const [key, action] of Object.entries(spec.actions)) {
|
|
105
|
+
actions[key] = toActionDefinition(key, action, freeKey);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
id: spec.id,
|
|
109
|
+
type: 'SLOT',
|
|
110
|
+
actions,
|
|
111
|
+
bet_levels: [...spec.betLevels],
|
|
112
|
+
max_win: { multiplier: spec.maxWin },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function luaTable(record) {
|
|
116
|
+
const parts = Object.entries(record).map(([k, v]) => `[${k}]=${v}`);
|
|
117
|
+
return `{${parts.join(', ')}}`;
|
|
118
|
+
}
|
|
119
|
+
function toLuaPrelude(spec) {
|
|
120
|
+
const lines = ['-- AUTO-GENERATED from game.spec.ts — do not edit'];
|
|
121
|
+
lines.push(`SPEC = { cols = ${spec.grid.cols}, rows = ${spec.grid.rows}, max_win = ${spec.maxWin} }`);
|
|
122
|
+
const symNames = spec.symbols.map((s) => `"${s.id}"`).join(', ');
|
|
123
|
+
lines.push(`SYMBOLS = { ${symNames} }`);
|
|
124
|
+
const symIndex = spec.symbols.map((s, i) => `${s.id}=${i + 1}`).join(', ');
|
|
125
|
+
lines.push(`SYM = { ${symIndex} }`);
|
|
126
|
+
const payEntries = spec.symbols
|
|
127
|
+
.filter((s) => s.pay)
|
|
128
|
+
.map((s) => ` ${s.id} = ${luaTable(s.pay)}`);
|
|
129
|
+
lines.push(`PAYTABLE = {\n${payEntries.join(',\n')}\n}`);
|
|
130
|
+
const valEntries = spec.symbols
|
|
131
|
+
.filter((s) => s.value !== undefined)
|
|
132
|
+
.map((s) => ` ${s.id} = ${Array.isArray(s.value) ? `{${s.value.join(', ')}}` : s.value}`);
|
|
133
|
+
if (valEntries.length)
|
|
134
|
+
lines.push(`VALUES = {\n${valEntries.join(',\n')}\n}`);
|
|
135
|
+
return lines.join('\n') + '\n';
|
|
136
|
+
}
|
|
137
|
+
function toModeMap(spec) {
|
|
138
|
+
const map = {};
|
|
139
|
+
for (const [key, action] of Object.entries(spec.actions)) {
|
|
140
|
+
const role = action.role ?? 'base';
|
|
141
|
+
if (role === 'free')
|
|
142
|
+
continue;
|
|
143
|
+
const mode = action.mode ?? (role === 'base' ? 'BASE' : key.toUpperCase());
|
|
144
|
+
map[key] = mode;
|
|
145
|
+
}
|
|
146
|
+
return map;
|
|
147
|
+
}
|
|
148
|
+
function toMathModes(spec) {
|
|
149
|
+
const modes = [];
|
|
150
|
+
for (const [key, action] of Object.entries(spec.actions)) {
|
|
151
|
+
const role = action.role ?? 'base';
|
|
152
|
+
if (role === 'free')
|
|
153
|
+
continue;
|
|
154
|
+
const mode = action.mode ?? (role === 'base' ? 'BASE' : key.toUpperCase());
|
|
155
|
+
modes.push({
|
|
156
|
+
action: key,
|
|
157
|
+
mode,
|
|
158
|
+
costMultiplier: action.cost ?? 1,
|
|
159
|
+
...(typeof action.rtp === 'number' ? { rtp: action.rtp } : {}),
|
|
160
|
+
maxWin: action.maxWin ?? spec.maxWin,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return modes;
|
|
164
|
+
}
|
|
165
|
+
function toPaytableView(spec) {
|
|
166
|
+
return {
|
|
167
|
+
symbols: spec.symbols
|
|
168
|
+
.filter((s) => s.pay)
|
|
169
|
+
.map((s) => ({ id: s.id, name: s.name ?? s.id, kind: s.kind, pay: { ...s.pay } })),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function defineGame(spec) {
|
|
174
|
+
validateSpec(spec);
|
|
175
|
+
return {
|
|
176
|
+
spec,
|
|
177
|
+
gameDefinition: toGameDefinition(spec),
|
|
178
|
+
luaPrelude: toLuaPrelude(spec),
|
|
179
|
+
modeMap: toModeMap(spec),
|
|
180
|
+
mathModes: toMathModes(spec),
|
|
181
|
+
paytable: toPaytableView(spec),
|
|
182
|
+
symbols: spec.symbols,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildLuaScript(model, logicLua) {
|
|
187
|
+
return model.luaPrelude + '\n' + logicLua;
|
|
188
|
+
}
|
|
189
|
+
function exportGame(spec, opts) {
|
|
190
|
+
const model = defineGame(spec);
|
|
191
|
+
return {
|
|
192
|
+
'gameDefinition.json': JSON.stringify(model.gameDefinition, null, 2),
|
|
193
|
+
'script.lua': buildLuaScript(model, opts.logicLua),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { GameSpecError, buildLuaScript, defineGame, exportGame, toGameDefinition, toLuaPrelude, toMathModes, toModeMap, toPaytableView, validateSpec };
|
|
198
|
+
//# sourceMappingURL=game-spec.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-spec.esm.js","sources":["../src/game-spec/validate.ts","../src/game-spec/derive.ts","../src/game-spec/defineGame.ts","../src/game-spec/export.ts"],"sourcesContent":[null,null,null,null],"names":[],"mappings":"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"}
|