koishi-plugin-cchess 2.0.1 → 2.2.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.
- package/README.md +5 -4
- package/lib/index.d.ts +3 -1
- package/lib/index.js +160 -74
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,17 +16,18 @@ yarn add koishi-plugin-cchess
|
|
|
16
16
|
| --- | --- |
|
|
17
17
|
| `cchess.开始 [红/黑] [人人/人机]` | 入座并开局,双方就位后自动开战 |
|
|
18
18
|
| `cchess.开始 <FEN>` | 摆谱,导入局面后再 `cchess.开始` 即可 |
|
|
19
|
+
| `cchess.落子 <着法>` | 按棋谱落子,如 `炮二平五` |
|
|
19
20
|
| `cchess.悔棋 [同意/拒绝]` | 请求悔棋,也可直接回复同意或拒绝表决 |
|
|
20
21
|
| `cchess.认输` | 认输 |
|
|
21
|
-
| `cchess.结束` |
|
|
22
|
-
| `cchess
|
|
22
|
+
| `cchess.结束` | 结束正在进行的一局 |
|
|
23
|
+
| `cchess.战绩 [@某人/榜] [胜场/输场] [人数]` | 查询战绩与排行榜 |
|
|
23
24
|
| `cchess.查看云库残局 [DTM/DTC]` | 云库残局统计 |
|
|
24
25
|
|
|
25
26
|
发送 `cchess.开始` 即入座,双方就位后自动开局,无需单独的加入与退出指令。
|
|
26
27
|
|
|
27
|
-
着法可直接发送,例如 `炮二平五` 或 `b2e2
|
|
28
|
+
着法可直接发送,例如 `炮二平五` 或 `b2e2`,也可以写成 `cchess.落子 炮二平五`。
|
|
28
29
|
|
|
29
|
-
悔棋请求送出后,对方直接回复 `同意` 或 `拒绝`
|
|
30
|
+
悔棋请求送出后,对方直接回复 `同意` 或 `拒绝` 即可表决,不必带指令前缀;请求方发送 `cchess.悔棋 拒绝` 可撤下请求。
|
|
30
31
|
|
|
31
32
|
## 许可证
|
|
32
33
|
|
package/lib/index.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { Context, Schema } from 'koishi';
|
|
2
2
|
export declare const name = "cchess";
|
|
3
|
-
export declare const usage = "## \u4F7F\u7528\n\n`cchess.\u5F00\u59CB` \u5373\u53EF\u5165\u5EA7\uFF0C\u53CC\u65B9\u5C31\u4F4D\u540E\u81EA\u52A8\u5F00\u5C40\uFF1B`cchess.\u5F00\u59CB \u4EBA\u673A` \u76F4\u63A5\u6311\u6218\u76AE\u5361\u9C7C\u3002\u7740\u6CD5\u53EF\u76F4\u63A5\u53D1\u9001\uFF0C\u4F8B\u5982 `\u70AE\u4E8C\u5E73\u4E94` \u6216 `b2e2`\u3002\n\n\u6094\u68CB\u8BF7\u6C42\u9001\u51FA\u540E\uFF0C\u5BF9\u65B9\u65E0\u9700\u5E26\u6307\u4EE4\u524D\u7F00\uFF0C\u76F4\u63A5\u56DE\u590D `\u540C\u610F` \u6216 `\u62D2\u7EDD` \u5373\u53EF\u8868\u51B3\u3002\n\n## \u6307\u4EE4\n\n| \u6307\u4EE4 | \u8BF4\u660E |\n| --- | --- |\n| `cchess.\u5F00\u59CB [\u7EA2/\u9ED1] [\u4EBA\u4EBA/\u4EBA\u673A]` | \u5165\u5EA7\u5E76\u5F00\u5C40\uFF0C\u53CC\u65B9\u5C31\u4F4D\u540E\u81EA\u52A8\u5F00\u6218 |\n| `cchess.\u5F00\u59CB <FEN>` | \u6446\u8C31\uFF0C\u5BFC\u5165\u5C40\u9762\u540E\u518D `cchess.\u5F00\u59CB` \u5373\u53EF |\n| `cchess.\u6094\u68CB [\u540C\u610F/\u62D2\u7EDD]` | \u8BF7\u6C42\u6094\u68CB\uFF0C\u4E5F\u53EF\u76F4\u63A5\u56DE\u590D\u540C\u610F\u6216\u62D2\u7EDD\u8868\u51B3 |\n| `cchess.\u8BA4\u8F93` | \u8BA4\u8F93\u5E76\u7ED3\u675F\u5BF9\u5C40 |\n| `cchess.\u7ED3\u675F` | \u7ED3\u675F\
|
|
3
|
+
export declare const usage = "## \u4F7F\u7528\n\n`cchess.\u5F00\u59CB` \u5373\u53EF\u5165\u5EA7\uFF0C\u53CC\u65B9\u5C31\u4F4D\u540E\u81EA\u52A8\u5F00\u5C40\uFF1B`cchess.\u5F00\u59CB \u4EBA\u673A` \u76F4\u63A5\u6311\u6218\u76AE\u5361\u9C7C\u3002\u7740\u6CD5\u53EF\u76F4\u63A5\u53D1\u9001\uFF0C\u4F8B\u5982 `\u70AE\u4E8C\u5E73\u4E94` \u6216 `b2e2`\uFF0C\u4E5F\u53EF\u4EE5\u5199\u6210 `cchess.\u843D\u5B50 \u70AE\u4E8C\u5E73\u4E94`\u3002\n\n\u6094\u68CB\u8BF7\u6C42\u9001\u51FA\u540E\uFF0C\u5BF9\u65B9\u65E0\u9700\u5E26\u6307\u4EE4\u524D\u7F00\uFF0C\u76F4\u63A5\u56DE\u590D `\u540C\u610F` \u6216 `\u62D2\u7EDD` \u5373\u53EF\u8868\u51B3\uFF1B\u8BF7\u6C42\u65B9\u53D1\u9001 `cchess.\u6094\u68CB \u62D2\u7EDD` \u53EF\u64A4\u4E0B\u8BF7\u6C42\u3002\n\n## \u6307\u4EE4\n\n| \u6307\u4EE4 | \u8BF4\u660E |\n| --- | --- |\n| `cchess.\u5F00\u59CB [\u7EA2/\u9ED1] [\u4EBA\u4EBA/\u4EBA\u673A]` | \u5165\u5EA7\u5E76\u5F00\u5C40\uFF0C\u53CC\u65B9\u5C31\u4F4D\u540E\u81EA\u52A8\u5F00\u6218 |\n| `cchess.\u5F00\u59CB <FEN>` | \u6446\u8C31\uFF0C\u5BFC\u5165\u5C40\u9762\u540E\u518D `cchess.\u5F00\u59CB` \u5373\u53EF |\n| `cchess.\u843D\u5B50 <\u7740\u6CD5>` | \u6309\u68CB\u8C31\u843D\u5B50\uFF0C\u5982 `\u70AE\u4E8C\u5E73\u4E94` |\n| `cchess.\u6094\u68CB [\u540C\u610F/\u62D2\u7EDD]` | \u8BF7\u6C42\u6094\u68CB\uFF0C\u4E5F\u53EF\u76F4\u63A5\u56DE\u590D\u540C\u610F\u6216\u62D2\u7EDD\u8868\u51B3 |\n| `cchess.\u8BA4\u8F93` | \u8BA4\u8F93\u5E76\u7ED3\u675F\u5BF9\u5C40 |\n| `cchess.\u7ED3\u675F` | \u7ED3\u675F\u6B63\u5728\u8FDB\u884C\u7684\u4E00\u5C40 |\n| `cchess.\u6218\u7EE9 [@\u67D0\u4EBA/\u699C] [\u80DC\u573A/\u8F93\u573A] [\u4EBA\u6570]` | \u67E5\u8BE2\u6218\u7EE9\u6216\u6392\u884C\u699C |\n| `cchess.\u67E5\u770B\u4E91\u5E93\u6B8B\u5C40 [DTM/DTC]` | \u67E5\u8BE2\u4E91\u5E93\u6B8B\u5C40\u7EDF\u8BA1 |";
|
|
4
4
|
export declare const inject: string[];
|
|
5
5
|
export interface Config {
|
|
6
6
|
boardSkin: string;
|
|
7
7
|
pieceSkin: string;
|
|
8
8
|
defaultEngineThinkingDepth: number;
|
|
9
9
|
allowFreePieceMovementInHumanMachineMode: boolean;
|
|
10
|
+
enableDirectInput: boolean;
|
|
10
11
|
defaultMaxLeaderboardEntries: number;
|
|
11
12
|
retractDelay: number;
|
|
12
13
|
isChessImageWithOutlineEnabled: boolean;
|
|
14
|
+
disableImages: boolean;
|
|
13
15
|
imgScale: number;
|
|
14
16
|
imageType: "png" | "jpeg" | "webp";
|
|
15
17
|
}
|
package/lib/index.js
CHANGED
|
@@ -234,6 +234,24 @@ var SHAPE = {
|
|
|
234
234
|
full: 9999
|
|
235
235
|
};
|
|
236
236
|
var FONT_STACK = '"Roboto Flex", "Roboto", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", "Source Han Sans SC", system-ui, sans-serif';
|
|
237
|
+
var TYPE = {
|
|
238
|
+
displayLarge: { size: 57, line: 64, weight: 400, tracking: -0.25 },
|
|
239
|
+
displayMedium: { size: 45, line: 52, weight: 400, tracking: 0 },
|
|
240
|
+
displaySmall: { size: 36, line: 44, weight: 400, tracking: 0 },
|
|
241
|
+
headlineLarge: { size: 32, line: 40, weight: 400, tracking: 0 },
|
|
242
|
+
headlineMedium: { size: 28, line: 36, weight: 400, tracking: 0 },
|
|
243
|
+
headlineSmall: { size: 24, line: 32, weight: 400, tracking: 0 },
|
|
244
|
+
titleLarge: { size: 22, line: 28, weight: 400, tracking: 0 },
|
|
245
|
+
titleMedium: { size: 16, line: 24, weight: 500, tracking: 0.15 },
|
|
246
|
+
titleSmall: { size: 14, line: 20, weight: 500, tracking: 0.1 },
|
|
247
|
+
bodyLarge: { size: 16, line: 24, weight: 400, tracking: 0.5 },
|
|
248
|
+
bodyMedium: { size: 14, line: 20, weight: 400, tracking: 0.25 },
|
|
249
|
+
bodySmall: { size: 12, line: 16, weight: 400, tracking: 0.4 },
|
|
250
|
+
labelLarge: { size: 14, line: 20, weight: 500, tracking: 0.1 },
|
|
251
|
+
labelMedium: { size: 12, line: 16, weight: 500, tracking: 0.5 },
|
|
252
|
+
labelSmall: { size: 11, line: 16, weight: 500, tracking: 0.5 }
|
|
253
|
+
};
|
|
254
|
+
var EMPHASIZED_WEIGHT = { display: 600, headline: 600, title: 600, label: 600 };
|
|
237
255
|
var MEDAL = {
|
|
238
256
|
gold: lch(58, 52, 85),
|
|
239
257
|
silver: lch(66, 5, 260),
|
|
@@ -246,9 +264,9 @@ var fs = __toESM(require("fs"));
|
|
|
246
264
|
var name = "cchess";
|
|
247
265
|
var usage = `## 使用
|
|
248
266
|
|
|
249
|
-
\`cchess.开始\` 即可入座,双方就位后自动开局;\`cchess.开始 人机\` 直接挑战皮卡鱼。着法可直接发送,例如 \`炮二平五\` 或 \`b2e2
|
|
267
|
+
\`cchess.开始\` 即可入座,双方就位后自动开局;\`cchess.开始 人机\` 直接挑战皮卡鱼。着法可直接发送,例如 \`炮二平五\` 或 \`b2e2\`,也可以写成 \`cchess.落子 炮二平五\`。
|
|
250
268
|
|
|
251
|
-
悔棋请求送出后,对方无需带指令前缀,直接回复 \`同意\` 或 \`拒绝\`
|
|
269
|
+
悔棋请求送出后,对方无需带指令前缀,直接回复 \`同意\` 或 \`拒绝\` 即可表决;请求方发送 \`cchess.悔棋 拒绝\` 可撤下请求。
|
|
252
270
|
|
|
253
271
|
## 指令
|
|
254
272
|
|
|
@@ -256,9 +274,10 @@ var usage = `## 使用
|
|
|
256
274
|
| --- | --- |
|
|
257
275
|
| \`cchess.开始 [红/黑] [人人/人机]\` | 入座并开局,双方就位后自动开战 |
|
|
258
276
|
| \`cchess.开始 <FEN>\` | 摆谱,导入局面后再 \`cchess.开始\` 即可 |
|
|
277
|
+
| \`cchess.落子 <着法>\` | 按棋谱落子,如 \`炮二平五\` |
|
|
259
278
|
| \`cchess.悔棋 [同意/拒绝]\` | 请求悔棋,也可直接回复同意或拒绝表决 |
|
|
260
279
|
| \`cchess.认输\` | 认输并结束对局 |
|
|
261
|
-
| \`cchess.结束\` |
|
|
280
|
+
| \`cchess.结束\` | 结束正在进行的一局 |
|
|
262
281
|
| \`cchess.战绩 [@某人/榜] [胜场/输场] [人数]\` | 查询战绩或排行榜 |
|
|
263
282
|
| \`cchess.查看云库残局 [DTM/DTC]\` | 查询云库残局统计 |`;
|
|
264
283
|
var inject = ["database", "canvas"];
|
|
@@ -268,14 +287,15 @@ var Config = import_koishi.Schema.object({
|
|
|
268
287
|
boardSkin: import_koishi.Schema.union(boardSkins).default("象甲2023棋盘").description(`棋盘皮肤。`),
|
|
269
288
|
pieceSkin: import_koishi.Schema.union(pieceSkins).default("象甲棋子").description(`棋子皮肤。`),
|
|
270
289
|
allowFreePieceMovementInHumanMachineMode: import_koishi.Schema.boolean().default(false).description(`人机模式下,允许所有人自由移动棋子,开启后可以不入座直接开始人机对局。`),
|
|
290
|
+
enableDirectInput: import_koishi.Schema.boolean().default(true).description(`对局中直接发送棋谱或「同意」「拒绝」即可落子与应答,无需指令前缀。`),
|
|
271
291
|
defaultEngineThinkingDepth: import_koishi.Schema.number().min(0).max(100).default(10).description(`默认引擎思考深度,越高 AI 棋力越强,耗时也越长(小于 1 时按 1 计算)。由于 Nodejs 不支持 SIMD,所以不建议设置过高。`),
|
|
272
|
-
defaultMaxLeaderboardEntries: import_koishi.Schema.number().min(0).default(
|
|
292
|
+
defaultMaxLeaderboardEntries: import_koishi.Schema.number().min(0).default(4).description(`排行榜默认显示的人数。`),
|
|
273
293
|
retractDelay: import_koishi.Schema.number().min(0).default(0).description(`自动撤回延迟(秒),0 表示不撤回。`),
|
|
274
294
|
imgScale: import_koishi.Schema.number().min(1).default(1).description(`图片分辨率倍率。`),
|
|
275
295
|
imageType: import_koishi.Schema.union(["png", "jpeg", "webp"]).default("png").description(`发送的图片格式。`),
|
|
276
|
-
isChessImageWithOutlineEnabled: import_koishi.Schema.boolean().default(true).description(`给棋盘图片加上坐标外框。关闭后出图明显更快,但没有外框,玩起来可能会比较累。`)
|
|
296
|
+
isChessImageWithOutlineEnabled: import_koishi.Schema.boolean().default(true).description(`给棋盘图片加上坐标外框。关闭后出图明显更快,但没有外框,玩起来可能会比较累。`),
|
|
297
|
+
disableImages: import_koishi.Schema.boolean().default(false).description(`全部改用文本,不发送棋盘图片。`)
|
|
277
298
|
});
|
|
278
|
-
var RULE = "━━━━━━━━━━━━━━";
|
|
279
299
|
var SIDE_ICONS = { 红方: "🔴", 黑方: "⚫" };
|
|
280
300
|
var MEDALS = ["🥇", "🥈", "🥉"];
|
|
281
301
|
function withSideIcon(side) {
|
|
@@ -291,12 +311,12 @@ function isLine(line) {
|
|
|
291
311
|
}
|
|
292
312
|
__name(isLine, "isLine");
|
|
293
313
|
function panel(options) {
|
|
294
|
-
const { icon
|
|
295
|
-
const lines = [`${icon} ${title}
|
|
314
|
+
const { icon, title, at, body = [], tips = [], image } = options;
|
|
315
|
+
const lines = [`${icon} ${title}`];
|
|
296
316
|
if (at) lines.push(`@${at}`);
|
|
297
317
|
lines.push(...body.filter(isLine));
|
|
298
318
|
const validTips = tips.filter(isLine).filter(Boolean);
|
|
299
|
-
if (validTips.length) lines.push(
|
|
319
|
+
if (validTips.length) lines.push(...validTips);
|
|
300
320
|
if (image) lines.push(image);
|
|
301
321
|
return lines.join("\n");
|
|
302
322
|
}
|
|
@@ -306,6 +326,7 @@ function apply(ctx, config) {
|
|
|
306
326
|
const engines = {};
|
|
307
327
|
const enginePromises = {};
|
|
308
328
|
const busyChannels = /* @__PURE__ */ new Set();
|
|
329
|
+
const regretTicket = /* @__PURE__ */ new Map();
|
|
309
330
|
ctx.on("dispose", () => {
|
|
310
331
|
const channelIds = Object.keys(engines);
|
|
311
332
|
channelIds.forEach((channelId) => {
|
|
@@ -430,6 +451,7 @@ function apply(ctx, config) {
|
|
|
430
451
|
}
|
|
431
452
|
__name(matchRegretDecision, "matchRegretDecision");
|
|
432
453
|
ctx.middleware(async (session, next) => {
|
|
454
|
+
if (!config.enableDirectInput) return next();
|
|
433
455
|
const { channelId, content, userId } = session;
|
|
434
456
|
if (!channelId || !content) return next();
|
|
435
457
|
const decision = matchRegretDecision(content);
|
|
@@ -441,6 +463,7 @@ function apply(ctx, config) {
|
|
|
441
463
|
await resolveRegret(session, decision === "agree");
|
|
442
464
|
});
|
|
443
465
|
ctx.middleware(async (session, next) => {
|
|
466
|
+
if (!config.enableDirectInput) return next();
|
|
444
467
|
const { channelId, content, userId, username } = session;
|
|
445
468
|
if (!channelId || !content) return next();
|
|
446
469
|
const moveOperation = content.trim();
|
|
@@ -462,7 +485,19 @@ function apply(ctx, config) {
|
|
|
462
485
|
ctx.command("cchess", "中国象棋 · 楚河汉界").action(async ({ session }) => {
|
|
463
486
|
await session.execute(`cchess -h`);
|
|
464
487
|
});
|
|
465
|
-
ctx.command("cchess.开始 [args:text]", "
|
|
488
|
+
ctx.command("cchess.开始 [args:text]", "入座,双方就位后开局").usage("可选阵营「红」「黑」与对手「人人」「人机」;直接粘贴 FEN 串则按摆谱处理。").example("cchess.开始 红 人机").action(async ({ session }, args) => {
|
|
489
|
+
const { username, channelId } = session;
|
|
490
|
+
if (busyChannels.has(channelId)) {
|
|
491
|
+
return await sendMessage(session, busyPanel(username));
|
|
492
|
+
}
|
|
493
|
+
busyChannels.add(channelId);
|
|
494
|
+
try {
|
|
495
|
+
return await startGame(session, args);
|
|
496
|
+
} finally {
|
|
497
|
+
busyChannels.delete(channelId);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
async function startGame(session, args) {
|
|
466
501
|
const { username, userId, channelId } = session;
|
|
467
502
|
await updateNameInPlayerRecord(userId, username);
|
|
468
503
|
const gameRecord = await getGameRecord(channelId);
|
|
@@ -520,11 +555,10 @@ function apply(ctx, config) {
|
|
|
520
555
|
title: "已入座,静候对手",
|
|
521
556
|
at: username,
|
|
522
557
|
body: [
|
|
523
|
-
field("阵营", withSideIcon(selfSide)),
|
|
524
|
-
field("当前人数", `${players.length} 人`),
|
|
558
|
+
field("阵营", withSideIcon(selfSide)) + " " + field("当前人数", `${players.length} 人`),
|
|
525
559
|
!blackPlayers.length ? "黑方尚无人。" : "红方尚无人。"
|
|
526
560
|
],
|
|
527
|
-
tips: ["好友发送「cchess
|
|
561
|
+
tips: ["好友发送「cchess.开始」即可补位,指定阵营可发「cchess.开始 黑」"]
|
|
528
562
|
}));
|
|
529
563
|
}
|
|
530
564
|
await ctx.database.set("cchess_game_records", { channelId }, { isStarted: true });
|
|
@@ -533,32 +567,46 @@ function apply(ctx, config) {
|
|
|
533
567
|
icon: "✅",
|
|
534
568
|
title: "楚河汉界,对局开始",
|
|
535
569
|
body: [
|
|
536
|
-
`${withSideIcon("红方")}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
`${withSideIcon("黑方")}(${blackPlayers.length})`,
|
|
540
|
-
...blackPlayers.map((player) => ` @${player.username}`),
|
|
541
|
-
"",
|
|
542
|
-
field("先手", withSideIcon(sideString))
|
|
570
|
+
`${withSideIcon("红方")} · ${redPlayers.map((player) => `@${player.username}`).join("、")}`,
|
|
571
|
+
`${withSideIcon("黑方")} · ${blackPlayers.map((player) => `@${player.username}`).join("、")}`,
|
|
572
|
+
`先手 · ${withSideIcon(sideString)}`
|
|
543
573
|
],
|
|
544
574
|
tips: ["直接发送着法即可落子,如「炮二平五」或「b2e2」"],
|
|
545
575
|
image: await renderBoard(channelId)
|
|
546
576
|
}));
|
|
547
|
-
}
|
|
548
|
-
|
|
577
|
+
}
|
|
578
|
+
__name(startGame, "startGame");
|
|
579
|
+
ctx.command("cchess.结束", "结束正在进行的一局").userFields(["id", "name", "authority"]).action(async ({ session }) => {
|
|
549
580
|
const { username, userId, channelId } = session;
|
|
550
581
|
await updateNameInPlayerRecord(userId, username);
|
|
551
582
|
const gameRecord = await getGameRecord(channelId);
|
|
552
583
|
if (!gameRecord.isStarted) {
|
|
553
584
|
return await sendMessage(session, panel({
|
|
554
|
-
icon: "
|
|
585
|
+
icon: "💡",
|
|
555
586
|
title: "棋盘尚且空空",
|
|
556
587
|
at: username,
|
|
557
588
|
body: ["当前没有进行中的对局,无需收拾残局。"],
|
|
558
589
|
tips: ["发送「cchess.开始」入座,再开一局"]
|
|
559
590
|
}));
|
|
560
591
|
}
|
|
561
|
-
await
|
|
592
|
+
const playerRecord = await ctx.database.get("cchess_gaming_player_records", { channelId, userId });
|
|
593
|
+
if (!playerRecord.length && (session.user?.authority ?? 0) < 2) {
|
|
594
|
+
return await sendMessage(session, panel({
|
|
595
|
+
icon: "⚠️",
|
|
596
|
+
title: "权限不够",
|
|
597
|
+
at: username,
|
|
598
|
+
body: ["只有本局棋手或权限 2 以上的人能结束这一局。"]
|
|
599
|
+
}));
|
|
600
|
+
}
|
|
601
|
+
if (busyChannels.has(channelId)) {
|
|
602
|
+
return await sendMessage(session, busyPanel(username));
|
|
603
|
+
}
|
|
604
|
+
busyChannels.add(channelId);
|
|
605
|
+
try {
|
|
606
|
+
await endGame(channelId);
|
|
607
|
+
} finally {
|
|
608
|
+
busyChannels.delete(channelId);
|
|
609
|
+
}
|
|
562
610
|
return await sendMessage(session, panel({
|
|
563
611
|
icon: "✅",
|
|
564
612
|
title: "对局已结束",
|
|
@@ -567,6 +615,9 @@ function apply(ctx, config) {
|
|
|
567
615
|
tips: ["发送「cchess.开始」重整旗鼓"]
|
|
568
616
|
}));
|
|
569
617
|
});
|
|
618
|
+
ctx.command("cchess.落子 [move:string]", "发送着法,如「炮二平五」").usage("着法可用中式纵线记谱,也可用坐标,如 `b2e2`。").example("cchess.落子 炮二平五").action(async ({ session }, move) => {
|
|
619
|
+
return await handleMove(session, move);
|
|
620
|
+
});
|
|
570
621
|
async function handleMove(session, moveOperation) {
|
|
571
622
|
const { username, userId, channelId } = session;
|
|
572
623
|
await updateNameInPlayerRecord(userId, username);
|
|
@@ -575,12 +626,7 @@ function apply(ctx, config) {
|
|
|
575
626
|
return await sendMessage(session, invalidMovePanel(username, "看不懂这一着"));
|
|
576
627
|
}
|
|
577
628
|
if (busyChannels.has(channelId)) {
|
|
578
|
-
return await sendMessage(session,
|
|
579
|
-
icon: "⏳",
|
|
580
|
-
title: "棋局正在推演",
|
|
581
|
-
at: username,
|
|
582
|
-
body: ["上一着棋尚未结算,稍候片刻。"]
|
|
583
|
-
}));
|
|
629
|
+
return await sendMessage(session, busyPanel(username));
|
|
584
630
|
}
|
|
585
631
|
busyChannels.add(channelId);
|
|
586
632
|
try {
|
|
@@ -593,7 +639,7 @@ function apply(ctx, config) {
|
|
|
593
639
|
icon: "⏳",
|
|
594
640
|
title: "悔棋请求待决",
|
|
595
641
|
at: username,
|
|
596
|
-
body: ["
|
|
642
|
+
body: ["对方的悔棋请求尚未答复,对局暂歇。"],
|
|
597
643
|
tips: ["对方回复「同意」或「拒绝」即可表决"]
|
|
598
644
|
}));
|
|
599
645
|
}
|
|
@@ -679,6 +725,7 @@ function apply(ctx, config) {
|
|
|
679
725
|
}
|
|
680
726
|
const newMoves = record.moveList.slice(movesBefore);
|
|
681
727
|
return await sendMessage(session, panel({
|
|
728
|
+
icon: "✅",
|
|
682
729
|
title: `第 ${record.moveList.length} 手`,
|
|
683
730
|
body: [
|
|
684
731
|
...newMoves.map((move) => `${withSideIcon(convertTurnToString(move.side))} ${move.chnMoveName} ${move.move}`),
|
|
@@ -691,7 +738,7 @@ function apply(ctx, config) {
|
|
|
691
738
|
}
|
|
692
739
|
}
|
|
693
740
|
__name(handleMove, "handleMove");
|
|
694
|
-
ctx.command("cchess.悔棋 [decision:string]", "请求悔棋或答复对方").usage("
|
|
741
|
+
ctx.command("cchess.悔棋 [decision:string]", "请求悔棋或答复对方").usage("请求送出后,对方直接回复「同意」或「拒绝」即可表决,无需指令前缀;请求方发送「cchess.悔棋 拒绝」可撤下请求。").action(async ({ session }, decision) => {
|
|
695
742
|
const { username, userId, channelId } = session;
|
|
696
743
|
await updateNameInPlayerRecord(userId, username);
|
|
697
744
|
if (decision) {
|
|
@@ -727,8 +774,8 @@ function apply(ctx, config) {
|
|
|
727
774
|
}
|
|
728
775
|
if (gameRecord.moveList.length < 1) {
|
|
729
776
|
return await sendMessage(session, panel({
|
|
730
|
-
icon: "
|
|
731
|
-
title: "
|
|
777
|
+
icon: "💡",
|
|
778
|
+
title: "对局初开",
|
|
732
779
|
at: username,
|
|
733
780
|
body: ["一子未落,无悔可言。"]
|
|
734
781
|
}));
|
|
@@ -755,15 +802,32 @@ function apply(ctx, config) {
|
|
|
755
802
|
}));
|
|
756
803
|
}
|
|
757
804
|
await ctx.database.set("cchess_game_records", { channelId }, { isRegretRequest: true });
|
|
805
|
+
const ticket = (regretTicket.get(channelId) ?? 0) + 1;
|
|
806
|
+
regretTicket.set(channelId, ticket);
|
|
807
|
+
ctx.setTimeout(async () => {
|
|
808
|
+
if (regretTicket.get(channelId) !== ticket) return;
|
|
809
|
+
const current = await getGameRecord(channelId);
|
|
810
|
+
if (!current?.isRegretRequest) return;
|
|
811
|
+
await ctx.database.set("cchess_game_records", { channelId }, { isRegretRequest: false });
|
|
812
|
+
await sendMessage(session, panel({
|
|
813
|
+
icon: "⏳",
|
|
814
|
+
title: "悔棋请求已作罢",
|
|
815
|
+
body: ["等了 30 秒没有等到答复,请求自动撤回。"],
|
|
816
|
+
tips: ["发送「cchess.悔棋」可以再提一次。"]
|
|
817
|
+
}));
|
|
818
|
+
}, 30 * 1e3);
|
|
758
819
|
return await sendMessage(session, panel({
|
|
759
820
|
icon: "⏳",
|
|
760
821
|
title: "悔棋请求已送出",
|
|
761
822
|
at: username,
|
|
762
|
-
body: [
|
|
763
|
-
|
|
823
|
+
body: [
|
|
824
|
+
field("等待答复", withSideIcon(sideString)),
|
|
825
|
+
"对方回复「同意」或「拒绝」即可表决,30 秒等不到答复会自动作废。"
|
|
826
|
+
],
|
|
827
|
+
tips: ["发送「cchess.悔棋 拒绝」可撤下这次请求"]
|
|
764
828
|
}));
|
|
765
829
|
});
|
|
766
|
-
ctx.command("cchess.认输", "
|
|
830
|
+
ctx.command("cchess.认输", "认输并判对方获胜").action(async ({ session }) => {
|
|
767
831
|
const { username, userId, channelId } = session;
|
|
768
832
|
await updateNameInPlayerRecord(userId, username);
|
|
769
833
|
const gameRecord = await getGameRecord(channelId);
|
|
@@ -823,7 +887,7 @@ function apply(ctx, config) {
|
|
|
823
887
|
session,
|
|
824
888
|
byLose ? "lose" : "win",
|
|
825
889
|
byLose ? "总输场排行榜" : "总胜场排行榜",
|
|
826
|
-
|
|
890
|
+
"📋",
|
|
827
891
|
size
|
|
828
892
|
);
|
|
829
893
|
}
|
|
@@ -842,7 +906,7 @@ function apply(ctx, config) {
|
|
|
842
906
|
win: 0
|
|
843
907
|
});
|
|
844
908
|
return await sendMessage(session, panel({
|
|
845
|
-
icon: "
|
|
909
|
+
icon: "📋",
|
|
846
910
|
title: "棋士档案",
|
|
847
911
|
at: session.username,
|
|
848
912
|
body: [field("查询对象", username), "尚无对局记录,静待首战。"],
|
|
@@ -853,41 +917,39 @@ function apply(ctx, config) {
|
|
|
853
917
|
const total = win + lose;
|
|
854
918
|
const winRate = total === 0 ? "—" : `${(win / total * 100).toFixed(1)}%`;
|
|
855
919
|
return await sendMessage(session, panel({
|
|
856
|
-
icon: "
|
|
920
|
+
icon: "📋",
|
|
857
921
|
title: "棋士档案",
|
|
858
922
|
at: session.username,
|
|
859
923
|
body: [
|
|
860
924
|
field("查询对象", username),
|
|
861
|
-
field("胜 场", `${win} 局`),
|
|
862
|
-
field("
|
|
863
|
-
field("总对局", `${total} 局`),
|
|
864
|
-
field("胜 率", winRate)
|
|
925
|
+
field("胜 场", `${win} 局`) + " " + field("负 场", `${lose} 局`),
|
|
926
|
+
field("总对局", `${total} 局`) + " " + field("胜 率", winRate)
|
|
865
927
|
]
|
|
866
928
|
}));
|
|
867
929
|
});
|
|
868
930
|
function notStartedPanel(username) {
|
|
869
931
|
return panel({
|
|
870
|
-
icon: "
|
|
871
|
-
title: "
|
|
932
|
+
icon: "💡",
|
|
933
|
+
title: "对局尚未开始",
|
|
872
934
|
at: username,
|
|
873
935
|
body: ["当前频道还没有进行中的对局。"],
|
|
874
|
-
tips: ["「cchess
|
|
936
|
+
tips: ["「cchess.开始」入座对弈 · 「cchess.开始 人机」挑战皮卡鱼"]
|
|
875
937
|
});
|
|
876
938
|
}
|
|
877
939
|
__name(notStartedPanel, "notStartedPanel");
|
|
878
940
|
function alreadyStartedPanel(username) {
|
|
879
941
|
return panel({
|
|
880
|
-
icon: "
|
|
881
|
-
title: "
|
|
942
|
+
icon: "💡",
|
|
943
|
+
title: "对局已经开战",
|
|
882
944
|
at: username,
|
|
883
945
|
body: ["本局尚未结束,不必重开。"],
|
|
884
|
-
tips: ["「cchess.认输」结束本局
|
|
946
|
+
tips: ["「cchess.认输」结束本局 · 「cchess.结束」清盘"]
|
|
885
947
|
});
|
|
886
948
|
}
|
|
887
949
|
__name(alreadyStartedPanel, "alreadyStartedPanel");
|
|
888
950
|
function notJoinedPanel(username) {
|
|
889
951
|
return panel({
|
|
890
|
-
icon: "
|
|
952
|
+
icon: "💡",
|
|
891
953
|
title: "你尚未入座",
|
|
892
954
|
at: username,
|
|
893
955
|
body: ["先入座,才好落子。"],
|
|
@@ -895,6 +957,15 @@ function apply(ctx, config) {
|
|
|
895
957
|
});
|
|
896
958
|
}
|
|
897
959
|
__name(notJoinedPanel, "notJoinedPanel");
|
|
960
|
+
function busyPanel(username) {
|
|
961
|
+
return panel({
|
|
962
|
+
icon: "⏳",
|
|
963
|
+
title: "对局正在推演",
|
|
964
|
+
at: username,
|
|
965
|
+
body: ["上一着棋尚未结算,稍候片刻。"]
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
__name(busyPanel, "busyPanel");
|
|
898
969
|
function analyzingPanel(username) {
|
|
899
970
|
return panel({
|
|
900
971
|
icon: "⏳",
|
|
@@ -906,7 +977,7 @@ function apply(ctx, config) {
|
|
|
906
977
|
__name(analyzingPanel, "analyzingPanel");
|
|
907
978
|
function notYourTurnPanel(username, yourSide, currentSide) {
|
|
908
979
|
return panel({
|
|
909
|
-
icon: "
|
|
980
|
+
icon: "⏳",
|
|
910
981
|
title: "尚未轮到你",
|
|
911
982
|
at: username,
|
|
912
983
|
body: [field("你的阵营", withSideIcon(yourSide)), field("当前轮走", withSideIcon(currentSide))],
|
|
@@ -916,11 +987,11 @@ function apply(ctx, config) {
|
|
|
916
987
|
__name(notYourTurnPanel, "notYourTurnPanel");
|
|
917
988
|
function invalidMovePanel(username, reason) {
|
|
918
989
|
return panel({
|
|
919
|
-
icon: "
|
|
990
|
+
icon: "⚠️",
|
|
920
991
|
title: reason,
|
|
921
992
|
at: username,
|
|
922
993
|
body: ["请确认着法是否合乎棋规。"],
|
|
923
|
-
tips: ["纵线:炮二平五 / 炮8平5
|
|
994
|
+
tips: ["纵线:炮二平五 / 炮8平5 · 坐标:b2e2", "详见 https://www.xqbase.com/protocol/cchess_move.htm"]
|
|
924
995
|
});
|
|
925
996
|
}
|
|
926
997
|
__name(invalidMovePanel, "invalidMovePanel");
|
|
@@ -937,15 +1008,21 @@ function apply(ctx, config) {
|
|
|
937
1008
|
function invalidLeaderboardSizePanel(username) {
|
|
938
1009
|
return panel({
|
|
939
1010
|
icon: "⚠️",
|
|
940
|
-
title: "
|
|
1011
|
+
title: "排行榜人数有误",
|
|
941
1012
|
at: username,
|
|
942
|
-
body: ["
|
|
943
|
-
tips: ["
|
|
1013
|
+
body: ["请发送不小于 0 的整数。"],
|
|
1014
|
+
tips: ["发送「cchess.战绩 榜 10」可查询前 10 名"]
|
|
944
1015
|
});
|
|
945
1016
|
}
|
|
946
1017
|
__name(invalidLeaderboardSizePanel, "invalidLeaderboardSizePanel");
|
|
947
1018
|
async function renderBoard(channelId) {
|
|
948
|
-
|
|
1019
|
+
if (config.disableImages) return void 0;
|
|
1020
|
+
try {
|
|
1021
|
+
return import_koishi.h.image(await drawChessBoard(channelId), imageMimeType).toString();
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
logger.warn("棋盘渲染失败,本次只发文本:%s", error.message);
|
|
1024
|
+
return void 0;
|
|
1025
|
+
}
|
|
949
1026
|
}
|
|
950
1027
|
__name(renderBoard, "renderBoard");
|
|
951
1028
|
async function settleVictory(channelId, winSide, loseSide, title, options = {}) {
|
|
@@ -963,8 +1040,7 @@ function apply(ctx, config) {
|
|
|
963
1040
|
at,
|
|
964
1041
|
body: [
|
|
965
1042
|
...extra,
|
|
966
|
-
field("胜方", withSideIcon(convertTurnToString(winSide))),
|
|
967
|
-
field("棋手", winners.length ? winners.map((player) => `@${player.username}`).join("、") : "皮卡鱼")
|
|
1043
|
+
field("胜方", withSideIcon(convertTurnToString(winSide))) + " " + field("棋手", winners.length ? winners.map((player) => `@${player.username}`).join("、") : "皮卡鱼")
|
|
968
1044
|
],
|
|
969
1045
|
tips: ["发送「cchess.开始」再战一局"],
|
|
970
1046
|
image
|
|
@@ -1005,8 +1081,7 @@ function apply(ctx, config) {
|
|
|
1005
1081
|
body: [
|
|
1006
1082
|
field("棋 手", withSideIcon(humanSide)),
|
|
1007
1083
|
field("皮卡鱼", withSideIcon(engineSide)),
|
|
1008
|
-
field("先 手", withSideIcon(sideString)),
|
|
1009
|
-
field("思考深度", `${thinkingDepth} 层`)
|
|
1084
|
+
field("先 手", withSideIcon(sideString)) + " " + field("思考深度", `${thinkingDepth} 层`)
|
|
1010
1085
|
],
|
|
1011
1086
|
tips: ["直接发送着法即可落子,如「炮二平五」或「b2e2」"],
|
|
1012
1087
|
image: await renderBoard(channelId)
|
|
@@ -1031,6 +1106,15 @@ function apply(ctx, config) {
|
|
|
1031
1106
|
if (playerRecord.length === 0) return await sendMessage(session, notJoinedPanel(username));
|
|
1032
1107
|
const sideString = convertTurnToString(gameRecord.turn);
|
|
1033
1108
|
if (playerRecord[0].side !== sideString) {
|
|
1109
|
+
if (!agree) {
|
|
1110
|
+
await ctx.database.set("cchess_game_records", { channelId }, { isRegretRequest: false });
|
|
1111
|
+
return await sendMessage(session, panel({
|
|
1112
|
+
icon: "✅",
|
|
1113
|
+
title: "悔棋请求已撤下",
|
|
1114
|
+
at: username,
|
|
1115
|
+
body: ["对局继续,落子无悔。"]
|
|
1116
|
+
}));
|
|
1117
|
+
}
|
|
1034
1118
|
return await sendMessage(session, panel({
|
|
1035
1119
|
icon: "⚠️",
|
|
1036
1120
|
title: "无权表决",
|
|
@@ -1044,7 +1128,7 @@ function apply(ctx, config) {
|
|
|
1044
1128
|
icon: "⚠️",
|
|
1045
1129
|
title: "悔棋被拒",
|
|
1046
1130
|
at: username,
|
|
1047
|
-
body: ["
|
|
1131
|
+
body: ["落子无悔,对局继续。"],
|
|
1048
1132
|
image: await renderBoard(channelId)
|
|
1049
1133
|
}));
|
|
1050
1134
|
}
|
|
@@ -1109,8 +1193,8 @@ function apply(ctx, config) {
|
|
|
1109
1193
|
return await sendMessage(session, panel({
|
|
1110
1194
|
icon,
|
|
1111
1195
|
title,
|
|
1112
|
-
body: topPlayers.length ? topPlayers.map((player, index) => `${MEDALS[index] ?? `${index + 1}.`} ${player.username} ${player[sortField]} 局`) : ["
|
|
1113
|
-
tips: topPlayers.length ? [] : ["发送「cchess
|
|
1196
|
+
body: topPlayers.length ? topPlayers.map((player, index) => `${MEDALS[index] ?? `${index + 1}.`} ${player.username} ${player[sortField]} 局`) : ["榜上尚无名姓。", "第一局分出胜负,名字就会写在这里。"],
|
|
1197
|
+
tips: topPlayers.length ? [] : ["发送「cchess.开始」开启一局"]
|
|
1114
1198
|
}));
|
|
1115
1199
|
}
|
|
1116
1200
|
__name(getLeaderboard, "getLeaderboard");
|
|
@@ -1632,23 +1716,25 @@ function apply(ctx, config) {
|
|
|
1632
1716
|
const rowAt = /* @__PURE__ */ __name((row) => FRAME_BOARD.y + (row * CELL_WIDTH + BOARD_PADDING) * FRAME_ZOOM, "rowAt");
|
|
1633
1717
|
context.textAlign = "center";
|
|
1634
1718
|
context.textBaseline = "middle";
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1719
|
+
const labelFont = `${EMPHASIZED_WEIGHT.title} ${TYPE.titleLarge.size}px ${FONT_STACK}`;
|
|
1720
|
+
const labelSideFont = `${EMPHASIZED_WEIGHT.headline} ${TYPE.headlineSmall.size}px ${FONT_STACK}`;
|
|
1721
|
+
const label = /* @__PURE__ */ __name((text, x, y, font, color) => {
|
|
1722
|
+
context.font = font;
|
|
1637
1723
|
context.fillStyle = color;
|
|
1638
1724
|
context.fillText(text, x, y);
|
|
1639
1725
|
}, "label");
|
|
1640
1726
|
for (let col = 0; col < 9; col++) {
|
|
1641
1727
|
const x = columnAt(isFlipBoard ? 8 - col : col);
|
|
1642
|
-
label(FILES[col], x, 30,
|
|
1643
|
-
label(String(col + 1), x, 64,
|
|
1644
|
-
label(RED_FILES[col], x, FRAME_HEIGHT - 62,
|
|
1645
|
-
label(FILES[col], x, FRAME_HEIGHT - 26,
|
|
1728
|
+
label(FILES[col], x, 30, labelFont, FRAME.onSurfaceVariant);
|
|
1729
|
+
label(String(col + 1), x, 64, labelFont, FRAME.onSurfaceVariant);
|
|
1730
|
+
label(RED_FILES[col], x, FRAME_HEIGHT - 62, labelSideFont, FRAME.onSurfaceVariant);
|
|
1731
|
+
label(FILES[col], x, FRAME_HEIGHT - 26, labelFont, FRAME.onSurfaceVariant);
|
|
1646
1732
|
}
|
|
1647
1733
|
for (let row = 0; row < 10; row++) {
|
|
1648
1734
|
const y = rowAt(isFlipBoard ? 9 - row : row);
|
|
1649
1735
|
const text = String(9 - row);
|
|
1650
|
-
label(text, 19, y,
|
|
1651
|
-
label(text, FRAME_WIDTH - 19, y,
|
|
1736
|
+
label(text, 19, y, labelFont, FRAME.onSurfaceVariant);
|
|
1737
|
+
label(text, FRAME_WIDTH - 19, y, labelFont, FRAME.onSurfaceVariant);
|
|
1652
1738
|
}
|
|
1653
1739
|
return canvas.toBuffer(imageMimeType);
|
|
1654
1740
|
}
|
|
@@ -2132,7 +2218,7 @@ function apply(ctx, config) {
|
|
|
2132
2218
|
}
|
|
2133
2219
|
}
|
|
2134
2220
|
__name(drawChessBoard, "drawChessBoard");
|
|
2135
|
-
async function drawLineArrow(ctx2, fromX, fromY, toX, toY, color =
|
|
2221
|
+
async function drawLineArrow(ctx2, fromX, fromY, toX, toY, color = FRAME.onSurface, headSize = 20) {
|
|
2136
2222
|
var angle = Math.atan2(toY - fromY, toX - fromX);
|
|
2137
2223
|
var headAngle = 45 * Math.PI / 180;
|
|
2138
2224
|
ctx2.beginPath();
|