Files
erqiwang_youle/client/tests/test_handlers_play.js
T
joywayerandClaude Opus 5 5689ceab6f 二七王:重新生成夹具、BuryHandler 接住 maipai.playproc,修正甩错断言的错误映射
配合上一个 commit 服务端补发的 maipai.playproc,重新生成真包夹具(diff 只新增
三家 maipai 包各一份 round-1 初值 playproc,无其他变化)后跑一致性测试,暴露出
前端还有一处对应缺口:BuryHandler.js 组 table.playproc 时没有接 data.playproc
(PlayHandler.js 早已对 chupai1/2/3 做了同样的原样拷贝),于是「埋牌完成→庄家
首出」窗口增量路径的 table.playproc 停在上一阶段的 null。现补上,并在
test_handlers_bury.js 新增一条用例守住、用「退回修复必转红」反向验证。

test_handlers_play.js:84 的 `S.table.playproc.shuaicuo` 断言的正是此前 Fix Round 2
裁决②(本手信息收进事件载荷、不进 GameState)判定为缺陷的旧映射,playproc 改为
原样拷贝服务端结构后必然读 null 硬崩。按 task-12d-report.md §8 的裁决与建议替换:
断言改为守事件载荷(EQW_CARD_PLAYED 的 shuaicuo/order/seat/cards/count/flower/
cardtype)+ playproc 原样拷贝,强度不降——原断言只守住"甩错标志被处理"这一件事,
新断言额外多守了"本手信息不得混入 playproc"一条。

client/tests/test_consistency.js 全绿(23 checks,此前 20 绿/3 红);
client/tests/run.js 全绿(20 脚本);server/games/erqiwang/test/run.js 全绿
(17 脚本/675 checks,见上一个 commit)。

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

