WorkBuddy实战 2026-10-21

单机斗地主升级版:这次它不只打分,还要算清你亏了几手

上一版能打、能教,但点评只有"好棋"和"错了"两个词。这一版把评价变成了账:手数从几变几、拆了哪一组、更优的那手能少几手。顺带,你不用再固定坐庄了——三个人轮流叫分,你也可能当农民。

斗地主难教的不是规则,是取舍。同一个局面,老手出对子,新手出单张,两边的规则都没错,区别只在"这手之后手里还剩几手牌"。

上一版我做了个单机斗地主教学对局,能打、能点评、还带记牌器。但用久了有个不满:点评太像裁判——只说"✔ 好棋"或者"△ 可以更好",至于好在哪、亏在哪,得自己悟。

所以这一版全砸在这一件事上:把"为什么"变成可以数的东西。顺手还补了三处之前缺的:三方叫分、真记牌、农民配合。

跟上一版一样:一个 HTML 文件,78KB,双击就开打。文末可以直接下载。

一、先说最大的变化:你不用再固定坐庄了

上一版为了简单,玩家固定是地主。这其实是个偷懒的设定——地主和农民是两种完全不同的游戏:地主算的是怎么用 20 张牌压制两个人,农民算的是怎么用 17 张牌配合一个人。只教一半,等于没教。

现在开局是三方叫分,0 到 3 分,叫 3 直接定地主,三家都不叫就流局重发(带兜底上限,不会卡死)。AI 按王、2、炸弹、A、K 加权算手牌强度出分。顶部可以切三种模式:

  • 随机轮流叫分(含我)——真实牌桌的节奏,你也要参与出分;
  • 我来叫(我定地主)——想专练地主就选这个,直接坐庄;
  • AI 全自动叫(我只看)——三家叫分全交给 AI,你只负责打牌,用来观察不同角色怎么打。

切角色带来的连带收益挺实在:上一版"农民胜率"那一栏永远是空的,因为你从来不是农民;现在它终于有数据了,而且和地主胜率分开统计——两个位置打法不同,混在一起算毫无参考价值。

二、点评:从"这手不好"到"你少了 4 手"

核心是一个数:手数——这副牌最少要几手才能出完。它是整局的账本,也是评价每一手的尺子。

你出牌前,引擎先算一遍最优拆法;你选牌后,再算出牌后的最优拆法。两个手数一减,就是这一手的代价:

  • 减了(−1 手)——一手带走两手,赚了,标 ✔ 好棋;
  • 持平(0 手)——正好出掉完整一组,标准出法,也是 ✔;
  • 多 1 手(+1 手)——为了这一手拆了原有结构,标 △ 可以更好;
  • 多 2 手以上——标 ✘ 亏了,面板会直接写"一手拆出了几手的窟窿"。

光给数字还不够直观,所以面板里还有一组出牌前后结构对比:上下两排彩色标签,把出牌前、出牌后的最优拆法逐组列出来。一眼就能看见结构是怎么塌的。

单机斗地主对局界面:左侧牌桌,右侧教学中心显示「可以更好」的点评、出牌前后结构对比和更优选择
我随手抽了张单 Q,点评立刻判"△ 可以更好":手数 11 → 12,被拆掉的是顺 8910JQKA,本该走连对 8899 10 10,少 4 手。

我最喜欢的是下面这句拆牌归因:直接点名"被拆掉的牌组:顺 8910JQKA——这些组的牌被拿去凑当前这一手,剩下的碎片只能各自成手"。这比"你出错了"有用得多:它告诉你错在把一个整组拆成了三块碎片。

再往下是更优选择:引擎推荐的那手牌、出完还剩几手、比你这手少几手。有时候只差 1 手,有时候差 4 手——差多少,心里就有数了。

一个很容易漏的实现细节:点评必须在出牌之前算完。 牌一出,手牌和桌面都变了,这时候再算"刚才那手好不好"已经不是同一个局面。所以代码里的顺序是"先算点评,再执行出牌",不是算错顺序,是逻辑上必须如此。

三、记牌器不是给你看的,是 AI 在用

上一版就有个记牌器,但说白了是个给玩家看的表格。这一版把它接进了 AI 的决策链——记牌从"辅助功能"变成了"引擎的输入"。

口径很简单:未见牌 = 54 − 我的手牌 − 已经打出去的牌。15 个点数逐格显示还剩几张没露面,同时标出我自己留着几张,2 和王高亮成关键牌。

