@zhin.js/plugin-blackjack 0.0.3 → 0.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,26 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineCommand } from '@zhin.js/command';
3
+ import { messageFromCommandInput } from '@zhin.js/game-kit';
4
+ import { BJ_HELP, runBjCommand } from "../../lib/bj-command.js";
5
+ import { resolveGameServices } from "../../lib/runtime-store.js";
6
+ function normalizeBjAction(raw) {
7
+ const a = raw.trim().toLowerCase() || 'help';
8
+ if (a === '帮助')
9
+ return 'help';
10
+ if (a === '开始')
11
+ return 'start';
12
+ if (a === '继续')
13
+ return 'continue';
14
+ return a;
15
+ }
16
+ export default defineCommand({
17
+ description: 'Blackjack',
18
+ async execute({ params, input, use, owner }) {
19
+ const action = normalizeBjAction(String(params.action ?? ''));
20
+ if (!action || action === 'help')
21
+ return BJ_HELP;
22
+ const services = resolveGameServices({ use, owner });
23
+ const message = messageFromCommandInput(input);
24
+ return runBjCommand(services, message, action);
25
+ },
26
+ });
@@ -1,6 +1,6 @@
1
1
  import { defineCommand } from '@zhin.js/command';
2
2
  import { messageFromCommandInput } from '@zhin.js/game-kit';
3
- import { BJ_HELP, runBjCommandText } from '../../src/bj-command.js';
3
+ import { BJ_HELP, runBjCommand } from '../../src/bj-command.js';
4
4
  import { resolveGameServices } from '../../src/runtime-store.js';
5
5
 
6
6
  function normalizeBjAction(raw: string): string {
@@ -18,6 +18,6 @@ export default defineCommand({
18
18
  if (!action || action === 'help') return BJ_HELP;
19
19
  const services = resolveGameServices({ use, owner });
20
20
  const message = messageFromCommandInput(input);
21
- return runBjCommandText(services, message, action);
21
+ return runBjCommand(services, message, action);
22
22
  },
23
23
  });
@@ -1,6 +1,4 @@
1
- import type { Message, Plugin } from '@zhin.js/core';
1
+ import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
2
  import type { SessionService } from './session-service.js';
3
3
  export declare const BJ_HELP: string;
