二七王: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>
This commit is contained in:
@@ -22,7 +22,7 @@ function setup(roomtype, o_paiju, seatlist) {
|
||||
const o_desk = {
|
||||
paiju_list: [o_paiju],
|
||||
o_room: null,
|
||||
seatlist: [[0, []], [0, []], [0, []]],
|
||||
seatlist: [[0, [], 0, 0, 0], [0, [], 0, 0, 0], [0, [], 0, 0, 0]],
|
||||
prepare: [0, 0, 0],
|
||||
method: {
|
||||
curr_paiju: () => o_paiju,
|
||||
|
||||
@@ -72,9 +72,58 @@ t.eq('§7 捡分子数分配 庄±2X / 闲各∓X', aset.seatlist.map(p => p.gra
|
||||
const N = aset.seatlist.map(p => p.naward);
|
||||
t.eq('§8.4 算奖分配 X×(2Ni−Nj−Nk)', aset.seatlist.map(p => p.grade_aw),
|
||||
[X * (2 * N[0] - N[1] - N[2]), X * (2 * N[1] - N[0] - N[2]), X * (2 * N[2] - N[0] - N[1])]);
|
||||
// S-6:算奖得分按【冲关 / 傍王】两个分量分别再算一次,供大局结算分项展示。
|
||||
// 公式对 N 线性,故两分量之和必须恒等于 grade_aw——这是拆分正确性的核心不变量。
|
||||
t.eq('S-6 grade_cg + grade_bw == grade_aw',
|
||||
aset.seatlist.map(p => p.grade_cg + p.grade_bw), aset.seatlist.map(p => p.grade_aw));
|
||||
// 分量口径:冲关只计庄家;傍王未勾选(roomtype "00000" 位2=0)时应恒为 0
|
||||
t.eq('S-6 未勾傍王 → grade_bw 全 0', aset.seatlist.map(p => p.grade_bw), [0, 0, 0]);
|
||||
t.eq('S-6 未勾傍王 → grade_cg == grade_aw', aset.seatlist.map(p => p.grade_cg), aset.seatlist.map(p => p.grade_aw));
|
||||
t.eq('本局结算零和', aset.seatlist.reduce((s, p) => s + p.grade, 0), 0);
|
||||
t.eq('S-6 两个分量各自零和', [
|
||||
aset.seatlist.reduce((s, p) => s + p.grade_cg, 0),
|
||||
aset.seatlist.reduce((s, p) => s + p.grade_bw, 0)], [0, 0]);
|
||||
t.eq('§4.6 result 与判定一致(0庄赢/1闲赢)', pj.result, aset.upgrade > 0 ? 0 : 1);
|
||||
|
||||
// ============ S-6:开傍王局的分量拆分 + 大局累计 ============
|
||||
// 上面那局没开傍王,grade_bw 恒 0,只验到平凡情形。这里开傍王(roomtype 位2=1)跑一整局,
|
||||
// 让两个分量都非零,才算真的验证了「N 拆成冲关/傍王分别代入公式」这件事。
|
||||
{
|
||||
const bw = seatBanker("00100", 65); // 位2=1 → 傍王开
|
||||
mod.xuanzhu(pk(0, { flower: 1 }));
|
||||
mod.maipai(pk(0, { cards: P.get_seat_cards(bw.pj, 0).slice(-8) }));
|
||||
t.eq('S-6 傍王局 打满28轮', playAll(bw.pj) && bw.pj.playproc.round, 28);
|
||||
const a = bw.ctx.sent[bw.ctx.sent.length - 1].data.aset;
|
||||
t.eq('S-6 傍王局 bangwang 标志', a.bangwang, 1);
|
||||
// 核心不变量:两分量之和 == grade_aw
|
||||
t.eq('S-6 傍王局 grade_cg + grade_bw == grade_aw',
|
||||
a.seatlist.map(p => p.grade_cg + p.grade_bw), a.seatlist.map(p => p.grade_aw));
|
||||
// 分量各自零和(每条支付线都是三家之间的转移,不产生也不消灭分数)
|
||||
t.eq('S-6 傍王局 两分量各自零和', [
|
||||
a.seatlist.reduce((s, p) => s + p.grade_cg, 0),
|
||||
a.seatlist.reduce((s, p) => s + p.grade_bw, 0)], [0, 0]);
|
||||
// 口径:naward = 冲关(仅庄家) + 王数(傍王开)
|
||||
const X = a.multiple * Math.abs(a.upgrade);
|
||||
t.eq('S-6 傍王局 naward 口径', a.seatlist.map((p, i) => p.naward),
|
||||
a.seatlist.map((p, i) => (i === a.banker ? p.chongguan : 0) + p.wang));
|
||||
// 傍王分量必须真的非零,否则这一整块又是平凡情形(三家王数全相等时净额才会是 0)
|
||||
const wangs = a.seatlist.map(p => p.wang);
|
||||
const wangAllSame = wangs[0] === wangs[1] && wangs[1] === wangs[2];
|
||||
t.eq('S-6 傍王局 王数不全相等时 grade_bw 必有非零', wangAllSame || a.seatlist.some(p => p.grade_bw !== 0), true);
|
||||
// 逐项复核傍王分量的公式
|
||||
const NB = a.seatlist.map(p => p.wang);
|
||||
t.eq('S-6 傍王分量 == X×(2Ni−Nj−Nk)', a.seatlist.map(p => p.grade_bw),
|
||||
[X * (2 * NB[0] - NB[1] - NB[2]), X * (2 * NB[1] - NB[0] - NB[2]), X * (2 * NB[2] - NB[0] - NB[1])]);
|
||||
// 冲关分量:只有庄家有 N_cg,闲家为 0
|
||||
const NC = a.seatlist.map((p, i) => (i === a.banker ? p.chongguan : 0));
|
||||
t.eq('S-6 冲关分量 == X×(2Ni−Nj−Nk)(仅庄家有 N)', a.seatlist.map(p => p.grade_cg),
|
||||
[X * (2 * NC[0] - NC[1] - NC[2]), X * (2 * NC[1] - NC[0] - NC[2]), X * (2 * NC[2] - NC[0] - NC[1])]);
|
||||
// 大局累计:desk.seatlist 后三位分别累计 基础/冲关/傍王,三者之和 == 累积总分
|
||||
const dsk = bw.pj.o_desk.seatlist;
|
||||
t.eq('S-6 大局累计 基础+冲关+傍王 == 累积总分',
|
||||
dsk.map(s => s[2] + s[3] + s[4]), dsk.map(s => s[0]));
|
||||
}
|
||||
|
||||
// ============ §4.4 / §7.1 投降局 ============
|
||||
const tx = seatBanker("00000", 70);
|
||||
t.eq('§4.3 70分坐庄:暗牌亮3秒标志 + 闲家也收到暗牌',
|
||||
|
||||
@@ -90,7 +90,7 @@ t.eq('亮牌 已埋的牌不计入快照', sortIds(P.get_liangpai(buried).cards)
|
||||
|
||||
// ============ §7/§8 结算 get_paiju_account 集成 ============
|
||||
// 受控 o_paiju/o_desk/o_room:控制庄家算奖手牌(dealowner=1)与闲1捡分(playowner=1,score)
|
||||
const mkDesk = roomtype => ({ seatlist: [[0, []], [0, []], [0, []]], o_room: { roomtype, asetcount: 6 }, method: { get_desk_account: m => m } });
|
||||
const mkDesk = roomtype => ({ seatlist: [[0, [], 0, 0, 0], [0, [], 0, 0, 0], [0, [], 0, 0, 0]], o_room: { roomtype, asetcount: 6 }, method: { get_desk_account: m => m } });
|
||||
const paiju = (call, flower, cards, roomtype) => ({ call, banker: 0, flower, cards, idx: 2, o_desk: mkDesk(roomtype), endtime: null, result: null });
|
||||
// bankerIds→庄家算奖手牌(dealowner=1);xian1grade→闲1捡分(每张≤10分)
|
||||
function mkCards(bankerIds, xian1grade) {
|
||||
|
||||
@@ -285,7 +285,7 @@ t.eq('重连 常规房 ChooseMain.multiple=6', E.get_deskinfo(n45.c.o_room, 0).C
|
||||
t.eq('重连 爬坡房 ChooseMain.multiple=7', E.get_deskinfo(p45.c.o_room, 0).ChooseMain.multiple, 7);
|
||||
// 与结算包 aset.multiple 一致(同一 call 同一房间规则)
|
||||
const aset45 = P.get_paiju_account(
|
||||
{ call: 45, banker: 0, flower: 1, cards: [], idx: 1, endtime: null, result: null, o_desk: { seatlist: [[0, []], [0, []], [0, []]], o_room: { roomtype: "00010", asetcount: 6 }, method: { get_desk_account: m => m } } },
|
||||
{ call: 45, banker: 0, flower: 1, cards: [], idx: 1, endtime: null, result: null, o_desk: { seatlist: [[0, [], 0, 0, 0], [0, [], 0, 0, 0], [0, [], 0, 0, 0]], o_room: { roomtype: "00010", asetcount: 6 }, method: { get_desk_account: m => m } } },
|
||||
0, {}).data.aset.multiple;
|
||||
t.eq('爬坡房 下发面 multiple 与结算 aset.multiple 一致', szm(p45.c), aset45);
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ t.eq('chupai1 下发3包', cp1.length, 3);
|
||||
t.eq('chupai1 带 success=true', cp1.every(m => m.data.success === true), true);
|
||||
|
||||
// -- 结算包 jiesuan:正常/投降/解散 三种 type 都带 success --
|
||||
const mkDesk = () => ({ seatlist: [[0, []], [0, []], [0, []]], o_room: { roomtype: '00000', asetcount: 6 }, method: { get_desk_account: m => m } });
|
||||
const mkDesk = () => ({ seatlist: [[0, [], 0, 0, 0], [0, [], 0, 0, 0], [0, [], 0, 0, 0]], o_room: { roomtype: '00000', asetcount: 6 }, method: { get_desk_account: m => m } });
|
||||
const mkPaiju = () => ({ call: 65, banker: 0, flower: 1, cards: [], idx: 2, o_desk: mkDesk(), endtime: null, result: null });
|
||||
t.eq('jiesuan 正常结算 success=true', P.get_paiju_account(mkPaiju(), 0, {}).data.success, true);
|
||||
t.eq('jiesuan 投降结算 success=true', P.get_paiju_account(mkPaiju(), 1, {}).data.success, true);
|
||||
|
||||
Reference in New Issue
Block a user