792 lines
30 KiB
JavaScript
792 lines
30 KiB
JavaScript
//////////////////////////////////////////////////////////////////
|
||
/////////////////////////////咻一咻/////////////////////////////
|
||
//////////////////////////////////////////////////////////////////
|
||
|
||
var youle_erqiwang = youle_erqiwang || cls_mod.new("youle_erqiwang", "erqiwang", youle_app);
|
||
|
||
//配置解析类(roomtype 位串解析,SSOT)
|
||
min_loadJsFile("games/erqiwang/class.config.js", function(){
|
||
//牌类
|
||
min_loadJsFile("games/erqiwang/class.pai.js", function(){
|
||
//牌局类
|
||
min_loadJsFile("games/erqiwang/class.paiju.js", function(){
|
||
//牌桌类
|
||
min_loadJsFile("games/erqiwang/class.desk.js", function(){
|
||
//前后端共享牌值算法
|
||
min_loadJsFile("games/erqiwang/shared/cards.js", function(){
|
||
//算法类
|
||
min_loadJsFile("games/erqiwang/class.arith.js", function(){
|
||
//对内的输入接口类
|
||
min_loadJsFile("games/erqiwang/class.import.js", function(){
|
||
//对内的输出接口类
|
||
min_loadJsFile("games/erqiwang/class.export.js", function(){
|
||
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
//对内的输入接口类(需要的外部接口)
|
||
youle_erqiwang.import = null;
|
||
//对内的输出接口类(提供给外部使用的接口)
|
||
youle_erqiwang.export = null;
|
||
|
||
//失败码(协议层约定,非 design 玩法规则;详见 docs/protocol/packet_protocol.md)
|
||
//前端一律只看 data.success 判成败,errcode 仅用于区分失败原因、给出对应提示
|
||
youle_erqiwang.ERR = {
|
||
PLAYER: 1, //玩家/房间/座位校验不通过
|
||
NODESK: 2, //牌桌或牌局不存在
|
||
STEP: 3, //当前阶段不允许该操作
|
||
SEAT: 4, //位置不符,或还没轮到该玩家操作
|
||
PARAM: 5, //参数非法(类型/范围/张数/牌不在手上)
|
||
RULE: 6 //规则不允许(叫分未更低、出牌不合法、投降条件不满足、房间模式禁止等)
|
||
};
|
||
|
||
//失败回包:只回发给请求者,data.success=false
|
||
//(dev-guide server 03 §4/§5:成败标志唯一是 data.success,主动推送的 data 必须自带 success)
|
||
youle_erqiwang.do_sendfail = function(pack, rpc, errcode){
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = rpc;
|
||
msg.data = {};
|
||
msg.data.success = false;
|
||
msg.data.errcode = errcode;
|
||
msg.conmode = pack.conmode;
|
||
msg.fromid = pack.fromid;
|
||
youle_erqiwang.app.SendPack(msg);
|
||
}
|
||
|
||
//叫分或不叫
|
||
youle_erqiwang.jiaofen = function(pack){
|
||
var agentid = pack.data.agentid;
|
||
var playerid = parseInt(pack.data.playerid);
|
||
var gameid = pack.data.gameid;
|
||
var roomcode = parseInt(pack.data.roomcode);
|
||
var seat = parseInt(pack.data.seat);
|
||
var call = parseInt(pack.data.call);
|
||
//检查玩家的位置是否正确
|
||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||
if (!o_room){
|
||
youle_erqiwang.do_sendfail(pack, "jiaofen", youle_erqiwang.ERR.PLAYER);
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
youle_erqiwang.do_sendfail(pack, "jiaofen", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
youle_erqiwang.do_sendfail(pack, "jiaofen", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是叫分阶段
|
||
if (o_paiju.step != 1){
|
||
youle_erqiwang.do_sendfail(pack, "jiaofen", youle_erqiwang.ERR.STEP);
|
||
return;
|
||
}
|
||
//检查当前的叫分位置
|
||
if (o_paiju.method.get_callgrade_seat() != seat){
|
||
youle_erqiwang.do_sendfail(pack, "jiaofen", youle_erqiwang.ERR.SEAT);
|
||
return;
|
||
}
|
||
//检查叫分:0(不叫)或 5~70 且为 5 的整数倍(design §4)
|
||
if (call < 0 || call > 70 || call % 5 != 0){
|
||
youle_erqiwang.do_sendfail(pack, "jiaofen", youle_erqiwang.ERR.PARAM);
|
||
return;
|
||
}
|
||
var curr_call = o_paiju.method.get_callgrade_value();
|
||
//第一家不能不叫分
|
||
if (curr_call == 0 && call == 0){
|
||
youle_erqiwang.do_sendfail(pack, "jiaofen", youle_erqiwang.ERR.RULE);
|
||
return;
|
||
}
|
||
//叫分必须小于之前的叫分
|
||
if (curr_call != 0 && call >= curr_call){
|
||
youle_erqiwang.do_sendfail(pack, "jiaofen", youle_erqiwang.ERR.RULE);
|
||
return;
|
||
}
|
||
|
||
//本局房间可选规则:爬坡改变「叫分 → 基础子数」的取值(design §7.3.1),
|
||
//下发面的 multiple 必须与结算包 aset.multiple 用同一套规则,否则界面与结算不符
|
||
var _cfg = cls_youle_erqiwang_config.parse(o_room.roomtype);
|
||
|
||
//叫分成功
|
||
o_paiju.method.do_callgrade(call);
|
||
|
||
//返回前端
|
||
if (o_paiju.step == 1){
|
||
//等待下一家叫分
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "jiaofen";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
msg.data.seat = seat;
|
||
msg.data.call = call;
|
||
msg.data.currcall = o_paiju.method.get_callgrade_value();
|
||
msg.data.multiple = cls_youle_erqiwang_arith.get_base_bycall(msg.data.currcall, _cfg.climb);
|
||
//阶段:叫分尚未结束,仍是 step1。阶段唯一由服务端维护、并在包里显式给出——
|
||
//前端不得按 rpc 名反推(server 红线「状态机唯一在服务端 / 任何状态变更都要有包承载」)
|
||
msg.data.step = o_paiju.step;
|
||
//控制权:下一个叫分者(本包的「轮到谁」就是它,与重连包 CallRun.seat 同一个函数)
|
||
msg.data.nextseat = o_paiju.method.get_callgrade_seat();
|
||
msg.data.countdown = o_desk.method.get_countdown_jiaofen();
|
||
o_room.method.sendpack_toother(msg, -1);
|
||
return;
|
||
}
|
||
if (o_paiju.step == 2){
|
||
//等待庄家叫主
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "shangzhuang";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
msg.data.seat = seat;
|
||
msg.data.call = call;
|
||
msg.data.banker = o_paiju.banker;
|
||
msg.data.grade = o_paiju.call;
|
||
msg.data.multiple = cls_youle_erqiwang_arith.get_base_bycall(msg.data.grade, _cfg.climb);
|
||
//阶段:叫分结束、进入选主/投降(step2)。与重连包 deskinfo.step 同源同值
|
||
msg.data.step = o_paiju.step;
|
||
//控制权:选主与投降都只由庄家做(mod.xuanzhu / mod.touxiang 的 SEAT 校验就以 banker 为准),
|
||
//故这里的「轮到谁」= o_paiju.banker —— 权威值取自牌局对象本身,不是前端按 banker 自行推导。
|
||
//本包的 data.seat 是【最后一个叫分者】、不是控制权,别混用(协议 §4)
|
||
msg.data.nextseat = o_paiju.banker;
|
||
msg.data.countdown = o_desk.method.get_countdown_xuanzhu();
|
||
if (o_paiju.call != 70){
|
||
msg.data.touxiang = 0;
|
||
} else {
|
||
msg.data.touxiang = 1;
|
||
}
|
||
//70分坐庄:庄家摸底牌前,8张底牌向所有玩家亮3秒(design §4/§7.1)
|
||
if (o_paiju.call == 70){
|
||
msg.data.ancard3s = 1;
|
||
}
|
||
var _bottomcards = o_paiju.method.get_bottomcards();
|
||
//当前抓分倍数(design §7.2.0):此刻捡分为 0,即大光×3;供客户端顶部「抓分」角标显示
|
||
msg.data.curmultiple = o_paiju.method.get_curmultiple();
|
||
|
||
for (var i = 0; i < o_room.seatlist.length; i++) {
|
||
msg.conmode = o_room.seatlist[i].conmode;
|
||
msg.fromid = o_room.seatlist[i].fromid;
|
||
if (i == o_paiju.banker){
|
||
//庄家:手牌(含摸起的底牌)与底牌本身都可见
|
||
msg.data.cards = o_paiju.method.get_seat_cards(i);
|
||
msg.data.bottomcards = _bottomcards;
|
||
} else {
|
||
delete msg.data.cards;
|
||
//闲家:仅70分坐庄时才下发底牌(供亮3秒),否则底牌只有庄家可见
|
||
if (o_paiju.call == 70){
|
||
msg.data.bottomcards = _bottomcards;
|
||
} else {
|
||
delete msg.data.bottomcards;
|
||
}
|
||
}
|
||
youle_erqiwang.app.SendPack(msg);
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
//选主
|
||
youle_erqiwang.xuanzhu = function(pack){
|
||
var agentid = pack.data.agentid;
|
||
var playerid = parseInt(pack.data.playerid);
|
||
var gameid = pack.data.gameid;
|
||
var roomcode = parseInt(pack.data.roomcode);
|
||
var seat = parseInt(pack.data.seat);
|
||
var flower = parseInt(pack.data.flower);
|
||
//检查玩家的位置是否正确
|
||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||
if (!o_room){
|
||
youle_erqiwang.do_sendfail(pack, "xuanzhu", youle_erqiwang.ERR.PLAYER);
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
youle_erqiwang.do_sendfail(pack, "xuanzhu", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
youle_erqiwang.do_sendfail(pack, "xuanzhu", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是选主阶段
|
||
if (o_paiju.step != 2){
|
||
youle_erqiwang.do_sendfail(pack, "xuanzhu", youle_erqiwang.ERR.STEP);
|
||
return;
|
||
}
|
||
//检查庄家位置
|
||
if (o_paiju.banker != seat){
|
||
youle_erqiwang.do_sendfail(pack, "xuanzhu", youle_erqiwang.ERR.SEAT);
|
||
return;
|
||
}
|
||
//检查花色
|
||
if (flower != 1 && flower != 2 && flower != 3 && flower != 4){
|
||
youle_erqiwang.do_sendfail(pack, "xuanzhu", youle_erqiwang.ERR.PARAM);
|
||
return;
|
||
}
|
||
|
||
//选主成功
|
||
o_paiju.method.do_choiceflower(flower);
|
||
|
||
//返回前端
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "xuanzhu";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
msg.data.banker = o_paiju.banker;
|
||
msg.data.flower = flower;
|
||
//阶段:选主完成即进入埋牌(step3)。与重连包 deskinfo.step 同源同值
|
||
msg.data.step = o_paiju.step;
|
||
//控制权:埋牌只由庄家做(mod.maipai 的 SEAT 校验同样以 banker 为准),
|
||
//权威值取自牌局对象;前端不再自己套「埋牌 = 庄家」这条规则
|
||
msg.data.nextseat = o_paiju.banker;
|
||
msg.data.countdown = o_desk.method.get_countdown_maipai();
|
||
for (var i = 0; i < o_room.seatlist.length; i++) {
|
||
msg.conmode = o_room.seatlist[i].conmode;
|
||
msg.fromid = o_room.seatlist[i].fromid;
|
||
msg.data.cards = o_paiju.method.get_seat_cards(i);
|
||
youle_erqiwang.app.SendPack(msg);
|
||
}
|
||
}
|
||
|
||
//埋牌
|
||
youle_erqiwang.maipai = function(pack){
|
||
var agentid = pack.data.agentid;
|
||
var playerid = parseInt(pack.data.playerid);
|
||
var gameid = pack.data.gameid;
|
||
var roomcode = parseInt(pack.data.roomcode);
|
||
var seat = parseInt(pack.data.seat);
|
||
var cards = pack.data.cards;
|
||
//检查玩家的位置是否正确
|
||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||
if (!o_room){
|
||
youle_erqiwang.do_sendfail(pack, "maipai", youle_erqiwang.ERR.PLAYER);
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
youle_erqiwang.do_sendfail(pack, "maipai", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
youle_erqiwang.do_sendfail(pack, "maipai", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是埋牌阶段
|
||
if (o_paiju.step != 3){
|
||
youle_erqiwang.do_sendfail(pack, "maipai", youle_erqiwang.ERR.STEP);
|
||
return;
|
||
}
|
||
//检查庄家位置
|
||
if (o_paiju.banker != seat){
|
||
youle_erqiwang.do_sendfail(pack, "maipai", youle_erqiwang.ERR.SEAT);
|
||
return;
|
||
}
|
||
//检查埋牌入参本身是否合法(数组/整数牌id/范围/无重复),须先于 length 判断
|
||
if (!o_paiju.method.check_cards_valid(cards)){
|
||
youle_erqiwang.do_sendfail(pack, "maipai", youle_erqiwang.ERR.PARAM);
|
||
return;
|
||
}
|
||
//检查埋牌是否是8张
|
||
if (cards.length != 8){
|
||
youle_erqiwang.do_sendfail(pack, "maipai", youle_erqiwang.ERR.PARAM);
|
||
return;
|
||
}
|
||
//检查埋牌是否都在庄家手上
|
||
if (!o_paiju.method.check_cards_inhand(cards, seat)){
|
||
youle_erqiwang.do_sendfail(pack, "maipai", youle_erqiwang.ERR.PARAM);
|
||
return;
|
||
}
|
||
|
||
//埋牌成功
|
||
o_paiju.method.do_burycard(cards);
|
||
|
||
//亮牌(design §8.2):可查牌模式下,出牌开始向两个闲家亮出庄家手牌统计
|
||
var _cfg = cls_youle_erqiwang_config.parse(o_room.roomtype);
|
||
var _liangpai = null;
|
||
if (!_cfg.nocheck){
|
||
_liangpai = o_paiju.method.get_liangpai();
|
||
}
|
||
|
||
//返回前端
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "maipai";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
//阶段:埋牌完成即进入出牌(step5,无独立投降阶段)。与重连包 deskinfo.step 同源同值
|
||
msg.data.step = o_paiju.step;
|
||
//控制权:本包的 seat 就是「轮到谁」——即将首出的庄家,取自权威的 playproc.currseat
|
||
//(do_burycard 里 new_playround 刚写好),与重连包 PushCards.playproc.currseat 同源
|
||
msg.data.seat = o_paiju.playproc.currseat;
|
||
msg.data.countdown = o_desk.method.get_countdown_chupai();
|
||
if (!_cfg.nocheck){
|
||
//三家座位牌况(缺门/无对 + 报副统计),仅可查牌下发——与 chupai1/2/3、重连包
|
||
//PushCards.seatlist 同一份数据、同一道门控。埋牌完成时它是刚初始化的空表
|
||
//(每家 [[0,0],[0,0],[0,0],[0,0],[-1,-1]]),本身不含任何信息;但「埋牌完成 →
|
||
//庄家首出」这段窗口内前端也要有表可画,漏发会让增量路径此刻为空、重连路径却有表。
|
||
//可见性:表内只有「某家缺某花色/某花色无对」(由公开的跟牌行为推出)与报无主后
|
||
//全场公示的余主数量(design §9 明确要求向全体三人展示),故三家整表下发不构成泄露。
|
||
//【深拷贝】seatlist 是全程就地改写的活表,不把活对象挂进下发包(同 playproc/pushlist)
|
||
msg.data.seatlist = cls_youle_erqiwang_paiju.get_seatlist(o_paiju);
|
||
}
|
||
//本轮进行态(design §5.1):do_burycard 内部已调用 new_playround 就地初始化好了 round-1
|
||
//的进行态,与重连包 PushCards.playproc 同一个快照函数、同一结构(三家无差别)。
|
||
//没有它,「埋牌完成 → 庄家首出」这段窗口内增量路径的 table.playproc 是 null,
|
||
//重连路径却已有 round-1 初值,两条路径不一致。可见性:round/start/currseat 等全部
|
||
//由桌面公开信息推出,cards 此刻全空、shuai_demand 为 null,不含任何私密信息,
|
||
//与 chupai1/2/3.playproc 一样三家整体下发、不逐座位裁剪。
|
||
msg.data.playproc = cls_youle_erqiwang_paiju.get_playproc(o_paiju);
|
||
//埋牌底牌(本次埋下的 8 张):取服务端权威快照,【不回显客户端提交的 cards 原序】。
|
||
//get_burycard 与重连包 PushCards.burycards 是同一个函数、同一套按本局主牌花色的排序,
|
||
//两条路径因此顺序一致(SSOT);直接回显 cards 会把客户端的选牌顺序当成下发顺序。
|
||
var _burycards = cls_youle_erqiwang_paiju.get_burycard(o_paiju);
|
||
for (var i = 0; i < o_room.seatlist.length; i++) {
|
||
msg.conmode = o_room.seatlist[i].conmode;
|
||
msg.fromid = o_room.seatlist[i].fromid;
|
||
if (i == o_paiju.banker){
|
||
msg.data.cards = o_paiju.method.get_seat_cards(i);
|
||
//埋牌底牌(本次埋下的 8 张)。与 shangzhuang.bottomcards(底牌,发牌留桌 8 张)
|
||
//是两批不同的牌,字段名刻意区分,勿混用
|
||
msg.data.burycards = _burycards;
|
||
delete msg.data.liangpai;
|
||
} else {
|
||
delete msg.data.cards;
|
||
delete msg.data.burycards;
|
||
//闲家:可查牌且庄家达标时亮牌
|
||
if (_liangpai){
|
||
msg.data.liangpai = _liangpai;
|
||
} else {
|
||
delete msg.data.liangpai;
|
||
}
|
||
}
|
||
youle_erqiwang.app.SendPack(msg);
|
||
}
|
||
}
|
||
|
||
//投降
|
||
youle_erqiwang.touxiang = function(pack){
|
||
var agentid = pack.data.agentid;
|
||
var playerid = parseInt(pack.data.playerid);
|
||
var gameid = pack.data.gameid;
|
||
var roomcode = parseInt(pack.data.roomcode);
|
||
var seat = parseInt(pack.data.seat);
|
||
//检查玩家的位置是否正确
|
||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||
if (!o_room){
|
||
youle_erqiwang.do_sendfail(pack, "touxiang", youle_erqiwang.ERR.PLAYER);
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
youle_erqiwang.do_sendfail(pack, "touxiang", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
youle_erqiwang.do_sendfail(pack, "touxiang", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//投降是选主阶段(step 2)与选主互斥的选择:点投降直接结算,不选主、不埋牌(design §4)
|
||
if (o_paiju.step != 2){
|
||
youle_erqiwang.do_sendfail(pack, "touxiang", youle_erqiwang.ERR.STEP);
|
||
return;
|
||
}
|
||
//检查庄家位置
|
||
if (o_paiju.banker != seat){
|
||
youle_erqiwang.do_sendfail(pack, "touxiang", youle_erqiwang.ERR.SEAT);
|
||
return;
|
||
}
|
||
//仅 70 分坐庄可投降(design §4 / §7.1)
|
||
if (o_paiju.call != 70){
|
||
youle_erqiwang.do_sendfail(pack, "touxiang", youle_erqiwang.ERR.RULE);
|
||
return;
|
||
}
|
||
|
||
//投降结算
|
||
o_paiju.step = 6;
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "jiesuan";
|
||
msg.data = {};
|
||
msg = o_paiju.method.get_paiju_account(1, msg);
|
||
o_room.method.sendpack_toother(msg, -1);
|
||
}
|
||
|
||
//出牌
|
||
youle_erqiwang.chupai = function(pack){
|
||
var agentid = pack.data.agentid;
|
||
var playerid = parseInt(pack.data.playerid);
|
||
var gameid = pack.data.gameid;
|
||
var roomcode = parseInt(pack.data.roomcode);
|
||
var seat = parseInt(pack.data.seat);
|
||
var cards = pack.data.cards;
|
||
//检查玩家的位置是否正确
|
||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||
if (!o_room){
|
||
youle_erqiwang.do_sendfail(pack, "chupai", youle_erqiwang.ERR.PLAYER);
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
youle_erqiwang.do_sendfail(pack, "chupai", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
youle_erqiwang.do_sendfail(pack, "chupai", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是出牌阶段
|
||
if (o_paiju.step != 5){
|
||
youle_erqiwang.do_sendfail(pack, "chupai", youle_erqiwang.ERR.STEP);
|
||
return;
|
||
}
|
||
//检查出牌位置
|
||
if (seat != o_paiju.playproc.currseat){
|
||
youle_erqiwang.do_sendfail(pack, "chupai", youle_erqiwang.ERR.SEAT);
|
||
return;
|
||
}
|
||
//检查出牌入参本身是否合法(数组/整数牌id/范围/无重复,含非空)
|
||
if (!o_paiju.method.check_cards_valid(cards)){
|
||
youle_erqiwang.do_sendfail(pack, "chupai", youle_erqiwang.ERR.PARAM);
|
||
return;
|
||
}
|
||
//检查出牌是否都在玩家手上
|
||
if (!o_paiju.method.check_cards_inhand(cards, seat)){
|
||
youle_erqiwang.do_sendfail(pack, "chupai", youle_erqiwang.ERR.PARAM);
|
||
return;
|
||
}
|
||
|
||
//检查选中的牌是否可出,并出牌
|
||
var re = o_paiju.method.do_playcard(cards);
|
||
if (!re.result){
|
||
youle_erqiwang.do_sendfail(pack, "chupai", youle_erqiwang.ERR.RULE);
|
||
return;
|
||
}
|
||
|
||
//查牌模式(design §9):不查牌时不下发任何主牌统计(info/报无主)
|
||
var _cfg = cls_youle_erqiwang_config.parse(o_room.roomtype);
|
||
|
||
//返回前端
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
msg.data.seat = seat;
|
||
//实际打出的牌(甩错时为单张),顺序由 do_playcard 的 order_playcards 统一给出——
|
||
//与 playproc.cards、重连包 PushCards.pushlist 同一个口径(SSOT)。
|
||
//【不回落到入参 cards】那是客户端的点击顺序,服务端不采信、更不回显(server 04 §8);
|
||
//do_playcard 成功时三个分支都会给出 re.cards,缺了就是它的缺陷,显式暴露、不兜底。
|
||
msg.data.cards = re.cards;
|
||
if (!_cfg.nocheck){
|
||
//三家座位牌况(缺门/无对 + 报副统计),仅可查牌下发。
|
||
//必须整表下发、不能只带出牌者一家:design §9 要求「一旦有玩家报无主,就为全体三人
|
||
//显示另外两家各自的主牌数量与对子数」,服务端已在 do_playcard 里同时刷新三个座位
|
||
//(class.paiju.js),只带一家会让另两家的统计滞后到它们各自出牌时才补上。
|
||
//字段名与重连包 PushCards.seatlist 一致,前端两条路径复用同一份解析。
|
||
//【深拷贝】seatlist 是全程就地改写的活表(本函数下方 do_playcard 每手都在改它),
|
||
//不把活对象挂进下发包(同 playproc/pushlist 的理由)
|
||
msg.data.seatlist = cls_youle_erqiwang_paiju.get_seatlist(o_paiju);
|
||
}
|
||
msg.data.nextseat = o_paiju.playproc.currseat;
|
||
msg.data.countdown = o_desk.method.get_countdown_chupai();
|
||
//当前抓分倍数(design §7.2.0):随本包一起下发、不另开推送;三家同值,grade 本就公开
|
||
msg.data.curmultiple = o_paiju.method.get_curmultiple();
|
||
//本轮进行态(design §5.1):与重连包 PushCards.playproc 同一个快照函数、同一结构。
|
||
//没有它,前端在非重连时就拿不到「本轮谁先手/谁最大/三家各出了什么/第几轮」,只能自己
|
||
//累积推导——那就是把对局推进逻辑搬到前端(前端红线「数据驱动、前端无对局状态机」)。
|
||
//注意 re.idx==3 时 do_playcard 已在内部调用 new_playround 就地开了新一轮,
|
||
//故 chupai3 带的是【新一轮】的进行态(与 nextseat 同步),这与此刻重连拿到的完全一致。
|
||
//可见性:round/start/currseat/startcount/startflower/starttype/maxseat/maxcard 都是
|
||
//由桌面公开信息直接推出的;cards 就是本轮已摊在桌上的牌;shuai_demand 是首家甩牌的
|
||
//分量构成,而甩出的牌本身已公开、chupai1 也已把等价的 shuai 下发给三家。全部对三家可见,
|
||
//无需逐座位裁剪(重连包 PushCards.playproc 本就对三家无差别下发,两条路径口径一致)
|
||
msg.data.playproc = cls_youle_erqiwang_paiju.get_playproc(o_paiju);
|
||
if (re.shuaicuo){
|
||
msg.data.shuaicuo = 1; //甩错:本次甩牌被收回,只打出了最小一张(§5.4.5)
|
||
}
|
||
//报无主:仅可查牌模式下才据此展示他家主牌数量/对子(§9)
|
||
if (!_cfg.nocheck && o_paiju.method.have_baofu()){
|
||
msg.data.baozhu = 1;
|
||
} else {
|
||
msg.data.baozhu = 0;
|
||
}
|
||
|
||
switch (re.idx)
|
||
{
|
||
case 1: //第一个出牌
|
||
msg.rpc = "chupai1";
|
||
msg.data.count = re.count;
|
||
msg.data.flower = re.flower;
|
||
msg.data.cardtype = re.cardtype;
|
||
//合法甩牌的分量构成(design §5.4.3),供前端按 拖拉机/对子/单张 分组展示;
|
||
//非甩牌(含甩错退化为单张)无此属性
|
||
if (re.shuai){
|
||
msg.data.shuai = re.shuai;
|
||
}
|
||
break;
|
||
case 2: //第二个出牌
|
||
msg.rpc = "chupai2";
|
||
break;
|
||
case 3: //第三个出牌
|
||
msg.rpc = "chupai3";
|
||
msg.data.maxseat = re.maxseat;
|
||
if (re.grade > 0){
|
||
msg.data.grade = re.grade;
|
||
// msg.data.gradecards = re.gradecards;
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (o_paiju.step == 5){
|
||
//下一个出牌者本轮跟牌的必出牌(design §5.2),供其客户端自动选中。
|
||
//【只发给 nextseat 一家】——mustcard 是该玩家自己手牌的子集,发给他本人不泄露;
|
||
//整表下发三家会把另两家的手牌结构透露出去(server 红线:发全 ≠ 发多,按可见性下发)。
|
||
var _nextseat = o_paiju.playproc.currseat;
|
||
var _mustcard = o_paiju.method.get_mustcard(_nextseat);
|
||
for (var i = 0; i < o_room.seatlist.length; i++){
|
||
msg.conmode = o_room.seatlist[i].conmode;
|
||
msg.fromid = o_room.seatlist[i].fromid;
|
||
if (i == seat){
|
||
msg.data.cardsinhand = o_paiju.method.get_seat_cards(i);
|
||
} else {
|
||
delete msg.data.cardsinhand;
|
||
}
|
||
if (i == _nextseat && _mustcard){
|
||
msg.data.mustcard = _mustcard;
|
||
} else {
|
||
delete msg.data.mustcard;
|
||
}
|
||
youle_erqiwang.app.SendPack(msg);
|
||
}
|
||
} else if (o_paiju.step == 6){
|
||
//小局结算
|
||
//出牌包的调整
|
||
msg.data.chupai = {};
|
||
msg.data.chupai.seat = msg.data.seat;
|
||
msg.data.chupai.cards = msg.data.cards;
|
||
msg.data.chupai.maxseat = msg.data.maxseat;
|
||
if (msg.data.grade){
|
||
msg.data.chupai.grade = msg.data.grade;
|
||
}
|
||
if (msg.data.gradecards){
|
||
msg.data.chupai.gradecards = msg.data.gradecards;
|
||
}
|
||
delete msg.data.seat;
|
||
delete msg.data.cards;
|
||
delete msg.data.seatlist;
|
||
delete msg.data.nextseat;
|
||
delete msg.data.countdown;
|
||
delete msg.data.baozhu;
|
||
//本轮进行态只对「还要继续出牌」有意义;本包已转为 jiesuan,不带
|
||
delete msg.data.playproc;
|
||
delete msg.data.cardsinhand;
|
||
delete msg.data.maxseat;
|
||
delete msg.data.grade;
|
||
delete msg.data.gradecards;
|
||
|
||
//获取抠底包
|
||
msg = o_paiju.method.get_bottom_account(msg);
|
||
//获取小局结算包
|
||
msg = o_paiju.method.get_paiju_account(0, msg);
|
||
o_room.method.sendpack_toother(msg, -1);
|
||
}
|
||
}
|
||
|
||
//明牌:查看另外两家手中全部主牌的具体牌面(design §9,仅可查牌模式且已有人报无主)
|
||
youle_erqiwang.mingpai = function(pack){
|
||
var agentid = pack.data.agentid;
|
||
var playerid = parseInt(pack.data.playerid);
|
||
var gameid = pack.data.gameid;
|
||
var roomcode = parseInt(pack.data.roomcode);
|
||
var seat = parseInt(pack.data.seat);
|
||
//检查玩家的位置是否正确
|
||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||
if (!o_room){
|
||
youle_erqiwang.do_sendfail(pack, "mingpai", youle_erqiwang.ERR.PLAYER);
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
youle_erqiwang.do_sendfail(pack, "mingpai", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
youle_erqiwang.do_sendfail(pack, "mingpai", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//只在出牌阶段
|
||
if (o_paiju.step != 5){
|
||
youle_erqiwang.do_sendfail(pack, "mingpai", youle_erqiwang.ERR.STEP);
|
||
return;
|
||
}
|
||
//不查牌模式禁止明牌
|
||
var cfg = cls_youle_erqiwang_config.parse(o_room.roomtype);
|
||
if (cfg.nocheck){
|
||
youle_erqiwang.do_sendfail(pack, "mingpai", youle_erqiwang.ERR.RULE);
|
||
return;
|
||
}
|
||
//必须已有玩家报无主才允许明牌
|
||
if (!o_paiju.method.have_baofu()){
|
||
youle_erqiwang.do_sendfail(pack, "mingpai", youle_erqiwang.ERR.RULE);
|
||
return;
|
||
}
|
||
|
||
//另外两家未出的全部主牌,只回发给请求者
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "mingpai";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
msg.data.seat = seat;
|
||
msg.data.others = [];
|
||
for (var i = 0; i < 3; i++){
|
||
if (i != seat){
|
||
var one = {};
|
||
one.seat = i;
|
||
one.zhucards = cls_youle_erqiwang_paiju.get_seat_zhucards(o_paiju, i);
|
||
msg.data.others.push(one);
|
||
}
|
||
}
|
||
msg.conmode = pack.conmode;
|
||
msg.fromid = pack.fromid;
|
||
youle_erqiwang.app.SendPack(msg);
|
||
}
|
||
|
||
//闲家出牌提示(design §11):闲家在出牌阶段向对家(另一闲家)发"踩/没分/有分"信号。
|
||
//纯转发、不校验真实性、只发给对家;庄家无对家,不参与。tip:1=踩 2=没分 3=有分。
|
||
youle_erqiwang.tishi = function(pack){
|
||
var agentid = pack.data.agentid;
|
||
var playerid = parseInt(pack.data.playerid);
|
||
var gameid = pack.data.gameid;
|
||
var roomcode = parseInt(pack.data.roomcode);
|
||
var seat = parseInt(pack.data.seat);
|
||
var tip = parseInt(pack.data.tip);
|
||
//检查玩家的位置是否正确
|
||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||
if (!o_room){
|
||
youle_erqiwang.do_sendfail(pack, "tishi", youle_erqiwang.ERR.PLAYER);
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
youle_erqiwang.do_sendfail(pack, "tishi", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
youle_erqiwang.do_sendfail(pack, "tishi", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//只在出牌阶段(step5)任意时刻可发
|
||
if (o_paiju.step != 5){
|
||
youle_erqiwang.do_sendfail(pack, "tishi", youle_erqiwang.ERR.STEP);
|
||
return;
|
||
}
|
||
//只有闲家可发提示,庄家无对家、不参与
|
||
if (seat == o_paiju.banker){
|
||
youle_erqiwang.do_sendfail(pack, "tishi", youle_erqiwang.ERR.RULE);
|
||
return;
|
||
}
|
||
//提示类型:1=踩 2=没分 3=有分
|
||
if (tip != 1 && tip != 2 && tip != 3){
|
||
youle_erqiwang.do_sendfail(pack, "tishi", youle_erqiwang.ERR.PARAM);
|
||
return;
|
||
}
|
||
|
||
//对家 = 另一个闲家(三座位号之和为 0+1+2=3)
|
||
var partner = 3 - o_paiju.banker - seat;
|
||
|
||
//只转发给对家
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "tishi";
|
||
msg.data = {};
|
||
msg.data.success = true;
|
||
msg.data.seat = seat; //发出提示的闲家
|
||
msg.data.tip = tip;
|
||
msg.conmode = o_room.seatlist[partner].conmode;
|
||
msg.fromid = o_room.seatlist[partner].fromid;
|
||
youle_erqiwang.app.SendPack(msg);
|
||
}
|
||
|
||
//准备
|
||
youle_erqiwang.zhunbei = function(pack){
|
||
var agentid = pack.data.agentid;
|
||
var playerid = parseInt(pack.data.playerid);
|
||
var gameid = pack.data.gameid;
|
||
var roomcode = parseInt(pack.data.roomcode);
|
||
var seat = parseInt(pack.data.seat);
|
||
//检查玩家的位置是否正确
|
||
var o_room = youle_erqiwang.import.check_player(agentid, gameid, roomcode, seat, playerid, pack.conmode, pack.fromid);
|
||
if (!o_room){
|
||
youle_erqiwang.do_sendfail(pack, "zhunbei", youle_erqiwang.ERR.PLAYER);
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
youle_erqiwang.do_sendfail(pack, "zhunbei", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
youle_erqiwang.do_sendfail(pack, "zhunbei", youle_erqiwang.ERR.NODESK);
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是结算阶段
|
||
if (o_paiju.step != 6){
|
||
youle_erqiwang.do_sendfail(pack, "zhunbei", youle_erqiwang.ERR.STEP);
|
||
return;
|
||
}
|
||
//检查当前局数小于总局数
|
||
if (o_desk.paiju_list.length >= o_room.asetcount){
|
||
youle_erqiwang.do_sendfail(pack, "zhunbei", youle_erqiwang.ERR.RULE);
|
||
return;
|
||
}
|
||
|
||
//准备成功
|
||
o_desk.method.do_prepare(seat);
|
||
}
|
||
|
||
//Node(本地/单元测试)导出模块对象;友乐/浏览器无 module 时跳过(dev-guide 01 §1)
|
||
if (typeof module !== "undefined"){
|
||
module.exports = youle_erqiwang;
|
||
}
|