From 9456ab9319fc676172d8e8d1e7dba672d7f872b1 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Sun, 5 Jul 2026 16:39:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E4=B8=83=E7=8E=8B=EF=BC=9A=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=20=C2=A711=20=E9=97=B2=E5=AE=B6=E4=B8=89=E6=8F=90?= =?UTF-8?q?=E7=A4=BA(=E8=B8=A9/=E6=B2=A1=E5=88=86/=E6=9C=89=E5=88=86)=20+?= =?UTF-8?q?=20=E6=98=8E=E7=A1=AE=E5=89=AF7/=E5=89=AF2=20=E6=88=90=E5=AF=B9?= =?UTF-8?q?=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 全面符合性再审查(4 维度独立对抗核对)结论:与计分/发牌/出牌/跟牌/甩牌 相关规则均一致,无缺陷;仅 §11 闲家三提示服务端缺失、副7/副2 异花色 成对语义存疑两项待办。本次按用户裁定落地: 1. §11 三提示(新增 mod.tishi):闲家在出牌阶段(step5)发 踩/没分/有分, 服务端不校验真实性、只 SendPack 转发给对家(另一闲家 3-banker-seat); 庄家无对家不参与;非法 tip/非出牌阶段/庄家发均拒。 协议补 packet_protocol §13.7/13.8;test_rpc +11 例(正反+边界)。 2. 副7/副2 成对语义:裁定"只同花色同点数两副成对"(代码本已如此,无需改), 在 design §3 补说明闭合该反复出现的存疑(异花色副7 不成对)。 compliance 文档同步:§11 状态转✅、首轮结论摘要加"非当前状态"说明、 计数 259→270。全套 270 项断言全绿(rpc 34→45)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../docs/compliance/01-design合规逐节核对.md | 13 +++-- .../erqiwang/docs/compliance/02-测试计划.md | 5 +- server/games/erqiwang/docs/design/design.md | 2 + .../erqiwang/docs/protocol/packet_protocol.md | 24 +++++++++ server/games/erqiwang/mod.js | 53 +++++++++++++++++++ server/games/erqiwang/test/test_rpc.js | 27 ++++++++++ 6 files changed, 117 insertions(+), 7 deletions(-) diff --git a/server/games/erqiwang/docs/compliance/01-design合规逐节核对.md b/server/games/erqiwang/docs/compliance/01-design合规逐节核对.md index c7cbf4e..d949e34 100644 --- a/server/games/erqiwang/docs/compliance/01-design合规逐节核对.md +++ b/server/games/erqiwang/docs/compliance/01-design合规逐节核对.md @@ -45,7 +45,7 @@ - ✅ **§5.4 甩牌(已全部完成)**:副牌绝对禁甩、最大性按对手全部主牌逐分量判定、甩错惩罚、以及 §5.4.4 强制跟牌分量拆解(`flush_follow_ok`:拖拉机→对子→单张优先级)均已实现并用 Node 验证。 - 🟩 **§9 查牌 / §8.2 亮牌(服务端已完成)**:亮牌 `get_liangpai`(庄家埋牌后 28 张按阈值统计)在 `maipai`/重连下发给闲家;报无主的座位统计(`info`/`baozhu`/`PushCards.seatlist`)与亮牌均按查牌位 `cfg.nocheck` 门控(不查牌一律不下发);新增 `mingpai` RPC(可查牌 + 已报无主时查看他家全部主牌)。剩客户端展示(面板/明牌按钮)。 - 🟩 **§4.4 70 分暗牌亮 3 秒(服务端已就绪)**:`shangzhuang` 对 70 分坐庄把 8 张暗牌下发给所有玩家并带 `ancard3s=1`,供客户端在庄家摸暗牌前亮 3 秒;顺带修正了**非 70 分把暗牌泄露给闲家**的问题(现非 70 分暗牌只发庄家,符合 §4"只有庄家可见")。剩下的 3 秒动画由客户端完成。 -- 🟧 **§11 闲家 3 提示(踩/没分/有分)**:交互展示,需客户端配合。 +- ✅ **§11 闲家 3 提示(踩/没分/有分)**:`mod.tishi` 服务端转发给对家已实现;剩客户端展示。 - 🟧 **客户端建房配置**:`roomtype` 已改为位串(见下表:位0局数/位1扣卡/位2傍王/位3爬坡/位4查牌),需在 erqiwang 客户端建房界面按位勾选拼串;服务端已按该格式读取(`class.config.js`)。 ### roomtype 位串约定(服务端已按此读取;解析入口 `class.config.js`) @@ -66,7 +66,9 @@ ## 结论摘要 -| 设计章节 | 主题 | 结论 | +> **注**:下表是**首轮核对**的初始发现快照(保留以记录起点)。其中 §4.4、§5.4、§6.3、§7、§8.1-8.4、§9、§10、§11 等标 🟥/🟧/🟨 的项**后续均已修复并验证**——当前逐节状态以本文档后面的分节详情及全套单测(270 项全绿)为准。 + +| 设计章节 | 主题 | 结论(首轮快照,非当前状态) | | --- | --- | --- | | §2 | 牌局构成(92 张、去 3/4) | ✅ 符合 | | §3 | 主牌顺序(牌编码) | ✅ 符合(副 7/副 2 跨花色成对存疑,见下) | @@ -271,11 +273,12 @@ --- -## §11 牌局交互提示 — 🟨 +## §11 牌局交互提示 — 🟩 - ✅ 牌局结束亮底牌:`get_bottom_account` 结算恒带 `bottom.cards`。 -- 甩牌相关提示:见 §5.4(甩牌本身实现不符)。 -- 🟨 **闲家 3 提示(踩 / 没分 / 有分)无服务端支持**:设计 §11 要求闲家出牌时提供这三种对家提示;服务端未计算 / 下发对应信号(当前仅有 `grade` 等原始信息,需客户端或后续补充)。 +- ✅ 选主对子数 / 70 分投降按钮门控:随 `shangzhuang`/`xuanzhu` 阶段包下发(见 §4)。 +- ✅ 甩牌相关提示:见 §5.4(甩牌实现已核对一致)。 +- ✅ **闲家 3 提示(踩 / 没分 / 有分)**:`mod.tishi`(design §11)——闲家在出牌阶段(step5)发 踩/没分/有分,服务端不校验真实性、只 `SendPack` 转发给对家(另一闲家 `3-banker-seat`);庄家不参与。协议见 packet_protocol §13.7/13.8,测试见 `test_rpc`。 --- diff --git a/server/games/erqiwang/docs/compliance/02-测试计划.md b/server/games/erqiwang/docs/compliance/02-测试计划.md index b0b6162..7f02466 100644 --- a/server/games/erqiwang/docs/compliance/02-测试计划.md +++ b/server/games/erqiwang/docs/compliance/02-测试计划.md @@ -96,7 +96,7 @@ | --- | --- | --- | --- | --- | | 一局端到端:发牌→叫分坐庄→选主→埋牌→逐轮出牌→末轮扣底→结算→轮庄 | 全链路 | 正:一局可控发牌打到结算,各家总分正确;含连庄/下家轮转 | L2 | ❌ | -> §11 牌局交互提示为纯客户端展示,无服务端可测项。 +> §11 牌局交互提示中,选主对子数/亮底牌等随对应阶段包下发;闲家三提示(踩/没分/有分)为服务端转发项(`mod.tishi`,见 P1)。 --- @@ -122,6 +122,7 @@ 11. ✅ §8.2 亮牌更多阈值(6个7/固定主9-10边界)、§7 算子每档分界+升3级(`test_arith`)。 12. ✅ §12 端到端(`test_rpc` 真实驱动到出牌就绪)。 13. ✅ §10 直接调 `export.get_asetcount`/`get_needroomcard`/`get_needroomcard_joinroom`(`test_config`)。 +14. ✅ **§11 闲家三提示 `mod.tishi`**——正:闲家 step5 发 踩/没分/有分,只转发给对家(`3-banker-seat`,验证 fromid/seat/tip);反:庄家发/非出牌阶段/非法 tip/缺 tip 均拒(`test_rpc` 11 例)。 --- @@ -146,7 +147,7 @@ ## 5. 当前状态小结 —— ✅ DoD 达成,P0/P1/P2 全部完成 -**共 259 项断言全绿**(`test_arith` 101 / `test_callgrade` 6 / `test_config` 19 / `test_deal` 12 / `test_desk` 6 / `test_follow` 50 / `test_paiju` 31 / `test_rpc` 34)。 +**共 270 项断言全绿**(`test_arith` 101 / `test_callgrade` 6 / `test_config` 19 / `test_deal` 12 / `test_desk` 6 / `test_follow` 50 / `test_paiju` 31 / `test_rpc` 45)。 design 全部服务端可验证章节均有正/反/边界单测: - **§2 构成**:`test_deal` 92张/去3-4/三家28+底8/分值。 diff --git a/server/games/erqiwang/docs/design/design.md b/server/games/erqiwang/docs/design/design.md index b40bcad..ac962c5 100644 --- a/server/games/erqiwang/docs/design/design.md +++ b/server/games/erqiwang/docs/design/design.md @@ -90,6 +90,8 @@ 副牌(非选定花色,且非 2、7、王)从大到小:A、K、Q、J、10、9、8、6、5。 +> **关于"对子"的判定**:一副牌里同一张牌有两副(deck1 / deck2),"对子"指**同一张具体牌的两副**(如两张♦7、两张大王)。正 7、副 7、正 2、副 2 虽在上表中各占一个**大小等级**(用于排序与比大小时,同级的三张副 7 视为等大),但**成对只认同一花色同点数的两副**——即两张**不同花色**的副 7(如♠7 + ♥7)**不构成一对**,两张不同花色的副 2 同理。拖拉机的连对判定也以此为基础(每一节连对里的每个对子都须是同花色同点数两副)。 + --- ## 4. 开局与坐庄流程 diff --git a/server/games/erqiwang/docs/protocol/packet_protocol.md b/server/games/erqiwang/docs/protocol/packet_protocol.md index ea20fbe..94d99e7 100644 --- a/server/games/erqiwang/docs/protocol/packet_protocol.md +++ b/server/games/erqiwang/docs/protocol/packet_protocol.md @@ -232,6 +232,30 @@ > "再点一次取消查看"是客户端的显示开关,无需再请求服务端。 +## 13.7 出牌提示(tishi,客户端→服务器) + +发包者:游戏 收包者:服务器 app:youle route:erqiwang rpc:tishi + +闲家在出牌阶段向对家(另一闲家)发"踩/没分/有分"提示(design §11)。服务端仅在**出牌阶段(step5)、且发起者为闲家**(`seat != banker`)、**tip 合法(1/2/3)** 时受理;**不校验提示真实性**(玩家可主观发送);庄家无对家,不受理。 + +| 参数名 | 类型 | 说明 | +| --- | --- | --- | +| agentid | 字符 | 代理id | +| playerid | 整数 | 玩家id | +| gameid | 字符 | 游戏id | +| roomcode | 整数 | 房间号 | +| seat | 整数 | 发出提示的闲家位置序号 | +| tip | 整数 | 提示类型:1=踩(我能大过庄家)、2=没分(我手上没分了)、3=有分(我手上有分) | + +## 13.8 出牌提示(tishi,服务器→客户端) + +发包者:服务器 收包者:游戏 app:youle route:erqiwang rpc:tishi (**只转发给对家**,即另一闲家 `3 - banker - seat`) + +| 参数名 | 类型 | 说明 | +| --- | --- | --- | +| seat | 整数 | 发出提示的闲家位置序号 | +| tip | 整数 | 提示类型:1=踩、2=没分、3=有分 | + --- ## 14. 结算(jiesuan) diff --git a/server/games/erqiwang/mod.js b/server/games/erqiwang/mod.js index 14c1b81..1046729 100644 --- a/server/games/erqiwang/mod.js +++ b/server/games/erqiwang/mod.js @@ -516,6 +516,59 @@ youle_erqiwang.mingpai = function(pack){ youle_erqiwang.app.SendPack(msg); } +//闲家出牌提示(design §11):闲家在出牌阶段向对家(另一闲家)发"踩/没分/有分"信号。 +//纯转发、不校验真实性、只发给对家;庄家无对家,不参与。tip:1=踩 2=没分 3=有分。 +youle_erqiwang.tishi = function(pack){ + var agentid = pack.data.agentid; + var playerid = parseInt(pack.data.playerid); + var gameid = pack.data.gameid; + var roomcode = parseInt(pack.data.roomcode); + var seat = parseInt(pack.data.seat); + var tip = parseInt(pack.data.tip); + //检查玩家的位置是否正确 + var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid); + if (!o_room){ + return; + } + //牌桌 + var o_desk = o_room.o_desk; + if (!o_desk){ + return; + } + //牌局 + var o_paiju = o_desk.method.curr_paiju(); + if (!o_paiju){ + return; + } + //只在出牌阶段(step5)任意时刻可发 + if (o_paiju.step != 5){ + return; + } + //只有闲家可发提示,庄家无对家、不参与 + if (seat == o_paiju.banker){ + return; + } + //提示类型:1=踩 2=没分 3=有分 + if (tip != 1 && tip != 2 && tip != 3){ + return; + } + + //对家 = 另一个闲家(三座位号之和为 0+1+2=3) + var partner = 3 - o_paiju.banker - seat; + + //只转发给对家 + var msg = {}; + msg.app = "youle"; + msg.route = "erqiwang"; + msg.rpc = "tishi"; + msg.data = {}; + msg.data.seat = seat; //发出提示的闲家 + msg.data.tip = tip; + msg.conmode = o_room.seatlist[partner].conmode; + msg.fromid = o_room.seatlist[partner].fromid; + youle_erqiwang.app.SendPack(msg); +} + //准备 youle_erqiwang.zhunbei = function(pack){ var agentid = pack.data.agentid; diff --git a/server/games/erqiwang/test/test_rpc.js b/server/games/erqiwang/test/test_rpc.js index 8d0dd08..4640eda 100644 --- a/server/games/erqiwang/test/test_rpc.js +++ b/server/games/erqiwang/test/test_rpc.js @@ -126,4 +126,31 @@ t.eq('埋牌 不查牌 闲家无liangpai', mpXian && mpXian.data.liangpai, undef t.eq('重连 可查牌 PushCards有seatlist', E.get_deskinfo(e.o_room, 1).PushCards.seatlist !== undefined, true); t.eq('重连 不查牌 PushCards无seatlist', E.get_deskinfo(en.o_room, 1).PushCards.seatlist, undefined); +// ===================== §11 闲家出牌提示 tishi(只转发给对家,不校验)===================== +const mkTip = (step, banker) => ({ step, banker }); +// 闲家1 发"踩"(tip=1),banker=0 → 对家=2;默认 seatlist fromid==座位号 +let tp = setup("00000", mkTip(5, 0)); mod.tishi(pack(1, { tip: 1 })); +t.eq('提示 合法 下发1包', tp.sent.length, 1); +t.eq('提示 只发给对家(fromid=2)', tp.sent[0] && tp.sent[0].fromid, 2); +t.eq('提示 rpc=tishi', tp.sent[0] && tp.sent[0].rpc, 'tishi'); +t.eq('提示 data.seat=发出者1', tp.sent[0] && tp.sent[0].data.seat, 1); +t.eq('提示 data.tip=1(踩)', tp.sent[0] && tp.sent[0].data.tip, 1); +// 闲家2 发"有分"(tip=3) → 对家=1 +tp = setup("00000", mkTip(5, 0)); mod.tishi(pack(2, { tip: 3 })); +t.eq('提示 闲2发→对家1(fromid=1)', tp.sent[0] && tp.sent[0].fromid, 1); +// banker=2 时,闲家0 发"没分"(tip=2) → 对家=1 +tp = setup("00000", mkTip(5, 2)); mod.tishi(pack(0, { tip: 2 })); +t.eq('提示 banker=2·闲0发→对家1', tp.sent[0] && tp.sent[0].fromid, 1); +// 庄家发 → 拒(庄家无对家) +tp = setup("00000", mkTip(5, 0)); mod.tishi(pack(0, { tip: 1 })); +t.eq('提示 庄家发 拒', tp.sent.length, 0); +// 非出牌阶段 → 拒 +tp = setup("00000", mkTip(3, 0)); mod.tishi(pack(1, { tip: 1 })); +t.eq('提示 非出牌阶段(step3) 拒', tp.sent.length, 0); +// 非法 tip / 缺 tip → 拒 +tp = setup("00000", mkTip(5, 0)); mod.tishi(pack(1, { tip: 9 })); +t.eq('提示 非法tip=9 拒', tp.sent.length, 0); +tp = setup("00000", mkTip(5, 0)); mod.tishi(pack(1, {})); +t.eq('提示 缺tip 拒', tp.sent.length, 0); + process.exit(t.done('rpc') ? 0 : 1);