@zhin.js/game-kit 1.0.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.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/lib/board-sender.d.ts +14 -0
  4. package/lib/board-sender.d.ts.map +1 -0
  5. package/lib/board-sender.js +7 -0
  6. package/lib/board-sender.js.map +1 -0
  7. package/lib/choice-keyboard.d.ts +44 -0
  8. package/lib/choice-keyboard.d.ts.map +1 -0
  9. package/lib/choice-keyboard.js +67 -0
  10. package/lib/choice-keyboard.js.map +1 -0
  11. package/lib/command-alias.d.ts +21 -0
  12. package/lib/command-alias.d.ts.map +1 -0
  13. package/lib/command-alias.js +22 -0
  14. package/lib/command-alias.js.map +1 -0
  15. package/lib/command-help.d.ts +19 -0
  16. package/lib/command-help.d.ts.map +1 -0
  17. package/lib/command-help.js +91 -0
  18. package/lib/command-help.js.map +1 -0
  19. package/lib/command-message.d.ts +25 -0
  20. package/lib/command-message.d.ts.map +1 -0
  21. package/lib/command-message.js +58 -0
  22. package/lib/command-message.js.map +1 -0
  23. package/lib/game-action-alias.d.ts +8 -0
  24. package/lib/game-action-alias.d.ts.map +1 -0
  25. package/lib/game-action-alias.js +117 -0
  26. package/lib/game-action-alias.js.map +1 -0
  27. package/lib/game-commands.d.ts +3 -0
  28. package/lib/game-commands.d.ts.map +1 -0
  29. package/lib/game-commands.js +5 -0
  30. package/lib/game-commands.js.map +1 -0
  31. package/lib/game-hub-feature.d.ts +58 -0
  32. package/lib/game-hub-feature.d.ts.map +1 -0
  33. package/lib/game-hub-feature.js +100 -0
  34. package/lib/game-hub-feature.js.map +1 -0
  35. package/lib/game-hub-flow.d.ts +9 -0
  36. package/lib/game-hub-flow.d.ts.map +1 -0
  37. package/lib/game-hub-flow.js +90 -0
  38. package/lib/game-hub-flow.js.map +1 -0
  39. package/lib/game-hub-menu-context.d.ts +25 -0
  40. package/lib/game-hub-menu-context.d.ts.map +1 -0
  41. package/lib/game-hub-menu-context.js +64 -0
  42. package/lib/game-hub-menu-context.js.map +1 -0
  43. package/lib/game-hub-menu.d.ts +37 -0
  44. package/lib/game-hub-menu.d.ts.map +1 -0
  45. package/lib/game-hub-menu.js +162 -0
  46. package/lib/game-hub-menu.js.map +1 -0
  47. package/lib/game-hub-mount.d.ts +9 -0
  48. package/lib/game-hub-mount.d.ts.map +1 -0
  49. package/lib/game-hub-mount.js +108 -0
  50. package/lib/game-hub-mount.js.map +1 -0
  51. package/lib/game-interactive.d.ts +25 -0
  52. package/lib/game-interactive.d.ts.map +1 -0
  53. package/lib/game-interactive.js +62 -0
  54. package/lib/game-interactive.js.map +1 -0
  55. package/lib/game-middleware.d.ts +6 -0
  56. package/lib/game-middleware.d.ts.map +1 -0
  57. package/lib/game-middleware.js +9 -0
  58. package/lib/game-middleware.js.map +1 -0
  59. package/lib/game-onboarding.d.ts +8 -0
  60. package/lib/game-onboarding.d.ts.map +1 -0
  61. package/lib/game-onboarding.js +66 -0
  62. package/lib/game-onboarding.js.map +1 -0
  63. package/lib/game-records.d.ts +52 -0
  64. package/lib/game-records.d.ts.map +1 -0
  65. package/lib/game-records.js +206 -0
  66. package/lib/game-records.js.map +1 -0
  67. package/lib/game-session.d.ts +64 -0
  68. package/lib/game-session.d.ts.map +1 -0
  69. package/lib/game-session.js +44 -0
  70. package/lib/game-session.js.map +1 -0
  71. package/lib/grid-keyboard.d.ts +67 -0
  72. package/lib/grid-keyboard.d.ts.map +1 -0
  73. package/lib/grid-keyboard.js +104 -0
  74. package/lib/grid-keyboard.js.map +1 -0
  75. package/lib/help-card.d.ts +12 -0
  76. package/lib/help-card.d.ts.map +1 -0
  77. package/lib/help-card.js +129 -0
  78. package/lib/help-card.js.map +1 -0
  79. package/lib/index.d.ts +29 -0
  80. package/lib/index.d.ts.map +1 -0
  81. package/lib/index.js +29 -0
  82. package/lib/index.js.map +1 -0
  83. package/lib/interaction-profiles.d.ts +16 -0
  84. package/lib/interaction-profiles.d.ts.map +1 -0
  85. package/lib/interaction-profiles.js +35 -0
  86. package/lib/interaction-profiles.js.map +1 -0
  87. package/lib/memory-db.d.ts +41 -0
  88. package/lib/memory-db.d.ts.map +1 -0
  89. package/lib/memory-db.js +92 -0
  90. package/lib/memory-db.js.map +1 -0
  91. package/lib/random.d.ts +6 -0
  92. package/lib/random.d.ts.map +1 -0
  93. package/lib/random.js +29 -0
  94. package/lib/random.js.map +1 -0
  95. package/lib/runtime-hub.d.ts +28 -0
  96. package/lib/runtime-hub.d.ts.map +1 -0
  97. package/lib/runtime-hub.js +66 -0
  98. package/lib/runtime-hub.js.map +1 -0
  99. package/lib/send-content-text.d.ts +5 -0
  100. package/lib/send-content-text.d.ts.map +1 -0
  101. package/lib/send-content-text.js +24 -0
  102. package/lib/send-content-text.js.map +1 -0
  103. package/package.json +49 -0
  104. package/src/board-sender.ts +16 -0
  105. package/src/choice-keyboard.ts +117 -0
  106. package/src/command-alias.ts +45 -0
  107. package/src/command-help.ts +117 -0
  108. package/src/command-message.ts +76 -0
  109. package/src/game-action-alias.ts +129 -0
  110. package/src/game-commands.ts +4 -0
  111. package/src/game-hub-feature.ts +151 -0
  112. package/src/game-hub-flow.ts +114 -0
  113. package/src/game-hub-menu-context.ts +94 -0
  114. package/src/game-hub-menu.ts +223 -0
  115. package/src/game-hub-mount.ts +172 -0
  116. package/src/game-interactive.ts +88 -0
  117. package/src/game-middleware.ts +14 -0
  118. package/src/game-onboarding.ts +67 -0
  119. package/src/game-records.ts +287 -0
  120. package/src/game-session.ts +97 -0
  121. package/src/grid-keyboard.ts +180 -0
  122. package/src/help-card.ts +188 -0
  123. package/src/index.ts +29 -0
  124. package/src/interaction-profiles.ts +56 -0
  125. package/src/memory-db.ts +128 -0
  126. package/src/random.ts +32 -0
  127. package/src/runtime-hub.ts +86 -0
  128. package/src/send-content-text.ts +21 -0
