二七王:修复跟牌判定依赖客户端入参顺序(可翻转本轮胜负与结算)
can_followcard 尾段计算 cardvalue/noflower/nopair 时用的是入参原始数组 followcards(客户端提交顺序),而 get_pairlist/get_tuolaji_list 都按降序 相邻取对。端到端实测:闲家用主拖拉机毙牌且为末轮,降序提交时闲家赢下本轮 (捡分 40、扣底 ×4),把同一手牌打乱成 [51,50,105,104] 后对子漏判、 cardvalue 归 0,变成庄家赢、闲家 0 分大光、不扣底——同一手合法牌仅靠数组 顺序就能翻转胜负、捡分归属与最终结算,同源问题还能抹掉 noflower 缺门标志 污染 §9 下发给全场的牌况表。 改为在 min_ary_deduct 削减 _followcards 之前另存完整排序快照 _sortfollow, 尾段 8 处全部改用它(_followcards 会被削减、不可复用)。 顺带给 get_disbandRoom 加空守卫:平台在 makewar 后立刻置 battlestate=1, 而首局是延迟 1 秒创建的,这段窗口内解散会在 curr_paiju() 的 undefined 上 解引用抛异常、打断平台解散链路;现返回 null 走平台既有的「不带 deskfree」分支。 补 8 条回归(入参顺序无关性 5 条 + 端到端 1 条 + 解散空守卫 2 条), 已用「回退修复 → 用例转红」反验有效性;全套单测 401 项全绿。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,7 +89,40 @@ t.eq('§4.6 投降 result=2(下家坐庄)', tx.pj.result, 2);
|
||||
// ============ §12.2 中途解散 ============
|
||||
const ds = seatBanker("00000", 65);
|
||||
mod.xuanzhu(pk(0, { flower: 1 }));
|
||||
const dsmsg = require('../class.export.js').new().get_disbandRoom(ds.ctx.o_room);
|
||||
const EXPORT = require('../class.export.js').new();
|
||||
const dsmsg = EXPORT.get_disbandRoom(ds.ctx.o_room);
|
||||
t.eq('§12.2 解散:不结算子数、result=3', [dsmsg.data.aset.multiple, dsmsg.data.aset.seatlist.map(p => p.grade), ds.pj.result], [0, [0, 0, 0], 3]);
|
||||
// 平台在 makewar 后立刻置 battlestate=1,而首局是延迟 1 秒创建的:这段窗口内解散必须返回 null,
|
||||
// 而不是让 curr_paiju() 的 undefined 解引用抛异常打断平台的整条解散链路
|
||||
t.eq('解散·牌局尚未创建 → 返回 null 不抛异常',
|
||||
EXPORT.get_disbandRoom({ o_desk: { method: { curr_paiju: () => undefined } } }), null);
|
||||
t.eq('解散·牌桌尚未创建 → 返回 null 不抛异常', EXPORT.get_disbandRoom({}), null);
|
||||
|
||||
// ============ 出牌入参顺序无关性(端到端)============
|
||||
// 同一手完全合法的跟牌,仅客户端提交的数组顺序不同,本轮胜者/捡分/扣底/结算判定必须完全一致。
|
||||
// 构造:庄家(0)首出 红心KKQQ(副牌两连对,含20分),闲家(1)用 黑桃KKQQ(主拖拉机,含20分)毙牌,
|
||||
// 闲家(2)垫方块杂牌;这是本局最后一轮,故同时覆盖扣底判定。
|
||||
function oneTrick(followOrder) {
|
||||
const p = P.new({ paiju_list: [] }, 0);
|
||||
const c = setup("00000", p);
|
||||
p.banker = 0; p.call = 65; p.flower = 4; p.step = 5;
|
||||
// 先把所有牌置为「第1轮已由庄家赢走」,再把参战的 12 张发回三家手上
|
||||
for (const card of p.cards) { card.playround = 1; card.playowner = 0; }
|
||||
const give = (ids, owner) => ids.forEach(i => Object.assign(p.cards[i], { dealowner: owner + 1, playround: -1, playowner: -1 }));
|
||||
give([38, 92, 37, 91], 0); // 红心 K K Q Q
|
||||
give([51, 105, 50, 104], 1); // 黑桃 K K Q Q(主)
|
||||
give([5, 7, 8, 10], 2); // 方块杂牌
|
||||
P.new_playround(p, 1, 0);
|
||||
mod.chupai(pk(0, { cards: [38, 92, 37, 91] }));
|
||||
mod.chupai(pk(1, { cards: followOrder }));
|
||||
mod.chupai(pk(2, { cards: [5, 7, 8, 10] }));
|
||||
const m = c.ctx ? c.ctx : c;
|
||||
const j = m.sent[m.sent.length - 1];
|
||||
return [p.playproc.maxseat, j.data.chupai.grade, j.data.aset.grade, j.data.bottom.multiple];
|
||||
}
|
||||
const ordered = oneTrick([51, 105, 50, 104]); // 降序
|
||||
const shuffled = oneTrick([51, 50, 105, 104]); // 乱序(把两个对子拆开摆)
|
||||
t.eq('序无关 端到端·有序提交:闲家毙牌赢下末轮并扣底×4', ordered, [1, 40, 40, 4]);
|
||||
t.eq('序无关 端到端·乱序提交结果完全一致', shuffled, ordered);
|
||||
|
||||
process.exit(t.done('endgame') ? 0 : 1);
|
||||
|
||||
Reference in New Issue
Block a user