- 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>
87 lines
5.8 KiB
JavaScript
87 lines
5.8 KiB
JavaScript
// 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);
|