@@ -0,0 +1,206 @@
1
+ import { MessageCommand, } from '@zhin.js/core';
2
+ import { channelKey } from './board-sender.js';
3
+ import { getRegisteredGame, getRegisteredGames } from './game-hub-feature.js';
4
+ import { createHostGameDb } from './memory-db.js';
5
+ import { generateCompactId } from './random.js';
6
+ let legacyDb = null;
7
+ let hostDatabases = new WeakMap();
8
+ const hostRegistrations = [];
9
+ function activeDatabase() {
10
+ return hostRegistrations[hostRegistrations.length - 1]?.database ?? legacyDb;
11
+ }
12
+ function getRecordModel(database) {
13
+ const model = database.models.get('game_records');
14
+ if (!model)
15
+ throw new Error('game_records not registered');
16
+ return model;
17
+ }
18
+ export function registerGameRecordModels(plugin) {
19
+ plugin.defineModel('game_records', {
20
+ id: { type: 'text', primary: true },
21
+ user_id: { type: 'text', nullable: false },
22
+ user_name: { type: 'text', default: '' },
23
+ channel_key: { type: 'text', nullable: false },
24
+ game_id: { type: 'text', nullable: false },
25
+ result: { type: 'text', nullable: false },
26
+ score: { type: 'integer', default: 0 },
27
+ created_at: { type: 'integer', default: 0 },
28
+ });
29
+ }
30
+ export function initGameRecordDatabase(dbFeature) {
31
+ legacyDb = dbFeature.db;
32
+ }
33
+ const GAME_RECORDS_DEFINITION = {
34
+ id: { type: 'text', primary: true },
35
+ user_id: { type: 'text', nullable: false },
36
+ user_name: { type: 'text', default: '' },
37
+ channel_key: { type: 'text', nullable: false },
38
+ game_id: { type: 'text', nullable: false },
39
+ result: { type: 'text', nullable: false },
40
+ score: { type: 'integer', default: 0 },
41
+ created_at: { type: 'integer', default: 0 },
42
+ };
43
+ /**
44
+ * Plugin Runtime 下用 databaseHostToken 初始化战绩库。
45
+ * 幂等:多个游戏插件 setup 都会调用,只建一次;无 token 时保持跳过(内存/静默)。
46
+ */
47
+ export function initGameRecordHost(host) {
48
+ let database = hostDatabases.get(host);
49
+ if (!database) {
50
+ host.define('game_records', GAME_RECORDS_DEFINITION);
51
+ database = createHostGameDb(host, ['game_records']);
52
+ hostDatabases.set(host, database);
53
+ }
54
+ const registration = Object.freeze({ host, database });
55
+ hostRegistrations.push(registration);
56
+ return () => {
57
+ const index = hostRegistrations.lastIndexOf(registration);
58
+ if (index >= 0)
59
+ hostRegistrations.splice(index, 1);
60
+ };
61
+ }
62
+ function recordId() {
63
+ return generateCompactId('gr');
64
+ }
65
+ /** 对局结束时写入战绩(database 未就绪时静默跳过) */
66
+ export async function recordGameOutcome(message, gameId, result, score = 0) {
67
+ const db = activeDatabase();
68
+ if (!db)
69
+ return;
70
+ const row = {
71
+ id: recordId(),
72
+ user_id: message.$sender.id,
73
+ user_name: String(message.$sender.name ?? message.$sender.id),
74
+ channel_key: channelKey(message),
75
+ game_id: gameId,
76
+ result,
77
+ score,
78
+ created_at: Date.now(),
79
+ };
80
+ await getRecordModel(db).create(row);
81
+ }
82
+ export async function getUserGameStats(userId, channelKeyFilter) {
83
+ const db = activeDatabase();
84
+ if (!db)
85
+ return [];
86
+ const where = channelKeyFilter
87
+ ? { user_id: userId, channel_key: channelKeyFilter }
88
+ : { user_id: userId };
89
+ const rows = await getRecordModel(db).findAll(where);
90
+ const byGame = new Map();
91
+ for (const row of rows) {
92
+ let stat = byGame.get(row.game_id);
93
+ if (!stat) {
94
+ stat = {
95
+ gameId: row.game_id,
96
+ wins: 0,
97
+ losses: 0,
98
+ draws: 0,
99
+ totalScore: 0,
100
+ games: 0,
101
+ };
102
+ byGame.set(row.game_id, stat);
103
+ }
104
+ stat.games++;
105
+ stat.totalScore += row.score;
106
+ if (row.result === 'won')
107
+ stat.wins++;
108
+ else if (row.result === 'lost')
109
+ stat.losses++;
110
+ else if (row.result === 'draw')
111
+ stat.draws++;
112
+ }
113
+ return [...byGame.values()].sort((a, b) => b.wins - a.wins);
114
+ }
115
+ export async function getGameLeaderboard(gameId, channelKeyFilter, limit = 10) {
116
+ const db = activeDatabase();
117
+ if (!db)
118
+ return [];
119
+ const rows = await getRecordModel(db).findAll({
120
+ game_id: gameId,
121
+ channel_key: channelKeyFilter,
122
+ });
123
+ const byUser = new Map();
124
+ for (const row of rows) {
125
+ let entry = byUser.get(row.user_id);
126
+ if (!entry) {
127
+ entry = {
128
+ userId: row.user_id,
129
+ userName: row.user_name,
130
+ wins: 0,
131
+ totalScore: 0,
132
+ games: 0,
133
+ };
134
+ byUser.set(row.user_id, entry);
135
+ }
136
+ entry.games++;
137
+ entry.totalScore += row.score;
138
+ if (row.result === 'won')
139
+ entry.wins++;
140
+ }
141
+ return [...byUser.values()]
142
+ .sort((a, b) => b.wins - a.wins || b.totalScore - a.totalScore)
143
+ .slice(0, limit);
144
+ }
145
+ function gameTitle(gameId) {
146
+ const g = getRegisteredGame(gameId);
147
+ return g ? `${g.icon} ${g.title}` : gameId;
148
+ }
149
+ function formatUserStats(stats) {
150
+ if (!stats.length)
151
+ return '暂无战绩记录,快去 `/游戏` 开一局吧!';
152
+ const lines = stats.map((s) => {
153
+ const title = gameTitle(s.gameId);
154
+ return `• ${title}:${s.wins} 胜 ${s.losses} 负${s.draws ? ` ${s.draws} 平` : ''}(${s.games} 局,得分 ${s.totalScore})`;
155
+ });
156
+ return ['📊 **你的战绩**', '', ...lines].join('\n');
157
+ }
158
+ function formatLeaderboard(gameId, entries) {
159
+ const title = gameTitle(gameId);
160
+ if (!entries.length) {
161
+ return `${title} 在本群暂无排行,发送 \`/游戏\` 开始第一局!`;
162
+ }
163
+ const lines = entries.map((e, i) => `${i + 1}. ${e.userName} — ${e.wins} 胜 / ${e.games} 局(得分 ${e.totalScore})`);
164
+ return [`🏆 **${title} 本群排行**`, '', ...lines].join('\n');
165
+ }
166
+ export function mountGameRecordCommands(root) {
167
+ const disposers = [];
168
+ disposers.push(root.addCommand(new MessageCommand('/战绩')
169
+ .desc('查看本人在本群的游戏战绩')
170
+ .action(async (message) => {
171
+ const stats = await getUserGameStats(message.$sender.id, channelKey(message));
172
+ return formatUserStats(stats);
173
+ })), root.addCommand(new MessageCommand('/排行')
174
+ .desc('查看本群某游戏排行榜')
175
+ .action(async (message, ...args) => {
176
+ const query = args.join(' ').trim();
177
+ const games = getRegisteredGames();
178
+ if (!games.length)
179
+ return '暂无已注册游戏。';
180
+ let gameId = games[0].id;
181
+ if (query) {
182
+ const hit = games.find((g) => g.id === query
183
+ || g.title.includes(query)
184
+ || g.commandPrefix.includes(query)
185
+ || g.aliases?.some((a) => a.includes(query)));
186
+ if (!hit) {
187
+ const names = games.map((g) => g.title).join('、');
188
+ return `未找到游戏「${query}」。可选:${names}`;
189
+ }
190
+ gameId = hit.id;
191
+ }
192
+ const board = await getGameLeaderboard(gameId, channelKey(message));
193
+ return formatLeaderboard(gameId, board);
194
+ })));
195
+ return () => {
196
+ for (const d of disposers)
197
+ d();
198
+ };
199
+ }
200
+ /** 测试专用 */
201
+ export function resetGameRecordsForTests() {
202
+ legacyDb = null;
203
+ hostDatabases = new WeakMap();
204
+ hostRegistrations.splice(0);
205
+ }
206
+ //# sourceMappingURL=game-records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-records.js","sourceRoot":"","sources":["../src/game-records.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 通用游戏会话接口
3
+ * 定义回合制游戏的基础会话结构
4
+ */
5
+ import type { Message } from '@zhin.js/core';
6
+ /** 会话状态 */
7
+ export type GameStatus = 'active' | 'won' | 'draw' | 'aborted';
8
+ /** 通用游戏会话接口 */
9
+ export interface GameSession {
10
+ /** 会话 ID */
11
+ id: string;
12
+ /** 适配器名称 */
13
+ adapter: string;
14
+ /** Endpoint 名称 */
15
+ endpoint: string;
16
+ /** 频道类型 */
17
+ channel_type: 'private' | 'group' | 'channel';
18
+ /** 频道 ID */
19
+ channel_id: string;
20
+ /** 频道唯一键 */
21
+ channel_key: string;
22
+ /** 棋盘消息 ID(用于编辑) */
23
+ board_message_id: string;
24
+ /** 当前状态 */
25
+ status: GameStatus;
26
+ /** 更新时间戳 */
27
+ updated_at: number;
28
+ /** 创建时间戳 */
29
+ created_at: number;
30
+ }
31
+ /** 回合制游戏会话(2 人对战) */
32
+ export interface TurnBasedSession extends GameSession {
33
+ /** 玩家 1 ID */
34
+ player_1: string;
35
+ /** 玩家 2 ID */
36
+ player_2: string;
37
+ /** 玩家 1 显示名 */
38
+ player_1_name: string;
39
+ /** 玩家 2 显示名 */
40
+ player_2_name: string;
41
+ /** 当前回合(1 或 2) */
42
+ turn: 1 | 2;
43
+ /** 胜者(0=无,1=玩家1,2=玩家2) */
44
+ winner: 0 | 1 | 2;
45
+ /** 步数 */
46
+ move_count: number;
47
+ }
48
+ /** 生成会话 ID */
49
+ export declare function generateSessionId(): string;
50
+ /** 获取当前回合玩家 ID */
51
+ export declare function currentPlayerId(session: TurnBasedSession): string;
52
+ /** 判断用户是否为本局玩家 */
53
+ export declare function isPlayer(session: TurnBasedSession, userId: string): boolean;
54
+ /** 判断是否轮到该用户 */
55
+ export declare function isPlayerTurn(session: TurnBasedSession, userId: string): boolean;
56
+ /** 获取玩家编号(1 或 2),非玩家返回 null */
57
+ export declare function playerNumber(session: TurnBasedSession, userId: string): 1 | 2 | null;
58
+ /** 切换回合 */
59
+ export declare function nextTurn(current: 1 | 2): 1 | 2;
60
+ /** 从 Message 提取发送者显示名 */
61
+ export declare function senderDisplayName(message: Message<any>): string;
62
+ /** 比对出站 board_message_id 与平台回调 messageId(含 composite id) */
63
+ export declare function boardMessageMatches(stored: string, messageId: string): boolean;
64
+ //# sourceMappingURL=game-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-session.d.ts","sourceRoot":"","sources":["../src/game-session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,WAAW;AACX,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;AAE/D,eAAe;AACf,MAAM,WAAW,WAAW;IAC1B,YAAY;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,YAAY;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW;IACX,YAAY,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9C,YAAY;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAqB;AACrB,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,cAAc;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB;IAClB,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACZ,0BAA0B;IAC1B,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClB,SAAS;IACT,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,cAAc;AACd,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,kBAAkB;AAClB,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAEjE;AAED,kBAAkB;AAClB,wBAAgB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3E;AAED,gBAAgB;AAChB,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE/E;AAED,+BAA+B;AAC/B,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAIpF;AAED,WAAW;AACX,wBAAgB,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAE9C;AAED,yBAAyB;AACzB,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAG/D;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAK9E"}
@@ -0,0 +1,44 @@
1
+ import { generateCompactId } from './random.js';
2
+ /** 生成会话 ID */
3
+ export function generateSessionId() {
4
+ return generateCompactId('s');
5
+ }
6
+ /** 获取当前回合玩家 ID */
7
+ export function currentPlayerId(session) {
8
+ return session.turn === 1 ? session.player_1 : session.player_2;
9
+ }
10
+ /** 判断用户是否为本局玩家 */
11
+ export function isPlayer(session, userId) {
12
+ return session.player_1 === userId || session.player_2 === userId;
13
+ }
14
+ /** 判断是否轮到该用户 */
15
+ export function isPlayerTurn(session, userId) {
16
+ return currentPlayerId(session) === userId;
17
+ }
18
+ /** 获取玩家编号(1 或 2),非玩家返回 null */
19
+ export function playerNumber(session, userId) {
20
+ if (session.player_1 === userId)
21
+ return 1;
22
+ if (session.player_2 === userId)
23
+ return 2;
24
+ return null;
25
+ }
26
+ /** 切换回合 */
27
+ export function nextTurn(current) {
28
+ return current === 1 ? 2 : 1;
29
+ }
30
+ /** 从 Message 提取发送者显示名 */
31
+ export function senderDisplayName(message) {
32
+ const name = message.$sender.name?.trim();
33
+ return name || message.$sender.id;
34
+ }
35
+ /** 比对出站 board_message_id 与平台回调 messageId(含 composite id) */
36
+ export function boardMessageMatches(stored, messageId) {
37
+ if (!stored || !messageId)
38
+ return false;
39
+ if (stored === messageId || stored.endsWith(`:${messageId}`))
40
+ return true;
41
+ const tail = stored.split(':').pop();
42
+ return !!(tail && messageId.endsWith(tail));
43
+ }
44
+ //# sourceMappingURL=game-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-session.js","sourceRoot":"","sources":["../src/game-session.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA+ChD,cAAc;AACd,MAAM,UAAU,iBAAiB;IAC/B,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AAED,kBAAkB;AAClB,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,OAAO,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;AAClE,CAAC;AAED,kBAAkB;AAClB,MAAM,UAAU,QAAQ,CAAC,OAAyB,EAAE,MAAc;IAChE,OAAO,OAAO,CAAC,QAAQ,KAAK,MAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,CAAC;AACpE,CAAC;AAED,gBAAgB;AAChB,MAAM,UAAU,YAAY,CAAC,OAAyB,EAAE,MAAc;IACpE,OAAO,eAAe,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC;AAC7C,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,YAAY,CAAC,OAAyB,EAAE,MAAc;IACpE,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM;QAAE,OAAO,CAAC,CAAC;IAC1C,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM;QAAE,OAAO,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,WAAW;AACX,MAAM,UAAU,QAAQ,CAAC,OAAc;IACrC,OAAO,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED,yBAAyB;AACzB,MAAM,UAAU,iBAAiB,CAAC,OAAqB;IACrD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IAC1C,OAAO,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;AACpC,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,SAAiB;IACnE,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IACrC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 通用网格按钮键盘构建器
3
+ * 支持井字棋(3×3)、五子棋(15×15)、四子棋(7×6)等
4
+ */
5
+ import { type SendContent } from '@zhin.js/core';
6
+ import { type ChoiceOption } from './choice-keyboard.js';
7
+ export interface GridCell<T = unknown> {
8
+ /** 单元格状态(游戏自定义,如 0=空, 1=X, 2=O) */
9
+ state: T;
10
+ /** 按钮显示文本 */
11
+ label: string;
12
+ /** 是否禁用(已落子或终局) */
13
+ disabled: boolean;
14
+ /** 高亮(胜利连线等) */
15
+ highlight?: boolean;
16
+ }
17
+ export interface GridKeyboardOptions<T = unknown> {
18
+ /** 游戏前缀(如 'ttt', 'gomoku', 'c4') */
19
+ gamePrefix: string;
20
+ /** 会话 ID */
21
+ sessionId: string;
22
+ /** 行数 */
23
+ rows: number;
24
+ /** 列数 */
25
+ cols: number;
26
+ /** 单元格数据,按行优先 [row * cols + col] */
27
+ cells: GridCell<T>[];
28
+ /** 状态行文本 */
29
+ statusLine: string;
30
+ /** 是否终局(全部按钮禁用) */
31
+ terminal?: boolean;
32
+ /** 省略 ASCII 文本棋盘(QQ 等平台) */
33
+ omitAsciiBoard?: boolean;
34
+ /** ASCII 棋盘渲染器(可选,省略则不渲染 ASCII) */
35
+ renderAscii?: (cells: GridCell<T>[], rows: number, cols: number, highlight?: number[]) => string;
36
+ /** 高亮的单元格索引(胜利连线等) */
37
+ highlight?: number[];
38
+ /** fallback 提示文本 */
39
+ fallbackHint?: string;
40
+ /** 终局时在棋盘下方追加的选项(如「再来一局」,不受 terminal 禁用) */
41
+ postChoices?: ChoiceOption[];
42
+ /** postChoices 使用的 profile,默认 terminal */
43
+ postChoicesProfile?: 'terminal';
44
+ /** terminal 私聊 auto-enter */
45
+ channelType?: string;
46
+ }
47
+ /**
48
+ * 构建 fallback 数字映射(仅可落子的格子)
49
+ */
50
+ export declare function buildGridFallbackMap(gamePrefix: string, sessionId: string, cells: GridCell[]): Record<string, string>;
51
+ /**
52
+ * 构建网格按钮键盘消息内容
53
+ */
54
+ export declare function buildGridKeyboard<T>(options: GridKeyboardOptions<T>): SendContent;
55
+ /**
56
+ * 解析网格 payload:`{prefix}:{sessionId}:{cellIndex}`
57
+ */
58
+ export declare function parseGridPayload(payload: string, expectedPrefix?: string): {
59
+ prefix: string;
60
+ sessionId: string;
61
+ cell: number;
62
+ } | null;
63
+ /**
64
+ * 解析按钮 ID 格式:`c{cellIndex}`(QQ 等平台 action 可能只回传 button_id)
65
+ */
66
+ export declare function parseCellButtonId(value: string): number | null;
67
+ //# sourceMappingURL=grid-keyboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid-keyboard.d.ts","sourceRoot":"","sources":["../src/grid-keyboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAW,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAIjF,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACnC,mCAAmC;IACnC,KAAK,EAAE,CAAC,CAAC;IACT,aAAa;IACb,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,OAAO;IAC9C,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS;IACT,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,YAAY;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mCAAmC;IACnC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IACjG,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;IAC7B,0CAA0C;IAC1C,kBAAkB,CAAC,EAAE,UAAU,CAAC;IAChC,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,QAAQ,EAAE,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,WAAW,CA+EjF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,MAAM,GACtB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQ5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM9D"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * 通用网格按钮键盘构建器
3
+ * 支持井字棋(3×3)、五子棋(15×15)、四子棋(7×6)等
4
+ */
5
+ import { segment } from '@zhin.js/core';
6
+ import { buildChoiceFallbackMap } from './choice-keyboard.js';
7
+ import { applyInteractionProfile } from './interaction-profiles.js';
8
+ /**
9
+ * 构建 fallback 数字映射(仅可落子的格子)
10
+ */
11
+ export function buildGridFallbackMap(gamePrefix, sessionId, cells) {
12
+ const map = {};
13
+ let n = 1;
14
+ for (let i = 0; i < cells.length; i++) {
15
+ if (!cells[i].disabled) {
16
+ map[String(n)] = `${gamePrefix}:${sessionId}:${i}`;
17
+ n++;
18
+ }
19
+ }
20
+ return map;
21
+ }
22
+ /**
23
+ * 构建网格按钮键盘消息内容
24
+ */
25
+ export function buildGridKeyboard(options) {
26
+ const { gamePrefix, sessionId, rows, cols, cells, statusLine, terminal, omitAsciiBoard, renderAscii, highlight, fallbackHint, postChoices, postChoicesProfile = 'terminal', channelType, } = options;
27
+ const buttonRows = [];
28
+ for (let r = 0; r < rows; r++) {
29
+ const row = [];
30
+ for (let c = 0; c < cols; c++) {
31
+ const i = r * cols + c;
32
+ const cell = cells[i];
33
+ row.push(segment.button({
34
+ id: `c${i}`,
35
+ label: cell.label,
36
+ payload: `${gamePrefix}:${sessionId}:${i}`,
37
+ disabled: terminal || cell.disabled,
38
+ style: cell.highlight ? 'primary' : 'secondary',
39
+ }));
40
+ }
41
+ buttonRows.push(row);
42
+ }
43
+ const fallback = buildGridFallbackMap(gamePrefix, sessionId, cells);
44
+ const textLines = [statusLine];
45
+ if (!omitAsciiBoard && renderAscii) {
46
+ textLines.push('', renderAscii(cells, rows, cols, highlight));
47
+ }
48
+ if (postChoices?.length) {
49
+ buttonRows.push(postChoices.map((choice) => {
50
+ const base = segment.button({
51
+ id: choice.id,
52
+ label: choice.label,
53
+ payload: `${gamePrefix}:${sessionId}:${choice.id}`,
54
+ disabled: choice.disabled ?? false,
55
+ style: choice.style ?? 'primary',
56
+ });
57
+ return segment.button(applyInteractionProfile(base.data, {
58
+ profile: postChoicesProfile,
59
+ channelType,
60
+ }));
61
+ }));
62
+ Object.assign(fallback, buildChoiceFallbackMap(gamePrefix, sessionId, postChoices));
63
+ }
64
+ const parts = [
65
+ segment.text(textLines.join('\n')),
66
+ segment.keyboard(buttonRows, {
67
+ fallback: fallbackHint || Object.keys(fallback).length > 0
68
+ ? {
69
+ hint: fallbackHint ?? '回复数字选择',
70
+ map: fallback,
71
+ }
72
+ : undefined,
73
+ }).toElement(),
74
+ ];
75
+ return parts;
76
+ }
77
+ /**
78
+ * 解析网格 payload:`{prefix}:{sessionId}:{cellIndex}`
79
+ */
80
+ export function parseGridPayload(payload, expectedPrefix) {
81
+ const m = /^([a-z0-9_]+):([^:]+):(\d+)$/i.exec(payload);
82
+ if (!m)
83
+ return null;
84
+ const prefix = m[1];
85
+ if (expectedPrefix && prefix !== expectedPrefix)
86
+ return null;
87
+ const cell = Number(m[3]);
88
+ if (!Number.isInteger(cell) || cell < 0)
89
+ return null;
90
+ return { prefix, sessionId: m[2], cell };
91
+ }
92
+ /**
93
+ * 解析按钮 ID 格式:`c{cellIndex}`(QQ 等平台 action 可能只回传 button_id)
94
+ */
95
+ export function parseCellButtonId(value) {
96
+ const m = /^c(\d+)$/.exec(value);
97
+ if (!m)
98
+ return null;
99
+ const cell = Number(m[1]);
100
+ if (!Number.isInteger(cell) || cell < 0)
101
+ return null;
102
+ return cell;
103
+ }
104
+ //# sourceMappingURL=grid-keyboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid-keyboard.js","sourceRoot":"","sources":["../src/grid-keyboard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { RegisteredGame } from './game-hub-feature.js';
2
+ import { type CommandHelpSource } from './command-help.js';
3
+ export declare const HELP_CARD_CANVAS: "#d8dce3";
4
+ export declare const HELP_CARD_WIDTH = 560;
5
+ export interface BotHelpCardInput {
6
+ games: readonly RegisteredGame[];
7
+ commands?: readonly CommandHelpSource[];
8
+ channelType?: string;
9
+ }
10
+ export declare function buildBotHelpHtml(input: BotHelpCardInput): string;
11
+ export declare function buildHubHelpHtml(games: readonly RegisteredGame[], commands?: readonly CommandHelpSource[]): string;
12
+ //# sourceMappingURL=help-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-card.d.ts","sourceRoot":"","sources":["../src/help-card.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAE3B,eAAO,MAAM,gBAAgB,WAA4B,CAAC;AAC1D,eAAO,MAAM,eAAe,MAAM,CAAC;AAsBnC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IACjC,QAAQ,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CA+FhE;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,QAAQ,CAAC,EAAE,SAAS,iBAAiB,EAAE,GACtC,MAAM,CAwCR"}