- mod.js:加 module.exports 守卫,使其可被 Node require(友乐无 module 跳过) - test/_rpc.js(新):L3 脚手架——挂类全局、mock cls_mod/min_loadJsFile 装配 mod.js、 捕获 SendPack/sendpack_toother 下发包 - test/test_rpc.js(新):§9 明牌 mingpai(合法/不查牌·未报无主·非出牌阶段拒 + others结构)、 §9 出牌 chupai 的 info/baozhu 查牌门控(可查有/不查无)、§4 投降 touxiang 条件(70·step2·庄家)、 §4.2 mod.jiaofen 入参校验(>70/非5倍/首家必叫/后叫更低)、 §4 70分上庄暗牌亮牌(真实 jiaofen 驱动到上庄,验证 bottomcards/ancard3s 门控) - test/test_desk.js(新):§4.6 坐庄轮换 do_prepare(庄赢连庄/闲赢·投降下家) - 02-测试计划.md/README:P1 标记完成,剩余归入 P2 全部单测 156 项断言全绿(node server/games/erqiwang/test/run.js) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
557 lines
15 KiB
JavaScript
557 lines
15 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/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;
|
||
|
||
//叫分或不叫
|
||
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){
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是叫分阶段
|
||
if (o_paiju.step != 1){
|
||
return;
|
||
}
|
||
//检查当前的叫分位置
|
||
if (o_paiju.method.get_callgrade_seat() != seat){
|
||
return;
|
||
}
|
||
//检查叫分:0(不叫)或 5~70 且为 5 的整数倍(design §4)
|
||
if (call < 0 || call > 70 || call % 5 != 0){
|
||
return;
|
||
}
|
||
var curr_call = o_paiju.method.get_callgrade_value();
|
||
//第一家不能不叫分
|
||
if (curr_call == 0 && call == 0){
|
||
return;
|
||
}
|
||
//叫分必须小于之前的叫分
|
||
if (curr_call != 0 && call >= curr_call){
|
||
return;
|
||
}
|
||
|
||
//叫分成功
|
||
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.seat = seat;
|
||
msg.data.call = call;
|
||
msg.data.currcall = o_paiju.method.get_callgrade_value();
|
||
msg.data.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(msg.data.currcall);
|
||
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.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_multiple_bycall(msg.data.grade);
|
||
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();
|
||
|
||
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){
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是选主阶段
|
||
if (o_paiju.step != 2){
|
||
return;
|
||
}
|
||
//检查庄家位置
|
||
if (o_paiju.banker != seat){
|
||
return;
|
||
}
|
||
//检查花色
|
||
if (flower != 1 && flower != 2 && flower != 3 && flower != 4){
|
||
return;
|
||
}
|
||
|
||
//选主成功
|
||
o_paiju.method.do_choiceflower(flower);
|
||
|
||
//返回前端
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "xuanzhu";
|
||
msg.data = {};
|
||
msg.data.banker = o_paiju.banker;
|
||
msg.data.flower = flower;
|
||
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){
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是埋牌阶段
|
||
if (o_paiju.step != 3){
|
||
return;
|
||
}
|
||
//检查庄家位置
|
||
if (o_paiju.banker != seat){
|
||
return;
|
||
}
|
||
//检查埋牌是否是8张
|
||
if (cards.length != 8){
|
||
return;
|
||
}
|
||
//检查埋牌是否都在庄家手上
|
||
if (!o_paiju.method.check_cards_inhand(cards, seat)){
|
||
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.seat = o_paiju.playproc.currseat;
|
||
msg.data.countdown = o_desk.method.get_countdown_chupai();
|
||
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 = cards;
|
||
delete msg.data.liangpai;
|
||
} else {
|
||
delete msg.data.cards;
|
||
delete msg.data.bottomcards;
|
||
//闲家:可查牌且庄家达标时亮牌
|
||
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){
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
return;
|
||
}
|
||
//投降是选主阶段(step 2)与选主互斥的选择:点投降直接结算,不选主、不埋牌(design §4)
|
||
if (o_paiju.step != 2){
|
||
return;
|
||
}
|
||
//检查庄家位置
|
||
if (o_paiju.banker != seat){
|
||
return;
|
||
}
|
||
//仅 70 分坐庄可投降(design §4 / §7.1)
|
||
if (o_paiju.call != 70){
|
||
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){
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是出牌阶段
|
||
if (o_paiju.step != 5){
|
||
return;
|
||
}
|
||
//检查出牌位置
|
||
if (seat != o_paiju.playproc.currseat){
|
||
return;
|
||
}
|
||
//检查出牌
|
||
if (cards.length == 0){
|
||
return;
|
||
}
|
||
//检查出牌是否都在玩家手上
|
||
if (!o_paiju.method.check_cards_inhand(cards, seat)){
|
||
return;
|
||
}
|
||
|
||
//检查选中的牌是否可出,并出牌
|
||
var re = o_paiju.method.do_playcard(cards);
|
||
if (!re.result){
|
||
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.seat = seat;
|
||
msg.data.cards = re.cards ? re.cards : cards; //实际打出的牌(甩错时为单张)
|
||
if (!_cfg.nocheck){
|
||
msg.data.info = o_paiju.seatlist[seat]; //座位牌况(报副统计),仅可查牌下发
|
||
}
|
||
msg.data.nextseat = o_paiju.playproc.currseat;
|
||
msg.data.countdown = o_desk.method.get_countdown_chupai();
|
||
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;
|
||
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){
|
||
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;
|
||
}
|
||
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.info;
|
||
delete msg.data.nextseat;
|
||
delete msg.data.countdown;
|
||
delete msg.data.baozhu;
|
||
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){
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
return;
|
||
}
|
||
//只在出牌阶段
|
||
if (o_paiju.step != 5){
|
||
return;
|
||
}
|
||
//不查牌模式禁止明牌
|
||
var cfg = cls_youle_erqiwang_config.parse(o_room.roomtype);
|
||
if (cfg.nocheck){
|
||
return;
|
||
}
|
||
//必须已有玩家报无主才允许明牌
|
||
if (!o_paiju.method.have_baofu()){
|
||
return;
|
||
}
|
||
|
||
//另外两家未出的全部主牌,只回发给请求者
|
||
var msg = {};
|
||
msg.app = "youle";
|
||
msg.route = "erqiwang";
|
||
msg.rpc = "mingpai";
|
||
msg.data = {};
|
||
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);
|
||
}
|
||
|
||
//准备
|
||
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){
|
||
return;
|
||
}
|
||
//牌桌
|
||
var o_desk = o_room.o_desk;
|
||
if (!o_desk){
|
||
return;
|
||
}
|
||
//牌局
|
||
var o_paiju = o_desk.method.curr_paiju();
|
||
if (!o_paiju){
|
||
return;
|
||
}
|
||
//检查当前的游戏阶段是否是结算阶段
|
||
if (o_paiju.step != 6){
|
||
return;
|
||
}
|
||
//检查当前局数小于总局数
|
||
if (o_desk.paiju_list.length >= o_room.asetcount){
|
||
return;
|
||
}
|
||
|
||
//准备成功
|
||
o_desk.method.do_prepare(seat);
|
||
}
|
||
|
||
//Node(本地/单元测试)导出模块对象;友乐/浏览器无 module 时跳过(dev-guide 01 §1)
|
||
if (typeof module !== "undefined"){
|
||
module.exports = youle_erqiwang;
|
||
} |