记牌器面板:15 个点数逐格显示场上剩余张数与自己手牌数,2 和大小王被高亮
记牌器 tab:每格显示"场上还剩几张 / 我自己留着几张"。大王、小王、2 高亮——这三类决定了残局谁能控制节奏。

这里有个刻意的保守选择:农民视角不扣底牌。 底牌虽然公开了,但它还在对手手里,随时能打出来,所以按"威胁"算,不按"已排除"算。

有了剩余牌,引擎就能判断一件事:这手出完,有没有人压得住? 也就是"必大牌"。判定按牌型分级——王炸恒真;炸弹要看场上还有没有更大的炸;单张看有没有更大的牌;对子得剩余更大的牌 ≥2 张;三张同理要 ≥3 张;连牌受长度和连续性影响,一律保守返回"不一定"。宁可少判一次必大,也不要把牌扔出去被人接走。

四、AI 终于学会"该跟才跟"

上一版的 AI 有个明显的毛病:一遇到要拆顺子的跟牌就 pass,看着像不会打。根子在于当时用了一条硬门槛——"跟完手数不能增加"。这条线太死了。

现在的做法是按拆牌代价分档,代价就是刚才那个数:出牌后手数减出牌前手数。

代价什么时候跟
≤ 0(不亏或赚)无条件跟。这是绝大多数情况,AI 出牌率一下就正常了
= 1(多拆一手)对手剩 ≤8 张、或自己剩 ≤8 张时跟——局面开始紧了,该付这个代价
≥ 2(拆散多组)只在对手剩 ≤4 张时跟。否则留着牌型,别为了跟一手把整副牌拆烂

农民这边补了四条主动配合,这是上一版完全没有的:

  • 不压队友——桌面最大牌在自己人手里,直接让位,绝不抢;
  • 送牌——队友只剩 1 张、自己又是领出方,出最小的单张:地主不要,队友就走了;
  • 锁死——对手只剩 1 张、自己领出,改出对子或连牌。他只有单张,接不住,出牌权就被永久锁住;
  • 拦死——对手只剩 1~2 张还要跟牌时,放弃省牌,用能压的最大牌顶上去。这时候省牌毫无意义,他一接过就结束了。

炸弹也收紧了:只在一把能走完、对手将赢(≤2 张)、炸完基本净手、地主剩 ≤5 张这几种情况下启用。平时它是稀缺资源,不是清小牌的工具。

五、两个致命 bug:有牌却一直"不要"

说实话,这一版有一半时间不是在加功能,是在修两个"看起来像 AI 犯傻"的问题。

第一个:AI 几乎不出牌,只剩炸弹。 现象是所有跟牌一律 pass。查下来是个类型错位——桌面牌存的是牌数组,而生成跟牌的函数要的是牌型对象(带 type 和 main 字段)。数组当对象传进去,取到的"主牌点数"是 undefined,所有候选都被过滤掉,只剩炸弹这种不看主牌的牌型能出。

第二个更阴:飞机永远跟不了。 牌型编码不一致——纯飞机记的"长度"是组数,带牌飞机记的是牌数,而跟牌逻辑统一按"牌数 ÷ 3"反推组数。两个分支各错各的。更麻烦的是 12 张牌既可能是四连三张,也可能是三连三张带三张单,光看牌数根本分不清。

修法是给飞机加两个显式字段:planes(几组三张)和 wing(带不带、带单还是带对),比较时按"组数 + 带牌 + 主牌"三段比。凡是靠牌数反推结构的地方,早晚都会炸。

改完不敢直接信,写了一套引擎断言:76 条(34 条核心规则 + 13 条叫地主与团队配合 + 29 条升级专项),外加 300 局全自动对战跑到正常终局。顺带跑出一组决策分布:出牌 48%、牌型上跟不了 37%、让位队友 13%、能跟但主动保留只有 2%——说明 AI 既在积极出牌,也没有为了跟牌乱拆牌型。

性能倒是意外地轻:20 张手牌、71 个候选算一遍领出决策,大约 0.6 毫秒;跟牌 0.02 毫秒;拆牌 0.008 毫秒。全部同步跑,不需要 Worker。

六、规则这块,还是得老老实实写完

斗地主的牌型有 11 种,四类:基础的(单张、对子、三张)、带牌的(三带一、三带二、四带二)、连牌的(顺子、连对、飞机)、控场的(炸弹、王炸)。

