二七王:出牌顺序收敛为唯一口径,重连包补发 baozhu
同一份数据「这一手打出的牌」此前有两个顺序口径:chupai*/playproc.cards 回显客户端请求原序,重连包 pushlist 则从 playround 反查后自己再排一次序。 一手多张(甩牌/对子/拖拉机)时两者必然分岔;同编码的一对牌更是排序都 区分不了,靠"两处各自排序"永远收敛不了。 改为单一写入处: - 新增 order_playcards(),do_playcard 入口归一化一次(顺带隔离 can_playcard 的原地排序,不再就地改写 pack.data.cards) - 新增 paiju.playhistory,出牌当场归档;新增 get_pushlist() 只做深拷贝 - class.export.js 删掉整段反查+重排,改为直读归档 - do_playcard 三个分支都给 re.cards,mod.js 去掉回落到入参的兜底 顺带修复重连包漏发 baozhu:它是余主公示与明牌按钮的开关,此前只在 chupai 包里给,报无主后重连按钮会凭空消失。现与 chupai 同源同门控下发。 协议文档新增 §0.6「一手出牌」的顺序口径,并补 PushCards.baozhu 字段。 测试:pushlist 一节改为真实链路驱动(一手多张 + 乱序提交),断言升级为 「重连每一手与当时的 chupai.cards 逐元素相等」;三处修复均已退回验证转红。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -171,6 +171,13 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
paiju.bottomcards = [];
|
||||
//当前出牌情况
|
||||
paiju.playproc = {};
|
||||
//出牌历史归档:外层下标 = 轮次-1,内层恒 3 个数组、下标 = 座位;每一项就是那一手
|
||||
//【实际打出、且已归一成权威顺序】的牌。唯一写入处是 do_playcard 的 doplay()。
|
||||
//【为什么要存】重连包 PushCards.pushlist 需要往轮的出牌历史,而牌一旦落地就只剩
|
||||
//「第几轮 + 属于谁」(cards[i].playround/dealowner),一手之内的顺序无从恢复——
|
||||
//从那里反查就必须自己再排一次序,那是同一份数据的第二个写入处(SSOT 违反),
|
||||
//且遇到同编码的两张牌(一对)时两处必然分岔。改为出牌当场归档、下游只读。
|
||||
paiju.playhistory = [];
|
||||
//输赢结果 0:庄赢 1:闲赢 2:庄投降 3:中途解散
|
||||
paiju.result = null;
|
||||
//结算包,供断线重连时使用,新开一局时清除
|
||||
@@ -283,6 +290,11 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
return cls_youle_erqiwang_paiju.get_curmultiple(paiju);
|
||||
}
|
||||
|
||||
//获取出牌历史快照(重连包 PushCards.pushlist)
|
||||
paiju.method.get_pushlist = function(){
|
||||
return cls_youle_erqiwang_paiju.get_pushlist(paiju);
|
||||
}
|
||||
|
||||
//获取某座位本轮跟牌的必出牌(design §5.2)
|
||||
paiju.method.get_mustcard = function(seat){
|
||||
return cls_youle_erqiwang_paiju.get_mustcard(paiju, seat);
|
||||
@@ -480,6 +492,24 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
//随后续出牌被回改;平台真实链路虽然立刻序列化看不出来,但这是隐患,不留。
|
||||
//structure:round/start/currseat/startcount/startflower/starttype/maxseat/maxcard/
|
||||
// cards(定长3、下标=座位)/shuai_demand
|
||||
//获取【供下发的】出牌历史快照(重连包 PushCards.pushlist)。
|
||||
//唯一来源是 do_playcard 当场归档的 playhistory,这里只拷贝、不重算也不重排:
|
||||
//它与 chupai1/2/3 的 data.cards、playproc.cards 是同一次 order_playcards 的产物,
|
||||
//因此两条路径逐元素相等(前端增量回放与重连重建必定一致)。
|
||||
//【返回深拷贝】不把活数组挂进下发包(同 get_playproc)。
|
||||
get_pushlist: function(o_paiju){
|
||||
var re = [];
|
||||
for (var i = 0; i < o_paiju.playhistory.length; i++){
|
||||
var _round = o_paiju.playhistory[i];
|
||||
var _one = [];
|
||||
for (var s = 0; s < 3; s++){
|
||||
_one.push(_round[s].concat());
|
||||
}
|
||||
re.push(_one);
|
||||
}
|
||||
return re;
|
||||
},
|
||||
|
||||
get_playproc: function(o_paiju){
|
||||
var _p = o_paiju.playproc;
|
||||
if (!_p){
|
||||
@@ -602,6 +632,23 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
o_paiju.playproc.shuai_demand= null; //首家甩牌的分量需求(供跟牌逐分量匹配),非甩牌为null
|
||||
},
|
||||
|
||||
//【一手出牌的顺序:唯一口径】把一手牌规整成本局的权威展示顺序
|
||||
//(按主牌花色从大到小)。只在 do_playcard 落牌那一刻调一次,之后全体下游只读:
|
||||
// · playproc.cards[座位] → chupai1/2/3 与重连包的 playproc
|
||||
// · do_playcard 返回的 re.cards → chupai1/2/3 的 data.cards
|
||||
// · playhistory[轮次][座位] → 重连包的 PushCards.pushlist
|
||||
//三者同源同数组内容,所以前端“增量回放”与“重连重建”必定逐元素相等。
|
||||
//
|
||||
//为什么不能用客户端提交的原序:那是玩家的点击顺序,同一手牌每次都可能不同,
|
||||
//服务端不得依赖、也不回显(server 04 §8:前端不是数据源)。
|
||||
//【返回新数组】——order_cards 是原地排序,直接排入参会就地改写调用方(含 pack.data.cards)。
|
||||
order_playcards: function(o_paiju, cards){
|
||||
if (!cards){
|
||||
return cards;
|
||||
}
|
||||
return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, cards.concat());
|
||||
},
|
||||
|
||||
//在牌列表中获取分牌和分值
|
||||
get_grade_incard: function(o_paiju, cards){
|
||||
var re = {};
|
||||
@@ -618,6 +665,12 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
|
||||
//检查选中的牌是否可出,并出牌
|
||||
do_playcard: function(o_paiju, cards){
|
||||
//【顺序归一:唯一写入处】入参是客户端提交的点击顺序,服务端不采信、不下发。
|
||||
//在这里一次性转成权威顺序(order_playcards),此后 playproc.cards / 返回给下发包的
|
||||
//re.cards / 重连包 pushlist 全都是同一个口径,不会再有第二处排序(SSOT)。
|
||||
//顺带隔离了 can_playcard 的【原地排序】——否则它会就地改写 pack.data.cards。
|
||||
cards = cls_youle_erqiwang_paiju.order_playcards(o_paiju, cards);
|
||||
|
||||
//出牌
|
||||
var doplay = function(playindex){
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
@@ -625,6 +678,11 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
o_paiju.cards[cards[i]].playindex = playindex;
|
||||
};
|
||||
o_paiju.playproc.cards[o_paiju.playproc.currseat] = cards;
|
||||
//归档这一手,供重连包 PushCards.pushlist 直读(唯一写入处,见 paiju.playhistory)
|
||||
while (o_paiju.playhistory.length < o_paiju.playproc.round){
|
||||
o_paiju.playhistory.push([[], [], []]);
|
||||
}
|
||||
o_paiju.playhistory[o_paiju.playproc.round - 1][o_paiju.playproc.currseat] = cards.concat();
|
||||
|
||||
//进入出牌阶段
|
||||
o_paiju.step = 5;
|
||||
@@ -676,7 +734,7 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
if (can.shuaicuo){
|
||||
//甩错惩罚(§5.4.5):收回甩牌,本轮只强制打出最小一张
|
||||
_shuaicuo = true;
|
||||
cards = [can.smallest];
|
||||
cards = cls_youle_erqiwang_paiju.order_playcards(o_paiju, [can.smallest]);
|
||||
can = cls_youle_erqiwang_arith.can_playcard(o_paiju.flower, cards, o_paiju.playproc.currseat, o_paiju.seatlist, opp_zhulist);
|
||||
if (!can.result){
|
||||
var reErr = {};
|
||||
@@ -707,7 +765,10 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
re1.count = cards.length;
|
||||
re1.flower = can.flower;
|
||||
re1.cardtype = can.cardtype;
|
||||
re1.cards = cards; //实际打出的牌(甩错时为单张)
|
||||
//实际打出的牌(甩错时为单张),已是权威顺序。
|
||||
//【给副本】cards 本体已挂在 playproc.cards[座位] 上,直接交出去会让「已发出的包」
|
||||
//随后续改动被回改(同 get_playproc 的深拷贝理由)
|
||||
re1.cards = cards.concat();
|
||||
re1.shuaicuo = _shuaicuo;
|
||||
//合法甩牌的分量构成:cardtype 只能表达单一牌型,甩牌(单张/对子/拖拉机自由混搭,
|
||||
//design §5.4.3)会被压平成 1xx/2xx,客户端无从据此还原分组,故另行下发分量需求
|
||||
@@ -759,6 +820,7 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
var re2 = {};
|
||||
re2.idx = 2;
|
||||
re2.result = true;
|
||||
re2.cards = cards.concat(); //实际打出的牌,权威顺序(理由同 re1.cards)
|
||||
return re2;
|
||||
}
|
||||
|
||||
@@ -776,6 +838,7 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
var re3 = {};
|
||||
re3.result = true;
|
||||
re3.idx = 3;
|
||||
re3.cards = cards.concat(); //实际打出的牌,权威顺序(理由同 re1.cards)
|
||||
re3.maxseat = o_paiju.playproc.maxseat;
|
||||
if (o_paiju.playproc.maxseat != o_paiju.banker){
|
||||
re3.grade = 0;
|
||||
|
||||
Reference in New Issue
Block a user