From 0d9ea36c10e3422f143e77bfe77abb6848b34f7f Mon Sep 17 00:00:00 2001 From: Joywayer Date: Sun, 5 Jul 2026 01:51:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E4=B8=83=E7=8E=8B=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=EF=BC=9A=E7=A9=B7=E4=B8=BE=E8=B7=9F=E7=89=8C=E6=96=B9=E6=89=8B?= =?UTF-8?q?=E7=89=8C=E5=88=86=E6=94=AF=EF=BC=8C=E8=A1=A5=E4=B8=BB=E7=89=8C?= =?UTF-8?q?=E9=A6=96=E5=87=BA=E4=B8=8E=E6=8B=96=E6=8B=89=E6=9C=BA=E5=90=84?= =?UTF-8?q?=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 回应"是否模拟了跟牌方所有手牌与边界"的核对,补齐: - 主牌首出的跟牌(单/对)——原仅测副牌首出 - 对子首出·同花色仅1张(必出它+补1张) - 拖拉机首出的跟牌方分支:对子不够(出仅有的对+补)、同花无对(补同花)、 多候选拖拉机(cantype=3xx,回归 can_followcard 曾崩溃的 D1 分支)、 3连对首出、富余对子中必出3连拖/拆散拒 - 手牌数==首出张数(全部必出) 全部单测 244 项断言全绿(test_follow 35) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../erqiwang/docs/compliance/02-测试计划.md | 4 +-- server/games/erqiwang/test/test_follow.js | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/server/games/erqiwang/docs/compliance/02-测试计划.md b/server/games/erqiwang/docs/compliance/02-测试计划.md index 4342e07..d5a6d17 100644 --- a/server/games/erqiwang/docs/compliance/02-测试计划.md +++ b/server/games/erqiwang/docs/compliance/02-测试计划.md @@ -146,13 +146,13 @@ ## 5. 当前状态小结 —— ✅ DoD 达成,P0/P1/P2 全部完成 -**共 226 项断言全绿**(`test_arith` 101 / `test_callgrade` 6 / `test_config` 19 / `test_deal` 12 / `test_desk` 6 / `test_follow` 17 / `test_paiju` 31 / `test_rpc` 34)。 +**共 244 项断言全绿**(`test_arith` 101 / `test_callgrade` 6 / `test_config` 19 / `test_deal` 12 / `test_desk` 6 / `test_follow` 35 / `test_paiju` 31 / `test_rpc` 34)。 design 全部服务端可验证章节均有正/反/边界单测: - **§2 构成**:`test_deal` 92张/去3-4/三家28+底8/分值。 - **§3 编码/排序**:`test_arith` order_cards 降序=设计顺序、is_continuous 相邻链逐段+反例。 - **§4**:叫分坐庄 do_callgrade、`mod.jiaofen` 入参校验、投降 touxiang 条件、70分上庄暗牌亮牌下发、坐庄轮换 do_prepare。 -- **§5**:正常跟牌/毙牌/垫牌(§5.1/5.2);**甩牌全面覆盖(§5.4)**——合法(纯单/纯对/单+拖/单+对+拖三混)、甩错(单张/主对/拖拉机分量各自被压、双对之一被压)、副牌禁甩(对手缺门与非缺门)、甩错惩罚**实际执行**(do_playcard 只打最小一张、其余留手);**跟甩牌全面覆盖(§5.4.4)**——对子必出、拖拉机必出/退化对子/退化到单张、多组拖拉机、混合demand逐分量对位、主牌最大化、无主全垫,以及"首出甩牌→跟牌违规→do_playcard 拒"的集成。 +- **§5**:**正常跟牌全面覆盖(§5.1/5.2)**——按跟牌方手牌各分支穷举:主牌首出与副牌首出、单张/对子/拖拉机各牌型、有同花必出对应牌型、同花不足则出仅有的+补、缺门毙牌(数量对应)与垫牌(不压)、拖拉机的对子不够/同花无对/**多候选拖拉机(D1崩溃回归)**/3连对/富余对子必出拖、手牌数==首出张数全部必出;**甩牌全面覆盖(§5.4)**——合法(纯单/纯对/单+拖/单+对+拖三混)、甩错(单张/主对/拖拉机分量各自被压、双对之一被压)、副牌禁甩(对手缺门与非缺门)、甩错惩罚**实际执行**(do_playcard 只打最小一张、其余留手);**跟甩牌全面覆盖(§5.4.4)**——对子必出、拖拉机必出/退化对子/退化到单张、多组拖拉机、混合demand逐分量对位、主牌最大化、无主全垫,以及"首出甩牌→跟牌违规→do_playcard 拒"的集成。 - **§6**:捡分(结算集成间接)、扣底倍数+触发。 - **§7**:算子常规+爬坡逐档、每档分界+升3级、结算集成(大光/过庄/升级/爬坡/傍王/投降)。 - **§8**:算奖 get_chongguan、亮牌 get_liangpai(含 6个7/固定主9-10边界)、算奖并入 X×N(傍王对照)。 diff --git a/server/games/erqiwang/test/test_follow.js b/server/games/erqiwang/test/test_follow.js index 453b3db..77147b9 100644 --- a/server/games/erqiwang/test/test_follow.js +++ b/server/games/erqiwang/test/test_follow.js @@ -43,4 +43,35 @@ t.eq('拖-无拖两对不连必出两对(合法)', res(f([J(1), J(2), N9(1), N9( t.eq('拖-有两对却出散张(拒)', res(f([J(1), J(2), N9(1), N9(2), N8(1)], [J(1), N9(1), N8(1), Q(1)], 4, 2, 302)), false); t.eq('拖-缺门毙同长主拖(合法且压)', beat(f([zK(1), zK(2), zQ(1), zQ(2), oth(9)], [zK(1), zK(2), zQ(1), zQ(2)], 4, 2, 302)), true); +const N6 = d => id(d, 2, 6); + +// ===================== 主牌首出的跟牌(followers 必须跟主牌)===================== +t.eq('主单 有主必跟主', res(f([zQ(1), oth(9)], [zQ(1)], 1, 1, 101)), true); +t.eq('主单 有主却垫副→拒', res(f([zQ(1), oth(9)], [oth(9)], 1, 1, 101)), false); +t.eq('主单 缺主垫副(合法不压)', res(f([oth(9), oth(8)], [oth(9)], 1, 1, 101)), true); +t.eq('主单 缺主垫副 不压', beat(f([oth(9), oth(8)], [oth(9)], 1, 1, 101)), false); +t.eq('主对 有主对必出对', res(f([zJ(1), zJ(2), z9(1)], [zJ(1), zJ(2)], 2, 1, 201)), true); +t.eq('主对 有主对却拆散→拒', res(f([zJ(1), zJ(2), z9(1)], [zJ(1), z9(1)], 2, 1, 201)), false); + +// ===================== 对子首出·同花色不足两张(必出仅有的1张+补)===================== +t.eq('对 同花1张必出它+补1', res(f([N9(1), oth(5), oth(6)], [N9(1), oth(5)], 2, 2, 201)), true); +t.eq('对 同花1张却不出它→拒', res(f([N9(1), oth(5), oth(6)], [oth(5), oth(6)], 2, 2, 201)), false); + +// ===================== 拖拉机首出·跟牌方各手牌分支 ===================== +t.eq('拖 对子不够·必出1对+补', res(f([J(1), J(2), N9(1), N8(1), N6(1)], [J(1), J(2), N9(1), N8(1)], 4, 2, 302)), true); +t.eq('拖 对子不够·不出对→拒', res(f([J(1), J(2), N9(1), N8(1), N6(1)], [N9(1), N8(1), N6(1), Q(1)], 4, 2, 302)), false); +t.eq('拖 同花无对·补同花4张', res(f([K(1), Q(1), J(1), T(1), N9(1)], [K(1), Q(1), J(1), T(1)], 4, 2, 302)), true); +// 多候选拖拉机(3连对里选1个2连拖)——回归 can_followcard 曾崩溃的 cantype=3xx 分支 +t.eq('拖 多候选·出合法2连拖', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2)], [K(1), K(2), Q(1), Q(2)], 4, 2, 302)), true); +t.eq('拖 多候选·出不连两对→拒(D1回归)', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2)], [K(1), K(2), J(1), J(2)], 4, 2, 302)), false); +// 3连对首出(303) +t.eq('拖 3连对·有3连拖必出', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2), N8(1)], [K(1), K(2), Q(1), Q(2), J(1), J(2)], 6, 2, 303)), true); +// 3连对首出、跟牌方有富余对子(4对)但含1个3连拖 → 必出该3连拖,拆散→拒 +t.eq('拖 3连对·富余对子中出3连拖', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2), N9(1), N9(2)], [K(1), K(2), Q(1), Q(2), J(1), J(2)], 6, 2, 303)), true); +t.eq('拖 3连对·拆散3连拖(改出99)→拒', res(f([K(1), K(2), Q(1), Q(2), J(1), J(2), N9(1), N9(2)], [K(1), K(2), Q(1), Q(2), N9(1), N9(2)], 6, 2, 303)), false); + +// ===================== 手牌数 == 首出张数:全部必出 ===================== +t.eq('全出 手牌恰1张(单张首出)', res(f([oth(9)], [oth(9)], 1, 2, 101)), true); +t.eq('全出 手牌恰2张(对子首出)', res(f([oth(9), oth(8)], [oth(9), oth(8)], 2, 2, 201)), true); + process.exit(t.done('follow') ? 0 : 1);