@zhin.js/plugin-tic-tac-toe 1.0.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.
Files changed (53) hide show
  1. package/README.md +36 -0
  2. package/lib/board-view.d.ts +24 -0
  3. package/lib/board-view.d.ts.map +1 -0
  4. package/lib/board-view.js +58 -0
  5. package/lib/board-view.js.map +1 -0
  6. package/lib/commands.d.ts +6 -0
  7. package/lib/commands.d.ts.map +1 -0
  8. package/lib/commands.js +111 -0
  9. package/lib/commands.js.map +1 -0
  10. package/lib/engine.d.ts +25 -0
  11. package/lib/engine.d.ts.map +1 -0
  12. package/lib/engine.js +117 -0
  13. package/lib/engine.js.map +1 -0
  14. package/lib/game-flow.d.ts +11 -0
  15. package/lib/game-flow.d.ts.map +1 -0
  16. package/lib/game-flow.js +143 -0
  17. package/lib/game-flow.js.map +1 -0
  18. package/lib/hub-register.d.ts +5 -0
  19. package/lib/hub-register.d.ts.map +1 -0
  20. package/lib/hub-register.js +30 -0
  21. package/lib/hub-register.js.map +1 -0
  22. package/lib/index.d.ts +2 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +37 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/models.d.ts +55 -0
  27. package/lib/models.d.ts.map +1 -0
  28. package/lib/models.js +44 -0
  29. package/lib/models.js.map +1 -0
  30. package/lib/player-label.d.ts +16 -0
  31. package/lib/player-label.d.ts.map +1 -0
  32. package/lib/player-label.js +36 -0
  33. package/lib/player-label.js.map +1 -0
  34. package/lib/session-service.d.ts +50 -0
  35. package/lib/session-service.d.ts.map +1 -0
  36. package/lib/session-service.js +176 -0
  37. package/lib/session-service.js.map +1 -0
  38. package/lib/ttt-command.d.ts +5 -0
  39. package/lib/ttt-command.d.ts.map +1 -0
  40. package/lib/ttt-command.js +67 -0
  41. package/lib/ttt-command.js.map +1 -0
  42. package/package.json +54 -0
  43. package/plugin.yml +2 -0
  44. package/src/board-view.ts +93 -0
  45. package/src/commands.ts +129 -0
  46. package/src/engine.ts +131 -0
  47. package/src/game-flow.ts +217 -0
  48. package/src/hub-register.ts +31 -0
  49. package/src/index.ts +43 -0
  50. package/src/models.ts +104 -0
  51. package/src/player-label.ts +48 -0
  52. package/src/session-service.ts +205 -0
  53. package/src/ttt-command.ts +76 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/lib/index.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @zhin.js/plugin-tic-tac-toe — 跨平台井字棋
