Files
erqiwang_youle/server/games/erqiwang/test/test_paiju.js
T
joywayerandClaude Opus 4.8 0049754988 二七王测试:完成 P0 用例(正常跟牌/结算集成/叫分坐庄/扣底触发)
- test_follow.js(新):§5.1/5.2 正常跟牌/毙牌/垫牌 17 例——单张/对子/拖拉机的
  必出、缺门毙牌数量对应(副单→主单/副对→主对/副N连→主N连)、垫牌、有对/拖却出散张被拒
- test_callgrade.js(新):§4.2 叫分坐庄 do_callgrade 6 例(叫5立即/两家不叫/后叫更低)
- test_paiju.js:§7/§8 结算集成扩充(大光/过庄/升级/爬坡/傍王+算奖参与/对照)、
  §6.3 扣底触发 get_bottom_account(主对×2/两连对×4/副牌不扣/庄赢不扣)
- 02-测试计划.md/README:P0 全部标记完成

全部单测 123 项断言全绿(node server/games/erqiwang/test/run.js)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 01:03:01 +08:00

87 lines
5.8 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.
// paiju 单测:亮牌(§8.2) + 结算 get_paiju_account(§7/§8) 集成
require('./_shim');
const P = require('../class.paiju.js');
const t = require('./_assert')();
const id = (d, f, n) => (d - 1) * 54 + (f - 1) * 13 + (n - 1);
const big = d => (d - 1) * 54 + 53;
const small = d => (d - 1) * 54 + 52;
// ============ §8.2 亮牌 get_liangpai ============
// 构造庄家(seat0)手牌 o_paiju;副花色中性单张填充到28
function fill(req, n) {
const u = new Set(req), o = req.slice();
for (const f of [2, 3, 4]) for (const x of [1, 5, 6, 8, 9, 10, 11, 12, 13]) {
if (o.length >= n) break;
const c = id(1, f, x);
if (!u.has(c)) { u.add(c); o.push(c); }
}
return o;
}
const mkHand = ids => ({ flower: 1, banker: 0, cards: ids.map(x => ({ id: x, playround: -1, dealowner: 1 })) });
const lp = ids => P.get_liangpai(mkHand(fill(ids, 28)));
t.eq('亮牌 3王', lp([big(1), small(1), small(2)]), { wang: 3 });
t.eq('亮牌 4王+6个7(固定主10)', lp([big(1), big(2), small(1), small(2), id(1, 1, 7), id(2, 1, 7), id(1, 2, 7), id(2, 2, 7), id(1, 3, 7), id(2, 3, 7)]), { zhu: 10, zhupair: 5, zhutuo: 1, wang: 4, qi: 6 });
t.eq('亮牌 6个2', lp([id(1, 1, 2), id(2, 1, 2), id(1, 2, 2), id(2, 2, 2), id(1, 3, 2), id(2, 3, 2)]), { er: 6 });
t.eq('亮牌 2王2个7(不达标)', lp([big(1), small(1), id(1, 1, 7), id(2, 1, 7)]), null);
// ============ §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 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) {
const cs = bankerIds.map(x => ({ id: x, dealowner: 1, playround: -1, playowner: -1, score: 0 }));
let g = xian1grade, i = 0;
while (g > 0) { const s = g >= 10 ? 10 : g; cs.push({ id: 200 + i, dealowner: 2, playround: 1, playowner: 1, score: s }); g -= s; i++; }
return cs;
}
const settle = (call, flower, cards, roomtype, type = 0) => P.get_paiju_account(paiju(call, flower, cards, roomtype), type, {});
const grades = msg => msg.data.aset.seatlist.map(p => p.grade);
const B0 = [id(1, 1, 1)]; // 庄家仅1张手牌(不足28→无算奖),用于纯算子用例
// 常规算子:庄赢/闲赢各档(无算奖)
t.eq('结算 65小光(grade20) X=4', grades(settle(65, 1, mkCards(B0, 20), "00000")), [8, -4, -4]);
t.eq('结算 65 upgrade/multiple', [settle(65, 1, mkCards(B0, 20), "00000").data.aset.upgrade, settle(65, 1, mkCards(B0, 20), "00000").data.aset.multiple], [2, 2]);
t.eq('结算 65大光(grade0)', grades(settle(65, 1, mkCards(B0, 0), "00000")), [12, -6, -6]);
t.eq('结算 65过庄(grade50)', grades(settle(65, 1, mkCards(B0, 50), "00000")), [4, -2, -2]);
t.eq('结算 65升2级(grade110)', grades(settle(65, 1, mkCards(B0, 110), "00000")), [-8, 4, 4]);
// 爬坡局(位3=1):40分大光 base8 → X=24
t.eq('结算 爬坡40大光', grades(settle(40, 1, mkCards(B0, 0), "00010")), [48, -24, -24]);
// 投降(type1):基础1、X=1 → 庄-2、闲各+1
t.eq('结算 投降 grade', grades(settle(70, -1, mkCards(B0, 0), "00000", 1)), [-2, 1, 1]);
t.eq('结算 投降 upgrade标识', settle(70, -1, mkCards(B0, 0), "00000", 1).data.aset.upgrade, -99);
// 傍王(位2=1) + 庄家3王:N_庄=1(常规算奖)+3(傍王)=4;65小光 X=4
// jf 庄+8/闲-4,aw 庄+32/闲-16 → 庄40、闲各-20(零和)
const B3wang = fill([big(1), small(1), small(2)], 28);
t.eq('结算 傍王65小光+庄3王', grades(settle(65, 1, mkCards(B3wang, 20), "00100")), [40, -20, -20]);
// 对照:同局不勾傍王 → N_庄=1(仅常规算奖):aw 庄+8/闲-4,jf 庄+8/闲-4 → 庄16、闲各-8
t.eq('结算 不傍王65小光+庄3王(仅常规算奖)', grades(settle(65, 1, mkCards(B3wang, 20), "00000")), [16, -8, -8]);
// ============ §6.3 扣底触发 get_bottom_account ============
// 仅当闲家(maxseat!=banker)用主牌赢末轮才扣底/翻倍
function mkBottomPaiju(maxseat, winCards, bottomScoreCards, flower) {
const cards = [];
for (let i = 0; i < 108; i++) cards[i] = { id: i, playround: -1, score: 0, dealowner: 1, playowner: -1 };
for (const [cid, sc] of bottomScoreCards) { cards[cid].playround = 0; cards[cid].score = sc; }
const pc = [[], [], []]; pc[maxseat] = winCards;
const o = { banker: 0, flower, cards, playproc: { maxseat, cards: pc } };
o.method = { get_burycard: () => P.get_burycard(o), get_grade_incard: c => P.get_grade_incard(o, c) };
return o;
}
// 闲1用主对(方块Q对)赢末轮,底2张K=20分 → 主对×2、grade2=40
const bo = P.get_bottom_account(mkBottomPaiju(1, [id(1, 1, 12), id(2, 1, 12)], [[id(1, 1, 13), 10], [id(2, 1, 13), 10]], 1), { data: {} });
t.eq('扣底 闲家主对赢 multiple', bo.data.bottom.multiple, 2);
t.eq('扣底 grade1/grade2', [bo.data.bottom.grade1, bo.data.bottom.grade2], [20, 40]);
// 闲1用副牌(副K)赢末轮 → 非主牌不扣底
const bo2 = P.get_bottom_account(mkBottomPaiju(1, [id(1, 2, 13)], [[id(1, 1, 13), 10]], 1), { data: {} });
t.eq('扣底 副牌赢不扣底', bo2.data.bottom.multiple, undefined);
// 庄家(maxseat==banker)赢末轮 → 不扣底
const bo3 = P.get_bottom_account(mkBottomPaiju(0, [id(1, 1, 12), id(2, 1, 12)], [[id(1, 1, 13), 10]], 1), { data: {} });
t.eq('扣底 庄家赢不扣底', bo3.data.bottom.multiple, undefined);
// 闲1用两连对拖拉机赢 → ×4
const bo4 = P.get_bottom_account(mkBottomPaiju(1, [id(1, 1, 13), id(2, 1, 13), id(1, 1, 12), id(2, 1, 12)], [[id(1, 4, 13), 10]], 1), { data: {} });
t.eq('扣底 两连对拖赢 ×4', bo4.data.bottom.multiple, 4);
process.exit(t.done('paiju') ? 0 : 1);