Files
erqiwang_youle/client/tests/test_handlers_call.js
joywayerandClaude Opus 5 8b2cfdaf27 二七王:前端删掉 9 处阶段/控制权推导,一律读服务端包字段
I-3 前端不再持有对局状态机(配合服务端上一提交的下发面补齐):
- 删掉 5 处按 rpc 名硬编码的 aset.step(Deal=1 / Call=2 / Main=3 / Bury=5 / Result=6),
  一律改 _apply 读包里的 step
- 删掉 4 处 turn.seat = aset.banker(CallHandler / MainHandler / ResyncHandler×2),
  增量路径读 nextseat、重连路径读分组里的 seat;同一条规则不再有 4 个写入处

I-2 ResyncHandler._applyBalance 由只接 aset 扩为接 aset/bottom/account,
结算面板开着时重连/硬刷新不再丢抠底明细与末局大结算。
一致性测试 EXCLUDE 随之从 4 条收缩为 2 条(去掉 result.bottom / result.account,
只留一次性事件 table.ancard3s 与归下一轮的 result.chupai),未新增任何豁免条目。

夹具按新包结构重跑(种子不变,牌局不变):diff 纯为 67 行新增字段、0 行删除;
test_fixture 补上 step/nextseat/Balance 同源断言(68→82 checks)。
export_packets.js 里关于 playproc/seatlist 活引用的注释已过时,更正为仍存在的
Balance.readystate 与三份结算快照。

各 handler 单测补齐新字段并新增守卫用例:刻意构造 nextseat != banker、
step 为非预期值的包,使「退回硬编码/banker 反推」的写法必然转红(11/11 已验证)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:31:20 +08:00

