<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>中国象棋 on CloudLake</title><link>https://cloudlake.eu.org/tags/%E4%B8%AD%E5%9B%BD%E8%B1%A1%E6%A3%8B/</link><description>Recent content in 中国象棋 on CloudLake</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Mon, 12 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://cloudlake.eu.org/tags/%E4%B8%AD%E5%9B%BD%E8%B1%A1%E6%A3%8B/index.xml" rel="self" type="application/rss+xml"/><item><title>中国象棋：perft 证明我的规则写对了</title><link>https://cloudlake.eu.org/p/chinese-cheese/</link><pubDate>Mon, 12 Oct 2026 00:00:00 +0000</pubDate><guid>https://cloudlake.eu.org/p/chinese-cheese/</guid><description>&lt;img src="https://cloudlake.eu.org/p/chinese-cheese/cover.jpg" alt="Featured image of post 中国象棋：perft 证明我的规则写对了" /&gt;&lt;p&gt;一个完整的中国象棋网页版。&lt;/p&gt;&#10;&lt;p&gt;不需要构建步骤，克隆下来 &lt;code&gt;npm start&lt;/code&gt; 就能跑。手机、平板、电脑都能连。&lt;/p&gt;&#10;&lt;p&gt;但真正让我想做这个项目的，不是&amp;quot;做个象棋&amp;quot;，而是——&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;一个规则引擎怎么写才算&amp;quot;写对了&amp;quot;？&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="规则这东西看起来简单"&gt;&lt;a href="#%e8%a7%84%e5%88%99%e8%bf%99%e4%b8%9c%e8%a5%bf%e7%9c%8b%e8%b5%b7%e6%9d%a5%e7%ae%80%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;规则这东西，看起来简单&#10;&lt;/h2&gt;&lt;p&gt;七种棋子，每种走法都耳熟能详。写起来好像没什么难的。&lt;/p&gt;&#10;&lt;p&gt;真正难的是那些&lt;strong&gt;边界条款&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;规则&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;容易漏在哪&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;帅/将&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;九宫内直走一步；&lt;strong&gt;任何导致两将同线且中间无子的着法都非法&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;仕/士&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;九宫内斜走一步&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;相/象&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;走「田」字，&lt;strong&gt;象眼被堵不能走，不能过河&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;马&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;走「日」字，&lt;strong&gt;马腿被堵则该方向不能走&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;车&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;直线滑行，遇子即止&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;炮&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不吃子时同车；&lt;strong&gt;吃子必须隔且只隔一个炮架&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;兵/卒&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;未过河只能向前；&lt;strong&gt;过河后可向前或左右，永不后退&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;还有胜负判定层：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;将死判负&lt;/strong&gt; —— 被将军且无合法着法&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;困毙判负&lt;/strong&gt; —— 没被将军但也没有任何合法着法（中国象棋特有，国际象棋是判和）&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;长将判负&lt;/strong&gt; —— 循环内某方每一步都在将军&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;长捉判负&lt;/strong&gt; —— 循环内某方每步都在追捉同一个非兵非将棋子&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;重复局面判和&lt;/strong&gt; —— 三次重复且不构成长将/长捉&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;自然限着判和&lt;/strong&gt; —— 120 个半回合无吃子&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;光靠&amp;quot;我觉得应该对了&amp;quot;是写不完的。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="perft一个不给你留面子的检验"&gt;&lt;a href="#perft%e4%b8%80%e4%b8%aa%e4%b8%8d%e7%bb%99%e4%bd%a0%e7%95%99%e9%9d%a2%e5%ad%90%e7%9a%84%e6%a3%80%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;perft：一个不给你留面子的检验&#10;&lt;/h2&gt;&lt;p&gt;国际象棋引擎圈子里有个标准测试叫 &lt;strong&gt;perft&lt;/strong&gt;：从开局开始，逐层统计所有合法着法能生成的节点数。&lt;/p&gt;&#10;&lt;p&gt;它有一个残忍的性质：&lt;strong&gt;任何一处规则写错，数字立刻对不上，而且不告诉你错在哪。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;中国象棋的公认值是这样的。这是我的实测：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;深度&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;节点数（公认）&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;结果&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;44&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1,920&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;79,666&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;3,290,240&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;到深度 4，要生成并验证 &lt;strong&gt;329 万个局面&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;这比&amp;quot;手动走几步看起来没问题&amp;quot;强得多——它逼你把每一个边界条款都写对，因为只要有一处错，329 万会变成 328 万或者别的数。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;一个能被数字否证的测试，比一百次&amp;quot;看起来没问题&amp;quot;更有价值。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;另外还有 69 项单元测试，逐条覆盖蹩马腿、塞象眼、炮翻山、将帅照面、被牵制棋子、困毙、长将判负、自然限着，以及中文记谱的前后缀消歧。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="ai六档难度但不许装傻"&gt;&lt;a href="#ai%e5%85%ad%e6%a1%a3%e9%9a%be%e5%ba%a6%e4%bd%86%e4%b8%8d%e8%ae%b8%e8%a3%85%e5%82%bb" class="header-anchor"&gt;&lt;/a&gt;AI：六档难度，但不许装傻&#10;&lt;/h2&gt;&lt;p&gt;引擎是纯 JavaScript，跑在 Web Worker 里，所以深度搜索不会卡住界面。&lt;/p&gt;&#10;&lt;h3 id="搜索"&gt;&lt;a href="#%e6%90%9c%e7%b4%a2" class="header-anchor"&gt;&lt;/a&gt;搜索&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Negamax + Alpha-Beta&lt;/strong&gt; 负极大值搜索&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;迭代加深 + 时间控制&lt;/strong&gt;（随时可以返回当前最优着法）&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;置换表&lt;/strong&gt;：Zobrist 校验、深度优先替换、100 万槽位&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;着法排序&lt;/strong&gt;：置换表着法 &amp;gt; MVV-LVA 吃子 &amp;gt; 杀手着法 &amp;gt; 历史启发&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;静态搜索（Quiescence）&lt;/strong&gt;：消除水平线效应&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;空着裁剪&lt;/strong&gt;、&lt;strong&gt;将军延伸&lt;/strong&gt;、&lt;strong&gt;后期着法削减（LMR）&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;搜索路径内重复局面按和棋处理，&lt;strong&gt;避免引擎自己走进循环&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="评估"&gt;&lt;a href="#%e8%af%84%e4%bc%b0" class="header-anchor"&gt;&lt;/a&gt;评估&#10;&lt;/h3&gt;&lt;p&gt;子力价值 + 位置价值表（每种棋子一张 10×9 表）+ 结构项：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;机动性（马/车/炮/兵各自可走到的格数）&lt;/li&gt;&#10;&lt;li&gt;车的纵线开放性（半开放 / 全开放）&lt;/li&gt;&#10;&lt;li&gt;缺士怕双车、残局炮贬值、残局兵升值&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;开局评估为 0 分（完全对称），速度约 &lt;strong&gt;30 万节点/秒&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;h3 id="六档难度不是简单调深度"&gt;&lt;a href="#%e5%85%ad%e6%a1%a3%e9%9a%be%e5%ba%a6%e4%b8%8d%e6%98%af%e7%ae%80%e5%8d%95%e8%b0%83%e6%b7%b1%e5%ba%a6" class="header-anchor"&gt;&lt;/a&gt;六档难度：不是简单调深度&#10;&lt;/h3&gt;&lt;p&gt;难度是&lt;strong&gt;四层&lt;/strong&gt;可调的：搜索深度 + 思考时间 + 选点随机窗口 + 失误概率。&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;档位&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;深度&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;时间&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;随机窗口&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;失误率&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;入门&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.15s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;150&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;初级&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.3s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;55&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;中级&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.7s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;18&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;高级&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.4s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;大师&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.8s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;宗师&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;「随机窗口」＝在「最优分 − 窗口」范围内的着法里随机挑一个——这是低难度&amp;quot;&lt;strong&gt;像人但会犯错&lt;/strong&gt;&amp;ldquo;的关键。&lt;/p&gt;&#10;&lt;p&gt;但这里有一条铁律：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;无论哪一档，能一步杀就一定会杀。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;低难度可以犯错，但&lt;strong&gt;不能在该赢的时候装傻&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;因为一旦那种事发生，玩家就不认为自己在跟一个对手下棋，而是在跟一个随机数生成器。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="联机服务端说了算"&gt;&lt;a href="#%e8%81%94%e6%9c%ba%e6%9c%8d%e5%8a%a1%e7%ab%af%e8%af%b4%e4%ba%86%e7%ae%97" class="header-anchor"&gt;&lt;/a&gt;联机：服务端说了算&#10;&lt;/h2&gt;&lt;p&gt;同一份服务端同时提供 HTTP 静态服务与 WebSocket 信令，端口共用一个。&lt;/p&gt;&#10;&lt;p&gt;关键是&lt;strong&gt;规则引擎浏览器与 Node 共用同一份代码&lt;/strong&gt;，所以：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;服务端为每个房间持有一份 &lt;code&gt;Position&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;每一步都用规则引擎&lt;strong&gt;重新算一遍&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;非法着法直接回 &lt;code&gt;error&lt;/code&gt;，局面不变&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;改前端无法作弊。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;其他细节：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;房间码 4 位，字符集去掉易混淆的 &lt;code&gt;I/O/0/1&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;座位满了自动成为观战者，可观战、可聊天，不能走棋&lt;/li&gt;&#10;&lt;li&gt;悔棋需对方同意；对方在你请求后走了子，请求自动作废&lt;/li&gt;&#10;&lt;li&gt;断线重连：token 存在 &lt;code&gt;localStorage&lt;/code&gt;，120 秒内自动回到座位，超时判负&lt;/li&gt;&#10;&lt;li&gt;心跳 25 秒一次，死连接自动清理&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一个手机端的真实坑"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e6%89%8b%e6%9c%ba%e7%ab%af%e7%9a%84%e7%9c%9f%e5%ae%9e%e5%9d%91" class="header-anchor"&gt;&lt;/a&gt;一个手机端的真实坑&#10;&lt;/h2&gt;&lt;p&gt;移动浏览器的音频策略比桌面严格得多。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;页面加载时创建 &lt;code&gt;AudioContext&lt;/code&gt; 会被自动播放策略永久挂起。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;所以这个项目特意&lt;strong&gt;不在加载时建上下文&lt;/strong&gt;，而是在第一次用户触摸时才创建并解锁。&lt;/p&gt;&#10;&lt;p&gt;配套做了个&lt;strong&gt;音效自检&lt;/strong&gt;功能：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;状态显示&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;含义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;运行中 · 音效已开启，却仍听不到&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;设备侧静音（iPhone 检查侧边静音拨片；Android 调&lt;strong&gt;媒体音量&lt;/strong&gt;不是通话音量）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一直显示「已挂起」&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;浏览器还在拦截，再多点几下棋盘&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;显示「尚未开始」&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;还没产生过触摸&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;试听按钮&lt;strong&gt;始终会发声&lt;/strong&gt;——这样就能区分&amp;quot;开关关了&amp;quot;和&amp;quot;设备真没声音&amp;rdquo;。&lt;/p&gt;&#10;&lt;p&gt;自动化验证会在&lt;strong&gt;严格自动播放策略 + 手机触屏模拟&lt;/strong&gt;下跑一条完整链路：加载时不建上下文 → 首次触摸即解锁 → 首声不被吞掉 → 落子/AI 应招都有声。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="素材全部可自由使用"&gt;&lt;a href="#%e7%b4%a0%e6%9d%90%e5%85%a8%e9%83%a8%e5%8f%af%e8%87%aa%e7%94%b1%e4%bd%bf%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;素材：全部可自由使用&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;棋子汉字字形取自 &lt;strong&gt;Wikimedia Commons&lt;/strong&gt; 的&lt;strong&gt;公有领域&lt;/strong&gt;作品（作者 Inductiveload）&lt;/li&gt;&#10;&lt;li&gt;原素材是四格精灵图，只取汉字字形，去掉原图圆片，重新合成为木纹实体棋子（径向渐变 + 车削年轮 + 倒角 + 内圈阳文 + 投影）&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;棋盘木纹是 SVG &lt;code&gt;feTurbulence&lt;/code&gt; 程序化生成的&lt;/strong&gt;，无第三方版权&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;音效是 WebAudio 现场合成的&lt;/strong&gt;，不打包任何音频文件&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;所以这个项目可以随便分发，没有版权风险。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p style="text-align:center;margin:2.5em 0"&gt;&#13;&#10;&lt;a href="https://cloudlake.eu.org/lab/xiangqi/" style="display:inline-block;padding:12px 26px;border-radius:8px;background:var(--accent-color);color:var(--accent-color-text);text-decoration:none;font-weight:600"&gt;下一盘（单机版）→&lt;/a&gt;&#13;&#10;&lt;/p&gt;&#13;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;上面是在线单机版。局域网联机需要跑 Node 服务端，本地 &lt;code&gt;npm start&lt;/code&gt; 即可。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="技术选型"&gt;&lt;a href="#%e6%8a%80%e6%9c%af%e9%80%89%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;技术选型&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;选择&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;原因&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;原生 JS，&lt;strong&gt;无构建步骤&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;克隆下来 &lt;code&gt;npm install &amp;amp;&amp;amp; npm start&lt;/code&gt; 就能跑&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;规则引擎浏览器与 Node 共用&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;联机时服务端复用同一份规则做权威校验，&lt;strong&gt;避免两套实现不一致&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AI 用 Web Worker&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;深度搜索不阻塞界面；拿不到 Worker 时自动退回主线程&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;棋子用内联 SVG 精灵表&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;32 枚棋子共用一份渐变与滤镜定义，零额外请求，任意缩放清晰&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;音效用 WebAudio 合成&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不需要打包音频，落子/吃子/将军音色各不相同&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;ws&lt;/code&gt; 是唯一运行时依赖&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;WebSocket 手写容易在分片、掩码、关闭握手上出错&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;hr&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;这个项目让我对&amp;quot;做对&amp;quot;这件事的看法变了一点。&lt;/p&gt;&#10;&lt;p&gt;规则能凭记忆写出来，但&lt;strong&gt;只有 perft 能证明你写对了&lt;/strong&gt;。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item></channel></rss>