From 070ff37db15ffd00602972319b2510193cfd7904 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Thu, 27 Aug 2026 18:04:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E4=B8=83=E7=8E=8B=EF=BC=9A=E6=98=8E?= =?UTF-8?q?=E7=89=8C=E3=80=81=E6=8F=90=E7=A4=BA=E3=80=81=E5=87=86=E5=A4=87?= =?UTF-8?q?=20handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 明牌数据进 GameState(面板可反复开关查看);提示不进——它是一次性通知、 不是对局状态,重连也不重放,存下来只会变成幽灵数据,故只随事件带出。 Co-Authored-By: Claude Opus 5 (1M context) --- .../codes/net/handlers/QueryHandler.js | 33 +++++++++++++ .../codes/net/handlers/ReadyHandler.js | 16 +++++++ client/tests/test_handlers_query.js | 47 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 client/js/01_SubGame/codes/net/handlers/QueryHandler.js create mode 100644 client/js/01_SubGame/codes/net/handlers/ReadyHandler.js create mode 100644 client/tests/test_handlers_query.js diff --git a/client/js/01_SubGame/codes/net/handlers/QueryHandler.js b/client/js/01_SubGame/codes/net/handlers/QueryHandler.js new file mode 100644 index 0000000..7f1a2ed --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/QueryHandler.js @@ -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 }); + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/ReadyHandler.js b/client/js/01_SubGame/codes/net/handlers/ReadyHandler.js new file mode 100644 index 0000000..d67c8dc --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/ReadyHandler.js @@ -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 }); + } +}; diff --git a/client/tests/test_handlers_query.js b/client/tests/test_handlers_query.js new file mode 100644 index 0000000..a558076 --- /dev/null +++ b/client/tests/test_handlers_query.js @@ -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);