@zhin.js/plugin-rps 1.0.2 → 1.0.4
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=].ts +2 -2
- package/lib/game-flow.d.ts +5 -11
- package/lib/game-flow.js +14 -35
- package/lib/game-flow.js.map +1 -1
- package/lib/rps-command.d.ts +2 -4
- package/lib/rps-command.js +3 -7
- package/lib/rps-command.js.map +1 -1
- package/lib/session-service.d.ts +3 -2
- package/lib/session-service.js.map +1 -1
- package/middlewares/rps-alias.ts +6 -3
- package/middlewares/rps-choice.ts +2 -2
- package/package.json +6 -6
- package/src/game-flow.ts +17 -44
- package/src/rps-command.ts +5 -16
- package/src/session-service.ts +3 -3
|
@@ -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
|
|
2
|
-
import
|
|
3
|
-
import { RPS_PREFIX, type RpsMove } from './engine.js';
|
|
1
|
+
import { type GameMessageLike } 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<string | undefined>;
|
|
5
|
+
export declare function handleChoice(services: SessionService, message: GameMessageLike, sessionId: string, choiceId: string): Promise<string | null>;
|
|
6
|
+
export declare function continueGame(services: SessionService, message: GameMessageLike): Promise<string>;
|
|
13
7
|
export { RPS_PREFIX };
|
package/lib/game-flow.js
CHANGED
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
|
|
2
2
|
import { RPS_PREFIX, WIN_TARGET, randomBotMove, resolveRound, } from './engine.js';
|
|
3
3
|
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 });
|
|
4
|
+
/**
|
|
5
|
+
* Plugin Runtime: render the board as text. Interactive in-place board editing
|
|
6
|
+
* (the old Adapter.editMessage path) is not part of the runtime flow; commands
|
|
7
|
+
* and the choice middleware return fresh text each turn.
|
|
8
|
+
*/
|
|
9
|
+
function renderView(session, message, lastRound) {
|
|
10
|
+
return plainTextFromSendContent(buildRpsView(session, lastRound, message.$channel.type));
|
|
26
11
|
}
|
|
27
|
-
export async function startGame(
|
|
12
|
+
export async function startGame(services, message) {
|
|
28
13
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
29
14
|
const active = await services.getActiveByChannel(ch);
|
|
30
15
|
if (active) {
|
|
@@ -34,10 +19,9 @@ export async function startGame(plugin, services, message) {
|
|
|
34
19
|
return `本频道 ${active.player_name} 正在猜拳对决中。`;
|
|
35
20
|
}
|
|
36
21
|
const session = await services.createSession(message);
|
|
37
|
-
|
|
38
|
-
return typeof text === 'string' ? text : undefined;
|
|
22
|
+
return renderView(session, message);
|
|
39
23
|
}
|
|
40
|
-
export async function handleChoice(
|
|
24
|
+
export async function handleChoice(services, message, sessionId, choiceId) {
|
|
41
25
|
const session = await services.getById(sessionId);
|
|
42
26
|
if (!session)
|
|
43
27
|
return '对局不存在。';
|
|
@@ -48,8 +32,7 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
|
|
|
48
32
|
return '这是别人的对局。';
|
|
49
33
|
if (choiceId === 'restart') {
|
|
50
34
|
await services.updateSession(session.id, { status: 'aborted' });
|
|
51
|
-
|
|
52
|
-
return (await startGame(plugin, services, message)) ?? null;
|
|
35
|
+
return (await startGame(services, message)) ?? null;
|
|
53
36
|
}
|
|
54
37
|
const player = choiceId;
|
|
55
38
|
if (!['rock', 'paper', 'scissors'].includes(player))
|
|
@@ -78,17 +61,13 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
|
|
|
78
61
|
void recordGameOutcome(message, 'rps', 'won', playerWins * 10);
|
|
79
62
|
else if (status === 'lost')
|
|
80
63
|
void recordGameOutcome(message, 'rps', 'lost');
|
|
81
|
-
|
|
82
|
-
return typeof text === 'string' ? text : null;
|
|
64
|
+
return renderView(updated, message, { player, bot, result });
|
|
83
65
|
}
|
|
84
|
-
export async function continueGame(
|
|
66
|
+
export async function continueGame(services, message) {
|
|
85
67
|
const session = await services.getActiveForUser(`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`, message.$sender.id);
|
|
86
68
|
if (!session)
|
|
87
69
|
return '你没有进行中的猜拳,发送「猜拳 开始」。';
|
|
88
|
-
|
|
89
|
-
if (typeof text === 'string')
|
|
90
|
-
return text;
|
|
91
|
-
return '已刷新猜拳界面。';
|
|
70
|
+
return renderView(session, message);
|
|
92
71
|
}
|
|
93
72
|
export { RPS_PREFIX };
|
|
94
73
|
//# 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/rps-command.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type GameMessageLike } 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<string>;
|
package/lib/rps-command.js
CHANGED
|
@@ -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,9 +22,9 @@ 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)
|
|
@@ -34,8 +34,4 @@ export async function runRpsCommand(plugin, services, message, action) {
|
|
|
34
34
|
}
|
|
35
35
|
return `未知子命令:${action}\n\n${RPS_HELP}`;
|
|
36
36
|
}
|
|
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
37
|
//# 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,EAAE,UAAU,EAAwB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGzD,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,MAAM,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC5D,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,SAAS,MAAM,OAAO,QAAQ,EAAE,CAAC;AAC1C,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 { 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
5
|
export declare class SessionService {
|
|
@@ -8,7 +9,7 @@ export declare class SessionService {
|
|
|
8
9
|
getActiveForUser(channel: string, userId: string): Promise<RpsSessionRow | null>;
|
|
9
10
|
getById(id: string): Promise<RpsSessionRow | null>;
|
|
10
11
|
getActiveByBoardMessageId(messageId: string): Promise<RpsSessionRow | null>;
|
|
11
|
-
createSession(message:
|
|
12
|
+
createSession(message: GameMessageLike): Promise<RpsSessionRow>;
|
|
12
13
|
updateSession(id: string, patch: Partial<RpsSessionRow>): Promise<void>;
|
|
13
14
|
abortStale(idleMs: number): Promise<number>;
|
|
14
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"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"}
|
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
|
});
|
|
@@ -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
|
}
|
|
@@ -60,7 +60,7 @@ export default defineMiddleware<Message>({
|
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const reply = await handleChoice(
|
|
63
|
+
const reply = await handleChoice(services, message, session.id, parsed.choiceId);
|
|
64
64
|
if (reply) await context.input.$reply(reply);
|
|
65
65
|
},
|
|
66
66
|
});
|
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.4",
|
|
4
4
|
"description": "Zhin.js 猜拳游戏 — 石头剪刀布三局两胜 (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zhin.js/command": "1.0.
|
|
28
|
-
"@zhin.js/core": "1.
|
|
29
|
-
"@zhin.js/game-kit": "
|
|
30
|
-
"@zhin.js/middleware": "1.0.
|
|
31
|
-
"@zhin.js/plugin-runtime": "1.
|
|
27
|
+
"@zhin.js/command": "1.0.3",
|
|
28
|
+
"@zhin.js/core": "1.4.1",
|
|
29
|
+
"@zhin.js/game-kit": "2.0.1",
|
|
30
|
+
"@zhin.js/middleware": "1.0.3",
|
|
31
|
+
"@zhin.js/plugin-runtime": "1.1.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "^6.0.3",
|
package/src/game-flow.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
|
|
1
|
+
import { plainTextFromSendContent, recordGameOutcome, type GameMessageLike } from '@zhin.js/game-kit';
|
|
3
2
|
import type { RpsSessionRow } from './models.js';
|
|
4
3
|
import {
|
|
5
4
|
RPS_PREFIX,
|
|
@@ -11,41 +10,22 @@ 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
|
+
): string {
|
|
23
|
+
return plainTextFromSendContent(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:
|
|
28
|
+
message: GameMessageLike,
|
|
49
29
|
): Promise<string | undefined> {
|
|
50
30
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
51
31
|
const active = await services.getActiveByChannel(ch);
|
|
@@ -56,14 +36,12 @@ 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
47
|
): Promise<string | null> {
|
|
@@ -76,8 +54,7 @@ export async function handleChoice(
|
|
|
76
54
|
|
|
77
55
|
if (choiceId === 'restart') {
|
|
78
56
|
await services.updateSession(session.id, { status: 'aborted' });
|
|
79
|
-
|
|
80
|
-
return (await startGame(plugin, services, message)) ?? null;
|
|
57
|
+
return (await startGame(services, message)) ?? null;
|
|
81
58
|
}
|
|
82
59
|
|
|
83
60
|
const player = choiceId as RpsMove;
|
|
@@ -104,23 +81,19 @@ export async function handleChoice(
|
|
|
104
81
|
const updated = (await services.getById(session.id))!;
|
|
105
82
|
if (status === 'won') void recordGameOutcome(message, 'rps', 'won', playerWins * 10);
|
|
106
83
|
else if (status === 'lost') void recordGameOutcome(message, 'rps', 'lost');
|
|
107
|
-
|
|
108
|
-
return typeof text === 'string' ? text : null;
|
|
84
|
+
return renderView(updated, message, { player, bot, result });
|
|
109
85
|
}
|
|
110
86
|
|
|
111
87
|
export async function continueGame(
|
|
112
|
-
plugin: Plugin | null,
|
|
113
88
|
services: SessionService,
|
|
114
|
-
message:
|
|
89
|
+
message: GameMessageLike,
|
|
115
90
|
): Promise<string> {
|
|
116
91
|
const session = await services.getActiveForUser(
|
|
117
92
|
`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
|
|
118
93
|
message.$sender.id,
|
|
119
94
|
);
|
|
120
95
|
if (!session) return '你没有进行中的猜拳,发送「猜拳 开始」。';
|
|
121
|
-
|
|
122
|
-
if (typeof text === 'string') return text;
|
|
123
|
-
return '已刷新猜拳界面。';
|
|
96
|
+
return renderView(session, message);
|
|
124
97
|
}
|
|
125
98
|
|
|
126
99
|
export { RPS_PREFIX };
|
package/src/rps-command.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { channelKey } from '@zhin.js/game-kit';
|
|
1
|
+
import { channelKey, type GameMessageLike } from '@zhin.js/game-kit';
|
|
3
2
|
import { continueGame, startGame } from './game-flow.js';
|
|
4
3
|
import type { SessionService } from './session-service.js';
|
|
5
4
|
|
|
@@ -12,11 +11,10 @@ export const RPS_HELP = [
|
|
|
12
11
|
].join('\n');
|
|
13
12
|
|
|
14
13
|
export async function runRpsCommand(
|
|
15
|
-
plugin: Plugin | null,
|
|
16
14
|
services: SessionService,
|
|
17
|
-
message:
|
|
15
|
+
message: GameMessageLike,
|
|
18
16
|
action: string,
|
|
19
|
-
): Promise<string
|
|
17
|
+
): Promise<string> {
|
|
20
18
|
const ch = channelKey(message);
|
|
21
19
|
const userId = message.$sender.id;
|
|
22
20
|
|
|
@@ -31,8 +29,8 @@ export async function runRpsCommand(
|
|
|
31
29
|
return lines.join('\n');
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
if (action === 'start') return startGame(
|
|
35
|
-
if (action === 'continue') return continueGame(
|
|
32
|
+
if (action === 'start') return (await startGame(services, message)) ?? '';
|
|
33
|
+
if (action === 'continue') return continueGame(services, message);
|
|
36
34
|
|
|
37
35
|
if (action === 'quit') {
|
|
38
36
|
const row = await services.getActiveForUser(ch, userId);
|
|
@@ -43,12 +41,3 @@ export async function runRpsCommand(
|
|
|
43
41
|
|
|
44
42
|
return `未知子命令:${action}\n\n${RPS_HELP}`;
|
|
45
43
|
}
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import type { Database,
|
|
2
|
-
import { channelKey, generateSessionId, boardMessageMatches } from '@zhin.js/game-kit';
|
|
1
|
+
import type { Database, Models, RelatedModel } from '@zhin.js/core';
|
|
2
|
+
import { channelKey, generateSessionId, boardMessageMatches, type GameMessageLike } from '@zhin.js/game-kit';
|
|
3
3
|
import type { RpsSessionRow } from './models.js';
|
|
4
4
|
|
|
5
5
|
export type RpsDatabase = Database<unknown, Models, string>;
|
|
@@ -37,7 +37,7 @@ export class SessionService {
|
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
async createSession(message:
|
|
40
|
+
async createSession(message: GameMessageLike): Promise<RpsSessionRow> {
|
|
41
41
|
const now = Date.now();
|
|
42
42
|
const row: RpsSessionRow = {
|
|
43
43
|
id: generateSessionId(),
|