/////////////////////////////////////////////////////////////// ////////// 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。 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); } //—— 末家出完:本轮结束,记下谁最大、闲家得了多少分 —— if (order === 3) { if (!EQW_GameState.result.chupai) { EQW_GameState.result.chupai = {}; } EQW_GameState._apply(EQW_GameState.result.chupai, data, { seat: 'seat', cards: 'cards', maxseat: 'maxseat', grade: 'grade' }); //本轮闲家得分累加进小局捡分;没得分时不带 grade,累计值保持不变 if (data.hasOwnProperty('grade')) { EQW_GameState.aset.grade = EQW_GameState.aset.grade + data.grade; } EventBus.emit(EQW_Events.EQW_TRICK_END); } 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; } };