@zhin.js/plugin-blackjack 0.0.0

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.
Files changed (48) hide show
  1. package/README.md +9 -0
  2. package/lib/bj-command.d.ts +5 -0
  3. package/lib/bj-command.d.ts.map +1 -0
  4. package/lib/bj-command.js +21 -0
  5. package/lib/bj-command.js.map +1 -0
  6. package/lib/commands.d.ts +6 -0
  7. package/lib/commands.d.ts.map +1 -0
  8. package/lib/commands.js +98 -0
  9. package/lib/commands.js.map +1 -0
  10. package/lib/engine.d.ts +14 -0
  11. package/lib/engine.d.ts.map +1 -0
  12. package/lib/engine.js +65 -0
  13. package/lib/engine.js.map +1 -0
  14. package/lib/game-flow.d.ts +8 -0
  15. package/lib/game-flow.d.ts.map +1 -0
  16. package/lib/game-flow.js +118 -0
  17. package/lib/game-flow.js.map +1 -0
  18. package/lib/hub-register.d.ts +3 -0
  19. package/lib/hub-register.d.ts.map +1 -0
  20. package/lib/hub-register.js +29 -0
  21. package/lib/hub-register.js.map +1 -0
  22. package/lib/index.d.ts +2 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +26 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/models.d.ts +26 -0
  27. package/lib/models.d.ts.map +1 -0
  28. package/lib/models.js +20 -0
  29. package/lib/models.js.map +1 -0
  30. package/lib/session-service.d.ts +19 -0
  31. package/lib/session-service.d.ts.map +1 -0
  32. package/lib/session-service.js +108 -0
  33. package/lib/session-service.js.map +1 -0
  34. package/lib/view.d.ts +7 -0
  35. package/lib/view.d.ts.map +1 -0
  36. package/lib/view.js +64 -0
  37. package/lib/view.js.map +1 -0
  38. package/package.json +37 -0
  39. package/plugin.yml +2 -0
  40. package/src/bj-command.ts +26 -0
  41. package/src/commands.ts +117 -0
  42. package/src/engine.ts +67 -0
  43. package/src/game-flow.ts +164 -0
  44. package/src/hub-register.ts +28 -0
  45. package/src/index.ts +36 -0
  46. package/src/models.ts +47 -0
  47. package/src/session-service.ts +118 -0
  48. package/src/view.ts +70 -0
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @zhin.js/plugin-blackjack
2
+
3
+ QQ 群友好的 **21 点(Blackjack)** 插件:要牌 / 停牌按钮,支持游戏大厅与 `/战绩` 统计。
4
+
5
+ ## 命令
6
+
7
+ - `/21点 开始` — 新局
8
+ - `/bj start` — 英文别名
9
+ - 大厅:`/游戏` → 21 点
@@ -0,0 +1,5 @@
1
+ import type { Message, Plugin } from 'zhin.js';
2
+ import type { SessionService } from './session-service.js';
3
+ export declare const BJ_HELP: string;
4
+ export declare function runBjCommand(plugin: Plugin, services: SessionService, message: Message<any>, action: string): Promise<string | undefined>;
5
+ //# sourceMappingURL=bj-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bj-command.d.ts","sourceRoot":"","sources":["../src/bj-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,eAAO,MAAM,OAAO,QAQR,CAAC;AAEb,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM7B"}
@@ -0,0 +1,21 @@
1
+ import { continueGame, startGame } from './game-flow.js';
2
+ export const BJ_HELP = [
3
+ '🃏 **21 点**',
4
+ '',
5
+ '尽量让手牌点数接近 21 且不超过,比庄家大即获胜。',
6
+ '',
7
+ '• `/21点 开始` — 新局',
8
+ '• `/21点 继续` — 刷新界面',
9
+ '• 对局中点击 **要牌** / **停牌**',
10
+ ].join('\n');
11
+ export async function runBjCommand(plugin, services, message, action) {
12
+ const a = action.trim().toLowerCase() || 'help';
13
+ if (a === 'help' || a === '帮助')
14
+ return BJ_HELP;
15
+ if (a === 'start' || a === '开始')
16
+ return startGame(plugin, services, message);
17
+ if (a === 'continue' || a === '继续')
18
+ return continueGame(plugin, services, message);
19
+ return `未知操作「${action}」。发送 \`/21点 帮助\`。`;
20
+ }
21
+ //# sourceMappingURL=bj-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bj-command.js","sourceRoot":"","sources":["../src/bj-command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAgB,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGvE,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,MAAc,EACd,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"}
@@ -0,0 +1,6 @@
1
+ import { type Plugin } from 'zhin.js';
2
+ import type { SessionService } from './session-service.js';
3
+ export declare function registerCommands(plugin: Plugin, getServices: () => SessionService | null): void;
4
+ export declare function registerInteractive(plugin: Plugin, getServices: () => SessionService | null): void;
5
+ export declare function registerTextFallback(plugin: Plugin, getServices: () => SessionService | null): void;
6
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiD,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAWrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAsB3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAG/F;AAgBD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAUlG;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAoDnG"}
@@ -0,0 +1,98 @@
1
+ import { MessageCommand, getActionFromMessage } from 'zhin.js';
2
+ import { buildChoiceFallbackMap, channelKey, parseChoicePayload, registerGameTextMiddleware, resolveGameChoice, resolveGameTextPayload, } from '@zhin.js/game-shared';
3
+ import { BJ_PREFIX, handleChoice } from './game-flow.js';
4
+ import { runBjCommand } from './bj-command.js';
5
+ const VALID_CHOICES = ['hit', 'stand', 'restart'];
6
+ function registerPattern(plugin, pattern, desc, getServices) {
7
+ plugin.addCommand(new MessageCommand(pattern)
8
+ .desc(desc)
9
+ .action(async (message, result) => {
10
+ const services = getServices();
11
+ if (!services)
12
+ return '21 点需要启用 database 配置。';
13
+ const raw = result.params.action ?? '';
14
+ return runBjCommand(plugin, services, message, raw);
15
+ }));
16
+ }
17
+ export function registerCommands(plugin, getServices) {
18
+ registerPattern(plugin, '/21点 [action:word]', '21 点(中文)', getServices);
19
+ registerPattern(plugin, '/bj [action:word]', 'Blackjack (English)', getServices);
20
+ }
21
+ async function resolveChoice(message, services) {
22
+ return resolveGameChoice({
23
+ message,
24
+ gamePrefix: BJ_PREFIX,
25
+ validChoiceIds: VALID_CHOICES,
26
+ getById: (id) => services.getById(id),
27
+ getActiveForUser: (ch, uid) => services.getActiveForUser(ch, uid),
28
+ getByBoardMessageId: (mid) => services.getActiveByBoardMessageId(mid),
29
+ });
30
+ }
31
+ export function registerInteractive(plugin, getServices) {
32
+ plugin.registerInteractiveHandler(`${BJ_PREFIX}:`, async (message) => {
33
+ const services = getServices();
34
+ if (!services)
35
+ return false;
36
+ const choice = await resolveChoice(message, services);
37
+ if (!choice)
38
+ return false;
39
+ const err = await handleChoice(plugin, services, message, choice.sessionId, choice.choiceId);
40
+ if (err)
41
+ await message.$reply?.(err);
42
+ return true;
43
+ });
44
+ }
45
+ export function registerTextFallback(plugin, getServices) {
46
+ registerGameTextMiddleware(plugin, async (message, next) => {
47
+ const services = getServices();
48
+ if (!services)
49
+ return next();
50
+ const action = getActionFromMessage(message);
51
+ if (action?.payload.startsWith(`${BJ_PREFIX}:`))
52
+ return next();
53
+ const raw = message.$raw?.trim() ?? '';
54
+ const ch = channelKey(message);
55
+ const payloadFromText = resolveGameTextPayload(raw);
56
+ if (payloadFromText?.startsWith(`${BJ_PREFIX}:`)) {
57
+ const parsed = parseChoicePayload(payloadFromText, BJ_PREFIX);
58
+ if (parsed) {
59
+ const session = await services.getById(parsed.sessionId);
60
+ if (session?.channel_key === ch) {
61
+ const err = await handleChoice(plugin, services, message, parsed.sessionId, parsed.choiceId);
62
+ if (err)
63
+ await message.$reply?.(err);
64
+ return;
65
+ }
66
+ }
67
+ }
68
+ const session = await services.getActiveForUser(ch, message.$sender.id);
69
+ if (!session)
70
+ return next();
71
+ if (session.status !== 'active') {
72
+ const map = buildChoiceFallbackMap(BJ_PREFIX, session.id, [
73
+ { id: 'restart', label: '再来一局', keepEnabledWhenTerminal: true },
74
+ ]);
75
+ const payload = resolveGameTextPayload(raw, map);
76
+ const parsed = payload ? parseChoicePayload(payload, BJ_PREFIX) : null;
77
+ if (parsed?.choiceId === 'restart') {
78
+ const err = await handleChoice(plugin, services, message, parsed.sessionId, 'restart');
79
+ if (err)
80
+ await message.$reply?.(err);
81
+ return;
82
+ }
83
+ return next();
84
+ }
85
+ const map = buildChoiceFallbackMap(BJ_PREFIX, session.id, [
86
+ { id: 'hit', label: '要牌' },
87
+ { id: 'stand', label: '停牌' },
88
+ ]);
89
+ const payload = resolveGameTextPayload(raw, map);
90
+ const parsed = payload ? parseChoicePayload(payload, BJ_PREFIX) : null;
91
+ if (!parsed || parsed.sessionId !== session.id)
92
+ return next();
93
+ const err = await handleChoice(plugin, services, message, session.id, parsed.choiceId);
94
+ if (err)
95
+ await message.$reply?.(err);
96
+ }, 'bj:text');
97
+ }
98
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export declare const BJ_PREFIX = "bj";
2
+ export declare const TARGET = 21;
3
+ declare const RANKS: readonly ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
4
+ declare const SUITS: readonly ["♠", "♥", "♦", "♣"];
5
+ export type Card = `${typeof RANKS[number]}${typeof SUITS[number]}`;
6
+ export declare function freshDeck(): Card[];
7
+ export declare function cardValue(rank: string): number;
8
+ export declare function handValue(cards: readonly string[]): number;
9
+ export declare function formatHand(cards: readonly string[], hideSecond?: boolean): string;
10
+ export declare function isBlackjack(cards: readonly string[]): boolean;
11
+ export declare function dealerShouldHit(cards: readonly string[]): boolean;
12
+ export declare function compareHands(player: readonly string[], dealer: readonly string[]): 'won' | 'lost' | 'draw';
13
+ export {};
14
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,OAAO,CAAC;AAC9B,eAAO,MAAM,MAAM,KAAK,CAAC;AAEzB,QAAA,MAAM,KAAK,6EAA8E,CAAC;AAC1F,QAAA,MAAM,KAAK,+BAAgC,CAAC;AAE5C,MAAM,MAAM,IAAI,GAAG,GAAG,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,OAAO,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;AAEpE,wBAAgB,SAAS,IAAI,IAAI,EAAE,CAYlC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAa1D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,UAAQ,GAAG,MAAM,CAK/E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAE7D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAEjE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAQ1G"}
package/lib/engine.js ADDED
@@ -0,0 +1,65 @@
1
+ export const BJ_PREFIX = 'bj';
2
+ export const TARGET = 21;
3
+ const RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
4
+ const SUITS = ['♠', '♥', '♦', '♣'];
5
+ export function freshDeck() {
6
+ const deck = [];
7
+ for (const r of RANKS) {
8
+ for (const s of SUITS) {
9
+ deck.push(`${r}${s}`);
10
+ }
11
+ }
12
+ for (let i = deck.length - 1; i > 0; i--) {
13
+ const j = Math.floor(Math.random() * (i + 1));
14
+ [deck[i], deck[j]] = [deck[j], deck[i]];
15
+ }
16
+ return deck;
17
+ }
18
+ export function cardValue(rank) {
19
+ if (rank === 'A')
20
+ return 11;
21
+ if (rank === 'J' || rank === 'Q' || rank === 'K')
22
+ return 10;
23
+ return Number(rank);
24
+ }
25
+ export function handValue(cards) {
26
+ let total = 0;
27
+ let aces = 0;
28
+ for (const c of cards) {
29
+ const rank = c.slice(0, c.length - 1);
30
+ if (rank === 'A')
31
+ aces++;
32
+ total += cardValue(rank);
33
+ }
34
+ while (total > TARGET && aces > 0) {
35
+ total -= 10;
36
+ aces--;
37
+ }
38
+ return total;
39
+ }
40
+ export function formatHand(cards, hideSecond = false) {
41
+ if (hideSecond && cards.length >= 2) {
42
+ return `${cards[0]} 🂠 (${handValue([cards[0]])})`;
43
+ }
44
+ return `${cards.join(' ')} (${handValue(cards)})`;
45
+ }
46
+ export function isBlackjack(cards) {
47
+ return cards.length === 2 && handValue(cards) === TARGET;
48
+ }
49
+ export function dealerShouldHit(cards) {
50
+ return handValue(cards) < 17;
51
+ }
52
+ export function compareHands(player, dealer) {
53
+ const pv = handValue(player);
54
+ const dv = handValue(dealer);
55
+ if (pv > TARGET)
56
+ return 'lost';
57
+ if (dv > TARGET)
58
+ return 'won';
59
+ if (pv > dv)
60
+ return 'won';
61
+ if (pv < dv)
62
+ return 'lost';
63
+ return 'draw';
64
+ }
65
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Message, Plugin } from 'zhin.js';
2
+ import { BJ_PREFIX, handValue, TARGET } from './engine.js';
3
+ import type { SessionService } from './session-service.js';
4
+ export declare function startGame(plugin: Plugin, services: SessionService, message: Message<any>): Promise<string | undefined>;
5
+ export declare function continueGame(plugin: Plugin, services: SessionService, message: Message<any>): Promise<string>;
6
+ export declare function handleChoice(plugin: Plugin, services: SessionService, message: Message<any>, sessionId: string, choiceId: string): Promise<string | null>;
7
+ export { BJ_PREFIX, handValue, TARGET };
8
+ //# sourceMappingURL=game-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-flow.d.ts","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAExD,OAAO,EACL,SAAS,EAGT,SAAS,EACT,MAAM,EACP,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA8D3D,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GACpB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiB7B;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GACpB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8CxB;AAED,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,118 @@
1
+ import { recordGameOutcome } from '@zhin.js/game-shared';
2
+ import { BJ_PREFIX, compareHands, dealerShouldHit, handValue, TARGET, } from './engine.js';
3
+ import { parseCards, parseDeck } from './session-service.js';
4
+ 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
+ const adapter = plugin.root.inject(message.$adapter);
8
+ if (session.board_message_id) {
9
+ const msgId = await adapter.editMessage({
10
+ messageId: session.board_message_id,
11
+ context: String(message.$adapter),
12
+ endpoint: message.$endpoint,
13
+ id: message.$channel.id,
14
+ type: message.$channel.type,
15
+ content,
16
+ });
17
+ if (msgId !== session.board_message_id) {
18
+ await services.updateSession(session.id, { board_message_id: msgId });
19
+ }
20
+ return;
21
+ }
22
+ const msgId = await message.$reply?.(content);
23
+ if (msgId)
24
+ await services.updateSession(session.id, { board_message_id: msgId });
25
+ }
26
+ async function finishRound(plugin, services, message, session, status) {
27
+ await services.updateSession(session.id, { status });
28
+ const updated = (await services.getById(session.id));
29
+ if (status === 'won')
30
+ void recordGameOutcome(message, 'blackjack', 'won', 30);
31
+ else if (status === 'lost')
32
+ void recordGameOutcome(message, 'blackjack', 'lost');
33
+ else if (status === 'draw')
34
+ void recordGameOutcome(message, 'blackjack', 'draw');
35
+ await sendOrEditView(plugin, services, message, updated, true, true);
36
+ }
37
+ async function dealerPlay(deck, dealer) {
38
+ const hand = [...dealer];
39
+ while (dealerShouldHit(hand)) {
40
+ const card = deck.pop();
41
+ if (!card)
42
+ break;
43
+ hand.push(card);
44
+ }
45
+ return hand;
46
+ }
47
+ export async function startGame(plugin, services, message) {
48
+ const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
49
+ const active = await services.getActiveByChannel(ch);
50
+ if (active) {
51
+ if (active.player_id === message.$sender.id) {
52
+ return '你已有进行中的 21 点,发送「21点 继续」刷新。';
53
+ }
54
+ return `本频道 ${active.player_name} 正在玩 21 点。`;
55
+ }
56
+ const session = await services.createSession(message);
57
+ const natural = naturalOutcome(session);
58
+ if (natural) {
59
+ await finishRound(plugin, services, message, session, natural);
60
+ return undefined;
61
+ }
62
+ await sendOrEditView(plugin, services, message, session);
63
+ return undefined;
64
+ }
65
+ export async function continueGame(plugin, services, message) {
66
+ const session = await services.getActiveForUser(`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`, message.$sender.id);
67
+ if (!session)
68
+ return '你没有进行中的 21 点,发送「21点 开始」。';
69
+ await sendOrEditView(plugin, services, message, session);
70
+ return '已刷新 21 点界面。';
71
+ }
72
+ export async function handleChoice(plugin, services, message, sessionId, choiceId) {
73
+ const session = await services.getById(sessionId);
74
+ if (!session)
75
+ return '对局不存在。';
76
+ if (session.player_id !== message.$sender.id)
77
+ return '这是别人的对局。';
78
+ if (session.status !== 'active' && choiceId === 'restart') {
79
+ await services.updateSession(session.id, { status: 'aborted' });
80
+ await startGame(plugin, services, message);
81
+ return null;
82
+ }
83
+ if (session.status !== 'active')
84
+ return '对局已结束,请点击再来一局。';
85
+ let deck = parseDeck(session.deck_json);
86
+ let player = parseCards(session.player_cards_json);
87
+ let dealer = parseCards(session.dealer_cards_json);
88
+ if (choiceId === 'hit') {
89
+ const card = deck.pop();
90
+ if (!card)
91
+ return '牌堆已空。';
92
+ player = [...player, card];
93
+ await services.updateSession(session.id, {
94
+ deck_json: JSON.stringify(deck),
95
+ player_cards_json: JSON.stringify(player),
96
+ });
97
+ if (playerBust({ ...session, player_cards_json: JSON.stringify(player) })) {
98
+ await finishRound(plugin, services, message, session, 'lost');
99
+ return null;
100
+ }
101
+ const updated = (await services.getById(session.id));
102
+ await sendOrEditView(plugin, services, message, updated);
103
+ return null;
104
+ }
105
+ if (choiceId !== 'stand')
106
+ return '无效操作。';
107
+ dealer = await dealerPlay(deck, dealer);
108
+ await services.updateSession(session.id, {
109
+ deck_json: JSON.stringify(deck),
110
+ dealer_cards_json: JSON.stringify(dealer),
111
+ });
112
+ const outcome = compareHands(player, dealer);
113
+ const status = outcome === 'won' ? 'won' : outcome === 'lost' ? 'lost' : 'draw';
114
+ await finishRound(plugin, services, message, session, status);
115
+ return null;
116
+ }
117
+ export { BJ_PREFIX, handValue, TARGET };
118
+ //# sourceMappingURL=game-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SessionService } from './session-service.js';
2
+ export declare function registerBjHub(getServices: () => SessionService | null): () => void;
3
+ //# sourceMappingURL=hub-register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub-register.d.ts","sourceRoot":"","sources":["../src/hub-register.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,MAAM,IAAI,CAsBlF"}
@@ -0,0 +1,29 @@
1
+ import { getPlugin } from 'zhin.js';
2
+ import { ensureGameHubService } from '@zhin.js/game-shared';
3
+ import { runBjCommand, BJ_HELP } from './bj-command.js';
4
+ export function registerBjHub(getServices) {
5
+ const plugin = getPlugin();
6
+ ensureGameHubService(plugin);
7
+ return plugin.registerGame({
8
+ id: 'blackjack',
9
+ title: '21 点',
10
+ icon: '🃏',
11
+ description: '经典 Blackjack,要牌或停牌挑战庄家',
12
+ commandPrefix: '/21点',
13
+ quickStart: '开始',
14
+ aliases: ['bj', '21点'],
15
+ menus: [
16
+ { id: 'start', label: '🎮 开始新局', style: 'primary' },
17
+ { id: 'help', label: '📖 玩法说明' },
18
+ ],
19
+ runAction: async (actionId, ctx) => {
20
+ const services = getServices();
21
+ if (!services)
22
+ return '21 点需要启用 database 配置。';
23
+ if (actionId === 'help')
24
+ return BJ_HELP;
25
+ return runBjCommand(plugin, services, ctx.message, actionId === 'start' ? '开始' : actionId);
26
+ },
27
+ });
28
+ }
29
+ //# sourceMappingURL=hub-register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub-register.js","sourceRoot":"","sources":["../src/hub-register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAGxD,MAAM,UAAU,aAAa,CAAC,WAAwC;IACpE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,YAAY,CAAC;QACzB,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,wBAAwB;QACrC,aAAa,EAAE,MAAM;QACrB,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;QACtB,KAAK,EAAE;YACL,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;YACnD,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE;SACjC;QACD,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE;YACjC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,QAAQ;gBAAE,OAAO,uBAAuB,CAAC;YAC9C,IAAI,QAAQ,KAAK,MAAM;gBAAE,OAAO,OAAO,CAAC;YACxC,OAAO,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC7F,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/lib/index.js ADDED
@@ -0,0 +1,26 @@
1
+ import { Cron, formatCompact, usePlugin } from 'zhin.js';
2
+ import { registerModels } from './models.js';
3
+ import { createServices, resolveGameDatabase, } from './session-service.js';
4
+ import { registerCommands, registerInteractive, registerTextFallback } from './commands.js';
5
+ import { registerBjHub } from './hub-register.js';
6
+ const plugin = usePlugin();
7
+ const { logger, useContext, addCron } = plugin;
8
+ registerModels(plugin);
9
+ let services = null;
10
+ useContext('database', (dbFeature) => {
11
+ services = createServices(resolveGameDatabase(dbFeature));
12
+ logger.info(formatCompact({ 模块: '21点', 数据模型: '已就绪' }));
13
+ });
14
+ registerBjHub(() => services);
15
+ registerCommands(plugin, () => services);
16
+ registerInteractive(plugin, () => services);
17
+ registerTextFallback(plugin, () => services);
18
+ addCron(new Cron('0 */15 * * * *', async () => {
19
+ if (!services)
20
+ return;
21
+ const n = await services.abortStale(30 * 60 * 1000);
22
+ if (n > 0)
23
+ logger.debug(formatCompact({ '21点': '清理超时局', count: n }));
24
+ }));
25
+ logger.info(formatCompact({ 模块: '21点', 状态: '已加载' }));
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAwB,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,mBAAmB,GAEpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;AAC3B,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;AAE/C,cAAc,CAAC,MAAM,CAAC,CAAC;AAEvB,IAAI,QAAQ,GAA0B,IAAI,CAAC;AAE3C,UAAU,CAAC,UAAU,EAAE,CAAC,SAA0B,EAAE,EAAE;IACpD,QAAQ,GAAG,cAAc,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEH,aAAa,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAC9B,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AACzC,mBAAmB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAC5C,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAE7C,OAAO,CACL,IAAI,IAAI,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;IACpC,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC,CAAC,CACH,CAAC;AAEF,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { Models, Plugin } from 'zhin.js';
2
+ export type BjSessionStatus = 'active' | 'won' | 'lost' | 'draw' | 'aborted';
3
+ declare module 'zhin.js' {
4
+ interface Models {
5
+ bj_sessions: {
6
+ id: string;
7
+ adapter: string;
8
+ endpoint: string;
9
+ channel_type: string;
10
+ channel_id: string;
11
+ channel_key: string;
12
+ player_id: string;
13
+ player_name: string;
14
+ deck_json: string;
15
+ player_cards_json: string;
16
+ dealer_cards_json: string;
17
+ board_message_id: string;
18
+ status: BjSessionStatus;
19
+ updated_at: number;
20
+ created_at: number;
21
+ };
22
+ }
23
+ }
24
+ export type BjSessionRow = Models['bj_sessions'];
25
+ export declare function registerModels(plugin: Plugin): void;
26
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7E,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,MAAM;QACd,WAAW,EAAE;YACX,EAAE,EAAE,MAAM,CAAC;YACX,OAAO,EAAE,MAAM,CAAC;YAChB,QAAQ,EAAE,MAAM,CAAC;YACjB,YAAY,EAAE,MAAM,CAAC;YACrB,UAAU,EAAE,MAAM,CAAC;YACnB,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC;YAClB,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC;YAClB,iBAAiB,EAAE,MAAM,CAAC;YAC1B,iBAAiB,EAAE,MAAM,CAAC;YAC1B,gBAAgB,EAAE,MAAM,CAAC;YACzB,MAAM,EAAE,eAAe,CAAC;YACxB,UAAU,EAAE,MAAM,CAAC;YACnB,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC;KACH;CACF;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;AAEjD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAkBnD"}
package/lib/models.js ADDED
@@ -0,0 +1,20 @@
1
+ export function registerModels(plugin) {
2
+ plugin.defineModel('bj_sessions', {
3
+ id: { type: 'text', primary: true },
4
+ adapter: { type: 'text', nullable: false },
5
+ endpoint: { type: 'text', nullable: false },
6
+ channel_type: { type: 'text', nullable: false },
7
+ channel_id: { type: 'text', nullable: false },
8
+ channel_key: { type: 'text', nullable: false },
9
+ player_id: { type: 'text', nullable: false },
10
+ player_name: { type: 'text', default: '' },
11
+ deck_json: { type: 'text', default: '[]' },
12
+ player_cards_json: { type: 'text', default: '[]' },
13
+ dealer_cards_json: { type: 'text', default: '[]' },
14
+ board_message_id: { type: 'text', default: '' },
15
+ status: { type: 'text', default: 'active' },
16
+ updated_at: { type: 'integer', default: 0 },
17
+ created_at: { type: 'integer', default: 0 },
18
+ });
19
+ }
20
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AA4BA,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,CAAC,WAAW,CAAC,aAAa,EAAE;QAChC,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"}
@@ -0,0 +1,19 @@
1
+ import type { Database, DatabaseFeature, Message, Models } from 'zhin.js';
2
+ import type { BjSessionRow } from './models.js';
3
+ export type BjDatabase = Database<unknown, Models, string>;
4
+ export declare function resolveGameDatabase(feature: DatabaseFeature): BjDatabase;
5
+ export declare class SessionService {
6
+ private readonly db;
7
+ constructor(db: BjDatabase);
8
+ createSession(message: Message<any>): Promise<BjSessionRow>;
9
+ getById(id: string): Promise<BjSessionRow | null>;
10
+ getActiveForUser(channelKeyValue: string, userId: string): Promise<BjSessionRow | null>;
11
+ getActiveByChannel(channelKeyValue: string): Promise<BjSessionRow | null>;
12
+ getActiveByBoardMessageId(messageId: string): Promise<BjSessionRow | null>;
13
+ updateSession(id: string, patch: Partial<BjSessionRow>): Promise<void>;
14
+ abortStale(maxAgeMs: number): Promise<number>;
15
+ }
16
+ export declare function createServices(db: BjDatabase): SessionService;
17
+ export declare function parseCards(json: string | string[]): string[];
18
+ export declare function parseDeck(json: string | string[]): string[];
19
+ //# sourceMappingURL=session-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-service.d.ts","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAgB,MAAM,SAAS,CAAC;AAExF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAQ3D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,UAAU,CAExE;AAED,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,UAAU;IAErC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IA0B3D,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAIjD,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IASvF,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAQzE,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAQ1E,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAapD;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,UAAU,GAAG,cAAc,CAE7D;AAeD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAE5D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAE3D"}