Files
erqiwang_youle/server/games/erqiwang/class.desk.js
T
joywayerandClaude Opus 5 76e82405e1 二七王:S-6 大局结算按 基础分/冲关分/傍王分 三项分解
问题:大局结算面板要分项显示,而 account 只有「累积得分 + 每局得分数组」。
傍王分尤其拆不出来——N = 冲关奖数 + 傍王王数,两个来源【求和之后】才代入
grade_aw = X×(2Ni−Nj−Nk),事后无法反推各自占比。

解法:在算钱时就拆。把 N 分成 N_冲关(只计庄家)与 N_傍王(勾选后庄闲都算),
分别代入同一公式各算一次。公式对 N 线性,故恒有
  grade_cg + grade_bw == grade_aw
这条恒等式正好当回归断言用。

服务端:
- class.paiju.js 抽出 award_pay(n) 复用同一公式,新增 grade_cg / grade_bw;
  desk.seatlist 追加三个累计位(基础/冲关/傍王)。
- class.desk.js seatlist 初始化为 5 元素;account 下发改为【对象数组】
  { score, grades, grade_jf_total, grade_cg_total, grade_bw_total }。
  原二元数组下标语义不清,且前端未开工,此时改代价最小(同 S-4 的判断)。

协议:aset.seatlist 增补 grade_cg / grade_bw;account 结构与恒等式
  grade_jf_total + grade_cg_total + grade_bw_total == score 写明;
  并记录这次结构变更的时间与理由。

测试(endgame 26 → 39 checks):
- 核心不变量:两分量之和 == grade_aw;两分量各自零和。
- 未勾傍王局:grade_bw 恒 0、grade_cg == grade_aw。
- 【新增一整局开傍王的端到端】:不开傍王时 grade_bw 恒 0,只验到平凡情形,
  必须有傍王局才算真验证了拆分。该局逐项复核两个分量的公式、naward 口径,
  并钉住「王数不全相等时 grade_bw 必有非零」防止又退化成平凡。
- 大局累计恒等式:基础+冲关+傍王 == 累积总分。

反向验证:把 _awbw 置零 → 4 条断言失败;把冲关分量误算到闲家头上 →
2 条断言失败。均已还原、全量 17 文件全绿。

另修四处测试桩:_rpc.js / test_paiju / test_rpc / test_success 里手工构造的
o_desk.seatlist 仍是 2 元素,未跟上 class.desk.js 的结构变更(脚本缺陷,
非业务缺陷)。

顺带解决小局结算「冲关分」标签的不严谨:改取 grade_cg 而非 grade_aw,
开傍王时不再把傍王贡献算进来。

遗留 T-31:参考图「基础分」与「总得分」两行数值相同且与右侧大字对不上
(30/30 vs +135),mock 不自洽。当前按 基础分=grade_jf_total、总分=score
提供。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 00:34:46 +08:00

211 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
///////////////////////////////////////////////////
////////// 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;
}
//新开一局
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;
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;
}