diff --git a/server/games/erqiwang/class.arith.js b/server/games/erqiwang/class.arith.js index 5608b80..7407fe6 100644 --- a/server/games/erqiwang/class.arith.js +++ b/server/games/erqiwang/class.arith.js @@ -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; diff --git a/server/games/erqiwang/class.paiju.js b/server/games/erqiwang/class.paiju.js index 2b947c6..e374e11 100644 --- a/server/games/erqiwang/class.paiju.js +++ b/server/games/erqiwang/class.paiju.js @@ -485,7 +485,8 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { o_paiju.playproc.maxseat = -1; //谁的牌最大,位置 o_paiju.playproc.maxcard = -1; //满足第一个玩家出牌的牌型最大牌 o_paiju.playproc.cards = []; //本轮各位置出的牌,数组与位置序号对应 - o_paiju.playproc.cards.length= 3; + 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){