中国象棋 Xiangqi · 方法与技能总录
一套可玩、可分发、可上架的中国象棋小游戏,同时是一份「如何做一个规则正确的中国象棋游戏」的方法记录。
Overview
本技能交付三种形态的中国象棋(9×10 交叉点,红先,将死或困毙对方即胜),全部为纯人机对战(玩家执红先手,AI 执黑自动应手),并带简单 / 普通 / 困难三档难度:
- 桌面窗口版 —
xiangqi_tk.py(Python + tkinter),木纹棋盘、河界、九宫斜线、兵/炮位星标、a–i/0–9坐标标号、最后一手红圈、悬停预览、难度按钮、声音开关(默认静音)。 - 终端版 —
xiangqi-cli.mjs(Node.js),命令行落子,坐标形如a6a5。 - 网页版 —
index.html(HTML5 Canvas + Web Audio),单文件、双击即玩、零外部请求;手机触屏可玩。
外加一条把桌面版打包成免环境单文件 exe 的完整方法(PyInstaller)。
> 本作与「五子棋」「国际象棋」套件是同源镜像:同一套视觉与交互(深色底 + 金标题、木纹棋盘、坐标标号、
> 最后一手高亮、悬停预览、三档难度、默认静音、结束浮层),仅棋盘与规则不同 —— 中国象棋是
> 9×10 交叉点 + 河界 + 九宫,并且有三条与国际象棋相反或独有的规则(困毙判负、帅将照面判负、单方长将判负)。
文件结构
| 文件 | 作用 |
|---|---|
xiangqi.mjs |
纯逻辑引擎:棋盘/走法/完整规则/终局判定/AI,终端版与网页版共用 |
index.html |
网页版(HTML5 Canvas)。单文件、自带引擎,双击即玩、零外部请求 |
xiangqi_tk.py |
桌面窗口版(tkinter GUI),内含 Python 版引擎(xiangqi.mjs 的逐条移植) |
xiangqi-cli.mjs |
终端版(Node.js,人机) |
test_xiangqi.mjs |
走法几何与基本规则自测(51 项断言) |
test_rules.mjs |
规则专项测试(66 项:将军判定 20 + 终局判定 21 + 落点提示 25) |
test_ai.mjs |
AI 自测(15 项) |
perft_xiangqi.mjs |
perft 走法枚举对照测试(JS 引擎,对照公开基准) |
perft_xiangqi.py |
perft 走法枚举对照测试(Python 桌面引擎,同一组基准) |
build_exe.bat.txt |
Windows 单文件 exe 一键打包脚本。⚠️ 平台白名单不含 .bat,用前改回 build_exe.bat |
README.md |
使用说明 |
一、游戏规则与设计约定
- 棋盘 9 列 × 10 行,棋子落在交叉点上,红先;将死对方或将对方困毙即胜。
- 棋盘索引
board[r*9+c]:r是行(0 = 黑方底线在屏幕最上方,9 = 红方底线),c是列(0..8,从屏幕左侧数起)。
⚠️ 黑白视角与屏幕上下方向绑定,写测试摆位时务必先确认r的方向,本项目因此误判过数次。 - 棋子编码:
EMPTY=0;红方为正值、黑方为负值;K=1 将/帅 A=2 士/仕 E=3 象/相 H=4 马 R=5 车 C=6 炮 P=7 兵/卒。 - 着法编码:一个整数
from*128 + to(from/to都是 0..89 的格号)。 - 棋子用字两侧必须分开:黑方「将士象马车炮卒」,红方「帅仕相马车炮兵」。
- 完整实现:马蹩腿、象塞眼、象不过河、炮需恰一个炮架才能吃子、兵/卒过河后可横走但不可后退、
士帅不出九宫、帅将照面判负、困毙判负、被将军时必须应将、自然限着 60 回合判和、
同一局面三次重复判和、单方长将判负。
三条最容易做错的规则(本项目的核心经验)
| 规则 | 中国象棋 | 国际象棋 | 说明 |
|---|---|---|---|
| 无子可动 | 判负(困毙) | 判和(逼和) | 《中国象棋竞赛规则》第 3.2 / 4.1.3 条。完全相反,本项目最初就按国际象棋的直觉写错了 |
| 帅将照面 | 判负/不可走 | 无此规则 | 同列且中间无子时,等于自己把帅送上去被吃,所以「不能走出照面」要算进合法着法过滤 |
| 长将 | 判负 | 判和 | 棋例 23.5.1:反复三次时若只有一方在连续将军,该方立即判负;双方都在将才算和 |
二、三种形态的运行方式
- 桌面版:
python xiangqi_tk.py(需带 tkinter 的 Python;Windows 官方版默认自带)。鼠标悬停显示该子可走点。 - 终端版:
node xiangqi-cli.mjs(人机;输入a6a5落子,q退出;非法着法会提示该子能走到哪些点)。 - 网页版:直接用浏览器打开
index.html(单文件、无需服务器)。点己方棋子选中 → 高亮点是可走位 → 点目标点落子。
三、规则正确性怎么验(本技能的核心方法)
中国象棋规则细节多,靠人眼看代码几乎必漏。用 perft(走法枚举节点数对照公开基准)来验证:
只要引擎算出的节点数与公认数值一致,就说明走子、吃子、蹩马腿、塞象眼、炮架、过河兵、九宫限制、
帅将照面、被将军只能应将……这些规则全部正确。
中国象棋开局局面的公开基准:depth1 = 44,depth2 = 1920,depth3 = 79666,depth4 = 3290240。
| 测试 | 结果 |
|---|---|
| perft(JS 引擎) | 3 / 3 通过(44 / 1920 / 79666) |
| perft(Python 桌面引擎) | 3 / 3 通过(同一组基准) |
| 两套引擎差分一致性(随机 60 个局面,逐条比对合法着法集合) | 60 / 60 完全一致 |
规则专项 test_rules.mjs |
66 / 66 通过 |
走法几何 test_xiangqi.mjs |
51 / 51 通过 |
AI 自测 test_ai.mjs |
15 / 15 通过 |
为什么要有「差分一致性测试」:Python 桌面版是把 xiangqi.mjs 手工移植过去的,
光靠两边各自跑 perft 只能证明「节点数相同」,不能保证每一步的具体着法集合都相同。
所以额外做了一次:用一方随机走 60 个局面、把每步的合法着法集合记下来,交给另一方逐条复算比对 ——
两套引擎在真实局面上完全一致,这才算移植没走样。
规则专项覆盖(perft 测不到的部分):将军判定的独立路径互证(敌人能否走到我方将/帅所在格)、
应将着法过滤、帅将照面、被牵制与被堵死棋子的落点提示、
将死判负、困毙判负(且优于自然限着/长将判定)、自然限着 60 回合、三次重复局面判和、单方长将判负、
「提示的落点必须恰等于引擎给出的合法着法」(一个不多一个不少)。
四、AI 难度设计
- 评估函数:子力价值(
K 10000 / A 230 / E 250 / H 470 / R 1000 / C 510 / P 110) - 棋子位置表 PST(黑方镜像取行)+ 兵过河加分 + 受攻击/受保护的战术修正项。
- 搜索:negamax + α-β 剪枝,着法排序(吃子优先 + PST 差)提升剪枝效率,
带静态搜索(quiescence)避免「吃子吃一半」的评估抖动,并带时限中断(到点即用当前最好的着法落子)。 - 三档难度:
- 简单:只在前 120 个着法里随机挑一个,适合新手练手。
- 普通:3 层搜索,稳健有来有回。
- 困难:4 层搜索,多看一步、会抓杀棋。
五、声音方案(无需任何音频素材文件)
- 桌面版:Windows 系统蜂鸣按频率合成(红方高音、黑方低音,吃子更高一档,胜利不同琶音)。默认静音,界面按钮手动开启。
- 网页版:Web Audio API 实时合成振荡器音(无 mp3/wav 文件),落子「咔哒」声、将军提示音、胜利琶音。
六、网页版布局规格(与五子棋/国际象棋严格对齐)
- 深色主题:
--bg:#33312e/--gold:#f0c674/ 主按钮#d35400/ ghost 灰按钮 +.active高亮。 - 页面顺序:
标题 → 棋盘 canvas → 状态栏 → 控制条(重新开始 / 难度 / 声音)→ 结束浮层。 - 自适应缩放:设计稿 508 × 560,由 格宽 52 + 边距 46 算出(9 列 10 行交叉点),
木框 14,故画布 536 × 588。 - 木纹渐变棋盘 + 内边框 + 网格(竖线在河界处断开)+ 九宫斜线 + 兵/炮位四角折角星标 + 四周坐标标号;
最后一手红圈、悬停/选中显示可走点、结束浮层。 - 制胜一手:分出胜负时把最后那一手清楚地画出来 —— 起格金色实块、落点橙环 + 白描边橙箭头 + 起点圆点。
棋子有半径(0.45 格),所以必须分两层画(见下方踩坑 6)。 - 适配 iOS 安全区(
env(safe-area-*))、100dvh、visualViewport高度修正。
七、桌面 exe 打包(PyInstaller)
python -m PyInstaller --onefile --windowed --name Xiangqi xiangqi_tk.py
--onefile单文件;--windowed无控制台窗口。也可直接跑随包的build_exe.bat.txt(用前改名回.bat)。- 产物约 12–13 MB,自带 Python 解释器 + Tk 运行库 + VC++ 运行库,目标机免装任何环境;要求 64 位 Windows 10 及以上。
- 未做代码签名时,目标机首次双击可能弹 SmartScreen「未知发布者」,点「更多信息 → 仍要运行」即可。
- ⚠️ 分发提醒:二进制 exe 不要放进技能包(上架平台禁止二进制文件);本包只附源码 +
build_exe.bat.txt。
八、踩坑与经验(可复用的坑)
- 困毙判负 ≠ 逼和判和 —— 中国象棋里「轮到走棋一方无子可动」是输,与国际象棋相反。
本项目最初沿用国际象棋的stalemate → draw写法,导致「该输的判成和棋」。判定顺序也有讲究:
无子可动要优先于自然限着/重复局面,否则一方被围死却被判成和棋。 doMove的返回值语义 —— 引擎的doMove(board, move)会原地修改数组,并返回
移动的那枚棋子(不是被吃掉的子)。用它当「被吃子」会静默出错;需要被吃子必须
在走子之前自己取board[mvTo(move)]。- 坐标串必须在走子之前算 —— 终端版曾把每一手都打印成吃子(
a6xa5):因为先doMove
再算字符串,落点上已经是刚走过去的子,board[to] !== EMPTY恒成立。先算字符串,再改棋盘。 - tkinter 不支持
rgba()颜色字符串 —— 传'rgba(200,40,40,0.5)'会直接抛TclError。
桌面版所有颜色必须用不透明十六进制;需要半透明效果就自己按比例混色(本包用blend()实现)。
「将军」提示改用红框而非红色填充,否则会盖住棋子。 Math.roundvs Pythonround—— Python 内置round是「银行家舍入」(round(0.5)=0),
与 JS 的Math.round(.5一律向上)不同。点击换算格子必须用floor(v+0.5),
否则点在两格正中间时两套实现的命中结果会不一致。- 制胜一手必须分上下两层画 —— 棋子半径 0.45 格,把箭头画在棋子下面时,
只走一步(吃士、兵吃将这种相邻着法)箭头会被两枚棋子完全盖住,等于没画。
解法:起格/终格底色画在棋子下面(走完起格是空的,金色整块最醒目,一眼看出从哪出发),
落点橙环 + 箭头 + 起点圆点画在棋子上面(箭头指向落点棋子外沿,并加白色描边保证浅色盘面上也有对比)。 - 棋子用字两侧必须分开查表 —— 红方用「帅仕相马车炮兵」、黑方用「将士象马车炮卒」。
本项目早期版本把两张表装反(红方被画成「将」、黑方被画成「帅」),肉眼在满盘棋里很难发现。
可靠的检出方式:用 canvas 桩把fillText的实参记下来,分别摆一枚红帅与一枚黑将,
断言「红方那格画的是帅」——比盯屏幕看快且不会漏。 <script type="module">在file://下会被 CORS 拦 —— 想让用户「双击就能玩」,
必须把引擎内联进 HTML 做成单文件(只有外部模块文件才会被拦,内联模块可以跑)。
发到微信场景还要进一步改成普通脚本 + IIFE(老 WebView 对 ES module 支持不稳)。- 页面别加载外部 CDN —— 国内 CDN 不可达时会阻塞 HTML 解析导致整页白屏。棋类小游戏应当零外部请求。
(本包网页版已核验:<script>仅 1 个内联、<link>0 个、无任何http(s)://引用。) - 默认按钮必须带
active类 —— 启动时会querySelector('.diff.active')取值,漏了会直接报错。 - 发微信的分享渠道要选对:
*.app.workbuddy.host(发布应用现造的沙箱子域)在微信内置浏览器里
会被「域名风控」拦截,提示「如需浏览,请长按网址复制后使用浏览器访问」——这是域名级拦截
(服务器对微信 UA 仍返回 200),应用侧改代码绕不过。做法:对外分享走workbuddy.link/p/<id>
(资料库轻量发布),微信内可干净打开。 - 分享出去的页面一律零弹窗零提醒:不要在任何环境主动弹横幅/引导/安装提示(微信里尤其不能弹),
否则会被当成骚扰;安装入口交给浏览器自带菜单,页面不插一脚。 - 给 Python 引擎调时限:Python 比 V8 慢得多,直接照搬 JS 的 5.2 秒时限会让「困难」档每步卡 5 秒以上。
桌面版把三档时限降到 0.5 / 1.5 / 3.5 秒,并保留「到点用当前最好着法落子」的中断逻辑。
九、网页版成品链接(微信可直接打开)
本作网页版的对外分享地址(微信内可直接点开、无安全提醒):
https://www.workbuddy.link/p/Wp1BFek9ScotcmOGWzrkvy
> 若要自己发布同类页面,遵循同一条选型:发微信优先用 workbuddy.link;
> 只有确实需要云端读写/访问统计时才用 *.app.workbuddy.host 应用域名。
十、作为技能如何使用
当用户提出「做个中国象棋 / 象棋 / 棋类小游戏 / 人机对战棋 / 把游戏打包成 exe」等需求时:
- 把本目录源码复制到用户的工作目录;
- 按第二节说明运行对应形态(桌面 / 终端 / 网页);
- 若需要免环境 exe,跑
build_exe.bat.txt(用前改名回.bat)或上面的 PyInstaller 命令在本机打包; - 若要改规则或 AI,改
xiangqi.mjs(纯逻辑、可单测),同时同步改xiangqi_tk.py
里对应的那段(两边是同一套逻辑的两种语言实现),然后重跑
node perft_xiangqi.mjs与python perft_xiangqi.py;
⚠️ 改完务必重跑 perft —— 这是唯一能低成本发现「规则被改坏」的手段。 - 想单独验证某条规则时,直接跑
node test_rules.mjs(66 项规则断言,失败会打印具体哪条不对)。
十一、验证结论
- JS 引擎 perft 3/3;Python 引擎 perft 3/3(同一组基准 44 / 1920 / 79666)。
- 两套引擎差分一致性 60/60 局面完全一致。
- 规则专项 66/66;走法几何 51/51;AI 自测 15/15。
- 桌面版
py_compile通过;几何自检画布 536×588 / 盘面 508×560正确;无rgba(残留;无 DOM 引用。 - 终端版
node --check通过,并已实际跑通对局链路:选难度 → 落子(含a7xa3隔炮架吃子)
→ AI 应手 → 非法着法给出「该子可走:…」提示 →q退出。 - 网页版已核验:单文件、零外部请求;棋子用字经 canvas 桩断言「红帅 / 黑将 / 红仕 / 黑士 / 红相 / 黑象 / 红兵 / 黑卒」全部正确。
作者与联系
- 作者:安朋康|348760700@qq.com