@zhin.js/plugin-guess-number 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/guess/[action:string=].js +16 -0
- package/lib/game-flow.d.ts +5 -4
- package/lib/game-flow.js +31 -24
- package/lib/game-flow.js.map +1 -1
- package/lib/guess-command.d.ts +3 -3
- package/lib/guess-command.js +5 -4
- package/lib/guess-command.js.map +1 -1
- package/lib/index.d.ts +1 -3
- package/lib/index.js +1 -3
- package/lib/index.js.map +1 -1
- package/lib/session-service.d.ts +2 -9
- package/lib/session-service.js +21 -53
- package/lib/session-service.js.map +1 -1
- package/lib/view.d.ts +4 -0
- package/lib/view.js +43 -0
- package/lib/view.js.map +1 -0
- package/middlewares/guess-alias.js +18 -0
- package/middlewares/guess-choice.js +25 -0
- package/middlewares/guess-choice.ts +35 -0
- package/middlewares/guess-digit.js +23 -0
- package/package.json +7 -7
- package/plugin.js +35 -0
- package/src/game-flow.ts +52 -26
- package/src/guess-command.ts +11 -5
- package/src/index.ts +1 -3
- package/src/session-service.ts +32 -59
- package/src/view.ts +52 -0
- package/lib/memory-db.d.ts +0 -6
- package/lib/memory-db.js +0 -18
- package/lib/memory-db.js.map +0 -1
- package/plugin.ts +0 -62
- package/src/memory-db.ts +0 -22
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { messageFromCommandInput, normalizeGuessAction } from '@zhin.js/game-kit';
|
|
4
|
+
import { GUESS_HELP, runGuessCommand } from "../../lib/guess-command.js";
|
|
5
|
+
import { resolveGameServices } from "../../lib/runtime-store.js";
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: 'Guess Number',
|
|
8
|
+
async execute({ params, input, use, owner }) {
|
|
9
|
+
const action = normalizeGuessAction(String(params.action ?? ''));
|
|
10
|
+
if (!action || action === 'help')
|
|
11
|
+
return GUESS_HELP;
|
|
12
|
+
const services = resolveGameServices({ use, owner });
|
|
13
|
+
const message = messageFromCommandInput(input);
|
|
14
|
+
return runGuessCommand(services, message, action);
|
|
15
|
+
},
|
|
16
|
+
});
|
package/lib/game-flow.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export declare function startGame(services: SessionService, message: GameMessageLike): Promise<
|
|
4
|
-
export declare function processGuess(services: SessionService, message: GameMessageLike, value: number): Promise<
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
2
|
+
import type { SessionService } from './session-service.js';
|
|
3
|
+
export declare function startGame(services: SessionService, message: GameMessageLike): Promise<GameReply>;
|
|
4
|
+
export declare function processGuess(services: SessionService, message: GameMessageLike, value: number): Promise<GameReply | null>;
|
|
5
|
+
export declare function handleGuessChoice(services: SessionService, message: GameMessageLike, sessionId: string, choiceId: string): Promise<GameReply>;
|
package/lib/game-flow.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { recordGameOutcome } from '@zhin.js/game-kit';
|
|
2
1
|
import { evaluateGuess, hintText, MAX, MIN } from './engine.js';
|
|
3
|
-
import {
|
|
2
|
+
import { buildGuessView } from './view.js';
|
|
4
3
|
export async function startGame(services, message) {
|
|
5
4
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
6
5
|
const mine = await services.getActiveForUser(ch, message.$sender.id);
|
|
7
6
|
if (mine) {
|
|
8
|
-
return
|
|
7
|
+
return buildGuessView(mine, ['你已有进行中的一局,请继续猜数字。'], message.$channel.type);
|
|
9
8
|
}
|
|
10
9
|
const session = await services.createSession(message);
|
|
11
|
-
return
|
|
10
|
+
return buildGuessView(session, [], message.$channel.type);
|
|
12
11
|
}
|
|
13
12
|
export async function processGuess(services, message, value) {
|
|
14
13
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
@@ -22,23 +21,13 @@ export async function processGuess(services, message, value) {
|
|
|
22
21
|
const attempts = session.attempts + 1;
|
|
23
22
|
if (result === 'win') {
|
|
24
23
|
await services.updateSession(session.id, { attempts, status: 'won' });
|
|
25
|
-
|
|
26
|
-
return [
|
|
27
|
-
'🎉 **猜对了!**',
|
|
28
|
-
'',
|
|
29
|
-
`答案就是 **${session.secret}**,你用了 ${attempts} 次。`,
|
|
30
|
-
'发送 `/猜数 开始` 再来一局。',
|
|
31
|
-
].join('\n');
|
|
24
|
+
const updated = (await services.getById(session.id));
|
|
25
|
+
return buildGuessView(updated, [`你用了 **${attempts}** 次。`], message.$channel.type);
|
|
32
26
|
}
|
|
33
27
|
if (attempts >= session.max_attempts) {
|
|
34
28
|
await services.updateSession(session.id, { attempts, status: 'lost' });
|
|
35
|
-
|
|
36
|
-
return [
|
|
37
|
-
'💀 **机会用完了!**',
|
|
38
|
-
'',
|
|
39
|
-
`正确答案是 **${session.secret}**。`,
|
|
40
|
-
'发送 `/猜数 开始` 再来一局。',
|
|
41
|
-
].join('\n');
|
|
29
|
+
const updated = (await services.getById(session.id));
|
|
30
|
+
return buildGuessView(updated, [], message.$channel.type);
|
|
42
31
|
}
|
|
43
32
|
let rangeMin = session.range_min;
|
|
44
33
|
let rangeMax = session.range_max;
|
|
@@ -47,11 +36,29 @@ export async function processGuess(services, message, value) {
|
|
|
47
36
|
else
|
|
48
37
|
rangeMax = Math.min(rangeMax, value - 1);
|
|
49
38
|
await services.updateSession(session.id, { attempts, range_min: rangeMin, range_max: rangeMax });
|
|
50
|
-
const
|
|
51
|
-
return [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
const updated = (await services.getById(session.id));
|
|
40
|
+
return buildGuessView(updated, [hintText(result, rangeMin, rangeMax)], message.$channel.type);
|
|
41
|
+
}
|
|
42
|
+
export async function handleGuessChoice(services, message, sessionId, choiceId) {
|
|
43
|
+
const session = await services.getById(sessionId);
|
|
44
|
+
if (!session)
|
|
45
|
+
return '这局已经不存在了,请重新开始。';
|
|
46
|
+
if (session.channel_key
|
|
47
|
+
!== `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`) {
|
|
48
|
+
return '请回到开局的频道操作。';
|
|
49
|
+
}
|
|
50
|
+
if (session.player_id !== message.$sender.id)
|
|
51
|
+
return '这是别人的猜数字对局。';
|
|
52
|
+
if (choiceId === 'restart' && session.status !== 'active') {
|
|
53
|
+
return startGame(services, message);
|
|
54
|
+
}
|
|
55
|
+
if (choiceId === 'quit' && session.status === 'active') {
|
|
56
|
+
await services.updateSession(session.id, { status: 'aborted' });
|
|
57
|
+
const updated = (await services.getById(session.id));
|
|
58
|
+
return buildGuessView(updated, ['你放弃了本局。'], message.$channel.type);
|
|
59
|
+
}
|
|
60
|
+
return session.status === 'active'
|
|
61
|
+
? '请直接回复一个范围内的整数。'
|
|
62
|
+
: buildGuessView(session, [], message.$channel.type);
|
|
56
63
|
}
|
|
57
64
|
//# sourceMappingURL=game-flow.js.map
|
package/lib/game-flow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"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"}
|
package/lib/guess-command.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type GameMessageLike } from '@zhin.js/game-kit';
|
|
2
|
-
import {
|
|
1
|
+
import { type GameMessageLike, type GameReply } from '@zhin.js/game-kit';
|
|
2
|
+
import type { SessionService } from './session-service.js';
|
|
3
3
|
export declare const GUESS_HELP: string;
|
|
4
|
-
export declare function runGuessCommand(services: SessionService, message: GameMessageLike, action: string): Promise<
|
|
4
|
+
export declare function runGuessCommand(services: SessionService, message: GameMessageLike, action: string): Promise<GameReply>;
|
package/lib/guess-command.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { channelKey } from '@zhin.js/game-kit';
|
|
1
|
+
import { channelKey, } from '@zhin.js/game-kit';
|
|
2
2
|
import { startGame } from './game-flow.js';
|
|
3
|
-
import {
|
|
3
|
+
import { buildGuessView } from './view.js';
|
|
4
4
|
export const GUESS_HELP = [
|
|
5
5
|
'🔢 猜数字(1~100,7 次机会)',
|
|
6
6
|
'猜数 / guess — 帮助',
|
|
@@ -16,7 +16,7 @@ export async function runGuessCommand(services, message, action) {
|
|
|
16
16
|
const active = await services.getActiveForUser(ch, userId);
|
|
17
17
|
const lines = [GUESS_HELP, ''];
|
|
18
18
|
if (active) {
|
|
19
|
-
|
|
19
|
+
return buildGuessView(active, [GUESS_HELP], message.$channel.type);
|
|
20
20
|
}
|
|
21
21
|
else {
|
|
22
22
|
lines.push('暂无对局,发送「猜数 开始」。');
|
|
@@ -30,7 +30,8 @@ export async function runGuessCommand(services, message, action) {
|
|
|
30
30
|
if (!row)
|
|
31
31
|
return '你没有进行中的猜数字。';
|
|
32
32
|
await services.updateSession(row.id, { status: 'aborted' });
|
|
33
|
-
|
|
33
|
+
const updated = (await services.getById(row.id));
|
|
34
|
+
return buildGuessView(updated, ['已放弃本局。'], message.$channel.type);
|
|
34
35
|
}
|
|
35
36
|
return `未知子命令:${action}\n\n${GUESS_HELP}`;
|
|
36
37
|
}
|
package/lib/guess-command.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guess-command.js","sourceRoot":"","sources":["../src/guess-command.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"guess-command.js","sourceRoot":"","sources":["../src/guess-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,GAGX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,qBAAqB;IACrB,iBAAiB;IACjB,aAAa;IACb,eAAe;IACf,EAAE;IACF,cAAc;CACf,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,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,gBAAgB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC/B,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,cAAc,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACrE,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE5D,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,aAAa,CAAC;QAC/B,MAAM,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAE,CAAC;QAClD,OAAO,cAAc,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,SAAS,MAAM,OAAO,UAAU,EAAE,CAAC;AAC5C,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export { GUESS_HELP } from './guess-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryGuessDb, mountGuessMemoryServices } from './memory-db.js';
|
|
4
3
|
/**
|
|
5
4
|
* 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
|
|
6
|
-
*
|
|
7
|
-
* host DatabaseHost(databaseHostToken)。仍未接:interactive 按钮回调。
|
|
5
|
+
* 游戏大厅注册、过期会话清理与 database host / memory 双轨。
|
|
8
6
|
*/
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export { GUESS_HELP } from './guess-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryGuessDb, mountGuessMemoryServices } from './memory-db.js';
|
|
4
3
|
/**
|
|
5
4
|
* 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
|
|
6
|
-
*
|
|
7
|
-
* host DatabaseHost(databaseHostToken)。仍未接:interactive 按钮回调。
|
|
5
|
+
* 游戏大厅注册、过期会话清理与 database host / memory 双轨。
|
|
8
6
|
*/
|
|
9
7
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE5E;;;GAGG"}
|
package/lib/session-service.d.ts
CHANGED
|
@@ -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 { GuessSessionRow } from './models.js';
|
|
4
4
|
export type GuessDatabase = Database<unknown, Models, string>;
|
|
5
|
-
export declare class SessionService {
|
|
6
|
-
private readonly db;
|
|
5
|
+
export declare class SessionService extends BaseSessionService<GuessSessionRow> {
|
|
7
6
|
constructor(db: GuessDatabase);
|
|
8
|
-
getActiveByChannel(channel: string): Promise<GuessSessionRow | null>;
|
|
9
|
-
getActiveForUser(channel: string, userId: string): Promise<GuessSessionRow | null>;
|
|
10
|
-
getById(id: string): Promise<GuessSessionRow | null>;
|
|
11
7
|
createSession(message: GameMessageLike): Promise<GuessSessionRow>;
|
|
12
|
-
updateSession(id: string, patch: Partial<GuessSessionRow>): Promise<void>;
|
|
13
|
-
abortStale(idleMs: number): Promise<number>;
|
|
14
8
|
}
|
|
15
9
|
export declare function createServices(db: GuessDatabase): SessionService;
|
|
16
|
-
export declare function formatStatus(session: GuessSessionRow): string;
|
package/lib/session-service.js
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
|
-
import { channelKey, generateSessionId } from '@zhin.js/game-kit';
|
|
1
|
+
import { BaseSessionService, channelKey, generateSessionId, } from '@zhin.js/game-kit';
|
|
2
2
|
import { MAX, MAX_ATTEMPTS, MIN, newSecret } from './engine.js';
|
|
3
|
-
|
|
4
|
-
const model = db.models.get('guess_sessions');
|
|
5
|
-
if (!model)
|
|
6
|
-
throw new Error('guess_sessions not registered');
|
|
7
|
-
return model;
|
|
8
|
-
}
|
|
9
|
-
export class SessionService {
|
|
10
|
-
db;
|
|
3
|
+
export class SessionService extends BaseSessionService {
|
|
11
4
|
constructor(db) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
super(db, {
|
|
6
|
+
gameId: 'guess',
|
|
7
|
+
table: 'guess_sessions',
|
|
8
|
+
userFields: ['player_id'],
|
|
9
|
+
projectOutcomes: (session) => session.status === 'won'
|
|
10
|
+
? [{
|
|
11
|
+
userId: session.player_id,
|
|
12
|
+
userName: session.player_name,
|
|
13
|
+
result: 'won',
|
|
14
|
+
score: Math.max(1, session.max_attempts - session.attempts + 1) * 10,
|
|
15
|
+
}]
|
|
16
|
+
: session.status === 'lost'
|
|
17
|
+
? [{
|
|
18
|
+
userId: session.player_id,
|
|
19
|
+
userName: session.player_name,
|
|
20
|
+
result: 'lost',
|
|
21
|
+
}]
|
|
22
|
+
: [],
|
|
23
23
|
});
|
|
24
|
-
return rows[0] ?? null;
|
|
25
|
-
}
|
|
26
|
-
async getById(id) {
|
|
27
|
-
return getModel(this.db).findOne({ id });
|
|
28
24
|
}
|
|
29
25
|
async createSession(message) {
|
|
30
26
|
const now = Date.now();
|
|
@@ -46,38 +42,10 @@ export class SessionService {
|
|
|
46
42
|
updated_at: now,
|
|
47
43
|
created_at: now,
|
|
48
44
|
};
|
|
49
|
-
|
|
50
|
-
return row;
|
|
51
|
-
}
|
|
52
|
-
async updateSession(id, patch) {
|
|
53
|
-
await getModel(this.db).updateWhere({ id }, { ...patch, updated_at: Date.now() });
|
|
54
|
-
}
|
|
55
|
-
async abortStale(idleMs) {
|
|
56
|
-
const cutoff = Date.now() - idleMs;
|
|
57
|
-
const model = getModel(this.db);
|
|
58
|
-
const rows = await model.findAll({ status: 'active' });
|
|
59
|
-
let n = 0;
|
|
60
|
-
for (const row of rows) {
|
|
61
|
-
if (row.updated_at < cutoff) {
|
|
62
|
-
await model.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
63
|
-
n++;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return n;
|
|
45
|
+
return this.createRow(row);
|
|
67
46
|
}
|
|
68
47
|
}
|
|
69
48
|
export function createServices(db) {
|
|
70
49
|
return new SessionService(db);
|
|
71
50
|
}
|
|
72
|
-
export function formatStatus(session) {
|
|
73
|
-
const left = session.max_attempts - session.attempts;
|
|
74
|
-
return [
|
|
75
|
-
'🔢 **猜数字**',
|
|
76
|
-
'',
|
|
77
|
-
`我想了一个 **${session.range_min} ~ ${session.range_max}** 之间的整数。`,
|
|
78
|
-
`你还有 **${left}** 次机会(已猜 ${session.attempts} 次)。`,
|
|
79
|
-
'',
|
|
80
|
-
'直接回复数字即可,例如:`50`',
|
|
81
|
-
].join('\n');
|
|
82
|
-
}
|
|
83
51
|
//# sourceMappingURL=session-service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,iBAAiB,GAGlB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAKhE,MAAM,OAAO,cAAe,SAAQ,kBAAmC;IACrE,YAAY,EAAiB;QAC3B,KAAK,CAAC,EAAqD,EAAE;YAC3D,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,gBAAgB;YACvB,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,IAAI,CAAC,GAAG,CACb,CAAC,EACD,OAAO,CAAC,YAAY,GAAG,OAAO,CAAC,QAAQ,GAAG,CAAC,CAC5C,GAAG,EAAE;qBACP,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,GAAoB;YAC3B,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,MAAM,EAAE,SAAS,EAAE;YACnB,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;YACd,QAAQ,EAAE,CAAC;YACX,YAAY,EAAE,YAAY;YAC1B,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,EAAiB;IAC9C,OAAO,IAAI,cAAc,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC"}
|
package/lib/view.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SendContent } from '@zhin.js/core';
|
|
2
|
+
import type { GuessSessionRow } from './models.js';
|
|
3
|
+
export declare const GUESS_PREFIX = "guess";
|
|
4
|
+
export declare function buildGuessView(session: GuessSessionRow, eventLines?: readonly string[], channelType?: string): SendContent;
|
package/lib/view.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { buildChoiceKeyboard } from '@zhin.js/game-kit';
|
|
2
|
+
export const GUESS_PREFIX = 'guess';
|
|
3
|
+
export function buildGuessView(session, eventLines = [], channelType) {
|
|
4
|
+
const terminal = session.status !== 'active';
|
|
5
|
+
const left = Math.max(0, session.max_attempts - session.attempts);
|
|
6
|
+
const lines = [
|
|
7
|
+
'🔢 **猜数字**',
|
|
8
|
+
'',
|
|
9
|
+
terminal
|
|
10
|
+
? `答案:**${session.secret}** · 共猜 ${session.attempts} 次`
|
|
11
|
+
: `范围:**${session.range_min} ~ ${session.range_max}**`,
|
|
12
|
+
];
|
|
13
|
+
if (eventLines.length > 0)
|
|
14
|
+
lines.push('', ...eventLines);
|
|
15
|
+
if (terminal) {
|
|
16
|
+
if (session.status === 'won')
|
|
17
|
+
lines.push('', '🎉 **猜对了!**');
|
|
18
|
+
else if (session.status === 'lost')
|
|
19
|
+
lines.push('', '💀 **机会用完了。**');
|
|
20
|
+
else
|
|
21
|
+
lines.push('', '🏳️ 本局已结束。');
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
lines.push(`机会:**${left}** 次 · 已猜 ${session.attempts} 次`, '', '直接回复范围内的整数。');
|
|
25
|
+
}
|
|
26
|
+
return buildChoiceKeyboard({
|
|
27
|
+
gamePrefix: GUESS_PREFIX,
|
|
28
|
+
sessionId: session.id,
|
|
29
|
+
narrative: lines.join('\n'),
|
|
30
|
+
choices: terminal
|
|
31
|
+
? [{
|
|
32
|
+
id: 'restart',
|
|
33
|
+
label: '🔄 再来一局',
|
|
34
|
+
style: 'primary',
|
|
35
|
+
keepEnabledWhenTerminal: true,
|
|
36
|
+
}]
|
|
37
|
+
: [{ id: 'quit', label: '🏳️ 放弃', style: 'danger' }],
|
|
38
|
+
terminal,
|
|
39
|
+
interactionProfile: terminal ? 'terminal' : 'gameplay',
|
|
40
|
+
channelType,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=view.js.map
|
package/lib/view.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAIxD,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC;AAEpC,MAAM,UAAU,cAAc,CAC5B,OAAwB,EACxB,aAAgC,EAAE,EAClC,WAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG;QACZ,YAAY;QACZ,EAAE;QACF,QAAQ;YACN,CAAC,CAAC,QAAQ,OAAO,CAAC,MAAM,WAAW,OAAO,CAAC,QAAQ,IAAI;YACvD,CAAC,CAAC,QAAQ,OAAO,CAAC,SAAS,MAAM,OAAO,CAAC,SAAS,IAAI;KACzD,CAAC;IAEF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,UAAU,CAAC,CAAC;IAEzD,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;aACvD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,CAAC,CAAC;;YAC/D,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CACR,QAAQ,IAAI,aAAa,OAAO,CAAC,QAAQ,IAAI,EAC7C,EAAE,EACF,aAAa,CACd,CAAC;IACJ,CAAC;IAED,OAAO,mBAAmB,CAAC;QACzB,UAAU,EAAE,YAAY;QACxB,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3B,OAAO,EAAE,QAAQ;YACf,CAAC,CAAC,CAAC;oBACC,EAAE,EAAE,SAAS;oBACb,KAAK,EAAE,SAAS;oBAChB,KAAK,EAAE,SAAS;oBAChB,uBAAuB,EAAE,IAAI;iBAC9B,CAAC;YACJ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QACtD,QAAQ;QACR,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, isGuessAction, messageFromCommandInput, normalizeGuessAction, } from '@zhin.js/game-kit';
|
|
3
|
+
import { GUESS_HELP, runGuessCommand } from "../lib/guess-command.js";
|
|
4
|
+
import { resolveGameServices } from "../lib/runtime-store.js";
|
|
5
|
+
export default defineGameCommandAliasMiddleware({
|
|
6
|
+
aliases: ['猜数', 'guess'],
|
|
7
|
+
async run(action, input, context) {
|
|
8
|
+
const normalized = normalizeGuessAction(String(action ?? ''));
|
|
9
|
+
if (normalized === 'help')
|
|
10
|
+
return GUESS_HELP;
|
|
11
|
+
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
12
|
+
if (!isGuessAction(normalized))
|
|
13
|
+
return null;
|
|
14
|
+
const services = resolveGameServices(context);
|
|
15
|
+
const message = messageFromCommandInput(input);
|
|
16
|
+
return runGuessCommand(services, message, normalized);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
3
|
+
import { messageFromCommandInput, parseChoicePayload, resolveGameTextPayload, } from '@zhin.js/game-kit';
|
|
4
|
+
import { handleGuessChoice } from "../lib/game-flow.js";
|
|
5
|
+
import { resolveGameServices } from "../lib/runtime-store.js";
|
|
6
|
+
import { GUESS_PREFIX } from "../lib/view.js";
|
|
7
|
+
export default defineMiddleware({
|
|
8
|
+
target: 'inbound',
|
|
9
|
+
async handle(context, next) {
|
|
10
|
+
const raw = context.input.content?.trim() ?? '';
|
|
11
|
+
const payload = resolveGameTextPayload(raw);
|
|
12
|
+
if (!payload?.startsWith(`${GUESS_PREFIX}:`)) {
|
|
13
|
+
await next();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const parsed = parseChoicePayload(payload, GUESS_PREFIX);
|
|
17
|
+
if (!parsed || (parsed.choiceId !== 'quit' && parsed.choiceId !== 'restart')) {
|
|
18
|
+
await next();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const services = resolveGameServices(context);
|
|
22
|
+
const reply = await handleGuessChoice(services, messageFromCommandInput(context.input), parsed.sessionId, parsed.choiceId);
|
|
23
|
+
await context.input.$reply(reply);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Message } from '@zhin.js/core/runtime';
|
|
2
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
3
|
+
import {
|
|
4
|
+
messageFromCommandInput,
|
|
5
|
+
parseChoicePayload,
|
|
6
|
+
resolveGameTextPayload,
|
|
7
|
+
} from '@zhin.js/game-kit';
|
|
8
|
+
import { handleGuessChoice } from '../src/game-flow.js';
|
|
9
|
+
import { resolveGameServices } from '../src/runtime-store.js';
|
|
10
|
+
import { GUESS_PREFIX } from '../src/view.js';
|
|
11
|
+
|
|
12
|
+
export default defineMiddleware<Message>({
|
|
13
|
+
target: 'inbound',
|
|
14
|
+
async handle(context, next) {
|
|
15
|
+
const raw = context.input.content?.trim() ?? '';
|
|
16
|
+
const payload = resolveGameTextPayload(raw);
|
|
17
|
+
if (!payload?.startsWith(`${GUESS_PREFIX}:`)) {
|
|
18
|
+
await next();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const parsed = parseChoicePayload(payload, GUESS_PREFIX);
|
|
22
|
+
if (!parsed || (parsed.choiceId !== 'quit' && parsed.choiceId !== 'restart')) {
|
|
23
|
+
await next();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const services = resolveGameServices(context);
|
|
27
|
+
const reply = await handleGuessChoice(
|
|
28
|
+
services,
|
|
29
|
+
messageFromCommandInput(context.input),
|
|
30
|
+
parsed.sessionId,
|
|
31
|
+
parsed.choiceId,
|
|
32
|
+
);
|
|
33
|
+
await context.input.$reply(reply);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
3
|
+
import { messageFromCommandInput } from '@zhin.js/game-kit';
|
|
4
|
+
import { resolveGameServices } from "../lib/runtime-store.js";
|
|
5
|
+
import { processGuess } from "../lib/game-flow.js";
|
|
6
|
+
export default defineMiddleware({
|
|
7
|
+
target: 'inbound',
|
|
8
|
+
async handle(context, next) {
|
|
9
|
+
const raw = context.input.content?.trim() ?? '';
|
|
10
|
+
if (!/^(\d+)$/.test(raw)) {
|
|
11
|
+
await next();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const services = resolveGameServices(context);
|
|
15
|
+
const message = messageFromCommandInput(context.input);
|
|
16
|
+
const reply = await processGuess(services, message, Number(raw));
|
|
17
|
+
if (reply) {
|
|
18
|
+
await context.input.$reply(reply);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
await next();
|
|
22
|
+
},
|
|
23
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-guess-number",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Zhin.js 猜数字 — 1~100 七步之内猜中 (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
-
"plugin.
|
|
17
|
+
"plugin.js",
|
|
18
18
|
"schema.json",
|
|
19
19
|
"commands",
|
|
20
20
|
"middlewares",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zhin.js/command": "1.0.
|
|
28
|
-
"@zhin.js/core": "1.4.
|
|
29
|
-
"@zhin.js/game-kit": "2.0.
|
|
30
|
-
"@zhin.js/middleware": "1.0.
|
|
27
|
+
"@zhin.js/command": "1.0.4",
|
|
28
|
+
"@zhin.js/core": "1.4.3",
|
|
29
|
+
"@zhin.js/game-kit": "2.0.3",
|
|
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.
|
|
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/quit via in-memory DB).
|
|
9
|
+
* - DB: prefer databaseHostToken; else in-memory SessionService.
|
|
10
|
+
* - Digit middleware under `middlewares/` handles bare-number guesses.
|
|
11
|
+
*/
|
|
12
|
+
export default defineGamePlugin({
|
|
13
|
+
name: 'guess-number',
|
|
14
|
+
metadata: {
|
|
15
|
+
displayName: 'Guess Number',
|
|
16
|
+
},
|
|
17
|
+
game: {
|
|
18
|
+
id: 'guess',
|
|
19
|
+
title: '猜数字',
|
|
20
|
+
icon: '🔢',
|
|
21
|
+
description: '1~100 七步猜中神秘数',
|
|
22
|
+
commandPrefix: '/猜数',
|
|
23
|
+
quickStart: '开始',
|
|
24
|
+
aliases: ['guess'],
|
|
25
|
+
menus: [
|
|
26
|
+
{ id: 'start', label: '🎮 开始新局' },
|
|
27
|
+
{ id: 'help', label: '📖 玩法说明' },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
tables: ['guess_sessions'],
|
|
31
|
+
servicesToken: gameServicesToken,
|
|
32
|
+
defineHostTables,
|
|
33
|
+
createServices: (database) => createServices(database),
|
|
34
|
+
session: (services) => services,
|
|
35
|
+
});
|
package/src/game-flow.ts
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
2
2
|
import { evaluateGuess, hintText, MAX, MIN } from './engine.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { SessionService } from './session-service.js';
|
|
4
|
+
import { buildGuessView } from './view.js';
|
|
4
5
|
|
|
5
6
|
export async function startGame(
|
|
6
7
|
services: SessionService,
|
|
7
8
|
message: GameMessageLike,
|
|
8
|
-
): Promise<
|
|
9
|
+
): Promise<GameReply> {
|
|
9
10
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
10
11
|
const mine = await services.getActiveForUser(ch, message.$sender.id);
|
|
11
12
|
if (mine) {
|
|
12
|
-
return
|
|
13
|
+
return buildGuessView(
|
|
14
|
+
mine,
|
|
15
|
+
['你已有进行中的一局,请继续猜数字。'],
|
|
16
|
+
message.$channel.type,
|
|
17
|
+
);
|
|
13
18
|
}
|
|
14
19
|
const session = await services.createSession(message);
|
|
15
|
-
return
|
|
20
|
+
return buildGuessView(session, [], message.$channel.type);
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export async function processGuess(
|
|
19
24
|
services: SessionService,
|
|
20
25
|
message: GameMessageLike,
|
|
21
26
|
value: number,
|
|
22
|
-
): Promise<
|
|
27
|
+
): Promise<GameReply | null> {
|
|
23
28
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
24
29
|
const session = await services.getActiveForUser(ch, message.$sender.id);
|
|
25
30
|
if (!session) return null;
|
|
@@ -33,24 +38,18 @@ export async function processGuess(
|
|
|
33
38
|
|
|
34
39
|
if (result === 'win') {
|
|
35
40
|
await services.updateSession(session.id, { attempts, status: 'won' });
|
|
36
|
-
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
].join('\n');
|
|
41
|
+
const updated = (await services.getById(session.id))!;
|
|
42
|
+
return buildGuessView(
|
|
43
|
+
updated,
|
|
44
|
+
[`你用了 **${attempts}** 次。`],
|
|
45
|
+
message.$channel.type,
|
|
46
|
+
);
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
if (attempts >= session.max_attempts) {
|
|
46
50
|
await services.updateSession(session.id, { attempts, status: 'lost' });
|
|
47
|
-
|
|
48
|
-
return [
|
|
49
|
-
'💀 **机会用完了!**',
|
|
50
|
-
'',
|
|
51
|
-
`正确答案是 **${session.secret}**。`,
|
|
52
|
-
'发送 `/猜数 开始` 再来一局。',
|
|
53
|
-
].join('\n');
|
|
51
|
+
const updated = (await services.getById(session.id))!;
|
|
52
|
+
return buildGuessView(updated, [], message.$channel.type);
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
let rangeMin = session.range_min;
|
|
@@ -60,10 +59,37 @@ export async function processGuess(
|
|
|
60
59
|
|
|
61
60
|
await services.updateSession(session.id, { attempts, range_min: rangeMin, range_max: rangeMax });
|
|
62
61
|
|
|
63
|
-
const
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
const updated = (await services.getById(session.id))!;
|
|
63
|
+
return buildGuessView(
|
|
64
|
+
updated,
|
|
65
|
+
[hintText(result, rangeMin, rangeMax)],
|
|
66
|
+
message.$channel.type,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function handleGuessChoice(
|
|
71
|
+
services: SessionService,
|
|
72
|
+
message: GameMessageLike,
|
|
73
|
+
sessionId: string,
|
|
74
|
+
choiceId: string,
|
|
75
|
+
): Promise<GameReply> {
|
|
76
|
+
const session = await services.getById(sessionId);
|
|
77
|
+
if (!session) return '这局已经不存在了,请重新开始。';
|
|
78
|
+
if (session.channel_key
|
|
79
|
+
!== `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`) {
|
|
80
|
+
return '请回到开局的频道操作。';
|
|
81
|
+
}
|
|
82
|
+
if (session.player_id !== message.$sender.id) return '这是别人的猜数字对局。';
|
|
83
|
+
|
|
84
|
+
if (choiceId === 'restart' && session.status !== 'active') {
|
|
85
|
+
return startGame(services, message);
|
|
86
|
+
}
|
|
87
|
+
if (choiceId === 'quit' && session.status === 'active') {
|
|
88
|
+
await services.updateSession(session.id, { status: 'aborted' });
|
|
89
|
+
const updated = (await services.getById(session.id))!;
|
|
90
|
+
return buildGuessView(updated, ['你放弃了本局。'], message.$channel.type);
|
|
91
|
+
}
|
|
92
|
+
return session.status === 'active'
|
|
93
|
+
? '请直接回复一个范围内的整数。'
|
|
94
|
+
: buildGuessView(session, [], message.$channel.type);
|
|
69
95
|
}
|
package/src/guess-command.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
channelKey,
|
|
3
|
+
type GameMessageLike,
|
|
4
|
+
type GameReply,
|
|
5
|
+
} from '@zhin.js/game-kit';
|
|
2
6
|
import { startGame } from './game-flow.js';
|
|
3
|
-
import {
|
|
7
|
+
import type { SessionService } from './session-service.js';
|
|
8
|
+
import { buildGuessView } from './view.js';
|
|
4
9
|
|
|
5
10
|
export const GUESS_HELP = [
|
|
6
11
|
'🔢 猜数字(1~100,7 次机会)',
|
|
@@ -15,7 +20,7 @@ export async function runGuessCommand(
|
|
|
15
20
|
services: SessionService,
|
|
16
21
|
message: GameMessageLike,
|
|
17
22
|
action: string,
|
|
18
|
-
): Promise<
|
|
23
|
+
): Promise<GameReply> {
|
|
19
24
|
const ch = channelKey(message);
|
|
20
25
|
const userId = message.$sender.id;
|
|
21
26
|
|
|
@@ -23,7 +28,7 @@ export async function runGuessCommand(
|
|
|
23
28
|
const active = await services.getActiveForUser(ch, userId);
|
|
24
29
|
const lines = [GUESS_HELP, ''];
|
|
25
30
|
if (active) {
|
|
26
|
-
|
|
31
|
+
return buildGuessView(active, [GUESS_HELP], message.$channel.type);
|
|
27
32
|
} else {
|
|
28
33
|
lines.push('暂无对局,发送「猜数 开始」。');
|
|
29
34
|
}
|
|
@@ -36,7 +41,8 @@ export async function runGuessCommand(
|
|
|
36
41
|
const row = await services.getActiveForUser(ch, userId);
|
|
37
42
|
if (!row) return '你没有进行中的猜数字。';
|
|
38
43
|
await services.updateSession(row.id, { status: 'aborted' });
|
|
39
|
-
|
|
44
|
+
const updated = (await services.getById(row.id))!;
|
|
45
|
+
return buildGuessView(updated, ['已放弃本局。'], message.$channel.type);
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
return `未知子命令:${action}\n\n${GUESS_HELP}`;
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export { GUESS_HELP } from './guess-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryGuessDb, mountGuessMemoryServices } from './memory-db.js';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
|
|
7
|
-
*
|
|
8
|
-
* host DatabaseHost(databaseHostToken)。仍未接:interactive 按钮回调。
|
|
6
|
+
* 游戏大厅注册、过期会话清理与 database host / memory 双轨。
|
|
9
7
|
*/
|
package/src/session-service.ts
CHANGED
|
@@ -1,35 +1,40 @@
|
|
|
1
|
-
import type { Database, Models
|
|
2
|
-
import {
|
|
1
|
+
import type { Database, Models } from '@zhin.js/core';
|
|
2
|
+
import {
|
|
3
|
+
BaseSessionService,
|
|
4
|
+
channelKey,
|
|
5
|
+
generateSessionId,
|
|
6
|
+
type GameMessageLike,
|
|
7
|
+
type GameSessionDatabase,
|
|
8
|
+
} from '@zhin.js/game-kit';
|
|
3
9
|
import { MAX, MAX_ATTEMPTS, MIN, newSecret } from './engine.js';
|
|
4
10
|
import type { GuessSessionRow } from './models.js';
|
|
5
11
|
|
|
6
12
|
export type GuessDatabase = Database<unknown, Models, string>;
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
export class SessionService extends BaseSessionService<GuessSessionRow> {
|
|
15
|
+
constructor(db: GuessDatabase) {
|
|
16
|
+
super(db as unknown as GameSessionDatabase<GuessSessionRow>, {
|
|
17
|
+
gameId: 'guess',
|
|
18
|
+
table: 'guess_sessions',
|
|
19
|
+
userFields: ['player_id'],
|
|
20
|
+
projectOutcomes: (session) => session.status === 'won'
|
|
21
|
+
? [{
|
|
22
|
+
userId: session.player_id,
|
|
23
|
+
userName: session.player_name,
|
|
24
|
+
result: 'won',
|
|
25
|
+
score: Math.max(
|
|
26
|
+
1,
|
|
27
|
+
session.max_attempts - session.attempts + 1,
|
|
28
|
+
) * 10,
|
|
29
|
+
}]
|
|
30
|
+
: session.status === 'lost'
|
|
31
|
+
? [{
|
|
32
|
+
userId: session.player_id,
|
|
33
|
+
userName: session.player_name,
|
|
34
|
+
result: 'lost',
|
|
35
|
+
}]
|
|
36
|
+
: [],
|
|
27
37
|
});
|
|
28
|
-
return rows[0] ?? null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async getById(id: string): Promise<GuessSessionRow | null> {
|
|
32
|
-
return getModel(this.db).findOne({ id });
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
async createSession(message: GameMessageLike): Promise<GuessSessionRow> {
|
|
@@ -52,42 +57,10 @@ export class SessionService {
|
|
|
52
57
|
updated_at: now,
|
|
53
58
|
created_at: now,
|
|
54
59
|
};
|
|
55
|
-
|
|
56
|
-
return row;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async updateSession(id: string, patch: Partial<GuessSessionRow>): Promise<void> {
|
|
60
|
-
await getModel(this.db).updateWhere({ id }, { ...patch, updated_at: Date.now() });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async abortStale(idleMs: number): Promise<number> {
|
|
64
|
-
const cutoff = Date.now() - idleMs;
|
|
65
|
-
const model = getModel(this.db);
|
|
66
|
-
const rows = await model.findAll({ status: 'active' });
|
|
67
|
-
let n = 0;
|
|
68
|
-
for (const row of rows) {
|
|
69
|
-
if (row.updated_at < cutoff) {
|
|
70
|
-
await model.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
71
|
-
n++;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return n;
|
|
60
|
+
return this.createRow(row);
|
|
75
61
|
}
|
|
76
62
|
}
|
|
77
63
|
|
|
78
64
|
export function createServices(db: GuessDatabase): SessionService {
|
|
79
65
|
return new SessionService(db);
|
|
80
66
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
export function formatStatus(session: GuessSessionRow): string {
|
|
84
|
-
const left = session.max_attempts - session.attempts;
|
|
85
|
-
return [
|
|
86
|
-
'🔢 **猜数字**',
|
|
87
|
-
'',
|
|
88
|
-
`我想了一个 **${session.range_min} ~ ${session.range_max}** 之间的整数。`,
|
|
89
|
-
`你还有 **${left}** 次机会(已猜 ${session.attempts} 次)。`,
|
|
90
|
-
'',
|
|
91
|
-
'直接回复数字即可,例如:`50`',
|
|
92
|
-
].join('\n');
|
|
93
|
-
}
|
package/src/view.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { buildChoiceKeyboard } from '@zhin.js/game-kit';
|
|
2
|
+
import type { SendContent } from '@zhin.js/core';
|
|
3
|
+
import type { GuessSessionRow } from './models.js';
|
|
4
|
+
|
|
5
|
+
export const GUESS_PREFIX = 'guess';
|
|
6
|
+
|
|
7
|
+
export function buildGuessView(
|
|
8
|
+
session: GuessSessionRow,
|
|
9
|
+
eventLines: readonly string[] = [],
|
|
10
|
+
channelType?: string,
|
|
11
|
+
): SendContent {
|
|
12
|
+
const terminal = session.status !== 'active';
|
|
13
|
+
const left = Math.max(0, session.max_attempts - session.attempts);
|
|
14
|
+
const lines = [
|
|
15
|
+
'🔢 **猜数字**',
|
|
16
|
+
'',
|
|
17
|
+
terminal
|
|
18
|
+
? `答案:**${session.secret}** · 共猜 ${session.attempts} 次`
|
|
19
|
+
: `范围:**${session.range_min} ~ ${session.range_max}**`,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
if (eventLines.length > 0) lines.push('', ...eventLines);
|
|
23
|
+
|
|
24
|
+
if (terminal) {
|
|
25
|
+
if (session.status === 'won') lines.push('', '🎉 **猜对了!**');
|
|
26
|
+
else if (session.status === 'lost') lines.push('', '💀 **机会用完了。**');
|
|
27
|
+
else lines.push('', '🏳️ 本局已结束。');
|
|
28
|
+
} else {
|
|
29
|
+
lines.push(
|
|
30
|
+
`机会:**${left}** 次 · 已猜 ${session.attempts} 次`,
|
|
31
|
+
'',
|
|
32
|
+
'直接回复范围内的整数。',
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return buildChoiceKeyboard({
|
|
37
|
+
gamePrefix: GUESS_PREFIX,
|
|
38
|
+
sessionId: session.id,
|
|
39
|
+
narrative: lines.join('\n'),
|
|
40
|
+
choices: terminal
|
|
41
|
+
? [{
|
|
42
|
+
id: 'restart',
|
|
43
|
+
label: '🔄 再来一局',
|
|
44
|
+
style: 'primary',
|
|
45
|
+
keepEnabledWhenTerminal: true,
|
|
46
|
+
}]
|
|
47
|
+
: [{ id: 'quit', label: '🏳️ 放弃', style: 'danger' }],
|
|
48
|
+
terminal,
|
|
49
|
+
interactionProfile: terminal ? 'terminal' : 'gameplay',
|
|
50
|
+
channelType,
|
|
51
|
+
});
|
|
52
|
+
}
|
package/lib/memory-db.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { DatabaseHost } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import { type GuessDatabase, type SessionService } from './session-service.js';
|
|
3
|
-
/** In-memory guess_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
4
|
-
export declare function createInMemoryGuessDb(): GuessDatabase;
|
|
5
|
-
export declare function mountGuessMemoryServices(): SessionService;
|
|
6
|
-
export declare function mountGuessHostServices(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 GUESS_TABLES = ['guess_sessions'];
|
|
5
|
-
/** In-memory guess_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
6
|
-
export function createInMemoryGuessDb() {
|
|
7
|
-
return createInMemoryGameDb(GUESS_TABLES);
|
|
8
|
-
}
|
|
9
|
-
export function mountGuessMemoryServices() {
|
|
10
|
-
const services = createServices(createInMemoryGuessDb());
|
|
11
|
-
return services;
|
|
12
|
-
}
|
|
13
|
-
export function mountGuessHostServices(host) {
|
|
14
|
-
defineHostTables(host);
|
|
15
|
-
const services = createServices(createHostGameDb(host, GUESS_TABLES));
|
|
16
|
-
return services;
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=memory-db.js.map
|
package/lib/memory-db.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-db.js","sourceRoot":"","sources":["../src/memory-db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,cAAc,EAA2C,MAAM,sBAAsB,CAAC;AAE/F,MAAM,YAAY,GAAG,CAAC,gBAAgB,CAAU,CAAC;AAEjD,gFAAgF;AAChF,MAAM,UAAU,qBAAqB;IACnC,OAAO,oBAAoB,CAAC,YAAY,CAA6B,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,MAAM,QAAQ,GAAG,cAAc,CAAC,qBAAqB,EAAE,CAAC,CAAC;IACzD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAkB;IACvD,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,cAAc,CAAC,gBAAgB,CAAC,IAAI,EAAE,YAAY,CAA6B,CAAC,CAAC;IAClG,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 { mountGuessHostServices, mountGuessMemoryServices } 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/quit via in-memory DB).
|
|
15
|
-
* - DB: prefer databaseHostToken; else in-memory SessionService.
|
|
16
|
-
* - Digit middleware under `middlewares/` handles bare-number guesses.
|
|
17
|
-
*/
|
|
18
|
-
export default definePlugin({
|
|
19
|
-
name: 'guess-number',
|
|
20
|
-
metadata: {
|
|
21
|
-
displayName: 'Guess Number',
|
|
22
|
-
},
|
|
23
|
-
setup(context) {
|
|
24
|
-
let services: SessionService;
|
|
25
|
-
if (context.resources.has(databaseHostToken)) {
|
|
26
|
-
const host = context.resources.use(databaseHostToken);
|
|
27
|
-
services = mountGuessHostServices(host);
|
|
28
|
-
context.lifecycle.add(initGameRecordHost(host));
|
|
29
|
-
} else {
|
|
30
|
-
services = mountGuessMemoryServices();
|
|
31
|
-
}
|
|
32
|
-
context.resources.provide(gameServicesToken, services);
|
|
33
|
-
const disposeHub = registerRuntimeGame({
|
|
34
|
-
id: 'guess',
|
|
35
|
-
title: '猜数字',
|
|
36
|
-
icon: '🔢',
|
|
37
|
-
description: '1~100 七步猜中神秘数',
|
|
38
|
-
commandPrefix: '/猜数',
|
|
39
|
-
quickStart: '开始',
|
|
40
|
-
aliases: ['guess'],
|
|
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: 'guess/abort-stale',
|
|
52
|
-
cron: DEFAULT_GAME_STALE_CRON,
|
|
53
|
-
description: 'Abort stale guess-number 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 GuessDatabase, type SessionService } from './session-service.js';
|
|
5
|
-
|
|
6
|
-
const GUESS_TABLES = ['guess_sessions'] as const;
|
|
7
|
-
|
|
8
|
-
/** In-memory guess_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
9
|
-
export function createInMemoryGuessDb(): GuessDatabase {
|
|
10
|
-
return createInMemoryGameDb(GUESS_TABLES) as unknown as GuessDatabase;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function mountGuessMemoryServices(): SessionService {
|
|
14
|
-
const services = createServices(createInMemoryGuessDb());
|
|
15
|
-
return services;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function mountGuessHostServices(host: DatabaseHost): SessionService {
|
|
19
|
-
defineHostTables(host);
|
|
20
|
-
const services = createServices(createHostGameDb(host, GUESS_TABLES) as unknown as GuessDatabase);
|
|
21
|
-
return services;
|
|
22
|
-
}
|