牌型库面板:11 种牌型的规则与要点,按基础、带牌、连牌、控场四类标注
牌型库:11 种牌型各配一条规则 + 一条实战要点。这页在游戏里随时能翻,出牌后点评下方也会自动挂上对应牌型的讲解。

真正的坑都在边界上:

  • 顺子 ≥5 张、连对 ≥3 对、飞机 ≥2 组三张,都含 3~A,不含 2 和王;长度相同才能互压;
  • 四带二不是炸弹:只能被同型四带二压,任何炸弹都能压它——手上有炸弹的人千万别把它当炸弹用;
  • 飞机的翅膀不能从构成三张的那几组牌里凑,这个判定最容易写错;
  • 春天:一方一张没出就结束,额外翻倍。

这些边界漏一条,对局里就会出现"明明能压却不让压",玩家第一反应是"这 AI 有问题",而不是"这代码有 bug"。

七、战绩:地主和农民分开算

战绩存在浏览器本地,跨局累计:总盘数、综合胜率、地主胜率、农民胜率、当前连胜、最高连胜。按综合胜率给 7 级评估,不到 5 盘只显示"样本不足"——不许几把运气就定级。

战绩面板:六项统计与七级评级阶梯,当前等级铂金·进阶级被高亮
战绩面板:6 项统计 + 7 级阶梯,满 5 盘才给评级。地主、农民胜率分两栏——混着算没有参考价值。

阈值卡得比较硬:85% 才算大师·宗师,75% 钻石,65% 铂金,55% 黄金,45% 白银,低于 45% 青铜。打 AI 能稳定过 65%,基本就该去真人桌上试试了。

八、音效和动效,一个文件都没多

这一版加了音效,但没有引入任何音频文件——全部用 WebAudio 现场合成:出牌是 430Hz 到 640Hz 的双音短促"咔",不要是 230Hz 的低频"噗",炸弹是噪声爆破叠 72Hz 锯齿轰鸣,王炸再来一串 440→1320Hz 的上行四音。提示音就一个 900Hz 轻点。

这样做的唯一理由是:单文件、离线可用这条底线不能破。加一个 mp3,就不再是"双击就能玩"了。

动效也是同一套逻辑:发牌逐张落下(22ms 递增延迟),出牌按张数递增 45ms,炸弹有爆闪和牌桌震颤。系统开了"减少动态效果"的话,动画自动全关。音效和动效都能在顶栏一键关掉,状态记在本地。

九、还有几个小心思

  • 悔棋:快照实现,最多退 20 步。教学工具必须允许反悔,不然人不敢试。
  • 金边提示:跟牌时给所有能压过当前牌的牌描金边,但从不灰化、从不禁用——所有牌永远能点。想看"如果我出一张废牌会怎样",那就是它该有的反应。
  • AI 慢速出牌:1.6 秒一步,可选。上一版是强制暂停 5 秒,用过两次就烦了。
  • 非法牌兜底:万一引擎吐出非法组合,不崩、不死循环,最多是这一步跳过。

十、所以这次升级的本质是什么

不是加了几个功能。

上一版的点评像老师傅在旁边看着你打牌,看到臭手就摇头;这一版把摇头的理由写出来了:你这一手,把 12 手能走完的牌打成了 13 手,被拆掉的是那副顺子,其实该走连对。

能说出这句话,需要三样东西凑齐:一个能算手数的拆牌引擎、一个知道场上还剩什么牌的记牌层、一个能对比"你出的"和"引擎想出的"的诊断器。这三样串起来,才叫教学。

至于做出来的过程,还是和 WorkBuddy 聊:先把规则边界一条条讲死,再说要什么样的点评,最后把 AI 该怎么打描述清楚。中间那两个致命 bug,也是我截图盯着看出来的——不是它不会修,是它一开始不觉得那是 bug。

这句话我上一版写过,这一版更信了:难的不是写代码,是想清楚自己要什么,以及在它说"改好了"之后,你自己再看一眼。

文件就一个 HTML,78KB,双击即玩。战绩存在你自己浏览器里,换台设备就是新的一页。

在微信里点不动?点右上角「···」→「在浏览器打开」,再点下载。
文件是单个 HTML,双击就能玩,断网也照常跑;音效是实时合成的,不带任何音频文件。战绩存在浏览器本地,换个浏览器就从零开始。