二七王:实现 §5.4.4 强制跟牌分量拆解
原缺陷:混合甩牌在 can_playcard 牌型计算中被塌缩为扁平"多单张/多对",
跟牌只强制"同花色凑张数",未强制"拖拉机→对子→单张"的分量优先级。
修复:
- 合法甩牌时 can_playcard 记录分量需求 shuai_demand{tractors,pairs,singles},
do_playcard 存入 playproc、new_playround 复位
- 新增 arith.flush_follow_ok:在"同花色凑张数、主牌最大化"之上追加逐分量强制匹配
——能凑同长主拖必凑、能凑主对必凑(不足则退化为对子/单张),不得拆散去垫
- 新增 arith.remove_pairs 辅助
- 跟牌时 do_playcard 在 can_followcard 通过后再过 flush_follow_ok
不影响捡分/输赢(合法甩牌仍恒不可反超),仅补齐闲家被迫垫牌的结构约束。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -374,6 +374,93 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
return false;
|
||||
},
|
||||
|
||||
//从对子列表中移除指定拖拉机(flat牌id数组)用到的对子
|
||||
remove_pairs: function(pairlist, tractorcards){
|
||||
var result = [];
|
||||
for (var i = 0; i < pairlist.length; i++){
|
||||
var inT = false;
|
||||
for (var j = 0; j < tractorcards.length; j++){
|
||||
if (tractorcards[j] == pairlist[i][0]){
|
||||
inT = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inT){
|
||||
result.push(pairlist[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
//跟甩牌的分量拆解校验(design §5.4.4)
|
||||
//inhandcards: 跟牌方出牌前的全部手牌;followcards: 提交的跟牌;demand: {tractors:[连对数...], pairs, singles}
|
||||
//在"同花色凑张数、主牌最大化"之上,追加"拖拉机→对子→单张"的强制分量优先级:
|
||||
//能凑同长主拖就必须凑、能凑主对就必须凑,不得拆散去垫。返回 true 表示合规。
|
||||
flush_follow_ok: function(mainflower, inhandcards, followcards, demand){
|
||||
var isTrump = function(cid){
|
||||
return cls_youle_erqiwang_arith.id_to_code(mainflower, cid) >= 1000;
|
||||
}
|
||||
//手牌与跟牌里的主牌
|
||||
var heldTrump = [];
|
||||
var playedTrump = [];
|
||||
for (var i = 0; i < inhandcards.length; i++){
|
||||
if (isTrump(inhandcards[i])){ heldTrump.push(inhandcards[i]); }
|
||||
}
|
||||
for (var i = 0; i < followcards.length; i++){
|
||||
if (isTrump(followcards[i])){ playedTrump.push(followcards[i]); }
|
||||
}
|
||||
heldTrump = cls_youle_erqiwang_arith.order_cards(mainflower, heldTrump);
|
||||
playedTrump = cls_youle_erqiwang_arith.order_cards(mainflower, playedTrump);
|
||||
|
||||
//甩牌总张数
|
||||
var total = demand.singles + demand.pairs * 2;
|
||||
for (var t = 0; t < demand.tractors.length; t++){
|
||||
total = total + demand.tractors[t] * 2;
|
||||
}
|
||||
|
||||
//主牌最大化:手上主牌够则必须全打主牌,不够则全部主牌都要打出
|
||||
var needTrump = (heldTrump.length >= total) ? total : heldTrump.length;
|
||||
if (playedTrump.length != needTrump){
|
||||
return false;
|
||||
}
|
||||
|
||||
//按 拖拉机(长→短) → 对子 的优先级,算出跟牌方"必须打出"的拖拉机与对子数
|
||||
var heldPairs = cls_youle_erqiwang_arith.get_pairlist(mainflower, heldTrump);
|
||||
var _tractors = demand.tractors.concat();
|
||||
_tractors.sort(function(a, b){ return b - a; }); //降序
|
||||
var reqTractors = [];
|
||||
var workPairs = heldPairs.concat();
|
||||
var unmatchedTractorPairs = 0;
|
||||
for (var t = 0; t < _tractors.length; t++){
|
||||
var N = _tractors[t];
|
||||
var tl = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, workPairs, 300 + N);
|
||||
if (tl.length > 0){
|
||||
reqTractors.push(N);
|
||||
workPairs = cls_youle_erqiwang_arith.remove_pairs(workPairs, tl[0]);
|
||||
} else {
|
||||
//凑不成同长拖拉机,该拖拉机分量退化为对子需求
|
||||
unmatchedTractorPairs = unmatchedTractorPairs + N;
|
||||
}
|
||||
}
|
||||
//对子需求 = 对子分量 + 退化的拖拉机分量;受限于剩余可凑对子数
|
||||
var wantedPairs = demand.pairs + unmatchedTractorPairs;
|
||||
var reqPairs = (workPairs.length < wantedPairs) ? workPairs.length : wantedPairs;
|
||||
|
||||
//校验跟牌里的主牌是否包含这些必须的拖拉机与对子
|
||||
var playedPairs = cls_youle_erqiwang_arith.get_pairlist(mainflower, playedTrump);
|
||||
for (var r = 0; r < reqTractors.length; r++){
|
||||
var ptl = cls_youle_erqiwang_arith.get_tuolaji_list(mainflower, playedPairs, 300 + reqTractors[r]);
|
||||
if (ptl.length == 0){
|
||||
return false; //必须打出的同长主拖拉机没打
|
||||
}
|
||||
playedPairs = cls_youle_erqiwang_arith.remove_pairs(playedPairs, ptl[0]);
|
||||
}
|
||||
if (playedPairs.length < reqPairs){
|
||||
return false; //必须打出的主对子没凑够
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
//第一个位置是否可出牌
|
||||
can_playcard: function(mainflower, cards, selfseat, seatlist, opp_zhulist){
|
||||
/*
|
||||
@@ -548,6 +635,20 @@ var cls_youle_erqiwang_arith = cls_youle_erqiwang_arith || {
|
||||
can.smallest = cards[cards.length - 1]; //cards 已从大到小排序,末位最小
|
||||
return can;
|
||||
}
|
||||
//合法甩牌:记录分量需求,供跟牌逐分量强制匹配(§5.4.4)
|
||||
var _dTractors = [];
|
||||
var _dPairs = 0;
|
||||
var _dSingles = 0;
|
||||
for (var ci2 = 0; ci2 < comps.length; ci2++){
|
||||
if (comps[ci2].type == "tractor"){
|
||||
_dTractors.push(comps[ci2].len);
|
||||
} else if (comps[ci2].type == "pair"){
|
||||
_dPairs = _dPairs + 1;
|
||||
} else {
|
||||
_dSingles = _dSingles + 1;
|
||||
}
|
||||
}
|
||||
can.shuai_demand = { tractors: _dTractors, pairs: _dPairs, singles: _dSingles };
|
||||
}
|
||||
|
||||
return can;
|
||||
|
||||
@@ -486,6 +486,7 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
o_paiju.playproc.maxcard = -1; //满足第一个玩家出牌的牌型最大牌
|
||||
o_paiju.playproc.cards = []; //本轮各位置出的牌,数组与位置序号对应
|
||||
o_paiju.playproc.cards.length= 3;
|
||||
o_paiju.playproc.shuai_demand= null; //首家甩牌的分量需求(供跟牌逐分量匹配),非甩牌为null
|
||||
},
|
||||
|
||||
//在牌列表中获取分牌和分值
|
||||
@@ -577,6 +578,8 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
o_paiju.playproc.starttype = can.cardtype;
|
||||
o_paiju.playproc.maxseat = o_paiju.playproc.currseat;
|
||||
o_paiju.playproc.maxcard = can.cardvalue;
|
||||
//合法甩牌的分量需求,供跟牌逐分量强制匹配(§5.4.4);甩错退化为单张则为 null
|
||||
o_paiju.playproc.shuai_demand = can.shuai_demand ? can.shuai_demand : null;
|
||||
o_paiju.playproc.currseat = nextseat_1;
|
||||
//返回结果
|
||||
var re1 = {};
|
||||
@@ -598,6 +601,14 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || {
|
||||
re.result = false;
|
||||
return re;
|
||||
}
|
||||
//首家是甩牌时,追加"拖拉机→对子→单张"的强制分量匹配校验(§5.4.4)
|
||||
if (o_paiju.playproc.shuai_demand){
|
||||
if (!cls_youle_erqiwang_arith.flush_follow_ok(o_paiju.flower, cardsinhand, cards, o_paiju.playproc.shuai_demand)){
|
||||
var reF = {};
|
||||
reF.result = false;
|
||||
return reF;
|
||||
}
|
||||
}
|
||||
|
||||
//与之前的出牌比较大小
|
||||
if (can.cardvalue > o_paiju.playproc.maxcard){
|
||||
|
||||
Reference in New Issue
Block a user