@yyyu-star/arcade-kit 0.0.0-stage → 0.10.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.
- package/CHANGELOG.md +249 -0
- package/LICENSE +21 -0
- package/README.md +440 -2
- package/dist/auth.d.ts +66 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +86 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +194 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +379 -0
- package/dist/client.js.map +1 -0
- package/dist/fun-art.d.ts +41 -0
- package/dist/fun-art.d.ts.map +1 -0
- package/dist/fun-art.js +141 -0
- package/dist/fun-art.js.map +1 -0
- package/dist/fun.d.ts +87 -0
- package/dist/fun.d.ts.map +1 -0
- package/dist/fun.js +512 -0
- package/dist/fun.js.map +1 -0
- package/dist/game.d.ts +160 -0
- package/dist/game.d.ts.map +1 -0
- package/dist/game.js +41 -0
- package/dist/game.js.map +1 -0
- package/dist/guest.d.ts +60 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +87 -0
- package/dist/guest.js.map +1 -0
- package/dist/internal.d.ts +33 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +53 -0
- package/dist/internal.js.map +1 -0
- package/dist/meter.d.ts +102 -0
- package/dist/meter.d.ts.map +1 -0
- package/dist/meter.js +207 -0
- package/dist/meter.js.map +1 -0
- package/dist/protocol.d.ts +227 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +65 -0
- package/dist/protocol.js.map +1 -0
- package/dist/rng.d.ts +40 -0
- package/dist/rng.d.ts.map +1 -0
- package/dist/rng.js +119 -0
- package/dist/rng.js.map +1 -0
- package/dist/room.d.ts +352 -0
- package/dist/room.d.ts.map +1 -0
- package/dist/room.js +1563 -0
- package/dist/room.js.map +1 -0
- package/dist/testing.d.ts +125 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +272 -0
- package/dist/testing.js.map +1 -0
- package/dist/worker.d.ts +89 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +177 -0
- package/dist/worker.js.map +1 -0
- package/package.json +107 -4
- package/src/auth.ts +100 -0
- package/src/client.ts +438 -0
- package/src/fun-art.ts +184 -0
- package/src/fun.ts +626 -0
- package/src/game.ts +200 -0
- package/src/guest.ts +122 -0
- package/src/internal.ts +64 -0
- package/src/meter.ts +255 -0
- package/src/protocol.ts +243 -0
- package/src/rng.ts +131 -0
- package/src/room.ts +1677 -0
- package/src/testing.ts +300 -0
- package/src/worker.ts +229 -0
package/dist/rng.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 联机发牌用的随机数:ChaCha20(RFC 8439),纯函数,浏览器、DO、普通 Node 都能用,不依赖 DOM、网络、运行时的随机源。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不用 32 位种子:种子只有 2^32 种,玩家拿自己的手牌逐个试种子,几分钟就能反推出全桌的牌(0.2.0 前的房间就有这个问题)。
|
|
5
|
+
* 房间开局时用 crypto.getRandomValues 拿 256 位熵(8 个 u32,Entropy),交给 RoomGame.begin 当 ChaCha20 的密钥;
|
|
6
|
+
* 密钥只在服务端的牌局状态里,绝不下发。同一把密钥、同样的用途和序号永远得到同样的结果,存档、回放、联机校验照样成立。
|
|
7
|
+
*
|
|
8
|
+
* 只用 32 位整数运算(加法后 >>> 0、移位、异或),结果不依赖平台的数学函数,规则核心可以放心用。
|
|
9
|
+
* 一把密钥按"用途 + 序号"分出互不相干的流(比如 stream(key, 'deal', 3) 是第 3 副的发牌):
|
|
10
|
+
* 用途进 nonce 的第 0 个字(FNV-1a 32 位),序号进第 1、2 个字,块计数从 0 起。用途名请用固定的短字符串,同一个游戏里不要两个用途撞同一个哈希
|
|
11
|
+
* (游戏在自己的测试里断言一下)。
|
|
12
|
+
*/
|
|
13
|
+
/** 256 位熵:8 个 0–2^32-1 的整数(ChaCha20 的密钥,按 RFC 8439 的小端字)。 */
|
|
14
|
+
export type Entropy = readonly number[];
|
|
15
|
+
export declare const ENTROPY_WORDS = 8;
|
|
16
|
+
/** 是不是合格的 256 位熵:8 个 u32 整数。 */
|
|
17
|
+
export declare function isEntropy(x: unknown): x is Entropy;
|
|
18
|
+
/** RFC 8439 2.1 的四分之一轮,原地改 s 的四个字。 */
|
|
19
|
+
export declare function quarterRound(s: Uint32Array, a: number, b: number, c: number, d: number): void;
|
|
20
|
+
/** RFC 8439 2.3 的块函数:密钥 8 字、块计数 1 字、nonce 3 字 → 16 个字(64 字节的密钥流,小端)。 */
|
|
21
|
+
export declare function chacha20Block(key: Entropy, counter: number, nonce: readonly number[]): Uint32Array;
|
|
22
|
+
/** 用途名的 32 位哈希(FNV-1a,按 UTF-16 码元)。 */
|
|
23
|
+
export declare function purposeHash(purpose: string): number;
|
|
24
|
+
/** 一条随机流:按顺序吐 u32,用完一块(16 个字)算下一块。 */
|
|
25
|
+
export interface RandomStream {
|
|
26
|
+
/** 下一个 0–2^32-1 的整数 */
|
|
27
|
+
u32(): number;
|
|
28
|
+
/** 0 到 n-1 的均匀整数(1 ≤ n ≤ 2^32),拒绝采样,没有取模偏差 */
|
|
29
|
+
below(n: number): number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 从密钥分出一条流:purpose 用途(如 'deal'),index 序号(0 到 2^53-1 的整数,如第几副)。
|
|
33
|
+
* 每条流最多 2^32 块(每块 16 个字),一副牌远远用不完。
|
|
34
|
+
*/
|
|
35
|
+
export declare function stream(key: Entropy, purpose: string, index?: number): RandomStream;
|
|
36
|
+
/** 0 到 n-1 的均匀整数:next 给 u32,落在"2^32 里最大的 n 的整数倍"以上的丢掉重抽,剩下的取模才没有偏差。 */
|
|
37
|
+
export declare function uniformBelow(next: () => number, n: number): number;
|
|
38
|
+
/** Fisher–Yates 洗牌(从后往前,每一步用 below 拒绝采样),返回新数组,不改原数组。 */
|
|
39
|
+
export declare function shuffle<T>(items: readonly T[], rng: RandomStream): T[];
|
|
40
|
+
//# sourceMappingURL=rng.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rng.d.ts","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,6DAA6D;AAC7D,MAAM,MAAM,OAAO,GAAG,SAAS,MAAM,EAAE,CAAC;AAExC,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B,gCAAgC;AAChC,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,OAAO,CAElD;AAMD,sCAAsC;AACtC,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAS7F;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAwBlG;AAED,uCAAuC;AACvC,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQnD;AAED,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,uBAAuB;IACvB,GAAG,IAAI,MAAM,CAAC;IACd,8CAA8C;IAC9C,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,YAAY,CAmB7E;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAOlE;AAED,yDAAyD;AACzD,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,GAAG,CAAC,EAAE,CAStE"}
|
package/dist/rng.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 联机发牌用的随机数:ChaCha20(RFC 8439),纯函数,浏览器、DO、普通 Node 都能用,不依赖 DOM、网络、运行时的随机源。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不用 32 位种子:种子只有 2^32 种,玩家拿自己的手牌逐个试种子,几分钟就能反推出全桌的牌(0.2.0 前的房间就有这个问题)。
|
|
5
|
+
* 房间开局时用 crypto.getRandomValues 拿 256 位熵(8 个 u32,Entropy),交给 RoomGame.begin 当 ChaCha20 的密钥;
|
|
6
|
+
* 密钥只在服务端的牌局状态里,绝不下发。同一把密钥、同样的用途和序号永远得到同样的结果,存档、回放、联机校验照样成立。
|
|
7
|
+
*
|
|
8
|
+
* 只用 32 位整数运算(加法后 >>> 0、移位、异或),结果不依赖平台的数学函数,规则核心可以放心用。
|
|
9
|
+
* 一把密钥按"用途 + 序号"分出互不相干的流(比如 stream(key, 'deal', 3) 是第 3 副的发牌):
|
|
10
|
+
* 用途进 nonce 的第 0 个字(FNV-1a 32 位),序号进第 1、2 个字,块计数从 0 起。用途名请用固定的短字符串,同一个游戏里不要两个用途撞同一个哈希
|
|
11
|
+
* (游戏在自己的测试里断言一下)。
|
|
12
|
+
*/
|
|
13
|
+
export const ENTROPY_WORDS = 8;
|
|
14
|
+
/** 是不是合格的 256 位熵:8 个 u32 整数。 */
|
|
15
|
+
export function isEntropy(x) {
|
|
16
|
+
return Array.isArray(x) && x.length === ENTROPY_WORDS && x.every((w) => Number.isInteger(w) && w >= 0 && w <= 0xffffffff);
|
|
17
|
+
}
|
|
18
|
+
function rotl(x, n) {
|
|
19
|
+
return ((x << n) | (x >>> (32 - n))) >>> 0;
|
|
20
|
+
}
|
|
21
|
+
/** RFC 8439 2.1 的四分之一轮,原地改 s 的四个字。 */
|
|
22
|
+
export function quarterRound(s, a, b, c, d) {
|
|
23
|
+
s[a] = (s[a] + s[b]) >>> 0;
|
|
24
|
+
s[d] = rotl(s[d] ^ s[a], 16);
|
|
25
|
+
s[c] = (s[c] + s[d]) >>> 0;
|
|
26
|
+
s[b] = rotl(s[b] ^ s[c], 12);
|
|
27
|
+
s[a] = (s[a] + s[b]) >>> 0;
|
|
28
|
+
s[d] = rotl(s[d] ^ s[a], 8);
|
|
29
|
+
s[c] = (s[c] + s[d]) >>> 0;
|
|
30
|
+
s[b] = rotl(s[b] ^ s[c], 7);
|
|
31
|
+
}
|
|
32
|
+
/** RFC 8439 2.3 的块函数:密钥 8 字、块计数 1 字、nonce 3 字 → 16 个字(64 字节的密钥流,小端)。 */
|
|
33
|
+
export function chacha20Block(key, counter, nonce) {
|
|
34
|
+
const init = new Uint32Array(16);
|
|
35
|
+
init[0] = 0x61707865;
|
|
36
|
+
init[1] = 0x3320646e;
|
|
37
|
+
init[2] = 0x79622d32;
|
|
38
|
+
init[3] = 0x6b206574;
|
|
39
|
+
for (let i = 0; i < 8; i++)
|
|
40
|
+
init[4 + i] = key[i];
|
|
41
|
+
init[12] = counter;
|
|
42
|
+
init[13] = nonce[0];
|
|
43
|
+
init[14] = nonce[1];
|
|
44
|
+
init[15] = nonce[2];
|
|
45
|
+
const s = init.slice();
|
|
46
|
+
for (let i = 0; i < 10; i++) {
|
|
47
|
+
quarterRound(s, 0, 4, 8, 12);
|
|
48
|
+
quarterRound(s, 1, 5, 9, 13);
|
|
49
|
+
quarterRound(s, 2, 6, 10, 14);
|
|
50
|
+
quarterRound(s, 3, 7, 11, 15);
|
|
51
|
+
quarterRound(s, 0, 5, 10, 15);
|
|
52
|
+
quarterRound(s, 1, 6, 11, 12);
|
|
53
|
+
quarterRound(s, 2, 7, 8, 13);
|
|
54
|
+
quarterRound(s, 3, 4, 9, 14);
|
|
55
|
+
}
|
|
56
|
+
for (let i = 0; i < 16; i++)
|
|
57
|
+
s[i] = (s[i] + init[i]) >>> 0;
|
|
58
|
+
return s;
|
|
59
|
+
}
|
|
60
|
+
/** 用途名的 32 位哈希(FNV-1a,按 UTF-16 码元)。 */
|
|
61
|
+
export function purposeHash(purpose) {
|
|
62
|
+
let h = 0x811c9dc5;
|
|
63
|
+
for (let i = 0; i < purpose.length; i++) {
|
|
64
|
+
h ^= purpose.charCodeAt(i);
|
|
65
|
+
// 乘 FNV 质数 16777619 = 2^24 + 403:拆成移位和加法,不用 Math.imul,每一步都在 2^53 以内
|
|
66
|
+
h = (h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
67
|
+
}
|
|
68
|
+
return h;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 从密钥分出一条流:purpose 用途(如 'deal'),index 序号(0 到 2^53-1 的整数,如第几副)。
|
|
72
|
+
* 每条流最多 2^32 块(每块 16 个字),一副牌远远用不完。
|
|
73
|
+
*/
|
|
74
|
+
export function stream(key, purpose, index = 0) {
|
|
75
|
+
if (!isEntropy(key))
|
|
76
|
+
throw new Error('rng key must be 8 u32 words');
|
|
77
|
+
if (!Number.isSafeInteger(index) || index < 0)
|
|
78
|
+
throw new Error('rng index must be a non-negative safe integer');
|
|
79
|
+
const nonce = [purposeHash(purpose), index % 0x1_0000_0000, Math.floor(index / 0x1_0000_0000)];
|
|
80
|
+
let counter = 0;
|
|
81
|
+
let block = null;
|
|
82
|
+
let at = 16;
|
|
83
|
+
const u32 = () => {
|
|
84
|
+
if (at >= 16) {
|
|
85
|
+
if (counter > 0xffffffff)
|
|
86
|
+
throw new Error('rng stream exhausted');
|
|
87
|
+
block = chacha20Block(key, counter++, nonce);
|
|
88
|
+
at = 0;
|
|
89
|
+
}
|
|
90
|
+
return block[at++];
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
u32,
|
|
94
|
+
below: (n) => uniformBelow(u32, n),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** 0 到 n-1 的均匀整数:next 给 u32,落在"2^32 里最大的 n 的整数倍"以上的丢掉重抽,剩下的取模才没有偏差。 */
|
|
98
|
+
export function uniformBelow(next, n) {
|
|
99
|
+
if (!Number.isInteger(n) || n < 1 || n > 0x1_0000_0000)
|
|
100
|
+
throw new Error('rng below(n) needs 1 ≤ n ≤ 2^32');
|
|
101
|
+
const limit = 0x1_0000_0000 - (0x1_0000_0000 % n);
|
|
102
|
+
for (;;) {
|
|
103
|
+
const x = next();
|
|
104
|
+
if (x < limit)
|
|
105
|
+
return x % n;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Fisher–Yates 洗牌(从后往前,每一步用 below 拒绝采样),返回新数组,不改原数组。 */
|
|
109
|
+
export function shuffle(items, rng) {
|
|
110
|
+
const out = items.slice();
|
|
111
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
112
|
+
const j = rng.below(i + 1);
|
|
113
|
+
const t = out[i];
|
|
114
|
+
out[i] = out[j];
|
|
115
|
+
out[j] = t;
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=rng.js.map
|
package/dist/rng.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rng.js","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"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"}
|
package/dist/room.d.ts
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 房间基类:游戏 Worker 里的 Durable Object 继承它,填上规则(RoomGame)就是一张联机桌(docs/platform-backend.md 第 4 节)。
|
|
3
|
+
*
|
|
4
|
+
* 守的规矩(4.1–4.6),每条都有测试:
|
|
5
|
+
* - 只用 ctx.acceptWebSocket(休眠),只用一个闹钟,不用 setTimeout / setInterval;心跳 ping/pong 由运行时自动应答;
|
|
6
|
+
* - 每一步最多写 2 行:房间状态一行(到期时间、计数都合在这一行里),外加"最早到期时间变早了"才重设闹钟(setAlarm 也算一行)。
|
|
7
|
+
* 闹钟只往早了调:期限往后推(比如出完牌下一手的 30 秒)时不动它,到点醒来发现没到期再改,那一步只写这两行。
|
|
8
|
+
* 例外只有开房:第一次建表另算 2 行(SQLite 的建表记录),一个房间只有这一次;
|
|
9
|
+
* - 状态没变不写:没入座的人进出、本来就有人在线时又连进来一个,都不写库、不广播;
|
|
10
|
+
* - 服务端说了算,下发前按座位过滤:每条连接只收到 game.view(state, 它的座位) 的结果,绝不发别人的隐藏信息;
|
|
11
|
+
* - 续局:接受连接、发快照、记下座位在同一段没有 await 的代码里做完;只向已经收到快照的连接广播;广播在 await(设闹钟)之前;
|
|
12
|
+
* - 每个入口(fetch、RPC、webSocketMessage、webSocketClose、alarm)都包一层:先计数、再查硬限制,最后才进游戏代码;游戏代码抛错只记日志,
|
|
13
|
+
* 闹钟里不往外抛(闹钟失败会自动重试最多 6 次,每次都计费,4.4)。
|
|
14
|
+
* - 内存里的东西都是缓存:被驱逐后从存储重建(attachment 里只放 userId、昵称);闹钟时间以 getAlarm() 读到的为准。
|
|
15
|
+
* - 额度(0.2.0,绑了 METER 才有,见 meter.ts):开房先向计量占名额;自己数写了几行,攒够 meterBatch 行或房间结束时报给计量;
|
|
16
|
+
* 打完、关房时还房主的名额。找计量都在 entry 之外(同步的那一步做完、广播完以后)await,不影响"一步之内没有 await"。
|
|
17
|
+
* 计量说今天快满了(busy)就不进新人;说满了(stop)就在这一段打完(RoomGame.breakPoint)时收场。
|
|
18
|
+
*/
|
|
19
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
20
|
+
import type { RoomEffect, RoomGame } from './game.js';
|
|
21
|
+
import { type Identity } from './internal.js';
|
|
22
|
+
export type { RoomEffect, RoomGame, RoomSetup } from './game.js';
|
|
23
|
+
export type { Identity } from './internal.js';
|
|
24
|
+
export interface RoomLimits {
|
|
25
|
+
/** 单条入站消息字节数,超了 1009 断开 */
|
|
26
|
+
maxMessageBytes: number;
|
|
27
|
+
/** 每个连接的令牌桶:每秒补几个、最多攒几个,超了 1008 断开 */
|
|
28
|
+
ratePerSec: number;
|
|
29
|
+
rateBurst: number;
|
|
30
|
+
/** 房间一生的入站消息数,超了房间结束 */
|
|
31
|
+
maxInbound: number;
|
|
32
|
+
/**
|
|
33
|
+
* 一个人最多用掉 maxInbound 的几成(0–1)。超了只断开他本人(4030)、以后也进不来,房间照常:
|
|
34
|
+
* 一个人刷不完整个房间的额度。默认 0.4:正常一局一个人用不到一成(掼蛋 4 真人桌一局全桌约 1000 条)
|
|
35
|
+
*/
|
|
36
|
+
maxInboundShare: number;
|
|
37
|
+
/**
|
|
38
|
+
* 每个人一生的"掉线后回来"次数(只数会写库的那种),用完以后他再连是 4029。断开总是跟在一次进房后面,
|
|
39
|
+
* 所以也管住了断开的写入。按 userId 分开算:别人刷不光你的额度,离座再坐下也不清零;没入座的人进出不写库,不受这个管。
|
|
40
|
+
* 这些重连同时算进 maxInbound,房间一生的写入总数有上限
|
|
41
|
+
*/
|
|
42
|
+
maxJoins: number;
|
|
43
|
+
/** 没入座的连接最多几条(来看的、还没选座位的),超了 4003。入座的人不占这个数,掉线回来总能进 */
|
|
44
|
+
extraConnections: number;
|
|
45
|
+
/** 房间最长寿命 */
|
|
46
|
+
maxLifeMs: number;
|
|
47
|
+
/** 没有真人连着多久关房间 */
|
|
48
|
+
noHumanMs: number;
|
|
49
|
+
/** 每一手的时间,超时代出 */
|
|
50
|
+
turnMs: number;
|
|
51
|
+
/** 掉线宽限:超过就由电脑托管,回来交还 */
|
|
52
|
+
graceMs: number;
|
|
53
|
+
/** 电脑连续出手时每一步的播放间隔 */
|
|
54
|
+
aiDelayMs: number;
|
|
55
|
+
/** 一次最多连算几步电脑,剩下的按闹钟接着算(防死循环) */
|
|
56
|
+
maxAiBatch: number;
|
|
57
|
+
/** 客户端版本低于这个就 4005 让它刷新 */
|
|
58
|
+
minClientVersion: number;
|
|
59
|
+
/**
|
|
60
|
+
* 快捷短语的令牌桶(0.7.0 起):每个座位每 emoteMs 补 1 个、最多攒 emoteBurst 个,没令牌回 emote_too_fast、不转发。
|
|
61
|
+
* 这两个是代码里的默认值;游戏 wrangler.jsonc 的 vars.KIT_QUOTA 里写了 emoteMs / emoteBurst 就以那里为准(不用发版就能调)。
|
|
62
|
+
* emoteMs 为 0 表示不限速
|
|
63
|
+
*/
|
|
64
|
+
emoteMs: number;
|
|
65
|
+
emoteBurst: number;
|
|
66
|
+
/**
|
|
67
|
+
* 写了多少行报一次计量(0.2.0)。计量每报一次自己写 1 行,200 就是多花 0.5%;
|
|
68
|
+
* 太大的话计量知道得晚,满了以后房间还会多写最多这么多行才收场
|
|
69
|
+
*/
|
|
70
|
+
meterBatch: number;
|
|
71
|
+
/**
|
|
72
|
+
* 关房时还有对外的事没办完(RoomGame.effects,0.3.0):断开所有人以后留着存储接着办,最多等这么久,
|
|
73
|
+
* 到了还办不完就记日志照样删(办事的一方要有自己的兜底,比如钱包到期退回买入)
|
|
74
|
+
*/
|
|
75
|
+
closingMs: number;
|
|
76
|
+
}
|
|
77
|
+
/** 4.5、4.6 的默认值。 */
|
|
78
|
+
export declare const DEFAULT_LIMITS: RoomLimits;
|
|
79
|
+
/** 开房的结果(kitCreate → worker.ts 的 acceptCreate)。 */
|
|
80
|
+
export type CreateOutcome = {
|
|
81
|
+
r: 'created' | 'exists' | 'bad' | 'bad_options' | 'error' | 'unavailable' | 'busy' | 'daily_limit';
|
|
82
|
+
} | {
|
|
83
|
+
r: 'has_room';
|
|
84
|
+
code: string;
|
|
85
|
+
};
|
|
86
|
+
export declare abstract class GameRoom<S, A, V, Env = unknown, O = unknown, E extends RoomEffect = RoomEffect> extends DurableObject<Env> {
|
|
87
|
+
protected abstract readonly game: RoomGame<S, A, V, O, E>;
|
|
88
|
+
protected readonly limits: RoomLimits;
|
|
89
|
+
/**
|
|
90
|
+
* 去外面办一笔事(0.3.0,游戏写了 effects 时房间子类实现):比如经 DO 调用从 userId 的钱包扣买入。
|
|
91
|
+
* userId 是 effect.seat 绑定的人(没绑人的电脑座位是 null)。返回值原样交给 RoomGame.settled;抛错算失败,按退避重试。
|
|
92
|
+
* 同一笔可能被办两次(断线、被驱逐),办事的一方按 effect.id 去重。在 entry 之外 await,不打断"一步之内没有 await"
|
|
93
|
+
*/
|
|
94
|
+
protected perform(_effect: E, _userId: string | null): Promise<unknown>;
|
|
95
|
+
/**
|
|
96
|
+
* 每一步广播完之后调一次(0.10.0,可选覆盖):state 是这一步结束时的牌局状态(服务端全量,含隐藏信息)。
|
|
97
|
+
* 给"电脑也会互动"用:房间子类看这一步的**公开**事件(谁出了什么、谁赢了一把),需要时调 botEmote 让电脑座位发一句短语。
|
|
98
|
+
* 同步执行、不能 await、不能写存储;抛错只记日志,不影响这一步。公平约束(游戏侧守):电脑发不发、发什么只看公开信息,
|
|
99
|
+
* 不读任何真人的短语、不读隐藏的牌。房间要收场(closing)时不调
|
|
100
|
+
*/
|
|
101
|
+
protected onStep(_state: S): void;
|
|
102
|
+
/**
|
|
103
|
+
* 真人发的短语校验通过、广播完之后调一次(0.10.1,可选覆盖):from 是发的座位,id 是编号,to 是对象座位(没指定是 undefined)。
|
|
104
|
+
* **只许用来调 botEmote 让电脑回一句**(被点名的电脑回嘴、全桌起哄),别的什么都不做:
|
|
105
|
+
* - 故意不给牌局状态:电脑的出牌决策(RoomGame.ai)绝不能读到真人的短语,游戏也不许把短语记进状态、字段或存储,留着以后喂给电脑;
|
|
106
|
+
* - 同步执行、不能 await、不能写存储;抛错只记日志,不影响已经广播的那一句;
|
|
107
|
+
* - 电脑自己的 botEmote 不会再触发它(不然几个电脑互相回个没完);回应同样走那个电脑座位的令牌桶,刷不出限速。
|
|
108
|
+
*/
|
|
109
|
+
protected onEmote(_from: number, _id: number, _to?: number): void;
|
|
110
|
+
/**
|
|
111
|
+
* 让电脑座位发一句快捷短语(0.10.0):广播的消息和真人发的一样({ t: 'emote', seat, id, to? }),多带 bot: true,客户端可以不管它。
|
|
112
|
+
* 只有电脑在出手的座位能发:kind 是 ai,或者真人掉线后由电脑托管(auto)。id、to 的校验和真人一样,走同一个座位的令牌桶
|
|
113
|
+
* (emoteMs / emoteBurst),不写存储、不改 seq。发出去了回 true;座位是真人在打、空位、编号不对、目标不对、房间打完了、太快了,回 false 不广播
|
|
114
|
+
*/
|
|
115
|
+
protected botEmote(seat: number, id: number, to?: number): boolean;
|
|
116
|
+
/** 内存缓存,被驱逐后从存储重建 */
|
|
117
|
+
private st;
|
|
118
|
+
/** 现在设着的闹钟;undefined 表示还不知道(刚建出来),进第一个入口前用 getAlarm() 读 */
|
|
119
|
+
private alarmAt;
|
|
120
|
+
/** 这一步要设的闹钟,entry 最后(广播之后)才真正 setAlarm */
|
|
121
|
+
private alarmToSet;
|
|
122
|
+
private pendingInbound;
|
|
123
|
+
private readonly pendingBy;
|
|
124
|
+
private readonly buckets;
|
|
125
|
+
/** 每个座位快捷短语的令牌桶(内存里;被驱逐就重置成满的,驱逐前至少空闲了一阵,本来也该满了) */
|
|
126
|
+
private readonly emoteBuckets;
|
|
127
|
+
private emoteRateCache;
|
|
128
|
+
/** 这一步写了几行(sql 的 rowsWritten + setAlarm、关房各 1);测试断言 ≤ 2 */
|
|
129
|
+
private writes;
|
|
130
|
+
/** 每个入口结束时记下来,给测试看 */
|
|
131
|
+
lastStepWrites: number;
|
|
132
|
+
maxStepWrites: number;
|
|
133
|
+
/** 这个实例一共写了几行(测试断言"整轮"的写入) */
|
|
134
|
+
totalWrites: number;
|
|
135
|
+
lastStepFailed: boolean;
|
|
136
|
+
/** 写了还没报给计量的行数 */
|
|
137
|
+
private unmetered;
|
|
138
|
+
/** 这一步做完以后要还房主的名额(打完、关房),entry 最后去找计量 */
|
|
139
|
+
private release;
|
|
140
|
+
/** 计量最近一次说的门槛(内存;被驱逐就重新问) */
|
|
141
|
+
private level;
|
|
142
|
+
/** 计量连不上时,什么时候再试着报数 */
|
|
143
|
+
private meterRetryAt;
|
|
144
|
+
/** 计量说满了:在打的房间走到 breakPoint 就收场 */
|
|
145
|
+
private winding;
|
|
146
|
+
/** 这一步走到了收场点:commit 广播完就关房 */
|
|
147
|
+
private closing;
|
|
148
|
+
/** 正在办的对外的事(内存;被驱逐就重新办,办事的一方按 id 去重) */
|
|
149
|
+
private readonly inflight;
|
|
150
|
+
private effectsBusy;
|
|
151
|
+
/** 游戏写了 pause 没写 resume,已经记过日志(0.5.0;每个实例只记一次) */
|
|
152
|
+
private warnedNoResume;
|
|
153
|
+
/** 每个实例不一样(测试用来确认真的被驱逐、构造函数重跑过) */
|
|
154
|
+
readonly instanceId: `${string}-${string}-${string}-${string}-${string}`;
|
|
155
|
+
constructor(ctx: DurableObjectState, env: Env);
|
|
156
|
+
/**
|
|
157
|
+
* 开房(游戏 Worker 经 RPC 调,见 worker.ts 的 acceptCreate)。绑了计量时先占名额(按人、全站、当天写入),
|
|
158
|
+
* 占不到就不建房间、这个房间对象什么都不写;占到了却没建成,退回去。
|
|
159
|
+
*/
|
|
160
|
+
kitCreate(code: string, host: Identity, rawOpts?: unknown): Promise<CreateOutcome>;
|
|
161
|
+
/** 进房:只认 kit 自己拼的内部地址(worker.ts 的 acceptJoin),身份在 x-kit-user 头里。 */
|
|
162
|
+
fetch(request: Request): Promise<Response>;
|
|
163
|
+
webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
|
|
164
|
+
webSocketClose(ws: WebSocket, code: number): Promise<void>;
|
|
165
|
+
webSocketError(ws: WebSocket): Promise<void>;
|
|
166
|
+
alarm(): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* 包装层:先清这一步的写入计数,跑游戏代码(同步,中间没有 await),记下写了几行,最后才设闹钟(广播之后,4.5)。
|
|
169
|
+
* 游戏代码抛错只记日志:fetch 回 500,消息和关闭事件吞掉;内存缓存作废(下次从存储读),已经算好的闹钟照样设
|
|
170
|
+
* (状态可能已经写进去了;闹钟设早了没关系,醒来会重新算)。
|
|
171
|
+
*/
|
|
172
|
+
private entry;
|
|
173
|
+
/**
|
|
174
|
+
* 计量的绑定:默认是 env.METER。没绑(0.1.x 的游戏还没加绑定)就不限额、不计数——这只为平滑升级,正式环境一定要绑。
|
|
175
|
+
* 游戏不用覆盖它;kit 的测试靠它换一个能"断线"的计量
|
|
176
|
+
*/
|
|
177
|
+
protected meterNamespace(): DurableObjectNamespace | undefined;
|
|
178
|
+
private meter;
|
|
179
|
+
/**
|
|
180
|
+
* 一步做完、广播完以后找计量:要还名额的还(带上最后一批写入),没有就看写入攒够一批没有,够了报一次。
|
|
181
|
+
* 先从 unmetered 里扣掉再 await,报失败了加回去:中间别的事件进来不会把同一批报两次。
|
|
182
|
+
* 被驱逐时存下的 unmetered 可能还没扣掉刚报过的那批,会再报一次,偏保守。
|
|
183
|
+
*/
|
|
184
|
+
private afterStep;
|
|
185
|
+
/** 计量说满了:没在打(等人、打完了)的、正停在收场点的、游戏没有收场点的,马上关;在打的等 step 走到收场点。 */
|
|
186
|
+
private windDown;
|
|
187
|
+
/**
|
|
188
|
+
* 今天额度快用完时不进新人(计量说 busy、stop):房主、坐着的、开打后绑了座位的、已经连着的照常进。
|
|
189
|
+
* 计量连不上时也不进新人,但用 1013 让客户端过一会儿重试。只读,不写库。返回关闭码,放行为 null
|
|
190
|
+
*/
|
|
191
|
+
private newcomerGate;
|
|
192
|
+
/** 进房被拒:接受升级后马上用关闭码关掉(浏览器拿不到 HTTP 状态码)。 */
|
|
193
|
+
private refuse;
|
|
194
|
+
private create;
|
|
195
|
+
/** 进房(含重连):接受、记座位、发快照,中间没有 await(4.5 续局的写法)。 */
|
|
196
|
+
private join;
|
|
197
|
+
private onMessage;
|
|
198
|
+
/**
|
|
199
|
+
* 快捷短语(0.6.0):校验编号、按座位限频,然后只广播,不写存储、不改 seq。
|
|
200
|
+
* 入站已经在 onMessage 里记过(占令牌桶和这个人的那一份额度),刷不出房间一生的上限以外。
|
|
201
|
+
* to(0.7.0,可选):冲着哪个座位发(扔道具、点名嘲讽)。得是有人(真人或电脑)的别的座位,不然回 bad_target;被拒的不算这个座位的一句
|
|
202
|
+
*/
|
|
203
|
+
private emote;
|
|
204
|
+
/** 快捷短语的节奏:KIT_QUOTA 里写了就用那里的,没写用 RoomLimits(只算一次) */
|
|
205
|
+
private emoteRate;
|
|
206
|
+
private takeEmote;
|
|
207
|
+
private sit;
|
|
208
|
+
private leave;
|
|
209
|
+
private start;
|
|
210
|
+
/**
|
|
211
|
+
* 开局拿随机:写了 begin 的给 256 位熵(只在这里用一次,房间不存;游戏要接着发牌就把它放进自己的状态,那只在服务端)。
|
|
212
|
+
* 只有 start 的旧游戏照旧给 32 位种子,记一条警告:这种种子能被玩家用手牌反推出来。
|
|
213
|
+
*/
|
|
214
|
+
private begin;
|
|
215
|
+
private act;
|
|
216
|
+
/**
|
|
217
|
+
* 除了 seat 以外的人看到的一切(其他座位和没入座的人的视图、房间信息),拼成一个串用来比较(0.7.1)。
|
|
218
|
+
* 不只比在线的连接:掉线的人回来拿到的快照也不能和没这一步时不一样
|
|
219
|
+
*/
|
|
220
|
+
private othersSee;
|
|
221
|
+
/**
|
|
222
|
+
* 别人看不出来的一步(0.7.1):照常存(和闹钟,最多 2 行),只回给出手的连接,seq 不加。
|
|
223
|
+
* 广播出去(哪怕内容一样)或者 seq 跳一下,别人就知道有人做了点什么——比如声明窗口里有真人能碰、杠、胡。
|
|
224
|
+
* 回给他的消息 seq 和上一条一样、带着他的 ack 和 quiet:RoomClient 0.7.1 起认这种消息,旧页面会丢掉(只是自己的画面慢一步更新)
|
|
225
|
+
*/
|
|
226
|
+
private commitQuiet;
|
|
227
|
+
/** 连接断了:留着座位,记下掉线时间;没有入座的人连着就开始算"没人"的时间。没入座的人走了不写。 */
|
|
228
|
+
private onGone;
|
|
229
|
+
private onAlarm;
|
|
230
|
+
/** 只用一个闹钟:所有到期时间取最早的。 */
|
|
231
|
+
private nextDeadline;
|
|
232
|
+
/**
|
|
233
|
+
* 只在比现在设的更早(或没设)时重设,因为每次 setAlarm 也算一行写入。
|
|
234
|
+
* alarmAt 马上改(同步),真正 setAlarm 留到 entry 最后:这中间别的事件看到的已经是新时间,不会把它往后改。
|
|
235
|
+
*/
|
|
236
|
+
private planAlarm;
|
|
237
|
+
/**
|
|
238
|
+
* 做一步;不合法返回错误码。换成了新的一手(组)时,从 at 起计时。
|
|
239
|
+
* free(不轮流的操作):该谁出手没变就不算新的一手,不改期限和 turnSeq。
|
|
240
|
+
*/
|
|
241
|
+
private step;
|
|
242
|
+
/**
|
|
243
|
+
* 牌局换成 next 以后的收尾:打完了、卡住了、换了新的一手(组)。mover 是出手的座位;
|
|
244
|
+
* null 是不轮流的变化(free 操作、座位换人、对外的事办完):该谁出手原样没变就还是同一手。
|
|
245
|
+
*/
|
|
246
|
+
private land;
|
|
247
|
+
/** 开打以后座位换了人(0.3.0):告诉游戏(RoomGame.seated),和这一步一起写。游戏出错只记日志、牌局不动。 */
|
|
248
|
+
private seatChanged;
|
|
249
|
+
/**
|
|
250
|
+
* 牌局没打完、却没有任何座位能出手:游戏出错了,房间不然会一直卡着(没人能出、也不会超时)。
|
|
251
|
+
* 记日志,按打完处理(不再计时、不再驱动电脑),房间之后照常按没人、寿命到期关掉。
|
|
252
|
+
* 游戏说在等外面的事(RoomGame.waiting,0.3.2)的不算:停着等,牌局再变时重新看。
|
|
253
|
+
*/
|
|
254
|
+
private stalled;
|
|
255
|
+
/** 超时代出:先用 timeout,被规则拒了再用 ai 试一次。都不行返回 false(记日志)。 */
|
|
256
|
+
private timeoutStep;
|
|
257
|
+
/**
|
|
258
|
+
* 这一刻能出手的座位:游戏写了 actors 用它(去掉不合法、重复的),没写就是 [turn](和 0.1.0 一样)。
|
|
259
|
+
* group 是游戏给的组标识(没给为 null),用来判断出完一步还是不是同一组。
|
|
260
|
+
*/
|
|
261
|
+
private turnOf;
|
|
262
|
+
private actorsOf;
|
|
263
|
+
/** 这一手只有这一个座位要出(一个人的一手,不是一组)。 */
|
|
264
|
+
private onlyActor;
|
|
265
|
+
private turnMsOf;
|
|
266
|
+
/**
|
|
267
|
+
* 这一手(组)的截止时间;没有在等真人(都是电脑、托管,或者没人要出)时为 null。
|
|
268
|
+
* 在停一会儿时(0.5.0)是停够的时间:nextDeadline 把它算进唯一的闹钟。
|
|
269
|
+
*/
|
|
270
|
+
private deadline;
|
|
271
|
+
/** 停顿的截止时间(0.5.0):在打、在停时才有,给 RoomInfo.pause */
|
|
272
|
+
private pausedUntil;
|
|
273
|
+
/**
|
|
274
|
+
* 这一刻要停多久(0.5.0,RoomGame.pause):没人能出手、没打完、游戏给了正的有限数才算,否则 null。
|
|
275
|
+
* 只写了 pause 没写 resume 的,停了也没法接着来,当作没有停顿(行为和 0.4.0 一样),只记一次日志免得每步刷屏。
|
|
276
|
+
*/
|
|
277
|
+
private pauseOf;
|
|
278
|
+
/**
|
|
279
|
+
* 停够了:调游戏的 resume 做一步(和闹钟这一步一起写)。resume 抛错、或者返回的还在停,就不用它的结果,
|
|
280
|
+
* 把截止时间推到 ALARM_RETRY_MS 以后再试——不推的话截止时间一直是过去的,闹钟会在当前时刻反复响、每次写一行。
|
|
281
|
+
* 返回 true 是牌局往前走了。
|
|
282
|
+
*/
|
|
283
|
+
private resumeStep;
|
|
284
|
+
private isAuto;
|
|
285
|
+
/** 轮到电脑(或托管)时一次算到下一个真人回合,每步带播放延迟(4.4);算不完的按闹钟接着算。 */
|
|
286
|
+
private runAuto;
|
|
287
|
+
/** 游戏说还没办完的事;格式不对的丢掉、游戏出错当没有(都记日志)。 */
|
|
288
|
+
private effectsOf;
|
|
289
|
+
/**
|
|
290
|
+
* 一步做完以后(entry 之外)把没在办的事交给 perform,办完再用一步(entry)把结果写回状态。
|
|
291
|
+
* 办完一笔可能引出下一笔,最多连着 EFFECT_ROUNDS 轮;有失败的就记下退避时间、交给闹钟。
|
|
292
|
+
* 正在关的房间办完了就删掉。同一时间只有一处在跑(写回那一步的 afterStep 不会再进来)。
|
|
293
|
+
*/
|
|
294
|
+
private runEffects;
|
|
295
|
+
/**
|
|
296
|
+
* 把办完的事写回状态(1 行,需要时再设闹钟);有失败的记下退避时间。正在关的房间全办完了就删掉。
|
|
297
|
+
* 返回 true:游戏的 settled 写错了,办完的事还留在 effects() 里。记日志、当失败交给退避,不在这一轮里接着重办(否则每步之后同一笔要办 EFFECT_ROUNDS 次)。
|
|
298
|
+
*/
|
|
299
|
+
private settle;
|
|
300
|
+
/** 正在关的房间醒来:到了 closingMs 还没办完就放弃(记日志删掉);到了重试时间就清掉等待,afterStep 接着办。 */
|
|
301
|
+
private endingAlarm;
|
|
302
|
+
/**
|
|
303
|
+
* 一步的收尾:seq 加一、算闹钟、写那一行,然后广播(都在同步代码里),闹钟留到 entry 最后设。
|
|
304
|
+
* 这一步走到了收场点(closing):不写了,广播完直接关房,大家先看到这一副的结果再收到 4000 和原因。
|
|
305
|
+
*/
|
|
306
|
+
private commit;
|
|
307
|
+
/** 一个人在这个房间最多能用多少条入站额度。 */
|
|
308
|
+
private userInboundCap;
|
|
309
|
+
private inboundOf;
|
|
310
|
+
/**
|
|
311
|
+
* 记一条入站(消息或会写库的重连)。'user':这个人的那一份用完了,调用方只断开他(这条不记,不写库);
|
|
312
|
+
* 'room':房间一生的上限到了,调用方关房;'ok':照常。
|
|
313
|
+
*/
|
|
314
|
+
private countInbound;
|
|
315
|
+
private save;
|
|
316
|
+
/** 只读:表不在(从没开过房、或者关房时连表一起删了)就是没有这个房间,不建表、不写任何东西。 */
|
|
317
|
+
private load;
|
|
318
|
+
/**
|
|
319
|
+
* 关房间:断开所有人、删闹钟、删存储(4.1),不留任何东西;房主的名额在 entry 最后还(afterStep)。
|
|
320
|
+
* 游戏有对外的事(0.3.0):先让游戏记下收场要办的(RoomGame.closing),还有没办完的就断开所有人、留着存储接着办(ending),
|
|
321
|
+
* 办完或者到了 closingMs 再删。
|
|
322
|
+
*/
|
|
323
|
+
private closeRoom;
|
|
324
|
+
/** 真的删掉:断开所有人、删闹钟、删存储。 */
|
|
325
|
+
private wipe;
|
|
326
|
+
private sockets;
|
|
327
|
+
/** 还有没有入座的真人连着(不算 except 这条) */
|
|
328
|
+
private seatedOnline;
|
|
329
|
+
/** 校验开局选项(游戏写了 options 时)和它定的座位数;不合格返回 undefined */
|
|
330
|
+
private checkOpts;
|
|
331
|
+
private countOf;
|
|
332
|
+
private seatOf;
|
|
333
|
+
private roomInfo;
|
|
334
|
+
/** 这个人开打后绑定的座位(离座了也还是它);还没开打、没坐过为 null。 */
|
|
335
|
+
private boundOf;
|
|
336
|
+
private welcome;
|
|
337
|
+
private stateMsg;
|
|
338
|
+
/** 按座位过滤后广播:同一个座位(加上绑定的座位,没入座的人各自不同)的消息只算一次。只发给已经收到快照(有 attachment)的连接。 */
|
|
339
|
+
private broadcast;
|
|
340
|
+
private send;
|
|
341
|
+
private reject;
|
|
342
|
+
/** 每个连接一个令牌桶(内存里;被驱逐后重置成满的,驱逐前至少空闲了一阵,本来也该满了)。 */
|
|
343
|
+
private takeToken;
|
|
344
|
+
}
|
|
345
|
+
/** 快捷短语的节奏(0.7.0):每 ms 毫秒补 1 个令牌、最多攒 burst 个 */
|
|
346
|
+
export interface EmoteRate {
|
|
347
|
+
ms: number;
|
|
348
|
+
burst: number;
|
|
349
|
+
}
|
|
350
|
+
/** KIT_QUOTA(对象或 JSON 字符串)里的 emoteMs / emoteBurst 覆盖默认值;不合格的忽略。burst 至少 1 */
|
|
351
|
+
export declare function emoteRateOf(raw: unknown, base: EmoteRate): EmoteRate;
|
|
352
|
+
//# sourceMappingURL=room.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"room.d.ts","sourceRoot":"","sources":["../src/room.ts"],"names":[],"mappings":"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"}
|