@zhin.js/plugin-rps 1.0.4 → 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.
@@ -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,7 +1,7 @@
1
- import { type GameMessageLike } from '@zhin.js/game-kit';
1
+ import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
2
  import { RPS_PREFIX } from './engine.js';
3
3
  import type { SessionService } from './session-service.js';
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>;
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>;
7
7
  export { RPS_PREFIX };
package/lib/game-flow.js CHANGED
@@ -1,4 +1,3 @@
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
3
  /**
@@ -7,7 +6,7 @@ import { buildRpsView } from './view.js';
7
6
  * and the choice middleware return fresh text each turn.
8
7
  */
9
8
  function renderView(session, message, lastRound) {
10
- return plainTextFromSendContent(buildRpsView(session, lastRound, message.$channel.type));
9
+ return buildRpsView(session, lastRound, message.$channel.type);
11
10
  }
12
11
  export async function startGame(services, message) {
13
12
  const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
@@ -34,6 +33,10 @@ export async function handleChoice(services, message, sessionId, choiceId) {
34
33
  await services.updateSession(session.id, { status: 'aborted' });
35
34
  return (await startGame(services, message)) ?? null;
36
35
  }
36
+ if (choiceId === 'quit') {
37
+ await services.updateSession(session.id, { status: 'aborted' });
38
+ return renderView((await services.getById(session.id)), message);
39
+ }
37
40
  const player = choiceId;
38
41
  if (!['rock', 'paper', 'scissors'].includes(player))
39
42
  return '无效出拳。';
@@ -57,10 +60,6 @@ export async function handleChoice(services, message, sessionId, choiceId) {
57
60
  status,
58
61
  });
59
62
  const updated = (await services.getById(session.id));
60
- if (status === 'won')
61
- void recordGameOutcome(message, 'rps', 'won', playerWins * 10);
62
- else if (status === 'lost')
63
- void recordGameOutcome(message, 'rps', 'lost');
64
63
  return renderView(updated, message, { player, bot, result });
65
64
  }
