I-3 阶段与控制权改由服务端唯一维护并逐包下发: - fapai / jiaofen / shangzhuang / xuanzhu / maipai / jiesuan 各补 step(取自 o_paiju.step) - shangzhuang / xuanzhu 补 nextseat(控制权 = o_paiju.banker,选主/投降/埋牌均只由庄家做) - deskinfo 的 ChooseMain / BuryCards 各补 seat(控制权,与增量推送 nextseat 同源同值) - 各下发点的真实值先用探针跑真实一局逐个核实;顺带发现 shangzhuang.data.seat 是 最后一个叫分者而非控制权(本局实测 2 vs 庄家 0),故控制权另起名 nextseat - get_paiju_account 统一把 step 置 6:解散结算此前无人推进 step,包里给不出诚实的阶段值 I-2 结算阶段的重连包补发抠底明细与大局结算: - get_paiju_account 末尾一并冻结 tmp_jiesuan_bottom / tmp_jiesuan_account - get_deskinfo case 6 的 Balance 据此补 bottom / account,与 jiesuan 推送同源同值、 门控一致(投降/解散无 bottom、非末局无 account);新局开始时三份快照一并清除 - 修复「结算面板开着时断线重连/硬刷新 → 抠底明细与末局大结算空白」 M-2 seatlist 不再以活引用挂进下发包: - 新增 get_seatlist() 深拷贝快照,maipai / chupai / deskinfo.PushCards 三处改用它 测试:test_rpc 新增 I-3/I-2/M-2 三节(131→166 checks)、test_desk 新增 fapai 阶段与控制权用例(6→10);全部新增守卫已逐条做「退回修复必转红」反向验证(15/15)。 test_leak 的可见性规则同步收紧到「本局结算(step6)后埋牌底牌才全场公开」, 并修掉一处把阶段写死成 step5 的报错文案;整局泄露审计仍全绿。 协议文档同步补充上述字段说明。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
218 lines
7.6 KiB
JavaScript
218 lines
7.6 KiB
JavaScript
///////////////////////////////////////////////////
|
||
////////// cls_youle_erqiwang_desk: 牌桌 //////////
|
||
///////////////////////////////////////////////////
|
||
//跨模块依赖:Node 走 require 守卫;友乐/浏览器由 min_loadJsFile 加载为同名全局(dev-guide 01 §1)
|
||
if (typeof require !== "undefined"){
|
||
var cls_youle_erqiwang_paiju = require("./class.paiju.js");
|
||
}
|
||
|
||
var cls_youle_erqiwang_desk = cls_youle_erqiwang_desk || {
|
||
|
||
countdown_jiaofen: 15, //叫分倒计时
|
||
countdown_xuanzhu: 20, //选主倒计时
|
||
countdown_maipai: 25, //埋牌倒计时
|
||
countdown_chupai: 30, //出牌倒计时
|
||
|
||
//创建牌桌对象
|
||
new: function(o_room){
|
||
|
||
var desk = {};
|
||
|
||
desk.o_room = o_room; //桌所属的房间对象
|
||
//玩家得分。每个元素:[累积总分, [每局总分...], 基础分累计, 冲关分累计, 傍王分累计]
|
||
//后三位是大局结算面板要分项展示的累计(design §7 捡分子数 / §8.1 冲关 / §8.3 傍王),
|
||
//三者之和恒等于累积总分——下发时组装成对象(见 get_desk_account),数组下标只在内部用
|
||
desk.seatlist = [];
|
||
desk.seatlist.push([0, [], 0, 0, 0]);
|
||
desk.seatlist.push([0, [], 0, 0, 0]);
|
||
desk.seatlist.push([0, [], 0, 0, 0]);
|
||
desk.prepare = [0,0,0]; //玩家准备状态
|
||
desk.paiju_list = []; //牌局列表
|
||
|
||
//方法
|
||
desk.method = {};
|
||
|
||
//当前牌局
|
||
desk.method.curr_paiju = function(){
|
||
return desk.paiju_list[desk.paiju_list.length - 1];
|
||
}
|
||
|
||
//新牌局
|
||
desk.method.do_new_paiju = function(firstseat){
|
||
cls_youle_erqiwang_desk.do_new_paiju(desk, firstseat);
|
||
}
|
||
|
||
//叫分倒计时
|
||
desk.method.get_countdown_jiaofen = function(){
|
||
return cls_youle_erqiwang_desk.countdown_jiaofen;
|
||
}
|
||
|
||
//叫主倒计时
|
||
desk.method.get_countdown_xuanzhu = function(){
|
||
return cls_youle_erqiwang_desk.countdown_xuanzhu;
|
||
}
|
||
|
||
//埋牌倒计时
|
||
desk.method.get_countdown_maipai = function(){
|
||
return cls_youle_erqiwang_desk.countdown_maipai;
|
||
}
|
||
|
||
//出牌倒计时
|
||
desk.method.get_countdown_chupai = function(){
|
||
return cls_youle_erqiwang_desk.countdown_chupai;
|
||
}
|
||
|
||
//准备
|
||
desk.method.do_prepare = function(seat){
|
||
return cls_youle_erqiwang_desk.do_prepare(desk, seat);
|
||
}
|
||
|
||
//获取大局结算包
|
||
desk.method.get_desk_account = function(msg){
|
||
return cls_youle_erqiwang_desk.get_desk_account(desk, msg);
|
||
}
|
||
|
||
return desk;
|
||
},
|
||
|
||
//新开一局
|
||
do_new_paiju: function(o_desk, firstseat){
|
||
//恢复玩家准备状态
|
||
o_desk.prepare = [0,0,0];
|
||
|
||
if (o_desk.paiju_list.length > 0){
|
||
//上一局的结算快照三件套一并清除(新局开始后重连不该再看到上一局的结算面板)
|
||
delete o_desk.paiju_list[o_desk.paiju_list.length - 1].tmp_jiesuan_aset;
|
||
delete o_desk.paiju_list[o_desk.paiju_list.length - 1].tmp_jiesuan_bottom;
|
||
delete o_desk.paiju_list[o_desk.paiju_list.length - 1].tmp_jiesuan_account;
|
||
}
|
||
|
||
//新开一局
|
||
cls_youle_erqiwang_paiju.new(o_desk, firstseat);
|
||
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "fapai";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
msg.data.asetidx = o_desk.paiju_list.length;
|
||
msg.data.asetcount = o_desk.o_room.asetcount;
|
||
//阶段:发完牌即 step1(叫分)。由服务端权威给出、不让前端按 rpc 推——
|
||
//没有包承载的状态变化等于逼前端去猜(server 03 §1.4 / 红线「状态机唯一在服务端」)
|
||
msg.data.step = o_desk.method.curr_paiju().step;
|
||
//控制权:当前等待叫分者。与重连包 CallRun.seat 同一个 get_callgrade_seat()(SSOT)
|
||
msg.data.seat = o_desk.method.curr_paiju().method.get_callgrade_seat();
|
||
msg.data.countdown = o_desk.method.get_countdown_jiaofen();
|
||
for (var i = 0; i < o_desk.o_room.seatlist.length; i++) {
|
||
msg.conmode = o_desk.o_room.seatlist[i].conmode;
|
||
msg.fromid = o_desk.o_room.seatlist[i].fromid;
|
||
msg.data.cards = o_desk.method.curr_paiju().method.get_seat_cards(i);
|
||
youle_erqiwang.app.SendPack(msg);
|
||
}
|
||
},
|
||
|
||
//准备
|
||
do_prepare: function(o_desk, seat){
|
||
o_desk.prepare[seat] = 1;
|
||
if (o_desk.prepare[0] && o_desk.prepare[1] && o_desk.prepare[2]){
|
||
//新开一局
|
||
var paiju = o_desk.method.curr_paiju();
|
||
var firstseat = paiju.banker;
|
||
if (paiju.result == 1 || paiju.result == 2){
|
||
firstseat = (firstseat + 1) % 3;
|
||
}
|
||
cls_youle_erqiwang_desk.do_new_paiju(o_desk, firstseat);
|
||
} else {
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "zhunbei";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
msg.data.seat = seat;
|
||
o_desk.o_room.method.sendpack_toother(msg, -1);
|
||
}
|
||
},
|
||
|
||
//大局结算包
|
||
get_desk_account: function(o_desk, msg){
|
||
//保存战绩
|
||
var do_save_grade = function(){
|
||
var o_gameinfo1 = {};
|
||
o_gameinfo1.roomcode = o_desk.o_room.roomcode;
|
||
o_gameinfo1.asetcount = o_desk.paiju_list.length;
|
||
o_gameinfo1.createtime = o_desk.o_room.createtime;
|
||
o_gameinfo1.makewartime = o_desk.o_room.makewartime;
|
||
o_gameinfo1.players = [];
|
||
for (var i = 0; i < o_desk.o_room.seatlist.length; i++){
|
||
var _player = {};
|
||
_player.seat = i;
|
||
_player.playerid = o_desk.o_room.seatlist[i].playerid;
|
||
_player.name = o_desk.o_room.seatlist[i].nickname;
|
||
_player.avatar = o_desk.o_room.seatlist[i].avatar;
|
||
_player.score = o_desk.seatlist[i][0];
|
||
o_gameinfo1.players.push(_player);
|
||
}
|
||
|
||
var o_gameinfo2 = [];
|
||
for (var i = 0; i < o_desk.paiju_list.length; i++) {
|
||
var _paiju = {};
|
||
_paiju.starttime = o_desk.paiju_list[i].starttime;
|
||
_paiju.endtime = o_desk.paiju_list[i].endtime;
|
||
_paiju.seatlist = [0, 0, 0];
|
||
_paiju.seatlist[0] = o_desk.seatlist[0][1][i];
|
||
_paiju.seatlist[1] = o_desk.seatlist[1][1][i];
|
||
_paiju.seatlist[2] = o_desk.seatlist[2][1][i];
|
||
_paiju.callproc = o_desk.paiju_list[i].callproc;
|
||
_paiju.banker = o_desk.paiju_list[i].banker;
|
||
_paiju.call = o_desk.paiju_list[i].call;
|
||
_paiju.flower = o_desk.paiju_list[i].flower;
|
||
_paiju.result = o_desk.paiju_list[i].result;
|
||
//数据量太多,json转数组后进行存储
|
||
_paiju.cards = [];
|
||
for (var j = 0; j < o_desk.paiju_list[i].cards.length; j++) {
|
||
var _pai = o_desk.paiju_list[i].cards[j];
|
||
var _card = [];
|
||
_card.push(_pai.id);
|
||
_card.push(_pai.flower);
|
||
_card.push(_pai.number);
|
||
_card.push(_pai.score);
|
||
_card.push(_pai.dealowner);
|
||
_card.push(_pai.playround);
|
||
_card.push(_pai.playindex);
|
||
_card.push(_pai.playowner);
|
||
_paiju.cards.push(_card);
|
||
}
|
||
o_gameinfo2.push(_paiju);
|
||
}
|
||
|
||
//累积游戏得分
|
||
for (var i = 0; i < o_desk.o_room.seatlist.length; i++) {
|
||
o_desk.o_room.seatlist[i].gameinfo.grade = o_desk.seatlist[i][0];
|
||
}
|
||
//保存战绩
|
||
youle_erqiwang.import.save_grade(o_desk.o_room, o_gameinfo1, o_gameinfo2, 1);
|
||
}
|
||
min_ontimeout(do_save_grade, 1000);
|
||
|
||
//大局结算下发:组装成对象数组,字段自解释(内部 o_desk.seatlist 是数组,下标语义不清,不直接外抛)
|
||
msg.data.account = [];
|
||
for (var s = 0; s < o_desk.seatlist.length; s++){
|
||
var _a = {};
|
||
_a.score = o_desk.seatlist[s][0]; //累积总分
|
||
_a.grades = o_desk.seatlist[s][1]; //每局总分列表
|
||
_a.grade_jf_total = o_desk.seatlist[s][2]; //基础分(捡分子数得分)累计
|
||
_a.grade_cg_total = o_desk.seatlist[s][3]; //冲关分累计(design §8.1,不含傍王)
|
||
_a.grade_bw_total = o_desk.seatlist[s][4]; //傍王分累计(design §8.3)
|
||
msg.data.account.push(_a);
|
||
}
|
||
return msg;
|
||
}
|
||
}
|
||
|
||
//Node 导出;友乐/浏览器无 module 时跳过(dev-guide 01 §1)。SendPack/youle_erqiwang.* 为平台全局
|
||
if (typeof module !== "undefined"){
|
||
module.exports = cls_youle_erqiwang_desk;
|
||
}
|