Files
erqiwang_youle/server/games/erqiwang/mod.js
T
joywayerandClaude Opus 4.8 6120580505 二七王服务端:按 design.md 整改结算/算奖/甩牌,roomtype 改位串
结算与算奖(§6.3/§7/§8)
- 扣底倍数改线性:单张×1、主对×2、N连对×2N
- 算子实现常规算子 + 爬坡两套(大光×3、Q=40 / 爬坡分段),X=基础子数×判定倍率
- 常规算奖只计庄家;连对链修复(传实际主花色);移除 ≥10 老主误加奖与设计外的
  「冲关必叫/四王必踢」作废;算奖快照取庄家埋牌后28张(新增 get_seat_cards_award)
- 算奖并入结算改为 X×(2Ni−Nj−Nk);傍王按房间开关、庄闲每王1奖

出牌与甩牌(§5.4)
- 副牌绝对禁甩;甩牌最大性按对手全部手牌逐分量判定(新增 trump_rank/
  decompose_trump/opp_can_beat_flush);甩错惩罚(收回、只打最小一张、下发 shuaicuo)
- 修复 can_followcard 引用未定义 tuolaji_list 的崩溃、can_playcard 14张上限失效

开局/投降/暗牌(§4)
- 投降改为选主阶段(step2)与选主互斥、不埋牌、算奖用庄家36张;埋牌后直进出牌(删废弃step4)
- 70分暗牌摸牌前向所有玩家亮3秒(+ancard3s),非70分不再泄露暗牌给闲家;叫分上限≤70

查牌/亮牌(§8.2/§9)
- 新增亮牌 get_liangpai(庄家埋牌后28张按阈值统计);报无主统计与亮牌按查牌位门控
- 新增 mingpai 明牌 RPC(可查牌+已报无主时查看他家全部主牌)

房间与配置(§10)
- 局数6/12、房主扣卡2/4;新增 class.config.js 以位串解析 roomtype(唯一入口 SSOT)
- 清理 do_choiceflower 死代码

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 22:32:53 +08:00

552 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//////////////////////////////////////////////////////////////////
/////////////////////////////咻一咻/////////////////////////////
//////////////////////////////////////////////////////////////////
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);
}