@zhin.js/plugin-tic-tac-toe 1.0.3 → 1.0.5
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/commands/ttt/[action:string=].js +16 -0
- package/commands/ttt/[action:string=].ts +2 -2
- package/lib/game-flow.d.ts +7 -6
- package/lib/game-flow.js +33 -73
- package/lib/game-flow.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/models.d.ts +0 -1
- package/lib/models.js +0 -1
- package/lib/models.js.map +1 -1
- package/lib/session-service.d.ts +8 -10
- package/lib/session-service.js +55 -51
- package/lib/session-service.js.map +1 -1
- package/lib/ttt-command.d.ts +2 -4
- package/lib/ttt-command.js +9 -9
- package/lib/ttt-command.js.map +1 -1
- package/middlewares/ttt-alias.js +18 -0
- package/middlewares/ttt-alias.ts +6 -3
- package/middlewares/ttt-choice.js +66 -0
- package/middlewares/ttt-choice.ts +3 -3
- package/package.json +8 -8
- package/plugin.js +84 -0
- package/src/board-view.ts +1 -1
- package/src/game-flow.ts +45 -90
- package/src/index.ts +0 -1
- package/src/models.ts +0 -2
- package/src/session-service.ts +64 -57
- package/src/ttt-command.ts +14 -17
- package/lib/memory-db.d.ts +0 -6
- package/lib/memory-db.js +0 -18
- package/lib/memory-db.js.map +0 -1
- package/plugin.ts +0 -64
- package/src/memory-db.ts +0 -22
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { messageFromCommandInput, normalizeTttAction } from '@zhin.js/game-kit';
|
|
4
|
+
import { TTT_HELP, runTttCommand } from "../../lib/ttt-command.js";
|
|
5
|
+
import { resolveGameServices } from "../../lib/runtime-store.js";
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: 'Tic Tac Toe',
|
|
8
|
+
async execute({ params, input, use, owner }) {
|
|
9
|
+
const action = normalizeTttAction(String(params.action ?? ''));
|
|
10
|
+
if (!action || action === 'help')
|
|
11
|
+
return TTT_HELP;
|
|
12
|
+
const services = resolveGameServices({ use, owner });
|
|
13
|
+
const message = messageFromCommandInput(input);
|
|
14
|
+
return runTttCommand(services, message, action);
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from '@zhin.js/command';
|
|
2
2
|
import { messageFromCommandInput, normalizeTttAction } from '@zhin.js/game-kit';
|
|
3
|
-
import { TTT_HELP,
|
|
3
|
+
import { TTT_HELP, runTttCommand } from '../../src/ttt-command.js';
|
|
4
4
|
import { resolveGameServices } from '../../src/runtime-store.js';
|
|
5
5
|
|
|
6
6
|
export default defineCommand({
|
|
@@ -10,6 +10,6 @@ export default defineCommand({
|
|
|
10
10
|
if (!action || action === 'help') return TTT_HELP;
|
|
11
11
|
const services = resolveGameServices({ use, owner });
|
|
12
12
|
const message = messageFromCommandInput(input);
|
|
13
|
-
return
|
|
13
|
+
return runTttCommand(services, message, action);
|
|
14
14
|
},
|
|
15
15
|
});
|
package/lib/game-flow.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
2
2
|
import type { TttSessionRow } from './models.js';
|
|
3
3
|
import { BOT_ID } from './player-label.js';
|
|
4
4
|
import type { SessionServices, TttPlayerRef } from './session-service.js';
|
|
5
5
|
export { BOT_ID };
|
|
6
6
|
export declare function isBotSession(session: TttSessionRow): boolean;
|
|
7
|
-
|
|
8
|
-
export declare function
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
7
|
+
/** Render the canonical board reply; delivery belongs to the Runtime Host. */
|
|
8
|
+
export declare function renderBoard(message: GameMessageLike, session: TttSessionRow, statusLine: string, terminal?: boolean, highlight?: number[]): GameReply;
|
|
9
|
+
export declare function restartFromTerminal(services: SessionServices, message: GameMessageLike, sessionId: string): Promise<GameReply | null>;
|
|
10
|
+
export declare function handleMove(services: SessionServices, message: GameMessageLike, sessionId: string, cell: number): Promise<GameReply | null>;
|
|
11
|
+
export declare function startBotGame(services: SessionServices, message: GameMessageLike): Promise<GameReply>;
|
|
12
|
+
export declare function startPvpGame(services: SessionServices, message: GameMessageLike, playerX: TttPlayerRef, playerO: TttPlayerRef): Promise<GameReply>;
|
package/lib/game-flow.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
|
|
2
1
|
import { buildBoardInteractive } from './board-view.js';
|
|
3
2
|
import { applyMove, bestMove, checkWinner, isDraw, parseBoard, playerMark, validMove, X, O, cellLabel, } from './engine.js';
|
|
4
3
|
import { BOT_ID, formatPlayerWithMark, formatRosterLine, formatTurnStatus, formatWinHeadline, playerRefForMark, senderDisplayName, } from './player-label.js';
|
|
@@ -6,7 +5,8 @@ export { BOT_ID };
|
|
|
6
5
|
export function isBotSession(session) {
|
|
7
6
|
return session.player_o === BOT_ID || session.player_x === BOT_ID;
|
|
8
7
|
}
|
|
9
|
-
|
|
8
|
+
/** Render the canonical board reply; delivery belongs to the Runtime Host. */
|
|
9
|
+
export function renderBoard(message, session, statusLine, terminal = false, highlight) {
|
|
10
10
|
const board = parseBoard(session.board);
|
|
11
11
|
const content = buildBoardInteractive({
|
|
12
12
|
sessionId: session.id,
|
|
@@ -18,33 +18,14 @@ export async function sendOrEditBoard(plugin, services, message, session, status
|
|
|
18
18
|
highlight,
|
|
19
19
|
channelType: message.$channel.type,
|
|
20
20
|
});
|
|
21
|
-
|
|
22
|
-
return plainTextFromSendContent(content);
|
|
23
|
-
const adapter = plugin.root.inject(message.$adapter);
|
|
24
|
-
if (session.board_message_id) {
|
|
25
|
-
const msgId = await adapter.editMessage({
|
|
26
|
-
messageId: session.board_message_id,
|
|
27
|
-
context: String(message.$adapter),
|
|
28
|
-
endpoint: message.$endpoint,
|
|
29
|
-
id: message.$channel.id,
|
|
30
|
-
type: message.$channel.type,
|
|
31
|
-
content,
|
|
32
|
-
});
|
|
33
|
-
if (msgId !== session.board_message_id) {
|
|
34
|
-
await services.session.updateSession(session.id, { board_message_id: msgId });
|
|
35
|
-
}
|
|
36
|
-
return msgId;
|
|
37
|
-
}
|
|
38
|
-
const msgId = await message.$reply?.(content);
|
|
39
|
-
if (msgId) {
|
|
40
|
-
await services.session.updateSession(session.id, { board_message_id: msgId });
|
|
41
|
-
}
|
|
42
|
-
return msgId ?? '';
|
|
21
|
+
return content;
|
|
43
22
|
}
|
|
44
|
-
export async function restartFromTerminal(
|
|
23
|
+
export async function restartFromTerminal(services, message, sessionId) {
|
|
45
24
|
const session = await services.session.getById(sessionId);
|
|
46
25
|
if (!session)
|
|
47
26
|
return '对局不存在。';
|
|
27
|
+
if (session.status === 'active')
|
|
28
|
+
return '对局尚未结束,无法重开。';
|
|
48
29
|
const mark = playerMark(message.$sender.id, {
|
|
49
30
|
playerX: session.player_x,
|
|
50
31
|
playerO: session.player_o,
|
|
@@ -52,11 +33,17 @@ export async function restartFromTerminal(plugin, services, message, sessionId)
|
|
|
52
33
|
if (!mark)
|
|
53
34
|
return '你不是本局玩家。';
|
|
54
35
|
await services.session.updateSession(session.id, { status: 'aborted', winner: 0 });
|
|
55
|
-
|
|
56
|
-
if (
|
|
36
|
+
// PvP 局重开仍是 PvP(原班人马),不静默降级为人机
|
|
37
|
+
if (!isBotSession(session)) {
|
|
38
|
+
const board = await startPvpGame(services, message, { id: session.player_x, displayName: session.player_x_name }, { id: session.player_o, displayName: session.player_o_name });
|
|
39
|
+
if (typeof board === 'string' && board.includes('已有'))
|
|
40
|
+
return board;
|
|
41
|
+
return board;
|
|
42
|
+
}
|
|
43
|
+
const hint = await startBotGame(services, message);
|
|
44
|
+
if (typeof hint === 'string' && hint.includes('已有'))
|
|
57
45
|
return hint;
|
|
58
|
-
|
|
59
|
-
return plugin ? null : hint;
|
|
46
|
+
return hint;
|
|
60
47
|
}
|
|
61
48
|
function turnCell(session) {
|
|
62
49
|
return session.turn === 1 ? X : O;
|
|
@@ -64,7 +51,7 @@ function turnCell(session) {
|
|
|
64
51
|
function playerIdForTurn(session) {
|
|
65
52
|
return session.turn === 1 ? session.player_x : session.player_o;
|
|
66
53
|
}
|
|
67
|
-
export async function handleMove(
|
|
54
|
+
export async function handleMove(services, message, sessionId, cell) {
|
|
68
55
|
const session = await services.session.getById(sessionId);
|
|
69
56
|
if (!session || session.status !== 'active') {
|
|
70
57
|
return '对局不存在或已结束。';
|
|
@@ -101,48 +88,21 @@ export async function handleMove(plugin, services, message, sessionId, cell) {
|
|
|
101
88
|
winner: win ? win.winner : 0,
|
|
102
89
|
});
|
|
103
90
|
const updated = (await services.session.getById(session.id));
|
|
104
|
-
if (win || draw) {
|
|
105
|
-
const humanId = isBotSession(updated)
|
|
106
|
-
? (updated.player_x === BOT_ID ? updated.player_o : updated.player_x)
|
|
107
|
-
: message.$sender.id;
|
|
108
|
-
const humanName = isBotSession(updated)
|
|
109
|
-
? (updated.player_x === BOT_ID ? updated.player_o_name : updated.player_x_name)
|
|
110
|
-
: String(message.$sender.name ?? message.$sender.id);
|
|
111
|
-
const humanMsg = {
|
|
112
|
-
...message,
|
|
113
|
-
$sender: { ...message.$sender, id: humanId, name: humanName },
|
|
114
|
-
};
|
|
115
|
-
if (win) {
|
|
116
|
-
const humanMark = playerMark(humanId, {
|
|
117
|
-
playerX: updated.player_x,
|
|
118
|
-
playerO: updated.player_o,
|
|
119
|
-
});
|
|
120
|
-
const humanWon = humanMark === win.winner;
|
|
121
|
-
void recordGameOutcome(humanMsg, 'ttt', humanWon ? 'won' : 'lost', humanWon ? 20 : 0);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
void recordGameOutcome(humanMsg, 'ttt', 'draw');
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
91
|
if (win) {
|
|
128
92
|
const status = formatWinHeadline(updated, win.winner);
|
|
129
|
-
|
|
130
|
-
// text-only 模式(plugin===null)下 sendOrEditBoard 的唯一输出就是返回文本
|
|
131
|
-
return plugin ? null : text;
|
|
93
|
+
return renderBoard(message, updated, status, true, win.line);
|
|
132
94
|
}
|
|
133
95
|
if (draw) {
|
|
134
|
-
|
|
135
|
-
return plugin ? null : text;
|
|
96
|
+
return renderBoard(message, updated, '平局。', true);
|
|
136
97
|
}
|
|
137
98
|
// 人机:玩家落子后立刻由服务端代下,避免先发「轮到机器人」再发终盘(QQ 被动消息多耗一次)
|
|
138
99
|
if (isBotSession(updated) && playerIdForTurn(updated) === BOT_ID) {
|
|
139
|
-
return runBotMove(
|
|
100
|
+
return runBotMove(services, message, updated);
|
|
140
101
|
}
|
|
141
102
|
const status = formatTurnStatus(updated, moveCount);
|
|
142
|
-
|
|
143
|
-
return plugin ? null : text;
|
|
103
|
+
return renderBoard(message, updated, status, false);
|
|
144
104
|
}
|
|
145
|
-
async function runBotMove(
|
|
105
|
+
async function runBotMove(services, message, session) {
|
|
146
106
|
const board = parseBoard(session.board);
|
|
147
107
|
const aiMark = session.player_o === BOT_ID ? O : X;
|
|
148
108
|
const cell = bestMove(board, aiMark);
|
|
@@ -152,9 +112,9 @@ async function runBotMove(plugin, services, message, session) {
|
|
|
152
112
|
...message,
|
|
153
113
|
$sender: { ...message.$sender, id: BOT_ID, name: '机器人' },
|
|
154
114
|
};
|
|
155
|
-
return handleMove(
|
|
115
|
+
return handleMove(services, fakeMessage, session.id, cell);
|
|
156
116
|
}
|
|
157
|
-
export async function startBotGame(
|
|
117
|
+
export async function startBotGame(services, message) {
|
|
158
118
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
159
119
|
const active = await services.session.getActiveByChannel(ch);
|
|
160
120
|
if (active)
|
|
@@ -168,13 +128,15 @@ export async function startBotGame(plugin, services, message) {
|
|
|
168
128
|
boardJson: JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0]),
|
|
169
129
|
});
|
|
170
130
|
const status = `${formatRosterLine(session)} · 你先手 (✕)`;
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
return '开局成功!点击棋盘或回复数字落子。';
|
|
131
|
+
const board = renderBoard(message, session, status, false);
|
|
132
|
+
const success = '\n\n开局成功!点击空格或回复数字 1–9 落子。';
|
|
133
|
+
return Array.isArray(board) ? [...board, success] : [board, success];
|
|
176
134
|
}
|
|
177
|
-
export async function startPvpGame(
|
|
135
|
+
export async function startPvpGame(services, message, playerX, playerO) {
|
|
136
|
+
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
137
|
+
const active = await services.session.getActiveByChannel(ch);
|
|
138
|
+
if (active)
|
|
139
|
+
return '当前频道已有进行中的对局。';
|
|
178
140
|
const session = await services.session.createSession({
|
|
179
141
|
message,
|
|
180
142
|
playerX: playerX.id,
|
|
@@ -185,8 +147,6 @@ export async function startPvpGame(plugin, services, message, playerX, playerO)
|
|
|
185
147
|
});
|
|
186
148
|
const opener = formatPlayerWithMark(session.player_x, session.player_x_name, '✕');
|
|
187
149
|
const status = `${formatRosterLine(session)} · 先手:${opener}`;
|
|
188
|
-
|
|
189
|
-
if (!plugin)
|
|
190
|
-
return boardText;
|
|
150
|
+
return renderBoard(message, session, status, false);
|
|
191
151
|
}
|
|
192
152
|
//# sourceMappingURL=game-flow.js.map
|
package/lib/game-flow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { TTT_HELP } from './ttt-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryTttDb, mountTttMemoryServices } from './memory-db.js';
|
|
4
3
|
/**
|
|
5
4
|
* 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
|
|
6
5
|
* 游戏大厅注册(registerRuntimeGame)、过期会话 cron(scheduleHostToken)、
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { TTT_HELP } from './ttt-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryTttDb, mountTttMemoryServices } from './memory-db.js';
|
|
4
3
|
/**
|
|
5
4
|
* 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
|
|
6
5
|
* 游戏大厅注册(registerRuntimeGame)、过期会话 cron(scheduleHostToken)、
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE5E;;;;GAIG"}
|
package/lib/models.d.ts
CHANGED
package/lib/models.js
CHANGED
|
@@ -14,7 +14,6 @@ export function defineHostTables(db) {
|
|
|
14
14
|
turn: { type: 'integer', default: 1 },
|
|
15
15
|
status: { type: 'text', default: 'active' },
|
|
16
16
|
winner: { type: 'integer', default: 0 },
|
|
17
|
-
board_message_id: { type: 'text', default: '' },
|
|
18
17
|
move_count: { type: 'integer', default: 0 },
|
|
19
18
|
updated_at: { type: 'integer', default: 0 },
|
|
20
19
|
created_at: { type: 'integer', default: 0 },
|
package/lib/models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AA0DA,MAAM,UAAU,gBAAgB,CAC9B,EAA2E;IAE3E,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE;QACxB,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QACnC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC1C,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC3C,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC/C,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC7C,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC9C,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC3C,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC3C,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC5C,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC5C,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QACtC,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QACrC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC3C,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QACvC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC3C,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC3C,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;KAC5C,CAAC,CAAC;IACH,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE;QACrB,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE;QACtC,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC9C,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC1C,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QACxC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;KAC3C,CAAC,CAAC;IACH,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE;QACrB,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE;QACtC,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC7C,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC5C,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC1C,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE;QAChD,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;KAC5C,CAAC,CAAC;IACH,EAAE,CAAC,MAAM,CAAC,gBAAgB,EAAE;QAC1B,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE;QACtC,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC7C,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC1C,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;KAC3C,CAAC,CAAC;AACL,CAAC"}
|
package/lib/session-service.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Database,
|
|
1
|
+
import type { Database, Models } from '@zhin.js/core';
|
|
2
|
+
import { BaseSessionService, type GameMessageLike } from '@zhin.js/game-kit';
|
|
2
3
|
import type { TttSessionRow } from './models.js';
|
|
3
4
|
/** 井字棋服务使用的数据库实例(Models 经 models.ts 模块增强) */
|
|
4
5
|
export type TttDatabase = Database<unknown, Models, string>;
|
|
@@ -6,9 +7,13 @@ export type TttPlayerRef = {
|
|
|
6
7
|
id: string;
|
|
7
8
|
displayName: string;
|
|
8
9
|
};
|
|
10
|
+
/** 排队行过期时间(超时未匹配的排队视为失效) */
|
|
11
|
+
export declare const QUEUE_TTL_MS: number;
|
|
9
12
|
export declare class QueueService {
|
|
10
13
|
private readonly db;
|
|
11
14
|
constructor(db: TttDatabase);
|
|
15
|
+
/** 清掉频道内过期排队行(ttt_queue 无 TTL,靠 join 时顺带清理) */
|
|
16
|
+
private pruneExpired;
|
|
12
17
|
join(channel: string, userId: string, displayName?: string): Promise<{
|
|
13
18
|
queued: boolean;
|
|
14
19
|
position: number;
|
|
@@ -19,27 +24,20 @@ export declare class QueueService {
|
|
|
19
24
|
count(channel: string): Promise<number>;
|
|
20
25
|
}
|
|
21
26
|
export declare function sessionId(): string;
|
|
22
|
-
export declare class SessionService {
|
|
27
|
+
export declare class SessionService extends BaseSessionService<TttSessionRow> {
|
|
23
28
|
private readonly db;
|
|
24
29
|
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
30
|
createSession(input: {
|
|
31
|
-
message:
|
|
31
|
+
message: GameMessageLike;
|
|
32
32
|
playerX: string;
|
|
33
33
|
playerO: string;
|
|
34
34
|
playerXName?: string;
|
|
35
35
|
playerOName?: string;
|
|
36
36
|
boardJson: string;
|
|
37
37
|
}): Promise<TttSessionRow>;
|
|
38
|
-
updateSession(id: string, patch: Partial<TttSessionRow>): Promise<void>;
|
|
39
38
|
recordMove(sessionId: string, playerId: string, cell: number, moveIndex: number): Promise<void>;
|
|
40
39
|
addSpectator(sessionId: string, userId: string): Promise<void>;
|
|
41
40
|
listSpectators(sessionId: string): Promise<string[]>;
|
|
42
|
-
abortStale(idleMs: number): Promise<number>;
|
|
43
41
|
}
|
|
44
42
|
export type SessionServices = {
|
|
45
43
|
queue: QueueService;
|
package/lib/session-service.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseSessionService, channelKey, generateCompactId, } from '@zhin.js/game-kit';
|
|
2
|
+
import { BOT_ID } from './player-label.js';
|
|
3
|
+
/** 排队行过期时间(超时未匹配的排队视为失效) */
|
|
4
|
+
export const QUEUE_TTL_MS = 10 * 60 * 1000;
|
|
2
5
|
function getModel(db, name) {
|
|
3
6
|
const model = db.models.get(name);
|
|
4
7
|
if (!model) {
|
|
@@ -11,8 +14,20 @@ export class QueueService {
|
|
|
11
14
|
constructor(db) {
|
|
12
15
|
this.db = db;
|
|
13
16
|
}
|
|
17
|
+
/** 清掉频道内过期排队行(ttt_queue 无 TTL,靠 join 时顺带清理) */
|
|
18
|
+
async pruneExpired(channel) {
|
|
19
|
+
const q = getModel(this.db, 'ttt_queue');
|
|
20
|
+
const cutoff = Date.now() - QUEUE_TTL_MS;
|
|
21
|
+
const rows = await q.findAll({ channel_key: channel });
|
|
22
|
+
for (const row of rows) {
|
|
23
|
+
if (row.joined_at < cutoff) {
|
|
24
|
+
await q.deleteWhere({ channel_key: channel, user_id: row.user_id });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
14
28
|
async join(channel, userId, displayName) {
|
|
15
29
|
const q = getModel(this.db, 'ttt_queue');
|
|
30
|
+
await this.pruneExpired(channel);
|
|
16
31
|
const existing = await q.findAll({ channel_key: channel, user_id: userId });
|
|
17
32
|
const all = await q.findAll({ channel_key: channel });
|
|
18
33
|
all.sort((a, b) => a.joined_at - b.joined_at);
|
|
@@ -43,7 +58,9 @@ export class QueueService {
|
|
|
43
58
|
rows.sort((a, b) => a.joined_at - b.joined_at);
|
|
44
59
|
if (rows.length < 2)
|
|
45
60
|
return null;
|
|
46
|
-
|
|
61
|
+
// 只删除匹配到的两人,保留队列中其余的等待者
|
|
62
|
+
await q.deleteWhere({ channel_key: channel, user_id: rows[0].user_id });
|
|
63
|
+
await q.deleteWhere({ channel_key: channel, user_id: rows[1].user_id });
|
|
47
64
|
const toRef = (userId, userName) => ({
|
|
48
65
|
id: userId,
|
|
49
66
|
displayName: userName.trim() || userId,
|
|
@@ -61,39 +78,44 @@ export class QueueService {
|
|
|
61
78
|
export function sessionId() {
|
|
62
79
|
return generateCompactId('s');
|
|
63
80
|
}
|
|
64
|
-
export class SessionService {
|
|
81
|
+
export class SessionService extends BaseSessionService {
|
|
65
82
|
db;
|
|
66
83
|
constructor(db) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
super(db, {
|
|
85
|
+
gameId: 'ttt',
|
|
86
|
+
table: 'ttt_sessions',
|
|
87
|
+
userFields: ['player_x', 'player_o'],
|
|
88
|
+
projectOutcomes: (session) => {
|
|
89
|
+
if (session.status !== 'won' && session.status !== 'draw')
|
|
90
|
+
return [];
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
id: session.player_x,
|
|
94
|
+
name: session.player_x_name,
|
|
95
|
+
mark: 1,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: session.player_o,
|
|
99
|
+
name: session.player_o_name,
|
|
100
|
+
mark: 2,
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
.filter((player) => player.id !== BOT_ID)
|
|
104
|
+
.map((player) => ({
|
|
105
|
+
userId: player.id,
|
|
106
|
+
userName: player.name,
|
|
107
|
+
result: session.status === 'draw'
|
|
108
|
+
? 'draw'
|
|
109
|
+
: session.winner === player.mark
|
|
110
|
+
? 'won'
|
|
111
|
+
: 'lost',
|
|
112
|
+
score: session.status === 'won' && session.winner === player.mark
|
|
113
|
+
? 20
|
|
114
|
+
: undefined,
|
|
115
|
+
}));
|
|
116
|
+
},
|
|
73
117
|
});
|
|
74
|
-
|
|
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({});
|
|
92
|
-
for (const row of rows) {
|
|
93
|
-
if (boardMessageMatches(row.board_message_id ?? '', messageId))
|
|
94
|
-
return row;
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
118
|
+
this.db = db;
|
|
97
119
|
}
|
|
98
120
|
async createSession(input) {
|
|
99
121
|
const now = Date.now();
|
|
@@ -114,16 +136,11 @@ export class SessionService {
|
|
|
114
136
|
turn: 1,
|
|
115
137
|
status: 'active',
|
|
116
138
|
winner: 0,
|
|
117
|
-
board_message_id: '',
|
|
118
139
|
move_count: 0,
|
|
119
140
|
updated_at: now,
|
|
120
141
|
created_at: now,
|
|
121
142
|
};
|
|
122
|
-
|
|
123
|
-
return row;
|
|
124
|
-
}
|
|
125
|
-
async updateSession(id, patch) {
|
|
126
|
-
await getModel(this.db, 'ttt_sessions').updateWhere({ id }, { ...patch, updated_at: Date.now() });
|
|
143
|
+
return this.createRow(row);
|
|
127
144
|
}
|
|
128
145
|
async recordMove(sessionId, playerId, cell, moveIndex) {
|
|
129
146
|
await getModel(this.db, 'ttt_moves').create({
|
|
@@ -145,19 +162,6 @@ export class SessionService {
|
|
|
145
162
|
const rows = await getModel(this.db, 'ttt_spectators').findAll({ session_id: sessionId });
|
|
146
163
|
return rows.map((r) => r.user_id);
|
|
147
164
|
}
|
|
148
|
-
async abortStale(idleMs) {
|
|
149
|
-
const cutoff = Date.now() - idleMs;
|
|
150
|
-
const sessions = getModel(this.db, 'ttt_sessions');
|
|
151
|
-
const rows = await sessions.findAll({ status: 'active' });
|
|
152
|
-
let n = 0;
|
|
153
|
-
for (const row of rows) {
|
|
154
|
-
if (row.updated_at < cutoff) {
|
|
155
|
-
await sessions.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
156
|
-
n++;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return n;
|
|
160
|
-
}
|
|
161
165
|
}
|
|
162
166
|
export function createServices(db) {
|
|
163
167
|
return { queue: new QueueService(db), session: new SessionService(db) };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"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"}
|
package/lib/ttt-command.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type GameMessageLike, type GameReply } from '@zhin.js/game-kit';
|
|
2
2
|
import type { SessionServices } from './session-service.js';
|
|
3
3
|
export declare const TTT_HELP: string;
|
|
4
|
-
export declare function runTttCommand(
|
|
5
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
6
|
-
export declare function runTttCommandText(services: SessionServices, message: Message<any>, action: string): Promise<string>;
|
|
4
|
+
export declare function runTttCommand(services: SessionServices, message: GameMessageLike, action: string): Promise<GameReply>;
|