66
65
  export async function continueGame(services, message) {
@@ -1 +1 @@
1
- {"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"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"}
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 { 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;AAC5E,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE7E;;;;GAIG"}
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
@@ -15,7 +15,6 @@ declare module '@zhin.js/core' {
15
15
  bot_wins: number;
16
16
  round: number;
17
17
  status: RpsSessionStatus;
18
- board_message_id: string;
19
18
  updated_at: number;
20
19
  created_at: number;
21
20
  };
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":"AA6BA,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,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC/C,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"}
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"}
@@ -1,4 +1,4 @@
1
- import { type GameMessageLike } from '@zhin.js/game-kit';
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(services: SessionService, message: GameMessageLike, action: string): Promise<string>;
4
+ export declare function runRpsCommand(services: SessionService, message: GameMessageLike, action: string): Promise<GameReply>;
@@ -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 — 帮助',
@@ -29,8 +29,7 @@ export async function runRpsCommand(services, message, action) {
29
29
  const row = await services.getActiveForUser(ch, userId);
30
30
  if (!row)
31
31
  return '你没有进行中的猜拳。';
32
- await services.updateSession(row.id, { status: 'aborted' });
33
- return '已放弃猜拳对局。';
32
+ return (await handleChoice(services, message, row.id, 'quit')) ?? '';
34
33
  }
35
34
  return `未知子命令:${action}\n\n${RPS_HELP}`;
36
35
  }
@@ -1 +1 @@
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"}
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"}
@@ -1,16 +1,9 @@
1
1
  import type { Database, Models } from '@zhin.js/core';
2
- import { type GameMessageLike } from '@zhin.js/game-kit';
2
+ import { BaseSessionService, type GameMessageLike } from '@zhin.js/game-kit';
3
3
  import type { RpsSessionRow } from './models.js';
4
4
  export type RpsDatabase = Database<unknown, Models, string>;
5
- export declare class SessionService {
6
- private readonly db;
5
+ export declare class SessionService extends BaseSessionService<RpsSessionRow> {
7
6
  constructor(db: RpsDatabase);
8
- getActiveByChannel(channel: string): Promise<RpsSessionRow | null>;
9
- getActiveForUser(channel: string, userId: string): Promise<RpsSessionRow | null>;
10
- getById(id: string): Promise<RpsSessionRow | null>;
11
- getActiveByBoardMessageId(messageId: string): Promise<RpsSessionRow | null>;
12
7
  createSession(message: GameMessageLike): Promise<RpsSessionRow>;
13
- updateSession(id: string, patch: Partial<RpsSessionRow>): Promise<void>;
14
- abortStale(idleMs: number): Promise<number>;
15
8
  }
16
9
  export declare function createServices(db: RpsDatabase): SessionService;
@@ -1,37 +1,25 @@
1
- import { channelKey, generateSessionId, boardMessageMatches } from '@zhin.js/game-kit';
2
- function getModel(db) {
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
- this.db = db;
12
- }
13
- async getActiveByChannel(channel) {
14
- const rows = await getModel(this.db).findAll({ channel_key: channel, status: 'active' });
15
- return rows[0] ?? null;
16
- }
17
- async getActiveForUser(channel, userId) {
18
- const row = await this.getActiveByChannel(channel);
19
- if (!row || row.player_id !== userId)
20
- return null;
21
- return row;
22
- }
23
- async getById(id) {
24
- return getModel(this.db).findOne({ id });
25
- }
26
- async getActiveByBoardMessageId(messageId) {
27
- if (!messageId)
28
- return null;
29
- const rows = await getModel(this.db).findAll({});
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
- await getModel(this.db).create(row);
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":"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"}
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
- : ['rock', 'paper', 'scissors'].map((m) => ({
35
- id: m,
36
- label: MOVE_LABELS[m],
37
- style: 'secondary',
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: '回复数字出拳(1石头 2布 3剪刀)',
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;IACpE,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,CAAE,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvD,EAAE,EAAE,CAAC;YACL,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC;YACrB,KAAK,EAAE,WAAoB;SAC5B,CAAC,CAAC,CAAC;IAER,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,oBAAoB;QAClC,kBAAkB,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;QACtD,WAAW;KACZ,CAAC,CAAC;AACL,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
+ });
@@ -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
+ });
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-rps",
3
- "version": "1.0.4",
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.ts",
17
+ "plugin.js",
18
18
  "schema.json",
19
19
  "commands",
20
20
  "middlewares",
@@ -24,10 +24,10 @@
24
24
  ],
25
25
  "license": "MIT",
26
26
  "dependencies": {
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",
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
31
  "@zhin.js/plugin-runtime": "1.1.1"
32
32
  },
33
33
  "devDependencies": {
@@ -49,7 +49,7 @@
49
49
  "zhin": {
50
50
  "protocol": 1,
51
51
  "type": "plugin",
52
- "entry": "./plugin.ts",
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,4 +1,4 @@
1
- import { plainTextFromSendContent, recordGameOutcome, type GameMessageLike } from '@zhin.js/game-kit';
1
+ import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
2
  import type { RpsSessionRow } from './models.js';
3
3
  import {
4
4
  RPS_PREFIX,
@@ -19,14 +19,14 @@ function renderView(
19
19
  session: RpsSessionRow,
20
20
  message: GameMessageLike,
21
21
  lastRound?: { player: RpsMove; bot: RpsMove; result: 0 | 1 | 2 },
22
- ): string {
23
- return plainTextFromSendContent(buildRpsView(session, lastRound, message.$channel.type));
22
+ ): GameReply {
23
+ return buildRpsView(session, lastRound, message.$channel.type);
24
24
  }
25
25
 
26
26
  export async function startGame(
27
27
  services: SessionService,
28
28
  message: GameMessageLike,
29
- ): Promise<string | undefined> {
29
+ ): Promise<GameReply | undefined> {
30
30
  const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
31
31
  const active = await services.getActiveByChannel(ch);
32
32
  if (active) {
@@ -44,7 +44,7 @@ export async function handleChoice(
44
44
  message: GameMessageLike,
45
45
  sessionId: string,
46
46
  choiceId: string,
47
- ): Promise<string | null> {
47
+ ): Promise<GameReply | null> {
48
48
  const session = await services.getById(sessionId);
49
49
  if (!session) return '对局不存在。';
50
50
  if (session.status !== 'active' && choiceId !== 'restart') {
@@ -56,6 +56,10 @@ export async function handleChoice(
56
56
  await services.updateSession(session.id, { status: 'aborted' });
57
57
  return (await startGame(services, message)) ?? null;
58
58
  }
59
+ if (choiceId === 'quit') {
60
+ await services.updateSession(session.id, { status: 'aborted' });
61
+ return renderView((await services.getById(session.id))!, message);
62
+ }
59
63
 
60
64
  const player = choiceId as RpsMove;
61
65
  if (!['rock', 'paper', 'scissors'].includes(player)) return '无效出拳。';
@@ -79,15 +83,13 @@ export async function handleChoice(
79
83
  });
80
84
 
81
85
  const updated = (await services.getById(session.id))!;
82
- if (status === 'won') void recordGameOutcome(message, 'rps', 'won', playerWins * 10);
83
- else if (status === 'lost') void recordGameOutcome(message, 'rps', 'lost');
84
86
  return renderView(updated, message, { player, bot, result });
85
87
  }
86
88
 
87
89
  export async function continueGame(
88
90
  services: SessionService,
89
91
  message: GameMessageLike,
90
- ): Promise<string> {
92
+ ): Promise<GameReply> {
91
93
  const session = await services.getActiveForUser(
92
94
  `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
93
95
  message.$sender.id,
package/src/index.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
  /**
6
5
  * 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
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
  });
@@ -1,5 +1,9 @@
1
- import { channelKey, type GameMessageLike } from '@zhin.js/game-kit';
2
- import { continueGame, startGame } from './game-flow.js';
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';
3
7
  import type { SessionService } from './session-service.js';
4
8
 
5
9
  export const RPS_HELP = [
@@ -14,7 +18,7 @@ export async function runRpsCommand(
14
18
  services: SessionService,
15
19
  message: GameMessageLike,
16
20
  action: string,
17
- ): Promise<string> {
21
+ ): Promise<GameReply> {
18
22
  const ch = channelKey(message);
19
23
  const userId = message.$sender.id;
20
24
 
@@ -35,8 +39,7 @@ export async function runRpsCommand(
35
39
  if (action === 'quit') {
36
40
  const row = await services.getActiveForUser(ch, userId);
37
41
  if (!row) return '你没有进行中的猜拳。';
38
- await services.updateSession(row.id, { status: 'aborted' });
39
- return '已放弃猜拳对局。';
42
+ return (await handleChoice(services, message, row.id, 'quit')) ?? '';
40
43
  }
41
44
 
42
45
  return `未知子命令:${action}\n\n${RPS_HELP}`;
@@ -1,40 +1,36 @@
1
- import type { Database, Models, RelatedModel } from '@zhin.js/core';
2
- import { channelKey, generateSessionId, boardMessageMatches, type GameMessageLike } from '@zhin.js/game-kit';
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
- function getModel(db: RpsDatabase) {
8
- const model = db.models.get('rps_sessions');
9
- if (!model) throw new Error('rps_sessions not registered');
10
- return model as RelatedModel<unknown, Models, 'rps_sessions'>;
11
- }
12
-
13
- export class SessionService {
14
- constructor(private readonly db: RpsDatabase) {}
15
-
16
- async getActiveByChannel(channel: string): Promise<RpsSessionRow | null> {
17
- const rows = await getModel(this.db).findAll({ channel_key: channel, status: 'active' });
18
- return rows[0] ?? null;
19
- }
20
-
21
- async getActiveForUser(channel: string, userId: string): Promise<RpsSessionRow | null> {
22
- const row = await this.getActiveByChannel(channel);
23
- if (!row || row.player_id !== userId) return null;
24
- return row;
25
- }
26
-
27
- async getById(id: string): Promise<RpsSessionRow | null> {
28
- return getModel(this.db).findOne({ id });
29
- }
30
-
31
- async getActiveByBoardMessageId(messageId: string): Promise<RpsSessionRow | null> {
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;
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
+ });
38
34
  }
39
35
 
40
36
  async createSession(message: GameMessageLike): Promise<RpsSessionRow> {
@@ -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
- await getModel(this.db).create(row);
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
- : (['rock', 'paper', 'scissors'] as RpsMove[]).map((m) => ({
43
- id: m,
44
- label: MOVE_LABELS[m],
45
- style: 'secondary' as const,
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: '回复数字出拳(1石头 2布 3剪刀)',
59
+ fallbackHint: '回复数字:1石头 2布 3剪刀 4放弃',
56
60
  interactionProfile: terminal ? 'terminal' : 'gameplay',
57
61
  channelType,
58
62
  });
@@ -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
@@ -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
- }