@yyyu-star/arcade-kit 0.12.5 → 0.13.1

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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 搭子战绩上报(0.13.0,平台 0.19.0,docs/platform-backend.md 15.19):好友房打完一场,房间的服务端把同桌真人和各自输赢经钱包服务绑定的 `together()` 报给平台,
3
+ * 平台给其中已经是好友的每一对累计"一起打了几场、谁赢了几场、做搭档几场"。浏览器碰不到这条路(只有服务绑定),所以只在房间里调。
4
+ *
5
+ * 不依赖 `cloudflare:workers`(纯函数加一次 RPC),`arcade-kit/room` 转出;房间子类一般用 `GameRoom.reportMatch`,它按座位认人、拼 op。
6
+ */
7
+
8
+ /** 一个真人这一场:team 只有分队的游戏才带(同一个数字是一队,0–3);win 由游戏定,写进各自的 DESIGN */
9
+ export interface TogetherPlayer {
10
+ userId: string;
11
+ team?: number;
12
+ win: boolean;
13
+ }
14
+
15
+ export type TogetherReply = { ok: true; pairs: number } | { ok: false; error: string };
16
+
17
+ /** 平台一次最多收几个人(15.19:2–8 个真人) */
18
+ export const TOGETHER_MAX = 8;
19
+
20
+ const OP_RE = /^[A-Za-z0-9_-]{8,64}$/;
21
+
22
+ interface TogetherWallet {
23
+ together?: (req: { op: string; players: TogetherPlayer[] }) => Promise<unknown>;
24
+ }
25
+
26
+ /**
27
+ * 一场的编号:平台按它去重(同一对的同一个 op 再报不加),所以同一场重报要得到同一个 op,不同的场不能撞。
28
+ * 房间码会被别的房间重用,所以带上开房时刻:`<房间码>-<开房时刻 36 进制>-<第几场>`,不合格的字符换成 `_`,截到 64 位
29
+ */
30
+ export function togetherOp(code: string, createdAt: number, match: number | string): string {
31
+ return `${code}-${createdAt.toString(36)}-${match}`.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 64).padEnd(8, '_');
32
+ }
33
+
34
+ /**
35
+ * 报一场。players 只放真人(电脑、游客不报),同一个 userId 只留第一个;少于 2 个、超过 TOGETHER_MAX 个、op 不合格,
36
+ * 或者没有 `env.WALLET`(单独部署)、它没有 `together`(平台还没上 0.19.0)时什么都不做,回 null。
37
+ * 平台的回话原样返回;RPC 抛错吞掉、只记错误类型(不记 userId),也回 null。**不重试**:平台忙时回 busy,少记一场不影响打牌。
38
+ * 只经服务绑定调,不用 setTimeout;房间在一场结束时交给 `ctx.waitUntil`(见 GameRoom.reportMatch)
39
+ */
40
+ export async function reportTogether(env: unknown, op: string, players: readonly TogetherPlayer[]): Promise<TogetherReply | null> {
41
+ const wallet = (env as { WALLET?: TogetherWallet } | null | undefined)?.WALLET;
42
+ if (!wallet || typeof wallet.together !== 'function') return null;
43
+ const seen = new Set<string>();
44
+ const list: TogetherPlayer[] = [];
45
+ for (const p of players) {
46
+ if (!p.userId || seen.has(p.userId)) continue;
47
+ seen.add(p.userId);
48
+ list.push(p.team === undefined ? { userId: p.userId, win: p.win } : { userId: p.userId, team: p.team, win: p.win });
49
+ }
50
+ if (list.length < 2 || list.length > TOGETHER_MAX || !OP_RE.test(op)) return null;
51
+ try {
52
+ return (await wallet.together({ op, players: list })) as TogetherReply;
53
+ } catch (e) {
54
+ console.error('together failed', e instanceof Error ? e.name : typeof e);
55
+ return null;
56
+ }
57
+ }