From 8948a2950fe840d80e012b506a1c6a419282756f Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 12 Aug 2026 04:21:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E4=B8=83=E7=8E=8B=EF=BC=9Achupai1=20?= =?UTF-8?q?=E4=B8=8B=E5=8F=91=E7=94=A9=E7=89=8C=E5=88=86=E9=87=8F=E6=9E=84?= =?UTF-8?q?=E6=88=90=20shuai?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cardtype 只能表达单一牌型(1xx 单张 / 2xx 对子 / 3xx 拖拉机),而 甩牌是单张、对子、拖拉机自由混搭(design §5.4.3),会被 can_playcard 的牌型推导压平——例如「主K对 + 主5」得到 103、「两连对 + 散对」得到 203,客户端拿到 cardtype 无从还原分组,甚至看不出这是一次甩牌。 服务端本就已经算出了分量需求 shuai_demand(供跟牌逐分量强制匹配, design §5.4.4),按「每个下发包必须携带前端界面所需的全部核心数据」 一并下发:chupai1 在合法甩牌时带 shuai = {tractors,pairs,singles}, 非甩牌与甩错退化为单张时不带此属性。 test_rpc 新增 8 项:合法甩牌带 shuai、非甩牌不带、甩错只打最小一张 且不带 shuai。 Co-Authored-By: Claude Opus 5 (1M context) --- server/games/erqiwang/class.paiju.js | 5 +++ server/games/erqiwang/mod.js | 5 +++ server/games/erqiwang/test/test_rpc.js | 45 ++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/server/games/erqiwang/class.paiju.js b/server/games/erqiwang/class.paiju.js index 284b2ad..dba0c8b 100644 --- a/server/games/erqiwang/class.paiju.js +++ b/server/games/erqiwang/class.paiju.js @@ -638,6 +638,11 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { re1.cardtype = can.cardtype; re1.cards = cards; //实际打出的牌(甩错时为单张) re1.shuaicuo = _shuaicuo; + //合法甩牌的分量构成:cardtype 只能表达单一牌型,甩牌(单张/对子/拖拉机自由混搭, + //design §5.4.3)会被压平成 1xx/2xx,客户端无从据此还原分组,故另行下发分量需求 + if (o_paiju.playproc.shuai_demand){ + re1.shuai = o_paiju.playproc.shuai_demand; + } return re1; } diff --git a/server/games/erqiwang/mod.js b/server/games/erqiwang/mod.js index ab51fc3..bb349d6 100644 --- a/server/games/erqiwang/mod.js +++ b/server/games/erqiwang/mod.js @@ -477,6 +477,11 @@ youle_erqiwang.chupai = function(pack){ msg.data.count = re.count; msg.data.flower = re.flower; msg.data.cardtype = re.cardtype; + //合法甩牌的分量构成(design §5.4.3),供前端按 拖拉机/对子/单张 分组展示; + //非甩牌(含甩错退化为单张)无此属性 + if (re.shuai){ + msg.data.shuai = re.shuai; + } break; case 2: //第二个出牌 msg.rpc = "chupai2"; diff --git a/server/games/erqiwang/test/test_rpc.js b/server/games/erqiwang/test/test_rpc.js index 0db7345..3f8eed0 100644 --- a/server/games/erqiwang/test/test_rpc.js +++ b/server/games/erqiwang/test/test_rpc.js @@ -133,6 +133,51 @@ 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); +// ===================== §5.4 甩牌:chupai1 需下发分量构成 shuai(cardtype 表达不了甩牌)===================== +// 主花色=♦(1)。庄家(0) 甩「主♦K对 + 主♦5单张」;两闲家手上一张主牌都没有 → 甩牌合法 +function mkShuai(extraToSeat1) { + const pj = { + idx: 1, step: 5, banker: 0, call: 65, flower: 1, callproc: [], + seatlist: [[[0, 0], [0, 0], [0, 0], [0, 0], [-1, -1]], [[0, 0], [0, 0], [0, 0], [0, 0], [-1, -1]], [[0, 0], [0, 0], [0, 0], [0, 0], [-1, -1]]], + playproc: {}, cards: make108() + }; + for (let i = 0; i < 108; i++) pj.cards[i].dealowner = -1; // 先全部清空 + const give = (cid, s) => { pj.cards[cid].dealowner = s + 1; pj.cards[cid].playround = -1; }; + give(id(1, 1, 13), 0); give(id(2, 1, 13), 0); give(id(1, 1, 5), 0); // 庄家:主♦K对 + 主♦5 + give(id(1, 3, 9), 1); give(id(1, 3, 8), 1); give(id(2, 3, 9), 1); // 闲1:全副♥,无主牌 + give(id(1, 2, 9), 2); give(id(1, 2, 8), 2); give(id(2, 2, 9), 2); // 闲2:全副♣,无主牌 + if (extraToSeat1) give(extraToSeat1, 1); // 需要造甩错时给闲1一张大主牌 + pj.method = { + check_cards_valid: c => P.check_cards_valid(pj, c), + check_cards_inhand: (c, s) => P.check_cards_inhand(pj, c, s), + do_playcard: c => P.do_playcard(pj, c), + get_seat_cards: s => P.get_seat_cards(pj, s), + have_baofu: () => (pj.seatlist[0][4][0] == 0 || pj.seatlist[1][4][0] == 0 || pj.seatlist[2][4][0] == 0) + }; + P.new_playround(pj, 1, 0); + return pj; +} +// 合法甩牌 → 带 shuai 分量 +let sh = mkShuai(null); let shc = setup("00000", sh); +mod.chupai(pack(0, { cards: [id(1, 1, 13), id(2, 1, 13), id(1, 1, 5)] })); +let shPk = shc.sent.filter(m => m.rpc === 'chupai1')[0]; +t.eq('甩牌 合法 下发 chupai1', !!shPk, true); +t.eq('甩牌 打出3张', shPk && shPk.data.cards.length, 3); +t.eq('甩牌 shuai=1对+1单张', shPk && shPk.data.shuai, { tractors: [], pairs: 1, singles: 1 }); +t.eq('甩牌 无 shuaicuo', shPk && shPk.data.shuaicuo, undefined); +// 非甩牌(单一对子)→ 无 shuai +sh = mkShuai(null); shc = setup("00000", sh); +mod.chupai(pack(0, { cards: [id(1, 1, 13), id(2, 1, 13)] })); +shPk = shc.sent.filter(m => m.rpc === 'chupai1')[0]; +t.eq('非甩牌(单一对子) 无 shuai', shPk && shPk.data.shuai, undefined); +// 甩错(闲1 持大王,能压过主K对里的单张分量)→ 收回、只打最小一张、无 shuai +sh = mkShuai((2 - 1) * 54 + 53); shc = setup("00000", sh); // 第二副大王给闲1 +mod.chupai(pack(0, { cards: [id(1, 1, 13), id(2, 1, 13), id(1, 1, 5)] })); +shPk = shc.sent.filter(m => m.rpc === 'chupai1')[0]; +t.eq('甩错 shuaicuo=1', shPk && shPk.data.shuaicuo, 1); +t.eq('甩错 只打出最小一张(主♦5)', shPk && shPk.data.cards, [id(1, 1, 5)]); +t.eq('甩错 无 shuai 分量', shPk && shPk.data.shuai, undefined); + // ===================== 重连 PushCards.pushlist:结构恒 3 座位 + 按本局主牌花色排序 ===================== // 造 5 轮已出牌的牌局(每轮每家 1 张),外加 seat0 在第 1 轮同时出「主5 + 副♥A」用于验证排序 function mkPushlist() {