问题:大局结算面板要分项显示,而 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>
57 lines
2.7 KiB
JavaScript
57 lines
2.7 KiB
JavaScript
// L3 RPC 测试脚手架:装配 mod.js(含 RPC 方法),捕获下发包
|
||
require('./_shim');
|
||
// mod.js 的 RPC handler 按全局名引用这些类,先挂到 global
|
||
global.cls_youle_erqiwang_config = require('../class.config.js');
|
||
global.cls_youle_erqiwang_pai = require('../class.pai.js');
|
||
global.cls_youle_erqiwang_arith = require('../class.arith.js');
|
||
global.cls_youle_erqiwang_paiju = require('../class.paiju.js');
|
||
global.cls_youle_erqiwang_desk = require('../class.desk.js');
|
||
// mod.js 顶部依赖:cls_mod.new 造模块对象、min_loadJsFile 置为同步 no-op(类已单独 require)
|
||
global.cls_mod = { new: (name, route, app) => ({ modname: name, routename: route, app }) };
|
||
global.youle_app = {};
|
||
global.min_loadJsFile = (p, cb) => { if (cb) cb(); };
|
||
|
||
const mod = require('../mod.js'); // = youle_erqiwang,含 jiaofen/xuanzhu/.../mingpai/zhunbei
|
||
|
||
const clone = m => JSON.parse(JSON.stringify(m));
|
||
|
||
// 装配一局:给定 roomtype 与 o_paiju,返回 { sent, o_room },并把 check_player/SendPack/sendpack_toother
|
||
// 全部指向同一个 sent 数组(快照每个下发包,因 mod 会跨座位改删字段)
|
||
function setup(roomtype, o_paiju, seatlist) {
|
||
const sent = [];
|
||
const o_desk = {
|
||
paiju_list: [o_paiju],
|
||
o_room: null,
|
||
seatlist: [[0, [], 0, 0, 0], [0, [], 0, 0, 0], [0, [], 0, 0, 0]],
|
||
prepare: [0, 0, 0],
|
||
method: {
|
||
curr_paiju: () => o_paiju,
|
||
get_countdown_jiaofen: () => 15, get_countdown_xuanzhu: () => 20,
|
||
get_countdown_maipai: () => 25, get_countdown_chupai: () => 30,
|
||
do_prepare: () => { }, get_desk_account: m => m
|
||
}
|
||
};
|
||
const o_room = {
|
||
roomtype, asetcount: 6,
|
||
seatlist: seatlist || [{ conmode: 0, fromid: 0 }, { conmode: 0, fromid: 1 }, { conmode: 0, fromid: 2 }],
|
||
o_desk,
|
||
method: { sendpack_toother: m => sent.push(clone(m)) }
|
||
};
|
||
o_desk.o_room = o_room;
|
||
if (o_paiju) o_paiju.o_desk = o_desk;
|
||
mod.import = { check_player: () => o_room, deduct_roomcard: () => { }, save_grade: () => { } };
|
||
mod.app = { SendPack: m => sent.push(clone(m)) };
|
||
return { sent, o_room, o_desk };
|
||
}
|
||
|
||
// 构造 108 张牌(带 flower/number/dealowner/playround),供 get_seat_zhucards / get_seat_cards 用
|
||
function make108(deal) {
|
||
const A = global.cls_youle_erqiwang_arith;
|
||
const cards = [];
|
||
for (let i = 0; i < 108; i++) cards[i] = { id: i, dealowner: 1, playround: -1, playowner: -1, score: 0, flower: A.id_to_flower(i), number: A.id_to_number(i) };
|
||
if (deal) for (const [cid, owner] of deal) cards[cid].dealowner = owner;
|
||
return cards;
|
||
}
|
||
|
||
module.exports = { mod, setup, make108, id: (d, f, n) => (d - 1) * 54 + (f - 1) * 13 + (n - 1) };
|