每步延迟: 深度/云库秒回时保证行棋节奏(计入 thinking_time); 油猴 v1.7.0, bot 加 --move-delay
This commit is contained in:
1 parent
1ed6a176cb
commit
adf3abb5fc
2 files changed
+39
-4
No files matched your search
+28
-4
@@ -2,7 +2,7 @@
|
||||
// @name Xiangqi.com 本地皮卡鱼引擎对接
|
||||
// @name:en Xiangqi.com Local Pikafish Engine
|
||||
// @namespace awesome-xiangqi
|
||||
// @version 1.6.0
|
||||
// @version 1.7.0
|
||||
// @description 在 play.xiangqi.com 对局中调用本地皮卡鱼引擎(http://127.0.0.1:9377)自动/手动走子
|
||||
// @description:en Hook the site WebSocket and let a local Pikafish engine play for you
|
||||
// @match https://play.xiangqi.com/*
|
||||
@@ -16,7 +16,7 @@
|
||||
const ENGINE_URL = "http://127.0.0.1:9377";
|
||||
const NS = "42/xiangqi,";
|
||||
const FIRST_MOVE_EXTRA_SEC = 5;
|
||||
const VERSION = "1.6.0";
|
||||
const VERSION = "1.7.0";
|
||||
const PATCH_MARK = Symbol.for("xiangqi.pikafish.ws.patched");
|
||||
const uW = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
movetime: Math.max(50, +storage.get("pf_movetime", 800) || 800),
|
||||
depth: Math.max(0, +storage.get("pf_depth", 0) || 0),
|
||||
firstDelay: Math.max(0, +storage.get("pf_firstdelay", 0) || 0),
|
||||
moveDelay: Math.max(0, +storage.get("pf_movedelay", 1) || 1), // 每步最小间隔秒(深度/云库秒回时保证节奏)
|
||||
sideOverride: ["auto", "red", "black"].includes(
|
||||
storage.get("pf_side", "auto"),
|
||||
)
|
||||
@@ -67,6 +68,7 @@
|
||||
storage.set("pf_movetime", cfg.movetime);
|
||||
storage.set("pf_depth", cfg.depth);
|
||||
storage.set("pf_firstdelay", cfg.firstDelay);
|
||||
storage.set("pf_movedelay", cfg.moveDelay);
|
||||
storage.set("pf_side", cfg.sideOverride);
|
||||
}
|
||||
|
||||
@@ -78,6 +80,7 @@
|
||||
over: false,
|
||||
thinking: false,
|
||||
firstMoveDeadline: 0,
|
||||
lastMoveAt: 0,
|
||||
reqId: 0,
|
||||
turnTimer: 0,
|
||||
sideCache: { at: 0, side: null },
|
||||
@@ -293,9 +296,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
const reqId = ++G.reqId;
|
||||
// 每步最小间隔(深度/云库秒回时保持行棋节奏), 计入 thinking_time
|
||||
G.thinking = true;
|
||||
setStatus("皮卡鱼思考中…", true);
|
||||
if (!force && cfg.moveDelay > 0) {
|
||||
const remainMs = cfg.moveDelay * 1000 - (Date.now() - (G.lastMoveAt || 0));
|
||||
if (remainMs > 0) {
|
||||
setStatus(`行棋节奏延迟: ${Math.ceil(remainMs / 1000)}s`, true);
|
||||
await new Promise((r) => setTimeout(r, remainMs));
|
||||
if (G.over || !isMyTurn()) {
|
||||
G.thinking = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const reqId = ++G.reqId;
|
||||
try {
|
||||
const out = await engineBestmove({
|
||||
moves: G.moves.slice(),
|
||||
@@ -320,6 +334,7 @@
|
||||
throw new Error("未找到 socket 连接");
|
||||
|
||||
G.moves.push(move);
|
||||
G.lastMoveAt = Date.now();
|
||||
setScore(
|
||||
`${out.source === "cloud" ? "☁ 云库" : "♞ 引擎"} · 评分 ${out.score ?? "?"} · ${out.ms ?? "?"}ms`,
|
||||
);
|
||||
@@ -507,6 +522,7 @@
|
||||
<div class="row"><span>每步用时(ms)</span><input type="number" id="pf-mt" min="50" step="50"></div>
|
||||
<div class="row"><span>深度(0=用时间)</span><input type="number" id="pf-depth" min="0" max="40" step="1"></div>
|
||||
<div class="row"><span>首步延迟(秒+5)</span><input type="number" id="pf-delay" min="0" max="120" step="1"></div>
|
||||
<div class="row"><span>每步延迟(秒)</span><input type="number" id="pf-mvdelay" min="0" max="60" step="1"></div>
|
||||
<div class="row"><span>我方执子</span>
|
||||
<select id="pf-side"><option value="auto">自动识别</option>
|
||||
<option value="red">红方</option><option value="black">黑方</option></select></div>
|
||||
@@ -569,6 +585,14 @@
|
||||
saveCfg();
|
||||
});
|
||||
|
||||
const mv = panel.querySelector("#pf-mvdelay");
|
||||
mv.value = cfg.moveDelay;
|
||||
mv.addEventListener("change", () => {
|
||||
cfg.moveDelay = clampInt(mv.value, 0, 60, 1);
|
||||
mv.value = cfg.moveDelay;
|
||||
saveCfg();
|
||||
});
|
||||
|
||||
const sel = panel.querySelector("#pf-side");
|
||||
sel.value = cfg.sideOverride;
|
||||
sel.addEventListener("change", () => {
|
||||
|
||||
@@ -50,6 +50,7 @@ class OnlineBot:
|
||||
self.thinking = False
|
||||
self.game_over = False
|
||||
self.first_deadline = None
|
||||
self.last_move_at = 0.0
|
||||
self._ack = 0
|
||||
self._send_lock = asyncio.Lock()
|
||||
|
||||
@@ -199,6 +200,14 @@ class OnlineBot:
|
||||
|
||||
fen = fen_from_moves(self.moves)
|
||||
t0 = asyncio.get_event_loop().time()
|
||||
# 每步最小间隔(深度/云库秒回时保持节奏)
|
||||
if self.args.move_delay:
|
||||
gap = self.args.move_delay - (t0 - self.last_move_at)
|
||||
if gap > 0:
|
||||
log.info('[game] 行棋节奏延迟 %.1fs', gap)
|
||||
await asyncio.sleep(gap)
|
||||
if self.game_over or not self.is_my_turn():
|
||||
return
|
||||
mv = score = None
|
||||
source = 'engine'
|
||||
|
||||
@@ -215,6 +224,7 @@ class OnlineBot:
|
||||
|
||||
site_mv = ucci_to_site(mv)
|
||||
self.moves.append(mv)
|
||||
self.last_move_at = asyncio.get_event_loop().time()
|
||||
think_ms = int((asyncio.get_event_loop().time() - t0) * 1000)
|
||||
log.info('[game] %s: %s → %s %s %dms', source, mv, site_mv, score, think_ms)
|
||||
self.emit('g_v2.move', {'game_id': self.game_id, 'move': site_mv,
|
||||
@@ -244,6 +254,7 @@ def main():
|
||||
p.add_argument('--hash', type=int, default=256, help='哈希表 MB')
|
||||
p.add_argument('--config', default='10/2', help='匹配时制 总分钟/加秒')
|
||||
p.add_argument('--first-delay', type=int, default=0, help='红方首步延迟秒(实际=此值+5)')
|
||||
p.add_argument('--move-delay', type=float, default=1.0, help='每步最小间隔秒(深度/云库秒回时保持节奏)')
|
||||
p.add_argument('--cloud-min-score', type=int, default=15, help='云库采纳阈值(厘兵)')
|
||||
p.add_argument('--no-cloud', action='store_true', help='禁用云库')
|
||||
args = p.parse_args()
|
||||
|
||||
Reference in new issue
Block a user