103 lines
5.4 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.
// 出牌 handler(chupai1/2/3)
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/PlayHandler.js');
const S = EQW_GameState;
const evts = [];
Object.keys(EQW_Events).forEach(k => EventBus.on(EQW_Events[k], () => evts.push(k)));
// ================= chupai1(首家出牌,自己出的)=================
S.reset(); S.room.mySeat = 0; S.my.cards = [1,2,3,4]; evts.length = 0;
EQW_PlayHandler.handle({ success: true, seat: 0, cards: [1,2], count: 2, flower: 3,
cardtype: 201, nextseat: 1, countdown: 15, baozhu: 0,
cardsinhand: [3,4], curmultiple: 2,
seatlist: [[0,0,0,0,[5,2]],[0,0,0,0,[6,1]],[0,0,0,0,[7,3]]] }, 1);
t.eq('出牌者手牌被更新', S.my.cards, [3,4]);
t.eq('下一个出牌者', S.turn.seat, 1);
t.eq('倒计时', S.turn.countdown, 15);
t.eq('抓分倍数', S.aset.curmultiple, 2);
t.eq('牌况表', S.table.seatlist.length, 3);
t.eq('出牌发事件', evts.indexOf('EQW_CARD_PLAYED') >= 0, true);
// ================= chupai2(别人出牌)=================
// 别人出牌时没有 cardsinhand,不能抹掉自己的手牌
S.reset(); S.room.mySeat = 0; S.my.cards = [3,4]; evts.length = 0;
EQW_PlayHandler.handle({ success: true, seat: 1, cards: [5,6], nextseat: 2,
countdown: 15, baozhu: 0 }, 2);
t.eq('别人出牌不抹自己手牌', S.my.cards, [3,4]);
t.eq('控制权推进', S.turn.seat, 2);
// mustcard 只发给下一个出牌者
S.reset(); S.room.mySeat = 2;
EQW_PlayHandler.handle({ success: true, seat: 1, cards: [5,6], nextseat: 2,
countdown: 15, mustcard: [7,8] }, 2);
t.eq('必出牌', S.my.mustCard, [7,8]);
// 没有 mustcard 时要清空——否则上一轮的建议会残留
S.reset(); S.my.mustCard = [7,8];
EQW_PlayHandler.handle({ success: true, seat: 1, cards: [5,6], nextseat: 0, countdown: 15 }, 2);
t.eq('无 mustcard 时清空', S.my.mustCard, []);
// ================= chupai3(末家出牌,本轮结束)=================
// 真实的 chupai3 也带 nextseat(下一轮首家):服务端在 switch 前统一设置,
// 只有整轮结束进入结算时才会整体换成 jiesuan rpc(不会以 chupai3 到达 handler)
S.reset(); evts.length = 0;
EQW_PlayHandler.handle({ success: true, seat: 2, cards: [9,10], maxseat: 0, grade: 25,
nextseat: 0, countdown: 15, baozhu: 1, curmultiple: -2 }, 3);
t.eq('本轮最大者', S.result.chupai.maxseat, 0);
t.eq('本轮闲家得分', S.result.chupai.grade, 25);
t.eq('累计捡分', S.aset.grade, 25);
t.eq('报无主标志', S.aset.baozhu, 1);
t.eq('抓分倍数(带符号)', S.aset.curmultiple, -2);
t.eq('下一轮首家', S.turn.seat, 0);
t.eq('本轮结束发事件', evts.indexOf('EQW_TRICK_END') >= 0, true);
// 本轮闲家没得分时不带 grade,累计分不该被抹
S.reset(); S.aset.grade = 40;
EQW_PlayHandler.handle({ success: true, seat: 2, cards: [9,10], maxseat: 1,
nextseat: 1, countdown: 15 }, 3);
t.eq('无 grade 时累计分保持', S.aset.grade, 40);
t.eq('无 grade 时仍推进控制权', S.turn.seat, 1);
// grade 是「本轮」得分,要跨包累加进小局捡分,不是赋值——
// 若实现改成 aset.grade = data.grade,这条会把 25 覆盖成 15 而非累加到 25
S.reset();
EQW_PlayHandler.handle({ success: true, seat: 2, cards: [1,2], maxseat: 0, grade: 10,
nextseat: 0, countdown: 15 }, 3);
t.eq('第一轮捡分', S.aset.grade, 10);
t.eq('第一轮下一轮首家', S.turn.seat, 0);
EQW_PlayHandler.handle({ success: true, seat: 2, cards: [3,4], maxseat: 1, grade: 15,
nextseat: 1, countdown: 15 }, 3);
t.eq('第二轮跨包累加捡分', S.aset.grade, 25);
t.eq('第二轮下一轮首家(证明第二个包确被处理)', S.turn.seat, 1);
// ================= 甩错 =================
// shuaicuo 是「这一手」的一次性信息,走 EQW_CARD_PLAYED 事件载荷,不进 GameState;
// playproc 是服务端下发的本轮进行态,原样拷贝,两者结构不同、不得混写(见 PlayHandler 顶部注释)。
S.reset(); evts.length = 0;
var lastPlayed = null;
EventBus.on(EQW_Events.EQW_CARD_PLAYED, function (p) { lastPlayed = p; });
EQW_PlayHandler.handle({ success: true, seat: 0, cards: [11], shuaicuo: 1, count: 1,
flower: 3, cardtype: 101, nextseat: 1, countdown: 15,
playproc: { round: 2, start: 0, currseat: 1, cards: [[11], null, null] } }, 1);
t.eq('甩错标志走事件载荷', lastPlayed.shuaicuo, 1);
t.eq('本手信息走事件载荷', [lastPlayed.order, lastPlayed.seat, lastPlayed.cards,
lastPlayed.count, lastPlayed.flower, lastPlayed.cardtype],
[1, 0, [11], 1, 3, 101]);
t.eq('本手信息不进 GameState', S.table.playproc.hasOwnProperty('shuaicuo'), false);
t.eq('playproc 原样拷贝服务端本轮进行态', S.table.playproc,
{ round: 2, start: 0, currseat: 1, cards: [[11], null, null] });
// ================= 缺必需字段 =================
S.reset();
EQW_PlayHandler.handle({ success: true, cards: [1] }, 1); //缺 seat
t.eq('缺 seat 时跳过该包', S.turn.seat, -1);
process.exit(t.done('handlers_play') ? 0 : 1);