Files
erqiwang_youle/server/games/erqiwang/test/test_paiju.js
T
joywayerandClaude Opus 5 b98f58effc 二七王:亮牌改为下发庄家全部固定主牌的具体牌面
规则变更(design §8.2):原先「只亮数量/结构,不亮具体是哪几张牌」,现改为
向两个闲家亮出【庄家手中全部固定主牌的具体牌面】。

- 固定主牌 = 双王 + 全部花色的 2 + 全部花色的 7,【不含】主花色普通牌
  A/K/Q/J/10/9/8/6/5。注意这与旧实现收集的「所有主牌」范围不同。
- 门槛不变(固定主牌总数 ≥10 / 王 ≥3 / 7 ≥6 / 2 ≥6,任一满足)。
- 不限叫分;仅可查牌模式下发;亮出的是固定的一份,不随被哪条门槛触发而增减。
- 协议 liangpai 由 {zhu,zhupair,zhutuo,wang,qi,er} 数量结构改为 {cards:[牌id...]},
  数量交由前端从 cards 自行计算。

服务端 get_liangpai() 重写(class.paiju.js);design §8.2 整节重写,术语表、
易混对照表、§9 相应更新;协议 §0.0b 与 maipai / PushCards 两处字段说明同步。

测试:
- test_paiju 亮牌用例按新规则重写(54 checks)。新增两条针对性用例:
  「只含固定主牌·主花色普通牌未混入」与「同时满足多档仍只亮同一份」——
  前者正是这次范围变更最容易写错的地方。
- test_leak 增加亮牌可见性规则。关键点:期望集合【独立于 get_liangpai 重算】,
  而不是复用被测实现——否则实现若误塞非固定主牌,审计会跟着放行、抓不住。
  反向验证证实了这一点:故意让 get_liangpai 混入主花色普通牌后,审计确实报警。

发现并修复一处假绿:原 6 局随机牌序下庄家一次都没达到亮牌门槛,即
「下发面无越权泄露」对亮牌这条路径根本没走到。现追加换牌序的 4 局(保留
原 6 局覆盖不动),并加两条覆盖断言钉住「可查牌达标 ≥1 局」「不查牌达标
≥1 局」,防止以后再退化成碰巧没触发。反向验证:去掉不查牌门控后审计立即
报出 5 张越权牌。

待定:T-34 亮牌的展示时机(出牌前一次性弹出 / 随时可查)。

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

