二七王:明牌、提示、准备 handler

明牌数据进 GameState(面板可反复开关查看);提示不进——它是一次性通知、
不是对局状态,重连也不重放,存下来只会变成幽灵数据,故只随事件带出。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 18:04:33 +08:00
co-authored by Claude Opus 5
parent 47c8a27d0d
commit 070ff37db1
3 changed files with 96 additions and 0 deletions
@@ -0,0 +1,33 @@
///////////////////////////////////////////////////////////////
////////// EQW_QueryHandler: 明牌(mingpai)与提示(tishi)///
///////////////////////////////////////////////////////////////
// 协议 §13.6 mingpai(只回给请求者):
// seat 请求者 / others 另外两家各自未出的全部主牌 [{seat, zhucards}]
// 受理条件:可查牌模式 + 出牌阶段 + 已有【任一】玩家报无主(design §9.3)
//
// 协议 §13.8 tishi(只转发给对家,即另一闲家):
// seat 发出提示的闲家 / tip 1=踩 2=没分 3=有分
// 注意:成功时【不给发送者回执】,只转发给对家;只有失败才回给发送者。
//
// 明牌数据进 GameState(面板可反复开关查看),提示不进——
// 提示是一次性的通知、不是对局状态,重连也不重放,存下来只会变成幽灵数据。
var EQW_QueryHandler = EQW_QueryHandler || {
handleMingpai: function (data) {
if (!data.hasOwnProperty('others')) {
console.error('[EQW_QueryHandler] mingpai 缺 others,已跳过该包');
return;
}
EQW_GameState.table.mingpai = data.others;
EventBus.emit(EQW_Events.EQW_MINGPAI);
},
handleTishi: function (data) {
if (!data.hasOwnProperty('seat') || !data.hasOwnProperty('tip')) {
console.error('[EQW_QueryHandler] tishi 缺 seat 或 tip,已跳过该包');
return;
}
//一次性通知:不进 GameState,随事件把内容带给订阅者
EventBus.emit(EQW_Events.EQW_TIP, { seat: data.seat, tip: data.tip });
}
};
@@ -0,0 +1,16 @@
///////////////////////////////////////////////////////////////
////////// EQW_ReadyHandler: 准备(zhunbei)//////////////////
///////////////////////////////////////////////////////////////
// 协议 §16:seat 位置序号。
// 准备状态由平台的玩家信息 UI 呈现(清单 T-15:准备标识用平台的),
// 子游戏只需把事件发出去,让关心的组件(如结算面板的「下一局」按钮)响应。
var EQW_ReadyHandler = EQW_ReadyHandler || {
handle: function (data) {
if (!data.hasOwnProperty('seat')) {
console.error('[EQW_ReadyHandler] zhunbei 缺 seat,已跳过该包');
return;
}
EventBus.emit(EQW_Events.EQW_READY_CHANGED, { seat: data.seat });
}
};
+47
View File
@@ -0,0 +1,47 @@
// 明牌 / 提示 / 准备 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/QueryHandler.js');
load('client/js/01_SubGame/codes/net/handlers/ReadyHandler.js');
const S = EQW_GameState;
const payloads = [];
EventBus.on(EQW_Events.EQW_TIP, d => payloads.push(d));
const evts = [];
Object.keys(EQW_Events).forEach(k => EventBus.on(EQW_Events[k], () => evts.push(k)));
// ================= mingpai =================
S.reset(); evts.length = 0;
EQW_QueryHandler.handleMingpai({ success: true, seat: 1,
others: [{ seat: 0, zhucards: [52, 2] }, { seat: 2, zhucards: [53, 15] }] });
t.eq('明牌数据入状态', S.table.mingpai,
[{ seat: 0, zhucards: [52, 2] }, { seat: 2, zhucards: [53, 15] }]);
t.eq('明牌发事件', evts.indexOf('EQW_MINGPAI') >= 0, true);
// ================= tishi =================
// 提示是一次性的,不进 GameState,只随事件带出去
S.reset(); payloads.length = 0; evts.length = 0;
const before = S.snapshot(); // 处理前的状态快照
EQW_QueryHandler.handleTishi({ success: true, seat: 2, tip: 1 });
t.eq('提示发事件', evts.indexOf('EQW_TIP') >= 0, true);
t.eq('提示内容随事件带出', payloads, [{ seat: 2, tip: 1 }]);
t.eq('提示未改动 GameState 任何字段', S.snapshot(), before);
// ================= zhunbei =================
S.reset(); evts.length = 0;
EQW_ReadyHandler.handle({ success: true, seat: 1 });
t.eq('准备发事件', evts.indexOf('EQW_READY_CHANGED') >= 0, true);
// ================= 缺必需字段 =================
S.reset();
EQW_QueryHandler.handleMingpai({ success: true }); //缺 others
t.eq('明牌缺 others 时不写', S.table.mingpai, null);
payloads.length = 0;
EQW_QueryHandler.handleTishi({ success: true, seat: 0 }); //缺 tip
t.eq('提示缺 tip 时不发事件', payloads, []);
process.exit(t.done('handlers_query') ? 0 : 1);