二七王:实现 S-1 curmultiple 与 S-2 mustcard
S-1 当前抓分倍数(design §7.2.0): - class.paiju.js 新增 get_curmultiple(),复用 get_qvalue + get_upgrade, 与结算 aset.upgrade 同源同算法,差别仅在不含扣底(末轮才产生)。 - 随现有包下发、不另开推送:shangzhuang / chupai1-3 / deskinfo.PushCards。 - 三家同值,整表下发不涉及泄露(捡分本就公开);叫分未定时为 0。 S-2 跟牌必出牌(design §5.2): - class.paiju.js 新增 get_mustcard(seat),内部复用与出牌校验完全相同的 get_followcard,故建议与校验天然一致。 - 【只发给 nextseat 一家】:它是该玩家自己手牌的子集,整表下发会泄露 他家手牌结构(server 红线:发全 ≠ 发多)。 - 四种情形不下发:非出牌阶段、该座位是本轮首家、首家甩牌、必出牌为空。 甩牌一条尤其重要——甩牌跟牌走 flush_follow_ok 的逐分量匹配,与 get_followcard 不是同一条路径,给建议会给错。 测试(新增 test/test_hint.js,33 checks): - curmultiple:算法层逐档对齐 design §7.2.0 判定表(含爬坡 Q 分段)、 三家同值、重连与出牌包一致、叫分未定为 0、与 get_upgrade 绝对值一致。 - mustcard:只有 nextseat 收到、另两家没有、内容确属收包者自己的牌、 首家无、缺门有富余时无、手牌数恰等于首家张数时整手必出、甩牌不发、 非出牌阶段不发、重连按 currseat 门控。 过程中修正的两个测试自身问题(均为脚本缺陷,非业务缺陷,业务判定经证据核实正确): - 「缺门方无必出」原用例给闲2 只发 2 张牌,恰等于首家张数,get_followcard 正确判定为「整手必出」;改为发 4 张才留出选择余地,并补一条对照用例 锁住「恰等于张数则整手必出」这个正确行为。 - 「甩牌不发」「非出牌阶段不发」两条原本是假通过——桩的 startcount 仍是 -1, 前置守卫先返回了 null,根本没测到被测因素。改为先把桩推进到跟牌态、 每个用例只变动一个因素,并加一条「自证」用例确认推进后确实能算出必出牌。 另:mustcard 补进 test_leak.js 的 CARD_FIELDS 审计白名单(与 burycards 同理, 不加则误发不会被泄露审计发现)。反向验证:改成整表下发后,审计立刻报出 5 张越权牌;把 curmultiple 写死为 1 后,两条断言失败。均已还原、全绿。 协议文档同步 shangzhuang / chupai1-3 / PushCards 的字段说明。 至此 §7.5 服务端待补 5 条全部完成,服务端侧无遗留。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -256,6 +256,16 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
return cls_youle_erqiwang_paiju.get_liangpai(paiju);
|
||||
}
|
||||
|
||||
//获取当前抓分倍数(design §7.2.0,实时预览)
|
||||
paiju.method.get_curmultiple = function(){
|
||||
return cls_youle_erqiwang_paiju.get_curmultiple(paiju);
|
||||
}
|
||||
|
||||
//获取某座位本轮跟牌的必出牌(design §5.2)
|
||||
paiju.method.get_mustcard = function(seat){
|
||||
return cls_youle_erqiwang_paiju.get_mustcard(paiju, seat);
|
||||
}
|
||||
|
||||
return paiju;
|
||||
},
|
||||
|
||||
@@ -827,6 +837,55 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
return re;
|
||||
},
|
||||
|
||||
//当前抓分倍数(design §7.2.0):按【当前累计捡分】实时算出的判定倍率绝对值,
|
||||
//供客户端顶部「抓分」列角标显示「若此刻结束是几倍」。
|
||||
//与结算包 aset.upgrade 同源同算法(get_qvalue + get_upgrade),差别只在:
|
||||
//这里的捡分【不含扣底】——扣底要到最后一轮打完才产生,出牌过程中尚不存在。
|
||||
//叫分未定(call<=0)时返回 0,前端不显示角标。
|
||||
get_curmultiple: function(o_paiju){
|
||||
var _call = o_paiju.call;
|
||||
if (!_call || _call <= 0){
|
||||
return 0;
|
||||
}
|
||||
var _cfg = cls_youle_erqiwang_config.parse(o_paiju.o_desk.o_room.roomtype);
|
||||
var _grade = cls_youle_erqiwang_paiju.get_jian_grade(o_paiju).grade;
|
||||
var _q = cls_youle_erqiwang_arith.get_qvalue(_call, _cfg.climb);
|
||||
var _u = cls_youle_erqiwang_arith.get_upgrade(_call, _grade, _q);
|
||||
return (_u < 0) ? (0 - _u) : _u;
|
||||
},
|
||||
|
||||
//某座位本轮跟牌的「必出牌」(design §5.2),供客户端自动选中。
|
||||
//与服务端出牌校验用的是同一个 get_followcard,故建议与校验结果天然一致。
|
||||
//返回 null(不下发)的情形:
|
||||
// 1. 非出牌阶段、或该座位就是本轮首家(首家自由出牌,无必出);
|
||||
// 2. 本轮首家【甩牌】——甩牌跟牌走 flush_follow_ok 的逐分量匹配(design §5.4.4),
|
||||
// 与 get_followcard 不是同一条路径,此时给建议会给错,宁可不发;
|
||||
// 3. 算出来的必出牌为空(玩家可自由选)。
|
||||
get_mustcard: function(o_paiju, seat){
|
||||
if (o_paiju.step != 5){
|
||||
return null;
|
||||
}
|
||||
var _proc = o_paiju.playproc;
|
||||
//本轮还没人出牌 / 该座位是首家 → 无必出
|
||||
if (!_proc || _proc.start == seat || _proc.startcount == null || _proc.startcount <= 0){
|
||||
return null;
|
||||
}
|
||||
//甩牌不走这条路径,见上
|
||||
if (_proc.shuai_demand){
|
||||
return null;
|
||||
}
|
||||
var _inhand = cls_youle_erqiwang_paiju.get_seat_cards(o_paiju, seat);
|
||||
if (!_inhand || _inhand.length <= 0){
|
||||
return null;
|
||||
}
|
||||
var _get = cls_youle_erqiwang_arith.get_followcard(
|
||||
o_paiju.flower, _inhand, _proc.startcount, _proc.startflower, _proc.starttype);
|
||||
if (!_get || !_get.mustcard || _get.mustcard.length <= 0){
|
||||
return null;
|
||||
}
|
||||
return _get.mustcard;
|
||||
},
|
||||
|
||||
//获取小局结算包,type 0正常结算 1投降结算 2解散结算
|
||||
get_paiju_account: function(o_paiju, type, msg){
|
||||
o_paiju.endtime = min_now();
|
||||
|
||||
Reference in New Issue
Block a user