@zhin.js/plugin-rps 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/rps/[action:string=].js +16 -0
- package/commands/rps/[action:string=].ts +2 -2
- package/lib/game-flow.d.ts +5 -11
- package/lib/game-flow.js +18 -40
- 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/rps-command.d.ts +2 -4
- package/lib/rps-command.js +6 -11
- package/lib/rps-command.js.map +1 -1
- package/lib/session-service.d.ts +4 -10
- package/lib/session-service.js +22 -52
- package/lib/session-service.js.map +1 -1
- package/lib/view.js +11 -6
- package/lib/view.js.map +1 -1
- package/middlewares/rps-alias.js +18 -0
- package/middlewares/rps-alias.ts +6 -3
- package/middlewares/rps-choice.js +58 -0
- package/middlewares/rps-choice.ts +3 -2
- package/package.json +8 -8
- package/plugin.js +36 -0
- package/src/game-flow.ts +24 -49
- package/src/index.ts +0 -1
- package/src/models.ts +0 -2
- package/src/rps-command.ts +11 -19
- package/src/session-service.ts +31 -56
- package/src/view.ts +10 -6
- 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 -63
- 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, normalizeRpsAction } from '@zhin.js/game-kit';
|
|
4
|
+
import { RPS_HELP, runRpsCommand } from "../../lib/rps-command.js";
|
|
5
|
+
import { resolveGameServices } from "../../lib/runtime-store.js";
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: 'RPS',
|
|
8
|
+
async execute({ params, input, use, owner }) {
|
|
9
|
+
const action = normalizeRpsAction(String(params.action ?? ''));
|
|
10
|
+
if (!action || action === 'help')
|
|
11
|
+
return RPS_HELP;
|
|
12
|
+
const services = resolveGameServices({ use, owner });
|
|
13
|
+
const message = messageFromCommandInput(input);
|
|
14
|
+
return runRpsCommand(services, message, action);
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from '@zhin.js/command';
|
|
2
2
|
import { messageFromCommandInput, normalizeRpsAction } from '@zhin.js/game-kit';
|
|
3
|
-
import { RPS_HELP,
|
|
3
|
+
import { RPS_HELP, runRpsCommand } from '../../src/rps-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 RPS_HELP;
|
|
11
11
|
const services = resolveGameServices({ use, owner });
|
|
12
12
|
const message = messageFromCommandInput(input);
|
|
13
|
-
return
|
|
13
|
+
return runRpsCommand(services, message, action);
|
|
14
14
|
},
|
|
15
15
|
});
|
package/lib/game-flow.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
import { RPS_PREFIX, type RpsMove } from './engine.js';
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
2
|
+
import { RPS_PREFIX } from './engine.js';
|
|
4
3
|
import type { SessionService } from './session-service.js';
|
|
5
|
-
export declare function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
result: 0 | 1 | 2;
|
|
9
|
-
}): Promise<string | void>;
|
|
10
|
-
export declare function startGame(plugin: Plugin | null, services: SessionService, message: Message<any>): Promise<string | undefined>;
|
|
11
|
-
export declare function handleChoice(plugin: Plugin | null, services: SessionService, message: Message<any>, sessionId: string, choiceId: string): Promise<string | null>;
|
|
12
|
-
export declare function continueGame(plugin: Plugin | null, services: SessionService, message: Message<any>): Promise<string>;
|
|
4
|
+
export declare function startGame(services: SessionService, message: GameMessageLike): Promise<GameReply | undefined>;
|
|
5
|
+
export declare function handleChoice(services: SessionService, message: GameMessageLike, sessionId: string, choiceId: string): Promise<GameReply | null>;
|
|
6
|
+
export declare function continueGame(services: SessionService, message: GameMessageLike): Promise<GameReply>;
|
|
13
7
|
export { RPS_PREFIX };
|
package/lib/game-flow.js
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
|
-
import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
|
|
2
1
|
import { RPS_PREFIX, WIN_TARGET, randomBotMove, resolveRound, } from './engine.js';
|
|
3
2
|
import { buildRpsView } from './view.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
messageId: session.board_message_id,
|
|
12
|
-
context: String(message.$adapter),
|
|
13
|
-
endpoint: message.$endpoint,
|
|
14
|
-
id: message.$channel.id,
|
|
15
|
-
type: message.$channel.type,
|
|
16
|
-
content,
|
|
17
|
-
});
|
|
18
|
-
if (msgId !== session.board_message_id) {
|
|
19
|
-
await services.updateSession(session.id, { board_message_id: msgId });
|
|
20
|
-
}
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const msgId = await message.$reply?.(content);
|
|
24
|
-
if (msgId)
|
|
25
|
-
await services.updateSession(session.id, { board_message_id: msgId });
|
|
3
|
+
/**
|
|
4
|
+
* Plugin Runtime: render the board as text. Interactive in-place board editing
|
|
5
|
+
* (the old Adapter.editMessage path) is not part of the runtime flow; commands
|
|
6
|
+
* and the choice middleware return fresh text each turn.
|
|
7
|
+
*/
|
|
8
|
+
function renderView(session, message, lastRound) {
|
|
9
|
+
return buildRpsView(session, lastRound, message.$channel.type);
|
|
26
10
|
}
|
|
27
|
-
export async function startGame(
|
|
11
|
+
export async function startGame(services, message) {
|
|
28
12
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
29
13
|
const active = await services.getActiveByChannel(ch);
|
|
30
14
|
if (active) {
|
|
@@ -34,10 +18,9 @@ export async function startGame(plugin, services, message) {
|
|
|
34
18
|
return `本频道 ${active.player_name} 正在猜拳对决中。`;
|
|
35
19
|
}
|
|
36
20
|
const session = await services.createSession(message);
|
|
37
|
-
|
|
38
|
-
return typeof text === 'string' ? text : undefined;
|
|
21
|
+
return renderView(session, message);
|
|
39
22
|
}
|
|
40
|
-
export async function handleChoice(
|
|
23
|
+
export async function handleChoice(services, message, sessionId, choiceId) {
|
|
41
24
|
const session = await services.getById(sessionId);
|
|
42
25
|
if (!session)
|
|
43
26
|
return '对局不存在。';
|
|
@@ -48,8 +31,11 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
|
|
|
48
31
|
return '这是别人的对局。';
|
|
49
32
|
if (choiceId === 'restart') {
|
|
50
33
|
await services.updateSession(session.id, { status: 'aborted' });
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
return (await startGame(services, message)) ?? null;
|
|
35
|
+
}
|
|
36
|
+
if (choiceId === 'quit') {
|
|
37
|
+
await services.updateSession(session.id, { status: 'aborted' });
|
|
38
|
+
return renderView((await services.getById(session.id)), message);
|
|
53
39
|
}
|
|
54
40
|
const player = choiceId;
|
|
55
41
|
if (!['rock', 'paper', 'scissors'].includes(player))
|
|
@@ -74,21 +60,13 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
|
|
|
74
60
|
status,
|
|
75
61
|
});
|
|
76
62
|
const updated = (await services.getById(session.id));
|
|
77
|
-
|
|
78
|
-
void recordGameOutcome(message, 'rps', 'won', playerWins * 10);
|
|
79
|
-
else if (status === 'lost')
|
|
80
|
-
void recordGameOutcome(message, 'rps', 'lost');
|
|
81
|
-
const text = await sendOrEditView(plugin, services, message, updated, { player, bot, result });
|
|
82
|
-
return typeof text === 'string' ? text : null;
|
|
63
|
+
return renderView(updated, message, { player, bot, result });
|
|
83
64
|
}
|
|
84
|
-
export async function continueGame(
|
|
65
|
+
export async function continueGame(services, message) {
|
|
85
66
|
const session = await services.getActiveForUser(`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`, message.$sender.id);
|
|
86
67
|
if (!session)
|
|
87
68
|
return '你没有进行中的猜拳,发送「猜拳 开始」。';
|
|
88
|
-
|
|
89
|
-
if (typeof text === 'string')
|
|
90
|
-
return text;
|
|
91
|
-
return '已刷新猜拳界面。';
|
|
69
|
+
return renderView(session, message);
|
|
92
70
|
}
|
|
93
71
|
export { RPS_PREFIX };
|
|
94
72
|
//# 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":"AAEA,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,YAAY,GAEb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;GAIG;AACH,SAAS,UAAU,CACjB,OAAsB,EACtB,OAAwB,EACxB,SAAgE;IAEhE,OAAO,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAwB,EACxB,OAAwB;IAExB,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;IACtG,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;IACrD,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;YAC5C,OAAO,iCAAiC,CAAC;QAC3C,CAAC;QACD,OAAO,OAAO,MAAM,CAAC,WAAW,WAAW,CAAC;IAC9C,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACtD,OAAO,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAwB,EACxB,OAAwB,EACxB,SAAiB,EACjB,QAAgB;IAEhB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClD,IAAI,CAAC,OAAO;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC1D,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,UAAU,CAAC;IAEhE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC;IACtD,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAChE,OAAO,UAAU,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAE,EAAE,OAAO,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,MAAM,GAAG,QAAmB,CAAC;IACnC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,OAAO,CAAC;IAEpE,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IACrC,IAAI,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC/B,IAAI,MAAM,KAAK,CAAC;QAAE,UAAU,EAAE,CAAC;IAC/B,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE5B,IAAI,MAAM,GAA4B,QAAQ,CAAC;IAC/C,IAAI,UAAU,IAAI,UAAU;QAAE,MAAM,GAAG,KAAK,CAAC;SACxC,IAAI,OAAO,IAAI,UAAU;QAAE,MAAM,GAAG,MAAM,CAAC;IAEhD,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE;QACvC,WAAW,EAAE,UAAU;QACvB,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,OAAO,CAAC,KAAK,GAAG,CAAC;QACxB,MAAM;KACP,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAE,CAAC;IACtD,OAAO,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAwB,EACxB,OAAwB;IAExB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,gBAAgB,CAC7C,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,EAC1F,OAAO,CAAC,OAAO,CAAC,EAAE,CACnB,CAAC;IACF,IAAI,CAAC,OAAO;QAAE,OAAO,sBAAsB,CAAC;IAC5C,OAAO,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { RPS_HELP } from './rps-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryRpsDb, mountRpsMemoryServices } 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 { RPS_HELP } from './rps-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryRpsDb, mountRpsMemoryServices } 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
|
@@ -12,7 +12,6 @@ export function defineHostTables(db) {
|
|
|
12
12
|
bot_wins: { type: 'integer', default: 0 },
|
|
13
13
|
round: { type: 'integer', default: 0 },
|
|
14
14
|
status: { type: 'text', default: 'active' },
|
|
15
|
-
board_message_id: { type: 'text', default: '' },
|
|
16
15
|
updated_at: { type: 'integer', default: 0 },
|
|
17
16
|
created_at: { type: 'integer', default: 0 },
|
|
18
17
|
});
|
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":"AA4BA,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,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC5C,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1C,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC5C,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QACzC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QACtC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,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;AACL,CAAC"}
|
package/lib/rps-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 { SessionService } from './session-service.js';
|
|
3
3
|
export declare const RPS_HELP: string;
|
|
4
|
-
export declare function runRpsCommand(
|
|
5
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
6
|
-
export declare function runRpsCommandText(services: SessionService, message: Message<any>, action: string): Promise<string>;
|
|
4
|
+
export declare function runRpsCommand(services: SessionService, message: GameMessageLike, action: string): Promise<GameReply>;
|
package/lib/rps-command.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { channelKey } from '@zhin.js/game-kit';
|
|
2
|
-
import { continueGame, startGame } from './game-flow.js';
|
|
1
|
+
import { channelKey, } from '@zhin.js/game-kit';
|
|
2
|
+
import { continueGame, handleChoice, startGame } from './game-flow.js';
|
|
3
3
|
export const RPS_HELP = [
|
|
4
4
|
'✊✋✌️ 猜拳对决(三局两胜)',
|
|
5
5
|
'猜拳 / rps — 帮助',
|
|
@@ -7,7 +7,7 @@ export const RPS_HELP = [
|
|
|
7
7
|
'猜拳 继续 — 刷新界面',
|
|
8
8
|
'猜拳 放弃 — 结束对局',
|
|
9
9
|
].join('\n');
|
|
10
|
-
export async function runRpsCommand(
|
|
10
|
+
export async function runRpsCommand(services, message, action) {
|
|
11
11
|
const ch = channelKey(message);
|
|
12
12
|
const userId = message.$sender.id;
|
|
13
13
|
if (!action || action === 'help') {
|
|
@@ -22,20 +22,15 @@ export async function runRpsCommand(plugin, services, message, action) {
|
|
|
22
22
|
return lines.join('\n');
|
|
23
23
|
}
|
|
24
24
|
if (action === 'start')
|
|
25
|
-
return startGame(
|
|
25
|
+
return (await startGame(services, message)) ?? '';
|
|
26
26
|
if (action === 'continue')
|
|
27
|
-
return continueGame(
|
|
27
|
+
return continueGame(services, message);
|
|
28
28
|
if (action === 'quit') {
|
|
29
29
|
const row = await services.getActiveForUser(ch, userId);
|
|
30
30
|
if (!row)
|
|
31
31
|
return '你没有进行中的猜拳。';
|
|
32
|
-
await services
|
|
33
|
-
return '已放弃猜拳对局。';
|
|
32
|
+
return (await handleChoice(services, message, row.id, 'quit')) ?? '';
|
|
34
33
|
}
|
|
35
34
|
return `未知子命令:${action}\n\n${RPS_HELP}`;
|
|
36
35
|
}
|
|
37
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
38
|
-
export async function runRpsCommandText(services, message, action) {
|
|
39
|
-
return (await runRpsCommand(null, services, message, action)) ?? '';
|
|
40
|
-
}
|
|
41
36
|
//# sourceMappingURL=rps-command.js.map
|
package/lib/rps-command.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rps-command.js","sourceRoot":"","sources":["../src/rps-command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rps-command.js","sourceRoot":"","sources":["../src/rps-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,GAGX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGvE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,iBAAiB;IACjB,eAAe;IACf,aAAa;IACb,cAAc;IACd,cAAc;CACf,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAwB,EACxB,OAAwB,EACxB,MAAc;IAEd,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IAElC,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7B,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,WAAW,MAAM,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrF,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,CAAC,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1E,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAElE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG;YAAE,OAAO,YAAY,CAAC;QAC9B,OAAO,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACvE,CAAC;IAED,OAAO,SAAS,MAAM,OAAO,QAAQ,EAAE,CAAC;AAC1C,CAAC"}
|
package/lib/session-service.d.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
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 { RpsSessionRow } from './models.js';
|
|
3
4
|
export type RpsDatabase = Database<unknown, Models, string>;
|
|
4
|
-
export declare class SessionService {
|
|
5
|
-
private readonly db;
|
|
5
|
+
export declare class SessionService extends BaseSessionService<RpsSessionRow> {
|
|
6
6
|
constructor(db: RpsDatabase);
|
|
7
|
-
|
|
8
|
-
getActiveForUser(channel: string, userId: string): Promise<RpsSessionRow | null>;
|
|
9
|
-
getById(id: string): Promise<RpsSessionRow | null>;
|
|
10
|
-
getActiveByBoardMessageId(messageId: string): Promise<RpsSessionRow | null>;
|
|
11
|
-
createSession(message: Message<any>): Promise<RpsSessionRow>;
|
|
12
|
-
updateSession(id: string, patch: Partial<RpsSessionRow>): Promise<void>;
|
|
13
|
-
abortStale(idleMs: number): Promise<number>;
|
|
7
|
+
createSession(message: GameMessageLike): Promise<RpsSessionRow>;
|
|
14
8
|
}
|
|
15
9
|
export declare function createServices(db: RpsDatabase): SessionService;
|
package/lib/session-service.js
CHANGED
|
@@ -1,37 +1,25 @@
|
|
|
1
|
-
import { channelKey, generateSessionId,
|
|
2
|
-
|
|
3
|
-
const model = db.models.get('rps_sessions');
|
|
4
|
-
if (!model)
|
|
5
|
-
throw new Error('rps_sessions not registered');
|
|
6
|
-
return model;
|
|
7
|
-
}
|
|
8
|
-
export class SessionService {
|
|
9
|
-
db;
|
|
1
|
+
import { BaseSessionService, channelKey, generateSessionId, } from '@zhin.js/game-kit';
|
|
2
|
+
export class SessionService extends BaseSessionService {
|
|
10
3
|
constructor(db) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
for (const row of rows) {
|
|
31
|
-
if (boardMessageMatches(row.board_message_id ?? '', messageId))
|
|
32
|
-
return row;
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
4
|
+
super(db, {
|
|
5
|
+
gameId: 'rps',
|
|
6
|
+
table: 'rps_sessions',
|
|
7
|
+
userFields: ['player_id'],
|
|
8
|
+
projectOutcomes: (session) => session.status === 'won'
|
|
9
|
+
? [{
|
|
10
|
+
userId: session.player_id,
|
|
11
|
+
userName: session.player_name,
|
|
12
|
+
result: 'won',
|
|
13
|
+
score: session.player_wins * 10,
|
|
14
|
+
}]
|
|
15
|
+
: session.status === 'lost'
|
|
16
|
+
? [{
|
|
17
|
+
userId: session.player_id,
|
|
18
|
+
userName: session.player_name,
|
|
19
|
+
result: 'lost',
|
|
20
|
+
}]
|
|
21
|
+
: [],
|
|
22
|
+
});
|
|
35
23
|
}
|
|
36
24
|
async createSession(message) {
|
|
37
25
|
const now = Date.now();
|
|
@@ -48,28 +36,10 @@ export class SessionService {
|
|
|
48
36
|
bot_wins: 0,
|
|
49
37
|
round: 0,
|
|
50
38
|
status: 'active',
|
|
51
|
-
board_message_id: '',
|
|
52
39
|
updated_at: now,
|
|
53
40
|
created_at: now,
|
|
54
41
|
};
|
|
55
|
-
|
|
56
|
-
return row;
|
|
57
|
-
}
|
|
58
|
-
async updateSession(id, patch) {
|
|
59
|
-
await getModel(this.db).updateWhere({ id }, { ...patch, updated_at: Date.now() });
|
|
60
|
-
}
|
|
61
|
-
async abortStale(idleMs) {
|
|
62
|
-
const cutoff = Date.now() - idleMs;
|
|
63
|
-
const model = getModel(this.db);
|
|
64
|
-
const rows = await model.findAll({ status: 'active' });
|
|
65
|
-
let n = 0;
|
|
66
|
-
for (const row of rows) {
|
|
67
|
-
if (row.updated_at < cutoff) {
|
|
68
|
-
await model.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
69
|
-
n++;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return n;
|
|
42
|
+
return this.createRow(row);
|
|
73
43
|
}
|
|
74
44
|
}
|
|
75
45
|
export function createServices(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":"AACA,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,iBAAiB,GAGlB,MAAM,mBAAmB,CAAC;AAK3B,MAAM,OAAO,cAAe,SAAQ,kBAAiC;IACnE,YAAY,EAAe;QACzB,KAAK,CAAC,EAAmD,EAAE;YACzD,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,cAAc;YACrB,UAAU,EAAE,CAAC,WAAW,CAAC;YACzB,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK;gBACpD,CAAC,CAAC,CAAC;wBACC,MAAM,EAAE,OAAO,CAAC,SAAS;wBACzB,QAAQ,EAAE,OAAO,CAAC,WAAW;wBAC7B,MAAM,EAAE,KAAK;wBACb,KAAK,EAAE,OAAO,CAAC,WAAW,GAAG,EAAE;qBAChC,CAAC;gBACJ,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM;oBACzB,CAAC,CAAC,CAAC;4BACC,MAAM,EAAE,OAAO,CAAC,SAAS;4BACzB,QAAQ,EAAE,OAAO,CAAC,WAAW;4BAC7B,MAAM,EAAE,MAAM;yBACf,CAAC;oBACJ,CAAC,CAAC,EAAE;SACT,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAwB;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,GAAG,GAAkB;YACzB,EAAE,EAAE,iBAAiB,EAAE;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;YACjC,QAAQ,EAAE,OAAO,CAAC,SAAS;YAC3B,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI;YACnC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE;YAC/B,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;YAChC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE;YAC7B,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC/D,WAAW,EAAE,CAAC;YACd,QAAQ,EAAE,CAAC;YACX,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,QAAQ;YAChB,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,GAAG;SAChB,CAAC;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,EAAe;IAC5C,OAAO,IAAI,cAAc,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC"}
|
package/lib/view.js
CHANGED
|
@@ -22,6 +22,8 @@ export function buildRpsView(session, lastRound, channelType) {
|
|
|
22
22
|
lines.push('🏆 **你赢得比赛!**');
|
|
23
23
|
else if (session.status === 'lost')
|
|
24
24
|
lines.push('💀 **机器人赢得比赛。**');
|
|
25
|
+
else
|
|
26
|
+
lines.push('🏳️ **本局已放弃。**');
|
|
25
27
|
}
|
|
26
28
|
else if (!lastRound) {
|
|
27
29
|
lines.push('', '出拳吧!');
|
|
@@ -31,11 +33,14 @@ export function buildRpsView(session, lastRound, channelType) {
|
|
|
31
33
|
}
|
|
32
34
|
const choices = terminal
|
|
33
35
|
? [{ id: 'restart', label: '🔄 再来一局', style: 'primary', keepEnabledWhenTerminal: true }]
|
|
34
|
-
: [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
: [
|
|
37
|
+
...['rock', 'paper', 'scissors'].map((m) => ({
|
|
38
|
+
id: m,
|
|
39
|
+
label: MOVE_LABELS[m],
|
|
40
|
+
style: 'secondary',
|
|
41
|
+
})),
|
|
42
|
+
{ id: 'quit', label: '🏳️ 放弃', style: 'danger' },
|
|
43
|
+
];
|
|
39
44
|
return buildChoiceKeyboard({
|
|
40
45
|
gamePrefix: RPS_PREFIX,
|
|
41
46
|
sessionId: session.id,
|
|
@@ -43,7 +48,7 @@ export function buildRpsView(session, lastRound, channelType) {
|
|
|
43
48
|
choices,
|
|
44
49
|
terminal,
|
|
45
50
|
buttonsPerRow: 3,
|
|
46
|
-
fallbackHint: '
|
|
51
|
+
fallbackHint: '回复数字:1石头 2布 3剪刀 4放弃',
|
|
47
52
|
interactionProfile: terminal ? 'terminal' : 'gameplay',
|
|
48
53
|
channelType,
|
|
49
54
|
});
|
package/lib/view.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AAEhF,MAAM,UAAU,YAAY,CAC1B,OAAsB,EACtB,SAAgE,EAChE,WAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;IAC7C,MAAM,KAAK,GAAG;QACZ,eAAe;QACf,EAAE;QACF,YAAY,OAAO,CAAC,WAAW,UAAU,OAAO,CAAC,QAAQ,YAAY,UAAU,KAAK;KACrF,CAAC;IAEF,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrF,KAAK,CAAC,IAAI,CACR,SAAS,CAAC,MAAM,KAAK,CAAC;YACpB,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;gBACtB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,cAAc,CACrB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;aACrD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AAEhF,MAAM,UAAU,YAAY,CAC1B,OAAsB,EACtB,SAAgE,EAChE,WAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;IAC7C,MAAM,KAAK,GAAG;QACZ,eAAe;QACf,EAAE;QACF,YAAY,OAAO,CAAC,WAAW,UAAU,OAAO,CAAC,QAAQ,YAAY,UAAU,KAAK;KACrF,CAAC;IAEF,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrF,KAAK,CAAC,IAAI,CACR,SAAS,CAAC,MAAM,KAAK,CAAC;YACpB,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;gBACtB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,cAAc,CACrB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;aACrD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;;YAC7D,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;SAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACzB,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ;QACtB,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAkB,EAAE,uBAAuB,EAAE,IAAI,EAAE,CAAC;QACjG,CAAC,CAAC;YACE,GAAI,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1D,EAAE,EAAE,CAAC;gBACL,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC;gBACrB,KAAK,EAAE,WAAoB;aAC5B,CAAC,CAAC;YACH,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAiB,EAAE;SAC1D,CAAC;IAEN,OAAO,mBAAmB,CAAC;QACzB,UAAU,EAAE,UAAU;QACtB,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3B,OAAO;QACP,QAAQ;QACR,aAAa,EAAE,CAAC;QAChB,YAAY,EAAE,qBAAqB;QACnC,kBAAkB,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;QACtD,WAAW;KACZ,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineGameCommandAliasMiddleware, isRpsAction, messageFromCommandInput, normalizeRpsAction, } from '@zhin.js/game-kit';
|
|
3
|
+
import { RPS_HELP, runRpsCommand } from "../lib/rps-command.js";
|
|
4
|
+
import { resolveGameServices } from "../lib/runtime-store.js";
|
|
5
|
+
export default defineGameCommandAliasMiddleware({
|
|
6
|
+
aliases: ['猜拳', 'rps'],
|
|
7
|
+
async run(action, input, context) {
|
|
8
|
+
const normalized = normalizeRpsAction(String(action ?? ''));
|
|
9
|
+
if (normalized === 'help')
|
|
10
|
+
return RPS_HELP;
|
|
11
|
+
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
12
|
+
if (!isRpsAction(normalized))
|
|
13
|
+
return null;
|
|
14
|
+
const services = resolveGameServices(context);
|
|
15
|
+
const message = messageFromCommandInput(input);
|
|
16
|
+
return runRpsCommand(services, message, normalized);
|
|
17
|
+
},
|
|
18
|
+
});
|
package/middlewares/rps-alias.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineGameCommandAliasMiddleware,
|
|
3
|
+
isRpsAction,
|
|
3
4
|
messageFromCommandInput,
|
|
4
5
|
normalizeRpsAction,
|
|
5
6
|
} from '@zhin.js/game-kit';
|
|
6
|
-
import { RPS_HELP,
|
|
7
|
+
import { RPS_HELP, runRpsCommand } from '../src/rps-command.js';
|
|
7
8
|
import { resolveGameServices } from '../src/runtime-store.js';
|
|
8
9
|
|
|
9
10
|
export default defineGameCommandAliasMiddleware({
|
|
10
11
|
aliases: ['猜拳', 'rps'],
|
|
11
12
|
async run(action, input, context) {
|
|
12
13
|
const normalized = normalizeRpsAction(String(action ?? ''));
|
|
13
|
-
if (
|
|
14
|
+
if (normalized === 'help') return RPS_HELP;
|
|
15
|
+
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
16
|
+
if (!isRpsAction(normalized)) return null;
|
|
14
17
|
const services = resolveGameServices(context);
|
|
15
18
|
const message = messageFromCommandInput(input);
|
|
16
|
-
return
|
|
19
|
+
return runRpsCommand(services, message, normalized);
|
|
17
20
|
},
|
|
18
21
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
3
|
+
import { buildChoiceFallbackMap, channelKey, messageFromCommandInput, parseChoicePayload, resolveGameTextPayload, } from '@zhin.js/game-kit';
|
|
4
|
+
import { resolveGameServices } from "../lib/runtime-store.js";
|
|
5
|
+
import { handleChoice, RPS_PREFIX } from "../lib/game-flow.js";
|
|
6
|
+
/**
|
|
7
|
+
* 文本入口:按钮 payload(`rps:session:rock|paper|scissors|restart`)与数字 fallback
|
|
8
|
+
* (『1石头 2布 3剪刀』,对应 view 的 fallbackHint)。
|
|
9
|
+
*/
|
|
10
|
+
export default defineMiddleware({
|
|
11
|
+
target: 'inbound',
|
|
12
|
+
async handle(context, next) {
|
|
13
|
+
const raw = context.input.content?.trim() ?? '';
|
|
14
|
+
if (!raw) {
|
|
15
|
+
await next();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const services = resolveGameServices(context);
|
|
19
|
+
const message = messageFromCommandInput(context.input);
|
|
20
|
+
const ch = channelKey(message);
|
|
21
|
+
// 直接 payload(QQ 指令预填 / Sandbox action→text)
|
|
22
|
+
const payloadFromText = resolveGameTextPayload(raw);
|
|
23
|
+
if (payloadFromText?.startsWith(`${RPS_PREFIX}:`)) {
|
|
24
|
+
const parsed = parseChoicePayload(payloadFromText, RPS_PREFIX);
|
|
25
|
+
if (parsed) {
|
|
26
|
+
const session = await services.getById(parsed.sessionId);
|
|
27
|
+
if (session?.channel_key === ch) {
|
|
28
|
+
const reply = await handleChoice(services, message, parsed.sessionId, parsed.choiceId);
|
|
29
|
+
if (reply)
|
|
30
|
+
await context.input.$reply(reply);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
await next();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const session = await services.getActiveForUser(ch, message.$sender.id);
|
|
38
|
+
if (!session || session.status !== 'active') {
|
|
39
|
+
await next();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const map = buildChoiceFallbackMap(RPS_PREFIX, session.id, [
|
|
43
|
+
{ id: 'rock', label: '石头' },
|
|
44
|
+
{ id: 'paper', label: '布' },
|
|
45
|
+
{ id: 'scissors', label: '剪刀' },
|
|
46
|
+
{ id: 'quit', label: '放弃' },
|
|
47
|
+
]);
|
|
48
|
+
const payload = resolveGameTextPayload(raw, map);
|
|
49
|
+
const parsed = payload ? parseChoicePayload(payload, RPS_PREFIX) : null;
|
|
50
|
+
if (!parsed || parsed.sessionId !== session.id) {
|
|
51
|
+
await next();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const reply = await handleChoice(services, message, session.id, parsed.choiceId);
|
|
55
|
+
if (reply)
|
|
56
|
+
await context.input.$reply(reply);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
@@ -33,7 +33,7 @@ export default defineMiddleware<Message>({
|
|
|
33
33
|
if (parsed) {
|
|
34
34
|
const session = await services.getById(parsed.sessionId);
|
|
35
35
|
if (session?.channel_key === ch) {
|
|
36
|
-
const reply = await handleChoice(
|
|
36
|
+
const reply = await handleChoice(services, message, parsed.sessionId, parsed.choiceId);
|
|
37
37
|
if (reply) await context.input.$reply(reply);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
@@ -52,6 +52,7 @@ export default defineMiddleware<Message>({
|
|
|
52
52
|
{ id: 'rock', label: '石头' },
|
|
53
53
|
{ id: 'paper', label: '布' },
|
|
54
54
|
{ id: 'scissors', label: '剪刀' },
|
|
55
|
+
{ id: 'quit', label: '放弃' },
|
|
55
56
|
]);
|
|
56
57
|
const payload = resolveGameTextPayload(raw, map);
|
|
57
58
|
const parsed = payload ? parseChoicePayload(payload, RPS_PREFIX) : null;
|
|
@@ -60,7 +61,7 @@ export default defineMiddleware<Message>({
|
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
const reply = await handleChoice(
|
|
64
|
+
const reply = await handleChoice(services, message, session.id, parsed.choiceId);
|
|
64
65
|
if (reply) await context.input.$reply(reply);
|
|
65
66
|
},
|
|
66
67
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-rps",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Zhin.js 猜拳游戏 — 石头剪刀布三局两胜 (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
-
"plugin.
|
|
17
|
+
"plugin.js",
|
|
18
18
|
"schema.json",
|
|
19
19
|
"commands",
|
|
20
20
|
"middlewares",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zhin.js/command": "1.0.
|
|
28
|
-
"@zhin.js/core": "1.4.
|
|
29
|
-
"@zhin.js/game-kit": "2.0.
|
|
30
|
-
"@zhin.js/middleware": "1.0.
|
|
31
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
27
|
+
"@zhin.js/command": "1.0.4",
|
|
28
|
+
"@zhin.js/core": "1.4.2",
|
|
29
|
+
"@zhin.js/game-kit": "2.0.2",
|
|
30
|
+
"@zhin.js/middleware": "1.0.4",
|
|
31
|
+
"@zhin.js/plugin-runtime": "1.1.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "^6.0.3",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"zhin": {
|
|
50
50
|
"protocol": 1,
|
|
51
51
|
"type": "plugin",
|
|
52
|
-
"entry": "./plugin.
|
|
52
|
+
"entry": "./plugin.js",
|
|
53
53
|
"engine": "^1.0.0",
|
|
54
54
|
"runtime": "trusted",
|
|
55
55
|
"features": [
|
package/plugin.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineGamePlugin } from '@zhin.js/game-kit';
|
|
3
|
+
import { defineHostTables } from "./lib/models.js";
|
|
4
|
+
import { gameServicesToken } from "./lib/runtime-store.js";
|
|
5
|
+
import { createServices, } from "./lib/session-service.js";
|
|
6
|
+
/**
|
|
7
|
+
* Plugin Runtime:
|
|
8
|
+
* - Commands under `commands/` are authoritative (help + start/continue/quit via in-memory DB).
|
|
9
|
+
* - DB: prefer databaseHostToken; else in-memory SessionService.
|
|
10
|
+
* - Choice middleware under `middlewares/` handles RPS choice payloads (Sandbox action→text).
|
|
11
|
+
*/
|
|
12
|
+
export default defineGamePlugin({
|
|
13
|
+
name: 'rps',
|
|
14
|
+
metadata: {
|
|
15
|
+
displayName: 'RPS',
|
|
16
|
+
},
|
|
17
|
+
game: {
|
|
18
|
+
id: 'rps',
|
|
19
|
+
title: '猜拳对决',
|
|
20
|
+
icon: '✊',
|
|
21
|
+
description: '石头剪刀布,三局两胜',
|
|
22
|
+
commandPrefix: '/猜拳',
|
|
23
|
+
quickStart: '开始',
|
|
24
|
+
aliases: ['rps'],
|
|
25
|
+
menus: [
|
|
26
|
+
{ id: 'start', label: '🎮 开始对局' },
|
|
27
|
+
{ id: 'continue', label: '🔄 继续' },
|
|
28
|
+
{ id: 'help', label: '📖 玩法说明' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
tables: ['rps_sessions'],
|
|
32
|
+
servicesToken: gameServicesToken,
|
|
33
|
+
defineHostTables,
|
|
34
|
+
createServices: (database) => createServices(database),
|
|
35
|
+
session: (services) => services,
|
|
36
|
+
});
|
package/src/game-flow.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
3
2
|
import type { RpsSessionRow } from './models.js';
|
|
4
3
|
import {
|
|
5
4
|
RPS_PREFIX,
|
|
@@ -11,42 +10,23 @@ import {
|
|
|
11
10
|
import type { SessionService } from './session-service.js';
|
|
12
11
|
import { buildRpsView } from './view.js';
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Plugin Runtime: render the board as text. Interactive in-place board editing
|
|
15
|
+
* (the old Adapter.editMessage path) is not part of the runtime flow; commands
|
|
16
|
+
* and the choice middleware return fresh text each turn.
|
|
17
|
+
*/
|
|
18
|
+
function renderView(
|
|
18
19
|
session: RpsSessionRow,
|
|
20
|
+
message: GameMessageLike,
|
|
19
21
|
lastRound?: { player: RpsMove; bot: RpsMove; result: 0 | 1 | 2 },
|
|
20
|
-
):
|
|
21
|
-
|
|
22
|
-
if (!plugin) return plainTextFromSendContent(content);
|
|
23
|
-
|
|
24
|
-
const adapter = plugin.root.inject(message.$adapter) as Adapter;
|
|
25
|
-
|
|
26
|
-
if (session.board_message_id) {
|
|
27
|
-
const msgId = await adapter.editMessage({
|
|
28
|
-
messageId: session.board_message_id,
|
|
29
|
-
context: String(message.$adapter),
|
|
30
|
-
endpoint: message.$endpoint,
|
|
31
|
-
id: message.$channel.id,
|
|
32
|
-
type: message.$channel.type,
|
|
33
|
-
content,
|
|
34
|
-
});
|
|
35
|
-
if (msgId !== session.board_message_id) {
|
|
36
|
-
await services.updateSession(session.id, { board_message_id: msgId });
|
|
37
|
-
}
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const msgId = await message.$reply?.(content);
|
|
42
|
-
if (msgId) await services.updateSession(session.id, { board_message_id: msgId });
|
|
22
|
+
): GameReply {
|
|
23
|
+
return buildRpsView(session, lastRound, message.$channel.type);
|
|
43
24
|
}
|
|
44
25
|
|
|
45
26
|
export async function startGame(
|
|
46
|
-
plugin: Plugin | null,
|
|
47
27
|
services: SessionService,
|
|
48
|
-
message:
|
|
49
|
-
): Promise<
|
|
28
|
+
message: GameMessageLike,
|
|
29
|
+
): Promise<GameReply | undefined> {
|
|
50
30
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
51
31
|
const active = await services.getActiveByChannel(ch);
|
|
52
32
|
if (active) {
|
|
@@ -56,17 +36,15 @@ export async function startGame(
|
|
|
56
36
|
return `本频道 ${active.player_name} 正在猜拳对决中。`;
|
|
57
37
|
}
|
|
58
38
|
const session = await services.createSession(message);
|
|
59
|
-
|
|
60
|
-
return typeof text === 'string' ? text : undefined;
|
|
39
|
+
return renderView(session, message);
|
|
61
40
|
}
|
|
62
41
|
|
|
63
42
|
export async function handleChoice(
|
|
64
|
-
plugin: Plugin | null,
|
|
65
43
|
services: SessionService,
|
|
66
|
-
message:
|
|
44
|
+
message: GameMessageLike,
|
|
67
45
|
sessionId: string,
|
|
68
46
|
choiceId: string,
|
|
69
|
-
): Promise<
|
|
47
|
+
): Promise<GameReply | null> {
|
|
70
48
|
const session = await services.getById(sessionId);
|
|
71
49
|
if (!session) return '对局不存在。';
|
|
72
50
|
if (session.status !== 'active' && choiceId !== 'restart') {
|
|
@@ -76,8 +54,11 @@ export async function handleChoice(
|
|
|
76
54
|
|
|
77
55
|
if (choiceId === 'restart') {
|
|
78
56
|
await services.updateSession(session.id, { status: 'aborted' });
|
|
79
|
-
|
|
80
|
-
|
|
57
|
+
return (await startGame(services, message)) ?? null;
|
|
58
|
+
}
|
|
59
|
+
if (choiceId === 'quit') {
|
|
60
|
+
await services.updateSession(session.id, { status: 'aborted' });
|
|
61
|
+
return renderView((await services.getById(session.id))!, message);
|
|
81
62
|
}
|
|
82
63
|
|
|
83
64
|
const player = choiceId as RpsMove;
|
|
@@ -102,25 +83,19 @@ export async function handleChoice(
|
|
|
102
83
|
});
|
|
103
84
|
|
|
104
85
|
const updated = (await services.getById(session.id))!;
|
|
105
|
-
|
|
106
|
-
else if (status === 'lost') void recordGameOutcome(message, 'rps', 'lost');
|
|
107
|
-
const text = await sendOrEditView(plugin, services, message, updated, { player, bot, result });
|
|
108
|
-
return typeof text === 'string' ? text : null;
|
|
86
|
+
return renderView(updated, message, { player, bot, result });
|
|
109
87
|
}
|
|
110
88
|
|
|
111
89
|
export async function continueGame(
|
|
112
|
-
plugin: Plugin | null,
|
|
113
90
|
services: SessionService,
|
|
114
|
-
message:
|
|
115
|
-
): Promise<
|
|
91
|
+
message: GameMessageLike,
|
|
92
|
+
): Promise<GameReply> {
|
|
116
93
|
const session = await services.getActiveForUser(
|
|
117
94
|
`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
|
|
118
95
|
message.$sender.id,
|
|
119
96
|
);
|
|
120
97
|
if (!session) return '你没有进行中的猜拳,发送「猜拳 开始」。';
|
|
121
|
-
|
|
122
|
-
if (typeof text === 'string') return text;
|
|
123
|
-
return '已刷新猜拳界面。';
|
|
98
|
+
return renderView(session, message);
|
|
124
99
|
}
|
|
125
100
|
|
|
126
101
|
export { RPS_PREFIX };
|
package/src/index.ts
CHANGED
package/src/models.ts
CHANGED
|
@@ -17,7 +17,6 @@ declare module '@zhin.js/core' {
|
|
|
17
17
|
bot_wins: number;
|
|
18
18
|
round: number;
|
|
19
19
|
status: RpsSessionStatus;
|
|
20
|
-
board_message_id: string;
|
|
21
20
|
updated_at: number;
|
|
22
21
|
created_at: number;
|
|
23
22
|
};
|
|
@@ -43,7 +42,6 @@ export function defineHostTables(
|
|
|
43
42
|
bot_wins: { type: 'integer', default: 0 },
|
|
44
43
|
round: { type: 'integer', default: 0 },
|
|
45
44
|
status: { type: 'text', default: 'active' },
|
|
46
|
-
board_message_id: { type: 'text', default: '' },
|
|
47
45
|
updated_at: { type: 'integer', default: 0 },
|
|
48
46
|
created_at: { type: 'integer', default: 0 },
|
|
49
47
|
});
|
package/src/rps-command.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
channelKey,
|
|
3
|
+
type GameMessageLike,
|
|
4
|
+
type GameReply,
|
|
5
|
+
} from '@zhin.js/game-kit';
|
|
6
|
+
import { continueGame, handleChoice, startGame } from './game-flow.js';
|
|
4
7
|
import type { SessionService } from './session-service.js';
|
|
5
8
|
|
|
6
9
|
export const RPS_HELP = [
|
|
@@ -12,11 +15,10 @@ export const RPS_HELP = [
|
|
|
12
15
|
].join('\n');
|
|
13
16
|
|
|
14
17
|
export async function runRpsCommand(
|
|
15
|
-
plugin: Plugin | null,
|
|
16
18
|
services: SessionService,
|
|
17
|
-
message:
|
|
19
|
+
message: GameMessageLike,
|
|
18
20
|
action: string,
|
|
19
|
-
): Promise<
|
|
21
|
+
): Promise<GameReply> {
|
|
20
22
|
const ch = channelKey(message);
|
|
21
23
|
const userId = message.$sender.id;
|
|
22
24
|
|
|
@@ -31,24 +33,14 @@ export async function runRpsCommand(
|
|
|
31
33
|
return lines.join('\n');
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
if (action === 'start') return startGame(
|
|
35
|
-
if (action === 'continue') return continueGame(
|
|
36
|
+
if (action === 'start') return (await startGame(services, message)) ?? '';
|
|
37
|
+
if (action === 'continue') return continueGame(services, message);
|
|
36
38
|
|
|
37
39
|
if (action === 'quit') {
|
|
38
40
|
const row = await services.getActiveForUser(ch, userId);
|
|
39
41
|
if (!row) return '你没有进行中的猜拳。';
|
|
40
|
-
await services
|
|
41
|
-
return '已放弃猜拳对局。';
|
|
42
|
+
return (await handleChoice(services, message, row.id, 'quit')) ?? '';
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
return `未知子命令:${action}\n\n${RPS_HELP}`;
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
48
|
-
export async function runRpsCommandText(
|
|
49
|
-
services: SessionService,
|
|
50
|
-
message: Message<any>,
|
|
51
|
-
action: string,
|
|
52
|
-
): Promise<string> {
|
|
53
|
-
return (await runRpsCommand(null, services, message, action)) ?? '';
|
|
54
|
-
}
|
package/src/session-service.ts
CHANGED
|
@@ -1,43 +1,39 @@
|
|
|
1
|
-
import type { Database,
|
|
2
|
-
import {
|
|
1
|
+
import type { Database, Models } from '@zhin.js/core';
|
|
2
|
+
import {
|
|
3
|
+
BaseSessionService,
|
|
4
|
+
channelKey,
|
|
5
|
+
generateSessionId,
|
|
6
|
+
type GameMessageLike,
|
|
7
|
+
type GameSessionDatabase,
|
|
8
|
+
} from '@zhin.js/game-kit';
|
|
3
9
|
import type { RpsSessionRow } from './models.js';
|
|
4
10
|
|
|
5
11
|
export type RpsDatabase = Database<unknown, Models, string>;
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return getModel(this.db).findOne({ id });
|
|
13
|
+
export class SessionService extends BaseSessionService<RpsSessionRow> {
|
|
14
|
+
constructor(db: RpsDatabase) {
|
|
15
|
+
super(db as unknown as GameSessionDatabase<RpsSessionRow>, {
|
|
16
|
+
gameId: 'rps',
|
|
17
|
+
table: 'rps_sessions',
|
|
18
|
+
userFields: ['player_id'],
|
|
19
|
+
projectOutcomes: (session) => session.status === 'won'
|
|
20
|
+
? [{
|
|
21
|
+
userId: session.player_id,
|
|
22
|
+
userName: session.player_name,
|
|
23
|
+
result: 'won',
|
|
24
|
+
score: session.player_wins * 10,
|
|
25
|
+
}]
|
|
26
|
+
: session.status === 'lost'
|
|
27
|
+
? [{
|
|
28
|
+
userId: session.player_id,
|
|
29
|
+
userName: session.player_name,
|
|
30
|
+
result: 'lost',
|
|
31
|
+
}]
|
|
32
|
+
: [],
|
|
33
|
+
});
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
async
|
|
32
|
-
if (!messageId) return null;
|
|
33
|
-
const rows = await getModel(this.db).findAll({});
|
|
34
|
-
for (const row of rows) {
|
|
35
|
-
if (boardMessageMatches(row.board_message_id ?? '', messageId)) return row;
|
|
36
|
-
}
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async createSession(message: Message<any>): Promise<RpsSessionRow> {
|
|
36
|
+
async createSession(message: GameMessageLike): Promise<RpsSessionRow> {
|
|
41
37
|
const now = Date.now();
|
|
42
38
|
const row: RpsSessionRow = {
|
|
43
39
|
id: generateSessionId(),
|
|
@@ -52,34 +48,13 @@ export class SessionService {
|
|
|
52
48
|
bot_wins: 0,
|
|
53
49
|
round: 0,
|
|
54
50
|
status: 'active',
|
|
55
|
-
board_message_id: '',
|
|
56
51
|
updated_at: now,
|
|
57
52
|
created_at: now,
|
|
58
53
|
};
|
|
59
|
-
|
|
60
|
-
return row;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async updateSession(id: string, patch: Partial<RpsSessionRow>): Promise<void> {
|
|
64
|
-
await getModel(this.db).updateWhere({ id }, { ...patch, updated_at: Date.now() });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async abortStale(idleMs: number): Promise<number> {
|
|
68
|
-
const cutoff = Date.now() - idleMs;
|
|
69
|
-
const model = getModel(this.db);
|
|
70
|
-
const rows = await model.findAll({ status: 'active' });
|
|
71
|
-
let n = 0;
|
|
72
|
-
for (const row of rows) {
|
|
73
|
-
if (row.updated_at < cutoff) {
|
|
74
|
-
await model.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
75
|
-
n++;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return n;
|
|
54
|
+
return this.createRow(row);
|
|
79
55
|
}
|
|
80
56
|
}
|
|
81
57
|
|
|
82
58
|
export function createServices(db: RpsDatabase): SessionService {
|
|
83
59
|
return new SessionService(db);
|
|
84
60
|
}
|
|
85
|
-
|
package/src/view.ts
CHANGED
|
@@ -31,6 +31,7 @@ export function buildRpsView(
|
|
|
31
31
|
lines.push('');
|
|
32
32
|
if (session.status === 'won') lines.push('🏆 **你赢得比赛!**');
|
|
33
33
|
else if (session.status === 'lost') lines.push('💀 **机器人赢得比赛。**');
|
|
34
|
+
else lines.push('🏳️ **本局已放弃。**');
|
|
34
35
|
} else if (!lastRound) {
|
|
35
36
|
lines.push('', '出拳吧!');
|
|
36
37
|
} else {
|
|
@@ -39,11 +40,14 @@ export function buildRpsView(
|
|
|
39
40
|
|
|
40
41
|
const choices = terminal
|
|
41
42
|
? [{ id: 'restart', label: '🔄 再来一局', style: 'primary' as const, keepEnabledWhenTerminal: true }]
|
|
42
|
-
:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
: [
|
|
44
|
+
...(['rock', 'paper', 'scissors'] as RpsMove[]).map((m) => ({
|
|
45
|
+
id: m,
|
|
46
|
+
label: MOVE_LABELS[m],
|
|
47
|
+
style: 'secondary' as const,
|
|
48
|
+
})),
|
|
49
|
+
{ id: 'quit', label: '🏳️ 放弃', style: 'danger' as const },
|
|
50
|
+
];
|
|
47
51
|
|
|
48
52
|
return buildChoiceKeyboard({
|
|
49
53
|
gamePrefix: RPS_PREFIX,
|
|
@@ -52,7 +56,7 @@ export function buildRpsView(
|
|
|
52
56
|
choices,
|
|
53
57
|
terminal,
|
|
54
58
|
buttonsPerRow: 3,
|
|
55
|
-
fallbackHint: '
|
|
59
|
+
fallbackHint: '回复数字:1石头 2布 3剪刀 4放弃',
|
|
56
60
|
interactionProfile: terminal ? 'terminal' : 'gameplay',
|
|
57
61
|
channelType,
|
|
58
62
|
});
|
package/lib/memory-db.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { DatabaseHost } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import { type RpsDatabase, type SessionService } from './session-service.js';
|
|
3
|
-
/** In-memory rps_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
4
|
-
export declare function createInMemoryRpsDb(): RpsDatabase;
|
|
5
|
-
export declare function mountRpsMemoryServices(): SessionService;
|
|
6
|
-
export declare function mountRpsHostServices(host: DatabaseHost): SessionService;
|
package/lib/memory-db.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { createHostGameDb, createInMemoryGameDb } from '@zhin.js/game-kit';
|
|
2
|
-
import { defineHostTables } from './models.js';
|
|
3
|
-
import { createServices } from './session-service.js';
|
|
4
|
-
const RPS_TABLES = ['rps_sessions'];
|
|
5
|
-
/** In-memory rps_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
6
|
-
export function createInMemoryRpsDb() {
|
|
7
|
-
return createInMemoryGameDb(RPS_TABLES);
|
|
8
|
-
}
|
|
9
|
-
export function mountRpsMemoryServices() {
|
|
10
|
-
const services = createServices(createInMemoryRpsDb());
|
|
11
|
-
return services;
|
|
12
|
-
}
|
|
13
|
-
export function mountRpsHostServices(host) {
|
|
14
|
-
defineHostTables(host);
|
|
15
|
-
const services = createServices(createHostGameDb(host, RPS_TABLES));
|
|
16
|
-
return services;
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=memory-db.js.map
|
package/lib/memory-db.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-db.js","sourceRoot":"","sources":["../src/memory-db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAyC,MAAM,sBAAsB,CAAC;AAE7F,MAAM,UAAU,GAAG,CAAC,cAAc,CAAU,CAAC;AAE7C,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB;IACjC,OAAO,oBAAoB,CAAC,UAAU,CAA2B,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,MAAM,QAAQ,GAAG,cAAc,CAAC,mBAAmB,EAAE,CAAC,CAAC;IACvD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAkB;IACrD,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,cAAc,CAAC,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAA2B,CAAC,CAAC;IAC9F,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/plugin.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { definePlugin, databaseHostToken, scheduleHostToken } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import {
|
|
3
|
-
registerRuntimeGame,
|
|
4
|
-
initGameRecordHost,
|
|
5
|
-
DEFAULT_GAME_STALE_CRON,
|
|
6
|
-
DEFAULT_GAME_STALE_IDLE_MS,
|
|
7
|
-
} from '@zhin.js/game-kit';
|
|
8
|
-
import { mountRpsHostServices, mountRpsMemoryServices } from './src/memory-db.js';
|
|
9
|
-
import { gameServicesToken } from './src/runtime-store.js';
|
|
10
|
-
import type { SessionService } from './src/session-service.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Plugin Runtime:
|
|
14
|
-
* - Commands under `commands/` are authoritative (help + start/continue/quit via in-memory DB).
|
|
15
|
-
* - DB: prefer databaseHostToken; else in-memory SessionService.
|
|
16
|
-
* - Choice middleware under `middlewares/` handles RPS choice payloads (Sandbox action→text).
|
|
17
|
-
*/
|
|
18
|
-
export default definePlugin({
|
|
19
|
-
name: 'rps',
|
|
20
|
-
metadata: {
|
|
21
|
-
displayName: 'RPS',
|
|
22
|
-
},
|
|
23
|
-
setup(context) {
|
|
24
|
-
let services: SessionService;
|
|
25
|
-
if (context.resources.has(databaseHostToken)) {
|
|
26
|
-
const host = context.resources.use(databaseHostToken);
|
|
27
|
-
services = mountRpsHostServices(host);
|
|
28
|
-
context.lifecycle.add(initGameRecordHost(host));
|
|
29
|
-
} else {
|
|
30
|
-
services = mountRpsMemoryServices();
|
|
31
|
-
}
|
|
32
|
-
context.resources.provide(gameServicesToken, services);
|
|
33
|
-
const disposeHub = registerRuntimeGame({
|
|
34
|
-
id: 'rps',
|
|
35
|
-
title: '猜拳对决',
|
|
36
|
-
icon: '✊',
|
|
37
|
-
description: '石头剪刀布,三局两胜',
|
|
38
|
-
commandPrefix: '/猜拳',
|
|
39
|
-
quickStart: '开始',
|
|
40
|
-
aliases: ['rps'],
|
|
41
|
-
menus: [
|
|
42
|
-
{ id: 'start', label: '🎮 开始对局' },
|
|
43
|
-
{ id: 'continue', label: '🔄 继续' },
|
|
44
|
-
{ id: 'help', label: '📖 玩法说明' },
|
|
45
|
-
],
|
|
46
|
-
});
|
|
47
|
-
context.lifecycle.add(disposeHub);
|
|
48
|
-
|
|
49
|
-
if (context.resources.has(scheduleHostToken)) {
|
|
50
|
-
const schedule = context.resources.use(scheduleHostToken);
|
|
51
|
-
const disposeCron = schedule.register({
|
|
52
|
-
id: 'rps/abort-stale',
|
|
53
|
-
cron: DEFAULT_GAME_STALE_CRON,
|
|
54
|
-
description: 'Abort stale rps sessions',
|
|
55
|
-
async execute() {
|
|
56
|
-
if (!services.abortStale) return;
|
|
57
|
-
await services.abortStale(DEFAULT_GAME_STALE_IDLE_MS);
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
context.lifecycle.add(disposeCron);
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
});
|
package/src/memory-db.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { createHostGameDb, createInMemoryGameDb } from '@zhin.js/game-kit';
|
|
2
|
-
import type { DatabaseHost } from '@zhin.js/plugin-runtime';
|
|
3
|
-
import { defineHostTables } from './models.js';
|
|
4
|
-
import { createServices, type RpsDatabase, type SessionService } from './session-service.js';
|
|
5
|
-
|
|
6
|
-
const RPS_TABLES = ['rps_sessions'] as const;
|
|
7
|
-
|
|
8
|
-
/** In-memory rps_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
9
|
-
export function createInMemoryRpsDb(): RpsDatabase {
|
|
10
|
-
return createInMemoryGameDb(RPS_TABLES) as unknown as RpsDatabase;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function mountRpsMemoryServices(): SessionService {
|
|
14
|
-
const services = createServices(createInMemoryRpsDb());
|
|
15
|
-
return services;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function mountRpsHostServices(host: DatabaseHost): SessionService {
|
|
19
|
-
defineHostTables(host);
|
|
20
|
-
const services = createServices(createHostGameDb(host, RPS_TABLES) as unknown as RpsDatabase);
|
|
21
|
-
return services;
|
|
22
|
-
}
|