4
- export declare function runBjCommand(plugin: Plugin | null, services: SessionService, message: Message<any>, action: string): Promise<string | undefined>;
5
- /** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
6
- export declare function runBjCommandText(services: SessionService, message: Message<any>, action: string): Promise<string>;
4
+ export declare function runBjCommand(services: SessionService, message: GameMessageLike, action: string): Promise<GameReply>;
package/lib/bj-command.js CHANGED
@@ -1,4 +1,4 @@
1
- import { continueGame, startGame } from './game-flow.js';
1
+ import { continueGame, handleChoice, startGame } from './game-flow.js';
2
2
  export const BJ_HELP = [
3
3
  '🃏 **21 点**',
4
4
  '',
@@ -6,20 +6,23 @@ export const BJ_HELP = [
6
6
  '',
7
7
  '• `/21点 开始` — 新局',
8
8
  '• `/21点 继续` — 刷新界面',
9
+ '• `/21点 放弃` — 结束本局',
9
10
  '• 对局中点击 **要牌** / **停牌**',
10
11
  ].join('\n');
11
- export async function runBjCommand(plugin, services, message, action) {
12
+ export async function runBjCommand(services, message, action) {
12
13
  const a = action.trim().toLowerCase() || 'help';
13
14
  if (a === 'help' || a === '帮助')
14
15
  return BJ_HELP;
15
16
  if (a === 'start' || a === '开始')
16
- return startGame(plugin, services, message);
17
+ return (await startGame(services, message)) ?? '';
17
18
  if (a === 'continue' || a === '继续')
18
- return continueGame(plugin, services, message);
19
+ return continueGame(services, message);
20
+ if (a === 'quit' || a === '放弃') {
21
+ const session = await services.getActiveForUser(`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`, message.$sender.id);
22
+ if (!session)
23
+ return '你没有进行中的 21 点。';
24
+ return (await handleChoice(services, message, session.id, 'quit')) ?? '';
25
+ }
19
26
  return `未知操作「${action}」。发送 \`/21点 帮助\`。`;
20
27
  }
21
- /** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
22
- export async function runBjCommandText(services, message, action) {
23
- return (await runBjCommand(null, services, message, action)) ?? '';
24
- }
25
28
  //# sourceMappingURL=bj-command.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bj-command.js","sourceRoot":"","sources":["../src/bj-command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGzD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,aAAa;IACb,EAAE;IACF,4BAA4B;IAC5B,EAAE;IACF,kBAAkB;IAClB,oBAAoB;IACpB,yBAAyB;CAC1B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,QAAwB,EACxB,OAAqB,EACrB,MAAc;IAEd,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,MAAM,CAAC;IAChD,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IAC/C,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7E,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnF,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AAC3C,CAAC;AAED,iEAAiE;AACjE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAwB,EACxB,OAAqB,EACrB,MAAc;IAEd,OAAO,CAAC,MAAM,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AACrE,CAAC"}
1
+ {"version":3,"file":"bj-command.js","sourceRoot":"","sources":["../src/bj-command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGvE,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,aAAa;IACb,EAAE;IACF,4BAA4B;IAC5B,EAAE;IACF,kBAAkB;IAClB,oBAAoB;IACpB,oBAAoB;IACpB,yBAAyB;CAC1B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAwB,EACxB,OAAwB,EACxB,MAAc;IAEd,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,MAAM,CAAC;IAChD,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IAC/C,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,CAAC,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACnF,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC3E,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/B,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;QACF,IAAI,CAAC,OAAO;YAAE,OAAO,eAAe,CAAC;QACrC,OAAO,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3E,CAAC;IACD,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AAC3C,CAAC"}
@@ -1,7 +1,7 @@
1
- import type { Message, Plugin } from '@zhin.js/core';
1
+ import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
2
  import { BJ_PREFIX, handValue, TARGET } from './engine.js';
3
3
  import { type SessionService } from './session-service.js';
4
- export declare function startGame(plugin: Plugin | null, services: SessionService, message: Message<any>): Promise<string | undefined>;
5
- export declare function continueGame(plugin: Plugin | null, services: SessionService, message: Message<any>): Promise<string>;
6
- export declare function handleChoice(plugin: Plugin | null, services: SessionService, message: Message<any>, sessionId: string, choiceId: string): Promise<string | null>;
4
+ export declare function startGame(services: SessionService, message: GameMessageLike): Promise<GameReply | undefined>;
5
+ export declare function continueGame(services: SessionService, message: GameMessageLike): Promise<GameReply>;
6
+ export declare function handleChoice(services: SessionService, message: GameMessageLike, sessionId: string, choiceId: string): Promise<GameReply | null>;
7
7
  export { BJ_PREFIX, handValue, TARGET };
package/lib/game-flow.js CHANGED
@@ -1,40 +1,18 @@
1
- import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
2
1
  import { BJ_PREFIX, compareHands, dealerShouldHit, handValue, TARGET, } from './engine.js';
3
2
  import { parseCards, parseDeck } from './session-service.js';
4
3
  import { buildBjView, naturalOutcome, playerBust } from './view.js';
5
- async function sendOrEditView(plugin, services, message, session, terminal = false, revealDealer = false) {
6
- const content = buildBjView(session, terminal, revealDealer, message.$channel.type);
7
- if (!plugin)
8
- return plainTextFromSendContent(content);
9
- const adapter = plugin.root.inject(message.$adapter);
10
- if (session.board_message_id) {
11
- const msgId = await adapter.editMessage({
12
- messageId: session.board_message_id,
13
- context: String(message.$adapter),
14
- endpoint: message.$endpoint,
15
- id: message.$channel.id,
16
- type: message.$channel.type,
17
- content,
18
- });
19
- if (msgId !== session.board_message_id) {
20
- await services.updateSession(session.id, { board_message_id: msgId });
21
- }
22
- return;
23
- }
24
- const msgId = await message.$reply?.(content);
25
- if (msgId)
26
- 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, terminal = false, revealDealer = false) {
10
+ return buildBjView(session, terminal, revealDealer, message.$channel.type);
27
11
  }
28
- async function finishRound(plugin, services, message, session, status) {
12
+ async function finishRound(services, message, session, status) {
29
13
  await services.updateSession(session.id, { status });
30
14
  const updated = (await services.getById(session.id));
31
- if (status === 'won')
32
- void recordGameOutcome(message, 'blackjack', 'won', 30);
33
- else if (status === 'lost')
34
- void recordGameOutcome(message, 'blackjack', 'lost');
35
- else if (status === 'draw')
36
- void recordGameOutcome(message, 'blackjack', 'draw');
37
- return sendOrEditView(plugin, services, message, updated, true, true);
15
+ return renderView(updated, message, true, true);
38
16
  }
39
17
  async function dealerPlay(deck, dealer) {
40
18
  const hand = [...dealer];
@@ -46,7 +24,7 @@ async function dealerPlay(deck, dealer) {
46
24
  }
47
25
  return hand;
48
26
  }
49
- export async function startGame(plugin, services, message) {
27
+ export async function startGame(services, message) {
50
28
  const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
51
29
  const active = await services.getActiveByChannel(ch);
52
30
  if (active) {
@@ -58,22 +36,17 @@ export async function startGame(plugin, services, message) {
58
36
  const session = await services.createSession(message);
59
37
  const natural = naturalOutcome(session);
60
38
  if (natural) {
61
- const text = await finishRound(plugin, services, message, session, natural);
62
- return typeof text === 'string' ? text : undefined;
39
+ return finishRound(services, message, session, natural);
63
40
  }
64
- const text = await sendOrEditView(plugin, services, message, session);
65
- return typeof text === 'string' ? text : undefined;
41
+ return renderView(session, message);
66
42
  }
67
- export async function continueGame(plugin, services, message) {
43
+ export async function continueGame(services, message) {
68
44
  const session = await services.getActiveForUser(`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`, message.$sender.id);
69
45
  if (!session)
70
46
  return '你没有进行中的 21 点,发送「21点 开始」。';
71
- const text = await sendOrEditView(plugin, services, message, session);
72
- if (typeof text === 'string')
73
- return text;
74
- return '已刷新 21 点界面。';
47
+ return renderView(session, message);
75
48
  }
76
- export async function handleChoice(plugin, services, message, sessionId, choiceId) {
49
+ export async function handleChoice(services, message, sessionId, choiceId) {
77
50
  const session = await services.getById(sessionId);
78
51
  if (!session)
79
52
  return '对局不存在。';
@@ -81,11 +54,14 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
81
54
  return '这是别人的对局。';
82
55
  if (session.status !== 'active' && choiceId === 'restart') {
83
56
  await services.updateSession(session.id, { status: 'aborted' });
84
- // text-only 模式(plugin===null)下 startGame 的唯一输出就是返回文本
85
- return (await startGame(plugin, services, message)) ?? null;
57
+ return (await startGame(services, message)) ?? null;
86
58
  }
87
59
  if (session.status !== 'active')
88
60
  return '对局已结束,请点击再来一局。';
61
+ if (choiceId === 'quit') {
62
+ await services.updateSession(session.id, { status: 'aborted' });
63
+ return renderView((await services.getById(session.id)), message, true, true);
64
+ }
89
65
  const deck = parseDeck(session.deck_json);
90
66
  let player = parseCards(session.player_cards_json);
91
67
  let dealer = parseCards(session.dealer_cards_json);
@@ -99,12 +75,10 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
99
75
  player_cards_json: JSON.stringify(player),
100
76
  });
101
77
  if (playerBust({ ...session, player_cards_json: JSON.stringify(player) })) {
102
- const text = await finishRound(plugin, services, message, session, 'lost');
103
- return typeof text === 'string' ? text : null;
78
+ return finishRound(services, message, session, 'lost');
104
79
  }
105
80
  const updated = (await services.getById(session.id));
106
- const text = await sendOrEditView(plugin, services, message, updated);
107
- return typeof text === 'string' ? text : null;
81
+ return renderView(updated, message);
108
82
  }
109
83
  if (choiceId !== 'stand')
110
84
  return '无效操作。';
@@ -115,8 +89,7 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
115
89
  });
116
90
  const outcome = compareHands(player, dealer);
117
91
  const status = outcome === 'won' ? 'won' : outcome === 'lost' ? 'lost' : 'draw';
118
- const text = await finishRound(plugin, services, message, session, status);
119
- return typeof text === 'string' ? text : null;
92
+ return finishRound(services, message, session, status);
120
93
  }
121
94
  export { BJ_PREFIX, handValue, TARGET };
122
95
  //# sourceMappingURL=game-flow.js.map
@@ -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":"AACA,OAAO,EACL,SAAS,EACT,YAAY,EACZ,eAAe,EACf,SAAS,EACT,MAAM,GACP,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAuB,MAAM,sBAAsB,CAAC;AAElF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEpE;;;;GAIG;AACH,SAAS,UAAU,CACjB,OAAqB,EACrB,OAAwB,EACxB,QAAQ,GAAG,KAAK,EAChB,YAAY,GAAG,KAAK;IAEpB,OAAO,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC7E,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,QAAwB,EACxB,OAAwB,EACxB,OAAqB,EACrB,MAA8B;IAE9B,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAE,CAAC;IACtD,OAAO,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,IAAc,EACd,MAAgB;IAEhB,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;IACzB,OAAO,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI;YAAE,MAAM;QACjB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,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,4BAA4B,CAAC;QACtC,CAAC;QACD,OAAO,OAAO,MAAM,CAAC,WAAW,YAAY,CAAC;IAC/C,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACtC,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,0BAA0B,CAAC;IAChD,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,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,UAAU,CAAC;IAEhE,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC1D,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;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAC;IAEzD,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,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACnD,IAAI,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAEnD,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI;YAAE,OAAO,OAAO,CAAC;QAC1B,MAAM,GAAG,CAAC,GAAG,MAAM,EAAE,IAAI,CAAC,CAAC;QAC3B,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE;YACvC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC/B,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;SAC1C,CAAC,CAAC;QACH,IAAI,UAAU,CAAC,EAAE,GAAG,OAAO,EAAE,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1E,OAAO,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAE,CAAC;QACtD,OAAO,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC;IAEzC,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE;QACvC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC/B,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;KAC1C,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,MAAM,GACV,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IACnE,OAAO,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AACzD,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC"}
package/lib/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { BJ_HELP } from './bj-command.js';
2
2
  export { gameServicesToken, resolveGameServices } from './runtime-store.js';
3
- export { createInMemoryBjDb, mountBjMemoryServices } 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 { BJ_HELP } from './bj-command.js';
2
2
  export { gameServicesToken, resolveGameServices } from './runtime-store.js';
3
- export { createInMemoryBjDb, mountBjMemoryServices } 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,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE3E;;;;GAIG"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE5E;;;;GAIG"}
package/lib/models.d.ts CHANGED
@@ -14,7 +14,6 @@ declare module '@zhin.js/core' {
14
14
  deck_json: string;
15
15
  player_cards_json: string;
16
16
  dealer_cards_json: string;
17
- board_message_id: string;
18
17
  status: BjSessionStatus;
19
18
  updated_at: number;
20
19
  created_at: number;
package/lib/models.js CHANGED
@@ -11,7 +11,6 @@ export function defineHostTables(db) {
11
11
  deck_json: { type: 'text', default: '[]' },
12
12
  player_cards_json: { type: 'text', default: '[]' },
13
13
  dealer_cards_json: { type: 'text', default: '[]' },
14
- board_message_id: { type: 'text', default: '' },
15
14
  status: { type: 'text', default: 'active' },
16
15
  updated_at: { type: 'integer', default: 0 },
17
16
  created_at: { type: 'integer', default: 0 },
package/lib/models.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AA6BA,MAAM,UAAU,gBAAgB,CAC9B,EAA2E;IAE3E,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE;QACvB,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,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAC1C,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAClD,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAClD,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC/C,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
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AA4BA,MAAM,UAAU,gBAAgB,CAC9B,EAA2E;IAE3E,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE;QACvB,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,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAC1C,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAClD,iBAAiB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAClD,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,16 +1,10 @@
1
- import type { Database, Message, Models } from '@zhin.js/core';
1
+ import type { Database, Models } from '@zhin.js/core';
2
+ import { BaseSessionService, type GameMessageLike } from '@zhin.js/game-kit';
2
3
  import type { BjSessionRow } from './models.js';
3
4
  export type BjDatabase = Database<unknown, Models, string>;
4
- export declare class SessionService {
5
- private readonly db;
5
+ export declare class SessionService extends BaseSessionService<BjSessionRow> {
6
6
  constructor(db: BjDatabase);
7
- createSession(message: Message<any>): Promise<BjSessionRow>;
8
- getById(id: string): Promise<BjSessionRow | null>;
9
- getActiveForUser(channelKeyValue: string, userId: string): Promise<BjSessionRow | null>;
10
- getActiveByChannel(channelKeyValue: string): Promise<BjSessionRow | null>;
11
- getActiveByBoardMessageId(messageId: string): Promise<BjSessionRow | null>;
12
- updateSession(id: string, patch: Partial<BjSessionRow>): Promise<void>;
13
- abortStale(maxAgeMs: number): Promise<number>;
7
+ createSession(message: GameMessageLike): Promise<BjSessionRow>;
14
8
  }
15
9
  export declare function createServices(db: BjDatabase): SessionService;
16
10
  export declare function parseCards(json: string | string[]): string[];
@@ -1,15 +1,23 @@
1
- import { channelKey, generateSessionId } from '@zhin.js/game-kit';
1
+ import { BaseSessionService, channelKey, generateSessionId, } from '@zhin.js/game-kit';
2
2
  import { freshDeck } from './engine.js';
3
- function getModel(db) {
4
- const model = db.models.get('bj_sessions');
5
- if (!model)
6
- throw new Error('bj_sessions not registered');
7
- return model;
8
- }
9
- export class SessionService {
10
- db;
3
+ export class SessionService extends BaseSessionService {
11
4
  constructor(db) {
12
- this.db = db;
5
+ super(db, {
6
+ gameId: 'blackjack',
7
+ table: 'bj_sessions',
8
+ userFields: ['player_id'],
9
+ projectOutcomes: (session) => [
10
+ ...(session.status === 'won' || session.status === 'lost'
11
+ || session.status === 'draw'
12
+ ? [{
13
+ userId: session.player_id,
14
+ userName: session.player_name,
15
+ result: session.status,
16
+ score: session.status === 'won' ? 30 : undefined,
17
+ }]
18
+ : []),
19
+ ],
20
+ });
13
21
  }
14
22
  async createSession(message) {
15
23
  const deck = freshDeck();
@@ -28,54 +36,11 @@ export class SessionService {
28
36
  deck_json: JSON.stringify(deck),
29
37
  player_cards_json: JSON.stringify(playerCards),
30
38
  dealer_cards_json: JSON.stringify(dealerCards),
31
- board_message_id: '',
32
39
  status: 'active',
33
40
  updated_at: now,
34
41
  created_at: now,
35
42
  };
36
- await getModel(this.db).create(row);
37
- return row;
38
- }
39
- async getById(id) {
40
- return getModel(this.db).findOne({ id });
41
- }
42
- async getActiveForUser(channelKeyValue, userId) {
43
- const rows = await getModel(this.db).findAll({
44
- channel_key: channelKeyValue,
45
- player_id: userId,
46
- status: 'active',
47
- });
48
- return rows[0] ?? null;
49
- }
50
- async getActiveByChannel(channelKeyValue) {
51
- const rows = await getModel(this.db).findAll({
52
- channel_key: channelKeyValue,
53
- status: 'active',
54
- });
55
- return rows[0] ?? null;
56
- }
57
- async getActiveByBoardMessageId(messageId) {
58
- const rows = await getModel(this.db).findAll({
59
- board_message_id: messageId,
60
- status: 'active',
61
- });
62
- return rows[0] ?? null;
63
- }
64
- async updateSession(id, patch) {
65
- await getModel(this.db).updateWhere({ id }, { ...patch, updated_at: Date.now() });
66
- }
67
- async abortStale(maxAgeMs) {
68
- const cutoff = Date.now() - maxAgeMs;
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;
43
+ return this.createRow(row);
79
44
  }
80
45
  }
81
46
  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":"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"}
package/lib/view.js CHANGED
@@ -16,8 +16,10 @@ export function buildBjView(session, terminal = false, revealDealer = false, cha
16
16
  lines.push('🎉 **你赢了!**');
17
17
  else if (session.status === 'lost')
18
18
  lines.push('💀 **你输了**');
19
- else
19
+ else if (session.status === 'draw')
20
20
  lines.push('🤝 **平局**');
21
+ else
22
+ lines.push('🏳️ **本局已放弃。**');
21
23
  lines.push('', '点击「再来一局」或发送 `/21点 开始`');
22
24
  }
23
25
  else {
@@ -28,6 +30,7 @@ export function buildBjView(session, terminal = false, revealDealer = false, cha
28
30
  : [
29
31
  { id: 'hit', label: '➕ 要牌', style: 'primary' },
30
32
  { id: 'stand', label: '✋ 停牌', style: 'secondary' },
33
+ { id: 'quit', label: '🏳️ 放弃', style: 'danger' },
31
34
  ];
32
35
  return buildChoiceKeyboard({
33
36
  gamePrefix: BJ_PREFIX,
@@ -35,7 +38,7 @@ export function buildBjView(session, terminal = false, revealDealer = false, cha
35
38
  narrative: lines.join('\n'),
36
39
  choices,
37
40
  terminal,
38
- fallbackHint: terminal ? '回复 1 再来一局' : '1 要牌 · 2 停牌',
41
+ fallbackHint: terminal ? '回复 1 再来一局' : '1 要牌 · 2 停牌 · 3 放弃',
39
42
  interactionProfile: terminal ? 'terminal' : 'gameplay',
40
43
  channelType,
41
44
  });
package/lib/view.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineGameCommandAliasMiddleware, messageFromCommandInput } from '@zhin.js/game-kit';
3
+ import { BJ_HELP, runBjCommand } from "../lib/bj-command.js";
4
+ import { resolveGameServices } from "../lib/runtime-store.js";
5
+ function normalizeBjAction(raw) {
6
+ const a = raw.trim().toLowerCase() || 'help';
7
+ if (a === '帮助')
8
+ return 'help';
9
+ if (a === '开始')
10
+ return 'start';
11
+ if (a === '继续')
12
+ return 'continue';
13
+ return a;
14
+ }
15
+ export default defineGameCommandAliasMiddleware({
16
+ aliases: ['21点', 'bj'],
17
+ async run(action, input, context) {
18
+ const normalized = normalizeBjAction(String(action ?? ''));
19
+ if (!normalized || normalized === 'help')
20
+ return BJ_HELP;
21
+ const services = resolveGameServices(context);
22
+ const message = messageFromCommandInput(input);
23
+ return runBjCommand(services, message, normalized);
24
+ },
25
+ });
@@ -1,5 +1,5 @@
1
1
  import { defineGameCommandAliasMiddleware, messageFromCommandInput } from '@zhin.js/game-kit';
2
- import { BJ_HELP, runBjCommandText } from '../src/bj-command.js';
2
+ import { BJ_HELP, runBjCommand } from '../src/bj-command.js';
3
3
  import { resolveGameServices } from '../src/runtime-store.js';
4
4
 
5
5
  function normalizeBjAction(raw: string): string {
@@ -17,6 +17,6 @@ export default defineGameCommandAliasMiddleware({
17
17
  if (!normalized || normalized === 'help') return BJ_HELP;
18
18
  const services = resolveGameServices(context);
19
19
  const message = messageFromCommandInput(input);
20
- return runBjCommandText(services, message, normalized);
20
+ return runBjCommand(services, message, normalized);
21
21
  },
22
22
  });
@@ -0,0 +1,73 @@
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 { BJ_PREFIX, handleChoice } from "../lib/game-flow.js";
6
+ /**
7
+ * 文本入口:按钮 payload(`bj:session:hit|stand|restart`)与数字 fallback
8
+ * (『1 要牌 2 停牌』、终局『1 再来一局』,对应 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(`${BJ_PREFIX}:`)) {
24
+ const parsed = parseChoicePayload(payloadFromText, BJ_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
+ // 不按 status 过滤取本频道本人最近一局:终局 session 也要能命中 restart 分支
38
+ const session = await services.getLatestForUser(ch, message.$sender.id);
39
+ if (!session) {
40
+ await next();
41
+ return;
42
+ }
43
+ if (session.status !== 'active') {
44
+ const map = buildChoiceFallbackMap(BJ_PREFIX, session.id, [
45
+ { id: 'restart', label: '再来一局', keepEnabledWhenTerminal: true },
46
+ ]);
47
+ const payload = resolveGameTextPayload(raw, map);
48
+ const parsed = payload ? parseChoicePayload(payload, BJ_PREFIX) : null;
49
+ if (parsed?.choiceId === 'restart') {
50
+ const reply = await handleChoice(services, message, parsed.sessionId, 'restart');
51
+ if (reply)
52
+ await context.input.$reply(reply);
53
+ return;
54
+ }
55
+ await next();
56
+ return;
57
+ }
58
+ const map = buildChoiceFallbackMap(BJ_PREFIX, session.id, [
59
+ { id: 'hit', label: '要牌' },
60
+ { id: 'stand', label: '停牌' },
61
+ { id: 'quit', label: '放弃' },
62
+ ]);
63
+ const payload = resolveGameTextPayload(raw, map);
64
+ const parsed = payload ? parseChoicePayload(payload, BJ_PREFIX) : null;
65
+ if (!parsed || parsed.sessionId !== session.id) {
66
+ await next();
67
+ return;
68
+ }
69
+ const reply = await handleChoice(services, message, session.id, parsed.choiceId);
70
+ if (reply)
71
+ await context.input.$reply(reply);
72
+ },
73
+ });
@@ -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(null, services, message, parsed.sessionId, parsed.choiceId);
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
  }
@@ -42,7 +42,8 @@ export default defineMiddleware<Message>({
42
42
  return;
43
43
  }
44
44
 
45
- const session = await services.getActiveForUser(ch, message.$sender.id);
45
+ // 不按 status 过滤取本频道本人最近一局:终局 session 也要能命中 restart 分支
46
+ const session = await services.getLatestForUser(ch, message.$sender.id);
46
47
  if (!session) {
47
48
  await next();
48
49
  return;
@@ -55,7 +56,7 @@ export default defineMiddleware<Message>({
55
56
  const payload = resolveGameTextPayload(raw, map);
56
57
  const parsed = payload ? parseChoicePayload(payload, BJ_PREFIX) : null;
57
58
  if (parsed?.choiceId === 'restart') {
58
- const reply = await handleChoice(null, services, message, parsed.sessionId, 'restart');
59
+ const reply = await handleChoice(services, message, parsed.sessionId, 'restart');
59
60
  if (reply) await context.input.$reply(reply);
60
61
  return;
61
62
  }
@@ -66,6 +67,7 @@ export default defineMiddleware<Message>({
66
67
  const map = buildChoiceFallbackMap(BJ_PREFIX, session.id, [
67
68
  { id: 'hit', label: '要牌' },
68
69
  { id: 'stand', label: '停牌' },
70
+ { id: 'quit', label: '放弃' },
69
71
  ]);
70
72
  const payload = resolveGameTextPayload(raw, map);
71
73
  const parsed = payload ? parseChoicePayload(payload, BJ_PREFIX) : null;
@@ -74,7 +76,7 @@ export default defineMiddleware<Message>({
74
76
  return;
75
77
  }
76
78
 
77
- const reply = await handleChoice(null, services, message, session.id, parsed.choiceId);
79
+ const reply = await handleChoice(services, message, session.id, parsed.choiceId);
78
80
  if (reply) await context.input.$reply(reply);
79
81
  },
80
82
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-blackjack",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Zhin.js 21 点(Blackjack)(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,11 +24,11 @@
24
24
  ],
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
- "@zhin.js/core": "1.4.0",
28
- "@zhin.js/middleware": "1.0.2",
29
- "@zhin.js/game-kit": "2.0.0",
30
- "@zhin.js/command": "1.0.2",
31
- "@zhin.js/plugin-runtime": "1.1.0"
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.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,35 @@
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 via in-memory DB).
9
+ * - DB: prefer databaseHostToken; else in-memory SessionService.
10
+ * - Choice middleware under `middlewares/` handles hit/stand payloads (Sandbox action→text).
11
+ */
12
+ export default defineGamePlugin({
13
+ name: 'blackjack',
14
+ metadata: {
15
+ displayName: 'Blackjack',
16
+ },
17
+ game: {
18
+ id: 'blackjack',
19
+ title: '21 点',
20
+ icon: '🃏',
21
+ description: '经典 Blackjack,要牌或停牌挑战庄家',
22
+ commandPrefix: '/21点',
23
+ quickStart: '开始',
24
+ aliases: ['bj', '21点'],
25
+ menus: [
26
+ { id: 'start', label: '🎮 开始新局' },
27
+ { id: 'help', label: '📖 玩法说明' },
28
+ ],
29
+ },
30
+ tables: ['bj_sessions'],
31
+ servicesToken: gameServicesToken,
32
+ defineHostTables,
33
+ createServices: (database) => createServices(database),
34
+ session: (services) => services,
35
+ });
package/src/bj-command.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Message, Plugin } from '@zhin.js/core';
2
- import { continueGame, startGame } from './game-flow.js';
1
+ import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
+ import { continueGame, handleChoice, startGame } from './game-flow.js';
3
3
  import type { SessionService } from './session-service.js';
