koishi-plugin-cchess 2.1.0 → 2.2.1
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 +2 -1
- package/lib/index.js +122 -54
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# koishi-plugin-cchess
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
中国象棋 · 人人对战与皮卡鱼人机对战
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
@@ -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
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,6 +1,6 @@
|
|
|
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;
|
|
@@ -11,6 +11,7 @@ export interface Config {
|
|
|
11
11
|
defaultMaxLeaderboardEntries: number;
|
|
12
12
|
retractDelay: number;
|
|
13
13
|
isChessImageWithOutlineEnabled: boolean;
|
|
14
|
+
disableImages: boolean;
|
|
14
15
|
imgScale: number;
|
|
15
16
|
imageType: "png" | "jpeg" | "webp";
|
|
16
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"];
|
|
@@ -270,11 +289,12 @@ var Config = import_koishi.Schema.object({
|
|
|
270
289
|
allowFreePieceMovementInHumanMachineMode: import_koishi.Schema.boolean().default(false).description(`人机模式下,允许所有人自由移动棋子,开启后可以不入座直接开始人机对局。`),
|
|
271
290
|
enableDirectInput: import_koishi.Schema.boolean().default(true).description(`对局中直接发送棋谱或「同意」「拒绝」即可落子与应答,无需指令前缀。`),
|
|
272
291
|
defaultEngineThinkingDepth: import_koishi.Schema.number().min(0).max(100).default(10).description(`默认引擎思考深度,越高 AI 棋力越强,耗时也越长(小于 1 时按 1 计算)。由于 Nodejs 不支持 SIMD,所以不建议设置过高。`),
|
|
273
|
-
defaultMaxLeaderboardEntries: import_koishi.Schema.number().min(0).default(
|
|
292
|
+
defaultMaxLeaderboardEntries: import_koishi.Schema.number().min(0).default(4).description(`排行榜默认显示的人数。`),
|
|
274
293
|
retractDelay: import_koishi.Schema.number().min(0).default(0).description(`自动撤回延迟(秒),0 表示不撤回。`),
|
|
275
294
|
imgScale: import_koishi.Schema.number().min(1).default(1).description(`图片分辨率倍率。`),
|
|
276
295
|
imageType: import_koishi.Schema.union(["png", "jpeg", "webp"]).default("png").description(`发送的图片格式。`),
|
|
277
|
-
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(`全部改用文本,不发送棋盘图片。`)
|
|
278
298
|
});
|
|
279
299
|
var SIDE_ICONS = { 红方: "🔴", 黑方: "⚫" };
|
|
280
300
|
var MEDALS = ["🥇", "🥈", "🥉"];
|
|
@@ -291,7 +311,7 @@ function isLine(line) {
|
|
|
291
311
|
}
|
|
292
312
|
__name(isLine, "isLine");
|
|
293
313
|
function panel(options) {
|
|
294
|
-
const { icon
|
|
314
|
+
const { icon, title, at, body = [], tips = [], image } = options;
|
|
295
315
|
const lines = [`${icon} ${title}`];
|
|
296
316
|
if (at) lines.push(`@${at}`);
|
|
297
317
|
lines.push(...body.filter(isLine));
|
|
@@ -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) => {
|
|
@@ -464,7 +485,19 @@ function apply(ctx, config) {
|
|
|
464
485
|
ctx.command("cchess", "中国象棋 · 楚河汉界").action(async ({ session }) => {
|
|
465
486
|
await session.execute(`cchess -h`);
|
|
466
487
|
});
|
|
467
|
-
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) {
|
|
468
501
|
const { username, userId, channelId } = session;
|
|
469
502
|
await updateNameInPlayerRecord(userId, username);
|
|
470
503
|
const gameRecord = await getGameRecord(channelId);
|
|
@@ -522,11 +555,10 @@ function apply(ctx, config) {
|
|
|
522
555
|
title: "已入座,静候对手",
|
|
523
556
|
at: username,
|
|
524
557
|
body: [
|
|
525
|
-
field("阵营", withSideIcon(selfSide)),
|
|
526
|
-
field("当前人数", `${players.length} 人`),
|
|
558
|
+
field("阵营", withSideIcon(selfSide)) + " " + field("当前人数", `${players.length} 人`),
|
|
527
559
|
!blackPlayers.length ? "黑方尚无人。" : "红方尚无人。"
|
|
528
560
|
],
|
|
529
|
-
tips: ["好友发送「cchess
|
|
561
|
+
tips: ["好友发送「cchess.开始」即可补位,指定阵营可发「cchess.开始 黑」"]
|
|
530
562
|
}));
|
|
531
563
|
}
|
|
532
564
|
await ctx.database.set("cchess_game_records", { channelId }, { isStarted: true });
|
|
@@ -535,19 +567,16 @@ function apply(ctx, config) {
|
|
|
535
567
|
icon: "✅",
|
|
536
568
|
title: "楚河汉界,对局开始",
|
|
537
569
|
body: [
|
|
538
|
-
`${withSideIcon("红方")}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
`${withSideIcon("黑方")}(${blackPlayers.length})`,
|
|
542
|
-
...blackPlayers.map((player) => ` @${player.username}`),
|
|
543
|
-
"",
|
|
544
|
-
field("先手", withSideIcon(sideString))
|
|
570
|
+
`${withSideIcon("红方")} · ${redPlayers.map((player) => `@${player.username}`).join("、")}`,
|
|
571
|
+
`${withSideIcon("黑方")} · ${blackPlayers.map((player) => `@${player.username}`).join("、")}`,
|
|
572
|
+
`先手 · ${withSideIcon(sideString)}`
|
|
545
573
|
],
|
|
546
574
|
tips: ["直接发送着法即可落子,如「炮二平五」或「b2e2」"],
|
|
547
575
|
image: await renderBoard(channelId)
|
|
548
576
|
}));
|
|
549
|
-
}
|
|
550
|
-
|
|
577
|
+
}
|
|
578
|
+
__name(startGame, "startGame");
|
|
579
|
+
ctx.command("cchess.结束", "结束正在进行的一局").userFields(["id", "name", "authority"]).action(async ({ session }) => {
|
|
551
580
|
const { username, userId, channelId } = session;
|
|
552
581
|
await updateNameInPlayerRecord(userId, username);
|
|
553
582
|
const gameRecord = await getGameRecord(channelId);
|
|
@@ -560,7 +589,24 @@ function apply(ctx, config) {
|
|
|
560
589
|
tips: ["发送「cchess.开始」入座,再开一局"]
|
|
561
590
|
}));
|
|
562
591
|
}
|
|
563
|
-
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
|
+
}
|
|
564
610
|
return await sendMessage(session, panel({
|
|
565
611
|
icon: "✅",
|
|
566
612
|
title: "对局已结束",
|
|
@@ -569,6 +615,9 @@ function apply(ctx, config) {
|
|
|
569
615
|
tips: ["发送「cchess.开始」重整旗鼓"]
|
|
570
616
|
}));
|
|
571
617
|
});
|
|
618
|
+
ctx.command("cchess.落子 [move:string]", "发送着法,如「炮二平五」").usage("着法可用中式纵线记谱,也可用坐标,如 `b2e2`。").example("cchess.落子 炮二平五").action(async ({ session }, move) => {
|
|
619
|
+
return await handleMove(session, move);
|
|
620
|
+
});
|
|
572
621
|
async function handleMove(session, moveOperation) {
|
|
573
622
|
const { username, userId, channelId } = session;
|
|
574
623
|
await updateNameInPlayerRecord(userId, username);
|
|
@@ -577,12 +626,7 @@ function apply(ctx, config) {
|
|
|
577
626
|
return await sendMessage(session, invalidMovePanel(username, "看不懂这一着"));
|
|
578
627
|
}
|
|
579
628
|
if (busyChannels.has(channelId)) {
|
|
580
|
-
return await sendMessage(session,
|
|
581
|
-
icon: "⏳",
|
|
582
|
-
title: "对局正在推演",
|
|
583
|
-
at: username,
|
|
584
|
-
body: ["上一着棋尚未结算,稍候片刻。"]
|
|
585
|
-
}));
|
|
629
|
+
return await sendMessage(session, busyPanel(username));
|
|
586
630
|
}
|
|
587
631
|
busyChannels.add(channelId);
|
|
588
632
|
try {
|
|
@@ -681,6 +725,7 @@ function apply(ctx, config) {
|
|
|
681
725
|
}
|
|
682
726
|
const newMoves = record.moveList.slice(movesBefore);
|
|
683
727
|
return await sendMessage(session, panel({
|
|
728
|
+
icon: "✅",
|
|
684
729
|
title: `第 ${record.moveList.length} 手`,
|
|
685
730
|
body: [
|
|
686
731
|
...newMoves.map((move) => `${withSideIcon(convertTurnToString(move.side))} ${move.chnMoveName} ${move.move}`),
|
|
@@ -693,7 +738,7 @@ function apply(ctx, config) {
|
|
|
693
738
|
}
|
|
694
739
|
}
|
|
695
740
|
__name(handleMove, "handleMove");
|
|
696
|
-
ctx.command("cchess.悔棋 [decision:string]", "请求悔棋或答复对方").usage("
|
|
741
|
+
ctx.command("cchess.悔棋 [decision:string]", "请求悔棋或答复对方").usage("请求送出后,对方直接回复「同意」或「拒绝」即可表决,无需指令前缀;请求方发送「cchess.悔棋 拒绝」可撤下请求。").action(async ({ session }, decision) => {
|
|
697
742
|
const { username, userId, channelId } = session;
|
|
698
743
|
await updateNameInPlayerRecord(userId, username);
|
|
699
744
|
if (decision) {
|
|
@@ -757,7 +802,10 @@ function apply(ctx, config) {
|
|
|
757
802
|
}));
|
|
758
803
|
}
|
|
759
804
|
await ctx.database.set("cchess_game_records", { channelId }, { isRegretRequest: true });
|
|
805
|
+
const ticket = (regretTicket.get(channelId) ?? 0) + 1;
|
|
806
|
+
regretTicket.set(channelId, ticket);
|
|
760
807
|
ctx.setTimeout(async () => {
|
|
808
|
+
if (regretTicket.get(channelId) !== ticket) return;
|
|
761
809
|
const current = await getGameRecord(channelId);
|
|
762
810
|
if (!current?.isRegretRequest) return;
|
|
763
811
|
await ctx.database.set("cchess_game_records", { channelId }, { isRegretRequest: false });
|
|
@@ -772,11 +820,14 @@ function apply(ctx, config) {
|
|
|
772
820
|
icon: "⏳",
|
|
773
821
|
title: "悔棋请求已送出",
|
|
774
822
|
at: username,
|
|
775
|
-
body: [
|
|
776
|
-
|
|
823
|
+
body: [
|
|
824
|
+
field("等待答复", withSideIcon(sideString)),
|
|
825
|
+
"对方回复「同意」或「拒绝」即可表决,30 秒等不到答复会自动作废。"
|
|
826
|
+
],
|
|
827
|
+
tips: ["发送「cchess.悔棋 拒绝」可撤下这次请求"]
|
|
777
828
|
}));
|
|
778
829
|
});
|
|
779
|
-
ctx.command("cchess.认输", "
|
|
830
|
+
ctx.command("cchess.认输", "认输并判对方获胜").action(async ({ session }) => {
|
|
780
831
|
const { username, userId, channelId } = session;
|
|
781
832
|
await updateNameInPlayerRecord(userId, username);
|
|
782
833
|
const gameRecord = await getGameRecord(channelId);
|
|
@@ -871,10 +922,8 @@ function apply(ctx, config) {
|
|
|
871
922
|
at: session.username,
|
|
872
923
|
body: [
|
|
873
924
|
field("查询对象", username),
|
|
874
|
-
field("胜 场", `${win} 局`),
|
|
875
|
-
field("
|
|
876
|
-
field("总对局", `${total} 局`),
|
|
877
|
-
field("胜 率", winRate)
|
|
925
|
+
field("胜 场", `${win} 局`) + " " + field("负 场", `${lose} 局`),
|
|
926
|
+
field("总对局", `${total} 局`) + " " + field("胜 率", winRate)
|
|
878
927
|
]
|
|
879
928
|
}));
|
|
880
929
|
});
|
|
@@ -884,7 +933,7 @@ function apply(ctx, config) {
|
|
|
884
933
|
title: "对局尚未开始",
|
|
885
934
|
at: username,
|
|
886
935
|
body: ["当前频道还没有进行中的对局。"],
|
|
887
|
-
tips: ["「cchess
|
|
936
|
+
tips: ["「cchess.开始」入座对弈 · 「cchess.开始 人机」挑战皮卡鱼"]
|
|
888
937
|
});
|
|
889
938
|
}
|
|
890
939
|
__name(notStartedPanel, "notStartedPanel");
|
|
@@ -894,7 +943,7 @@ function apply(ctx, config) {
|
|
|
894
943
|
title: "对局已经开战",
|
|
895
944
|
at: username,
|
|
896
945
|
body: ["本局尚未结束,不必重开。"],
|
|
897
|
-
tips: ["「cchess.认输」结束本局
|
|
946
|
+
tips: ["「cchess.认输」结束本局 · 「cchess.结束」清盘"]
|
|
898
947
|
});
|
|
899
948
|
}
|
|
900
949
|
__name(alreadyStartedPanel, "alreadyStartedPanel");
|
|
@@ -908,6 +957,15 @@ function apply(ctx, config) {
|
|
|
908
957
|
});
|
|
909
958
|
}
|
|
910
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");
|
|
911
969
|
function analyzingPanel(username) {
|
|
912
970
|
return panel({
|
|
913
971
|
icon: "⏳",
|
|
@@ -933,7 +991,7 @@ function apply(ctx, config) {
|
|
|
933
991
|
title: reason,
|
|
934
992
|
at: username,
|
|
935
993
|
body: ["请确认着法是否合乎棋规。"],
|
|
936
|
-
tips: ["纵线:炮二平五 / 炮8平5
|
|
994
|
+
tips: ["纵线:炮二平五 / 炮8平5 · 坐标:b2e2", "详见 https://www.xqbase.com/protocol/cchess_move.htm"]
|
|
937
995
|
});
|
|
938
996
|
}
|
|
939
997
|
__name(invalidMovePanel, "invalidMovePanel");
|
|
@@ -952,12 +1010,13 @@ function apply(ctx, config) {
|
|
|
952
1010
|
icon: "⚠️",
|
|
953
1011
|
title: "排行榜人数有误",
|
|
954
1012
|
at: username,
|
|
955
|
-
body: ["
|
|
956
|
-
tips: ["
|
|
1013
|
+
body: ["请发送不小于 0 的整数。"],
|
|
1014
|
+
tips: ["发送「cchess.战绩 榜 10」可查询前 10 名"]
|
|
957
1015
|
});
|
|
958
1016
|
}
|
|
959
1017
|
__name(invalidLeaderboardSizePanel, "invalidLeaderboardSizePanel");
|
|
960
1018
|
async function renderBoard(channelId) {
|
|
1019
|
+
if (config.disableImages) return void 0;
|
|
961
1020
|
try {
|
|
962
1021
|
return import_koishi.h.image(await drawChessBoard(channelId), imageMimeType).toString();
|
|
963
1022
|
} catch (error) {
|
|
@@ -981,8 +1040,7 @@ function apply(ctx, config) {
|
|
|
981
1040
|
at,
|
|
982
1041
|
body: [
|
|
983
1042
|
...extra,
|
|
984
|
-
field("胜方", withSideIcon(convertTurnToString(winSide))),
|
|
985
|
-
field("棋手", winners.length ? winners.map((player) => `@${player.username}`).join("、") : "皮卡鱼")
|
|
1043
|
+
field("胜方", withSideIcon(convertTurnToString(winSide))) + " " + field("棋手", winners.length ? winners.map((player) => `@${player.username}`).join("、") : "皮卡鱼")
|
|
986
1044
|
],
|
|
987
1045
|
tips: ["发送「cchess.开始」再战一局"],
|
|
988
1046
|
image
|
|
@@ -1023,8 +1081,7 @@ function apply(ctx, config) {
|
|
|
1023
1081
|
body: [
|
|
1024
1082
|
field("棋 手", withSideIcon(humanSide)),
|
|
1025
1083
|
field("皮卡鱼", withSideIcon(engineSide)),
|
|
1026
|
-
field("先 手", withSideIcon(sideString)),
|
|
1027
|
-
field("思考深度", `${thinkingDepth} 层`)
|
|
1084
|
+
field("先 手", withSideIcon(sideString)) + " " + field("思考深度", `${thinkingDepth} 层`)
|
|
1028
1085
|
],
|
|
1029
1086
|
tips: ["直接发送着法即可落子,如「炮二平五」或「b2e2」"],
|
|
1030
1087
|
image: await renderBoard(channelId)
|
|
@@ -1049,6 +1106,15 @@ function apply(ctx, config) {
|
|
|
1049
1106
|
if (playerRecord.length === 0) return await sendMessage(session, notJoinedPanel(username));
|
|
1050
1107
|
const sideString = convertTurnToString(gameRecord.turn);
|
|
1051
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
|
+
}
|
|
1052
1118
|
return await sendMessage(session, panel({
|
|
1053
1119
|
icon: "⚠️",
|
|
1054
1120
|
title: "无权表决",
|
|
@@ -1127,8 +1193,8 @@ function apply(ctx, config) {
|
|
|
1127
1193
|
return await sendMessage(session, panel({
|
|
1128
1194
|
icon,
|
|
1129
1195
|
title,
|
|
1130
|
-
body: topPlayers.length ? topPlayers.map((player, index) => `${MEDALS[index] ?? `${index + 1}.`} ${player.username} ${player[sortField]} 局`) : ["
|
|
1131
|
-
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.开始」开启一局"]
|
|
1132
1198
|
}));
|
|
1133
1199
|
}
|
|
1134
1200
|
__name(getLeaderboard, "getLeaderboard");
|
|
@@ -1650,23 +1716,25 @@ function apply(ctx, config) {
|
|
|
1650
1716
|
const rowAt = /* @__PURE__ */ __name((row) => FRAME_BOARD.y + (row * CELL_WIDTH + BOARD_PADDING) * FRAME_ZOOM, "rowAt");
|
|
1651
1717
|
context.textAlign = "center";
|
|
1652
1718
|
context.textBaseline = "middle";
|
|
1653
|
-
const
|
|
1654
|
-
|
|
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;
|
|
1655
1723
|
context.fillStyle = color;
|
|
1656
1724
|
context.fillText(text, x, y);
|
|
1657
1725
|
}, "label");
|
|
1658
1726
|
for (let col = 0; col < 9; col++) {
|
|
1659
1727
|
const x = columnAt(isFlipBoard ? 8 - col : col);
|
|
1660
|
-
label(FILES[col], x, 30,
|
|
1661
|
-
label(String(col + 1), x, 64,
|
|
1662
|
-
label(RED_FILES[col], x, FRAME_HEIGHT - 62,
|
|
1663
|
-
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);
|
|
1664
1732
|
}
|
|
1665
1733
|
for (let row = 0; row < 10; row++) {
|
|
1666
1734
|
const y = rowAt(isFlipBoard ? 9 - row : row);
|
|
1667
1735
|
const text = String(9 - row);
|
|
1668
|
-
label(text, 19, y,
|
|
1669
|
-
label(text, FRAME_WIDTH - 19, y,
|
|
1736
|
+
label(text, 19, y, labelFont, FRAME.onSurfaceVariant);
|
|
1737
|
+
label(text, FRAME_WIDTH - 19, y, labelFont, FRAME.onSurfaceVariant);
|
|
1670
1738
|
}
|
|
1671
1739
|
return canvas.toBuffer(imageMimeType);
|
|
1672
1740
|
}
|
|
@@ -2150,7 +2218,7 @@ function apply(ctx, config) {
|
|
|
2150
2218
|
}
|
|
2151
2219
|
}
|
|
2152
2220
|
__name(drawChessBoard, "drawChessBoard");
|
|
2153
|
-
async function drawLineArrow(ctx2, fromX, fromY, toX, toY, color =
|
|
2221
|
+
async function drawLineArrow(ctx2, fromX, fromY, toX, toY, color = FRAME.onSurface, headSize = 20) {
|
|
2154
2222
|
var angle = Math.atan2(toY - fromY, toX - fromX);
|
|
2155
2223
|
var headAngle = 45 * Math.PI / 180;
|
|
2156
2224
|
ctx2.beginPath();
|