3
+ *
4
+ * ```yaml
5
+ * plugins:
6
+ * - "@zhin.js/plugin-tic-tac-toe"
7
+ * database:
8
+ * dialect: sqlite
9
+ * storage: ./data/zhin.db
10
+ * ```
11
+ */
12
+ import { Cron, formatCompact, usePlugin } from 'zhin.js';
13
+ import { registerModels } from './models.js';
14
+ import { createServices, resolveGameDatabase } from './session-service.js';
15
+ import { registerCommands, registerInteractive, registerTextFallback } from './commands.js';
16
+ import { registerTttHub } from './hub-register.js';
17
+ const plugin = usePlugin();
18
+ const { logger, useContext, addCron } = plugin;
19
+ registerModels(plugin);
20
+ let services = null;
21
+ useContext('database', (dbFeature) => {
22
+ services = createServices(resolveGameDatabase(dbFeature));
23
+ logger.info(formatCompact({ 模块: '井字棋', 数据模型: '已就绪' }));
24
+ });
25
+ registerTttHub(() => services);
26
+ registerCommands(plugin, () => services);
27
+ registerInteractive(plugin, () => services);
28
+ registerTextFallback(plugin, () => services);
29
+ addCron(new Cron('0 */10 * * * *', async () => {
30
+ if (!services)
31
+ return;
32
+ const n = await services.session.abortStale(30 * 60 * 1000);
33
+ if (n > 0)
34
+ logger.debug(formatCompact({ 井字棋: '清理超时局', count: n }));
35
+ }));
36
+ logger.info(formatCompact({ 模块: '井字棋', 状态: '已加载' }));
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAwB,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAwB,MAAM,sBAAsB,CAAC;AACjG,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;AAC3B,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;AAE/C,cAAc,CAAC,MAAM,CAAC,CAAC;AAEvB,IAAI,QAAQ,GAA2B,IAAI,CAAC;AAE5C,UAAU,CAAC,UAAU,EAAE,CAAC,SAA0B,EAAE,EAAE;IACpD,QAAQ,GAAG,cAAc,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEH,cAAc,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAC/B,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AACzC,mBAAmB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAC5C,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAE7C,OAAO,CACL,IAAI,IAAI,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;IACpC,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC5D,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC,CAAC,CACH,CAAC;AAEF,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { Models, Plugin } from 'zhin.js';
2
+ export type TttSessionStatus = 'active' | 'won' | 'draw' | 'aborted';
3
+ declare module 'zhin.js' {
4
+ interface Models {
5
+ ttt_sessions: {
6
+ id: string;
7
+ adapter: string;
8
+ endpoint: string;
9
+ channel_type: string;
10
+ channel_id: string;
11
+ channel_key: string;
12
+ player_x: string;
13
+ player_o: string;
14
+ player_x_name: string;
15
+ player_o_name: string;
16
+ board: string;
17
+ turn: number;
18
+ status: TttSessionStatus;
19
+ winner: number;
20
+ board_message_id: string;
21
+ move_count: number;
22
+ updated_at: number;
23
+ created_at: number;
24
+ };
25
+ ttt_queue: {
26
+ id: number;
27
+ channel_key: string;
28
+ user_id: string;
29
+ user_name: string;
30
+ joined_at: number;
31
+ };
32
+ ttt_moves: {
33
+ id: number;
34
+ session_id: string;
35
+ player_id: string;
36
+ cell: number;
37
+ move_index: number;
38
+ created_at: number;
39
+ };
40
+ ttt_spectators: {
41
+ id: number;
42
+ session_id: string;
43
+ user_id: string;
44
+ joined_at: number;
45
+ };
46
+ }
47
+ }
48
+ export type TttSessionRow = Models['ttt_sessions'];
49
+ export type TttQueueRow = Models['ttt_queue'];
50
+ export type TttMoveRow = Models['ttt_moves'];
51
+ export type TttSpectatorRow = Models['ttt_spectators'];
52
+ /** 井字棋插件注册的表名 */
53
+ export type TttModelName = 'ttt_sessions' | 'ttt_queue' | 'ttt_moves' | 'ttt_spectators';
54
+ export declare function registerModels(plugin: Plugin): void;
55
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;AAErE,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,MAAM;QACd,YAAY,EAAE;YACZ,EAAE,EAAE,MAAM,CAAC;YACX,OAAO,EAAE,MAAM,CAAC;YAChB,QAAQ,EAAE,MAAM,CAAC;YACjB,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,MAAM,CAAC;YACnB,WAAW,EAAE,MAAM,CAAC;YACpB,QAAQ,EAAE,MAAM,CAAC;YACjB,QAAQ,EAAE,MAAM,CAAC;YACjB,aAAa,EAAE,MAAM,CAAC;YACtB,aAAa,EAAE,MAAM,CAAC;YACtB,KAAK,EAAE,MAAM,CAAC;YACd,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,gBAAgB,CAAC;YACzB,MAAM,EAAE,MAAM,CAAC;YACf,gBAAgB,EAAE,MAAM,CAAC;YACzB,UAAU,EAAE,MAAM,CAAC;YACnB,UAAU,EAAE,MAAM,CAAC;YACnB,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC;QACF,SAAS,EAAE;YACT,EAAE,EAAE,MAAM,CAAC;YACX,WAAW,EAAE,MAAM,CAAC;YACpB,OAAO,EAAE,MAAM,CAAC;YAChB,SAAS,EAAE,MAAM,CAAC;YAClB,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC;QACF,SAAS,EAAE;YACT,EAAE,EAAE,MAAM,CAAC;YACX,UAAU,EAAE,MAAM,CAAC;YACnB,SAAS,EAAE,MAAM,CAAC;YAClB,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,MAAM,CAAC;YACnB,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC;QACF,cAAc,EAAE;YACd,EAAE,EAAE,MAAM,CAAC;YACX,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,EAAE,MAAM,CAAC;YAChB,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC;KACH;CACF;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;AACnD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;AAC9C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;AAC7C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEvD,iBAAiB;AACjB,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,WAAW,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAEzF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CA6CnD"}
package/lib/models.js ADDED
@@ -0,0 +1,44 @@
1
+ export function registerModels(plugin) {
2
+ plugin.defineModel('ttt_sessions', {
3
+ id: { type: 'text', primary: true },
4
+ adapter: { type: 'text', nullable: false },
5
+ endpoint: { type: 'text', nullable: false },
6
+ channel_type: { type: 'text', nullable: false },
7
+ channel_id: { type: 'text', nullable: false },
8
+ channel_key: { type: 'text', nullable: false },
9
+ player_x: { type: 'text', nullable: false },
10
+ player_o: { type: 'text', nullable: false },
11
+ player_x_name: { type: 'text', default: '' },
12
+ player_o_name: { type: 'text', default: '' },
13
+ board: { type: 'text', default: '[]' },
14
+ turn: { type: 'integer', default: 1 },
15
+ status: { type: 'text', default: 'active' },
16
+ winner: { type: 'integer', default: 0 },
17
+ board_message_id: { type: 'text', default: '' },
18
+ move_count: { type: 'integer', default: 0 },
19
+ updated_at: { type: 'integer', default: 0 },
20
+ created_at: { type: 'integer', default: 0 },
21
+ });
22
+ plugin.defineModel('ttt_queue', {
23
+ id: { type: 'integer', primary: true },
24
+ channel_key: { type: 'text', nullable: false },
25
+ user_id: { type: 'text', nullable: false },
26
+ user_name: { type: 'text', default: '' },
27
+ joined_at: { type: 'integer', default: 0 },
28
+ });
29
+ plugin.defineModel('ttt_moves', {
30
+ id: { type: 'integer', primary: true },
31
+ session_id: { type: 'text', nullable: false },
32
+ player_id: { type: 'text', nullable: false },
33
+ cell: { type: 'integer', nullable: false },
34
+ move_index: { type: 'integer', nullable: false },
35
+ created_at: { type: 'integer', default: 0 },
36
+ });
37
+ plugin.defineModel('ttt_spectators', {
38
+ id: { type: 'integer', primary: true },
39
+ session_id: { type: 'text', nullable: false },
40
+ user_id: { type: 'text', nullable: false },
41
+ joined_at: { type: 'integer', default: 0 },
42
+ });
43
+ }
44
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { MessageSender } from 'zhin.js';
2
+ import type { TttSessionRow } from './models.js';
3
+ import { type Cell } from './engine.js';
4
+ export declare const BOT_ID = "__ttt_bot__";
5
+ export declare function senderDisplayName(sender: Pick<MessageSender, 'id' | 'name'>): string;
6
+ export declare function playerDisplayName(playerId: string, storedName?: string): string;
7
+ export declare function formatPlayerWithMark(playerId: string, storedName: string | undefined, mark: '✕' | '○'): string;
8
+ export declare function formatRosterLine(session: TttSessionRow): string;
9
+ export declare function playerRefForMark(session: TttSessionRow, mark: Cell): {
10
+ id: string;
11
+ name?: string;
12
+ };
13
+ export declare function formatTurnStatus(session: TttSessionRow, moveCount: number): string;
14
+ export declare function formatWinHeadline(session: TttSessionRow, winner: Cell): string;
15
+ export declare function formatWinStatus(session: TttSessionRow, winner: Cell, boardAscii: string): string;
16
+ //# sourceMappingURL=player-label.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player-label.d.ts","sourceRoot":"","sources":["../src/player-label.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAQ,KAAK,IAAI,EAAa,MAAM,aAAa,CAAC;AAEzD,eAAO,MAAM,MAAM,gBAAgB,CAAC;AAEpC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG,MAAM,CAGpF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAI/E;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,GAAG,GAAG,GAAG,GACd,MAAM,CAER;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAE/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAGlG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAIlF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,GAAG,MAAM,CAG9E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhG"}
@@ -0,0 +1,36 @@
1
+ import { X, O, cellLabel } from './engine.js';
2
+ export const BOT_ID = '__ttt_bot__';
3
+ export function senderDisplayName(sender) {
4
+ const name = sender.name?.trim();
5
+ return name || sender.id;
6
+ }
7
+ export function playerDisplayName(playerId, storedName) {
8
+ if (playerId === BOT_ID)
9
+ return '机器人';
10
+ const name = storedName?.trim();
11
+ return name || playerId;
12
+ }
13
+ export function formatPlayerWithMark(playerId, storedName, mark) {
14
+ return `${playerDisplayName(playerId, storedName)} (${mark})`;
15
+ }
16
+ export function formatRosterLine(session) {
17
+ return `${formatPlayerWithMark(session.player_x, session.player_x_name, '✕')} vs ${formatPlayerWithMark(session.player_o, session.player_o_name, '○')}`;
18
+ }
19
+ export function playerRefForMark(session, mark) {
20
+ if (mark === X)
21
+ return { id: session.player_x, name: session.player_x_name };
22
+ return { id: session.player_o, name: session.player_o_name };
23
+ }
24
+ export function formatTurnStatus(session, moveCount) {
25
+ const mark = session.turn === 1 ? X : O;
26
+ const { id, name } = playerRefForMark(session, mark);
27
+ return `第 ${moveCount} 手 · 轮到 ${formatPlayerWithMark(id, name, cellLabel(mark))}`;
28
+ }
29
+ export function formatWinHeadline(session, winner) {
30
+ const { id, name } = playerRefForMark(session, winner);
31
+ return `🎉 ${formatPlayerWithMark(id, name, cellLabel(winner))} 获胜!`;
32
+ }
33
+ export function formatWinStatus(session, winner, boardAscii) {
34
+ return `${formatWinHeadline(session, winner)}\n${boardAscii}`;
35
+ }
36
+ //# sourceMappingURL=player-label.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player-label.js","sourceRoot":"","sources":["../src/player-label.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,CAAC,EAAa,SAAS,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;AAEpC,MAAM,UAAU,iBAAiB,CAAC,MAA0C;IAC1E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACjC,OAAO,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,UAAmB;IACrE,IAAI,QAAQ,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,IAAI,GAAG,UAAU,EAAE,IAAI,EAAE,CAAC;IAChC,OAAO,IAAI,IAAI,QAAQ,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,QAAgB,EAChB,UAA8B,EAC9B,IAAe;IAEf,OAAO,GAAG,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC,KAAK,IAAI,GAAG,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,OAAO,GAAG,oBAAoB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,OAAO,oBAAoB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,EAAE,CAAC;AAC1J,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAsB,EAAE,IAAU;IACjE,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;IAC7E,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAsB,EAAE,SAAiB;IACxE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrD,OAAO,KAAK,SAAS,WAAW,oBAAoB,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAc,CAAC,EAAE,CAAC;AACjG,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAsB,EAAE,MAAY;IACpE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACvD,OAAO,MAAM,oBAAoB,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,MAAM,CAAc,CAAC,MAAM,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAsB,EAAE,MAAY,EAAE,UAAkB;IACtF,OAAO,GAAG,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { Database, DatabaseFeature, Message, Models } from 'zhin.js';
2
+ import type { TttSessionRow } from './models.js';
3
+ /** 井字棋服务使用的数据库实例(Models 经 models.ts 模块增强) */
4
+ export type TttDatabase = Database<unknown, Models, string>;
5
+ export type TttPlayerRef = {
6
+ id: string;
7
+ displayName: string;
8
+ };
9
+ export declare class QueueService {
10
+ private readonly db;
11
+ constructor(db: TttDatabase);
12
+ join(channel: string, userId: string, displayName?: string): Promise<{
13
+ queued: boolean;
14
+ position: number;
15
+ }>;
16
+ leave(channel: string, userId: string): Promise<boolean>;
17
+ list(channel: string): Promise<string[]>;
18
+ tryMatch(channel: string): Promise<[TttPlayerRef, TttPlayerRef] | null>;
19
+ count(channel: string): Promise<number>;
20
+ }
21
+ export declare function sessionId(): string;
22
+ export declare class SessionService {
23
+ private readonly db;
24
+ constructor(db: TttDatabase);
25
+ getActiveByChannel(channel: string): Promise<TttSessionRow | null>;
26
+ getById(id: string): Promise<TttSessionRow | null>;
27
+ getActiveForUser(channel: string, userId: string): Promise<TttSessionRow | null>;
28
+ /** 根据棋盘消息 ID 查找进行中的局(QQ 等 action 带 sourceMessageId) */
29
+ getActiveByBoardMessageId(messageId: string): Promise<TttSessionRow | null>;
30
+ createSession(input: {
31
+ message: Message<any>;
32
+ playerX: string;
33
+ playerO: string;
34
+ playerXName?: string;
35
+ playerOName?: string;
36
+ boardJson: string;
37
+ }): Promise<TttSessionRow>;
38
+ updateSession(id: string, patch: Partial<TttSessionRow>): Promise<void>;
39
+ recordMove(sessionId: string, playerId: string, cell: number, moveIndex: number): Promise<void>;
40
+ addSpectator(sessionId: string, userId: string): Promise<void>;
41
+ listSpectators(sessionId: string): Promise<string[]>;
42
+ abortStale(idleMs: number): Promise<number>;
43
+ }
44
+ export type SessionServices = {
45
+ queue: QueueService;
46
+ session: SessionService;
47
+ };
48
+ export declare function createServices(db: TttDatabase): SessionServices;
49
+ export declare function resolveGameDatabase(feature: DatabaseFeature): TttDatabase;
50
+ //# sourceMappingURL=session-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-service.d.ts","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,176 @@
1
+ import { channelKey } from '@zhin.js/game-shared';
2
+ function getModel(db, name) {
3
+ const model = db.models.get(name);
4
+ if (!model) {
5
+ throw new Error(`Model ${name} is not registered`);
6
+ }
7
+ return model;
8
+ }
9
+ export class QueueService {
10
+ db;
11
+ constructor(db) {
12
+ this.db = db;
13
+ }
14
+ async join(channel, userId, displayName) {
15
+ const q = getModel(this.db, 'ttt_queue');
16
+ const existing = await q.findAll({ channel_key: channel, user_id: userId });
17
+ const all = await q.findAll({ channel_key: channel });
18
+ all.sort((a, b) => a.joined_at - b.joined_at);
19
+ if (existing.length > 0) {
20
+ return { queued: true, position: all.findIndex((r) => r.user_id === userId) + 1 };
21
+ }
22
+ const name = displayName?.trim() || '';
23
+ await q.create({ channel_key: channel, user_id: userId, user_name: name, joined_at: Date.now() });
24
+ const after = await q.findAll({ channel_key: channel });
25
+ after.sort((a, b) => a.joined_at - b.joined_at);
26
+ return { queued: true, position: after.length };
27
+ }
28
+ async leave(channel, userId) {
29
+ const q = getModel(this.db, 'ttt_queue');
30
+ const rows = await q.findAll({ channel_key: channel, user_id: userId });
31
+ if (rows.length === 0)
32
+ return false;
33
+ await q.deleteWhere({ channel_key: channel, user_id: userId });
34
+ return true;
35
+ }
36
+ async list(channel) {
37
+ const rows = await getModel(this.db, 'ttt_queue').findAll({ channel_key: channel });
38
+ return rows.map((r) => r.user_id);
39
+ }
40
+ async tryMatch(channel) {
41
+ const q = getModel(this.db, 'ttt_queue');
42
+ const rows = await q.findAll({ channel_key: channel });
43
+ rows.sort((a, b) => a.joined_at - b.joined_at);
44
+ if (rows.length < 2)
45
+ return null;
46
+ await q.deleteWhere({ channel_key: channel });
47
+ const toRef = (userId, userName) => ({
48
+ id: userId,
49
+ displayName: userName.trim() || userId,
50
+ });
51
+ return [
52
+ toRef(rows[0].user_id, rows[0].user_name),
53
+ toRef(rows[1].user_id, rows[1].user_name),
54
+ ];
55
+ }
56
+ async count(channel) {
57
+ const rows = await getModel(this.db, 'ttt_queue').findAll({ channel_key: channel });
58
+ return rows.length;
59
+ }
60
+ }
61
+ export function sessionId() {
62
+ return `s${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
63
+ }
64
+ export class SessionService {
65
+ db;
66
+ constructor(db) {
67
+ this.db = db;
68
+ }
69
+ async getActiveByChannel(channel) {
70
+ const rows = await getModel(this.db, 'ttt_sessions').findAll({
71
+ channel_key: channel,
72
+ status: 'active',
73
+ });
74
+ return rows[0] ?? null;
75
+ }
76
+ async getById(id) {
77
+ return getModel(this.db, 'ttt_sessions').findOne({ id });
78
+ }
79
+ async getActiveForUser(channel, userId) {
80
+ const row = await this.getActiveByChannel(channel);
81
+ if (!row)
82
+ return null;
83
+ if (row.player_x === userId || row.player_o === userId)
84
+ return row;
85
+ return null;
86
+ }
87
+ /** 根据棋盘消息 ID 查找进行中的局(QQ 等 action 带 sourceMessageId) */
88
+ async getActiveByBoardMessageId(messageId) {
89
+ if (!messageId)
90
+ return null;
91
+ const rows = await getModel(this.db, 'ttt_sessions').findAll({ status: 'active' });
92
+ for (const row of rows) {
93
+ const stored = row.board_message_id;
94
+ if (!stored)
95
+ continue;
96
+ if (stored === messageId)
97
+ return row;
98
+ if (stored.endsWith(`:${messageId}`))
99
+ return row;
100
+ const tail = stored.split(':').pop();
101
+ if (tail && messageId.endsWith(tail))
102
+ return row;
103
+ }
104
+ return null;
105
+ }
106
+ async createSession(input) {
107
+ const now = Date.now();
108
+ const id = sessionId();
109
+ const ch = channelKey(input.message);
110
+ const row = {
111
+ id,
112
+ adapter: String(input.message.$adapter),
113
+ endpoint: input.message.$endpoint,
114
+ channel_type: input.message.$channel.type,
115
+ channel_id: input.message.$channel.id,
116
+ channel_key: ch,
117
+ player_x: input.playerX,
118
+ player_o: input.playerO,
119
+ player_x_name: input.playerXName?.trim() || input.playerX,
120
+ player_o_name: input.playerOName?.trim() || input.playerO,
121
+ board: input.boardJson,
122
+ turn: 1,
123
+ status: 'active',
124
+ winner: 0,
125
+ board_message_id: '',
126
+ move_count: 0,
127
+ updated_at: now,
128
+ created_at: now,
129
+ };
130
+ await getModel(this.db, 'ttt_sessions').create(row);
131
+ return row;
132
+ }
133
+ async updateSession(id, patch) {
134
+ await getModel(this.db, 'ttt_sessions').updateWhere({ id }, { ...patch, updated_at: Date.now() });
135
+ }
136
+ async recordMove(sessionId, playerId, cell, moveIndex) {
137
+ await getModel(this.db, 'ttt_moves').create({
138
+ session_id: sessionId,
139
+ player_id: playerId,
140
+ cell,
141
+ move_index: moveIndex,
142
+ created_at: Date.now(),
143
+ });
144
+ }
145
+ async addSpectator(sessionId, userId) {
146
+ const sp = getModel(this.db, 'ttt_spectators');
147
+ const existing = await sp.findOne({ session_id: sessionId, user_id: userId });
148
+ if (existing)
149
+ return;
150
+ await sp.create({ session_id: sessionId, user_id: userId, joined_at: Date.now() });
151
+ }
152
+ async listSpectators(sessionId) {
153
+ const rows = await getModel(this.db, 'ttt_spectators').findAll({ session_id: sessionId });
154
+ return rows.map((r) => r.user_id);
155
+ }
156
+ async abortStale(idleMs) {
157
+ const cutoff = Date.now() - idleMs;
158
+ const sessions = getModel(this.db, 'ttt_sessions');
159
+ const rows = await sessions.findAll({ status: 'active' });
160
+ let n = 0;
161
+ for (const row of rows) {
162
+ if (row.updated_at < cutoff) {
163
+ await sessions.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
164
+ n++;
165
+ }
166
+ }
167
+ return n;
168
+ }
169
+ }
170
+ export function createServices(db) {
171
+ return { queue: new QueueService(db), session: new SessionService(db) };
172
+ }
173
+ export function resolveGameDatabase(feature) {
174
+ return feature.db;
175
+ }
176
+ //# sourceMappingURL=session-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Message, Plugin } from 'zhin.js';
2
+ import type { SessionServices } from './session-service.js';
3
+ export declare const TTT_HELP: string;
4
+ export declare function runTttCommand(plugin: Plugin, services: SessionServices, message: Message<any>, action: string): Promise<string | undefined>;
5
+ //# sourceMappingURL=ttt-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ttt-command.d.ts","sourceRoot":"","sources":["../src/ttt-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAI/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,eAAO,MAAM,QAAQ,QAQT,CAAC;AAEb,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAsD7B"}
@@ -0,0 +1,67 @@
1
+ import { channelKey } from '@zhin.js/game-shared';
2
+ import { formatPlayerWithMark, formatRosterLine } from './player-label.js';
3
+ import { startBotGame, startPvpGame } from './game-flow.js';
4
+ export const TTT_HELP = [
5
+ '井字棋',
6
+ '井字棋 / ttt — 本帮助与频道状态',
7
+ '井字棋 人机 — 人机对战(私聊或单人)',
8
+ '井字棋 排队 — 群聊排队(满 2 人自动开局)',
9
+ '井字棋 离开 — 离开排队',
10
+ '井字棋 认输 — 结束当前局',
11
+ '井字棋 观战 — 订阅观战推送',
12
+ ].join('\n');
13
+ export async function runTttCommand(plugin, services, message, action) {
14
+ const ch = channelKey(message);
15
+ const userId = message.$sender.id;
16
+ if (!action || action === 'help') {
17
+ const active = await services.session.getActiveByChannel(ch);
18
+ const q = await services.queue.count(ch);
19
+ const lines = [TTT_HELP, ''];
20
+ if (active)
21
+ lines.push(`进行中:${formatRosterLine(active)}`);
22
+ if (q > 0)
23
+ lines.push(`排队:${q} 人`);
24
+ if (!active && q === 0)
25
+ lines.push('暂无对局,发送「井字棋 人机」或从游戏大厅进入。');
26
+ return lines.join('\n');
27
+ }
28
+ if (action === 'join') {
29
+ if (message.$channel.type === 'private') {
30
+ return '私聊请使用「井字棋 人机」。';
31
+ }
32
+ const inGame = await services.session.getActiveForUser(ch, userId);
33
+ if (inGame)
34
+ return '你已在进行中的对局里。';
35
+ const { position } = await services.queue.join(ch, userId, message.$sender.name);
36
+ const pair = await services.queue.tryMatch(ch);
37
+ if (pair) {
38
+ const [px, po] = pair;
39
+ await startPvpGame(plugin, services, message, px, po);
40
+ return `匹配成功!${formatPlayerWithMark(px.id, px.displayName, '✕')} vs ${formatPlayerWithMark(po.id, po.displayName, '○')}`;
41
+ }
42
+ return `已加入排队(第 ${position} 位),凑满 2 人自动开局。`;
43
+ }
44
+ if (action === 'leave') {
45
+ const ok = await services.queue.leave(ch, userId);
46
+ return ok ? '已离开排队。' : '你不在排队中。';
47
+ }
48
+ if (action === 'bot') {
49
+ return startBotGame(plugin, services, message);
50
+ }
51
+ if (action === 'quit') {
52
+ const row = await services.session.getActiveForUser(ch, userId);
53
+ if (!row)
54
+ return '你没有进行中的对局。';
55
+ await services.session.updateSession(row.id, { status: 'aborted', winner: 0 });
56
+ return '你已认输,对局结束。';
57
+ }
58
+ if (action === 'spectate') {
59
+ const active = await services.session.getActiveByChannel(ch);
60
+ if (!active)
61
+ return '当前频道没有进行中的对局。';
62
+ await services.session.addSpectator(active.id, userId);
63
+ return `已订阅观战(局 ${active.id})。每步会在频道更新棋盘。`;
64
+ }
65
+ return `未知子命令:${action}\n\n${TTT_HELP}`;
66
+ }
67
+ //# sourceMappingURL=ttt-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ttt-command.js","sourceRoot":"","sources":["../src/ttt-command.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@zhin.js/plugin-tic-tac-toe",
3
+ "version": "1.0.0",
4
+ "description": "Zhin.js 跨平台井字棋 — 按钮/文本双模式,群聊排队与人机对战",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "development": "./src/index.ts",
12
+ "import": "./lib/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "lib",
19
+ "plugin.yml",
20
+ "README.md"
21
+ ],
22
+ "keywords": [
23
+ "zhin",
24
+ "zhin.js",
25
+ "bot",
26
+ "plugin",
27
+ "game",
28
+ "tic-tac-toe"
29
+ ],
30
+ "license": "MIT",
31
+ "peerDependencies": {
32
+ "zhin.js": "4.1.0"
33
+ },
34
+ "dependencies": {
35
+ "@zhin.js/game-shared": "1.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^6.0.3",
39
+ "zhin.js": "4.1.0"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/zhinjs/zhin.git",
44
+ "directory": "plugins/games/tic-tac-toe"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org"
49
+ },
50
+ "scripts": {
51
+ "build": "tsc --build",
52
+ "clean": "rimraf lib"
53
+ }
54
+ }
package/plugin.yml ADDED
@@ -0,0 +1,2 @@
1
+ name: "@zhin.js/plugin-tic-tac-toe"
2
+ description: "跨平台井字棋游戏"