全程云库模式(cloud_always) + 修正 chessdb 分数口径为行棋方视角(去掉多余翻转); 油猴 v1.6.0 加'全程'开关
This commit is contained in:
1 parent
d3c667a61c
commit
9486fa3cdc
3 files changed
+32
-18
No files matched your search
+16
-4
@@ -2,7 +2,7 @@
|
|||||||
// @name Xiangqi.com 本地皮卡鱼引擎对接
|
// @name Xiangqi.com 本地皮卡鱼引擎对接
|
||||||
// @name:en Xiangqi.com Local Pikafish Engine
|
// @name:en Xiangqi.com Local Pikafish Engine
|
||||||
// @namespace awesome-xiangqi
|
// @namespace awesome-xiangqi
|
||||||
// @version 1.5.0
|
// @version 1.6.0
|
||||||
// @description 在 play.xiangqi.com 对局中调用本地皮卡鱼引擎(http://127.0.0.1:9377)自动/手动走子
|
// @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
|
// @description:en Hook the site WebSocket and let a local Pikafish engine play for you
|
||||||
// @match https://play.xiangqi.com/*
|
// @match https://play.xiangqi.com/*
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
const ENGINE_URL = "http://127.0.0.1:9377";
|
const ENGINE_URL = "http://127.0.0.1:9377";
|
||||||
const NS = "42/xiangqi,";
|
const NS = "42/xiangqi,";
|
||||||
const FIRST_MOVE_EXTRA_SEC = 5;
|
const FIRST_MOVE_EXTRA_SEC = 5;
|
||||||
const VERSION = "1.5.0";
|
const VERSION = "1.6.0";
|
||||||
const PATCH_MARK = Symbol.for("xiangqi.pikafish.ws.patched");
|
const PATCH_MARK = Symbol.for("xiangqi.pikafish.ws.patched");
|
||||||
const uW = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
|
const uW = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
|
||||||
|
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
const cfg = {
|
const cfg = {
|
||||||
enabled: storage.get("pf_enabled", "0") === "1",
|
enabled: storage.get("pf_enabled", "0") === "1",
|
||||||
useCloud: storage.get("pf_usecloud", "1") === "1",
|
useCloud: storage.get("pf_usecloud", "1") === "1",
|
||||||
|
cloudAlways: storage.get("pf_cloudalways", "0") === "1",
|
||||||
movetime: Math.max(50, +storage.get("pf_movetime", 800) || 800),
|
movetime: Math.max(50, +storage.get("pf_movetime", 800) || 800),
|
||||||
depth: Math.max(0, +storage.get("pf_depth", 0) || 0),
|
depth: Math.max(0, +storage.get("pf_depth", 0) || 0),
|
||||||
firstDelay: Math.max(0, +storage.get("pf_firstdelay", 0) || 0),
|
firstDelay: Math.max(0, +storage.get("pf_firstdelay", 0) || 0),
|
||||||
@@ -62,6 +63,7 @@
|
|||||||
function saveCfg() {
|
function saveCfg() {
|
||||||
storage.set("pf_enabled", cfg.enabled ? "1" : "0");
|
storage.set("pf_enabled", cfg.enabled ? "1" : "0");
|
||||||
storage.set("pf_usecloud", cfg.useCloud ? "1" : "0");
|
storage.set("pf_usecloud", cfg.useCloud ? "1" : "0");
|
||||||
|
storage.set("pf_cloudalways", cfg.cloudAlways ? "1" : "0");
|
||||||
storage.set("pf_movetime", cfg.movetime);
|
storage.set("pf_movetime", cfg.movetime);
|
||||||
storage.set("pf_depth", cfg.depth);
|
storage.set("pf_depth", cfg.depth);
|
||||||
storage.set("pf_firstdelay", cfg.firstDelay);
|
storage.set("pf_firstdelay", cfg.firstDelay);
|
||||||
@@ -197,7 +199,7 @@
|
|||||||
|
|
||||||
let liveWS = null;
|
let liveWS = null;
|
||||||
|
|
||||||
async function engineBestmove({ moves, fen, movetime, depth, useCloud, signal }) {
|
async function engineBestmove({ moves, fen, movetime, depth, useCloud, cloudAlways, signal }) {
|
||||||
const timeoutMs = Math.max(15000, movetime * 4 + 2000);
|
const timeoutMs = Math.max(15000, movetime * 4 + 2000);
|
||||||
const ctrl = new AbortController();
|
const ctrl = new AbortController();
|
||||||
const timer = setTimeout(
|
const timer = setTimeout(
|
||||||
@@ -219,6 +221,7 @@
|
|||||||
movetime,
|
movetime,
|
||||||
depth: depth > 0 ? depth : undefined,
|
depth: depth > 0 ? depth : undefined,
|
||||||
cloud: useCloud !== false,
|
cloud: useCloud !== false,
|
||||||
|
cloud_always: !!cloudAlways,
|
||||||
}),
|
}),
|
||||||
signal: ctrl.signal,
|
signal: ctrl.signal,
|
||||||
});
|
});
|
||||||
@@ -300,6 +303,7 @@
|
|||||||
movetime: cfg.movetime,
|
movetime: cfg.movetime,
|
||||||
depth: cfg.depth,
|
depth: cfg.depth,
|
||||||
useCloud: cfg.useCloud,
|
useCloud: cfg.useCloud,
|
||||||
|
cloudAlways: cfg.cloudAlways,
|
||||||
});
|
});
|
||||||
if (reqId !== G.reqId) return;
|
if (reqId !== G.reqId) return;
|
||||||
if (G.over) throw new Error("对局已结束");
|
if (G.over) throw new Error("对局已结束");
|
||||||
@@ -498,7 +502,8 @@
|
|||||||
<div class="title"><span>🐴 皮卡鱼</span><span class="dot" id="pf-dot" style="background:#666"></span></div>
|
<div class="title"><span>🐴 皮卡鱼</span><span class="dot" id="pf-dot" style="background:#666"></span></div>
|
||||||
<div class="row"><label><input type="checkbox" id="pf-enabled"> 自动走子</label>
|
<div class="row"><label><input type="checkbox" id="pf-enabled"> 自动走子</label>
|
||||||
<span id="pf-side-hint"></span></div>
|
<span id="pf-side-hint"></span></div>
|
||||||
<div class="row"><label><input type="checkbox" id="pf-cloud"> 使用云库</label></div>
|
<div class="row"><label><input type="checkbox" id="pf-cloud"> 使用云库</label>
|
||||||
|
<label><input type="checkbox" id="pf-cloudalways"> 全程</label></div>
|
||||||
<div class="row"><span>每步用时(ms)</span><input type="number" id="pf-mt" min="50" step="50"></div>
|
<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>深度(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>首步延迟(秒+5)</span><input type="number" id="pf-delay" min="0" max="120" step="1"></div>
|
||||||
@@ -532,6 +537,13 @@
|
|||||||
if (cfg.enabled) scheduleTurn(0);
|
if (cfg.enabled) scheduleTurn(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const caCb = panel.querySelector("#pf-cloudalways");
|
||||||
|
caCb.checked = cfg.cloudAlways;
|
||||||
|
caCb.addEventListener("change", () => {
|
||||||
|
cfg.cloudAlways = caCb.checked;
|
||||||
|
saveCfg();
|
||||||
|
});
|
||||||
|
|
||||||
const mt = panel.querySelector("#pf-mt");
|
const mt = panel.querySelector("#pf-mt");
|
||||||
mt.value = cfg.movetime;
|
mt.value = cfg.movetime;
|
||||||
mt.addEventListener("change", () => {
|
mt.addEventListener("change", () => {
|
||||||
|
|||||||
+8
-10
@@ -67,22 +67,20 @@ class CloudBook:
|
|||||||
self._cache[fen] = (now + CACHE_TTL, result)
|
self._cache[fen] = (now + CACHE_TTL, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def best(self, fen, moves_count=0):
|
async def best(self, fen, moves_count=0, any_score=False):
|
||||||
"""返回 (move, score_mover, depth_note)。
|
"""返回 (move, score_mover, note)。
|
||||||
|
|
||||||
云库分数为红方视角; 轮黑时取负。分数不足 min_score 时放弃(交给引擎)。
|
实测 chessdb 的 score 就是行棋方视角(与皮卡鱼 UCI 口径一致), 不需翻转。
|
||||||
moves_count 用于开局阶段放宽采纳阈值(前 8 回合云库几乎必赢)。
|
any_score=True(全程云库): 无条件采纳云库最佳着法。
|
||||||
|
否则: 开局(≤16步)直接采纳, 中后局要求分数 ≥ min_score。
|
||||||
"""
|
"""
|
||||||
entries = await self.query_all(fen)
|
entries = await self.query_all(fen)
|
||||||
if not entries:
|
if not entries:
|
||||||
return None
|
return None
|
||||||
side_is_red = (fen.split()[1] == 'w')
|
scored = sorted(entries, key=lambda x: -x[1])
|
||||||
sign = 1 if side_is_red else -1
|
|
||||||
scored = [(mv, sign * sc) for mv, sc in entries]
|
|
||||||
scored.sort(key=lambda x: -x[1])
|
|
||||||
mv, sc = scored[0]
|
mv, sc = scored[0]
|
||||||
# 开局阈值低(云库碾压引擎), 中后局要求云库分足够好
|
threshold = (-10 ** 9) if any_score else (
|
||||||
threshold = self.min_score if moves_count >= 16 else 0
|
self.min_score if moves_count >= 16 else 0)
|
||||||
if sc < threshold:
|
if sc < threshold:
|
||||||
return None
|
return None
|
||||||
return mv, sc, f'{len(scored)}候选'
|
return mv, sc, f'{len(scored)}候选'
|
||||||
@@ -31,15 +31,18 @@ class Service:
|
|||||||
await self.engine.quit()
|
await self.engine.quit()
|
||||||
await self.cloud.close()
|
await self.cloud.close()
|
||||||
|
|
||||||
async def bestmove(self, moves_site, fen=None, movetime=None, depth=None, req_cloud=None):
|
async def bestmove(self, moves_site, fen=None, movetime=None, depth=None,
|
||||||
|
req_cloud=None, cloud_always=None):
|
||||||
moves_ucci = [site_to_ucci(m) for m in moves_site]
|
moves_ucci = [site_to_ucci(m) for m in moves_site]
|
||||||
fen = fen or fen_from_moves(moves_ucci)
|
fen = fen or fen_from_moves(moves_ucci)
|
||||||
|
|
||||||
# 1. 云库优先(请求可显式关闭; 深度模式强制引擎)
|
# 1. 云库优先(请求可显式关闭; 全程云库模式无视分数门槛和深度强制)
|
||||||
use_cloud = req_cloud if req_cloud is not None else True
|
use_cloud = req_cloud if req_cloud is not None else True
|
||||||
if use_cloud and not depth:
|
if use_cloud:
|
||||||
try:
|
try:
|
||||||
cloud_hit = await self.cloud.best(fen, moves_count=len(moves_ucci))
|
cloud_hit = await self.cloud.best(
|
||||||
|
fen, moves_count=len(moves_ucci),
|
||||||
|
any_score=bool(cloud_always))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning('cloud error: %s', e)
|
log.warning('cloud error: %s', e)
|
||||||
cloud_hit = None
|
cloud_hit = None
|
||||||
@@ -99,6 +102,7 @@ def make_app(args):
|
|||||||
movetime=req.get('movetime'),
|
movetime=req.get('movetime'),
|
||||||
depth=req.get('depth'),
|
depth=req.get('depth'),
|
||||||
req_cloud=req.get('cloud'),
|
req_cloud=req.get('cloud'),
|
||||||
|
cloud_always=req.get('cloud_always'),
|
||||||
)
|
)
|
||||||
return web.json_response(out)
|
return web.json_response(out)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in new issue
Block a user