4
4
 
5
5
  export const BJ_HELP = [
@@ -9,27 +9,26 @@ export const BJ_HELP = [
9
9
  '',
10
10
  '• `/21点 开始` — 新局',
11
11
  '• `/21点 继续` — 刷新界面',
12
+ '• `/21点 放弃` — 结束本局',
12
13
  '• 对局中点击 **要牌** / **停牌**',
13
14
  ].join('\n');
14
15
 
15
16
  export async function runBjCommand(
16
- plugin: Plugin | null,
17
17
  services: SessionService,
18
- message: Message<any>,
18
+ message: GameMessageLike,
19
19
  action: string,
20
- ): Promise<string | undefined> {
20
+ ): Promise<GameReply> {
21
21
  const a = action.trim().toLowerCase() || 'help';
22
22
  if (a === 'help' || a === '帮助') return BJ_HELP;
23
- if (a === 'start' || a === '开始') return startGame(plugin, services, message);
24
- if (a === 'continue' || a === '继续') return continueGame(plugin, services, message);
23
+ if (a === 'start' || a === '开始') return (await startGame(services, message)) ?? '';
24
+ if (a === 'continue' || a === '继续') return continueGame(services, message);
25
+ if (a === 'quit' || a === '放弃') {
26
+ const session = await services.getActiveForUser(
27
+ `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
28
+ message.$sender.id,
29
+ );
30
+ if (!session) return '你没有进行中的 21 点。';
31
+ return (await handleChoice(services, message, session.id, 'quit')) ?? '';
32
+ }
25
33
  return `未知操作「${action}」。发送 \`/21点 帮助\`。`;
26
34
  }
27
-
28
- /** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
29
- export async function runBjCommandText(
30
- services: SessionService,
31
- message: Message<any>,
32
- action: string,
33
- ): Promise<string> {
34
- return (await runBjCommand(null, services, message, action)) ?? '';
35
- }
package/src/game-flow.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { Adapter, Message, Plugin } from '@zhin.js/core';
2
- import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
1
+ import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
3
2
  import {
4
3
  BJ_PREFIX,
5
4
  compareHands,
@@ -12,51 +11,29 @@ import { parseCards, parseDeck, type SessionService } from './session-service.js
12
11
 
13
12
  import { buildBjView, naturalOutcome, playerBust } from './view.js';
14
13
 
15
- async function sendOrEditView(
16
- plugin: Plugin | null,
17
- services: SessionService,
18
- message: Message<any>,
14
+ /**
15
+ * Plugin Runtime: render the board as text. Interactive in-place board editing
16
+ * (the old Adapter.editMessage path) is not part of the runtime flow; commands
17
+ * and the choice middleware return fresh text each turn.
18
+ */
19
+ function renderView(
19
20
  session: BjSessionRow,
21
+ message: GameMessageLike,
20
22
  terminal = false,
21
23
  revealDealer = false,
22
- ): Promise<string | void> {
23
- const content = buildBjView(session, terminal, revealDealer, message.$channel.type);
24
- if (!plugin) return plainTextFromSendContent(content);
25
-
26
- const adapter = plugin.root.inject(message.$adapter) as Adapter;
27
-
28
- if (session.board_message_id) {
29
- const msgId = await adapter.editMessage({
30
- messageId: session.board_message_id,
31
- context: String(message.$adapter),
32
- endpoint: message.$endpoint,
33
- id: message.$channel.id,
34
- type: message.$channel.type,
35
- content,
36
- });
37
- if (msgId !== session.board_message_id) {
38
- await services.updateSession(session.id, { board_message_id: msgId });
39
- }
40
- return;
41
- }
42
-
43
- const msgId = await message.$reply?.(content);
44
- if (msgId) await services.updateSession(session.id, { board_message_id: msgId });
24
+ ): GameReply {
25
+ return buildBjView(session, terminal, revealDealer, message.$channel.type);
45
26
  }
46
27
 
47
28
  async function finishRound(
48
- plugin: Plugin | null,
49
29
  services: SessionService,
50
- message: Message<any>,
30
+ message: GameMessageLike,
51
31
  session: BjSessionRow,
52
32
  status: BjSessionRow['status'],
53
- ): Promise<string | void> {
33
+ ): Promise<GameReply> {
54
34
  await services.updateSession(session.id, { status });
55
35
  const updated = (await services.getById(session.id))!;
56
- if (status === 'won') void recordGameOutcome(message, 'blackjack', 'won', 30);
57
- else if (status === 'lost') void recordGameOutcome(message, 'blackjack', 'lost');
58
- else if (status === 'draw') void recordGameOutcome(message, 'blackjack', 'draw');
59
- return sendOrEditView(plugin, services, message, updated, true, true);
36
+ return renderView(updated, message, true, true);
60
37
  }
61
38
 
62
39
  async function dealerPlay(
@@ -73,10 +50,9 @@ async function dealerPlay(
73
50
  }
74
51
 
75
52
  export async function startGame(
76
- plugin: Plugin | null,
77
53
  services: SessionService,
78
- message: Message<any>,
79
- ): Promise<string | undefined> {
54
+ message: GameMessageLike,
55
+ ): Promise<GameReply | undefined> {
80
56
  const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
81
57
  const active = await services.getActiveByChannel(ch);
82
58
  if (active) {
@@ -88,47 +64,45 @@ export async function startGame(
88
64
  const session = await services.createSession(message);
89
65
  const natural = naturalOutcome(session);
90
66
  if (natural) {
91
- const text = await finishRound(plugin, services, message, session, natural);
92
- return typeof text === 'string' ? text : undefined;
67
+ return finishRound(services, message, session, natural);
93
68
  }
94
- const text = await sendOrEditView(plugin, services, message, session);
95
- return typeof text === 'string' ? text : undefined;
69
+ return renderView(session, message);
96
70
  }
97
71
 
98
72
  export async function continueGame(
99
- plugin: Plugin | null,
100
73
  services: SessionService,
101
- message: Message<any>,
102
- ): Promise<string> {
74
+ message: GameMessageLike,
75
+ ): Promise<GameReply> {
103
76
  const session = await services.getActiveForUser(
104
77
  `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
105
78
  message.$sender.id,
106
79
  );
107
80
  if (!session) return '你没有进行中的 21 点,发送「21点 开始」。';
108
- const text = await sendOrEditView(plugin, services, message, session);
109
- if (typeof text === 'string') return text;
110
- return '已刷新 21 点界面。';
81
+ return renderView(session, message);
111
82
  }
112
83
 
113
84
  export async function handleChoice(
114
- plugin: Plugin | null,
115
85
  services: SessionService,
116
- message: Message<any>,
86
+ message: GameMessageLike,
117
87
  sessionId: string,
118
88
  choiceId: string,
119
- ): Promise<string | null> {
89
+ ): Promise<GameReply | null> {
120
90
  const session = await services.getById(sessionId);
121
91
  if (!session) return '对局不存在。';
122
92
  if (session.player_id !== message.$sender.id) return '这是别人的对局。';
123
93
 
124
94
  if (session.status !== 'active' && choiceId === 'restart') {
125
95
  await services.updateSession(session.id, { status: 'aborted' });
126
- // text-only 模式(plugin===null)下 startGame 的唯一输出就是返回文本
127
- return (await startGame(plugin, services, message)) ?? null;
96
+ return (await startGame(services, message)) ?? null;
128
97
  }
129
98
 
130
99
  if (session.status !== 'active') return '对局已结束,请点击再来一局。';
131
100
 
101
+ if (choiceId === 'quit') {
102
+ await services.updateSession(session.id, { status: 'aborted' });
103
+ return renderView((await services.getById(session.id))!, message, true, true);
104
+ }
105
+
132
106
  const deck = parseDeck(session.deck_json);
133
107
  let player = parseCards(session.player_cards_json);
134
108
  let dealer = parseCards(session.dealer_cards_json);
@@ -142,12 +116,10 @@ export async function handleChoice(
142
116
  player_cards_json: JSON.stringify(player),
143
117
  });
144
118
  if (playerBust({ ...session, player_cards_json: JSON.stringify(player) })) {
145
- const text = await finishRound(plugin, services, message, session, 'lost');
146
- return typeof text === 'string' ? text : null;
119
+ return finishRound(services, message, session, 'lost');
147
120
  }
148
121
  const updated = (await services.getById(session.id))!;
149
- const text = await sendOrEditView(plugin, services, message, updated);
150
- return typeof text === 'string' ? text : null;
122
+ return renderView(updated, message);
151
123
  }
152
124
 
153
125
  if (choiceId !== 'stand') return '无效操作。';
@@ -160,8 +132,7 @@ export async function handleChoice(
160
132
  const outcome = compareHands(player, dealer);
161
133
  const status: BjSessionRow['status'] =
162
134
  outcome === 'won' ? 'won' : outcome === 'lost' ? 'lost' : 'draw';
163
- const text = await finishRound(plugin, services, message, session, status);
164
- return typeof text === 'string' ? text : null;
135
+ return finishRound(services, message, session, status);
165
136
  }
166
137
 
167
138
  export { BJ_PREFIX, handValue, TARGET };
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { BJ_HELP } from './bj-command.js';
2
2
  export { gameServicesToken, resolveGameServices } from './runtime-store.js';
3
- export { createInMemoryBjDb, mountBjMemoryServices } from './memory-db.js';
4
3
 
5
4
  /**
6
5
  * 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
package/src/models.ts CHANGED
@@ -16,7 +16,6 @@ declare module '@zhin.js/core' {
16
16
  deck_json: string;
17
17
  player_cards_json: string;
18
18
  dealer_cards_json: string;
19
- board_message_id: string;
20
19
  status: BjSessionStatus;
21
20
  updated_at: number;
22
21
  created_at: number;
@@ -42,7 +41,6 @@ export function defineHostTables(
42
41
  deck_json: { type: 'text', default: '[]' },
43
42
  player_cards_json: { type: 'text', default: '[]' },
44
43
  dealer_cards_json: { type: 'text', default: '[]' },
45
- board_message_id: { type: 'text', default: '' },
46
44
  status: { type: 'text', default: 'active' },
47
45
  updated_at: { type: 'integer', default: 0 },
48
46
  created_at: { type: 'integer', default: 0 },
@@ -1,21 +1,37 @@
1
- import type { Database, Message, Models, RelatedModel } from '@zhin.js/core';
2
- import { channelKey, generateSessionId } 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 { BjSessionRow } from './models.js';
4
10
  import { freshDeck } from './engine.js';
5
11
 
6
12
  export type BjDatabase = Database<unknown, Models, string>;
7
13
 
8
- function getModel(db: BjDatabase): RelatedModel<unknown, Models, 'bj_sessions'> {
9
- const model = db.models.get('bj_sessions');
10
- if (!model) throw new Error('bj_sessions not registered');
11
- return model as RelatedModel<unknown, Models, 'bj_sessions'>;
12
- }
13
-
14
-
15
- export class SessionService {
16
- constructor(private readonly db: BjDatabase) {}
14
+ export class SessionService extends BaseSessionService<BjSessionRow> {
15
+ constructor(db: BjDatabase) {
16
+ super(db as unknown as GameSessionDatabase<BjSessionRow>, {
17
+ gameId: 'blackjack',
18
+ table: 'bj_sessions',
19
+ userFields: ['player_id'],
20
+ projectOutcomes: (session) => [
21
+ ...(session.status === 'won' || session.status === 'lost'
22
+ || session.status === 'draw'
23
+ ? [{
24
+ userId: session.player_id,
25
+ userName: session.player_name,
26
+ result: session.status,
27
+ score: session.status === 'won' ? 30 : undefined,
28
+ }]
29
+ : []),
30
+ ],
31
+ });
32
+ }
17
33
 
18
- async createSession(message: Message<any>): Promise<BjSessionRow> {
34
+ async createSession(message: GameMessageLike): Promise<BjSessionRow> {
19
35
  const deck = freshDeck();
20
36
  const playerCards = [deck.pop()!, deck.pop()!];
21
37
  const dealerCards = [deck.pop()!, deck.pop()!];
@@ -32,60 +48,11 @@ export class SessionService {
32
48
  deck_json: JSON.stringify(deck),
33
49
  player_cards_json: JSON.stringify(playerCards),
34
50
  dealer_cards_json: JSON.stringify(dealerCards),
35
- board_message_id: '',
36
51
  status: 'active',
37
52
  updated_at: now,
38
53
  created_at: now,
39
54
  };
40
- await getModel(this.db).create(row);
41
- return row;
42
- }
43
-
44
- async getById(id: string): Promise<BjSessionRow | null> {
45
- return getModel(this.db).findOne({ id });
46
- }
47
-
48
- async getActiveForUser(channelKeyValue: string, userId: string): Promise<BjSessionRow | null> {
49
- const rows = await getModel(this.db).findAll({
50
- channel_key: channelKeyValue,
51
- player_id: userId,
52
- status: 'active',
53
- });
54
- return rows[0] ?? null;
55
- }
56
-
57
- async getActiveByChannel(channelKeyValue: string): Promise<BjSessionRow | null> {
58
- const rows = await getModel(this.db).findAll({
59
- channel_key: channelKeyValue,
60
- status: 'active',
61
- });
62
- return rows[0] ?? null;
63
- }
64
-
65
- async getActiveByBoardMessageId(messageId: string): Promise<BjSessionRow | null> {
66
- const rows = await getModel(this.db).findAll({
67
- board_message_id: messageId,
68
- status: 'active',
69
- });
70
- return rows[0] ?? null;
71
- }
72
-
73
- async updateSession(id: string, patch: Partial<BjSessionRow>): Promise<void> {
74
- await getModel(this.db).updateWhere({ id }, { ...patch, updated_at: Date.now() });
75
- }
76
-
77
- async abortStale(maxAgeMs: number): Promise<number> {
78
- const cutoff = Date.now() - maxAgeMs;
79
- const model = getModel(this.db);
80
- const rows = await model.findAll({ status: 'active' });
81
- let n = 0;
82
- for (const row of rows) {
83
- if (row.updated_at < cutoff) {
84
- await model.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
85
- n++;
86
- }
87
- }
88
- return n;
55
+ return this.createRow(row);
89
56
  }
90
57
  }
91
58
 
package/src/view.ts CHANGED
@@ -23,7 +23,8 @@ export function buildBjView(
23
23
  if (terminal) {
24
24
  if (session.status === 'won') lines.push('🎉 **你赢了!**');
25
25
  else if (session.status === 'lost') lines.push('💀 **你输了**');
26
- else lines.push('🤝 **平局**');
26
+ else if (session.status === 'draw') lines.push('🤝 **平局**');
27
+ else lines.push('🏳️ **本局已放弃。**');
27
28
  lines.push('', '点击「再来一局」或发送 `/21点 开始`');
28
29
  } else {
29
30
  lines.push(`目标:尽量接近 ${TARGET} 且不超过。`);
@@ -34,6 +35,7 @@ export function buildBjView(
34
35
  : [
35
36
  { id: 'hit', label: '➕ 要牌', style: 'primary' as const },
36
37
  { id: 'stand', label: '✋ 停牌', style: 'secondary' as const },
38
+ { id: 'quit', label: '🏳️ 放弃', style: 'danger' as const },
37
39
  ];
38
40
 
39
41
  return buildChoiceKeyboard({
@@ -42,7 +44,7 @@ export function buildBjView(
42
44
  narrative: lines.join('\n'),
43
45
  choices,
44
46
  terminal,
45
- fallbackHint: terminal ? '回复 1 再来一局' : '1 要牌 · 2 停牌',
47
+ fallbackHint: terminal ? '回复 1 再来一局' : '1 要牌 · 2 停牌 · 3 放弃',
46
48
  interactionProfile: terminal ? 'terminal' : 'gameplay',
47
49
  channelType,
48
50
  });
@@ -1,6 +0,0 @@
1
- import type { DatabaseHost } from '@zhin.js/plugin-runtime';
2
- import { type BjDatabase, type SessionService } from './session-service.js';
3
- /** In-memory bj_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
4
- export declare function createInMemoryBjDb(): BjDatabase;
5
- export declare function mountBjMemoryServices(): SessionService;
6
- export declare function mountBjHostServices(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 BJ_TABLES = ['bj_sessions'];
5
- /** In-memory bj_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
6
- export function createInMemoryBjDb() {
7
- return createInMemoryGameDb(BJ_TABLES);
8
- }
9
- export function mountBjMemoryServices() {
10
- const services = createServices(createInMemoryBjDb());
11
- return services;
12
- }
13
- export function mountBjHostServices(host) {
14
- defineHostTables(host);
15
- const services = createServices(createHostGameDb(host, BJ_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,EAAwC,MAAM,sBAAsB,CAAC;AAE5F,MAAM,SAAS,GAAG,CAAC,aAAa,CAAU,CAAC;AAE3C,6EAA6E;AAC7E,MAAM,UAAU,kBAAkB;IAChC,OAAO,oBAAoB,CAAC,SAAS,CAA0B,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,MAAM,QAAQ,GAAG,cAAc,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACtD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAkB;IACpD,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,cAAc,CAAC,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAA0B,CAAC,CAAC;IAC5F,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/plugin.ts DELETED
@@ -1,62 +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 { mountBjHostServices, mountBjMemoryServices } 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 via in-memory DB).
15
- * - DB: prefer databaseHostToken; else in-memory SessionService.
16
- * - Choice middleware under `middlewares/` handles hit/stand payloads (Sandbox action→text).
17
- */
18
- export default definePlugin({
19
- name: 'blackjack',
20
- metadata: {
21
- displayName: 'Blackjack',
22
- },
23
- setup(context) {
24
- let services: SessionService;
25
- if (context.resources.has(databaseHostToken)) {
26
- const host = context.resources.use(databaseHostToken);
27
- services = mountBjHostServices(host);
28
- context.lifecycle.add(initGameRecordHost(host));
29
- } else {
30
- services = mountBjMemoryServices();
31
- }
32
- context.resources.provide(gameServicesToken, services);
33
- const disposeHub = registerRuntimeGame({
34
- id: 'blackjack',
35
- title: '21 点',
36
- icon: '🃏',
37
- description: '经典 Blackjack,要牌或停牌挑战庄家',
38
- commandPrefix: '/21点',
39
- quickStart: '开始',
40
- aliases: ['bj', '21点'],
41
- menus: [
42
- { id: 'start', label: '🎮 开始新局' },
43
- { id: 'help', label: '📖 玩法说明' },
44
- ],
45
- });
46
- context.lifecycle.add(disposeHub);
47
-
48
- if (context.resources.has(scheduleHostToken)) {
49
- const schedule = context.resources.use(scheduleHostToken);
50
- const disposeCron = schedule.register({
51
- id: 'blackjack/abort-stale',
52
- cron: DEFAULT_GAME_STALE_CRON,
53
- description: 'Abort stale blackjack sessions',
54
- async execute() {
55
- if (!services.abortStale) return;
56
- await services.abortStale(DEFAULT_GAME_STALE_IDLE_MS);
57
- },
58
- });
59
- context.lifecycle.add(disposeCron);
60
- }
61
- },
62
- });
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 BjDatabase, type SessionService } from './session-service.js';
5
-
6
- const BJ_TABLES = ['bj_sessions'] as const;
7
-
8
- /** In-memory bj_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
9
- export function createInMemoryBjDb(): BjDatabase {
10
- return createInMemoryGameDb(BJ_TABLES) as unknown as BjDatabase;
11
- }
12
-
13
- export function mountBjMemoryServices(): SessionService {
14
- const services = createServices(createInMemoryBjDb());
15
- return services;
16
- }
17
-
18
- export function mountBjHostServices(host: DatabaseHost): SessionService {
19
- defineHostTables(host);
20
- const services = createServices(createHostGameDb(host, BJ_TABLES) as unknown as BjDatabase);
21
- return services;
22
- }