diff --git a/CLAUDE.md b/CLAUDE.md index 574eeb6..3ecb5cc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,4 +74,6 @@ git config core.hooksPath .githooks ## 测试与 Git - **测试纪律**:细则见 client 05 §10 / server 04 §10。要点——测试代码可用现代语法(只跑 Node,不上线);**正式代码禁止为测试而加逻辑/放宽校验**,禁止为过测而改正式代码或降低测试标准(skip、软化断言、吞异常等);失败先用证据裁定「业务缺陷 vs 脚本缺陷」;正面/反面/边界用例都要覆盖。 +- **新增断言要反向验证**:写完一条守卫断言,**故意破坏它守的那处逻辑,确认它真会变红**——否则你加的可能是一条空转断言(「全绿但什么都没验」比红更有害)。 +- ⚠️ **本仓库文件是 CRLF 行尾**:任何基于行尾的脚本化改写(`sed`/正则批量删改)必须按 `\r?\n` 处理。写 `\n` 会**一行都匹配不上却静默成功**——曾据此得出「测试抓不住缺陷」的错误结论。脚本改完先 `git diff --stat` 确认文件真的变了,再据其结果下判断。 - **Git 提交**(细则见 server 04 §11):完成一个可独立成立的逻辑改动即可**自动提交、无需逐次确认**(`push` 按需);提交信息用中文、聚焦一件事、结尾保留 `Co-Authored-By` 署名;不用 `git add -A`/`git add .`、不跳过 hooks、不做 `reset --hard`/`push --force`。 diff --git a/client/index.html b/client/index.html index c7c9747..49d1687 100644 --- a/client/index.html +++ b/client/index.html @@ -236,7 +236,24 @@ - + + + + + + + + + + + + + + + + + + diff --git a/client/js/01_SubGame/codes/SubGameHooks.js b/client/js/01_SubGame/codes/SubGameHooks.js index ccdcb22..a4f1134 100644 --- a/client/js/01_SubGame/codes/SubGameHooks.js +++ b/client/js/01_SubGame/codes/SubGameHooks.js @@ -122,8 +122,7 @@ SubGameHooks.gamemydraw = function (gameid, spid, times, timelong, no2, no3, no4 // [A] Free(_msg):投票解散确认回调。 // SubGameHooks.Free = function (_msg) { /* ... */ }; -// [A] DeskInfo(_msg):未开战状态下加入牌桌的数据同步。 -// SubGameHooks.DeskInfo = function (_msg) { /* ... */ }; +// [A] DeskInfo(_msg):牌局已开始时加入牌桌的数据同步(实现见文末「网络与对局」一段)。 // [A] breakRoom():开局后自己退出房间触发。 // SubGameHooks.breakRoom = function () { /* ... */ }; @@ -246,5 +245,105 @@ SubGameHooks.gamemydraw = function (gameid, spid, times, timelong, no2, no3, no4 // [D] gameui_stop_voice(uiIndex):停止桌面语音气泡。 // SubGameHooks.gameui_stop_voice = function (uiIndex) { /* ... */ }; +//—— 网络与对局(B 阶段接线)—— +//平台入口只解包转交,业务全在 net/ 的 handler 里。 + +// --------------------------------------------------------------------------- +// 【room.mySeat 的唯一写入处】(SSOT) +// +// 为什么不能只在 appStart 里取一次: +// 平台 12_Logic.js:389 调 Game_Modify.appStart() 时,C_Player 还只是一个 +// `var C_Player;`——`C_Player = new Player(-1)` 要到【同一个函数】的第 480 行才执行。 +// 此刻 typeof C_Player === 'undefined',守卫为假、整个 appStart 空转; +// 之后玩家登录、07_Desk.js 里 C_Player.SetSeat() 拿到真座位,但 appStart 再也不会被调用, +// 于是 mySeat 永远停在 -1:发包带 seat:-1(服务端 check_player 必然拒绝)、 +// StartWar 差异化下发找不到本座位而整包丢弃、SeatMap.toDisplay(-1) 抛错。 +// ——而且【全程无报错】,这正是它能潜伏下来的原因。 +// +// 因此改为:单一写入处 + 多个调用点。凡是平台已经确定座位的入口都同步一次, +// 不赌「所有路径都会经过某一个入口」。 +// +// 幂等:可重复调用;C_Player 不可用或座位非法时【保持原值不写】—— +// 绝不用 -1 覆盖已同步好的正确座位。 +// --------------------------------------------------------------------------- +SubGameHooks._syncMySeat = function () { + if (typeof C_Player === 'undefined' || !C_Player) { return; } + var seat = C_Player.seat; + if (seat !== 0 && seat !== 1 && seat !== 2) { return; } + EQW_GameState.room.mySeat = seat; +}; + +// 对局推送。_msg 是完整包 {app, route, rpc, data} +// (平台 12_Logic.js 按 route 分流,只有子游戏路由 erqiwang 才到这里) +SubGameHooks._ReceiveData = function (_msg) { + if (!_msg) { return; } + EQW_Dispatcher.dispatch({ rpc: _msg.rpc, data: _msg.data }); +}; + +// 开局。可能差异化下发(sendtype:1 + seatlist[]) +// 【必须先同步座位】handleStartWar 要按 mySeat 在 seatlist[] 里认领自己那份 +SubGameHooks.StartWar = function (_msg) { + SubGameHooks._syncMySeat(); + EQW_ResyncHandler.handleStartWar(_msg); +}; + +// 断线重连:拿到 get_deskinfo 的全量快照 +// (handleDeskinfo 内的 GameState.reset() 不清 room.mySeat,此处同步的值不会被冲掉) +SubGameHooks.Reconnect = function (_deskinfo) { + SubGameHooks._syncMySeat(); + EQW_ResyncHandler.handleDeskinfo(_deskinfo); +}; + +// 中途进桌:牌局【已经开始】,但自己走的是「加入房间」而非「重连」这条路径。 +// 平台 07_Desk.js 在 self_join_room 且 battlestate==1 时走 Game_Modify.DeskInfo(_msg.data.deskinfo), +// 与 self_login 的 Game_Modify.Reconnect(_msg.data.deskinfo) 是两条并列分支—— +// 不接这个 hook,整份 deskinfo 会被 02 转发壳静默丢弃(无 hook 即 no-op),界面全空且不报错。 +// +// 【入参与 Reconnect 完全同源、且已按【自己的】座位裁剪】: +// 两条路径的 deskinfo 都出自服务端 server/youle/server_room/class.return.js —— 进房走 +// self_join_room 的 get_deskinfo(o_room, o_player.gameinfo.seat),登录走 self_login 的 +// get_deskinfo(o_player.gameinfo.o_room, o_player.gameinfo.seat),同一个函数、同一个 +// 「本人座位」入参。get_deskinfo 内部的可见性裁剪(底牌/埋牌底牌只给庄家、亮牌只给闲家、 +// mustcard 只给轮到的那家)因此对中途进桌一样生效,不会把别人的视角发过来。 +// 故直接复用同一条重画路径(前端 04 §3「重连 = 重画」),不为它单写一套渲染。 +SubGameHooks.DeskInfo = function (_deskinfo) { + SubGameHooks._syncMySeat(); + EQW_ResyncHandler.handleDeskinfo(_deskinfo); +}; + +// 重连但未开战:没有牌局数据,无需重画对局 +SubGameHooks.ReconnectNoMakewar = function () { + SubGameHooks._syncMySeat(); + console.log('[EQW] 重连(未开战)'); +}; + +// 解散。_msg 即 Desk.deskfree,可能为 null(首局发牌前解散) +SubGameHooks.Free = function (_msg) { + EQW_ResultHandler.handleFree(_msg); +}; + +// 房间信息(房号 / 总局数 / roomtype 位串) +// 平台三条进桌路径(07_Desk.js:358 登录 / 510 建房 / 631 进房)都在 C_Player.SetSeat() 之后、 +// Reconnect / StartWar / DeskInfo 之前调它——是覆盖面最广的一个同步点 +SubGameHooks.setRoomDes = function (roomcode, asetcount, roomtype) { + SubGameHooks._syncMySeat(); + EQW_ResyncHandler.handleSetRoomDes(roomcode, asetcount, roomtype); +}; + +// 换座(平台 07_Desk.js:186-191 已先把 C_Player.seat 改成新座位再回调这里)。 +// 这是唯一「座位会中途变化、且不重走 setRoomDes」的路径,漏掉这里 mySeat 会静默过期 +SubGameHooks.changeSeat = function (seat1, seat2) { + SubGameHooks._syncMySeat(); +}; + +// 启动编排。B 阶段只记下自己的座位;UI 初始化留给 C 阶段 +// 注意:此刻 C_Player 通常还没创建(见 _syncMySeat 上方注释),座位靠后续入口补齐 +SubGameHooks.appStart = function () { + SubGameHooks._syncMySeat(); + if (typeof Desk !== 'undefined' && Desk.roomtype) { + EQW_GameState.room.options = EQW_RoomOptions_Parse.parse(Desk.roomtype); + } +}; + window.SubGameHooks = SubGameHooks; if (typeof module !== 'undefined' && module.exports) { module.exports = SubGameHooks; } diff --git a/client/js/01_SubGame/codes/net/Dispatcher.js b/client/js/01_SubGame/codes/net/Dispatcher.js new file mode 100644 index 0000000..2f8c38c --- /dev/null +++ b/client/js/01_SubGame/codes/net/Dispatcher.js @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_Dispatcher: 唯一收包入口 + 纯路由表 //////////// +/////////////////////////////////////////////////////////////// +// 前端 04 §2:收包统一分发,一 rpc 一处理器。 +// 【本文件只分发,不写业务】——新增一种服务端推送 = 注册一条 rpc → handler。 +// +// 判定顺序(关键):先看 data.success,为 false 一律交 FailHandler、不进业务 handler。 +// 因为协议 §0.2 规定失败回包的 rpc 与【请求】同名(chupai 失败回 chupai, +// 而成功走 chupai1/2/3),不先分流的话每个业务 handler 都得自己判一遍。 +var EQW_Dispatcher = EQW_Dispatcher || { + + _handlers: {}, + + //注册一条 rpc → handler + register: function (rpc, handler) { + if (typeof rpc !== 'string' || typeof handler !== 'function') { + throw new Error('[EQW_Dispatcher] register 参数非法: ' + rpc); + } + if (this._handlers.hasOwnProperty(rpc)) { + throw new Error('[EQW_Dispatcher] rpc 重复注册: ' + rpc); + } + this._handlers[rpc] = handler; + }, + + hasHandler: function (rpc) { + return this._handlers.hasOwnProperty(rpc); + }, + + //唯一收包入口。msg = { rpc, data } + dispatch: function (msg) { + if (!msg || typeof msg.rpc !== 'string') { + console.warn('[EQW_Dispatcher] 畸形数据包,已忽略'); + return; + } + var data = msg.data || {}; + + //成败只认 data.success(前端 04 §4):失败包一律先分流 + if (data.success === false) { + EQW_FailHandler.handle(msg.rpc, data); + return; + } + + var h = this._handlers[msg.rpc]; + if (h) { + h(data); + } else { + //平台日后新增推送不该让前端崩 + console.warn('[EQW_Dispatcher] 未处理的 rpc: ' + msg.rpc); + } + } +}; + +//—— 路由表:一 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); }); diff --git a/client/js/01_SubGame/codes/net/Rpc.js b/client/js/01_SubGame/codes/net/Rpc.js new file mode 100644 index 0000000..1de1348 --- /dev/null +++ b/client/js/01_SubGame/codes/net/Rpc.js @@ -0,0 +1,123 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_Rpc: 语义化发包(一操作一方法)///////////////// +/////////////////////////////////////////////////////////////// +// 前端 04 §1:业务不直接拼包,走两层封装—— +// 业务 → EQW_Rpc.xxx(意图) → RpcHelper(注入平台字段)→ 引擎发送 +// +// 【route 必须是 erqiwang】RpcHelper.sendGameRpc 预设 route="room"(平台房间模块), +// 用它包会被路由到平台、永远到不了子游戏 mod.js,而且不会有任何报错。 +// +// 【请求包只带意图,不带结论】(前端 04 §1 / 服务端 04 §8) +// 可以带:操作类型、目标标识(牌 id / 花色 / 提示类型)、座位号(仅供一致性校验)。 +// 禁止带:分数、番数、判定结果、阶段推进指令、nextSeat、handCards。 +// 前端 shared/ 的计算只用于本地提示与预校验,【结果不回传】。 +// 一句话:前端能自己推出来的东西,就是玩家能改的东西——这是防作弊的根本。 +// +// 本文件只做【形状】校验(牌 id 范围/去重/张数),不做【规则】校验 +// (这手牌能不能出、叫分够不够低由服务端裁定)。形状不合法就 fail-fast 抛错, +// 不发出残缺包——服务端会按 PARAM 拒绝,但让错误暴露在最近处更省事。 +var EQW_Rpc = EQW_Rpc || { + + APP: 'youle', + ROUTE: 'erqiwang', + + //—— 内部:取自己的座位(fail-fast)—— + //room.mySeat 由 SubGameHooks._syncMySeat 从平台 C_Player.seat 同步,初始值是 -1。 + //【为什么不能透传】seat:-1 发出去,服务端 check_player 必然返回 falsy → 每个操作都回 ERR.PLAYER, + //叫分/选主/埋牌/出牌全部发不出去,而前端一声不吭。无声透传正是这类缺陷能长期潜伏的原因, + //故与本文件其余形状校验一样在最近处显式失败。 + _seat: function () { + var seat = EQW_GameState.room.mySeat; + if (seat !== 0 && seat !== 1 && seat !== 2) { + throw new Error('[EQW_Rpc] 座位未同步: ' + seat + + '(须为 0/1/2;检查 SubGameHooks._syncMySeat 是否已由平台入口调用)'); + } + return seat; + }, + + //—— 内部:发送 —— + _send: function (rpc, data) { + var pack = { seat: this._seat() }; + var key; + for (key in data) { + if (data.hasOwnProperty(key)) { + pack[key] = data[key]; + } + } + RpcHelper.sendRpc(this.APP, this.ROUTE, rpc, pack); + }, + + //—— 内部:牌 id 列表形状校验(协议 §0.2)—— + //必须是非空数组,元素是 0–107 的整数,互不重复 + _checkCards: function (cards, expectCount) { + if (Object.prototype.toString.call(cards) !== '[object Array]' || cards.length === 0) { + throw new Error('[EQW_Rpc] cards 必须是非空数组'); + } + if (typeof expectCount === 'number' && cards.length !== expectCount) { + throw new Error('[EQW_Rpc] cards 张数应为 ' + expectCount + ',实际 ' + cards.length); + } + var seen = {}; + for (var i = 0; i < cards.length; i++) { + var c = cards[i]; + if (typeof c !== 'number' || c !== Math.floor(c) || c < 0 || c > 107) { + throw new Error('[EQW_Rpc] 非法牌 id: ' + c + '(须为 0–107 的整数)'); + } + if (seen[c]) { throw new Error('[EQW_Rpc] 牌 id 重复: ' + c); } + seen[c] = true; + } + }, + + //—— 叫分(call: 5–70 的 5 的倍数;0 = 不叫)—— + jiaofen: function (call) { + if (typeof call !== 'number' || call !== Math.floor(call)) { + throw new Error('[EQW_Rpc] call 必须是整数'); + } + if (call !== 0 && (call < 5 || call > 70 || call % 5 !== 0)) { + throw new Error('[EQW_Rpc] 非法叫分: ' + call + '(须为 0 或 5–70 的 5 的倍数)'); + } + this._send('jiaofen', { call: call }); + }, + + //—— 投降(仅 70 分坐庄、选主阶段;合法性由服务端裁定)—— + touxiang: function () { + this._send('touxiang', {}); + }, + + //—— 选主(flower: 1方块 2梅花 3红心 4黑桃)—— + xuanzhu: function (flower) { + if (typeof flower !== 'number' || flower < 1 || flower > 4) { + throw new Error('[EQW_Rpc] 非法花色: ' + flower + '(须为 1–4)'); + } + this._send('xuanzhu', { flower: flower }); + }, + + //—— 埋牌(恒 8 张)—— + maipai: function (cards) { + this._checkCards(cards, 8); + this._send('maipai', { cards: cards }); + }, + + //—— 出牌 —— + chupai: function (cards) { + this._checkCards(cards); + this._send('chupai', { cards: cards }); + }, + + //—— 明牌(查看他家未出主牌)—— + mingpai: function () { + this._send('mingpai', {}); + }, + + //—— 出牌提示(tip: 1踩 2没分 3有分)—— + tishi: function (tip) { + if (tip !== 1 && tip !== 2 && tip !== 3) { + throw new Error('[EQW_Rpc] 非法提示类型: ' + tip + '(须为 1踩 2没分 3有分)'); + } + this._send('tishi', { tip: tip }); + }, + + //—— 准备 —— + zhunbei: function () { + this._send('zhunbei', {}); + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/BuryHandler.js b/client/js/01_SubGame/codes/net/handlers/BuryHandler.js new file mode 100644 index 0000000..d7afcb6 --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/BuryHandler.js @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_BuryHandler: 埋牌(maipai)//////////////////// +/////////////////////////////////////////////////////////////// +// 协议 §9:cards 埋牌后手牌(仅庄家) / burycards 埋牌底牌(仅庄家) / +// step 阶段(=5 出牌) / seat 控制权=首出者(必为庄家) / countdown 出牌倒计时 / +// seatlist 三家牌况(仅可查牌模式) / +// liangpai 亮牌(仅闲家 + 可查牌模式 + 庄家固定主牌达门槛) +// +// 注意 burycards(埋牌底牌,庄家埋下的 8 张)与 bottomcards(底牌,发牌留桌的 8 张) +// 是两批不同的牌,协议 §0.0 与验收清单专门警告过别写反。 +// +// 【seatlist 此刻是全初值,但必须接】:埋牌完成时这张表刚初始化 +// (每家 [[0,0],[0,0],[0,0],[0,0],[-1,-1]]),界面上看不出差别——但「埋牌完成 → 庄家首出」 +// 这段窗口内重连拿到的 PushCards.seatlist 就是它,漏接会让增量路径此刻为空、重连路径却有表。 +// +// 【bottomcards 到此失效,要清空】:底牌在上庄时已翻给玩家看并并入手牌,埋牌完成后 +// 只剩「埋牌底牌 burycards」有意义;deskinfo 的 PushCards 不带 bottomcards,重连重建恒为 []。 +// +// 【playproc 是服务端的「本轮进行态」,原样拷贝】:埋牌完成时服务端已就地开好 round-1 +// 的进行态(庄家首出、桌面全空),恒有、三家同值,与 chupai1/2/3.playproc / 重连包 +// PushCards.playproc 同源同结构(服务端同一个 get_playproc())。没有它,「埋牌完成 → +// 庄家首出」这段窗口增量路径 table.playproc 会停在上一阶段的 null,与重连路径不一致。 +var EQW_BuryHandler = EQW_BuryHandler || { + + handle: function (data) { + if (!data.hasOwnProperty('seat')) { + console.error('[EQW_BuryHandler] maipai 缺 seat,已跳过该包'); + return; + } + + //庄家才有的两项,闲家缺字段时保持原值、不抹手牌 + EQW_GameState._apply(EQW_GameState.my, data, { + cards: 'cards', + buryCards: 'burycards' + }); + //底牌已并入手牌、埋牌完成即失效(重连的 PushCards 不带它),显式清空保持两条路径一致 + EQW_GameState.my.bottomCards = []; + //三家牌况(仅可查牌模式)、闲家才可能有的亮牌、本轮进行态(恒有,见上方注释) + EQW_GameState._apply(EQW_GameState.table, data, { + seatlist: 'seatlist', + liangpai: 'liangpai', + playproc: 'playproc' + }); + //阶段只读包字段(服务端权威),不按 rpc 名硬编码 step=5 + EQW_GameState._apply(EQW_GameState.aset, data, { step: 'step' }); + //控制权:本包的 seat 就是「轮到谁」(即将首出的庄家,服务端取自 playproc.currseat) + EQW_GameState._apply(EQW_GameState.turn, data, { + seat: 'seat', + countdown: 'countdown' + }); + + EventBus.emit(EQW_Events.EQW_BURY_DONE); + if (data.hasOwnProperty('cards')) { EventBus.emit(EQW_Events.EQW_HAND_CHANGED); } + EventBus.emit(EQW_Events.EQW_TURN_CHANGED); + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/CallHandler.js b/client/js/01_SubGame/codes/net/handlers/CallHandler.js new file mode 100644 index 0000000..6ae54e2 --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/CallHandler.js @@ -0,0 +1,78 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_CallHandler: 叫分(jiaofen)与上庄(shangzhuang) +/////////////////////////////////////////////////////////////// +// 协议 §3 jiaofen:seat 叫分者 / call 分数(0=不叫) / currcall 当前叫到的分 / +// multiple 该叫分对应基础子数 / step 阶段(仍=1) / +// nextseat 下一个叫分者(控制权) / countdown +// 协议 §4 shangzhuang:seat / call / banker / grade 庄家叫分 / multiple / +// step 阶段(=2 选主/投降) / nextseat 控制权(=庄家) / +// bottomcards 底牌(庄家恒有,闲家仅 70 分时有) / ancard3s 开底标志 / +// cards 摸底后的 36 张(仅庄家) / countdown 选主倒计时 / +// touxiang 是否允许投降 / curmultiple 当前抓分倍数(带符号) +// +// 【shangzhuang 的 seat 与 nextseat 别混用】seat 是【最后一个叫分者】,nextseat 才是 +// 「轮到谁」(选主/投降只由庄家做,服务端 mod.xuanzhu/mod.touxiang 以 banker 为准)。 +// 曾经这里写 turn.seat = aset.banker 自己推——那是把「选主 = 庄家」这条规则搬到了前端; +// 同一条规则在 ResyncHandler 里还有一份,同一份数据两个写入处(SSOT)。现一律读包字段。 +var EQW_CallHandler = EQW_CallHandler || { + + handleJiaofen: function (data) { + if (!data.hasOwnProperty('seat')) { + console.error('[EQW_CallHandler] jiaofen 缺 seat,已跳过该包'); + return; + } + + //记录这一家叫了多少:0 = 不叫,与「还没叫」(null) 区分 + if (data.hasOwnProperty('call') && data.seat >= 0 && data.seat < 3) { + EQW_GameState.call.calls[data.seat] = data.call; + } + EQW_GameState._apply(EQW_GameState.call, data, { currcall: 'currcall' }); + EQW_GameState._apply(EQW_GameState.aset, data, { + multiple: 'multiple', + step: 'step' + }); + EQW_GameState._apply(EQW_GameState.turn, data, { + seat: 'nextseat', + countdown: 'countdown' + }); + + EventBus.emit(EQW_Events.EQW_CALL_CHANGED); + EventBus.emit(EQW_Events.EQW_TURN_CHANGED); + }, + + handleShangzhuang: function (data) { + if (!data.hasOwnProperty('banker')) { + console.error('[EQW_CallHandler] shangzhuang 缺 banker,已跳过该包'); + return; + } + + EQW_GameState._apply(EQW_GameState.aset, data, { + banker: 'banker', + call: 'grade', //协议里庄家叫分叫 grade + multiple: 'multiple', + touxiang: 'touxiang', + curmultiple:'curmultiple', + step: 'step' //阶段服务端权威给出,不按 rpc 名硬编码 + }); + EQW_GameState._apply(EQW_GameState.table, data, { ancard3s: 'ancard3s' }); + //底牌与摸底后的手牌只有庄家(及 70 分时的闲家)才有,缺就不写、不抹已有值 + EQW_GameState._apply(EQW_GameState.my, data, { + bottomCards: 'bottomcards', + cards: 'cards' + }); + //控制权:读包里的 nextseat(服务端权威),不用本地的 banker 反推 + EQW_GameState._apply(EQW_GameState.turn, data, { + seat: 'nextseat', + countdown: 'countdown' + }); + + //叫分过程量(currcall/calls)step2 起无语义:deskinfo 从 ChooseMain 起就不再带 CallRun, + //这里清成 reset() 的默认值,避免前端留着一份 deskinfo 不会再给的数据(视图=f(快照)) + EQW_GameState.call.currcall = 0; + EQW_GameState.call.calls = [null, null, null]; + + EventBus.emit(EQW_Events.EQW_BANKER_SET); + if (data.hasOwnProperty('cards')) { EventBus.emit(EQW_Events.EQW_HAND_CHANGED); } + EventBus.emit(EQW_Events.EQW_TURN_CHANGED); + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/DealHandler.js b/client/js/01_SubGame/codes/net/handlers/DealHandler.js new file mode 100644 index 0000000..2aa334c --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/DealHandler.js @@ -0,0 +1,37 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_DealHandler: 发牌(fapai)////////////////////// +/////////////////////////////////////////////////////////////// +// 协议 §1:asetidx 当前局数 / asetcount 总局数 / cards 自己的 28 张 / +// step 阶段(=1 叫分) / seat 当前等待叫分者 / countdown 叫分倒计时 +// +// 【step 只读包字段,不按 rpc 硬编码】阶段与控制权由服务端唯一维护、逐包显式下发 +//(前端红线「数据驱动、前端无对局状态机」/ 服务端红线「任何状态变更都要有包承载」)。 +// 曾经这里写死 aset.step = 1:服务端一旦插入中间阶段,前端不会红、只会静默画错。 +// +// fapai 是一局的起点,先 reset 清掉上一局的残留(reset 保留 mySeat 与 options)。 +var EQW_DealHandler = EQW_DealHandler || { + + handle: function (data) { + if (!data.hasOwnProperty('cards')) { + console.error('[EQW_DealHandler] fapai 缺 cards,已跳过该包'); + return; + } + + EQW_GameState.reset(); + + EQW_GameState._apply(EQW_GameState.room, data, { + asetIdx: 'asetidx', + asetCount: 'asetcount' + }); + EQW_GameState._apply(EQW_GameState.my, data, { cards: 'cards' }); + EQW_GameState._apply(EQW_GameState.aset, data, { step: 'step' }); + EQW_GameState._apply(EQW_GameState.turn, data, { + seat: 'seat', + countdown: 'countdown' + }); + + EventBus.emit(EQW_Events.EQW_RESET); + EventBus.emit(EQW_Events.EQW_HAND_CHANGED); + EventBus.emit(EQW_Events.EQW_TURN_CHANGED); + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/FailHandler.js b/client/js/01_SubGame/codes/net/handlers/FailHandler.js new file mode 100644 index 0000000..634225d --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/FailHandler.js @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_FailHandler: 失败回包 ////////////////////////// +/////////////////////////////////////////////////////////////// +// 协议 §0.2:服务端受理请求时任一校验不通过,回一个【rpc 与请求同名】的失败包, +// data 只含 success:false 与 errcode,且只回给请求者。 +// +// errcode(服务端 youle_erqiwang.ERR): +// 1 PLAYER 玩家/房间/座位校验不通过 2 NODESK 牌桌或牌局不存在 +// 3 STEP 当前阶段不允许该操作 4 SEAT 位置不符或还没轮到 +// 5 PARAM 参数非法 6 RULE 规则不允许 +// +// 失败信息是【一次性】的,不属于对局状态,故不进 GameState,只随事件带出去。 +var EQW_FailHandler = EQW_FailHandler || { + + ERR: { + PLAYER: 1, NODESK: 2, STEP: 3, SEAT: 4, PARAM: 5, RULE: 6 + }, + + handle: function (rpc, data) { + var errcode = (data && typeof data.errcode === 'number') ? data.errcode : 0; + console.warn('[EQW] 请求失败: rpc=' + rpc + ' errcode=' + errcode); + EventBus.emit(EQW_Events.EQW_RPC_FAILED, { rpc: rpc, errcode: errcode }); + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/MainHandler.js b/client/js/01_SubGame/codes/net/handlers/MainHandler.js new file mode 100644 index 0000000..445c468 --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/MainHandler.js @@ -0,0 +1,39 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_MainHandler: 选主(xuanzhu)/////////////////// +/////////////////////////////////////////////////////////////// +// 协议 §7:banker 庄家 / flower 主牌花色(1方块 2梅花 3红心 4黑桃) / +// step 阶段(=3 埋牌) / nextseat 控制权(=庄家,埋牌由庄家做) / +// countdown 埋牌倒计时 / cards 选主后自己手上的牌 +// +// 【step 与 nextseat 只读包字段】曾经这里写死 step=3 且 turn.seat = aset.banker: +// 阶段与「埋牌 = 庄家」两条规则都被搬到了前端(前端红线「数据驱动、前端无对局状态机」)。 +// +// 选主后正 2 / 正 7 升格、主花色普通牌并入主牌段(design §3),手牌需整体重排—— +// 但【重排是渲染时的事】,由 core/CardOrder 现算,不在这里落地成状态。 +// +// 投降(touxiang)没有成功推送:点投降后服务端直接结算,前端收到的是 jiesuan。 +// 失败时才回一个同名的 touxiang 失败包,由 FailHandler 统一处理。 +var EQW_MainHandler = EQW_MainHandler || { + + handleXuanzhu: function (data) { + if (!data.hasOwnProperty('flower')) { + console.error('[EQW_MainHandler] xuanzhu 缺 flower,已跳过该包'); + return; + } + + EQW_GameState._apply(EQW_GameState.aset, data, { + banker: 'banker', + flower: 'flower', + step: 'step' + }); + EQW_GameState._apply(EQW_GameState.my, data, { cards: 'cards' }); + EQW_GameState._apply(EQW_GameState.turn, data, { + seat: 'nextseat', + countdown: 'countdown' + }); + + EventBus.emit(EQW_Events.EQW_MAIN_SET); + if (data.hasOwnProperty('cards')) { EventBus.emit(EQW_Events.EQW_HAND_CHANGED); } + EventBus.emit(EQW_Events.EQW_TURN_CHANGED); + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/PlayHandler.js b/client/js/01_SubGame/codes/net/handlers/PlayHandler.js new file mode 100644 index 0000000..41aa649 --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/PlayHandler.js @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_PlayHandler: 出牌(chupai1 / 2 / 3)/////////// +/////////////////////////////////////////////////////////////// +// 协议 §11–§13。三个包字段不完全相同: +// chupai1 首家独有:shuaicuo 甩错标志 / count 出牌数量 / flower 出牌花色 / +// cardtype 牌型 / shuai 甩牌分量构成 +// chupai3 末家独有:maxseat 本轮谁最大 / grade 本轮闲家得分 +// 共有:seat / cards / seatlist(仅可查牌) / countdown / baozhu / nextseat / +// cardsinhand(仅出牌者自己有) / curmultiple(1 和 3 有) +// mustcard:三个包都可能带,但只发给 nextseat 那一家(其余两家无此属性) +// +// 【cardsinhand 只有出牌者自己有】:收到就更新自己的手牌;别人出牌时没这个字段, +// 绝不能抹掉自己的手牌——这正是「缺字段不兜底」要防的。 +// +// 【mustcard 没有就要清空】:它是「本轮跟牌的必出牌」,只对当前这一轮有效。 +// 上一轮的建议残留下来会让界面自动选中错误的牌。 +// +// 【playproc 是服务端的「本轮进行态」,原样拷贝】:它的结构是 +// {round, start, currseat, startcount, startflower, starttype, maxseat, maxcard, cards, shuai_demand} +// (协议 §11),与重连包 PushCards.playproc 同源同结构(服务端同一个 get_playproc())。 +// 注意它的 cards 是【本轮三家各自出的牌、下标 = 座位】,与本包的 cards(这一手出的牌) +// 语义相反,绝不能混写。轮次/最大者由服务端给,前端不累积、不推导。 +// +// 【这一手的信息走事件载荷,不进 GameState】:谁出了什么、几张、什么牌型是【一次性表现】 +// (落牌动画),不是对局状态;deskinfo 也不下发它。放进 GameState 会立刻制造一处 +// 增量/全量不一致。故随 EQW_CARD_PLAYED 事件下发给 UI。 +// 【本墩结果同理】:chupai3 的 maxseat / grade(谁最大、这墩闲家得几分)也是一次性表现, +// 随 EQW_TRICK_END 事件载荷下发,同样不进 GameState。 +var EQW_PlayHandler = EQW_PlayHandler || { + + //order: 1/2/3,对应 chupai1/2/3 + handle: function (data, order) { + if (!data.hasOwnProperty('seat') || !data.hasOwnProperty('cards')) { + console.error('[EQW_PlayHandler] chupai' + order + ' 缺 seat 或 cards,已跳过该包'); + return; + } + + //—— 本轮进行态:服务端权威快照,原样拷贝,前端零推导 —— + EQW_GameState._apply(EQW_GameState.table, data, { playproc: 'playproc' }); + + //—— 出牌历史:把这一手记进本轮(chupai1 = 一轮的第一手,开新的一轮)—— + EQW_GameState.pushPlay(data.seat, data.cards, order === 1); + + //—— 自己的手牌:只有出牌者本人才收到 cardsinhand —— + EQW_GameState._apply(EQW_GameState.my, data, { cards: 'cardsinhand' }); + + //—— 必出牌:本轮有效,没给就清空 —— + if (data.hasOwnProperty('mustcard')) { + EQW_GameState.my.mustCard = data.mustcard; + } else { + EQW_GameState.my.mustCard = []; + } + + //—— 牌况与报无主(仅可查牌模式下发)—— + EQW_GameState._apply(EQW_GameState.table, data, { seatlist: 'seatlist' }); + EQW_GameState._apply(EQW_GameState.aset, data, { + baozhu: 'baozhu', + curmultiple: 'curmultiple' + }); + + //—— 控制权与倒计时 —— + EQW_GameState._apply(EQW_GameState.turn, data, { + seat: 'nextseat', + countdown: 'countdown' + }); + + EventBus.emit(EQW_Events.EQW_CARD_PLAYED, this._playPayload(data, order)); + if (data.hasOwnProperty('cardsinhand')) { EventBus.emit(EQW_Events.EQW_HAND_CHANGED); } + + //—— 末家出完:本轮结束 —— + //【本墩结果走事件载荷,不进 GameState】谁最大、这墩闲家得了几分,是【一次性表现】 + //(收牌动画、得分飘字),与 EQW_CARD_PLAYED 同类;任何阶段的 deskinfo 都不下发它, + //写进 GameState 就多出一处「只活在前端、服务端不知道」的对局态——出牌中途断线重连 + //即归零,违反前端红线「丢弃 this.data、仅凭最近一次服务端快照重画,界面必须一致」。 + //小局累计捡分 aset.grade 不同:它有服务端来源(PushCards.grade / chupai3.grade), + //属于镜像量,仍留在 GameState。 + if (order === 3) { + //本轮闲家得分累加进小局捡分;没得分时不带 grade,累计值保持不变 + if (data.hasOwnProperty('grade')) { + EQW_GameState.aset.grade = EQW_GameState.aset.grade + data.grade; + } + EventBus.emit(EQW_Events.EQW_TRICK_END, this._trickPayload(data)); + } + + EventBus.emit(EQW_Events.EQW_TURN_CHANGED); + }, + + //EQW_CARD_PLAYED 的事件载荷:这一手的一次性信息(落牌动画用),不落 GameState。 + //【缺字段不兜底】:包里没有的键就不带(count/flower/cardtype 只有 chupai1 有, + //shuai 仅合法甩牌时有,shuaicuo 仅甩错时有),订阅方按 hasOwnProperty 判断。 + _playPayload: function (data, order) { + var payload = { order: order, seat: data.seat, cards: data.cards }; + var keys = ['count', 'flower', 'cardtype', 'shuai', 'shuaicuo']; + for (var i = 0; i < keys.length; i++) { + if (data.hasOwnProperty(keys[i])) { payload[keys[i]] = data[keys[i]]; } + } + return payload; + }, + + //EQW_TRICK_END 的事件载荷:这一墩的一次性结果(收牌动画、得分飘字用),不落 GameState。 + //【缺字段不兜底】grade 只在这墩被闲家收走且确实有分时才有,包里没有就不带这个键。 + //与 ResultHandler 为收尾墩发的那份载荷字段一致(收尾墩不发 chupai3,整包换成 jiesuan)。 + _trickPayload: function (data) { + var payload = { seat: data.seat, cards: data.cards }; + if (data.hasOwnProperty('maxseat')) { payload.maxseat = data.maxseat; } + if (data.hasOwnProperty('grade')) { payload.grade = data.grade; } + return payload; + } +}; 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/js/01_SubGame/codes/net/handlers/ResultHandler.js b/client/js/01_SubGame/codes/net/handlers/ResultHandler.js new file mode 100644 index 0000000..5a989e8 --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/ResultHandler.js @@ -0,0 +1,121 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_ResultHandler: 结算(jiesuan)与解散(Free)/// +/////////////////////////////////////////////////////////////// +// 协议 §14:结算包由 chupai + bottom + aset 拼装,末局再加 account。 +// 三种来源的组成不同: +// 正常出牌结算:chupai + bottom + aset(末局加 account) +// 投降结算: 只有 aset(末局加 account),无 chupai、无 bottom +// 解散结算: 只有 aset + account,且【走另一个入口】,见下 +// +// 【解散的特殊性】(协议 §14.1,已对平台代码核实): +// 解散包的 route 是平台的 "room",平台 12_Logic.js 按 route 分流时把它交给 +// Net[rpc] 而【不是】Game_Modify._ReceiveData——所以它根本进不了子游戏的分发表。 +// 平台另有专门入口:07_Desk.js 的 Game_Modify.Free(Desk.deskfree)。 +// 两个后果: +// 1. 取值路径比协议文档少一层——平台已把 deskfree 取出来传进来, +// 所以是 deskfree.data.aset,不是 data.deskfree.data.aset; +// 2. 参数可能是 null——解散若发生在开战后、首局发牌前,服务端 get_disbandRoom +// 返回 null,平台走「不带 deskfree」分支。这时只做房间收尾、不弹结算。 +var EQW_ResultHandler = EQW_ResultHandler || { + + handleJiesuan: function (data) { + if (!data.hasOwnProperty('aset')) { + console.error('[EQW_ResultHandler] jiesuan 缺 aset,已跳过该包'); + return; + } + this._applyResult(data); + }, + + //平台解散入口。deskfree = { rpc:"jiesuan", data:{ success, aset, account } },可能为 null + handleFree: function (deskfree) { + if (!deskfree || !deskfree.data) { + //首局发牌前解散:没有结算数据,只做房间收尾,不弹结算面板 + console.warn('[EQW_ResultHandler] 解散包无 deskfree 数据(首局发牌前解散)'); + return; + } + var d = deskfree.data; + if (!d.hasOwnProperty('aset')) { + console.error('[EQW_ResultHandler] 解散包缺 aset,已跳过'); + return; + } + this._applyResult(d); + }, + + //三种来源共用的落地逻辑:有哪组就写哪组,缺的保持 null + _applyResult: function (d) { + //—— 收尾轮的第三手:牌局在最后一手打完时【不发 chupai3】,整包换成 jiesuan(协议 §13/§14)—— + //夹具实跑一局的包数就是证据:chupai1×28 / chupai2×28 / chupai3×27。 + //只在 chupai3 归档会永远丢掉最后一轮,故在这里补齐——它必是本轮第三手,落进当前这一轮。 + //jiesuan.chupai 的字段与 chupai3 不同:只有 seat / cards / maxseat(+ 有分时的 grade), + //没有 order / playproc / cardsinhand。 + //归档随后会被下面的结算收敛清掉(deskinfo step6 不带 PushCards),但 emit 是同步的: + //落牌与收牌表现在清空之前就已取到完整的收尾轮。 + //【本墩结果只走事件载荷、不进 GameState】:谁最大、这墩得几分是一次性表现, + //deskinfo 的 Balance 不带它(它属于「出牌过程」而非结算数据),写进 GameState + //就会多出一处服务端不知道的对局态。收尾墩与 chupai3 走同一对事件、同一份载荷字段。 + if (d.chupai && d.chupai.hasOwnProperty('seat') && d.chupai.hasOwnProperty('cards')) { + EQW_GameState.pushPlay(d.chupai.seat, d.chupai.cards, false); + var lastPlay = { seat: d.chupai.seat, cards: d.chupai.cards }; + if (d.chupai.hasOwnProperty('maxseat')) { lastPlay.maxseat = d.chupai.maxseat; } + if (d.chupai.hasOwnProperty('grade')) { lastPlay.grade = d.chupai.grade; } + EventBus.emit(EQW_Events.EQW_CARD_PLAYED, lastPlay); + EventBus.emit(EQW_Events.EQW_TRICK_END, lastPlay); + } + + //结算是"清空过程量、只留结算数据"的快照点:deskinfo step6 只带 Balance(=aset), + //其余分组一律回到 reset() 默认值——jiesuan 也要收敛到同一状态,否则重连前后画面不一致 + //(清单以 ResyncHandler.handleDeskinfo 在 step6 时实际重建出的字段集为准,不多清不少清)。 + EQW_GameState.aset.banker = -1; + EQW_GameState.aset.call = -1; + EQW_GameState.aset.multiple = 0; + EQW_GameState.aset.flower = 0; + EQW_GameState.aset.curmultiple = 0; + EQW_GameState.aset.grade = 0; + EQW_GameState.aset.baozhu = 0; + EQW_GameState.aset.touxiang = 0; + EQW_GameState.turn.seat = -1; + EQW_GameState.turn.countdown = 0; + EQW_GameState.call.currcall = 0; + EQW_GameState.call.calls = [null, null, null]; + //末轮若由 jiesuan.chupai 带出(无 cardsinhand),出牌者自己的手牌不会被清空,这里显式清掉, + //不留幽灵牌——deskinfo 在结算阶段的 MyCards 恒为 [] + EQW_GameState.my.cards = []; + EQW_GameState.my.mustCard = []; + EQW_GameState.my.bottomCards = []; + EQW_GameState.my.buryCards = []; + EQW_GameState.table.ancard3s = 0; + EQW_GameState.table.playproc = null; + EQW_GameState.table.pushlist = []; + EQW_GameState.table.seatlist = []; + EQW_GameState.table.liangpai = null; + EQW_GameState.table.mingpai = null; + + //【不写 chupai】收尾墩已在上面随事件载荷发走;deskinfo step6 的 Balance 只有 + //aset / bottom / account 三份,多存一个 chupai 就是增量路径独有的私有对局态 + EQW_GameState._apply(EQW_GameState.result, d, { + bottom: 'bottom', + aset: 'aset', + account: 'account' + }); + //阶段只读包字段:jiesuan 的 step 由服务端 get_paiju_account 统一给出(三种结算来源恒为 6), + //前端不按 rpc 名硬编码——解散结算走的是另一条投递路径,同样带着这个字段 + EQW_GameState._apply(EQW_GameState.aset, d, { step: 'step' }); + + //三家总积分:jiesuan 的 aset.seatlist[i].score 就是"累积得分"(协议 §14), + //与 deskinfo.PlayerInfo 同源同值——deskinfo 恒有的 PlayerInfo 在增量路径上没有对应来源, + //只能从这里回填,否则一局中段(fapai 后至 jiesuan 前)room.playerScores 永远追不上服务端 + if (d.aset && Object.prototype.toString.call(d.aset.seatlist) === '[object Array]') { + var scores = []; + for (var i = 0; i < d.aset.seatlist.length; i++) { + scores.push(d.aset.seatlist[i] && d.aset.seatlist[i].score); + } + EQW_GameState.room.playerScores = scores; + } + + EventBus.emit(EQW_Events.EQW_ASET_RESULT); + //account 只在末局或解散时才有 + if (d.hasOwnProperty('account')) { + EventBus.emit(EQW_Events.EQW_ACCOUNT_RESULT); + } + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/ResyncHandler.js b/client/js/01_SubGame/codes/net/handlers/ResyncHandler.js new file mode 100644 index 0000000..a6f1291 --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/ResyncHandler.js @@ -0,0 +1,159 @@ +/////////////////////////////////////////////////////////////// +////////// 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', + curmultiple: 'curmultiple' //与 shangzhuang 推送同源同值,漏读会让「抓分」角标掉档 + }); + EQW_GameState._apply(EQW_GameState.my, g, { bottomCards: 'bottomcards' }); + //控制权由服务端在分组里显式给出(与 shangzhuang.nextseat 同源同值); + //曾经这里写 turn.seat = aset.banker 自推,与 CallHandler 里那份是同一条规则的第二个写入处 + EQW_GameState._apply(EQW_GameState.turn, g, { + seat: 'seat', + countdown: 'countdown' + }); + }, + + _applyBuryCards: function (g) { + EQW_GameState._apply(EQW_GameState.aset, g, { + banker: 'banker', + call: 'call', + multiple: 'multiple', + flower: 'flower', + curmultiple: 'curmultiple' //同上:一张牌未出时恒为 +3(大光),也必须重建 + }); + EQW_GameState._apply(EQW_GameState.my, g, { bottomCards: 'bottomcards' }); + //控制权同上,读服务端给的 seat(与 xuanzhu.nextseat 同源同值) + EQW_GameState._apply(EQW_GameState.turn, g, { + seat: 'seat', + countdown: 'countdown' + }); + }, + + _applyPushCards: function (g) { + EQW_GameState._apply(EQW_GameState.aset, g, { + banker: 'banker', + call: 'call', + multiple: 'multiple', + flower: 'flower', + grade: 'grade', + curmultiple: 'curmultiple', + //报无主:明牌按钮与余主公示的开关,增量路径由 chupai1/2/3 的 baozhu 维护, + //重连路径必须从 PushCards.baozhu 一起恢复,否则重连后按钮凭空消失 + baozhu: 'baozhu' + }); + 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; + } + }, + + //结算阶段(step6):Balance 与 jiesuan 推送给的是同一份结算数据(服务端同源快照)。 + //bottom(抠底明细)只有正常出牌结算才有、account(大局结算)只有末局/解散才有—— + //缺就不写、保持 reset() 的 null,与 jiesuan 那条路径的取舍完全一致。 + //【为什么必须接】结算面板还开着时断线重连/硬刷新,只恢复 aset 会让抠底明细与末局大结算空白。 + _applyBalance: function (g) { + EQW_GameState._apply(EQW_GameState.result, g, { + aset: 'aset', + bottom: 'bottom', + account: 'account' + }); + } +}; diff --git a/client/js/01_SubGame/codes/state/Events.js b/client/js/01_SubGame/codes/state/Events.js new file mode 100644 index 0000000..93235d3 --- /dev/null +++ b/client/js/01_SubGame/codes/state/Events.js @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_Events: 玩法事件常量 /////////////////////////// +/////////////////////////////////////////////////////////////// +// 前端红线:框架保持游戏中立,玩法专属事件由子游戏追加到 EventBus.Events。 +// +// 【对局状态不进事件载荷】——凡是 GameState 里有的,订阅者一律从 EQW_GameState 读, +// 同一份数据不在事件载荷与状态里各存一份(SSOT)。 +// 例外只有「一次性、不属于对局状态、deskinfo 也不下发」的信息,它们不该进 GameState +// (进了就会立刻制造一处增量/全量不一致),只能随事件载荷走: +// EQW_RPC_FAILED {rpc, errcode} 一次性错误信息 +// EQW_CARD_PLAYED {order, seat, cards, count?, flower?, cardtype?, shuai?, shuaicuo?} +// 这一手的落牌信息(一次性表现)。带 ? 的键【缺字段不兜底】, +// 包里没有就不带;收尾轮由 jiesuan.chupai 带出时只有 {seat, cards, maxseat?} +var EQW_Events = EQW_Events || { + + EQW_RESET: 'eqw.reset', //新一局发牌,全部清场 + EQW_HAND_CHANGED: 'eqw.hand_changed', //自己手牌变化 + EQW_CALL_CHANGED: 'eqw.call_changed', //叫分推进 + EQW_BANKER_SET: 'eqw.banker_set', //上庄 + EQW_MAIN_SET: 'eqw.main_set', //选主 + EQW_BURY_DONE: 'eqw.bury_done', //埋牌完成 + EQW_CARD_PLAYED: 'eqw.card_played', //有人出牌 + EQW_TRICK_END: 'eqw.trick_end', //一墩结束;载荷 {seat,cards,maxseat[,grade]},不落 GameState + EQW_TURN_CHANGED: 'eqw.turn_changed', //控制权或倒计时变化 + EQW_MINGPAI: 'eqw.mingpai', //收到明牌数据 + EQW_TIP: 'eqw.tip', //收到对家提示 + EQW_ASET_RESULT: 'eqw.aset_result', //小局结算 + EQW_ACCOUNT_RESULT: 'eqw.account_result', //大局/解散结算 + EQW_READY_CHANGED: 'eqw.ready_changed', //有人准备 + EQW_RESYNC_ALL: 'eqw.resync_all', //重连/开局全量重画 + EQW_RPC_FAILED: 'eqw.rpc_failed' //失败回包 {rpc, errcode} +}; + +//追加到框架事件总线的事件表(框架不认识玩法事件,由子游戏注册) +if (typeof EventBus !== 'undefined' && EventBus.Events) { + for (var _eqwEvtKey in EQW_Events) { + if (EQW_Events.hasOwnProperty(_eqwEvtKey)) { + EventBus.Events[_eqwEvtKey] = EQW_Events[_eqwEvtKey]; + } + } +} diff --git a/client/js/01_SubGame/codes/state/GameState.js b/client/js/01_SubGame/codes/state/GameState.js new file mode 100644 index 0000000..1523e9e --- /dev/null +++ b/client/js/01_SubGame/codes/state/GameState.js @@ -0,0 +1,136 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_GameState: 服务端快照的镜像(前端 SSOT)//////// +/////////////////////////////////////////////////////////////// +// 前端红线「视图 = f(服务端快照)」的落点:本对象只存【服务端下发过的字段】, +// 不存任何前端算出来的结论。手牌排序、牌面标记、花色统计等由 core/ 的纯函数 +// 在渲染时现算,不落地成状态。 +// +// 三条硬约束: +// 1. 只镜像不派生——存进来的必须是服务端给过的 +// 2. 字段名对齐协议——协议叫 currcall 就叫 currcall,便于逐条核对 +// 3. 缺字段不兜底——包里没有的保持原值,不填默认值掩盖;必需字段缺失 +// 则 console.error 并跳过该包(服务端漏发,修在服务端) +var EQW_GameState = EQW_GameState || { + + //数据分组名(snapshot / reset 按此遍历;新增分组要同步加进来) + _GROUPS: ['room', 'aset', 'turn', 'call', 'my', 'table', 'result'], + + //—— 房间级(跨小局)—— + room: { + asetCount: 0, //总局数 ← fapai.asetcount / deskinfo.count / setRoomDes + asetIdx: 0, //当前第几局 ← fapai.asetidx / deskinfo.idx + mySeat: -1, //自己的座位 ← 平台 C_Player.seat + playerScores: [], //三家总积分 ← deskinfo.PlayerInfo / jiesuan.aset.seatlist[].score,跨局不清 + options: null //roomtype 解析结果 + }, + + //—— 小局级 —— + aset: { + step: 0, //1叫分 2选主/投降 3埋牌 5出牌 6结算 + banker: -1, + call: -1, //庄家叫分 + multiple: 0, //基础子数 + flower: 0, //主牌花色(0 = 未选主) + curmultiple: 0, //当前抓分倍数(带符号,服务端权威,前端不自算) + grade: 0, //闲家已捡分 + baozhu: 0, //是否已有人报无主 + touxiang: 0 //是否允许投降 + }, + + //—— 当前控制权与倒计时(任何阶段都读这里)—— + turn: { + seat: -1, //该谁操作 + countdown: 0 //展示用秒数;服务端无对应定时器,归零【不做任何界面推进】 + }, + + //—— 叫分过程 —— + call: { + currcall: 0, + calls: [null, null, null] //null 未叫 / 0 不叫 / >0 叫了多少 + }, + + //—— 自己 —— + my: { + cards: [], + mustCard: [], //本轮必出牌(服务端建议,只发给轮到的那家) + bottomCards: [], //底牌(庄家恒有;闲家仅 70 分坐庄时有) + buryCards: [] //埋牌底牌(仅庄家) + }, + + //—— 桌面 —— + table: { + ancard3s: 0, //开底标志(70 分坐庄) + playproc: null, + pushlist: [], + seatlist: [], + liangpai: null, + mingpai: null + }, + + //—— 结算 —— + //字段集与 deskinfo step6 的 Balance 严格对齐(aset / bottom / account)。 + //【不存 chupai】收尾墩的「谁最大、得几分」是一次性表现,随 EQW_TRICK_END 事件载荷走, + //任何 deskinfo 都不下发它——存进来就是一处服务端不知道的私有对局态(见 ResultHandler / PlayHandler) + result: { + bottom: null, + aset: null, + account: null + }, + + //新一局开局时清空对局态;room.mySeat / room.options / room.playerScores 跨局不变,不清 + //(playerScores 由 deskinfo.PlayerInfo 或 jiesuan.aset.seatlist[].score 增量更新,见 ResyncHandler / ResultHandler) + reset: function () { + this.room.asetIdx = 0; + + this.aset = { step: 0, banker: -1, call: -1, multiple: 0, flower: 0, + curmultiple: 0, grade: 0, baozhu: 0, touxiang: 0 }; + this.turn = { seat: -1, countdown: 0 }; + this.call = { currcall: 0, calls: [null, null, null] }; + this.my = { cards: [], mustCard: [], bottomCards: [], buryCards: [] }; + this.table = { ancard3s: 0, playproc: null, pushlist: [], seatlist: [], + liangpai: null, mingpai: null }; + this.result = { bottom: null, aset: null, account: null }; + }, + + //深拷贝快照(只含数据分组,不含方法),供测试做「增量 vs 全量」比对 + snapshot: function () { + var out = {}; + for (var i = 0; i < this._GROUPS.length; i++) { + var g = this._GROUPS[i]; + out[g] = JSON.parse(JSON.stringify(this[g])); + } + return out; + }, + + //把「一手牌」记进出牌历史 table.pushlist。 + //结构与 deskinfo.PushCards.pushlist 完全一致:外层下标 = 轮次-1,内层恒 3 个数组、 + //下标 = 座位序号(协议 §断线重连 pushlist)。 + // + //【只记录服务端发来的事实】:谁、出了哪几张。轮次的推进不在这里推导—— + //startRound 由调用方按【包类型】给出:chupai1 就是「一轮的第一手」(服务端定义的包, + //不是前端算出来的),其余各手落进当前这一轮。 + // + //【为什么不能读 chupai3.playproc.cards 直接归档】:服务端 do_playcard 在第三家出完后 + //就地调用 new_playround,chupai3 带的是【下一轮】的进行态(round+1、cards 全空, + //协议 §13 明标),本轮那三手只在 chupai1/2/3 各自的 seat + cards 里。 + // + //【仅可查牌模式累积】:pushlist 是「查牌」历史,不查牌模式下服务端的 deskinfo 压根 + //不下发它(design §9),前端也不能自己攒一份——否则增量路径会凭空多出重连路径没有的历史。 + pushPlay: function (seat, cards, startRound) { + var opts = this.room.options; + if (!opts || opts.nocheck) { return; } + var list = this.table.pushlist; + if (startRound || list.length === 0) { list.push([[], [], []]); } + list[list.length - 1][seat] = cards; + }, + + //把包里的字段写进目标分组。map = { 目标键: 源键 }。 + //【缺字段不兜底】源键在包里不存在就不写,保持原值——不填默认值掩盖漏发。 + _apply: function (target, src, map) { + for (var dst in map) { + if (!map.hasOwnProperty(dst)) { continue; } + var srcKey = map[dst]; + if (src.hasOwnProperty(srcKey)) { target[dst] = src[srcKey]; } + } + } +}; diff --git a/client/js/01_SubGame/codes/state/RoomOptions.js b/client/js/01_SubGame/codes/state/RoomOptions.js new file mode 100644 index 0000000..99a64df --- /dev/null +++ b/client/js/01_SubGame/codes/state/RoomOptions.js @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_RoomOptions_Parse: roomtype 位串解析 /////////// +/////////////////////////////////////////////////////////////// +// 协议 §0.5:roomtype 是建房时拼好的定长数字位串(字符串),每一位 '0'/'1'。 +// 服务端解析入口是 class.config.js 的 parse(),本文件必须与之【同规则】。 +// +// 位0 局数 '0'=6局(缺省) '1'=12局 +// 位1 扣卡 '0'=房主扣卡 '1'=AA 每人扣卡 +// 位2 傍王 '0'=关 '1'=开 +// 位3 爬坡 '0'=常规算子 '1'=爬坡 +// 位4 查牌 '0'=可查牌 '1'=不查牌 +// +// 对【缺失、非字符串、过短】的 roomtype 一律按 '0' 处理(与服务端一致), +// 这是协议明文规定的行为,不是隐式兜底。 +var EQW_RoomOptions_Parse = (function() { + //取第 idx 位,越界或非字符串时按 '0' + function _bit(roomtype, idx) { + if (typeof roomtype !== 'string') { return 0; } + if (idx >= roomtype.length) { return 0; } + return roomtype.charAt(idx) === '1' ? 1 : 0; + } + + return { + parse: function (roomtype) { + return { + asetCount: _bit(roomtype, 0) ? 12 : 6, + deductAA: _bit(roomtype, 1), + bangwang: _bit(roomtype, 2), + climb: _bit(roomtype, 3), + nocheck: _bit(roomtype, 4) + }; + } + }; +})(); diff --git a/client/tests/fixtures/export_packets.js b/client/tests/fixtures/export_packets.js new file mode 100644 index 0000000..52f7922 --- /dev/null +++ b/client/tests/fixtures/export_packets.js @@ -0,0 +1,319 @@ +// 导出脚本(测试代码,跑在 Node,非正式代码): +// 用服务端 test/_rpc.js + class.desk.js 的既有测试脚手架跑一局完整流程 +//(发牌 fapai → 叫分 jiaofen → 上庄 shangzhuang → 选主 xuanzhu → 埋牌 maipai +// → 出牌 chupai1/2/3 若干轮 → 结算 jiesuan),把真实下发包按座位快照导出成 +// client/tests/fixtures/packets.json,供前端测试回放(不手写假包,测的是真实契约)。 +// +// 用法:node client/tests/fixtures/export_packets.js +// +// 装配方式参照服务端既有测试的两种建立法: +// - _rpc.js 的 setup() 只接受"已构造好的 o_paiju",不走 class.desk.js, +// 因此不会产生 fapai(发牌)包——它是给"单个 RPC 处理器"用的轻量桩。 +// - test_flow.js / test_endgame.js 的 mkRoom() 模式改用真实 class.desk.js +// 的 D.new()/D.do_new_paiju(),才会真的下发 fapai;本脚本需要完整流程 +// (含 fapai),故照抄这一种既有用法,而不是 setup()。 +// 两条路径殊途同归:都是把 mod.app / mod.import 指向同一个 sent 捕获数组, +// 区别只在"谁负责建立 o_room/o_desk/o_paiju"。 +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +// 装配 mod.js 及其依赖类,把关键类挂到 global(与 _rpc.js 内部做法一致) +const R = require('../../../server/games/erqiwang/test/_rpc.js'); +const mod = R.mod; +const P = global.cls_youle_erqiwang_paiju; +const A = global.cls_youle_erqiwang_arith; +const D = require('../../../server/games/erqiwang/class.desk.js'); +const E = require('../../../server/games/erqiwang/class.export.js').new(); + +const ROOT = path.resolve(__dirname, '..', '..', '..'); +const OUT = path.join(ROOT, 'client/tests/fixtures/packets.json'); + +// 常规房:可查牌、不爬坡、不傍王(roomtype 位串全 0,见 class.config.js) +const ROOMTYPE = '00000'; + +// 固定种子:只在本导出脚本进程内替换 global.min_random(不改 server/_shim.js), +// 让「同一份脚本重复运行」产出逐字节相同的 packets.json,便于 review diff 与回归。 +// 写法与服务端既有测试(test_flow.js/test_fuzz.js/test_leak.js)的 xorshift 种子一致。 +// 改这个值会重新生成整局牌(发牌结果、叫分走向、出牌过程全变),产物 diff 会很大。 +const DEAL_SEED = 0x5A5A2026; +(function seedDeal(seed) { + let state = seed >>> 0; + const rnd = function (n) { + state ^= state << 13; state >>>= 0; + state ^= state >>> 17; + state ^= state << 5; state >>>= 0; + return state % n; + }; + global.min_random = function (min, max) { return min + rnd(max - min + 1); }; +})(DEAL_SEED); + +const clone = function (m) { return JSON.parse(JSON.stringify(m)); }; + +// 装配一个"真实牌桌":D.new() 建牌桌,global.youle_erqiwang.app/import 与 +// mod.app/mod.import 同时指向同一个 sent 捕获数组——class.desk.js 的 fapai/zhunbei +// 走前者(bare 全局引用),mod.js 的 RPC handler 走后者(mod 自身的模块变量), +// 两条路径必须都接上,否则 fapai 会走到 _shim.js 的默认空实现,捕获不到。 +function makeRoom(roomtype) { + const sent = []; + const o_room = { + roomtype: roomtype, asetcount: 6, roomcode: 1, createtime: 'T', makewartime: 'T', + seatlist: [0, 1, 2].map(function (i) { + return { conmode: 0, fromid: i, playerid: 100 + i, nickname: 'P' + i, avatar: '', gameinfo: {} }; + }), + method: { sendpack_toother: function (m) { sent.push(clone(m)); } } + }; + const desk = D.new(o_room); + o_room.o_desk = desk; + global.youle_erqiwang.app = { SendPack: function (m) { sent.push(clone(m)); } }; + global.youle_erqiwang.import = { check_player: function () { return o_room; }, deduct_roomcard: function () { }, save_grade: function () { } }; + mod.import = global.youle_erqiwang.import; + mod.app = global.youle_erqiwang.app; + return { o_room: o_room, desk: desk, sent: sent }; +} + +const pk = function (seat, extra) { + return { + conmode: 0, fromid: seat, + data: Object.assign({ agentid: 'a', playerid: seat, gameid: 'g', roomcode: 1, seat: seat }, extra || {}) + }; +}; + +const room = makeRoom(ROOMTYPE); +const o_room = room.o_room; +const desk = room.desk; +const sent = room.sent; + +// ---- 按座位收集下发包(保留差异化下发:谁收到什么、谁没收到) ---- +// sent 里每一项要么带明确的 fromid(0/1/2,逐座位分别下发,内容可能互不相同), +// 要么是 sendpack_toother(msg, -1) 的整体广播(未逐座位改写 conmode/fromid, +// fromid 为 undefined)——广播内容三家相同,按"三家都收到"处理。 +const seatPackets = [[], [], []]; +const deskinfo = []; +let drained = 0; +function drain() { + for (; drained < sent.length; drained++) { + const raw = sent[drained]; + const packet = { rpc: raw.rpc, data: raw.data }; + if (raw.fromid === 0 || raw.fromid === 1 || raw.fromid === 2) { + seatPackets[raw.fromid].push(packet); + } else { + seatPackets[0].push(packet); + seatPackets[1].push(packet); + seatPackets[2].push(packet); + } + } +} +// packetIndex 是关键对齐锚点:取快照时先 drain,记下该座位此刻已收到的包数, +// 与 Task 12 一致性测试"喂完 seats[seat] 的前 packetIndex 个包"对齐。 +// +// 【必须 clone】get_deskinfo 的返回值里仍有若干【无拷贝的活引用赋值】—— +// 例如 Balance.readystate = o_desk.prepare、Balance.aset/bottom/account 三份结算快照。 +// (PushCards.playproc / PushCards.seatlist / pushlist / burycards 已各自走深拷贝快照函数, +// 这段注释早先把它们也算在内,已过时,现更正。) +// 真实链路里这份返回值被平台立刻序列化下发,活引用不会被观测到;但本脚本要跑完整局之后才 +// 统一 JSON.stringify 写盘,若不在这里当场拷贝一份,写盘时这些字段会被后续操作篡改成终局 +// 状态,导致同一座位不同 packetIndex 下的快照"逐字节相同"——这是本脚本自身的缺陷,与服务端 +// 无关(服务端没有"延后序列化"这个用法)。 +function snap(step, seat) { + drain(); + deskinfo.push({ + step: step, + seat: seat, + packetIndex: seatPackets[seat].length, + info: clone(E.get_deskinfo(o_room, seat)) + }); +} + +// ================= 跑一局 ================= + +// 发牌(fapai):class.desk.js 内部随机发牌 + 广播 +D.do_new_paiju(desk, 0); +const pj = desk.method.curr_paiju(); + +// StartWar(开局握手,前端 04/05 讲的"增量路径真实起点"):真实链路里客户端开局收到的 +// 第一包,结构就是 deskwar —— 即 get_deskinfo() 的返回值(见 server/youle/server_room/ +// rpc.js 里 pack.data.deskwar = youle_room.import.makewar_deskwar(o_room) 一段)。 +// 当前 erqiwang 的 exp.makewar(class.export.js)尚未把 get_deskinfo 接成返回值,这是 +// 另一项任务;本脚本独立调用 get_deskinfo 反映"接线后客户端此刻会收到什么",让前端测试 +// 能提前对齐这条起点(尤其是 room.playerScores 这类 fapai 包不带、只有 StartWar 能给的初值)。 +// 与紧随其后的 step1 快照取同一时刻(do_new_paiju 之后、任何 jiaofen 之前),按座位各存 +// 一份(三家 MyCards 视角不同),套一层 { deskwar: ... } 外壳与生产环境 pack.data.deskwar +// 的包裹方式对齐,供 EQW_ResyncHandler.handleStartWar 直接消费。 +const startwar = [0, 1, 2].map(function (seat) { + return { deskwar: clone(E.get_deskinfo(o_room, seat)) }; +}); + +snap(1, 0); snap(1, 1); snap(1, 2); // 叫分阶段(step1):三家快照,验证 MyCards 各不相同 + +// 叫分:0 号位叫 65,其余不叫 → 0 号位上庄(触发 jiaofen 广播 + shangzhuang 差异化下发) +mod.jiaofen(pk(pj.method.get_callgrade_seat(), { call: 65 })); +mod.jiaofen(pk(pj.method.get_callgrade_seat(), { call: 0 })); +mod.jiaofen(pk(pj.method.get_callgrade_seat(), { call: 0 })); +snap(2, 0); snap(2, 1); snap(2, 2); // 选主阶段(step2):庄家有 cards/bottomcards,闲家没有 + +// 选主:庄家选方块为主 +mod.xuanzhu(pk(pj.banker, { flower: 1 })); +snap(3, pj.banker); // 埋牌阶段(step3):庄家视角 + +// 埋牌:庄家埋掉当前手上最后 8 张(get_seat_cards 已按主牌排序,任取8张即可) +mod.maipai(pk(pj.banker, { cards: P.get_seat_cards(pj, pj.banker).slice(-8) })); +snap(5, 0); snap(5, 1); snap(5, 2); // 出牌阶段开局(step5):出牌者 cardsinhand、下一位 mustcard 差异 + +// ================= 出牌策略 ================= +// 【为什么不再是"全出单张"】:单张一手无所谓顺序,服务端「这一手打出的牌」的两个顺序口径 +// (chupai/playproc.cards 与 deskinfo.PushCards.pushlist)在单张局面下永远相等,夹具测不出 +// 分岔。甩牌(design §5.4)与领对子会产生"一手多张",才真正压到这条契约。 +// +// 策略(确定性,不含随机): +// 首家:优先甩牌(多分量主牌,且服务端 can_playcard 判定合法)→ 其次领最小的一对 +// → 否则最小单张 +// 跟家:先取 get_followcard 的必出牌,再从可出牌里补足张数,逐个组合试到 +// can_followcard(+ 甩牌时的 flush_follow_ok)通过为止 +// +// 【提交顺序刻意用升序】:真实客户端提交的是玩家的点击顺序,服务端不得依赖它 +//(server 04 §8「前端不是数据源」;class.arith.js can_followcard 里也有同样的告诫)。 +// 服务端的权威展示顺序是 order_cards 的【降序】,所以这里一律把选好的一手【反转成升序】 +// 再提交,模拟"玩家从小到大点选"。这不是伪造数据——包仍然是服务端真实产出的, +// 只是让请求侧的顺序与权威顺序刻意不同,把"下发顺序是否被请求顺序污染"暴露出来。 +const asc = function (cards) { + return A.order_cards(pj.flower, cards.concat()).reverse(); +}; + +// 两名对手未出的主牌(供甩牌最大性判定),与 class.paiju.js do_playcard 的取法一致 +function oppZhuList(seat) { + const list = []; + for (let os = 0; os < 3; os++) { + if (os !== seat) { list.push(P.get_seat_zhucards(pj, os)); } + } + return list; +} + +// 首家:尝试甩牌——把手上主牌分解成分量,按分量牌力从大到小取前 k 个(k 从多到少), +// 交给服务端 can_playcard 判定;只接受 result===true 的(合法甩牌), +// 不提交会被判"甩错"的组合(那会被服务端收回成单张,反而回到单张局面)。 +// 注意 can_playcard 会【就地排序】入参数组,故一律传副本。 +function tryShuai(seat, hand) { + const trumps = hand.filter(function (c) { return A.id_to_code(pj.flower, c) > 1000; }); + if (trumps.length < 2) { return null; } + const comps = A.decompose_trump(pj.flower, trumps).slice() + .sort(function (a, b) { return b.value - a.value; }); + const opp = oppZhuList(seat); + for (let k = Math.min(comps.length, 3); k >= 2; k--) { + let pick = []; + for (let i = 0; i < k; i++) { pick = pick.concat(comps[i].cards); } + if (A.can_playcard(pj.flower, pick.concat(), seat, pj.seatlist, opp).result) { + return pick; + } + } + return null; +} + +// 首家:尝试领一对。取【最大的一对】——甩牌合法的前提是两名对手都压不过任一分量 +//(§5.4.2 opp_can_beat_flush),大牌先走能更快拔掉对手的主牌,后续局面才会出现合法甩牌。 +// 这不是"为了凑测试"的取巧:本夹具的目的就是覆盖甩牌,而甩牌本来就只在这种局面下成立。 +// 同一颗种子下:领最小的一对 → 全局只有 1 手合法甩牌;领最大的一对 → 4 手。 +function tryLeadPair(seat, hand) { + const pairs = A.get_pairlist(pj.flower, hand.concat()); + if (!pairs.length) { return null; } + const pick = pairs[0].concat(); + if (A.can_playcard(pj.flower, pick.concat(), seat, pj.seatlist, oppZhuList(seat)).result) { + return pick; + } + return null; +} + +// 跟家:这一手是否被服务端接受(与 class.paiju.js do_playcard 的跟牌校验同一组判定) +function followOk(hand, cand) { + const proc = pj.playproc; + if (!A.can_followcard(pj.flower, hand, cand, proc.startcount, proc.startflower, proc.starttype).result) { + return false; + } + if (proc.shuai_demand && !A.flush_follow_ok(pj.flower, hand, cand, proc.shuai_demand)) { + return false; + } + return true; +} + +// 跟家:凑出 startcount 张的合法跟牌 +function pickFollow(seat, hand) { + const proc = pj.playproc; + const n = proc.startcount; + const get = A.get_followcard(pj.flower, hand.concat(), n, proc.startflower, proc.starttype); + const base = (get && get.mustcard) ? get.mustcard.concat() : []; + if (base.length === n) { + return followOk(hand, base) ? base : null; + } + const need = n - base.length; + if (need < 0) { return null; } + // 候选池:优先服务端给出的"可出的牌",为空则退回整手牌 + let pool = (get && get.cancard && get.cancard.length) ? get.cancard.concat() : hand.concat(); + pool = pool.filter(function (c) { return base.indexOf(c) < 0; }); + let found = null; + const combo = []; + (function walk(start) { + if (found) { return; } + if (combo.length === need) { + const cand = base.concat(combo); + if (followOk(hand, cand)) { found = cand; } + return; + } + for (let i = start; i < pool.length && !found; i++) { + combo.push(pool[i]); + walk(i + 1); + combo.pop(); + } + })(0); + return found; +} + +// 打到一半时的 step5 快照锚点(按已出手数计)。 +// 【为什么要不止一个】:出牌阶段开局那三张 step5 快照的 pushlist 都还是空的, +// 只有"打到一半"的快照才真的比对到出牌历史(一致性测试里唯一压到 pushlist 的点)。 +// 一个锚点=一个比对点,太薄;多取几个覆盖不同轮次/不同座位/轮中不同位次。 +const MID_SNAP_AT = [4, 9, 16, 25, 34]; +let guard = 0; +let shuaiHands = 0; // 统计:合法甩牌手数 +let multiHands = 0; // 统计:一手多张的手数(含跟牌) +while (pj.step === 5 && guard < 400) { + guard++; + const seat = pj.playproc.currseat; + const hand = P.get_seat_cards(pj, seat); + let pick = null; + if (seat === pj.playproc.start) { + pick = tryShuai(seat, hand); + if (pick) { shuaiHands++; } + if (!pick) { pick = tryLeadPair(seat, hand); } + if (!pick) { pick = [hand[hand.length - 1]]; } + } else { + pick = pickFollow(seat, hand); + } + // 找不到合法出牌属脚本缺陷(服务端保证当前座位总有牌可出),显式失败、不静默跳过 + if (!pick) { + throw new Error('export_packets: 座位 ' + seat + ' 第 ' + guard + ' 手凑不出合法出牌'); + } + if (pick.length > 1) { multiHands++; } + mod.chupai(pk(seat, { cards: asc(pick) })); + // 出牌进行到中段时额外拍 step5 快照,覆盖"打到一半"的重连场景 + if (MID_SNAP_AT.indexOf(guard) >= 0 && pj.step === 5) { + snap(5, pj.playproc.currseat); + } +} +snap(6, 0); snap(6, 1); snap(6, 2); // 结算阶段(step6):jiesuan 广播后 + +drain(); // 保险:吸收循环末尾可能残留的未 drain 包 + +const fixture = { + roomtype: ROOMTYPE, + startwar: startwar, + seats: seatPackets, + deskinfo: deskinfo +}; + +fs.writeFileSync(OUT, JSON.stringify(fixture, null, 2)); +console.log('已写入 ' + OUT); +console.log('座位包数: ' + seatPackets.map(function (a) { return a.length; }).join(' / ')); +console.log('deskinfo 快照数: ' + deskinfo.length); +console.log('出牌手数: ' + guard + '(其中一手多张 ' + multiHands + ' 手,合法甩牌 ' + shuaiHands + ' 手)'); +console.log('paiju.step(结束时)=' + pj.step + ' result=' + pj.result); diff --git a/client/tests/fixtures/packets.json b/client/tests/fixtures/packets.json new file mode 100644 index 0000000..4a47914 --- /dev/null +++ b/client/tests/fixtures/packets.json @@ -0,0 +1,19669 @@ +{ + "roomtype": "00000", + "startwar": [ + { + "deskwar": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 1, + "MyCards": [ + 52, + 19, + 94, + 68, + 14, + 55, + 93, + 105, + 103, + 102, + 46, + 92, + 37, + 36, + 90, + 67, + 75, + 18, + 72, + 71, + 54, + 0, + 10, + 64, + 63, + 62, + 7, + 59 + ], + "CallRun": { + "seat": 0, + "countdown": 15, + "nowcall": 0, + "multiple": 0, + "call": [ + null, + null, + null + ] + } + } + }, + { + "deskwar": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 1, + "MyCards": [ + 106, + 99, + 73, + 81, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17, + 12, + 11, + 65, + 9, + 8, + 5, + 58, + 4 + ], + "CallRun": { + "seat": 0, + "countdown": 15, + "nowcall": 0, + "multiple": 0, + "call": [ + null, + null, + null + ] + } + } + }, + { + "deskwar": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 1, + "MyCards": [ + 53, + 107, + 45, + 86, + 32, + 6, + 60, + 40, + 27, + 1, + 39, + 104, + 44, + 98, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74, + 61 + ], + "CallRun": { + "seat": 0, + "countdown": 15, + "nowcall": 0, + "multiple": 0, + "call": [ + null, + null, + null + ] + } + } + } + ], + "seats": [ + [ + { + "rpc": "fapai", + "data": { + "success": true, + "asetidx": 1, + "asetcount": 6, + "step": 1, + "seat": 0, + "countdown": 15, + "cards": [ + 52, + 19, + 94, + 68, + 14, + 55, + 93, + 105, + 103, + 102, + 46, + 92, + 37, + 36, + 90, + 67, + 75, + 18, + 72, + 71, + 54, + 0, + 10, + 64, + 63, + 62, + 7, + 59 + ] + } + }, + { + "rpc": "jiaofen", + "data": { + "success": true, + "seat": 0, + "call": 65, + "currcall": 65, + "multiple": 2, + "step": 1, + "nextseat": 1, + "countdown": 15 + } + }, + { + "rpc": "jiaofen", + "data": { + "success": true, + "seat": 1, + "call": 0, + "currcall": 65, + "multiple": 2, + "step": 1, + "nextseat": 2, + "countdown": 15 + } + }, + { + "rpc": "shangzhuang", + "data": { + "success": true, + "seat": 2, + "call": 0, + "banker": 0, + "grade": 65, + "multiple": 2, + "step": 2, + "nextseat": 0, + "countdown": 20, + "touxiang": 0, + "curmultiple": 3, + "cards": [ + 52, + 19, + 94, + 68, + 14, + 55, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33, + 67, + 79, + 23, + 75, + 20, + 72, + 18, + 71, + 0, + 54, + 66, + 10, + 64, + 63, + 62, + 7, + 59 + ], + "bottomcards": [ + 51, + 101, + 100, + 33, + 79, + 23, + 20, + 66 + ] + } + }, + { + "rpc": "xuanzhu", + "data": { + "success": true, + "banker": 0, + "flower": 1, + "step": 3, + "nextseat": 0, + "countdown": 25, + "cards": [ + 52, + 19, + 55, + 94, + 68, + 14, + 54, + 0, + 66, + 64, + 10, + 63, + 62, + 7, + 59, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33, + 67, + 79, + 23, + 75, + 20, + 72, + 18, + 71 + ] + } + }, + { + "rpc": "maipai", + "data": { + "success": true, + "step": 5, + "seat": 0, + "countdown": 30, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "playproc": { + "round": 1, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "cards": [ + 52, + 19, + 55, + 94, + 68, + 14, + 54, + 0, + 66, + 64, + 10, + 63, + 62, + 7, + 59, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33 + ], + "burycards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 14, + 68 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 0, + "maxcard": 2202, + "cards": [ + [ + 14, + 68 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201, + "cardsinhand": [ + 52, + 19, + 55, + 94, + 0, + 54, + 66, + 10, + 64, + 63, + 62, + 7, + 59, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 65, + 11 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 0, + "maxcard": 2202, + "cards": [ + [ + 14, + 68 + ], + [ + 65, + 11 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 107, + 53 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 60, + 6 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 8107, + "cards": [ + null, + null, + [ + 60, + 6 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 54, + 0 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 8107, + "cards": [ + [ + 54, + 0 + ], + null, + [ + 60, + 6 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "cardsinhand": [ + 52, + 19, + 55, + 94, + 66, + 10, + 64, + 63, + 62, + 7, + 59, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 58, + 4 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2, + "grade": 10 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 86, + 32 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 7307, + "cards": [ + null, + null, + [ + 86, + 32 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201, + "mustcard": [ + 10, + 64 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 64, + 10 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 7307, + "cards": [ + [ + 64, + 10 + ], + null, + [ + 86, + 32 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "cardsinhand": [ + 52, + 19, + 55, + 94, + 66, + 63, + 62, + 7, + 59, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 106, + 99 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 44, + 98 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 2, + "maxcard": 406, + "cards": [ + null, + null, + [ + 44, + 98 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 4, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 105, + 51 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 413, + "cards": [ + [ + 105, + 51 + ], + null, + [ + 44, + 98 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "cardsinhand": [ + 52, + 19, + 55, + 94, + 66, + 63, + 62, + 7, + 59, + 93, + 103, + 102, + 101, + 100, + 46, + 92, + 37, + 90, + 36, + 33 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 50, + 49 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 52, + 19 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 7207, + "cards": [ + [ + 52, + 19 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + }, + "cardsinhand": [ + 55, + 94, + 66, + 63, + 62, + 7, + 59, + 93, + 103, + 102, + 101, + 100, + 46, + 92, + 37, + 90, + 36, + 33 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 73, + 81 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 7207, + "cards": [ + [ + 52, + 19 + ], + [ + 73, + 81 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 45, + 1 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 55, + 94 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 2402, + "cards": [ + [ + 55, + 94 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + }, + "cardsinhand": [ + 66, + 63, + 62, + 7, + 59, + 93, + 103, + 102, + 101, + 100, + 46, + 92, + 37, + 36, + 90, + 33 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 12, + 9 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 2402, + "cards": [ + [ + 55, + 94 + ], + [ + 12, + 9 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 40, + 27 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 66, + 63, + 62 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 1, + "startcount": 3, + "startflower": 1, + "starttype": 103, + "maxseat": 0, + "maxcard": 1109, + "cards": [ + [ + 66, + 63, + 62 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 3 + } + }, + "baozhu": 0, + "count": 3, + "flower": 1, + "cardtype": 103, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 3 + }, + "cardsinhand": [ + 7, + 59, + 93, + 103, + 102, + 101, + 100, + 46, + 92, + 37, + 36, + 90, + 33 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 8, + 5, + 48 + ], + "seatlist": [ + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 2, + "startcount": 3, + "startflower": 1, + "starttype": 103, + "maxseat": 0, + "maxcard": 1109, + "cards": [ + [ + 66, + 63, + 62 + ], + [ + 8, + 5, + 48 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 3 + } + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 61, + 39, + 104 + ], + "seatlist": [ + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 7, + 59 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 1106, + "cards": [ + [ + 7, + 59 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 1, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + }, + "cardsinhand": [ + 93, + 103, + 102, + 101, + 100, + 46, + 92, + 37, + 36, + 90, + 33 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 47, + 80 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 1106, + "cards": [ + [ + 7, + 59 + ], + [ + 47, + 80 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 97, + 43 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 46, + 100 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 408, + "cards": [ + [ + 46, + 100 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 2, + "flower": 4, + "cardtype": 201, + "cardsinhand": [ + 93, + 103, + 102, + 101, + 92, + 37, + 90, + 36, + 33 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 88, + 34 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 408, + "cards": [ + [ + 46, + 100 + ], + [ + 88, + 34 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 26, + 38 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 36, + 90 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 3, + "starttype": 201, + "maxseat": 0, + "maxcard": 311, + "cards": [ + [ + 36, + 90 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 2, + "flower": 3, + "cardtype": 201, + "cardsinhand": [ + 93, + 103, + 102, + 101, + 92, + 37, + 33 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 87, + 31 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 3, + "starttype": 201, + "maxseat": 0, + "maxcard": 311, + "cards": [ + [ + 36, + 90 + ], + [ + 87, + 31 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 35, + 89 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 33 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 308, + "cards": [ + [ + 33 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101, + "cardsinhand": [ + 93, + 103, + 102, + 101, + 92, + 37 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 84 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 2, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 308, + "cards": [ + [ + 33 + ], + [ + 84 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 91 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 5 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 74 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 208, + "cards": [ + null, + null, + [ + 74 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 93 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 208, + "cards": [ + [ + 93 + ], + null, + [ + 74 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [ + 103, + 102, + 101, + 92, + 37 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 24 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 1, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 1 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 1, + "cards": [ + 17 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 2, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 1, + "maxcard": 205, + "cards": [ + null, + [ + 17 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 2, + "cards": [ + 13 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 214, + "cards": [ + null, + [ + 17 + ], + [ + 13 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 0, + "cards": [ + 103 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 5, + "cardsinhand": [ + 102, + 101, + 92, + 37 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 76 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 210, + "cards": [ + null, + null, + [ + 76 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 102 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 210, + "cards": [ + [ + 102 + ], + null, + [ + 76 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [ + 101, + 92, + 37 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 78 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 1, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 1, + "grade": 20 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 1, + "cards": [ + 21 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 2, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 1, + "maxcard": 209, + "cards": [ + null, + [ + 21 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 2, + "cards": [ + 25 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 213, + "cards": [ + null, + [ + 21 + ], + [ + 25 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 0, + "cards": [ + 101 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 10, + "cardsinhand": [ + 92, + 37 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 30 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 2, + "maxcard": 305, + "cards": [ + null, + null, + [ + 30 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 92 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 313, + "cards": [ + [ + 92 + ], + null, + [ + 30 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [ + 37 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 77 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 37 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 312, + "cards": [ + [ + 37 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101, + "cardsinhand": [] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 22 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 2, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 312, + "cards": [ + [ + 37 + ], + [ + 22 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "jiesuan", + "data": { + "success": true, + "curmultiple": 1, + "chupai": { + "seat": 2, + "cards": [ + 85 + ], + "maxseat": 0 + }, + "bottom": { + "cards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ] + }, + "step": 6, + "aset": { + "banker": 0, + "call": 65, + "multiple": 2, + "flower": 1, + "grade": 50, + "upgrade": 1, + "bangwang": 0, + "climb": 0, + "seatlist": [ + { + "cards": [ + 52 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": 4, + "grade": 4, + "score": 4 + }, + { + "cards": [ + 106 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + }, + { + "cards": [ + 53, + 107 + ], + "chongguan": 0, + "wang": 2, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + } + ] + } + } + } + ], + [ + { + "rpc": "fapai", + "data": { + "success": true, + "asetidx": 1, + "asetcount": 6, + "step": 1, + "seat": 0, + "countdown": 15, + "cards": [ + 106, + 99, + 73, + 81, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17, + 12, + 11, + 65, + 9, + 8, + 5, + 58, + 4 + ] + } + }, + { + "rpc": "jiaofen", + "data": { + "success": true, + "seat": 0, + "call": 65, + "currcall": 65, + "multiple": 2, + "step": 1, + "nextseat": 1, + "countdown": 15 + } + }, + { + "rpc": "jiaofen", + "data": { + "success": true, + "seat": 1, + "call": 0, + "currcall": 65, + "multiple": 2, + "step": 1, + "nextseat": 2, + "countdown": 15 + } + }, + { + "rpc": "shangzhuang", + "data": { + "success": true, + "seat": 2, + "call": 0, + "banker": 0, + "grade": 65, + "multiple": 2, + "step": 2, + "nextseat": 0, + "countdown": 20, + "touxiang": 0, + "curmultiple": 3 + } + }, + { + "rpc": "xuanzhu", + "data": { + "success": true, + "banker": 0, + "flower": 1, + "step": 3, + "nextseat": 0, + "countdown": 25, + "cards": [ + 106, + 99, + 73, + 81, + 12, + 11, + 65, + 9, + 8, + 5, + 58, + 4, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "maipai", + "data": { + "success": true, + "step": 5, + "seat": 0, + "countdown": 30, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "playproc": { + "round": 1, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + } + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 14, + 68 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 0, + "maxcard": 2202, + "cards": [ + [ + 14, + 68 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 65, + 11 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 0, + "maxcard": 2202, + "cards": [ + [ + 14, + 68 + ], + [ + 65, + 11 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "cardsinhand": [ + 106, + 99, + 73, + 81, + 12, + 9, + 8, + 5, + 4, + 58, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 107, + 53 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 60, + 6 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 8107, + "cards": [ + null, + null, + [ + 60, + 6 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 54, + 0 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 8107, + "cards": [ + [ + 54, + 0 + ], + null, + [ + 60, + 6 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "mustcard": [ + 4, + 58 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 58, + 4 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2, + "grade": 10, + "cardsinhand": [ + 106, + 99, + 73, + 81, + 12, + 9, + 8, + 5, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 86, + 32 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 7307, + "cards": [ + null, + null, + [ + 86, + 32 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 64, + 10 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 7307, + "cards": [ + [ + 64, + 10 + ], + null, + [ + 86, + 32 + ] + ], + "shuai_demand": null + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 106, + 99 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2, + "cardsinhand": [ + 73, + 81, + 12, + 9, + 8, + 5, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 44, + 98 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 2, + "maxcard": 406, + "cards": [ + null, + null, + [ + 44, + 98 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 4, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 105, + 51 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 413, + "cards": [ + [ + 105, + 51 + ], + null, + [ + 44, + 98 + ] + ], + "shuai_demand": null + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 50, + 49 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0, + "cardsinhand": [ + 73, + 81, + 12, + 9, + 8, + 5, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 52, + 19 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 7207, + "cards": [ + [ + 52, + 19 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 73, + 81 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 7207, + "cards": [ + [ + 52, + 19 + ], + [ + 73, + 81 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "cardsinhand": [ + 12, + 9, + 8, + 5, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 24, + 78, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 45, + 1 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 55, + 94 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 2402, + "cards": [ + [ + 55, + 94 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 12, + 9 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 2402, + "cards": [ + [ + 55, + 94 + ], + [ + 12, + 9 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "cardsinhand": [ + 8, + 5, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 24, + 78, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 40, + 27 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 66, + 63, + 62 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 1, + "startcount": 3, + "startflower": 1, + "starttype": 103, + "maxseat": 0, + "maxcard": 1109, + "cards": [ + [ + 66, + 63, + 62 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 3 + } + }, + "baozhu": 0, + "count": 3, + "flower": 1, + "cardtype": 103, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 3 + } + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 8, + 5, + 48 + ], + "seatlist": [ + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 2, + "startcount": 3, + "startflower": 1, + "starttype": 103, + "maxseat": 0, + "maxcard": 1109, + "cards": [ + [ + 66, + 63, + 62 + ], + [ + 8, + 5, + 48 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 3 + } + }, + "baozhu": 1, + "cardsinhand": [ + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 24, + 78, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 61, + 39, + 104 + ], + "seatlist": [ + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 7, + 59 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 1106, + "cards": [ + [ + 7, + 59 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 1, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 47, + 80 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 1106, + "cards": [ + [ + 7, + 59 + ], + [ + 47, + 80 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 1, + "cardsinhand": [ + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 97, + 43 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 46, + 100 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 408, + "cards": [ + [ + 46, + 100 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 2, + "flower": 4, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 88, + 34 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 408, + "cards": [ + [ + 46, + 100 + ], + [ + 88, + 34 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [ + 87, + 31, + 84, + 24, + 78, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 26, + 38 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 36, + 90 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 3, + "starttype": 201, + "maxseat": 0, + "maxcard": 311, + "cards": [ + [ + 36, + 90 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 2, + "flower": 3, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 87, + 31 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 3, + "starttype": 201, + "maxseat": 0, + "maxcard": 311, + "cards": [ + [ + 36, + 90 + ], + [ + 87, + 31 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [ + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 35, + 89 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 33 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 308, + "cards": [ + [ + 33 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101, + "mustcard": [ + 84 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 84 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 2, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 308, + "cards": [ + [ + 33 + ], + [ + 84 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [ + 24, + 78, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 91 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 5 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 74 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 208, + "cards": [ + null, + null, + [ + 74 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 93 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 208, + "cards": [ + [ + 93 + ], + null, + [ + 74 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 24 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 1, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 1, + "cardsinhand": [ + 78, + 77, + 22, + 21, + 17 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 1, + "cards": [ + 17 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 2, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 1, + "maxcard": 205, + "cards": [ + null, + [ + 17 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101, + "cardsinhand": [ + 78, + 77, + 22, + 21 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 2, + "cards": [ + 13 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 214, + "cards": [ + null, + [ + 17 + ], + [ + 13 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 0, + "cards": [ + 103 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 5 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 76 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 210, + "cards": [ + null, + null, + [ + 76 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 102 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 210, + "cards": [ + [ + 102 + ], + null, + [ + 76 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 78 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 1, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 1, + "grade": 20, + "cardsinhand": [ + 77, + 22, + 21 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 1, + "cards": [ + 21 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 2, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 1, + "maxcard": 209, + "cards": [ + null, + [ + 21 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101, + "cardsinhand": [ + 77, + 22 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 2, + "cards": [ + 25 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 213, + "cards": [ + null, + [ + 21 + ], + [ + 25 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 0, + "cards": [ + 101 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 10 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 30 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 2, + "maxcard": 305, + "cards": [ + null, + null, + [ + 30 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 92 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 313, + "cards": [ + [ + 92 + ], + null, + [ + 30 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 77 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0, + "cardsinhand": [ + 22 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 37 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 312, + "cards": [ + [ + 37 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101, + "mustcard": [ + 22 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 22 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 2, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 312, + "cards": [ + [ + 37 + ], + [ + 22 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [] + } + }, + { + "rpc": "jiesuan", + "data": { + "success": true, + "curmultiple": 1, + "chupai": { + "seat": 2, + "cards": [ + 85 + ], + "maxseat": 0 + }, + "bottom": { + "cards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ] + }, + "step": 6, + "aset": { + "banker": 0, + "call": 65, + "multiple": 2, + "flower": 1, + "grade": 50, + "upgrade": 1, + "bangwang": 0, + "climb": 0, + "seatlist": [ + { + "cards": [ + 52 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": 4, + "grade": 4, + "score": 4 + }, + { + "cards": [ + 106 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + }, + { + "cards": [ + 53, + 107 + ], + "chongguan": 0, + "wang": 2, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + } + ] + } + } + } + ], + [ + { + "rpc": "fapai", + "data": { + "success": true, + "asetidx": 1, + "asetcount": 6, + "step": 1, + "seat": 0, + "countdown": 15, + "cards": [ + 53, + 107, + 45, + 86, + 32, + 6, + 60, + 40, + 27, + 1, + 39, + 104, + 44, + 98, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74, + 61 + ] + } + }, + { + "rpc": "jiaofen", + "data": { + "success": true, + "seat": 0, + "call": 65, + "currcall": 65, + "multiple": 2, + "step": 1, + "nextseat": 1, + "countdown": 15 + } + }, + { + "rpc": "jiaofen", + "data": { + "success": true, + "seat": 1, + "call": 0, + "currcall": 65, + "multiple": 2, + "step": 1, + "nextseat": 2, + "countdown": 15 + } + }, + { + "rpc": "shangzhuang", + "data": { + "success": true, + "seat": 2, + "call": 0, + "banker": 0, + "grade": 65, + "multiple": 2, + "step": 2, + "nextseat": 0, + "countdown": 20, + "touxiang": 0, + "curmultiple": 3 + } + }, + { + "rpc": "xuanzhu", + "data": { + "success": true, + "banker": 0, + "flower": 1, + "step": 3, + "nextseat": 0, + "countdown": 25, + "cards": [ + 53, + 107, + 60, + 6, + 45, + 86, + 32, + 1, + 40, + 27, + 61, + 39, + 104, + 44, + 98, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "maipai", + "data": { + "success": true, + "step": 5, + "seat": 0, + "countdown": 30, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "playproc": { + "round": 1, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + } + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 14, + 68 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 0, + "maxcard": 2202, + "cards": [ + [ + 14, + 68 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 65, + 11 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 0, + "maxcard": 2202, + "cards": [ + [ + 14, + 68 + ], + [ + 65, + 11 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 107, + 53 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2, + "cardsinhand": [ + 6, + 60, + 45, + 32, + 86, + 1, + 40, + 27, + 61, + 39, + 104, + 98, + 44, + 43, + 97, + 26, + 38, + 91, + 35, + 89, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 60, + 6 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 8107, + "cards": [ + null, + null, + [ + 60, + 6 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201, + "cardsinhand": [ + 45, + 32, + 86, + 1, + 40, + 27, + 61, + 39, + 104, + 98, + 44, + 43, + 97, + 26, + 38, + 91, + 35, + 89, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 54, + 0 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 8107, + "cards": [ + [ + 54, + 0 + ], + null, + [ + 60, + 6 + ] + ], + "shuai_demand": null + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 58, + 4 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2, + "grade": 10 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 86, + 32 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 7307, + "cards": [ + null, + null, + [ + 86, + 32 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 201, + "cardsinhand": [ + 45, + 1, + 40, + 27, + 61, + 39, + 104, + 98, + 44, + 43, + 97, + 26, + 38, + 91, + 35, + 89, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 64, + 10 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 3, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 7307, + "cards": [ + [ + 64, + 10 + ], + null, + [ + 86, + 32 + ] + ], + "shuai_demand": null + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 106, + 99 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 2 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 44, + 98 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 2, + "maxcard": 406, + "cards": [ + null, + null, + [ + 44, + 98 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "count": 2, + "flower": 4, + "cardtype": 201, + "cardsinhand": [ + 45, + 1, + 40, + 27, + 61, + 39, + 104, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 105, + 51 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 1, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 413, + "cards": [ + [ + 105, + 51 + ], + null, + [ + 44, + 98 + ] + ], + "shuai_demand": null + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 50, + 49 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 52, + 19 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 7207, + "cards": [ + [ + 52, + 19 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 73, + 81 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 5, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 7207, + "cards": [ + [ + 52, + 19 + ], + [ + 73, + 81 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 45, + 1 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0, + "cardsinhand": [ + 40, + 27, + 61, + 39, + 104, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 55, + 94 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 2402, + "cards": [ + [ + 55, + 94 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 12, + 9 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 2402, + "cards": [ + [ + 55, + 94 + ], + [ + 12, + 9 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 40, + 27 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "maxseat": 0, + "cardsinhand": [ + 61, + 39, + 104, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 66, + 63, + 62 + ], + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 1, + "startcount": 3, + "startflower": 1, + "starttype": 103, + "maxseat": 0, + "maxcard": 1109, + "cards": [ + [ + 66, + 63, + 62 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 3 + } + }, + "baozhu": 0, + "count": 3, + "flower": 1, + "cardtype": 103, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 3 + } + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 8, + 5, + 48 + ], + "seatlist": [ + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 7, + "start": 0, + "currseat": 2, + "startcount": 3, + "startflower": 1, + "starttype": 103, + "maxseat": 0, + "maxcard": 1109, + "cards": [ + [ + 66, + 63, + 62 + ], + [ + 8, + 5, + 48 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 3 + } + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 61, + 39, + 104 + ], + "seatlist": [ + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0, + "cardsinhand": [ + 43, + 97, + 26, + 38, + 91, + 35, + 89, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 7, + 59 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 1106, + "cards": [ + [ + 7, + 59 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 1, + "count": 2, + "flower": 1, + "cardtype": 102, + "shuai": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 47, + 80 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 8, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 1106, + "cards": [ + [ + 7, + 59 + ], + [ + 47, + 80 + ], + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 97, + 43 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0, + "cardsinhand": [ + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 46, + 100 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 408, + "cards": [ + [ + 46, + 100 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 2, + "flower": 4, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 88, + 34 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 408, + "cards": [ + [ + 46, + 100 + ], + [ + 88, + 34 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 26, + 38 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0, + "cardsinhand": [ + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 36, + 90 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 3, + "starttype": 201, + "maxseat": 0, + "maxcard": 311, + "cards": [ + [ + 36, + 90 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 2, + "flower": 3, + "cardtype": 201 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 87, + 31 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 10, + "start": 0, + "currseat": 2, + "startcount": 2, + "startflower": 3, + "starttype": 201, + "maxseat": 0, + "maxcard": 311, + "cards": [ + [ + 36, + 90 + ], + [ + 87, + 31 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "mustcard": [ + 89, + 35 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 35, + 89 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0, + "cardsinhand": [ + 91, + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 33 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 308, + "cards": [ + [ + 33 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 84 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 11, + "start": 0, + "currseat": 2, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 308, + "cards": [ + [ + 33 + ], + [ + 84 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 2, + "cards": [ + 91 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 5, + "cardsinhand": [ + 85, + 30, + 13, + 25, + 76, + 74 + ] + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 74 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 208, + "cards": [ + null, + null, + [ + 74 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101, + "cardsinhand": [ + 85, + 30, + 13, + 25, + 76 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 93 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 208, + "cards": [ + [ + 93 + ], + null, + [ + 74 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 24 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 1, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 1 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 1, + "cards": [ + 17 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 2, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 1, + "maxcard": 205, + "cards": [ + null, + [ + 17 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 2, + "cards": [ + 13 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 13, + "start": 1, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 214, + "cards": [ + null, + [ + 17 + ], + [ + 13 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [ + 85, + 30, + 25, + 76 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 0, + "cards": [ + 103 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 5 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 76 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 210, + "cards": [ + null, + null, + [ + 76 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101, + "cardsinhand": [ + 85, + 30, + 25 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 102 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 2, + "playproc": { + "round": 14, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 210, + "cards": [ + [ + 102 + ], + null, + [ + 76 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 78 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 1, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 1, + "grade": 20 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 1, + "cards": [ + 21 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 2, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 1, + "maxcard": 209, + "cards": [ + null, + [ + 21 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 2, + "cardtype": 101, + "mustcard": [ + 25 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 2, + "cards": [ + 25 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 15, + "start": 1, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 213, + "cards": [ + null, + [ + 21 + ], + [ + 25 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "cardsinhand": [ + 85, + 30 + ] + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 0, + "cards": [ + 101 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 2, + "grade": 10 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 2, + "cards": [ + 30 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 2, + "maxcard": 305, + "cards": [ + null, + null, + [ + 30 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101, + "cardsinhand": [ + 85 + ] + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 0, + "cards": [ + 92 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 16, + "start": 2, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 313, + "cards": [ + [ + 92 + ], + null, + [ + 30 + ] + ], + "shuai_demand": null + }, + "baozhu": 1 + } + }, + { + "rpc": "chupai3", + "data": { + "success": true, + "seat": 1, + "cards": [ + 77 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 0, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "maxseat": 0 + } + }, + { + "rpc": "chupai1", + "data": { + "success": true, + "seat": 0, + "cards": [ + 37 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 1, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 1, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 312, + "cards": [ + [ + 37 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "count": 1, + "flower": 3, + "cardtype": 101 + } + }, + { + "rpc": "chupai2", + "data": { + "success": true, + "seat": 1, + "cards": [ + 22 + ], + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "nextseat": 2, + "countdown": 30, + "curmultiple": 1, + "playproc": { + "round": 17, + "start": 0, + "currseat": 2, + "startcount": 1, + "startflower": 3, + "starttype": 101, + "maxseat": 0, + "maxcard": 312, + "cards": [ + [ + 37 + ], + [ + 22 + ], + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "mustcard": [ + 85 + ] + } + }, + { + "rpc": "jiesuan", + "data": { + "success": true, + "curmultiple": 1, + "chupai": { + "seat": 2, + "cards": [ + 85 + ], + "maxseat": 0 + }, + "bottom": { + "cards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ] + }, + "step": 6, + "aset": { + "banker": 0, + "call": 65, + "multiple": 2, + "flower": 1, + "grade": 50, + "upgrade": 1, + "bangwang": 0, + "climb": 0, + "seatlist": [ + { + "cards": [ + 52 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": 4, + "grade": 4, + "score": 4 + }, + { + "cards": [ + 106 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + }, + { + "cards": [ + 53, + 107 + ], + "chongguan": 0, + "wang": 2, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + } + ] + } + } + } + ] + ], + "deskinfo": [ + { + "step": 1, + "seat": 0, + "packetIndex": 1, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 1, + "MyCards": [ + 52, + 19, + 94, + 68, + 14, + 55, + 93, + 105, + 103, + 102, + 46, + 92, + 37, + 36, + 90, + 67, + 75, + 18, + 72, + 71, + 54, + 0, + 10, + 64, + 63, + 62, + 7, + 59 + ], + "CallRun": { + "seat": 0, + "countdown": 15, + "nowcall": 0, + "multiple": 0, + "call": [ + null, + null, + null + ] + } + } + }, + { + "step": 1, + "seat": 1, + "packetIndex": 1, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 1, + "MyCards": [ + 106, + 99, + 73, + 81, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17, + 12, + 11, + 65, + 9, + 8, + 5, + 58, + 4 + ], + "CallRun": { + "seat": 0, + "countdown": 15, + "nowcall": 0, + "multiple": 0, + "call": [ + null, + null, + null + ] + } + } + }, + { + "step": 1, + "seat": 2, + "packetIndex": 1, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 1, + "MyCards": [ + 53, + 107, + 45, + 86, + 32, + 6, + 60, + 40, + 27, + 1, + 39, + 104, + 44, + 98, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74, + 61 + ], + "CallRun": { + "seat": 0, + "countdown": 15, + "nowcall": 0, + "multiple": 0, + "call": [ + null, + null, + null + ] + } + } + }, + { + "step": 2, + "seat": 0, + "packetIndex": 4, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 2, + "MyCards": [ + 52, + 19, + 94, + 68, + 14, + 55, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33, + 67, + 79, + 23, + 75, + 20, + 72, + 18, + 71, + 0, + 54, + 66, + 10, + 64, + 63, + 62, + 7, + 59 + ], + "ChooseMain": { + "seat": 0, + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 20, + "bottomcards": [ + 51, + 101, + 100, + 33, + 79, + 23, + 20, + 66 + ], + "touxiang": 0, + "curmultiple": 3 + } + } + }, + { + "step": 2, + "seat": 1, + "packetIndex": 4, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 2, + "MyCards": [ + 106, + 99, + 73, + 81, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17, + 12, + 11, + 65, + 9, + 8, + 5, + 58, + 4 + ], + "ChooseMain": { + "seat": 0, + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 20, + "touxiang": 0, + "curmultiple": 3 + } + } + }, + { + "step": 2, + "seat": 2, + "packetIndex": 4, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 2, + "MyCards": [ + 53, + 107, + 45, + 86, + 32, + 6, + 60, + 40, + 27, + 1, + 39, + 104, + 44, + 98, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74, + 61 + ], + "ChooseMain": { + "seat": 0, + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 20, + "touxiang": 0, + "curmultiple": 3 + } + } + }, + { + "step": 3, + "seat": 0, + "packetIndex": 5, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 3, + "MyCards": [ + 52, + 19, + 55, + 94, + 68, + 14, + 54, + 0, + 66, + 64, + 10, + 63, + 62, + 7, + 59, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33, + 67, + 79, + 23, + 75, + 20, + 72, + 18, + 71 + ], + "BuryCards": { + "seat": 0, + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 25, + "bottomcards": [ + 51, + 101, + 100, + 33, + 79, + 23, + 20, + 66 + ], + "flower": 1, + "curmultiple": 3 + } + } + }, + { + "step": 5, + "seat": 0, + "packetIndex": 6, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 5, + "MyCards": [ + 52, + 19, + 55, + 94, + 68, + 14, + 54, + 0, + 66, + 64, + 10, + 63, + 62, + 7, + 59, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33 + ], + "PushCards": { + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 30, + "flower": 1, + "burycards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ], + "grade": 0, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "pushlist": [] + } + } + }, + { + "step": 5, + "seat": 1, + "packetIndex": 6, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 5, + "MyCards": [ + 106, + 99, + 73, + 81, + 12, + 11, + 65, + 9, + 8, + 5, + 58, + 4, + 50, + 49, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ], + "PushCards": { + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 30, + "flower": 1, + "gradecards": [], + "grade": 0, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "pushlist": [] + } + } + }, + { + "step": 5, + "seat": 2, + "packetIndex": 6, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 5, + "MyCards": [ + 53, + 107, + 60, + 6, + 45, + 86, + 32, + 1, + 40, + 27, + 61, + 39, + 104, + 44, + 98, + 97, + 43, + 26, + 38, + 91, + 89, + 35, + 85, + 30, + 13, + 25, + 76, + 74 + ], + "PushCards": { + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 30, + "flower": 1, + "gradecards": [], + "grade": 0, + "curmultiple": 3, + "playproc": { + "round": 1, + "start": 0, + "currseat": 0, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "pushlist": [] + } + } + }, + { + "step": 5, + "seat": 0, + "packetIndex": 10, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 5, + "MyCards": [ + 52, + 19, + 55, + 94, + 0, + 54, + 66, + 10, + 64, + 63, + 62, + 7, + 59, + 93, + 51, + 105, + 103, + 102, + 101, + 46, + 100, + 92, + 37, + 36, + 90, + 33 + ], + "PushCards": { + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 30, + "flower": 1, + "burycards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ], + "grade": 0, + "curmultiple": 3, + "playproc": { + "round": 2, + "start": 2, + "currseat": 0, + "startcount": 2, + "startflower": 1, + "starttype": 201, + "maxseat": 2, + "maxcard": 8107, + "cards": [ + null, + null, + [ + 60, + 6 + ] + ], + "shuai_demand": null + }, + "baozhu": 0, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "pushlist": [ + [ + [ + 14, + 68 + ], + [ + 65, + 11 + ], + [ + 107, + 53 + ] + ], + [ + [], + [], + [ + 60, + 6 + ] + ] + ] + } + } + }, + { + "step": 5, + "seat": 2, + "packetIndex": 15, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 5, + "MyCards": [ + 45, + 1, + 40, + 27, + 61, + 39, + 104, + 98, + 44, + 43, + 97, + 26, + 38, + 91, + 35, + 89, + 85, + 30, + 13, + 25, + 76, + 74 + ], + "PushCards": { + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 30, + "flower": 1, + "gradecards": [ + 4, + 58 + ], + "grade": 10, + "curmultiple": 2, + "playproc": { + "round": 4, + "start": 2, + "currseat": 2, + "startcount": -1, + "startflower": -1, + "starttype": -1, + "maxseat": -1, + "maxcard": -1, + "cards": [ + null, + null, + null + ], + "shuai_demand": null + }, + "baozhu": 0, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "pushlist": [ + [ + [ + 14, + 68 + ], + [ + 65, + 11 + ], + [ + 107, + 53 + ] + ], + [ + [ + 54, + 0 + ], + [ + 58, + 4 + ], + [ + 60, + 6 + ] + ], + [ + [ + 64, + 10 + ], + [ + 106, + 99 + ], + [ + 86, + 32 + ] + ] + ] + } + } + }, + { + "step": 5, + "seat": 1, + "packetIndex": 22, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 5, + "MyCards": [ + 12, + 9, + 8, + 5, + 48, + 47, + 80, + 34, + 88, + 87, + 31, + 84, + 24, + 78, + 77, + 22, + 21, + 17 + ], + "PushCards": { + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 30, + "flower": 1, + "gradecards": [ + 4, + 58 + ], + "grade": 10, + "curmultiple": 2, + "playproc": { + "round": 6, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 1, + "starttype": 102, + "maxseat": 0, + "maxcard": 2402, + "cards": [ + [ + 55, + 94 + ], + null, + null + ], + "shuai_demand": { + "tractors": [], + "pairs": 0, + "singles": 2 + } + }, + "baozhu": 0, + "seatlist": [ + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + -1, + -1 + ] + ], + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1, + -1 + ] + ] + ], + "pushlist": [ + [ + [ + 14, + 68 + ], + [ + 65, + 11 + ], + [ + 107, + 53 + ] + ], + [ + [ + 54, + 0 + ], + [ + 58, + 4 + ], + [ + 60, + 6 + ] + ], + [ + [ + 64, + 10 + ], + [ + 106, + 99 + ], + [ + 86, + 32 + ] + ], + [ + [ + 105, + 51 + ], + [ + 50, + 49 + ], + [ + 44, + 98 + ] + ], + [ + [ + 52, + 19 + ], + [ + 73, + 81 + ], + [ + 45, + 1 + ] + ], + [ + [ + 55, + 94 + ], + [], + [] + ] + ] + } + } + }, + { + "step": 5, + "seat": 1, + "packetIndex": 31, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 5, + "MyCards": [ + 34, + 88, + 87, + 31, + 84, + 78, + 24, + 77, + 22, + 21, + 17 + ], + "PushCards": { + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 30, + "flower": 1, + "gradecards": [ + 4, + 58 + ], + "grade": 10, + "curmultiple": 2, + "playproc": { + "round": 9, + "start": 0, + "currseat": 1, + "startcount": 2, + "startflower": 4, + "starttype": 201, + "maxseat": 0, + "maxcard": 408, + "cards": [ + [ + 46, + 100 + ], + null, + null + ], + "shuai_demand": null + }, + "baozhu": 1, + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + ], + "pushlist": [ + [ + [ + 14, + 68 + ], + [ + 65, + 11 + ], + [ + 107, + 53 + ] + ], + [ + [ + 54, + 0 + ], + [ + 58, + 4 + ], + [ + 60, + 6 + ] + ], + [ + [ + 64, + 10 + ], + [ + 106, + 99 + ], + [ + 86, + 32 + ] + ], + [ + [ + 105, + 51 + ], + [ + 50, + 49 + ], + [ + 44, + 98 + ] + ], + [ + [ + 52, + 19 + ], + [ + 73, + 81 + ], + [ + 45, + 1 + ] + ], + [ + [ + 55, + 94 + ], + [ + 12, + 9 + ], + [ + 40, + 27 + ] + ], + [ + [ + 66, + 63, + 62 + ], + [ + 8, + 5, + 48 + ], + [ + 61, + 39, + 104 + ] + ], + [ + [ + 7, + 59 + ], + [ + 47, + 80 + ], + [ + 97, + 43 + ] + ], + [ + [ + 46, + 100 + ], + [], + [] + ] + ] + } + } + }, + { + "step": 5, + "seat": 0, + "packetIndex": 40, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 0, + 0, + 0 + ], + "step": 5, + "MyCards": [ + 93, + 103, + 102, + 101, + 92, + 37 + ], + "PushCards": { + "banker": 0, + "call": 65, + "multiple": 2, + "countdown": 30, + "flower": 1, + "burycards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ], + "grade": 15, + "curmultiple": 2, + "playproc": { + "round": 12, + "start": 2, + "currseat": 0, + "startcount": 1, + "startflower": 2, + "starttype": 101, + "maxseat": 2, + "maxcard": 208, + "cards": [ + null, + null, + [ + 74 + ] + ], + "shuai_demand": null + }, + "baozhu": 1, + "seatlist": [ + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ], + [ + [ + 1, + 1 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1, + 1 + ], + [ + 0, + 0 + ] + ] + ], + "pushlist": [ + [ + [ + 14, + 68 + ], + [ + 65, + 11 + ], + [ + 107, + 53 + ] + ], + [ + [ + 54, + 0 + ], + [ + 58, + 4 + ], + [ + 60, + 6 + ] + ], + [ + [ + 64, + 10 + ], + [ + 106, + 99 + ], + [ + 86, + 32 + ] + ], + [ + [ + 105, + 51 + ], + [ + 50, + 49 + ], + [ + 44, + 98 + ] + ], + [ + [ + 52, + 19 + ], + [ + 73, + 81 + ], + [ + 45, + 1 + ] + ], + [ + [ + 55, + 94 + ], + [ + 12, + 9 + ], + [ + 40, + 27 + ] + ], + [ + [ + 66, + 63, + 62 + ], + [ + 8, + 5, + 48 + ], + [ + 61, + 39, + 104 + ] + ], + [ + [ + 7, + 59 + ], + [ + 47, + 80 + ], + [ + 97, + 43 + ] + ], + [ + [ + 46, + 100 + ], + [ + 88, + 34 + ], + [ + 26, + 38 + ] + ], + [ + [ + 36, + 90 + ], + [ + 87, + 31 + ], + [ + 35, + 89 + ] + ], + [ + [ + 33 + ], + [ + 84 + ], + [ + 91 + ] + ], + [ + [], + [], + [ + 74 + ] + ] + ] + } + } + }, + { + "step": 6, + "seat": 0, + "packetIndex": 57, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 4, + -2, + -2 + ], + "step": 6, + "MyCards": [], + "Balance": { + "readystate": [ + 0, + 0, + 0 + ], + "aset": { + "banker": 0, + "call": 65, + "multiple": 2, + "flower": 1, + "grade": 50, + "upgrade": 1, + "bangwang": 0, + "climb": 0, + "seatlist": [ + { + "cards": [ + 52 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": 4, + "grade": 4, + "score": 4 + }, + { + "cards": [ + 106 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + }, + { + "cards": [ + 53, + 107 + ], + "chongguan": 0, + "wang": 2, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + } + ] + }, + "bottom": { + "cards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ] + } + } + } + }, + { + "step": 6, + "seat": 1, + "packetIndex": 57, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 4, + -2, + -2 + ], + "step": 6, + "MyCards": [], + "Balance": { + "readystate": [ + 0, + 0, + 0 + ], + "aset": { + "banker": 0, + "call": 65, + "multiple": 2, + "flower": 1, + "grade": 50, + "upgrade": 1, + "bangwang": 0, + "climb": 0, + "seatlist": [ + { + "cards": [ + 52 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": 4, + "grade": 4, + "score": 4 + }, + { + "cards": [ + 106 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + }, + { + "cards": [ + 53, + 107 + ], + "chongguan": 0, + "wang": 2, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + } + ] + }, + "bottom": { + "cards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ] + } + } + } + }, + { + "step": 6, + "seat": 2, + "packetIndex": 57, + "info": { + "count": 6, + "idx": 1, + "PlayerInfo": [ + 4, + -2, + -2 + ], + "step": 6, + "MyCards": [], + "Balance": { + "readystate": [ + 0, + 0, + 0 + ], + "aset": { + "banker": 0, + "call": 65, + "multiple": 2, + "flower": 1, + "grade": 50, + "upgrade": 1, + "bangwang": 0, + "climb": 0, + "seatlist": [ + { + "cards": [ + 52 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": 4, + "grade": 4, + "score": 4 + }, + { + "cards": [ + 106 + ], + "chongguan": 0, + "wang": 1, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + }, + { + "cards": [ + 53, + 107 + ], + "chongguan": 0, + "wang": 2, + "naward": 0, + "grade_aw": 0, + "grade_cg": 0, + "grade_bw": 0, + "grade_jf": -2, + "grade": -2, + "score": -2 + } + ] + }, + "bottom": { + "cards": [ + 67, + 79, + 23, + 75, + 20, + 18, + 72, + 71 + ] + } + } + } + } + ] +} \ No newline at end of file diff --git a/client/tests/test_appstart_timing.js b/client/tests/test_appstart_timing.js new file mode 100644 index 0000000..cd9af61 --- /dev/null +++ b/client/tests/test_appstart_timing.js @@ -0,0 +1,150 @@ +// 【C-1 回归】room.mySeat 的同步必须跑在【真实的平台初始化时序】上。 +// +// 平台 12_Logic.js 的 Logic.AppStart 跨 332–531 行,里面: +// line 389: Game_Modify.appStart() ← 此时 C_Player 只是 `var C_Player;`,未赋值 +// line 480: C_Player = new Player(-1) ← 对象到这里才存在,seat 仍是 -1 +// 之后玩家登录,07_Desk.js 的 C_Player.SetSeat(真座位), +// 但 Game_Modify.appStart() 【再也不会被调用】。 +// +// 所以「只在 appStart 里读一次 C_Player.seat」必然拿不到座位,mySeat 永远停在 -1: +// 发包带 seat:-1(服务端 check_player 必拒,叫分/选主/埋牌/出牌全发不出去)、 +// StartWar 差异化下发认领不到本座位而整包丢弃、SeatMap.toDisplay(-1) 抛错。 +// ——全程无一处报错。本测试就是拿这条时序当尺子量。 +// +// 本文件【不允许】用 `S.room.mySeat = N` 直接赋值来制造前提(其余 8 个测试文件那样做, +// 正是它们全部漏掉 C-1 的原因)。座位只能经由平台入口同步进来。 +const { load, throws } = require('./_load'); +const t = require('./_assert')(); + +global.window = global; + +load('client/js/gameabc-framework/system/EventBus.js'); +load('client/js/gameabc-framework/system/SpriteEventController.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'); +['DealHandler','CallHandler','MainHandler','BuryHandler','PlayHandler', + 'QueryHandler','ReadyHandler','ResultHandler','ResyncHandler','FailHandler'] + .forEach(h => load('client/js/01_SubGame/codes/net/handlers/' + h + '.js')); +load('client/js/01_SubGame/codes/net/Dispatcher.js'); +load('client/js/01_SubGame/codes/core/SeatMap.js'); + +const sent = []; +global.RpcHelper = { sendRpc: (app, route, rpc, data) => sent.push({ rpc, data }) }; +load('client/js/01_SubGame/codes/net/Rpc.js'); + +load('client/js/01_SubGame/codes/SubGameHooks.js'); + +const S = EQW_GameState; + +// 平台 06_Player.js 的最小复刻:只有 seat 与 SetSeat 与本用例相关 +function Player(seat) { this.seat = seat; } +Player.prototype.SetSeat = function (seat) { this.seat = seat; }; + +// ============================================================================ +// 阶段 1:Logic.AppStart 第 389 行——C_Player 尚未 new +// ============================================================================ +t.eq('前提:C_Player 此刻确实不存在', typeof global.C_Player, 'undefined'); +t.eq('前提:mySeat 初始值是 -1', S.room.mySeat, -1); + +let boom = false; +try { SubGameHooks.appStart(); } catch (e) { boom = true; } +t.eq('appStart 在 C_Player 缺席时不崩', boom, false); +t.eq('appStart 拿不到座位,mySeat 仍是 -1(不是缺陷,是时序事实)', S.room.mySeat, -1); + +// 这个 -1 一旦被发包无声带出去就是 C-1 的第一重后果 +t.eq('座位未同步时发包 fail-fast', throws(() => EQW_Rpc.zhunbei()), true); +t.eq('座位未同步时一个包都没发出去', sent.length, 0); + +// ============================================================================ +// 阶段 2:Logic.AppStart 第 480 行——C_Player = new Player(-1) +// ============================================================================ +global.C_Player = new Player(-1); +SubGameHooks.setRoomDes(1234, 6, '00010'); +t.eq('C_Player 存在但座位还是 -1 时,不写坏值', S.room.mySeat, -1); +t.eq('setRoomDes 该干的正事照干(解析 roomtype)', S.room.options.climb, 1); + +// ============================================================================ +// 阶段 3:玩家登录,07_Desk.js:320 C_Player.SetSeat(真座位) +// 紧接着 07_Desk.js:358 Game_Modify.setRoomDes(...) +// ============================================================================ +C_Player.SetSeat(2); +t.eq('SetSeat 本身不写 GameState(平台不知道子游戏)', S.room.mySeat, -1); + +SubGameHooks.setRoomDes(1234, 6, '00010'); +t.eq('【核心】走完平台时序后 mySeat = 真座位', S.room.mySeat, 2); + +// ---- 第一重后果解除:发包带真座位 ---- +sent.length = 0; +EQW_Rpc.zhunbei(); +t.eq('发包带上真座位', sent[0].data.seat, 2); + +// ---- 第二重后果解除:StartWar 差异化下发能认领到自己那份 ---- +const share = (seat, cards) => ({ seat, data: { count: 6, idx: 1, PlayerInfo: [0,0,0], step: 1, MyCards: cards } }); +SubGameHooks.StartWar({ data: { deskwar: { sendtype: 1, seatlist: [ + share(0, [10, 11]), share(1, [20, 21]), share(2, [30, 31]) +] } } }); +t.eq('StartWar 认领到本座位那份手牌', S.my.cards, [30, 31]); + +// ---- 第三重后果解除:SeatMap 不再对 -1 抛错 ---- +t.eq('SeatMap.toDisplay 用 mySeat 不抛错', throws(() => EQW_SeatMap.toDisplay(0, S.room.mySeat)), false); +t.eq('自己映射到 SELF', EQW_SeatMap.toDisplay(S.room.mySeat, S.room.mySeat), 'SELF'); + +// ============================================================================ +// 阶段 4:换座(07_Desk.js:186-191 先改 C_Player.seat,再回调 changeSeat) +// 这是唯一「座位中途变化且不重走 setRoomDes」的路径 +// ============================================================================ +C_Player.SetSeat(0); +SubGameHooks.changeSeat(2, 0); +t.eq('换座后 mySeat 跟着变', S.room.mySeat, 0); +sent.length = 0; +EQW_Rpc.zhunbei(); +t.eq('换座后发包带新座位', sent[0].data.seat, 0); + +// ============================================================================ +// 阶段 5:断线重连(07_Desk.js:320 SetSeat → 358 setRoomDes → 423 Reconnect) +// 这里单独校验 Reconnect 自身也同步,不依赖前面的入口是否跑过 +// ============================================================================ +S.room.mySeat = -1; // 人为退回未同步态,验证 Reconnect 能独立自愈 +C_Player.SetSeat(1); +SubGameHooks.Reconnect({ count: 6, idx: 3, PlayerInfo: [0,0,0], step: 1, MyCards: [5,6] }); +t.eq('Reconnect 自身也同步座位', S.room.mySeat, 1); +t.eq('Reconnect 的 reset() 不会把刚同步的座位清掉', S.room.mySeat, 1); +t.eq('Reconnect 正常填状态', S.my.cards, [5, 6]); + +S.room.mySeat = -1; +C_Player.SetSeat(2); +SubGameHooks.ReconnectNoMakewar(); +t.eq('ReconnectNoMakewar 也同步座位', S.room.mySeat, 2); + +S.room.mySeat = -1; +C_Player.SetSeat(0); +SubGameHooks.StartWar({ data: { deskwar: { sendtype: 1, seatlist: [ + share(0, [7, 8]), share(1, [9]), share(2, [1]) ] } } }); +t.eq('StartWar 自身也同步座位', S.room.mySeat, 0); +t.eq('StartWar 同步后认领正确的那份', S.my.cards, [7, 8]); + +// ============================================================================ +// 幂等 / 防倒退:_syncMySeat 可重复调用,且绝不用坏值覆盖好值 +// ============================================================================ +SubGameHooks._syncMySeat(); +SubGameHooks._syncMySeat(); +t.eq('重复同步结果不变(幂等)', S.room.mySeat, 0); + +C_Player.SetSeat(-1); // 平台 new Player(-1) 的初值又冒出来 +SubGameHooks._syncMySeat(); +t.eq('座位为 -1 时保持原值,不倒退', S.room.mySeat, 0); +C_Player.SetSeat(3); +SubGameHooks._syncMySeat(); +t.eq('座位越界时保持原值,不倒退', S.room.mySeat, 0); +C_Player.SetSeat('1'); +SubGameHooks._syncMySeat(); +t.eq('座位是字符串时保持原值,不倒退', S.room.mySeat, 0); + +delete global.C_Player; +boom = false; +try { SubGameHooks._syncMySeat(); } catch (e) { boom = true; } +t.eq('C_Player 消失时同步不崩', boom, false); +t.eq('C_Player 消失时保持原值', S.room.mySeat, 0); + +process.exit(t.done('appstart_timing') ? 0 : 1); diff --git a/client/tests/test_consistency.js b/client/tests/test_consistency.js new file mode 100644 index 0000000..2f10529 --- /dev/null +++ b/client/tests/test_consistency.js @@ -0,0 +1,114 @@ +// 【核心验收】增量累积的 GameState 必须与 deskinfo 全量重建的完全相等 +const fs = require('fs'); +const path = require('path'); +const { ROOT, 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/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'); + +const fx = JSON.parse(fs.readFileSync(path.join(ROOT, 'client/tests/fixtures/packets.json'), 'utf8')); + +// 这些字段是「一次性事件」或「重连不重放」的,不参与一致性比对。 +// 每加一条都必须写清理由——这个白名单是本测试唯一的松口处,滥用它等于废掉这条守卫。 +// +// 【清单历史】result.bottom / result.account 已随服务端 Balance 补发而删除; +// result.chupai 已随「本墩结果改走 EQW_TRICK_END 事件载荷、不进 GameState」而删除。 +// 现在只剩下面这一条。 +const EXCLUDE = [ + // 70 分坐庄的 3 秒开底是上庄时的一次性事件,协议明确 deskinfo 不重放。 + // ⚠️ 当前夹具无法触发(65 分坐庄、单局),此条未经验证——保留是因为删掉之后 + // 将来真出现不一致会静默通过;但它此刻并没有在守住任何东西,不要当成已验证的豁免。 + 'table.ancard3s' +]; + +function pick(snap, exclude) { + const out = JSON.parse(JSON.stringify(snap)); + exclude.forEach(p => { + const parts = p.split('.'); + let o = out; + for (let i = 0; i < parts.length - 1; i++) { o = o && o[parts[i]]; } + if (o) { delete o[parts[parts.length - 1]]; } + }); + return out; +} + +// 每个对齐点独立起步,不共用进程内任何残留(下面两个 build 函数各自从 +// EQW_GameState.reset() + 显式赋值房间级字段开始,互不依赖调用顺序/次数): +// +// 【曾经的假绿】EQW_GameState.reset() 按设计不清 room.playerScores(它是"跨局不清"的 +// 房间级累积量,见 GameState.js 注释)。旧版路径 A 从不写 playerScores(fapai 包不带), +// 于是它的值全靠"上一次由谁最后写过"——如果上一次恰好是路径 B(deskinfo 带 PlayerInfo) +// 跑过,路径 A 就会【继承】那份残留,凑巧与路径 B 的期望值相等,测试假绿;换一个座位 +// 顺序、或把它放到第一个跑,残留没了就立刻转红。 +// 真实客户端不存在这个残留——它在 fapai 之前先收到 StartWar(deskwar,见夹具补捕), +// 由它给出 room.playerScores 的开局初值。所以路径 A 的真实起点也改成先喂 StartWar, +// 每次都显式重新赋值,不再依赖任何"恰好还没被冲掉的旧值"。 +function buildIncremental(seat, packetIndex) { + EQW_GameState.reset(); + EQW_GameState.room.mySeat = seat; + EQW_GameState.room.options = EQW_RoomOptions_Parse.parse(fx.roomtype); + EQW_ResyncHandler.handleStartWar({ data: fx.startwar[seat] }); + fx.seats[seat].slice(0, packetIndex).forEach(p => { + EQW_Dispatcher.dispatch({ rpc: p.rpc, data: p.data }); + }); + return EQW_GameState.snapshot(); +} + +function buildFull(seat, info) { + EQW_GameState.reset(); + EQW_GameState.room.mySeat = seat; + EQW_GameState.room.options = EQW_RoomOptions_Parse.parse(fx.roomtype); + EQW_ResyncHandler.handleDeskinfo(info); + return EQW_GameState.snapshot(); +} + +// 对每个座位、每个 deskinfo 快照点做比对 +let compared = 0; +fx.deskinfo.forEach(snapPoint => { + const seat = snapPoint.seat; + + const incremental = buildIncremental(seat, snapPoint.packetIndex); + const full = buildFull(seat, snapPoint.info); + + t.eq('座位' + seat + ' step' + snapPoint.step + ' 增量 == 全量', + pick(incremental, EXCLUDE), pick(full, EXCLUDE)); + compared++; +}); + +t.eq('比对点数量 > 0', compared > 0, true); + +// ================= Ruling 3:step3(埋牌)deskinfo 直读断言 ================= +// 一致性比对无法单独覆盖 ResyncHandler._applyBuryCards 的字段映射正确性—— +// 若增量路径压根不写某字段,两边可能「同为初始值」而比对照样绿。 +// 这里独立取 step3 快照,逐字段直读 GameState 并核对具体值。 +const step3 = fx.deskinfo.filter(d => d.step === 3)[0]; +if (!step3) { + console.warn('[test_consistency] 夹具里没有 step3 快照,Ruling 3 的直读断言无法执行'); +} else { + EQW_GameState.reset(); + EQW_ResyncHandler.handleDeskinfo(step3.info); + const bc = step3.info.BuryCards; + t.eq('step3 直读 aset.banker', EQW_GameState.aset.banker, bc.banker); + t.eq('step3 直读 aset.call', EQW_GameState.aset.call, bc.call); + t.eq('step3 直读 aset.multiple', EQW_GameState.aset.multiple, bc.multiple); + t.eq('step3 直读 aset.flower', EQW_GameState.aset.flower, bc.flower); + t.eq('step3 直读 my.bottomCards', EQW_GameState.my.bottomCards, bc.bottomcards); + t.eq('step3 直读 turn.countdown', EQW_GameState.turn.countdown, bc.countdown); + t.eq('step3 直读 turn.seat(埋牌由庄家做)', EQW_GameState.turn.seat, bc.banker); + t.eq('step3 直读 aset.step', EQW_GameState.aset.step, step3.info.step); +} + +process.exit(t.done('consistency') ? 0 : 1); diff --git a/client/tests/test_dispatcher.js b/client/tests/test_dispatcher.js new file mode 100644 index 0000000..42a42c2 --- /dev/null +++ b/client/tests/test_dispatcher.js @@ -0,0 +1,59 @@ +// 分发器:success 优先分流、未知 rpc 不崩、路由表完整性 +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/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'); + +// ---- 注册与分发 ---- +const got = []; +EQW_Dispatcher.register('testRpc', function (d) { got.push(d); }); +EQW_Dispatcher.dispatch({ rpc: 'testRpc', data: { success: true, v: 1 } }); +t.eq('成功包进业务 handler', got, [{ success: true, v: 1 }]); + +// ---- success:false 优先走 FailHandler,不进业务 handler ---- +const failed = []; +EventBus.on(EQW_Events.EQW_RPC_FAILED, function (e) { failed.push(e); }); +got.length = 0; +EQW_Dispatcher.dispatch({ rpc: 'testRpc', data: { success: false, errcode: 5 } }); +t.eq('失败包不进业务 handler', got, []); +t.eq('失败包 emit RPC_FAILED', failed, [{ rpc: 'testRpc', errcode: 5 }]); + +// ---- 失败包即使 rpc 没注册过也要被处理(如 chupai 只会以失败包出现)---- +failed.length = 0; +EQW_Dispatcher.dispatch({ rpc: 'chupai', data: { success: false, errcode: 6 } }); +t.eq('未注册 rpc 的失败包也处理', failed, [{ rpc: 'chupai', errcode: 6 }]); + +// ---- 未知 rpc 的成功包:只警告、不抛错 ---- +let threw = false; +try { EQW_Dispatcher.dispatch({ rpc: 'brandNewRpc', data: { success: true } }); } +catch (e) { threw = true; } +t.eq('未知 rpc 不抛错', threw, false); + +// ---- 畸形入参不崩 ---- +[null, undefined, {}, { rpc: 'testRpc' }, { data: {} }].forEach((bad, i) => { + let boom = false; + try { EQW_Dispatcher.dispatch(bad); } catch (e) { boom = true; } + t.eq('畸形入参 #' + i + ' 不崩', boom, false); +}); + +// ---- 路由表完整性:协议里每个服务端推送 rpc 都要有 handler ---- +['fapai','jiaofen','shangzhuang','xuanzhu','maipai', + 'chupai1','chupai2','chupai3','mingpai','tishi','jiesuan','zhunbei'].forEach(rpc => { + t.eq('已注册 handler: ' + rpc, EQW_Dispatcher.hasHandler(rpc), true); +}); + +process.exit(t.done('dispatcher') ? 0 : 1); diff --git a/client/tests/test_fixture.js b/client/tests/test_fixture.js new file mode 100644 index 0000000..27bddcf --- /dev/null +++ b/client/tests/test_fixture.js @@ -0,0 +1,111 @@ +// 夹具自检:真包必须覆盖全部阶段,且保留座位差异 +const fs = require('fs'); +const path = require('path'); +const { ROOT } = require('./_load'); +const t = require('./_assert')(); + +const fx = JSON.parse(fs.readFileSync(path.join(ROOT, 'client/tests/fixtures/packets.json'), 'utf8')); + +t.eq('三个座位', fx.seats.length, 3); + +const rpcsOf = seat => fx.seats[seat].map(p => p.rpc); +const allRpcs = [].concat(rpcsOf(0), rpcsOf(1), rpcsOf(2)); + +['fapai','jiaofen','shangzhuang','xuanzhu','maipai','chupai1','chupai2','chupai3','jiesuan'] + .forEach(rpc => t.eq('夹具覆盖 ' + rpc, allRpcs.indexOf(rpc) >= 0, true)); + +// 差异化下发确实存在:bottomcards 恰好只发给庄家一人(不是"少于三家"这种可被空集合蒙混过去的弱断言) +const withBottom = [0,1,2].filter(s => + fx.seats[s].some(p => p.rpc === 'shangzhuang' && p.data.hasOwnProperty('bottomcards'))); +t.eq('底牌恰好发给1个座位(庄家)', withBottom.length, 1); + +// deskinfo 快照必须覆盖全部 5 个阶段(下游任务要用 step3 埋牌快照做回归断言) +const steps = fx.deskinfo.map(d => d.step).filter((v, i, a) => a.indexOf(v) === i).sort((a,b) => a - b); +t.eq('deskinfo 覆盖全部阶段 1/2/3/5/6', steps, [1, 2, 3, 5, 6]); + +// roomtype 必须存在且非空,Task 12 要用它还原房间选项 +t.eq('roomtype 存在且非空', typeof fx.roomtype === 'string' && fx.roomtype.length > 0, true); + +// startwar 是增量路径的真实起点(开局握手,结构=deskwar=deskinfo),必须每座位各一份, +// 且不得混进 seats[] 数组头部——那会让全部 packetIndex 集体错位一位(见夹具补捕说明) +t.eq('startwar 存在且为 3 份', Array.isArray(fx.startwar) && fx.startwar.length, 3); +[0, 1, 2].forEach(s => { + const dw = fx.startwar[s] && fx.startwar[s].deskwar; + t.eq('startwar[' + s + '].deskwar 存在', !!dw, true); + t.eq('startwar[' + s + '] 结构=deskinfo(带 step/PlayerInfo/MyCards)', + dw && dw.hasOwnProperty('step') && dw.hasOwnProperty('PlayerInfo') && dw.hasOwnProperty('MyCards'), true); + t.eq('startwar[' + s + '].PlayerInfo 为开局初值(三家均 0)', dw && dw.PlayerInfo, [0, 0, 0]); +}); +// seats[] 的第一包必须仍是 fapai——确认 startwar 没有被误插进 seats[] 头部 +[0, 1, 2].forEach(s => { + t.eq('seats[' + s + '][0] 仍是 fapai(startwar 未混入 seats)', fx.seats[s][0].rpc, 'fapai'); +}); + +// packetIndex 必须落在 (0, 该座位包总数] 区间内,且与 info.step 自洽 +fx.deskinfo.forEach(d => { + const inRange = d.packetIndex > 0 && d.packetIndex <= fx.seats[d.seat].length; + t.eq('packetIndex 落在有效区间 (step' + d.step + ' seat' + d.seat + ')', inRange, true); + t.eq('deskinfo.step 与 info.step 一致 (seat' + d.seat + ' pi' + d.packetIndex + ')', d.step, d.info.step); +}); + +// C-1 回归锁:同一座位的两条 step5 快照,playproc(当前轮桌面牌/currseat/round)不得相同—— +// 否则说明 deskinfo 快照存的是活引用,被后续出牌篡改成了终局状态(曾经的真实缺陷) +const step5BySeat = {}; +fx.deskinfo.filter(d => d.step === 5).forEach(d => { + (step5BySeat[d.seat] = step5BySeat[d.seat] || []).push(d); +}); +Object.keys(step5BySeat).forEach(seat => { + const list = step5BySeat[seat]; + if (list.length < 2) { return; } + for (let i = 1; i < list.length; i++) { + const same = JSON.stringify(list[0].info.PushCards.playproc) === JSON.stringify(list[i].info.PushCards.playproc); + t.eq('seat' + seat + ' 两条 step5 快照 playproc 不相同(未被活引用污染)', same, false); + } +}); + +// I-3:推进阶段的下发包必须自带 step,且与该时刻的 deskinfo.step 属于同一套取值。 +// 前端不得按 rpc 名硬编码阶段(前端红线「数据驱动、前端无对局状态机」)。 +const STEP_OF = { fapai: 1, jiaofen: 1, shangzhuang: 2, xuanzhu: 3, maipai: 5, jiesuan: 6 }; +const noStep = [], badStep = []; +[0, 1, 2].forEach(s => fx.seats[s].forEach(p => { + if (!STEP_OF.hasOwnProperty(p.rpc)) { return; } + if (!p.data.hasOwnProperty('step')) { noStep.push(s + ':' + p.rpc); return; } + if (p.data.step !== STEP_OF[p.rpc]) { badStep.push(s + ':' + p.rpc + '=' + p.data.step); } +})); +t.eq('推进阶段的下发包都带 step', noStep, []); +t.eq('各包的 step 就是该时点的真实阶段', badStep, []); + +// I-3:选主/埋牌阶段的「轮到谁」必须由服务端显式给出(增量 nextseat / 重连分组的 seat) +const noNext = []; +[0, 1, 2].forEach(s => fx.seats[s].forEach(p => { + if ((p.rpc === 'shangzhuang' || p.rpc === 'xuanzhu') && !p.data.hasOwnProperty('nextseat')) { + noNext.push(s + ':' + p.rpc); + } +})); +t.eq('shangzhuang/xuanzhu 都带 nextseat(控制权)', noNext, []); +fx.deskinfo.filter(d => d.step === 2).forEach(d => { + t.eq('重连 ChooseMain 带控制权 seat (seat' + d.seat + ')', typeof d.info.ChooseMain.seat, 'number'); +}); +fx.deskinfo.filter(d => d.step === 3).forEach(d => { + t.eq('重连 BuryCards 带控制权 seat (seat' + d.seat + ')', typeof d.info.BuryCards.seat, 'number'); +}); + +// I-2:结算阶段的重连包必须与 jiesuan 推送给同一份结算数据(抠底明细 bottom 恒有; +// account 只在末局,本夹具是第 1/6 局,故两条路径都没有) +const jiesuanPk = fx.seats[0].filter(p => p.rpc === 'jiesuan')[0]; +t.eq('夹具的 jiesuan 带抠底明细 bottom', !!(jiesuanPk && jiesuanPk.data.bottom), true); +fx.deskinfo.filter(d => d.step === 6).forEach(d => { + t.eq('重连 Balance.bottom == jiesuan.bottom (seat' + d.seat + ')', + JSON.stringify(d.info.Balance.bottom), JSON.stringify(jiesuanPk.data.bottom)); + t.eq('非末局:两条路径都不带 account (seat' + d.seat + ')', + [d.info.Balance.hasOwnProperty('account'), jiesuanPk.data.hasOwnProperty('account')], [false, false]); +}); + +// 每个包都有 success(协议 §0.1:每个下发包的 data 必带 success) +const noSuccess = []; +[0,1,2].forEach(s => fx.seats[s].forEach(p => { + if (!p.data || !p.data.hasOwnProperty('success')) { noSuccess.push(s + ':' + p.rpc); } +})); +t.eq('每个下发包都带 success', noSuccess, []); + +process.exit(t.done('fixture') ? 0 : 1); diff --git a/client/tests/test_gamestate.js b/client/tests/test_gamestate.js new file mode 100644 index 0000000..d8e3a35 --- /dev/null +++ b/client/tests/test_gamestate.js @@ -0,0 +1,56 @@ +// GameState 骨架:初始值、reset 的清空范围、snapshot 深拷贝 +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'); + +// ---- 事件常量已追加进 EventBus.Events ---- +t.eq('事件常量非空', Object.keys(EQW_Events).length > 0, true); +t.eq('RESET 已入 EventBus.Events', EventBus.Events.EQW_RESET, EQW_Events.EQW_RESET); +t.eq('事件名互不重复', + Object.keys(EQW_Events).length, + Object.keys(EQW_Events).map(k => EQW_Events[k]).filter((v, i, a) => a.indexOf(v) === i).length); + +// ---- 初始值 ---- +t.eq('初始 mySeat', EQW_GameState.room.mySeat, -1); +t.eq('初始 banker', EQW_GameState.aset.banker, -1); +t.eq('初始 flower', EQW_GameState.aset.flower, 0); +t.eq('初始手牌', EQW_GameState.my.cards, []); +t.eq('初始三家叫分', EQW_GameState.call.calls, [null, null, null]); + +// ---- snapshot 是深拷贝、且不含方法 ---- +EQW_GameState.my.cards = [1, 2, 3]; +const snap = EQW_GameState.snapshot(); +t.eq('snapshot 取到值', snap.my.cards, [1, 2, 3]); +EQW_GameState.my.cards.push(4); +t.eq('snapshot 是深拷贝(不随后续修改)', snap.my.cards, [1, 2, 3]); +t.eq('snapshot 不含方法', typeof snap.reset, 'undefined'); +t.eq('snapshot 含全部分组', + Object.keys(snap).sort(), + ['aset', 'call', 'my', 'result', 'room', 'table', 'turn']); + +// ---- reset:清对局态,但 mySeat / options 不清(跨局不变)---- +EQW_GameState.room.mySeat = 2; +EQW_GameState.room.options = { climb: 1 }; +EQW_GameState.aset.banker = 1; +EQW_GameState.aset.flower = 3; +EQW_GameState.table.pushlist = [[1], [2]]; +EQW_GameState.result.aset = { grade: 5 }; +EQW_GameState.reset(); + +t.eq('reset 后 mySeat 保留', EQW_GameState.room.mySeat, 2); +t.eq('reset 后 options 保留', EQW_GameState.room.options, { climb: 1 }); +t.eq('reset 后 banker 清空', EQW_GameState.aset.banker, -1); +t.eq('reset 后 flower 清空', EQW_GameState.aset.flower, 0); +t.eq('reset 后 pushlist 清空', EQW_GameState.table.pushlist, []); +t.eq('reset 后 result 清空', EQW_GameState.result.aset, null); +t.eq('reset 后手牌清空', EQW_GameState.my.cards, []); + +// ---- reset 两次结果一致(幂等)---- +const afterFirst = EQW_GameState.snapshot(); +EQW_GameState.reset(); +t.eq('reset 幂等', EQW_GameState.snapshot(), afterFirst); + +process.exit(t.done('gamestate') ? 0 : 1); diff --git a/client/tests/test_handlers_bury.js b/client/tests/test_handlers_bury.js new file mode 100644 index 0000000..55e2b50 --- /dev/null +++ b/client/tests/test_handlers_bury.js @@ -0,0 +1,85 @@ +// 选主 / 埋牌 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/MainHandler.js'); +load('client/js/01_SubGame/codes/net/handlers/BuryHandler.js'); + +const S = EQW_GameState; +const evts = []; +Object.keys(EQW_Events).forEach(k => EventBus.on(EQW_Events[k], () => evts.push(k))); + +// ================= xuanzhu ================= +S.reset(); evts.length = 0; +EQW_MainHandler.handleXuanzhu({ success: true, banker: 1, flower: 3, step: 3, nextseat: 1, + countdown: 30, cards: [5, 6, 7] }); +t.eq('主牌花色', S.aset.flower, 3); +t.eq('庄家', S.aset.banker, 1); +t.eq('选主后手牌', S.my.cards, [5, 6, 7]); +t.eq('step 读包字段(埋牌=3)', S.aset.step, 3); +t.eq('控制权读包里的 nextseat', S.turn.seat, 1); +t.eq('倒计时', S.turn.countdown, 30); +t.eq('选主发事件', + ['EQW_MAIN_SET','EQW_HAND_CHANGED'].every(e => evts.indexOf(e) >= 0), true); + +// 闲家视角:没有 cards,不抹已有手牌 +S.reset(); S.my.cards = [1, 2]; +EQW_MainHandler.handleXuanzhu({ success: true, banker: 0, flower: 2, step: 3, nextseat: 0, countdown: 30 }); +t.eq('闲家手牌不被抹', S.my.cards, [1, 2]); +t.eq('闲家也拿到花色', S.aset.flower, 2); + +// 【守卫】阶段与控制权都必须来自包,不得按 rpc 名硬编码、也不得用本地 banker 反推。 +// 真实链路里 nextseat 恒 == banker,相等时"读哪个"分辨不出来;这里刻意让它们不同, +// 退回 `aset.step = 3` / `turn.seat = aset.banker` 的写法必转红。 +S.reset(); +EQW_MainHandler.handleXuanzhu({ success: true, banker: 0, flower: 2, step: 4, nextseat: 2, countdown: 30 }); +t.eq('xuanzhu step 照读包字段', S.aset.step, 4); +t.eq('xuanzhu 控制权取 nextseat,不取 banker', S.turn.seat, 2); + +// ================= maipai ================= +S.reset(); evts.length = 0; +EQW_BuryHandler.handle({ success: true, cards: [1,2,3], burycards: [9,10,11,12,13,14,15,16], + step: 5, seat: 1, countdown: 20 }); +t.eq('埋牌后手牌', S.my.cards, [1,2,3]); +t.eq('埋牌底牌', S.my.buryCards, [9,10,11,12,13,14,15,16]); +t.eq('step 读包字段(出牌=5)', S.aset.step, 5); +t.eq('首出者(控制权读包里的 seat)', S.turn.seat, 1); +t.eq('埋牌发事件', + ['EQW_BURY_DONE','EQW_HAND_CHANGED','EQW_TURN_CHANGED'].every(e => evts.indexOf(e) >= 0), true); + +// 闲家视角:无 cards/burycards,但可能有 liangpai +S.reset(); S.my.cards = [4, 5]; evts.length = 0; +EQW_BuryHandler.handle({ success: true, step: 5, seat: 0, countdown: 20, + liangpai: { cards: [52, 53, 2, 15] } }); +t.eq('闲家手牌不被抹', S.my.cards, [4, 5]); +t.eq('闲家无埋牌底牌', S.my.buryCards, []); +t.eq('亮牌数据', S.table.liangpai, { cards: [52, 53, 2, 15] }); + +// 不达标/不查牌:无 liangpai 字段,保持 null +S.reset(); +EQW_BuryHandler.handle({ success: true, step: 5, seat: 0, countdown: 20 }); +t.eq('无亮牌时保持 null', S.table.liangpai, null); + +// 【守卫】maipai 的 step 同样只读包字段 +S.reset(); +EQW_BuryHandler.handle({ success: true, step: 4, seat: 0, countdown: 20 }); +t.eq('maipai step 照读包字段(不按 rpc 名硬编码)', S.aset.step, 4); + +// playproc:埋牌完成时服务端已就地开好 round-1 的进行态,恒有、原样拷贝进 table.playproc, +// 与 chupai1/2/3.playproc 同源同结构(协议 §9),漏接会让「埋牌完成→庄家首出」这段窗口 +// 增量路径 table.playproc 停在上一阶段的 null、与重连路径不一致 +S.reset(); +EQW_BuryHandler.handle({ success: true, step: 5, seat: 0, countdown: 20, cards: [1, 2, 3], + burycards: [9, 10, 11, 12, 13, 14, 15, 16], + playproc: { round: 1, start: 0, currseat: 0, startcount: -1, + startflower: -1, starttype: -1, maxseat: -1, maxcard: -1, + cards: [null, null, null], shuai_demand: null } }); +t.eq('埋牌 playproc 原样拷贝服务端本轮进行态', S.table.playproc, { + round: 1, start: 0, currseat: 0, startcount: -1, startflower: -1, starttype: -1, + maxseat: -1, maxcard: -1, cards: [null, null, null], shuai_demand: null +}); + +process.exit(t.done('handlers_bury') ? 0 : 1); diff --git a/client/tests/test_handlers_call.js b/client/tests/test_handlers_call.js new file mode 100644 index 0000000..75f1c45 --- /dev/null +++ b/client/tests/test_handlers_call.js @@ -0,0 +1,96 @@ +// 发牌 / 叫分 / 上庄 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); diff --git a/client/tests/test_handlers_play.js b/client/tests/test_handlers_play.js new file mode 100644 index 0000000..508240f --- /dev/null +++ b/client/tests/test_handlers_play.js @@ -0,0 +1,107 @@ +// 出牌 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) +// 本墩结果(谁最大 / 这墩得几分)是一次性表现,走 EQW_TRICK_END 事件载荷,【不进 GameState】—— +// 任何 deskinfo 都不下发它,存进来就是一处服务端不知道的私有对局态(出牌中途重连即归零) +S.reset(); evts.length = 0; +let lastTrick = null; +EventBus.on(EQW_Events.EQW_TRICK_END, function (p) { lastTrick = p; }); +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('本墩结果走事件载荷', lastTrick, { seat: 2, cards: [9,10], maxseat: 0, grade: 25 }); +t.eq('本墩结果不进 GameState', S.result.hasOwnProperty('chupai'), false); +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,累计分不该被抹;事件载荷也不得凭空补 grade(缺字段不兜底) +S.reset(); S.aset.grade = 40; lastTrick = null; +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 时事件载荷不带该键', lastTrick.hasOwnProperty('grade'), false); +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); 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); diff --git a/client/tests/test_handlers_result.js b/client/tests/test_handlers_result.js new file mode 100644 index 0000000..f9d232d --- /dev/null +++ b/client/tests/test_handlers_result.js @@ -0,0 +1,85 @@ +// 结算 / 解散 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/ResultHandler.js'); + +const S = EQW_GameState; +const evts = []; +Object.keys(EQW_Events).forEach(k => EventBus.on(EQW_Events[k], () => evts.push(k))); +let lastTrick = null; +EventBus.on(EQW_Events.EQW_TRICK_END, p => { lastTrick = p; }); + +const ASET = { banker: 0, call: 60, multiple: 3, flower: 3, grade: 45, upgrade: 1, + bangwang: 0, climb: 0, + seatlist: [{ grade: 6, score: 6 }, { grade: -3, score: -3 }, { grade: -3, score: -3 }] }; + +// ================= 正常出牌结算 ================= +S.reset(); evts.length = 0; lastTrick = null; +EQW_ResultHandler.handleJiesuan({ success: true, step: 6, + chupai: { seat: 2, cards: [9], maxseat: 0, grade: 10 }, + bottom: { cards: [1,2,3,4,5,6,7,8], multiple: 2, grade1: 20, grade2: 40 }, + aset: ASET }); + +t.eq('小局结算入状态', S.result.aset, ASET); +t.eq('抠底入状态', S.result.bottom.grade2, 40); +// 收尾墩(服务端不发 chupai3,整包换成 jiesuan)走事件载荷,与 chupai3 同一对事件、同一份字段; +// 【不进 GameState】——deskinfo step6 的 Balance 只有 aset/bottom/account,多存就是私有对局态 +t.eq('收尾墩走事件载荷', lastTrick, { seat: 2, cards: [9], maxseat: 0, grade: 10 }); +t.eq('收尾墩也发本墩结束事件', evts.indexOf('EQW_TRICK_END') >= 0, true); +t.eq('收尾墩不进 GameState', S.result.hasOwnProperty('chupai'), false); +t.eq('step 读包字段(结算=6)', S.aset.step, 6); +t.eq('结算发事件', evts.indexOf('EQW_ASET_RESULT') >= 0, true); +t.eq('非末局不发大局结算事件', evts.indexOf('EQW_ACCOUNT_RESULT') < 0, true); + +// ================= 投降结算(无 chupai / 无 bottom)================= +S.reset(); evts.length = 0; lastTrick = null; +EQW_ResultHandler.handleJiesuan({ success: true, step: 6, aset: { banker: 0, call: 70, multiple: 1, + upgrade: -99, seatlist: [] } }); +t.eq('投降结算入状态', S.result.aset.upgrade, -99); +t.eq('投降无 chupai → 不发本墩结束事件', lastTrick, null); +t.eq('投降无 bottom', S.result.bottom, null); +t.eq('投降也发结算事件', evts.indexOf('EQW_ASET_RESULT') >= 0, true); + +// ================= 末局:带 account ================= +const ACCOUNT = [{ score: 12, grades: [6,6], grade_jf_total: 8, grade_cg_total: 4, grade_bw_total: 0 }, + { score: -6, grades: [-3,-3], grade_jf_total: -4, grade_cg_total: -2, grade_bw_total: 0 }, + { score: -6, grades: [-3,-3], grade_jf_total: -4, grade_cg_total: -2, grade_bw_total: 0 }]; +S.reset(); evts.length = 0; +EQW_ResultHandler.handleJiesuan({ success: true, step: 6, aset: ASET, account: ACCOUNT }); +t.eq('大局结算入状态', S.result.account, ACCOUNT); +t.eq('末局发大局结算事件', evts.indexOf('EQW_ACCOUNT_RESULT') >= 0, true); + +// ================= 解散:走 Free 入口,取值少一层 ================= +S.reset(); evts.length = 0; +EQW_ResultHandler.handleFree({ rpc: 'jiesuan', + data: { success: true, step: 6, aset: { banker: -1, call: -1, multiple: 0, upgrade: 0, seatlist: [] }, + account: ACCOUNT } }); +t.eq('解散取到 aset', S.result.aset.multiple, 0); +t.eq('解散取到 account', S.result.account, ACCOUNT); +t.eq('解散发大局结算事件', evts.indexOf('EQW_ACCOUNT_RESULT') >= 0, true); +t.eq('解散包的 step 也读包字段', S.aset.step, 6); + +// 【守卫】结算 step 只读包字段,不按 rpc 名硬编码为 6。 +// 退回 `aset.step = 6` 的写法必转红。 +S.reset(); +EQW_ResultHandler.handleJiesuan({ success: true, step: 4, aset: ASET }); +t.eq('jiesuan step 照读包字段', S.aset.step, 4); + +// ================= 解散:deskfree 为 null(首局发牌前解散)================= +S.reset(); evts.length = 0; +let boom = false; +try { EQW_ResultHandler.handleFree(null); } catch (e) { boom = true; } +t.eq('deskfree 为 null 不崩', boom, false); +t.eq('deskfree 为 null 不写状态', S.result.aset, null); +t.eq('deskfree 为 null 不发结算事件', evts.indexOf('EQW_ACCOUNT_RESULT') < 0, true); + +// deskfree 存在但 data 缺失也要容忍 +boom = false; +try { EQW_ResultHandler.handleFree({ rpc: 'jiesuan' }); } catch (e) { boom = true; } +t.eq('deskfree.data 缺失不崩', boom, false); + +process.exit(t.done('handlers_result') ? 0 : 1); diff --git a/client/tests/test_hooks.js b/client/tests/test_hooks.js new file mode 100644 index 0000000..c53df31 --- /dev/null +++ b/client/tests/test_hooks.js @@ -0,0 +1,72 @@ +// SubGameHooks 接线:平台入口 → 新架构 +const { load } = require('./_load'); +const t = require('./_assert')(); + +global.window = global; // 转发壳用 window.SubGameHooks 判定 + +load('client/js/gameabc-framework/system/EventBus.js'); +load('client/js/gameabc-framework/system/SpriteEventController.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'); +['DealHandler','CallHandler','MainHandler','BuryHandler','PlayHandler', + 'QueryHandler','ReadyHandler','ResultHandler','ResyncHandler','FailHandler'] + .forEach(h => load('client/js/01_SubGame/codes/net/handlers/' + h + '.js')); +load('client/js/01_SubGame/codes/net/Dispatcher.js'); +load('client/js/01_SubGame/codes/SubGameHooks.js'); + +const S = EQW_GameState; + +// ---- _ReceiveData:完整包 → 分发 ---- +S.reset(); S.room.mySeat = 0; +SubGameHooks._ReceiveData({ app: 'youle', route: 'erqiwang', rpc: 'fapai', + data: { success: true, asetidx: 1, asetcount: 6, + cards: [1,2], seat: 0, countdown: 15 } }); +t.eq('_ReceiveData 分发到 handler', S.my.cards, [1,2]); + +// ---- setRoomDes ---- +SubGameHooks.setRoomDes(123, 12, '00010'); +t.eq('setRoomDes 解析选项', S.room.options.climb, 1); + +// ---- Free:null 不崩 ---- +let boom = false; +try { SubGameHooks.Free(null); } catch (e) { boom = true; } +t.eq('Free(null) 不崩', boom, false); + +// ---- Reconnect ---- +S.reset(); S.room.mySeat = 1; +SubGameHooks.Reconnect({ count: 6, idx: 3, PlayerInfo: [0,0,0], step: 1, MyCards: [5,6] }); +t.eq('Reconnect 填状态', S.my.cards, [5,6]); +t.eq('Reconnect 填局数', S.room.asetIdx, 3); + +// ---- DeskInfo(中途进桌)---- +// 平台 07_Desk.js 在「牌局已开始 + 走加入房间而非重连」时调 Game_Modify.DeskInfo(_msg.data.deskinfo)。 +// 入参结构与 Reconnect 完全一致(服务端两条路径都是 get_deskinfo(o_room, 本人座位)), +// 故必须走同一条重画路径;不接线的话整份 deskinfo 被转发壳静默丢弃、界面空白且不报错。 +const MIDJOIN = { count: 6, idx: 2, PlayerInfo: [3,-1,-2], step: 5, MyCards: [10,11], + PushCards: { banker: 1, call: 60, multiple: 3, flower: 2, grade: 25, + curmultiple: -1, baozhu: 0, countdown: 30, + playproc: { round: 4, start: 1, currseat: 2, cards: [null,[12],null] } } }; +S.reset(); S.room.mySeat = 2; +SubGameHooks.DeskInfo(MIDJOIN); +t.eq('DeskInfo 已接线(不再静默丢包)', S.my.cards, [10,11]); +t.eq('DeskInfo 复用重连的重画路径(阶段)', S.aset.step, 5); +t.eq('DeskInfo 复用重连的重画路径(控制权读 playproc.currseat)', S.turn.seat, 2); +t.eq('DeskInfo 与 Reconnect 落地结果逐字段相同', + (function () { const a = JSON.stringify(EQW_GameState.snapshot()); + S.reset(); S.room.mySeat = 2; SubGameHooks.Reconnect(MIDJOIN); + return a === JSON.stringify(EQW_GameState.snapshot()); })(), true); + +// ---- 畸形入参一律不崩 ---- +boom = false; +try { + SubGameHooks._ReceiveData(null); + SubGameHooks._ReceiveData({}); + SubGameHooks.StartWar(null); + SubGameHooks.Reconnect(null); + SubGameHooks.DeskInfo(null); + SubGameHooks.setRoomDes(); +} catch (e) { boom = true; } +t.eq('畸形入参不崩', boom, false); + +process.exit(t.done('hooks') ? 0 : 1); diff --git a/client/tests/test_index_load_order.js b/client/tests/test_index_load_order.js new file mode 100644 index 0000000..c347d9f --- /dev/null +++ b/client/tests/test_index_load_order.js @@ -0,0 +1,109 @@ +// index.html 加载顺序自检(task-13 步骤6守卫): +// 按 index.html 里