二七王:修复跟牌判定依赖客户端入参顺序(可翻转本轮胜负与结算)

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:
2026-08-23 12:18:04 +08:00
co-authored by Claude Opus 5
parent 685b629854
commit ce4c972b7b
4 changed files with 97 additions and 13 deletions
+18 -10
View File
@@ -1043,6 +1043,14 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
_inhandcards = cls_youle_erqiwang_arith.order_cards(mainflower, _inhandcards);
_followcards = cls_youle_erqiwang_arith.order_cards(mainflower, _followcards);
//已排序跟牌的完整快照:下面的必出/可出校验会用 min_ary_deduct 削减 _followcards,
//而后半段的牌面值(cardvalue)与缺门/无对(noflower/nopair)判定必须基于「完整且已排序」的跟牌。
//这里绝不能改用入参 followcards——那是客户端提交的原始顺序,而 get_pairlist / get_tuolaji_list
//都按降序相邻取对:乱序提交会漏判对子/拖拉机,把本该压过的主拖拉机算成牌面 0(本轮胜者、
//捡分归属、扣底倍数全被客户端的数组顺序左右),也能抹掉缺门标志污染 §9 牌况表。
//(dev-guide server 04 §8:前端不是数据源,服务端不得依赖客户端可控的入参顺序)
var _sortfollow = _followcards.concat();
//根据第一个玩家的出牌获取后面玩家必出的和可出的牌
var get = cls_youle_erqiwang_arith.get_followcard(mainflower, _inhandcards, startcount, startflower, startcardtype);
@@ -1113,8 +1121,8 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
/////////// 可以跟牌 ///////////
//根据跟牌情况获取是否有相同花色的牌,是否有对子
//如果第一张牌的花色与第一家的出牌花色不一样则认为没有了相同花色的牌
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, followcards[0]);
//如果最大的一张牌的花色与第一家的出牌花色不一样则认为没有了相同花色的牌
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, _sortfollow[0]);
var _flower = parseInt(_code / 100);
if (_code > 1000){
_flower = mainflower;
@@ -1123,8 +1131,8 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
can.noflower = true;
can.nopair = true;
}
//如果最后一张牌的花色与第一家的出牌花色不一样则认为没有了相同花色的牌
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, followcards[followcards.length - 1]);
//如果最小的一张牌的花色与第一家的出牌花色不一样则认为没有了相同花色的牌
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, _sortfollow[_sortfollow.length - 1]);
var _flower = parseInt(_code / 100);
if (_code > 1000){
_flower = mainflower;
@@ -1135,7 +1143,7 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
}
//如果第一家出的是对子或拖拉机牌型,而跟牌没有出对子则认为没有相同花色的对子
if (startcardtype > 200){
var pairlist = cls_youle_erqiwang_arith.get_pairlist(mainflower, followcards);
var pairlist = cls_youle_erqiwang_arith.get_pairlist(mainflower, _sortfollow);
if (pairlist.length != startcardtype % 100){
can.nopair = true;
}
@@ -1144,7 +1152,7 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
//根据第一家的出牌计算牌值大小
//一张单张
if (startcardtype == 101){
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, followcards[0]);
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, _sortfollow[0]);
var _flower = parseInt(_code / 100);
if (_code > 1000){ //大于1000是主牌
_flower = mainflower;
@@ -1173,13 +1181,13 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
//一对
if (startcardtype == 201){
var pairlist = cls_youle_erqiwang_arith.get_pairlist(mainflower, followcards);
var pairlist = cls_youle_erqiwang_arith.get_pairlist(mainflower, _sortfollow);
if (pairlist.length != 1){
can.cardvalue = 0;
return can;
}
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, followcards[0]);
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, _sortfollow[0]);
var _flower = parseInt(_code / 100);
if (_code > 1000){
_flower = mainflower;
@@ -1207,14 +1215,14 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
//拖拉机
if (startcardtype > 300 && startcardtype < 400){
var pairlist = cls_youle_erqiwang_arith.get_pairlist(mainflower, followcards);
var pairlist = cls_youle_erqiwang_arith.get_pairlist(mainflower, _sortfollow);
var tuolaji_list = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, pairlist, startcardtype);
if (tuolaji_list.length != 1){
can.cardvalue = 0;
return can;
}
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, followcards[0]);
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, _sortfollow[0]);
var _flower = parseInt(_code / 100);
if (_code > 1000){
_flower = mainflower;