koishi-plugin-cchess 2.0.0 → 2.1.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 +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +44 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ yarn add koishi-plugin-cchess
|
|
|
19
19
|
| `cchess.悔棋 [同意/拒绝]` | 请求悔棋,也可直接回复同意或拒绝表决 |
|
|
20
20
|
| `cchess.认输` | 认输 |
|
|
21
21
|
| `cchess.结束` | 强制结束棋局 |
|
|
22
|
-
| `cchess
|
|
22
|
+
| `cchess.战绩 [@某人/榜] [胜场/输场] [人数]` | 查询战绩与排行榜 |
|
|
23
23
|
| `cchess.查看云库残局 [DTM/DTC]` | 云库残局统计 |
|
|
24
24
|
|
|
25
25
|
发送 `cchess.开始` 即入座,双方就位后自动开局,无需单独的加入与退出指令。
|
package/lib/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface Config {
|
|
|
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;
|
package/lib/index.js
CHANGED
|
@@ -268,6 +268,7 @@ var Config = import_koishi.Schema.object({
|
|
|
268
268
|
boardSkin: import_koishi.Schema.union(boardSkins).default("象甲2023棋盘").description(`棋盘皮肤。`),
|
|
269
269
|
pieceSkin: import_koishi.Schema.union(pieceSkins).default("象甲棋子").description(`棋子皮肤。`),
|
|
270
270
|
allowFreePieceMovementInHumanMachineMode: import_koishi.Schema.boolean().default(false).description(`人机模式下,允许所有人自由移动棋子,开启后可以不入座直接开始人机对局。`),
|
|
271
|
+
enableDirectInput: import_koishi.Schema.boolean().default(true).description(`对局中直接发送棋谱或「同意」「拒绝」即可落子与应答,无需指令前缀。`),
|
|
271
272
|
defaultEngineThinkingDepth: import_koishi.Schema.number().min(0).max(100).default(10).description(`默认引擎思考深度,越高 AI 棋力越强,耗时也越长(小于 1 时按 1 计算)。由于 Nodejs 不支持 SIMD,所以不建议设置过高。`),
|
|
272
273
|
defaultMaxLeaderboardEntries: import_koishi.Schema.number().min(0).default(10).description(`排行榜默认显示的人数。`),
|
|
273
274
|
retractDelay: import_koishi.Schema.number().min(0).default(0).description(`自动撤回延迟(秒),0 表示不撤回。`),
|
|
@@ -275,7 +276,6 @@ var Config = import_koishi.Schema.object({
|
|
|
275
276
|
imageType: import_koishi.Schema.union(["png", "jpeg", "webp"]).default("png").description(`发送的图片格式。`),
|
|
276
277
|
isChessImageWithOutlineEnabled: import_koishi.Schema.boolean().default(true).description(`给棋盘图片加上坐标外框。关闭后出图明显更快,但没有外框,玩起来可能会比较累。`)
|
|
277
278
|
});
|
|
278
|
-
var RULE = "━━━━━━━━━━━━━━";
|
|
279
279
|
var SIDE_ICONS = { 红方: "🔴", 黑方: "⚫" };
|
|
280
280
|
var MEDALS = ["🥇", "🥈", "🥉"];
|
|
281
281
|
function withSideIcon(side) {
|
|
@@ -292,11 +292,11 @@ function isLine(line) {
|
|
|
292
292
|
__name(isLine, "isLine");
|
|
293
293
|
function panel(options) {
|
|
294
294
|
const { icon = "♟️", title, at, body = [], tips = [], image } = options;
|
|
295
|
-
const lines = [`${icon} ${title}
|
|
295
|
+
const lines = [`${icon} ${title}`];
|
|
296
296
|
if (at) lines.push(`@${at}`);
|
|
297
297
|
lines.push(...body.filter(isLine));
|
|
298
298
|
const validTips = tips.filter(isLine).filter(Boolean);
|
|
299
|
-
if (validTips.length) lines.push(
|
|
299
|
+
if (validTips.length) lines.push(...validTips);
|
|
300
300
|
if (image) lines.push(image);
|
|
301
301
|
return lines.join("\n");
|
|
302
302
|
}
|
|
@@ -430,6 +430,7 @@ function apply(ctx, config) {
|
|
|
430
430
|
}
|
|
431
431
|
__name(matchRegretDecision, "matchRegretDecision");
|
|
432
432
|
ctx.middleware(async (session, next) => {
|
|
433
|
+
if (!config.enableDirectInput) return next();
|
|
433
434
|
const { channelId, content, userId } = session;
|
|
434
435
|
if (!channelId || !content) return next();
|
|
435
436
|
const decision = matchRegretDecision(content);
|
|
@@ -441,6 +442,7 @@ function apply(ctx, config) {
|
|
|
441
442
|
await resolveRegret(session, decision === "agree");
|
|
442
443
|
});
|
|
443
444
|
ctx.middleware(async (session, next) => {
|
|
445
|
+
if (!config.enableDirectInput) return next();
|
|
444
446
|
const { channelId, content, userId, username } = session;
|
|
445
447
|
if (!channelId || !content) return next();
|
|
446
448
|
const moveOperation = content.trim();
|
|
@@ -551,7 +553,7 @@ function apply(ctx, config) {
|
|
|
551
553
|
const gameRecord = await getGameRecord(channelId);
|
|
552
554
|
if (!gameRecord.isStarted) {
|
|
553
555
|
return await sendMessage(session, panel({
|
|
554
|
-
icon: "
|
|
556
|
+
icon: "💡",
|
|
555
557
|
title: "棋盘尚且空空",
|
|
556
558
|
at: username,
|
|
557
559
|
body: ["当前没有进行中的对局,无需收拾残局。"],
|
|
@@ -577,7 +579,7 @@ function apply(ctx, config) {
|
|
|
577
579
|
if (busyChannels.has(channelId)) {
|
|
578
580
|
return await sendMessage(session, panel({
|
|
579
581
|
icon: "⏳",
|
|
580
|
-
title: "
|
|
582
|
+
title: "对局正在推演",
|
|
581
583
|
at: username,
|
|
582
584
|
body: ["上一着棋尚未结算,稍候片刻。"]
|
|
583
585
|
}));
|
|
@@ -593,7 +595,7 @@ function apply(ctx, config) {
|
|
|
593
595
|
icon: "⏳",
|
|
594
596
|
title: "悔棋请求待决",
|
|
595
597
|
at: username,
|
|
596
|
-
body: ["
|
|
598
|
+
body: ["对方的悔棋请求尚未答复,对局暂歇。"],
|
|
597
599
|
tips: ["对方回复「同意」或「拒绝」即可表决"]
|
|
598
600
|
}));
|
|
599
601
|
}
|
|
@@ -727,8 +729,8 @@ function apply(ctx, config) {
|
|
|
727
729
|
}
|
|
728
730
|
if (gameRecord.moveList.length < 1) {
|
|
729
731
|
return await sendMessage(session, panel({
|
|
730
|
-
icon: "
|
|
731
|
-
title: "
|
|
732
|
+
icon: "💡",
|
|
733
|
+
title: "对局初开",
|
|
732
734
|
at: username,
|
|
733
735
|
body: ["一子未落,无悔可言。"]
|
|
734
736
|
}));
|
|
@@ -755,12 +757,23 @@ function apply(ctx, config) {
|
|
|
755
757
|
}));
|
|
756
758
|
}
|
|
757
759
|
await ctx.database.set("cchess_game_records", { channelId }, { isRegretRequest: true });
|
|
760
|
+
ctx.setTimeout(async () => {
|
|
761
|
+
const current = await getGameRecord(channelId);
|
|
762
|
+
if (!current?.isRegretRequest) return;
|
|
763
|
+
await ctx.database.set("cchess_game_records", { channelId }, { isRegretRequest: false });
|
|
764
|
+
await sendMessage(session, panel({
|
|
765
|
+
icon: "⏳",
|
|
766
|
+
title: "悔棋请求已作罢",
|
|
767
|
+
body: ["等了 30 秒没有等到答复,请求自动撤回。"],
|
|
768
|
+
tips: ["发送「cchess.悔棋」可以再提一次。"]
|
|
769
|
+
}));
|
|
770
|
+
}, 30 * 1e3);
|
|
758
771
|
return await sendMessage(session, panel({
|
|
759
772
|
icon: "⏳",
|
|
760
773
|
title: "悔棋请求已送出",
|
|
761
774
|
at: username,
|
|
762
775
|
body: [field("等待答复", withSideIcon(sideString))],
|
|
763
|
-
tips: ["
|
|
776
|
+
tips: ["对方回复「同意」或「拒绝」即可表决;30 秒等不到答复会自动作废"]
|
|
764
777
|
}));
|
|
765
778
|
});
|
|
766
779
|
ctx.command("cchess.认输", "认输并结束对局").action(async ({ session }) => {
|
|
@@ -823,7 +836,7 @@ function apply(ctx, config) {
|
|
|
823
836
|
session,
|
|
824
837
|
byLose ? "lose" : "win",
|
|
825
838
|
byLose ? "总输场排行榜" : "总胜场排行榜",
|
|
826
|
-
|
|
839
|
+
"📋",
|
|
827
840
|
size
|
|
828
841
|
);
|
|
829
842
|
}
|
|
@@ -842,7 +855,7 @@ function apply(ctx, config) {
|
|
|
842
855
|
win: 0
|
|
843
856
|
});
|
|
844
857
|
return await sendMessage(session, panel({
|
|
845
|
-
icon: "
|
|
858
|
+
icon: "📋",
|
|
846
859
|
title: "棋士档案",
|
|
847
860
|
at: session.username,
|
|
848
861
|
body: [field("查询对象", username), "尚无对局记录,静待首战。"],
|
|
@@ -853,7 +866,7 @@ function apply(ctx, config) {
|
|
|
853
866
|
const total = win + lose;
|
|
854
867
|
const winRate = total === 0 ? "—" : `${(win / total * 100).toFixed(1)}%`;
|
|
855
868
|
return await sendMessage(session, panel({
|
|
856
|
-
icon: "
|
|
869
|
+
icon: "📋",
|
|
857
870
|
title: "棋士档案",
|
|
858
871
|
at: session.username,
|
|
859
872
|
body: [
|
|
@@ -867,8 +880,8 @@ function apply(ctx, config) {
|
|
|
867
880
|
});
|
|
868
881
|
function notStartedPanel(username) {
|
|
869
882
|
return panel({
|
|
870
|
-
icon: "
|
|
871
|
-
title: "
|
|
883
|
+
icon: "💡",
|
|
884
|
+
title: "对局尚未开始",
|
|
872
885
|
at: username,
|
|
873
886
|
body: ["当前频道还没有进行中的对局。"],
|
|
874
887
|
tips: ["「cchess.开始」入座并对弈", "「cchess.开始 人机」挑战皮卡鱼"]
|
|
@@ -877,8 +890,8 @@ function apply(ctx, config) {
|
|
|
877
890
|
__name(notStartedPanel, "notStartedPanel");
|
|
878
891
|
function alreadyStartedPanel(username) {
|
|
879
892
|
return panel({
|
|
880
|
-
icon: "
|
|
881
|
-
title: "
|
|
893
|
+
icon: "💡",
|
|
894
|
+
title: "对局已经开战",
|
|
882
895
|
at: username,
|
|
883
896
|
body: ["本局尚未结束,不必重开。"],
|
|
884
897
|
tips: ["「cchess.认输」结束本局", "「cchess.结束」强制清盘"]
|
|
@@ -887,7 +900,7 @@ function apply(ctx, config) {
|
|
|
887
900
|
__name(alreadyStartedPanel, "alreadyStartedPanel");
|
|
888
901
|
function notJoinedPanel(username) {
|
|
889
902
|
return panel({
|
|
890
|
-
icon: "
|
|
903
|
+
icon: "💡",
|
|
891
904
|
title: "你尚未入座",
|
|
892
905
|
at: username,
|
|
893
906
|
body: ["先入座,才好落子。"],
|
|
@@ -906,7 +919,7 @@ function apply(ctx, config) {
|
|
|
906
919
|
__name(analyzingPanel, "analyzingPanel");
|
|
907
920
|
function notYourTurnPanel(username, yourSide, currentSide) {
|
|
908
921
|
return panel({
|
|
909
|
-
icon: "
|
|
922
|
+
icon: "⏳",
|
|
910
923
|
title: "尚未轮到你",
|
|
911
924
|
at: username,
|
|
912
925
|
body: [field("你的阵营", withSideIcon(yourSide)), field("当前轮走", withSideIcon(currentSide))],
|
|
@@ -916,7 +929,7 @@ function apply(ctx, config) {
|
|
|
916
929
|
__name(notYourTurnPanel, "notYourTurnPanel");
|
|
917
930
|
function invalidMovePanel(username, reason) {
|
|
918
931
|
return panel({
|
|
919
|
-
icon: "
|
|
932
|
+
icon: "⚠️",
|
|
920
933
|
title: reason,
|
|
921
934
|
at: username,
|
|
922
935
|
body: ["请确认着法是否合乎棋规。"],
|
|
@@ -937,7 +950,7 @@ function apply(ctx, config) {
|
|
|
937
950
|
function invalidLeaderboardSizePanel(username) {
|
|
938
951
|
return panel({
|
|
939
952
|
icon: "⚠️",
|
|
940
|
-
title: "
|
|
953
|
+
title: "排行榜人数有误",
|
|
941
954
|
at: username,
|
|
942
955
|
body: ["请输入不小于 0 的整数。"],
|
|
943
956
|
tips: ["例如:cchess.战绩 榜 10"]
|
|
@@ -945,7 +958,12 @@ function apply(ctx, config) {
|
|
|
945
958
|
}
|
|
946
959
|
__name(invalidLeaderboardSizePanel, "invalidLeaderboardSizePanel");
|
|
947
960
|
async function renderBoard(channelId) {
|
|
948
|
-
|
|
961
|
+
try {
|
|
962
|
+
return import_koishi.h.image(await drawChessBoard(channelId), imageMimeType).toString();
|
|
963
|
+
} catch (error) {
|
|
964
|
+
logger.warn("棋盘渲染失败,本次只发文本:%s", error.message);
|
|
965
|
+
return void 0;
|
|
966
|
+
}
|
|
949
967
|
}
|
|
950
968
|
__name(renderBoard, "renderBoard");
|
|
951
969
|
async function settleVictory(channelId, winSide, loseSide, title, options = {}) {
|
|
@@ -1044,7 +1062,7 @@ function apply(ctx, config) {
|
|
|
1044
1062
|
icon: "⚠️",
|
|
1045
1063
|
title: "悔棋被拒",
|
|
1046
1064
|
at: username,
|
|
1047
|
-
body: ["
|
|
1065
|
+
body: ["落子无悔,对局继续。"],
|
|
1048
1066
|
image: await renderBoard(channelId)
|
|
1049
1067
|
}));
|
|
1050
1068
|
}
|
|
@@ -1640,15 +1658,15 @@ function apply(ctx, config) {
|
|
|
1640
1658
|
for (let col = 0; col < 9; col++) {
|
|
1641
1659
|
const x = columnAt(isFlipBoard ? 8 - col : col);
|
|
1642
1660
|
label(FILES[col], x, 30, 22, FRAME.onSurfaceVariant);
|
|
1643
|
-
label(String(col + 1), x, 64, 22, FRAME.
|
|
1661
|
+
label(String(col + 1), x, 64, 22, FRAME.onSurfaceVariant);
|
|
1644
1662
|
label(RED_FILES[col], x, FRAME_HEIGHT - 62, 24, FRAME.onSurfaceVariant);
|
|
1645
|
-
label(FILES[col], x, FRAME_HEIGHT - 26, 22, FRAME.
|
|
1663
|
+
label(FILES[col], x, FRAME_HEIGHT - 26, 22, FRAME.onSurfaceVariant);
|
|
1646
1664
|
}
|
|
1647
1665
|
for (let row = 0; row < 10; row++) {
|
|
1648
1666
|
const y = rowAt(isFlipBoard ? 9 - row : row);
|
|
1649
1667
|
const text = String(9 - row);
|
|
1650
|
-
label(text, 19, y, 22, FRAME.
|
|
1651
|
-
label(text, FRAME_WIDTH - 19, y, 22, FRAME.
|
|
1668
|
+
label(text, 19, y, 22, FRAME.onSurfaceVariant);
|
|
1669
|
+
label(text, FRAME_WIDTH - 19, y, 22, FRAME.onSurfaceVariant);
|
|
1652
1670
|
}
|
|
1653
1671
|
return canvas.toBuffer(imageMimeType);
|
|
1654
1672
|
}
|