二七王服务端:按 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>
This commit is contained in:
@@ -3,19 +3,66 @@
|
||||
///////////////////////////////////////////////////
|
||||
var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
|
||||
//根据叫分计算倍数
|
||||
get_multiple_bycall: function(callgrade){
|
||||
if (callgrade > 60){
|
||||
return 1;
|
||||
}
|
||||
if (callgrade > 40){
|
||||
//根据叫分计算基础子数(design §7.1 常规算子 / §7.3.1 爬坡)
|
||||
//climb: 是否勾选爬坡。70 分打牌基础子数 2;投降(基础1)在结算处特殊处理
|
||||
get_base_bycall: function(callgrade, climb){
|
||||
if (callgrade == 70){
|
||||
return 2;
|
||||
}
|
||||
if (callgrade > 0){
|
||||
return 4;
|
||||
if (!climb){
|
||||
//常规算子:65→2 60→3 55→4 50及以下统一6
|
||||
if (callgrade == 65) return 2;
|
||||
if (callgrade == 60) return 3;
|
||||
if (callgrade == 55) return 4;
|
||||
if (callgrade >= 5 && callgrade <= 50) return 6;
|
||||
return 0;
|
||||
}
|
||||
//爬坡:65→2 起每低5分+1子,直到5分→15
|
||||
switch (callgrade){
|
||||
case 65: return 2;
|
||||
case 60: return 3;
|
||||
case 55: return 4;
|
||||
case 50: return 6;
|
||||
case 45: return 7;
|
||||
case 40: return 8;
|
||||
case 35: return 9;
|
||||
case 30: return 10;
|
||||
case 25: return 11;
|
||||
case 20: return 12;
|
||||
case 15: return 13;
|
||||
case 10: return 14;
|
||||
case 5: return 15;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
|
||||
//小光/过庄分界与升级级距 Q(design §7.2.0 常规固定40 / §7.3.2 爬坡分段)
|
||||
get_qvalue: function(callgrade, climb){
|
||||
if (!climb){
|
||||
return 40;
|
||||
}
|
||||
if (callgrade >= 45){
|
||||
return 40;
|
||||
}
|
||||
if (callgrade == 40 || callgrade == 35){
|
||||
return 20;
|
||||
}
|
||||
if (callgrade == 30 || callgrade == 25){
|
||||
return 15;
|
||||
}
|
||||
if (callgrade == 20 || callgrade == 15){
|
||||
return 10;
|
||||
}
|
||||
if (callgrade == 10 || callgrade == 5){
|
||||
return 5;
|
||||
}
|
||||
return 40;
|
||||
},
|
||||
|
||||
//(兼容)叫分对应的基础子数,供叫分阶段展示,默认按常规算子取值
|
||||
get_multiple_bycall: function(callgrade){
|
||||
return cls_youle_erqiwang_arith.get_base_bycall(callgrade, false);
|
||||
},
|
||||
|
||||
//牌id转花色
|
||||
id_to_flower: function(cardid){
|
||||
@@ -231,8 +278,104 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
return tuolaji;
|
||||
},
|
||||
|
||||
//主牌归一化大小(副7 同级取 7000、副2 同级取 2000,其余用牌编码),仅用于甩牌分量比大小
|
||||
trump_rank: function(mainflower, cardid){
|
||||
var code = cls_youle_erqiwang_arith.id_to_code(mainflower, cardid);
|
||||
if (code > 7000 && code < 8000){ //副7
|
||||
return 7000;
|
||||
}
|
||||
if (code > 2000 && code < 3000){ //副2
|
||||
return 2000;
|
||||
}
|
||||
return code;
|
||||
},
|
||||
|
||||
//把一组主牌分解为分量列表(design §5.4.3/§5.4.4)
|
||||
//返回 [{type:'single'|'pair'|'tractor', len, value, cards:[]}...],value 为该分量最大牌的归一化 rank
|
||||
decompose_trump: function(mainflower, cards){
|
||||
var ordered = cls_youle_erqiwang_arith.order_cards(mainflower, cards.concat());
|
||||
//先分出对子与单张
|
||||
var pairs = []; //{code, cards:[id,id]}
|
||||
var singles = []; //{card}
|
||||
var i = 0;
|
||||
while (i < ordered.length){
|
||||
var code_i = cls_youle_erqiwang_arith.id_to_code(mainflower, ordered[i]);
|
||||
if (i + 1 < ordered.length){
|
||||
var code_n = cls_youle_erqiwang_arith.id_to_code(mainflower, ordered[i + 1]);
|
||||
if (code_i == code_n){
|
||||
pairs.push({code: code_i, cards: [ordered[i], ordered[i + 1]]});
|
||||
i = i + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
singles.push(ordered[i]);
|
||||
i++;
|
||||
}
|
||||
//连续对子组成拖拉机,其余为单独对子
|
||||
var comps = [];
|
||||
var j = 0;
|
||||
while (j < pairs.length){
|
||||
var run = [pairs[j]];
|
||||
var k = j;
|
||||
while (k + 1 < pairs.length && cls_youle_erqiwang_arith.is_continuous(pairs[k].code, pairs[k + 1].code)){
|
||||
run.push(pairs[k + 1]);
|
||||
k++;
|
||||
}
|
||||
if (run.length >= 2){
|
||||
var tcards = [];
|
||||
for (var r = 0; r < run.length; r++){
|
||||
tcards = tcards.concat(run[r].cards);
|
||||
}
|
||||
comps.push({type: "tractor", len: run.length, value: cls_youle_erqiwang_arith.trump_rank(mainflower, run[0].cards[0]), cards: tcards});
|
||||
} else {
|
||||
comps.push({type: "pair", len: 1, value: cls_youle_erqiwang_arith.trump_rank(mainflower, run[0].cards[0]), cards: run[0].cards});
|
||||
}
|
||||
j = k + 1;
|
||||
}
|
||||
for (var s = 0; s < singles.length; s++){
|
||||
comps.push({type: "single", len: 1, value: cls_youle_erqiwang_arith.trump_rank(mainflower, singles[s]), cards: [singles[s]]});
|
||||
}
|
||||
return comps;
|
||||
},
|
||||
|
||||
//甩牌最大性判定(design §5.4.2):两名对手是否持有能压过甩牌任一分量的主牌/主对/主拖拉机
|
||||
//comps:甩牌分量;opp_zhulist:各对手未出的主牌id列表数组。返回 true 表示有人能压(甩错)
|
||||
opp_can_beat_flush: function(mainflower, comps, opp_zhulist){
|
||||
for (var o = 0; o < opp_zhulist.length; o++){
|
||||
var zhu = cls_youle_erqiwang_arith.order_cards(mainflower, opp_zhulist[o].concat());
|
||||
var pairs = cls_youle_erqiwang_arith.get_pairlist(mainflower, zhu);
|
||||
for (var c = 0; c < comps.length; c++){
|
||||
var comp = comps[c];
|
||||
if (comp.type == "single"){
|
||||
//对手持有任一 rank 更大的主牌单张即可压
|
||||
for (var a = 0; a < zhu.length; a++){
|
||||
if (cls_youle_erqiwang_arith.trump_rank(mainflower, zhu[a]) > comp.value){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (comp.type == "pair"){
|
||||
//对手持有 rank 更大的主对即可压
|
||||
for (var p = 0; p < pairs.length; p++){
|
||||
if (cls_youle_erqiwang_arith.trump_rank(mainflower, pairs[p][0]) > comp.value){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//拖拉机:对手持有同长度、顶牌 rank 更大的主拖拉机即可压
|
||||
var tlist = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, pairs, 300 + comp.len);
|
||||
for (var t = 0; t < tlist.length; t++){
|
||||
if (cls_youle_erqiwang_arith.trump_rank(mainflower, tlist[t][0]) > comp.value){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
//第一个位置是否可出牌
|
||||
can_playcard: function(mainflower, cards, selfseat, seatlist){
|
||||
can_playcard: function(mainflower, cards, selfseat, seatlist, opp_zhulist){
|
||||
/*
|
||||
参数:
|
||||
mainflower: 主牌花色
|
||||
@@ -273,7 +416,7 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
|
||||
//规则外的约定,一次最多只能出14张牌
|
||||
if (cards.length > 14){
|
||||
do_returnfalse;
|
||||
do_returnfalse();
|
||||
return can;
|
||||
}
|
||||
|
||||
@@ -380,64 +523,33 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
}
|
||||
}
|
||||
|
||||
/////////// 甩牌处理 ///////////
|
||||
//其他玩家没有主牌
|
||||
var other_noflower = function(_flower){
|
||||
for (var i = 0; i < seatlist.length; i++){
|
||||
if (i != selfseat){
|
||||
if (seatlist[i][_flower - 1][0] == 0){
|
||||
return false;
|
||||
}
|
||||
/////////// 甩牌处理(design §5.4)///////////
|
||||
//把要出的牌分解为分量;>1 个分量即为甩牌
|
||||
var comps = cls_youle_erqiwang_arith.decompose_trump(mainflower, cards);
|
||||
if (comps.length > 1){
|
||||
//副牌绝对禁甩(§5.4.1):甩牌必须全是主牌
|
||||
var allTrump = true;
|
||||
for (var ci = 0; ci < cards.length; ci++){
|
||||
if (cls_youle_erqiwang_arith.id_to_code(mainflower, cards[ci]) < 1000){
|
||||
allTrump = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//其他玩家没有主对
|
||||
var other_noflowerpair = function(_flower){
|
||||
for (var i = 0; i < seatlist.length; i++){
|
||||
if (i != selfseat){
|
||||
if (seatlist[i][_flower - 1][1] == 0){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//不可甩牌判断
|
||||
if (can.cardtype > 101 && can.cardtype < 200){ //单张甩牌
|
||||
//其他玩家有主牌则不能甩牌
|
||||
if (!other_noflower(mainflower)){
|
||||
if (!allTrump){
|
||||
//副牌不能甩,属非法出牌(无甩错惩罚,直接拒绝)
|
||||
do_returnfalse();
|
||||
return can;
|
||||
}
|
||||
//副牌甩牌需要满足其他玩家没有主牌且其他玩家都没有该花色的牌
|
||||
if (!other_noflower(can.flower)){
|
||||
//最大性判定(§5.4.2):按全部手牌,两对手都不持有能压过任一分量的主牌
|
||||
if (cls_youle_erqiwang_arith.opp_can_beat_flush(mainflower, comps, opp_zhulist)){
|
||||
//甩错(§5.4.5):do_playcard 据此收回甩牌、只强制打出最小一张
|
||||
do_returnfalse();
|
||||
can.shuaicuo = true;
|
||||
can.smallest = cards[cards.length - 1]; //cards 已从大到小排序,末位最小
|
||||
return can;
|
||||
}
|
||||
} else if (can.cardtype > 201 && can.cardtype < 300){ //对子甩牌
|
||||
if (can.flower == mainflower){ //甩主对
|
||||
//其他玩家有主牌对子则不能甩牌
|
||||
if (!other_noflowerpair(mainflower)){
|
||||
do_returnfalse();
|
||||
return can;
|
||||
}
|
||||
} else { //甩副牌对子
|
||||
//其他玩家有主牌则不能甩牌
|
||||
if (!other_noflower(mainflower)){
|
||||
do_returnfalse();
|
||||
return can;
|
||||
}
|
||||
//副牌甩牌需要其他玩家没有主牌且其他玩家没有该花色的对子
|
||||
if (!other_noflowerpair(can.flower)){
|
||||
do_returnfalse();
|
||||
return can;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return can;
|
||||
},
|
||||
|
||||
@@ -798,7 +910,9 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
return can;
|
||||
}
|
||||
//要是正确的拖拉机牌型
|
||||
if (tuolaji_list.length == 0){
|
||||
var _fpairlist = cls_youle_erqiwang_arith.get_pairlist(mainflower, _followcards);
|
||||
var _ftljlist = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, _fpairlist, get.cantype);
|
||||
if (_ftljlist.length == 0){
|
||||
do_returnfalse();
|
||||
return can;
|
||||
}
|
||||
@@ -922,51 +1036,41 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
}
|
||||
},
|
||||
|
||||
//闲家抠底倍数
|
||||
//闲家抠底倍数(design §6.3:单张主 ×1、主对子 ×2、N 连对拖拉机 ×2N)
|
||||
get_bottom_multiple: function(mainflower, cards){
|
||||
//拷贝数组
|
||||
var _cards = cards.concat();
|
||||
var _cards = cards.concat();
|
||||
//将牌从大到小排序
|
||||
_cards = cls_youle_erqiwang_arith.order_cards(mainflower, _cards);
|
||||
|
||||
//扣底要求赢下最后一轮的牌全是主牌(首末张都 >=1000 即全为主牌)
|
||||
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, _cards[0]);
|
||||
if (_code < 1000){
|
||||
return 0;
|
||||
}
|
||||
var _code = cls_youle_erqiwang_arith.id_to_code(mainflower, _cards[_cards.length - 1]);
|
||||
_code = cls_youle_erqiwang_arith.id_to_code(mainflower, _cards[_cards.length - 1]);
|
||||
if (_code < 1000){
|
||||
return 0;
|
||||
}
|
||||
|
||||
//单张主牌不翻倍
|
||||
var _pairlist = cls_youle_erqiwang_arith.get_pairlist(mainflower, _cards);
|
||||
if (_pairlist.length == 0){
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
var _tljlist = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, _pairlist, 302);
|
||||
if (_tljlist.length == 0){
|
||||
return 4;
|
||||
}
|
||||
|
||||
var _tljlist = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, _pairlist, 303);
|
||||
if (_tljlist.length == 0){
|
||||
return 8;
|
||||
//取牌中最长的连对数 N,倍数为 2N(主对子按 N=1,即 2 倍)
|
||||
var _n = 1;
|
||||
var _len = 2;
|
||||
while (true){
|
||||
var _tljlist = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, _pairlist, 300 + _len);
|
||||
if (_tljlist.length == 0){
|
||||
break;
|
||||
}
|
||||
_n = _len;
|
||||
_len = _len + 1;
|
||||
}
|
||||
|
||||
var _tljlist = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, _pairlist, 304);
|
||||
if (_tljlist.length == 0){
|
||||
return 16;
|
||||
}
|
||||
|
||||
var _tljlist = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, _pairlist, 305);
|
||||
if (_tljlist.length == 0){
|
||||
return 32;
|
||||
}
|
||||
|
||||
var _tljlist = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, _pairlist, 306);
|
||||
if (_tljlist.length == 0){
|
||||
return 64;
|
||||
}
|
||||
return 2 * _n;
|
||||
},
|
||||
|
||||
//冲关计算关数
|
||||
@@ -1042,48 +1146,37 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
}
|
||||
}
|
||||
|
||||
//六2六7判断
|
||||
//六2六7判断(design §8.1:6/7/8 个 7 → 1/2/3 奖,6/7/8 个 2 同理)
|
||||
if (_7list.length >= 6){
|
||||
re.count = re.count + _7list.length - 5;
|
||||
do_addlisttocards(_7list);
|
||||
}
|
||||
if (_2list.length >= 6){
|
||||
re.count = re.count + _2list.length - 5;
|
||||
re.count = re.count + _2list.length - 5;
|
||||
do_addlisttocards(_2list);
|
||||
}
|
||||
|
||||
//10个老主判断
|
||||
var _total = _wlist.length + _7list.length + _2list.length;
|
||||
if (_total >= 10){
|
||||
re.count = re.count + _total - 9;
|
||||
do_addlisttocards(_7list);
|
||||
do_addlisttocards(_2list);
|
||||
}
|
||||
//注:固定主牌 ≥10 张只触发亮牌(§8.2)、不算奖,故此处不再加奖
|
||||
|
||||
re.cards = cls_youle_erqiwang_arith.order_cards(mainflower, re.cards);
|
||||
return re;
|
||||
},
|
||||
|
||||
//计算升级级数
|
||||
get_upgrade: function(call, grade){
|
||||
//叫分的一半
|
||||
var _halfcall;
|
||||
if (call % 2 == 0){
|
||||
_halfcall = call / 2;
|
||||
} else {
|
||||
_halfcall = (call + 5) / 2;
|
||||
//计算判定的子数倍率(带符号):大光 +3 / 小光 +2 / 过庄 +1 / 升N级 -N(design §7.2.0)
|
||||
//q: 小光/过庄分界与升级级距(见 get_qvalue:常规40,爬坡分段)
|
||||
//先判是否达标(升级),再判大光/小光/过庄,顺序不可颠倒
|
||||
get_upgrade: function(call, grade, q){
|
||||
if (grade >= call){
|
||||
//闲家捡分达到叫分:闲赢升级,级数 = 1 + floor((grade-call)/q)
|
||||
return -1 * (parseInt((grade - call) / q) + 1);
|
||||
}
|
||||
|
||||
if (grade == 0){
|
||||
return 4;
|
||||
return 3; //大光
|
||||
}
|
||||
if (grade < _halfcall){
|
||||
return 2;
|
||||
if (grade < q){
|
||||
return 2; //小光
|
||||
}
|
||||
if (grade < call){
|
||||
return 1;
|
||||
}
|
||||
return -1 * (parseInt((grade - call) / _halfcall) + 1);
|
||||
return 1; //过庄
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user