二七王:重连与开局 handler,路由表注册齐全
重连即重画:断线重连与硬刷新复用同一条路径(填 GameState → emit RESYNC_ALL), 不为重连单写一套渲染。deskinfo 按 step 分组填充,重连不重放 70 分开底那类 一次性事件。StartWar 支持差异化下发,按本座位取自己那份。 路由表 12 条注册齐全,test_dispatcher 的完整性断言随之转绿—— 漏接一个包就会红。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,3 +49,18 @@ var EQW_Dispatcher = EQW_Dispatcher || {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//—— 路由表:一 rpc 一处理器(协议里每个服务端推送都要在这里注册)——
|
||||||
|
//新增一种推送 = 加一条注册 + 写对应 handler;本文件不写业务。
|
||||||
|
EQW_Dispatcher.register('fapai', function (d) { EQW_DealHandler.handle(d); });
|
||||||
|
EQW_Dispatcher.register('jiaofen', function (d) { EQW_CallHandler.handleJiaofen(d); });
|
||||||
|
EQW_Dispatcher.register('shangzhuang', function (d) { EQW_CallHandler.handleShangzhuang(d); });
|
||||||
|
EQW_Dispatcher.register('xuanzhu', function (d) { EQW_MainHandler.handleXuanzhu(d); });
|
||||||
|
EQW_Dispatcher.register('maipai', function (d) { EQW_BuryHandler.handle(d); });
|
||||||
|
EQW_Dispatcher.register('chupai1', function (d) { EQW_PlayHandler.handle(d, 1); });
|
||||||
|
EQW_Dispatcher.register('chupai2', function (d) { EQW_PlayHandler.handle(d, 2); });
|
||||||
|
EQW_Dispatcher.register('chupai3', function (d) { EQW_PlayHandler.handle(d, 3); });
|
||||||
|
EQW_Dispatcher.register('mingpai', function (d) { EQW_QueryHandler.handleMingpai(d); });
|
||||||
|
EQW_Dispatcher.register('tishi', function (d) { EQW_QueryHandler.handleTishi(d); });
|
||||||
|
EQW_Dispatcher.register('jiesuan', function (d) { EQW_ResultHandler.handleJiesuan(d); });
|
||||||
|
EQW_Dispatcher.register('zhunbei', function (d) { EQW_ReadyHandler.handle(d); });
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
////////// EQW_ResyncHandler: 重连(deskinfo)与开局(StartWar)
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// 前端 04 §3:重连 = 重画。断线重连与硬刷新本质相同,复用同一条路径:
|
||||||
|
// 填 GameState → emit EQW_RESYNC_ALL → 各组件据数据重建界面
|
||||||
|
// 【绝不为重连单写一套渲染】。
|
||||||
|
//
|
||||||
|
// deskinfo 按 step 只带对应阶段的分组(协议文末):
|
||||||
|
// 恒有 count / idx / PlayerInfo / step / MyCards
|
||||||
|
// CallRun(step1) ChooseMain(step2) BuryCards(step3) PushCards(step5) Balance(step6)
|
||||||
|
//
|
||||||
|
// 【重连不重放一次性事件】:70 分坐庄的 3 秒开底是上庄时的一次性事件,
|
||||||
|
// deskinfo 里没有 ancard3s,前端也不得补播。
|
||||||
|
var EQW_ResyncHandler = EQW_ResyncHandler || {
|
||||||
|
|
||||||
|
//平台房间信息(roomcode / 总局数 / roomtype 位串)
|
||||||
|
handleSetRoomDes: function (roomcode, asetcount, roomtype) {
|
||||||
|
if (typeof asetcount === 'number') { EQW_GameState.room.asetCount = asetcount; }
|
||||||
|
EQW_GameState.room.options = EQW_RoomOptions_Parse.parse(roomtype);
|
||||||
|
},
|
||||||
|
|
||||||
|
//开局:makewar 可能差异化下发(sendtype:1 + seatlist[]),先取自己那份
|
||||||
|
handleStartWar: function (msg) {
|
||||||
|
if (!msg || !msg.data) {
|
||||||
|
console.warn('[EQW_ResyncHandler] StartWar 无数据,已忽略');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var raw = msg.data.deskwar || msg.data;
|
||||||
|
var mine = raw;
|
||||||
|
if (raw && raw.sendtype === 1 && Object.prototype.toString.call(raw.seatlist) === '[object Array]') {
|
||||||
|
mine = null;
|
||||||
|
for (var i = 0; i < raw.seatlist.length; i++) {
|
||||||
|
if (raw.seatlist[i] && raw.seatlist[i].seat === EQW_GameState.room.mySeat) {
|
||||||
|
mine = raw.seatlist[i].data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mine) {
|
||||||
|
console.error('[EQW_ResyncHandler] StartWar 差异化下发里没有本座位的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.handleDeskinfo(mine);
|
||||||
|
},
|
||||||
|
|
||||||
|
//重连:deskinfo 全量快照
|
||||||
|
handleDeskinfo: function (info) {
|
||||||
|
if (!info) {
|
||||||
|
console.warn('[EQW_ResyncHandler] deskinfo 为空,已忽略');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EQW_GameState.reset();
|
||||||
|
|
||||||
|
//—— 恒有的部分 ——
|
||||||
|
EQW_GameState._apply(EQW_GameState.room, info, {
|
||||||
|
asetCount: 'count',
|
||||||
|
asetIdx: 'idx',
|
||||||
|
playerScores: 'PlayerInfo'
|
||||||
|
});
|
||||||
|
EQW_GameState._apply(EQW_GameState.aset, info, { step: 'step' });
|
||||||
|
EQW_GameState._apply(EQW_GameState.my, info, { cards: 'MyCards' });
|
||||||
|
|
||||||
|
//—— 按阶段分组 ——
|
||||||
|
if (info.CallRun) { this._applyCallRun(info.CallRun); }
|
||||||
|
if (info.ChooseMain) { this._applyChooseMain(info.ChooseMain); }
|
||||||
|
if (info.BuryCards) { this._applyBuryCards(info.BuryCards); }
|
||||||
|
if (info.PushCards) { this._applyPushCards(info.PushCards); }
|
||||||
|
if (info.Balance) { this._applyBalance(info.Balance); }
|
||||||
|
|
||||||
|
EventBus.emit(EQW_Events.EQW_RESYNC_ALL);
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyCallRun: function (g) {
|
||||||
|
EQW_GameState._apply(EQW_GameState.call, g, {
|
||||||
|
currcall: 'nowcall',
|
||||||
|
calls: 'call'
|
||||||
|
});
|
||||||
|
EQW_GameState._apply(EQW_GameState.aset, g, { multiple: 'multiple' });
|
||||||
|
EQW_GameState._apply(EQW_GameState.turn, g, {
|
||||||
|
seat: 'seat',
|
||||||
|
countdown: 'countdown'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyChooseMain: function (g) {
|
||||||
|
EQW_GameState._apply(EQW_GameState.aset, g, {
|
||||||
|
banker: 'banker',
|
||||||
|
call: 'call',
|
||||||
|
multiple: 'multiple',
|
||||||
|
touxiang: 'touxiang'
|
||||||
|
});
|
||||||
|
EQW_GameState._apply(EQW_GameState.my, g, { bottomCards: 'bottomcards' });
|
||||||
|
EQW_GameState._apply(EQW_GameState.turn, g, { countdown: 'countdown' });
|
||||||
|
EQW_GameState.turn.seat = EQW_GameState.aset.banker; //选主由庄家做
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyBuryCards: function (g) {
|
||||||
|
EQW_GameState._apply(EQW_GameState.aset, g, {
|
||||||
|
banker: 'banker',
|
||||||
|
call: 'call',
|
||||||
|
multiple: 'multiple',
|
||||||
|
flower: 'flower'
|
||||||
|
});
|
||||||
|
EQW_GameState._apply(EQW_GameState.my, g, { bottomCards: 'bottomcards' });
|
||||||
|
EQW_GameState._apply(EQW_GameState.turn, g, { countdown: 'countdown' });
|
||||||
|
EQW_GameState.turn.seat = EQW_GameState.aset.banker; //埋牌由庄家做
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyPushCards: function (g) {
|
||||||
|
EQW_GameState._apply(EQW_GameState.aset, g, {
|
||||||
|
banker: 'banker',
|
||||||
|
call: 'call',
|
||||||
|
multiple: 'multiple',
|
||||||
|
flower: 'flower',
|
||||||
|
grade: 'grade',
|
||||||
|
curmultiple: 'curmultiple'
|
||||||
|
});
|
||||||
|
EQW_GameState._apply(EQW_GameState.my, g, {
|
||||||
|
buryCards: 'burycards',
|
||||||
|
mustCard: 'mustcard'
|
||||||
|
});
|
||||||
|
EQW_GameState._apply(EQW_GameState.table, g, {
|
||||||
|
playproc: 'playproc',
|
||||||
|
pushlist: 'pushlist',
|
||||||
|
seatlist: 'seatlist',
|
||||||
|
liangpai: 'liangpai'
|
||||||
|
});
|
||||||
|
EQW_GameState._apply(EQW_GameState.turn, g, { countdown: 'countdown' });
|
||||||
|
//当前该谁出牌由 playproc.currseat 给出(服务端权威,前端不推导)
|
||||||
|
if (g.playproc && typeof g.playproc.currseat === 'number') {
|
||||||
|
EQW_GameState.turn.seat = g.playproc.currseat;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyBalance: function (g) {
|
||||||
|
EQW_GameState._apply(EQW_GameState.result, g, { aset: 'aset' });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -4,7 +4,18 @@ const t = require('./_assert')();
|
|||||||
|
|
||||||
load('client/js/gameabc-framework/system/EventBus.js');
|
load('client/js/gameabc-framework/system/EventBus.js');
|
||||||
load('client/js/01_SubGame/codes/state/Events.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/state/RoomOptions.js');
|
||||||
load('client/js/01_SubGame/codes/net/handlers/FailHandler.js');
|
load('client/js/01_SubGame/codes/net/handlers/FailHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/DealHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/CallHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/MainHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/BuryHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/PlayHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/QueryHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/ReadyHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/ResultHandler.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/ResyncHandler.js');
|
||||||
load('client/js/01_SubGame/codes/net/Dispatcher.js');
|
load('client/js/01_SubGame/codes/net/Dispatcher.js');
|
||||||
|
|
||||||
// ---- 注册与分发 ----
|
// ---- 注册与分发 ----
|
||||||
@@ -40,16 +51,9 @@ t.eq('未知 rpc 不抛错', threw, false);
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ---- 路由表完整性:协议里每个服务端推送 rpc 都要有 handler ----
|
// ---- 路由表完整性:协议里每个服务端推送 rpc 都要有 handler ----
|
||||||
// (本条在 Task 10 全部 handler 就位后才会真正通过;此刻先写下期望,
|
|
||||||
// Task 10 完成时它会自动转绿——这正是它存在的意义:漏接一个包就会红)
|
|
||||||
// Task 10 完成后取消注释以下代码块
|
|
||||||
/*
|
|
||||||
load('client/js/01_SubGame/codes/state/GameState.js');
|
|
||||||
load('client/js/01_SubGame/codes/state/RoomOptions.js');
|
|
||||||
['fapai','jiaofen','shangzhuang','xuanzhu','maipai',
|
['fapai','jiaofen','shangzhuang','xuanzhu','maipai',
|
||||||
'chupai1','chupai2','chupai3','mingpai','tishi','jiesuan','zhunbei'].forEach(rpc => {
|
'chupai1','chupai2','chupai3','mingpai','tishi','jiesuan','zhunbei'].forEach(rpc => {
|
||||||
t.eq('已注册 handler: ' + rpc, EQW_Dispatcher.hasHandler(rpc), true);
|
t.eq('已注册 handler: ' + rpc, EQW_Dispatcher.hasHandler(rpc), true);
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
process.exit(t.done('dispatcher') ? 0 : 1);
|
process.exit(t.done('dispatcher') ? 0 : 1);
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// 重连(deskinfo)与开局(StartWar)
|
||||||
|
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/state/RoomOptions.js');
|
||||||
|
load('client/js/01_SubGame/codes/net/handlers/ResyncHandler.js');
|
||||||
|
|
||||||
|
const S = EQW_GameState;
|
||||||
|
const evts = [];
|
||||||
|
Object.keys(EQW_Events).forEach(k => EventBus.on(EQW_Events[k], () => evts.push(k)));
|
||||||
|
|
||||||
|
// ================= setRoomDes:房间选项 =================
|
||||||
|
S.reset(); S.room.options = null;
|
||||||
|
EQW_ResyncHandler.handleSetRoomDes(123456, 12, '10100');
|
||||||
|
t.eq('总局数', S.room.asetCount, 12);
|
||||||
|
t.eq('傍王开', S.room.options.bangwang, 1);
|
||||||
|
t.eq('可查牌', S.room.options.nocheck, 0);
|
||||||
|
|
||||||
|
// ================= deskinfo:step 5 出牌阶段 =================
|
||||||
|
S.reset(); S.room.mySeat = 1; evts.length = 0;
|
||||||
|
EQW_ResyncHandler.handleDeskinfo({
|
||||||
|
count: 6, idx: 2, PlayerInfo: [10, -5, -5], step: 5, MyCards: [1,2,3],
|
||||||
|
PushCards: {
|
||||||
|
banker: 0, call: 60, multiple: 3, flower: 3, countdown: 15,
|
||||||
|
grade: 25, playproc: { round: 4, currseat: 1, cards: [[9],[],[]] },
|
||||||
|
seatlist: [[0,0,0,0,[5,2]],[0,0,0,0,[6,1]],[0,0,0,0,[7,3]]],
|
||||||
|
liangpai: { cards: [52,53] }, pushlist: [[[1],[2],[3]]],
|
||||||
|
curmultiple: 2, mustcard: [4,5]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.eq('总局数', S.room.asetCount, 6);
|
||||||
|
t.eq('当前局', S.room.asetIdx, 2);
|
||||||
|
t.eq('三家总分', S.room.playerScores, [10, -5, -5]);
|
||||||
|
t.eq('阶段', S.aset.step, 5);
|
||||||
|
t.eq('手牌', S.my.cards, [1,2,3]);
|
||||||
|
t.eq('庄家', S.aset.banker, 0);
|
||||||
|
t.eq('主牌花色', S.aset.flower, 3);
|
||||||
|
t.eq('捡分', S.aset.grade, 25);
|
||||||
|
t.eq('抓分倍数', S.aset.curmultiple, 2);
|
||||||
|
t.eq('当前轮出牌情况', S.table.playproc.round, 4);
|
||||||
|
t.eq('出牌历史', S.table.pushlist.length, 1);
|
||||||
|
t.eq('亮牌', S.table.liangpai, { cards: [52,53] });
|
||||||
|
t.eq('必出牌', S.my.mustCard, [4,5]);
|
||||||
|
t.eq('控制权(由 playproc.currseat 给出)', S.turn.seat, 1);
|
||||||
|
t.eq('重连发全量重画事件', evts.indexOf('EQW_RESYNC_ALL') >= 0, true);
|
||||||
|
|
||||||
|
// ================= deskinfo:step 1 叫分阶段 =================
|
||||||
|
S.reset(); evts.length = 0;
|
||||||
|
EQW_ResyncHandler.handleDeskinfo({
|
||||||
|
count: 6, idx: 1, PlayerInfo: [0,0,0], step: 1, MyCards: [7,8],
|
||||||
|
CallRun: { seat: 2, countdown: 15, nowcall: 65, multiple: 2, call: [null, 0, 65] }
|
||||||
|
});
|
||||||
|
t.eq('叫分阶段', S.aset.step, 1);
|
||||||
|
t.eq('当前叫分者', S.turn.seat, 2);
|
||||||
|
t.eq('当前叫到的分', S.call.currcall, 65);
|
||||||
|
t.eq('三家叫分', S.call.calls, [null, 0, 65]);
|
||||||
|
|
||||||
|
// ================= deskinfo:step 2 选主阶段(庄家有 bottomcards)=================
|
||||||
|
S.reset(); evts.length = 0;
|
||||||
|
EQW_ResyncHandler.handleDeskinfo({
|
||||||
|
count: 6, idx: 1, PlayerInfo: [0,0,0], step: 2, MyCards: [1,2],
|
||||||
|
ChooseMain: { banker: 0, call: 70, multiple: 2, countdown: 20,
|
||||||
|
bottomcards: [1,2,3,4,5,6,7,8], touxiang: 1 }
|
||||||
|
});
|
||||||
|
t.eq('选主阶段', S.aset.step, 2);
|
||||||
|
t.eq('底牌', S.my.bottomCards, [1,2,3,4,5,6,7,8]);
|
||||||
|
t.eq('允许投降', S.aset.touxiang, 1);
|
||||||
|
t.eq('控制权归庄家', S.turn.seat, 0);
|
||||||
|
|
||||||
|
// 重连【不重放】70 分开底:deskinfo 里没有 ancard3s
|
||||||
|
t.eq('重连不重放开底', S.table.ancard3s, 0);
|
||||||
|
|
||||||
|
// ================= deskinfo:step 6 结算阶段 =================
|
||||||
|
S.reset(); evts.length = 0;
|
||||||
|
EQW_ResyncHandler.handleDeskinfo({
|
||||||
|
count: 6, idx: 6, PlayerInfo: [12,-6,-6], step: 6, MyCards: [],
|
||||||
|
Balance: { readystate: [1,0,0], aset: { banker: 0, upgrade: 1, seatlist: [] } }
|
||||||
|
});
|
||||||
|
t.eq('结算阶段', S.aset.step, 6);
|
||||||
|
t.eq('小局结算数据', S.result.aset.upgrade, 1);
|
||||||
|
|
||||||
|
// ================= StartWar:差异化下发 =================
|
||||||
|
S.reset(); S.room.mySeat = 2; evts.length = 0;
|
||||||
|
EQW_ResyncHandler.handleStartWar({ data: { deskwar: { sendtype: 1, seatlist: [
|
||||||
|
{ seat: 0, data: { count: 6, idx: 1, PlayerInfo: [0,0,0], step: 1, MyCards: [90] } },
|
||||||
|
{ seat: 2, data: { count: 6, idx: 1, PlayerInfo: [0,0,0], step: 1, MyCards: [11,12] } }
|
||||||
|
] } } });
|
||||||
|
t.eq('按本座位取到自己那份', S.my.cards, [11,12]);
|
||||||
|
|
||||||
|
// 非差异化下发:直接用 data
|
||||||
|
S.reset(); S.room.mySeat = 0;
|
||||||
|
EQW_ResyncHandler.handleStartWar({ data: { count: 6, idx: 1, PlayerInfo: [0,0,0],
|
||||||
|
step: 1, MyCards: [3,4] } });
|
||||||
|
t.eq('非差异化下发', S.my.cards, [3,4]);
|
||||||
|
|
||||||
|
// 畸形入参不崩
|
||||||
|
let boom = false;
|
||||||
|
try { EQW_ResyncHandler.handleStartWar(null); EQW_ResyncHandler.handleDeskinfo(null); }
|
||||||
|
catch (e) { boom = true; }
|
||||||
|
t.eq('畸形入参不崩', boom, false);
|
||||||
|
|
||||||
|
process.exit(t.done('resync') ? 0 : 1);
|
||||||
Reference in New Issue
Block a user