theref-sdk 0.1.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/README.md +326 -0
- package/README.md:Zone.Identifier +0 -0
- package/dist/index.d.mts +229 -0
- package/dist/index.d.ts +229 -0
- package/dist/index.js +634 -0
- package/dist/index.mjs +602 -0
- package/package.json +36 -0
- package/package.json:Zone.Identifier +0 -0
- package/src/adapters/normalizer.ts +252 -0
- package/src/adapters/normalizer.ts:Zone.Identifier +0 -0
- package/src/client/TheRefClient.ts +444 -0
- package/src/client/TheRefClient.ts:Zone.Identifier +0 -0
- package/src/index.ts +56 -0
- package/src/index.ts:Zone.Identifier +0 -0
- package/src/types/index.ts +149 -0
- package/src/types/index.ts:Zone.Identifier +0 -0
- package/src/utils/networks.ts +39 -0
- package/src/utils/networks.ts:Zone.Identifier +0 -0
- package/tsconfig.json +16 -0
- package/tsconfig.json:Zone.Identifier +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
NETWORKS: () => NETWORKS,
|
|
24
|
+
TheRefClient: () => TheRefClient,
|
|
25
|
+
createTheRef: () => createTheRef,
|
|
26
|
+
gidToNum: () => gidToNum,
|
|
27
|
+
normalizeMove: () => normalizeMove,
|
|
28
|
+
resolveNetwork: () => resolveNetwork
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/client/TheRefClient.ts
|
|
33
|
+
var import_genlayer_js = require("genlayer-js");
|
|
34
|
+
var import_types = require("genlayer-js/types");
|
|
35
|
+
|
|
36
|
+
// src/utils/networks.ts
|
|
37
|
+
var NETWORKS = {
|
|
38
|
+
bradbury: {
|
|
39
|
+
id: "bradbury",
|
|
40
|
+
rpc: "https://rpc-bradbury.genlayer.com",
|
|
41
|
+
chainId: 4221,
|
|
42
|
+
addresses: {
|
|
43
|
+
CORE: "0xA29CfFC83d32fe924cFf1F1bDCf21555CCC96206",
|
|
44
|
+
LB: "0x5D417F296b17656c9b950236feE66F63E22d8A54",
|
|
45
|
+
ORG: "0x440b28afc1804fc1E4AA8f5b559C18F7bCf43B3A",
|
|
46
|
+
FEE: "0x88A0A4d573fD9C63433E457e94d266D7904278C2",
|
|
47
|
+
TRN: "0xbcc0E82a17491297E0c4938606624Fa04e6abA1B"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
studionet: {
|
|
51
|
+
id: "studionet",
|
|
52
|
+
rpc: "https://studio.genlayer.com/api",
|
|
53
|
+
chainId: 61999,
|
|
54
|
+
addresses: {
|
|
55
|
+
CORE: "0x88CAA18419714aA38CdF53c0E603141c48fa3238",
|
|
56
|
+
LB: "0x8A2d05Df048A64cc6B83682a431ade05030e4BBB",
|
|
57
|
+
ORG: "0x265ef96A5230F13836c553D7DD2B9D7c3fE14aE1",
|
|
58
|
+
FEE: "0x0000000000000000000000000000000000000000",
|
|
59
|
+
TRN: "0x44f7c1bDa293B9cdBD79a6dfb66bD45696dEa4A6"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var DEFAULT_WS_URL = "wss://ws.theref.fun";
|
|
64
|
+
function resolveNetwork(network) {
|
|
65
|
+
if (typeof network === "string") return NETWORKS[network];
|
|
66
|
+
return network;
|
|
67
|
+
}
|
|
68
|
+
function gidToNum(gid) {
|
|
69
|
+
return parseInt(gid.replace(/^0+/, "") || "0", 36) || 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/adapters/normalizer.ts
|
|
73
|
+
function adaptChess(move) {
|
|
74
|
+
if (typeof move === "string") {
|
|
75
|
+
if (/^[a-h][1-8]$|^[NBRQK][a-h]?[1-8]?x?[a-h][1-8][+#]?$|^O-O(-O)?[+#]?$/.test(move)) {
|
|
76
|
+
return move;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (typeof move === "object" && move !== null && !Array.isArray(move)) {
|
|
80
|
+
const m = move;
|
|
81
|
+
if (m.from && m.to) {
|
|
82
|
+
const promo = m.promotion ? `=${String(m.promotion).toUpperCase()}` : "";
|
|
83
|
+
const piece = m.piece ? `${String(m.piece).toUpperCase()} ` : "";
|
|
84
|
+
return `${piece}${m.from}${m.promotion ? "" : ""}${m.to}${promo}`.trim();
|
|
85
|
+
}
|
|
86
|
+
if (m.piece && m.from && m.to) {
|
|
87
|
+
const pieceMap = {
|
|
88
|
+
knight: "N",
|
|
89
|
+
bishop: "B",
|
|
90
|
+
rook: "R",
|
|
91
|
+
queen: "Q",
|
|
92
|
+
king: "K",
|
|
93
|
+
pawn: ""
|
|
94
|
+
};
|
|
95
|
+
const p = pieceMap[String(m.piece).toLowerCase()] ?? "";
|
|
96
|
+
return `${p}${m.to}`;
|
|
97
|
+
}
|
|
98
|
+
if (m.uci && typeof m.uci === "string") {
|
|
99
|
+
return `${m.uci.slice(0, 2)}-${m.uci.slice(2, 4)}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(move) && move.length === 4 && move.every((n) => typeof n === "number")) {
|
|
103
|
+
const files = "abcdefgh";
|
|
104
|
+
return `${files[move[0]]}${move[1] + 1}-${files[move[2]]}${move[3] + 1}`;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
function adaptRPS(move) {
|
|
109
|
+
const choices = {
|
|
110
|
+
rock: "Rock",
|
|
111
|
+
paper: "Paper",
|
|
112
|
+
scissors: "Scissors",
|
|
113
|
+
r: "Rock",
|
|
114
|
+
p: "Paper",
|
|
115
|
+
s: "Scissors",
|
|
116
|
+
"0": "Rock",
|
|
117
|
+
"1": "Paper",
|
|
118
|
+
"2": "Scissors",
|
|
119
|
+
stone: "Rock",
|
|
120
|
+
shears: "Scissors",
|
|
121
|
+
lizard: "Lizard",
|
|
122
|
+
spock: "Spock"
|
|
123
|
+
// RPSLS variant
|
|
124
|
+
};
|
|
125
|
+
if (typeof move === "string") {
|
|
126
|
+
const normalized = choices[move.toLowerCase().trim()];
|
|
127
|
+
if (normalized) return normalized;
|
|
128
|
+
}
|
|
129
|
+
if (typeof move === "number") {
|
|
130
|
+
const normalized = choices[String(move)];
|
|
131
|
+
if (normalized) return normalized;
|
|
132
|
+
}
|
|
133
|
+
if (typeof move === "object" && !Array.isArray(move) && move !== null) {
|
|
134
|
+
const m = move;
|
|
135
|
+
const choice = m.choice ?? m.action ?? m.move ?? m.pick;
|
|
136
|
+
if (choice) return adaptRPS(choice);
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function adaptCombat(move) {
|
|
141
|
+
if (typeof move === "object" && !Array.isArray(move) && move !== null) {
|
|
142
|
+
const m = move;
|
|
143
|
+
const parts = [];
|
|
144
|
+
if (m.player || m.character || m.unit) {
|
|
145
|
+
parts.push(String(m.player ?? m.character ?? m.unit));
|
|
146
|
+
}
|
|
147
|
+
const action = m.action ?? m.move ?? m.skill ?? m.ability ?? m.attack ?? m.spell ?? m.card;
|
|
148
|
+
if (action) parts.push(`uses ${action}`);
|
|
149
|
+
const target = m.target ?? m.enemy ?? m.opponent;
|
|
150
|
+
if (target) parts.push(`on ${target}`);
|
|
151
|
+
if (m.power !== void 0) parts.push(`(power: ${m.power})`);
|
|
152
|
+
if (m.damage !== void 0) parts.push(`(damage: ${m.damage})`);
|
|
153
|
+
if (m.mana_cost !== void 0) parts.push(`(mana: ${m.mana_cost})`);
|
|
154
|
+
if (m.combo) parts.push(`via combo: ${Array.isArray(m.combo) ? m.combo.join("+") : m.combo}`);
|
|
155
|
+
if (m.type) parts.push(`[${m.type} type]`);
|
|
156
|
+
if (m.element) parts.push(`[${m.element}]`);
|
|
157
|
+
if (m.special) parts.push(`special: ${m.special}`);
|
|
158
|
+
if (m.position) parts.push(`at ${JSON.stringify(m.position)}`);
|
|
159
|
+
if (parts.length > 0) return parts.join(" ");
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function adaptStrategy(move) {
|
|
164
|
+
if (typeof move === "object" && !Array.isArray(move) && move !== null) {
|
|
165
|
+
const m = move;
|
|
166
|
+
const parts = [];
|
|
167
|
+
const action = m.action ?? m.command ?? m.order;
|
|
168
|
+
if (action) parts.push(String(action));
|
|
169
|
+
const unit = m.unit ?? m.piece ?? m.troop ?? m.building;
|
|
170
|
+
if (unit) parts.push(String(unit));
|
|
171
|
+
if (m.from && m.to) {
|
|
172
|
+
parts.push(`from ${JSON.stringify(m.from)} to ${JSON.stringify(m.to)}`);
|
|
173
|
+
} else if (m.position ?? m.target ?? m.location) {
|
|
174
|
+
parts.push(`at ${JSON.stringify(m.position ?? m.target ?? m.location)}`);
|
|
175
|
+
}
|
|
176
|
+
if (m.attack) parts.push(`attacking with ${m.attack}`);
|
|
177
|
+
if (m.defend) parts.push(`defending with ${m.defend}`);
|
|
178
|
+
if (m.resource !== void 0) parts.push(`(cost: ${m.resource})`);
|
|
179
|
+
if (parts.length > 0) return parts.join(" ");
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
function adaptTrivia(move) {
|
|
184
|
+
if (typeof move === "object" && !Array.isArray(move) && move !== null) {
|
|
185
|
+
const m = move;
|
|
186
|
+
const answer = m.answer ?? m.response ?? m.text ?? m.value;
|
|
187
|
+
if (answer !== void 0) return String(answer);
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function adaptDebate(move) {
|
|
192
|
+
if (typeof move === "object" && !Array.isArray(move) && move !== null) {
|
|
193
|
+
const m = move;
|
|
194
|
+
const parts = [];
|
|
195
|
+
if (m.position) parts.push(`Position: ${m.position}`);
|
|
196
|
+
if (m.argument) parts.push(`Argument: ${m.argument}`);
|
|
197
|
+
if (m.evidence) parts.push(`Evidence: ${m.evidence}`);
|
|
198
|
+
if (m.rebuttal) parts.push(`Rebuttal: ${m.rebuttal}`);
|
|
199
|
+
if (parts.length > 0) return parts.join(". ");
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
function flattenObject(obj, prefix = "") {
|
|
204
|
+
return Object.entries(obj).map(([key, val]) => {
|
|
205
|
+
const label = prefix ? `${prefix}.${key}` : key;
|
|
206
|
+
if (val === null || val === void 0) return null;
|
|
207
|
+
if (typeof val === "object" && !Array.isArray(val)) {
|
|
208
|
+
return flattenObject(val, label);
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(val)) {
|
|
211
|
+
return `${label}: [${val.map((v) => String(v)).join(", ")}]`;
|
|
212
|
+
}
|
|
213
|
+
return `${label}: ${val}`;
|
|
214
|
+
}).filter(Boolean).join(", ");
|
|
215
|
+
}
|
|
216
|
+
function normalizeMove(move, gameHint) {
|
|
217
|
+
if (typeof move === "string") {
|
|
218
|
+
return { raw: move, text: move.trim(), adapter: "passthrough" };
|
|
219
|
+
}
|
|
220
|
+
if (typeof move === "number") {
|
|
221
|
+
return { raw: move, text: String(move), adapter: "number" };
|
|
222
|
+
}
|
|
223
|
+
if (typeof move === "boolean") {
|
|
224
|
+
return { raw: move, text: move ? "Yes" : "No", adapter: "boolean" };
|
|
225
|
+
}
|
|
226
|
+
if (move === null || move === void 0) {
|
|
227
|
+
return { raw: move, text: "No move", adapter: "null" };
|
|
228
|
+
}
|
|
229
|
+
const hint = gameHint?.toLowerCase() ?? "";
|
|
230
|
+
if (hint.includes("chess")) {
|
|
231
|
+
const result = adaptChess(move);
|
|
232
|
+
if (result) return { raw: move, text: result, adapter: "chess" };
|
|
233
|
+
}
|
|
234
|
+
if (hint.includes("rock") || hint.includes("rps") || hint.includes("scissors")) {
|
|
235
|
+
const result = adaptRPS(move);
|
|
236
|
+
if (result) return { raw: move, text: result, adapter: "rps" };
|
|
237
|
+
}
|
|
238
|
+
if (hint.includes("trivia") || hint.includes("quiz") || hint.includes("question")) {
|
|
239
|
+
const result = adaptTrivia(move);
|
|
240
|
+
if (result) return { raw: move, text: result, adapter: "trivia" };
|
|
241
|
+
}
|
|
242
|
+
if (hint.includes("debate") || hint.includes("argument")) {
|
|
243
|
+
const result = adaptDebate(move);
|
|
244
|
+
if (result) return { raw: move, text: result, adapter: "debate" };
|
|
245
|
+
}
|
|
246
|
+
if (typeof move === "object") {
|
|
247
|
+
const chess = adaptChess(move);
|
|
248
|
+
if (chess) return { raw: move, text: chess, adapter: "chess" };
|
|
249
|
+
const rps = adaptRPS(move);
|
|
250
|
+
if (rps) return { raw: move, text: rps, adapter: "rps" };
|
|
251
|
+
const combat = adaptCombat(move);
|
|
252
|
+
if (combat) return { raw: move, text: combat, adapter: "combat" };
|
|
253
|
+
const strategy = adaptStrategy(move);
|
|
254
|
+
if (strategy) return { raw: move, text: strategy, adapter: "strategy" };
|
|
255
|
+
const trivia = adaptTrivia(move);
|
|
256
|
+
if (trivia) return { raw: move, text: trivia, adapter: "trivia" };
|
|
257
|
+
const debate = adaptDebate(move);
|
|
258
|
+
if (debate) return { raw: move, text: debate, adapter: "debate" };
|
|
259
|
+
if (Array.isArray(move)) {
|
|
260
|
+
const text = move.map((m) => normalizeMove(m).text).join(", ");
|
|
261
|
+
return { raw: move, text, adapter: "array" };
|
|
262
|
+
}
|
|
263
|
+
const flat = flattenObject(move);
|
|
264
|
+
return { raw: move, text: flat, adapter: "generic-object" };
|
|
265
|
+
}
|
|
266
|
+
return { raw: move, text: String(move), adapter: "fallback" };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/client/TheRefClient.ts
|
|
270
|
+
var TheRefClient = class {
|
|
271
|
+
constructor(config) {
|
|
272
|
+
this.network = resolveNetwork(config.network);
|
|
273
|
+
this.retries = config.retries ?? 300;
|
|
274
|
+
this.pollInterval = config.pollInterval ?? 5e3;
|
|
275
|
+
this.wsUrl = config.wsUrl ?? DEFAULT_WS_URL;
|
|
276
|
+
const chain = this.network.chainId === 4221 ? import_genlayer_js.chains.testnetBradbury : import_genlayer_js.chains.studionet;
|
|
277
|
+
let account;
|
|
278
|
+
if (config.privateKey) {
|
|
279
|
+
account = (0, import_genlayer_js.createAccount)(config.privateKey);
|
|
280
|
+
} else if (config.walletAddress) {
|
|
281
|
+
account = config.walletAddress;
|
|
282
|
+
} else {
|
|
283
|
+
account = (0, import_genlayer_js.createAccount)((0, import_genlayer_js.generatePrivateKey)());
|
|
284
|
+
}
|
|
285
|
+
this.glClient = (0, import_genlayer_js.createClient)({
|
|
286
|
+
chain,
|
|
287
|
+
endpoint: this.network.rpc,
|
|
288
|
+
account
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
292
|
+
async write(address, method, args, value = 0n) {
|
|
293
|
+
const tx = await this.glClient.writeContract({
|
|
294
|
+
address,
|
|
295
|
+
functionName: method,
|
|
296
|
+
args,
|
|
297
|
+
value
|
|
298
|
+
});
|
|
299
|
+
const receipt = await this.glClient.waitForTransactionReceipt({
|
|
300
|
+
hash: tx,
|
|
301
|
+
status: import_types.TransactionStatus.ACCEPTED,
|
|
302
|
+
retries: this.retries
|
|
303
|
+
});
|
|
304
|
+
const leader = receipt?.consensus_data?.leader_receipt?.[0];
|
|
305
|
+
const payload = String(
|
|
306
|
+
leader?.result?.payload?.readable ?? receipt?.data?.result ?? ""
|
|
307
|
+
).replace(/^"|"$/g, "");
|
|
308
|
+
return { payload, txHash: String(tx) };
|
|
309
|
+
}
|
|
310
|
+
async read(address, method, args = []) {
|
|
311
|
+
return this.glClient.readContract({
|
|
312
|
+
address,
|
|
313
|
+
functionName: method,
|
|
314
|
+
args
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
get CORE() {
|
|
318
|
+
return this.network.addresses.CORE;
|
|
319
|
+
}
|
|
320
|
+
get LB() {
|
|
321
|
+
return this.network.addresses.LB;
|
|
322
|
+
}
|
|
323
|
+
get TRN() {
|
|
324
|
+
return this.network.addresses.TRN;
|
|
325
|
+
}
|
|
326
|
+
mapGameState(raw) {
|
|
327
|
+
return {
|
|
328
|
+
gameId: raw.game_id ?? "",
|
|
329
|
+
gameName: raw.game_name ?? "",
|
|
330
|
+
status: raw.status ?? "waiting",
|
|
331
|
+
player1: raw.player1 ?? "",
|
|
332
|
+
player2: raw.player2 ?? "",
|
|
333
|
+
agent1: raw.agent1 ?? "",
|
|
334
|
+
agent2: raw.agent2 ?? "",
|
|
335
|
+
maxRounds: Number(raw.max_rounds ?? 0),
|
|
336
|
+
roundCount: Number(raw.round_count ?? 0),
|
|
337
|
+
judgedThrough: Number(raw.judged_through ?? 0),
|
|
338
|
+
rules: raw.rules ?? "",
|
|
339
|
+
winner: raw.winner ?? "",
|
|
340
|
+
score: raw.score ?? {},
|
|
341
|
+
playerTypes: raw.player_types ?? {},
|
|
342
|
+
caller: raw.caller ?? "",
|
|
343
|
+
rounds: (raw.rounds ?? []).map((r) => ({
|
|
344
|
+
roundNumber: r.round_number,
|
|
345
|
+
movePlayer1: r.move_player1 ?? "",
|
|
346
|
+
movePlayer2: r.move_player2 ?? "",
|
|
347
|
+
result: r.result ?? "pending",
|
|
348
|
+
reasonType: r.reason_type ?? "normal",
|
|
349
|
+
invalidPlayer: r.invalid_player ?? "none",
|
|
350
|
+
reasoning: r.reasoning ?? "",
|
|
351
|
+
confidence: Number(r.confidence ?? 0),
|
|
352
|
+
status: r.status ?? "pending"
|
|
353
|
+
}))
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
// ── Game API ────────────────────────────────────────────────────────────────
|
|
357
|
+
/**
|
|
358
|
+
* Create a new game. Returns the game ID.
|
|
359
|
+
*/
|
|
360
|
+
async createGame(options) {
|
|
361
|
+
const { payload } = await this.write(this.CORE, "start_game", [
|
|
362
|
+
options.name,
|
|
363
|
+
options.visibility ?? "public",
|
|
364
|
+
options.maxRounds ?? 3,
|
|
365
|
+
options.rules ?? "",
|
|
366
|
+
options.player1,
|
|
367
|
+
options.player2 ?? "",
|
|
368
|
+
options.agent1 ?? 0,
|
|
369
|
+
options.agent2 ?? 0
|
|
370
|
+
]);
|
|
371
|
+
return payload;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Submit a move for a player. Accepts ANY move format — string, object, array, etc.
|
|
375
|
+
* The SDK automatically normalizes it to a string the AI judge can understand.
|
|
376
|
+
*/
|
|
377
|
+
async submitMove(gameId, playerName, move, gameHint) {
|
|
378
|
+
const normalized = normalizeMove(move, gameHint);
|
|
379
|
+
const { txHash } = await this.write(this.CORE, "submit_move", [
|
|
380
|
+
gidToNum(gameId),
|
|
381
|
+
playerName,
|
|
382
|
+
normalized.text
|
|
383
|
+
]);
|
|
384
|
+
return { txHash, normalizedMove: normalized.text };
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Judge the game — triggers AI consensus on all pending rounds.
|
|
388
|
+
*/
|
|
389
|
+
async judgeGame(gameId) {
|
|
390
|
+
const { payload, txHash } = await this.write(
|
|
391
|
+
this.CORE,
|
|
392
|
+
"judge_game",
|
|
393
|
+
[gidToNum(gameId)]
|
|
394
|
+
);
|
|
395
|
+
const state = await this.getGameState(gameId);
|
|
396
|
+
return {
|
|
397
|
+
winner: state.winner,
|
|
398
|
+
isDraw: state.status === "draw",
|
|
399
|
+
score: state.score,
|
|
400
|
+
rounds: state.rounds,
|
|
401
|
+
txHash,
|
|
402
|
+
payload
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* End an open-ended game (max_rounds = 0). Only callable by the game creator.
|
|
407
|
+
*/
|
|
408
|
+
async endGame(gameId) {
|
|
409
|
+
const { payload, txHash } = await this.write(
|
|
410
|
+
this.CORE,
|
|
411
|
+
"end_game",
|
|
412
|
+
[gidToNum(gameId)]
|
|
413
|
+
);
|
|
414
|
+
const state = await this.getGameState(gameId);
|
|
415
|
+
return {
|
|
416
|
+
winner: state.winner,
|
|
417
|
+
isDraw: state.status === "draw",
|
|
418
|
+
score: state.score,
|
|
419
|
+
rounds: state.rounds,
|
|
420
|
+
txHash,
|
|
421
|
+
payload
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get the full game state.
|
|
426
|
+
*/
|
|
427
|
+
async getGameState(gameId) {
|
|
428
|
+
const raw = await this.read(this.CORE, "get_game_state", [gameId]);
|
|
429
|
+
return this.mapGameState(raw);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get all active games.
|
|
433
|
+
*/
|
|
434
|
+
async getActiveGames() {
|
|
435
|
+
const raw = await this.read(this.CORE, "get_active_games");
|
|
436
|
+
return (raw ?? []).map(this.mapGameState);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Get total number of games.
|
|
440
|
+
*/
|
|
441
|
+
async getTotalGames() {
|
|
442
|
+
const result = await this.read(this.CORE, "get_total_games");
|
|
443
|
+
return Number(result ?? 0);
|
|
444
|
+
}
|
|
445
|
+
// ── Polling helpers ─────────────────────────────────────────────────────────
|
|
446
|
+
/**
|
|
447
|
+
* Wait until both players have submitted their move for a given round.
|
|
448
|
+
* Polls every `pollInterval` ms.
|
|
449
|
+
*/
|
|
450
|
+
async waitForBothMoves(gameId, roundNumber) {
|
|
451
|
+
while (true) {
|
|
452
|
+
const state = await this.getGameState(gameId);
|
|
453
|
+
const round = state.rounds.find((r) => r.roundNumber === roundNumber);
|
|
454
|
+
if (round?.movePlayer1 && round?.movePlayer2) return round;
|
|
455
|
+
await this.sleep(this.pollInterval);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Wait until the opponent has submitted their move for a round.
|
|
460
|
+
* isPlayer1: true if you are player1, false if player2.
|
|
461
|
+
*/
|
|
462
|
+
async waitForOpponentMove(gameId, roundNumber, isPlayer1) {
|
|
463
|
+
while (true) {
|
|
464
|
+
const state = await this.getGameState(gameId);
|
|
465
|
+
const round = state.rounds.find((r) => r.roundNumber === roundNumber);
|
|
466
|
+
const opponentMove = isPlayer1 ? round?.movePlayer2 : round?.movePlayer1;
|
|
467
|
+
if (opponentMove) return opponentMove;
|
|
468
|
+
await this.sleep(this.pollInterval);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Wait until the game reaches a specific status.
|
|
473
|
+
*/
|
|
474
|
+
async waitForStatus(gameId, status) {
|
|
475
|
+
while (true) {
|
|
476
|
+
const state = await this.getGameState(gameId);
|
|
477
|
+
if (state.status === status) return state;
|
|
478
|
+
if (state.status === "completed" || state.status === "draw") return state;
|
|
479
|
+
await this.sleep(this.pollInterval);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Play a full game automatically.
|
|
484
|
+
* moveFn is called for each round — return your move in any format.
|
|
485
|
+
*/
|
|
486
|
+
async playGame(gameId, playerName, isPlayer1, moveFn, gameHint) {
|
|
487
|
+
let state = await this.getGameState(gameId);
|
|
488
|
+
const maxRounds = state.maxRounds || 999;
|
|
489
|
+
for (let round = 1; round <= maxRounds; round++) {
|
|
490
|
+
state = await this.getGameState(gameId);
|
|
491
|
+
if (state.status !== "active") break;
|
|
492
|
+
if (!isPlayer1) {
|
|
493
|
+
await this.waitForOpponentMove(gameId, round, false);
|
|
494
|
+
}
|
|
495
|
+
state = await this.getGameState(gameId);
|
|
496
|
+
const move = await moveFn(state, round);
|
|
497
|
+
await this.submitMove(gameId, playerName, move, gameHint);
|
|
498
|
+
if (isPlayer1) {
|
|
499
|
+
await this.waitForOpponentMove(gameId, round, true);
|
|
500
|
+
}
|
|
501
|
+
state = await this.getGameState(gameId);
|
|
502
|
+
if (state.roundCount >= maxRounds) break;
|
|
503
|
+
}
|
|
504
|
+
return this.judgeGame(gameId);
|
|
505
|
+
}
|
|
506
|
+
// ── Leaderboard API ─────────────────────────────────────────────────────────
|
|
507
|
+
async getLeaderboard(gameName, playerType = "all") {
|
|
508
|
+
const raw = await this.read(this.LB, "get_leaderboard", [gameName, playerType]);
|
|
509
|
+
return (raw ?? []).map((e) => ({
|
|
510
|
+
player: e.player ?? "",
|
|
511
|
+
wins: Number(e.wins ?? 0),
|
|
512
|
+
losses: Number(e.losses ?? 0),
|
|
513
|
+
draws: Number(e.draws ?? 0),
|
|
514
|
+
score: Number(e.score ?? 0),
|
|
515
|
+
playerType: e.player_type ?? "human"
|
|
516
|
+
}));
|
|
517
|
+
}
|
|
518
|
+
async getTopPlayers(gameName, n = 10) {
|
|
519
|
+
const raw = await this.read(this.LB, "get_top_players", [gameName, n]);
|
|
520
|
+
return (raw ?? []).map((e) => ({
|
|
521
|
+
player: e.player ?? "",
|
|
522
|
+
wins: Number(e.wins ?? 0),
|
|
523
|
+
losses: Number(e.losses ?? 0),
|
|
524
|
+
draws: Number(e.draws ?? 0),
|
|
525
|
+
score: Number(e.score ?? 0),
|
|
526
|
+
playerType: e.player_type ?? "human"
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
async getPlayerStats(gameName, playerName, playerType = "all") {
|
|
530
|
+
try {
|
|
531
|
+
const raw = await this.read(this.LB, "get_player_stats", [gameName, playerName, playerType]);
|
|
532
|
+
if (!raw) return null;
|
|
533
|
+
return {
|
|
534
|
+
player: raw.player ?? playerName,
|
|
535
|
+
wins: Number(raw.wins ?? 0),
|
|
536
|
+
losses: Number(raw.losses ?? 0),
|
|
537
|
+
draws: Number(raw.draws ?? 0),
|
|
538
|
+
score: Number(raw.score ?? 0),
|
|
539
|
+
games: Number(raw.games ?? 0),
|
|
540
|
+
playerType: raw.player_type ?? playerType
|
|
541
|
+
};
|
|
542
|
+
} catch {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// ── Tournament API ──────────────────────────────────────────────────────────
|
|
547
|
+
async createTournament(options) {
|
|
548
|
+
const { payload } = await this.write(this.TRN, "create_tournament", [
|
|
549
|
+
options.name,
|
|
550
|
+
options.gameName,
|
|
551
|
+
options.format,
|
|
552
|
+
options.maxPlayers,
|
|
553
|
+
0,
|
|
554
|
+
// entry fee — always free
|
|
555
|
+
options.prizeSplit ?? [70, 30],
|
|
556
|
+
options.rules ?? "",
|
|
557
|
+
options.roundsPerMatch ?? 1
|
|
558
|
+
]);
|
|
559
|
+
return payload;
|
|
560
|
+
}
|
|
561
|
+
async joinTournament(tid, playerName, playerType = "human") {
|
|
562
|
+
await this.write(this.TRN, "join_tournament", [tid, playerName, playerType]);
|
|
563
|
+
}
|
|
564
|
+
async startTournament(tid) {
|
|
565
|
+
await this.write(this.TRN, "start_tournament", [tid]);
|
|
566
|
+
}
|
|
567
|
+
async getTournament(tid) {
|
|
568
|
+
try {
|
|
569
|
+
const raw = await this.read(this.TRN, "get_tournament", [tid]);
|
|
570
|
+
if (!raw) return null;
|
|
571
|
+
return {
|
|
572
|
+
tid: raw.tid ?? tid,
|
|
573
|
+
name: raw.name ?? "",
|
|
574
|
+
gameName: raw.game_name ?? "",
|
|
575
|
+
format: raw.format ?? "",
|
|
576
|
+
maxPlayers: Number(raw.max_players ?? 0),
|
|
577
|
+
status: raw.status ?? "",
|
|
578
|
+
players: raw.players ?? [],
|
|
579
|
+
bracket: (raw.bracket ?? []).map((m) => ({
|
|
580
|
+
matchId: m.match_id,
|
|
581
|
+
round: m.round,
|
|
582
|
+
player1: m.player1,
|
|
583
|
+
player2: m.player2,
|
|
584
|
+
gameId: m.game_id,
|
|
585
|
+
winner: m.winner,
|
|
586
|
+
status: m.status
|
|
587
|
+
})),
|
|
588
|
+
winner: raw.winner ?? ""
|
|
589
|
+
};
|
|
590
|
+
} catch {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
async listTournaments() {
|
|
595
|
+
const raw = await this.read(this.TRN, "list_tournaments");
|
|
596
|
+
return (raw ?? []).map((t) => ({
|
|
597
|
+
tid: t.tid ?? "",
|
|
598
|
+
name: t.name ?? "",
|
|
599
|
+
gameName: t.game_name ?? "",
|
|
600
|
+
format: t.format ?? "",
|
|
601
|
+
maxPlayers: Number(t.max_players ?? 0),
|
|
602
|
+
status: t.status ?? "",
|
|
603
|
+
players: t.players ?? [],
|
|
604
|
+
bracket: [],
|
|
605
|
+
winner: t.winner ?? ""
|
|
606
|
+
}));
|
|
607
|
+
}
|
|
608
|
+
async recordMatchResult(tid, matchId, winner) {
|
|
609
|
+
await this.write(this.TRN, "record_match_result", [tid, matchId, winner]);
|
|
610
|
+
}
|
|
611
|
+
// ── Utils ───────────────────────────────────────────────────────────────────
|
|
612
|
+
/** Normalize any move to a string without submitting */
|
|
613
|
+
normalizeMove(move, gameHint) {
|
|
614
|
+
return normalizeMove(move, gameHint);
|
|
615
|
+
}
|
|
616
|
+
/** Sleep helper */
|
|
617
|
+
sleep(ms) {
|
|
618
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// src/index.ts
|
|
623
|
+
function createTheRef(config) {
|
|
624
|
+
return new TheRefClient(config);
|
|
625
|
+
}
|
|
626
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
627
|
+
0 && (module.exports = {
|
|
628
|
+
NETWORKS,
|
|
629
|
+
TheRefClient,
|
|
630
|
+
createTheRef,
|
|
631
|
+
gidToNum,
|
|
632
|
+
normalizeMove,
|
|
633
|
+
resolveNetwork
|
|
634
|
+
});
|