@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.
- package/README.md +36 -0
- package/lib/board-view.d.ts +24 -0
- package/lib/board-view.d.ts.map +1 -0
- package/lib/board-view.js +58 -0
- package/lib/board-view.js.map +1 -0
- package/lib/commands.d.ts +6 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +111 -0
- package/lib/commands.js.map +1 -0
- package/lib/engine.d.ts +25 -0
- package/lib/engine.d.ts.map +1 -0
- package/lib/engine.js +117 -0
- package/lib/engine.js.map +1 -0
- package/lib/game-flow.d.ts +11 -0
- package/lib/game-flow.d.ts.map +1 -0
- package/lib/game-flow.js +143 -0
- package/lib/game-flow.js.map +1 -0
- package/lib/hub-register.d.ts +5 -0
- package/lib/hub-register.d.ts.map +1 -0
- package/lib/hub-register.js +30 -0
- package/lib/hub-register.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +37 -0
- package/lib/index.js.map +1 -0
- package/lib/models.d.ts +55 -0
- package/lib/models.d.ts.map +1 -0
- package/lib/models.js +44 -0
- package/lib/models.js.map +1 -0
- package/lib/player-label.d.ts +16 -0
- package/lib/player-label.d.ts.map +1 -0
- package/lib/player-label.js +36 -0
- package/lib/player-label.js.map +1 -0
- package/lib/session-service.d.ts +50 -0
- package/lib/session-service.d.ts.map +1 -0
- package/lib/session-service.js +176 -0
- package/lib/session-service.js.map +1 -0
- package/lib/ttt-command.d.ts +5 -0
- package/lib/ttt-command.d.ts.map +1 -0
- package/lib/ttt-command.js +67 -0
- package/lib/ttt-command.js.map +1 -0
- package/package.json +54 -0
- package/plugin.yml +2 -0
- package/src/board-view.ts +93 -0
- package/src/commands.ts +129 -0
- package/src/engine.ts +131 -0
- package/src/game-flow.ts +217 -0
- package/src/hub-register.ts +31 -0
- package/src/index.ts +43 -0
- package/src/models.ts +104 -0
- package/src/player-label.ts +48 -0
- package/src/session-service.ts +205 -0
- 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
|
package/lib/index.js.map
ADDED
|
@@ -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"}
|
package/lib/models.d.ts
ADDED
|
@@ -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":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAUlD,SAAS,QAAQ,CAAyB,EAAe,EAAE,IAAO;IAChE,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,oBAAoB,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,KAAoB,CAAC;AAC9B,CAAC;AAED,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,EAAe;QAAf,OAAE,GAAF,EAAE,CAAa;IAAG,CAAC;IAEhD,KAAK,CAAC,IAAI,CACR,OAAe,EACf,MAAc,EACd,WAAoB;QAEpB,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QACtD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACpF,CAAC;QACD,MAAM,IAAI,GAAG,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACvC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAClG,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QACxD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;QAChD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,OAAe,EAAE,MAAc;QACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACxE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACpC,MAAM,CAAC,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAe;QACxB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QACpF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAe;QAC5B,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACjC,MAAM,CAAC,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,QAAgB,EAAE,EAAE,CAAC,CAAC;YACnD,EAAE,EAAE,MAAM;YACV,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,MAAM;SACvC,CAAC,CAAC;QACH,OAAO;YACL,KAAK,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,SAAS,CAAC;YAC3C,KAAK,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,SAAS,CAAC;SAC5C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,OAAe;QACzB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QACpF,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AAChF,CAAC;AAED,MAAM,OAAO,cAAc;IACI;IAA7B,YAA6B,EAAe;QAAf,OAAE,GAAF,EAAE,CAAa;IAAG,CAAC;IAEhD,KAAK,CAAC,kBAAkB,CAAC,OAAe;QACtC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,OAAO,CAAC;YAC3D,WAAW,EAAE,OAAO;YACpB,MAAM,EAAE,QAAQ;SACjB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,OAAe,EAAE,MAAc;QACpD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM;YAAE,OAAO,GAAG,CAAC;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,yBAAyB,CAAC,SAAiB;QAC/C,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC;YACpC,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,GAAG,CAAC;YACrC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,EAAE,CAAC;gBAAE,OAAO,GAAG,CAAC;YACjD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACrC,IAAI,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO,GAAG,CAAC;QACnD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAOnB;QACC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,GAAG,GAAkB;YACzB,EAAE;YACF,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;YACvC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS;YACjC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI;YACzC,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;YACrC,WAAW,EAAE,EAAE;YACf,QAAQ,EAAE,KAAK,CAAC,OAAO;YACvB,QAAQ,EAAE,KAAK,CAAC,OAAO;YACvB,aAAa,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,KAAK,CAAC,OAAO;YACzD,aAAa,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,KAAK,CAAC,OAAO;YACzD,KAAK,EAAE,KAAK,CAAC,SAAS;YACtB,IAAI,EAAE,CAAC;YACP,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,CAAC;YACT,gBAAgB,EAAE,EAAE;YACpB,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,GAAG;SAChB,CAAC;QACF,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,EAAU,EAAE,KAA6B;QAC3D,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,WAAW,CACjD,EAAE,EAAE,EAAE,EACN,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CACrC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,QAAgB,EAAE,IAAY,EAAE,SAAiB;QACnF,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,MAAM,CAAC;YAC1C,UAAU,EAAE,SAAS;YACrB,SAAS,EAAE,QAAQ;YACnB,IAAI;YACJ,UAAU,EAAE,SAAS;YACrB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;SACvB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAiB,EAAE,MAAc;QAClD,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9E,IAAI,QAAQ;YAAE,OAAO;QACrB,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,SAAiB;QACpC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1F,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;QACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,CAAC,UAAU,GAAG,MAAM,EAAE,CAAC;gBAC5B,MAAM,QAAQ,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC1F,CAAC,EAAE,CAAC;YACN,CAAC;QACH,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;CACF;AAID,MAAM,UAAU,cAAc,CAAC,EAAe;IAC5C,OAAO,EAAE,KAAK,EAAE,IAAI,YAAY,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,cAAc,CAAC,EAAE,CAAC,EAAE,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAwB;IAC1D,OAAO,OAAO,CAAC,EAAE,CAAC;AACpB,CAAC"}
|
|
@@ -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