97 lines
5.1 KiB
JavaScript
Raw Permalink 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.
// 发牌 / 叫分 / 上庄 handler
const { load } = require('./_load');
const t = require('./_assert')();
load('client/js/gameabc-framework/system/EventBus.js');
load('client/js/01_SubGame/codes/state/Events.js');
load('client/js/01_SubGame/codes/state/GameState.js');
load('client/js/01_SubGame/codes/net/handlers/DealHandler.js');
load('client/js/01_SubGame/codes/net/handlers/CallHandler.js');
const S = EQW_GameState;
const evts = [];
Object.keys(EQW_Events).forEach(k => EventBus.on(EQW_Events[k], () => evts.push(k)));
// ================= fapai =================
S.reset(); S.room.mySeat = 1; evts.length = 0;
EQW_DealHandler.handle({ success: true, asetidx: 2, asetcount: 6,
cards: [1, 2, 3], step: 1, seat: 0, countdown: 15 });
t.eq('局数', S.room.asetIdx, 2);
t.eq('总局数', S.room.asetCount, 6);
t.eq('手牌', S.my.cards, [1, 2, 3]);
t.eq('当前叫分者', S.turn.seat, 0);
t.eq('倒计时', S.turn.countdown, 15);
t.eq('step 读包字段(叫分=1)', S.aset.step, 1);
t.eq('mySeat 未被 reset 冲掉', S.room.mySeat, 1);
t.eq('fapai 发了 RESET/HAND/TURN 事件',
['EQW_RESET','EQW_HAND_CHANGED','EQW_TURN_CHANGED'].every(e => evts.indexOf(e) >= 0), true);
// fapai 必须先 reset:上一局的残留要清掉
S.aset.banker = 2; S.table.pushlist = [[9]];
EQW_DealHandler.handle({ success: true, asetidx: 3, asetcount: 6, cards: [4], step: 1, seat: 1, countdown: 15 });
t.eq('fapai 清掉上局 banker', S.aset.banker, -1);
t.eq('fapai 清掉上局 pushlist', S.table.pushlist, []);
// 【守卫】step 必须来自包,不得按 rpc 名硬编码。这里刻意给一个"服务端将来新插的阶段"值:
// 前端应照单收下(服务端权威),而不是把它改回 1。退回 `aset.step = 1` 的写法必转红。
EQW_DealHandler.handle({ success: true, asetidx: 3, asetcount: 6, cards: [4], step: 4, seat: 1, countdown: 15 });
t.eq('fapai step 照读包字段(不按 rpc 名硬编码)', S.aset.step, 4);
// ================= jiaofen =================
S.reset(); evts.length = 0;
EQW_CallHandler.handleJiaofen({ success: true, seat: 0, call: 60, currcall: 60,
multiple: 3, step: 1, nextseat: 1, countdown: 15 });
t.eq('记录该家叫分', S.call.calls, [60, null, null]);
t.eq('当前叫到的分', S.call.currcall, 60);
t.eq('基础子数', S.aset.multiple, 3);
t.eq('jiaofen step 读包字段(仍在叫分=1)', S.aset.step, 1);
t.eq('下一个叫分者', S.turn.seat, 1);
t.eq('jiaofen 发事件', evts.indexOf('EQW_CALL_CHANGED') >= 0, true);
// 不叫(call = 0)也要记下来,与「还没叫」(null) 区分
EQW_CallHandler.handleJiaofen({ success: true, seat: 1, call: 0, currcall: 60,
multiple: 3, step: 1, nextseat: 2, countdown: 15 });
t.eq('不叫记为 0,与未叫 null 区分', S.call.calls, [60, 0, null]);
// ================= shangzhuang =================
S.reset(); evts.length = 0;
EQW_CallHandler.handleShangzhuang({ success: true, seat: 0, call: 70, banker: 0, grade: 70,
multiple: 2, step: 2, nextseat: 0,
bottomcards: [1,2,3,4,5,6,7,8], ancard3s: 1,
cards: [1,2,3], countdown: 20, touxiang: 1, curmultiple: 3 });
t.eq('庄家', S.aset.banker, 0);
t.eq('庄家叫分', S.aset.call, 70);
t.eq('基础子数', S.aset.multiple, 2);
t.eq('允许投降', S.aset.touxiang, 1);
t.eq('抓分倍数', S.aset.curmultiple, 3);
t.eq('开底标志', S.table.ancard3s, 1);
t.eq('底牌', S.my.bottomCards, [1,2,3,4,5,6,7,8]);
t.eq('摸底后手牌', S.my.cards, [1,2,3]);
t.eq('step 读包字段(选主=2)', S.aset.step, 2);
t.eq('控制权读包里的 nextseat', S.turn.seat, 0);
t.eq('shangzhuang 发事件',
['EQW_BANKER_SET','EQW_HAND_CHANGED'].every(e => evts.indexOf(e) >= 0), true);
// 【守卫】控制权必须来自包里的 nextseat,不得用本地 banker 反推。
// 真实链路里 nextseat 恒 == banker,两者相等时"读哪个"分辨不出来;这里刻意让它们不同,
// 退回 `turn.seat = EQW_GameState.aset.banker` 的写法必转红。
// 同理 data.seat(=最后一个叫分者)也不是控制权,这里也给成第三个值。
S.reset();
EQW_CallHandler.handleShangzhuang({ success: true, seat: 1, call: 55, banker: 0, grade: 55,
multiple: 4, step: 2, nextseat: 2,
countdown: 20, touxiang: 0, curmultiple: 3 });
t.eq('控制权取 nextseat,不取 banker', S.turn.seat, 2);
t.eq('控制权不取 data.seat(最后一个叫分者)', S.turn.seat !== 1, true);
// 闲家视角:没有 cards / bottomcards(非 70 分),缺字段不能把已有值抹掉
S.reset(); S.my.cards = [7, 8, 9];
EQW_CallHandler.handleShangzhuang({ success: true, seat: 0, call: 55, banker: 0, grade: 55,
multiple: 4, step: 2, nextseat: 0,
countdown: 20, touxiang: 0, curmultiple: 3 });
t.eq('闲家手牌不被抹', S.my.cards, [7, 8, 9]);
t.eq('闲家无底牌', S.my.bottomCards, []);
t.eq('非 70 分无开底标志', S.table.ancard3s, 0);
process.exit(t.done('handlers_call') ? 0 : 1);