263 lines
17 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)));
// 亮牌返回 { cards: [固定主牌id...] }(按主牌序排好);不达标返回 null。
// 断言用「排序后的 id 集合」比较,避免把排序细节写死进用例。
const sortIds = a => a.slice().sort((x, y) => x - y);
const lpCards = ids => { const r = lp(ids); return r === null ? null : sortIds(r.cards); };
// —— 四条门槛各自能触发,且亮出的都是【全部固定主牌】——
{
const h = [big(1), small(1), small(2)]; // 3 王
t.eq('亮牌 3王 → 亮出这3张王', lpCards(h), sortIds(h));
}
{
const h = [id(1, 1, 2), id(2, 1, 2), id(1, 2, 2), id(2, 2, 2), id(1, 3, 2), id(2, 3, 2)]; // 6 个 2
t.eq('亮牌 6个2 → 亮出这6张2', lpCards(h), sortIds(h));
}
{
const h = [id(1, 1, 7), id(2, 1, 7), id(1, 2, 7), id(2, 2, 7), id(1, 3, 7), id(2, 3, 7)]; // 6 个 7
t.eq('亮牌 6个7 → 亮出这6张7', lpCards(h), sortIds(h));
}
{
const h = [big(1), small(1), id(1, 1, 7), id(2, 1, 7), id(1, 2, 7), id(2, 2, 7),
id(1, 1, 2), id(2, 1, 2), id(1, 2, 2), id(2, 2, 2)]; // 固定主 10(各档单看都不达标)
t.eq('亮牌 固定主10边界 → 亮出全部10张', lpCards(h), sortIds(h));
}
// —— 不达标 → null ——
t.eq('亮牌 2王2个7 不达标→null', lp([big(1), small(1), id(1, 1, 7), id(2, 1, 7)]), null);
t.eq('亮牌 固定主9 不达标→null',
lp([big(1), small(1), id(1, 1, 7), id(2, 1, 7), id(1, 2, 7), id(2, 2, 7), id(1, 1, 2), id(2, 1, 2), id(1, 2, 2)]), null);
// —— 关键:亮出的只有【固定主牌】,主花色普通牌【不能】混进来 ——
// 手牌里塞满主花色(♦)普通牌 A/K/Q/J/10/9/8/6/5,它们是主牌但不是固定主牌
{
const guding = [big(1), small(1), small(2)]; // 3 王 → 达标
const zhuNormal = [id(1, 1, 1), id(1, 1, 13), id(1, 1, 12), id(1, 1, 11), id(1, 1, 10)]; // ♦A/K/Q/J/10
const r = lp(guding.concat(zhuNormal));
t.eq('亮牌 只含固定主牌 张数=3', r.cards.length, 3);
t.eq('亮牌 主花色普通牌未混入', sortIds(r.cards), sortIds(guding));
}
// —— 亮出内容与「被哪条门槛触发」无关:4王+6个7 应亮出全部 10 张固定主 ——
{
const h = [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)];
t.eq('亮牌 同时满足多档 → 仍只亮同一份固定主牌', lpCards(h), sortIds(h));
}
// —— 排序:按本局主牌序从大到小(大王 > 小王 > 正7 > 副7 > 正2 > 副2)——
{
const A = require('../class.arith.js');
const h = [big(1), small(1), id(1, 1, 7), id(1, 2, 7), id(1, 1, 2), id(1, 2, 2),
id(2, 1, 7), id(2, 2, 7), id(2, 1, 2), id(2, 2, 2)]; // 固定主 10,主花色=♦(1)
const r = lp(h);
t.eq('亮牌 按主牌序排好', r.cards, A.order_cards(1, r.cards.slice()));
t.eq('亮牌 首张为大王', r.cards[0], big(1));
}
// design §8.2:亮牌依据是【庄家埋牌后】的静态手牌快照,全程固定、不随出牌缩水
// (闲家断线重连时 get_deskinfo 会再取一次;若按当前剩余手牌重算,亮牌会缩水甚至变 null)
const LIANG28 = fill([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)], 28);
const EXPECT_LIANG = sortIds([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)]);
t.eq('亮牌 埋牌后快照(出牌开始)', sortIds(P.get_liangpai(mkHand(LIANG28)).cards), EXPECT_LIANG);
const played = mkHand(LIANG28); // 同一手牌,已打出2大王+1对♦7
for (const c of played.cards){
if (c.id === big(1) || c.id === big(2) || c.id === id(1, 1, 7) || c.id === id(2, 1, 7)){ c.playround = 1; }
}
t.eq('亮牌 打出4张后重连仍按快照', sortIds(P.get_liangpai(played).cards), EXPECT_LIANG);
// 埋下的牌(playround==0)不算在快照里
const buried = mkHand(LIANG28);
for (const c of buried.cards){ if (c.id === big(1) || c.id === big(2)){ c.playround = 0; } }
t.eq('亮牌 已埋的牌不计入快照', sortIds(P.get_liangpai(buried).cards),
sortIds([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)]));
// ============ §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);
// ============ §5.4.5 甩错惩罚执行 do_playcard ============
const A = require('../class.arith.js');
// 造首出场景:庄家(0)手牌与两闲家主牌,step5 本轮首出
function mkFirstPlay(flower, dealMap) {
const cards = [];
for (let i = 0; i < 108; i++) cards[i] = { id: i, dealowner: 99, playround: -1, playowner: -1, score: 0, flower: A.id_to_flower(i), number: A.id_to_number(i) };
for (const [cid, owner] of dealMap) cards[cid].dealowner = owner;
const sl = () => [[0, 0], [0, 0], [0, 0], [0, 0], [-1, -1]];
const o = { step: 5, banker: 0, flower, cards, seatlist: [sl(), sl(), sl()], playproc: {} };
o.method = { have_baofu: () => o.seatlist.some(s => s[4][0] == 0) };
P.new_playround(o, 1, 0);
return o;
}
// 庄家甩 大王+主K对;seat1 持主A对(能压主K对) → 甩错:收回甩牌、只强制打出最小一张(主K)、其余留手
const fp = mkFirstPlay(1, [[big(1), 1], [id(1, 1, 13), 1], [id(2, 1, 13), 1], [id(1, 1, 1), 2], [id(2, 1, 1), 2]]);
const re = P.do_playcard(fp, [big(1), id(1, 1, 13), id(2, 1, 13)]);
t.eq('甩错执行 shuaicuo=true', re.shuaicuo, true);
t.eq('甩错执行 只打1张且为主K', re.cards.length === 1 && A.id_to_number(re.cards[0]) === 13, true);
t.eq('甩错执行 大王未出(留手)', fp.cards[big(1)].playround, -1);
t.eq('甩错执行 打出的主K已出', fp.cards[re.cards[0]].playround > 0, true);
// 对照:合法甩牌 do_playcard 正常打出整套、不甩错
const fp2 = mkFirstPlay(1, [[big(1), 1], [big(2), 1], [id(1, 1, 13), 1], [id(1, 1, 5), 2], [id(1, 1, 6), 3]]); // 大王对+主K单;对手仅小主牌
const re2 = P.do_playcard(fp2, [big(1), big(2), id(1, 1, 13)]);
t.eq('合法甩牌 do_playcard 非甩错', !!re2.shuaicuo, false);
t.eq('合法甩牌 整套3张打出', re2.cards.length, 3);
// §5.4.4 集成:首出合法甩牌 → 跟牌方违反分量拆解 → do_playcard 拒;正确对位 → 接受
const fpf = mkFirstPlay(1, [
[big(1), 1], [id(1, 1, 12), 1], [id(2, 1, 12), 1], // 庄家 大王+主Q对(合法甩牌)
[id(1, 1, 11), 2], [id(2, 1, 11), 2], [id(1, 1, 9), 2], [id(1, 1, 8), 2] // seat1 主J对+主9+主8
]);
const lead = P.do_playcard(fpf, [big(1), id(1, 1, 12), id(2, 1, 12)]);
t.eq('甩牌集成 首出合法(记录shuai_demand)', lead.result && !lead.shuaicuo, true);
const badF = P.do_playcard(fpf, [id(1, 1, 11), id(1, 1, 9), id(1, 1, 8)]); // 拆散主J对
t.eq('甩牌集成 跟牌拆散主对→拒', badF.result, false);
const goodF = P.do_playcard(fpf, [id(1, 1, 11), id(2, 1, 11), id(1, 1, 9)]); // 用主J对对位
t.eq('甩牌集成 跟牌用主对→接受', goodF.result, true);
// ============ §5.1 控制权 + §6.2 捡分归属 ============
// 造一轮三家各出 1 张同花色副牌的完整墩:主花色=♦(1),本轮花色=♥(3)。
// deal: [[牌id, 座位], ...];scores: [[牌id, 分值], ...]
function mkTrick(deal, scores) {
const cards = [];
for (let i = 0; i < 108; i++) cards[i] = { id: i, dealowner: 99, playround: -1, playowner: -1, score: 0, flower: A.id_to_flower(i), number: A.id_to_number(i) };
for (const [cid, seat] of deal) cards[cid].dealowner = seat + 1;
for (const [cid, s] of scores) cards[cid].score = s;
const sl = () => [[0, 0], [0, 0], [0, 0], [0, 0], [-1, -1]];
const o = { step: 5, banker: 0, flower: 1, cards, seatlist: [sl(), sl(), sl()], playproc: {} };
o.method = { have_baofu: () => o.seatlist.some(s => s[4][0] == 0) };
P.new_playround(o, 1, 0); // 第 1 轮由庄家(0)首出
return o;
}
const H5 = id(1, 3, 5), H8 = id(1, 3, 8), HK = id(1, 3, 13), HA = id(1, 3, 1); // ♥5 ♥8 ♥K ♥A(副牌)
const SC = [[H5, 5], [HK, 10]]; // §6.1:5→5分、K→10分;♥8/♥A 不计分
// —— 反:庄家赢下本轮 → 台面分牌全部作废,不计入闲家捡分 ——
const wb = mkTrick([[HA, 0], [HK, 1], [H5, 2]], SC);
P.do_playcard(wb, [HA]); P.do_playcard(wb, [HK]);
const wbRe = P.do_playcard(wb, [H5]);
// 注:第三家出完后 do_playcard 已开新一轮、playproc 被重置,胜者只能从返回值读
t.eq('§6.2 反 庄家赢本轮 maxseat=庄', wbRe.maxseat, 0);
t.eq('§6.2 反 庄家赢 → 下发面无本轮得分', wbRe.grade, undefined);
t.eq('§6.2 反 庄家赢 → 闲家捡分 0(分牌作废)', P.get_jian_grade(wb).grade, 0);
// —— 正:闲家1 赢下本轮 → 台面【全部】分牌(含庄家自己打出的♥5)计入闲家 ——
const w1 = mkTrick([[H5, 0], [HK, 1], [H8, 2]], SC);
P.do_playcard(w1, [H5]); P.do_playcard(w1, [HK]);
const w1Re = P.do_playcard(w1, [H8]);
t.eq('§6.2 正 闲1赢本轮 maxseat=1', w1Re.maxseat, 1);
t.eq('§6.2 正 本轮闲家得分=5+10(含庄家打出的♥5)', w1Re.grade, 15);
t.eq('§6.2 正 累计捡分=15', P.get_jian_grade(w1).grade, 15);
// §5.1:下一轮由本轮牌面最大的一方先出(不是固定庄家、也不是顺序下家)
t.eq('§5.1 正 下一轮由本轮最大者(闲1)先出', [w1.playproc.round, w1.playproc.start, w1.playproc.currseat], [2, 1, 1]);
// —— 边界:两个闲家谁赢都算「闲家捡到」,结果与闲1赢完全一致(design §6.2)——
const w2 = mkTrick([[H5, 0], [H8, 1], [HK, 2]], SC);
P.do_playcard(w2, [H5]); P.do_playcard(w2, [H8]);
const w2Re = P.do_playcard(w2, [HK]);
t.eq('§6.2 边界 闲2赢本轮 maxseat=2', w2Re.maxseat, 2);
t.eq('§6.2 边界 闲2赢与闲1赢同样计闲家捡分', [w2Re.grade, P.get_jian_grade(w2).grade], [15, 15]);
t.eq('§5.1 边界 下一轮改由闲2先出', [w2.playproc.round, w2.playproc.start], [2, 2]);
// ============ §8 算奖快照 get_seat_cards_award ============
// design §8:庄家按【埋牌完成后】的 28 张、闲家按【发牌后】的 28 张;快照全程固定,
// 不随之后的出牌改变。例外:70 分投降局庄家未埋牌,快照为「发牌 + 暗牌」共 36 张。
function mkAward() {
const cards = [];
for (let i = 0; i < 108; i++) cards[i] = { id: i, dealowner: -1, playround: -1, playowner: -1, score: 0 };
let n = 0;
const take = k => { const out = []; while (out.length < k) { out.push(n); n++; } return out; };
const bankerDeal = take(28), an = take(8), x1 = take(28), x2 = take(28);
bankerDeal.forEach(i => cards[i].dealowner = 1);
an.forEach(i => cards[i].dealowner = 0); // 暗牌
x1.forEach(i => cards[i].dealowner = 2);
x2.forEach(i => cards[i].dealowner = 3);
return { o: { banker: 0, flower: 1, cards }, bankerDeal, an, x1 };
}
// 投降局:未选主未埋牌 → 庄家快照 = 发牌 28 + 暗牌 8 = 36
const aw0 = mkAward();
t.eq('§8 边界 投降局 庄家快照=发牌+暗牌 36 张', P.get_seat_cards_award(aw0.o, 0).length, 36);
// 正常局:埋掉 8 张后 → 庄家快照 28 张,且已埋的不在内
const aw = mkAward();
const bury = aw.bankerDeal.slice(0, 5).concat(aw.an.slice(0, 3)); // 从发牌与暗牌里各埋几张
bury.forEach(i => aw.o.cards[i].playround = 0);
const snap = P.get_seat_cards_award(aw.o, 0);
t.eq('§8 正 庄家快照=埋牌后 28 张', snap.length, 28);
t.eq('§8 正 已埋的 8 张不在快照里', bury.some(i => snap.indexOf(i) >= 0), false);
t.eq('§8 正 闲家快照=发牌后 28 张', [P.get_seat_cards_award(aw.o, 1).length, P.get_seat_cards_award(aw.o, 2).length], [28, 28]);
// 快照是静态的:之后打出的牌仍算在内(否则闲家重连时算奖/亮牌会缩水)
snap.slice(0, 10).forEach(i => aw.o.cards[i].playround = 3);
t.eq('§8 边界 打出 10 张后 庄家快照仍是同样的 28 张', P.get_seat_cards_award(aw.o, 0), snap);
t.eq('§8 边界 打出后闲家快照也不缩水', P.get_seat_cards_award(aw.o, 1).length, 28);
process.exit(t.done('paiju') ? 0 : 1);