/////////////////////////////////////////////////// ////////// cls_youle_erqiwang_paiju: 牌局 ///////// /////////////////////////////////////////////////// //跨模块依赖:Node 走 require 守卫;友乐/浏览器无 require,由 min_loadJsFile 加载为同名全局(dev-guide 01 §1) if (typeof require !== "undefined"){ var cls_youle_erqiwang_arith = require("./class.arith.js"); var cls_youle_erqiwang_config = require("./class.config.js"); var cls_youle_erqiwang_pai = require("./class.pai.js"); } var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { new: function(o_desk, firstseat) { var paiju = {}; o_desk.paiju_list.push(paiju); // ================= 实例的私有属性 ================= // //发牌过程中待发的位置列表 var tmpdeal = []; //三家需要各发28张牌 for (var i = 0; i < 28; i++){ tmpdeal.push(0); //0表示第一个玩家的位置 tmpdeal.push(1); //1表示第二个玩家的位置 tmpdeal.push(2); //2表示第三个玩家的位置 } //8张底牌 for (var i = 0; i < 8; i++){ tmpdeal.push(-1); //-1 表示是底牌 } // ================= 实例的私有方法 ================= // //初始化座位列表 var init_seatlist = function(){ //第一层是玩家列表,与位置序号对应 //第二层前4个数组是花色列表,与花色-1对应 //第三层的第一位是无该花色的牌标志 //第三层的第二位是该花色无对标志 //第二层第5个数组是报副情况 //第三层的第一位是剩余主牌数量 //第三层的第二位是剩余主牌对子 //0号位 paiju.seatlist.push([[0,0],[0,0],[0,0],[0,0],[-1,-1]]); //1号位 paiju.seatlist.push([[0,0],[0,0],[0,0],[0,0],[-1,-1]]); //2号位 paiju.seatlist.push([[0,0],[0,0],[0,0],[0,0],[-1,-1]]); } //初始化牌列表并发牌 var init_cards = function(){ //两副牌 for (var i = 1; i <= 2; i++){ //四种花色 for (var j = 1; j <= 4; j++){ for (var k = 1; k <= 13; k++){ //牌id var id = (i - 1) * 54 + (j - 1) * 13 + k - 1; //分值 var score = 0; //发牌状态 var dealowner = 0; //特殊处理 switch (k) { case 5: score = 5; break; case 10: score = 10; break; case 13: score = 10; break; case 3: dealowner = -1; break; case 4: dealowner = -1; break; } var pai = cls_youle_erqiwang_pai.new(id, j, k, score, dealowner); paiju.cards.push(pai); //发牌 if (k != 3 && k != 4){ do_dealpai(pai); } } } //小王 var pai = cls_youle_erqiwang_pai.new((i - 1) * 54 + 53 - 1, 5, 53, 0, 0); paiju.cards.push(pai); do_dealpai(pai); //大王 var pai = cls_youle_erqiwang_pai.new((i - 1) * 54 + 54 - 1, 5, 54, 0, 0); paiju.cards.push(pai); do_dealpai(pai); } } //将单张牌随机发到玩家手中或成为底牌 var do_dealpai = function(o_pai){ var idx = min_random(0, tmpdeal.length - 1); if (tmpdeal[idx] == -1) { //底牌 o_pai.dealowner = 0; } else { o_pai.dealowner = tmpdeal[idx] + 1; } //将数组的最后一位至该位置 if (idx < tmpdeal.length - 1) { tmpdeal[idx] = tmpdeal[tmpdeal.length - 1]; }; //数组长度减一 tmpdeal.length = tmpdeal.length - 1; } //进入叫分阶段 var start_call = function(){ paiju.step = 1; var o_call = {}; o_call.seat = paiju.firstseat; o_call.call = null; //null:未叫分 0:不叫分 >0:叫分分数 paiju.callproc.push(o_call); } // ================= 实例的公有属性 ================= // paiju.o_desk = o_desk; //牌局号 paiju.idx = o_desk.paiju_list.length; //第一个叫分者的位置 paiju.firstseat = firstseat; //开始时间 paiju.starttime = min_now(); //结束时间 paiju.endtime = null; //座位列表 paiju.seatlist = []; //牌列表 paiju.cards = []; //当前阶段 paiju.step = -1; //1发完牌叫分 2选主/投降 3埋牌 5出牌 6结算(无独立投降阶段,投降在step2) //叫分过程 paiju.callproc = []; //庄家位置 paiju.banker = -1; //庄家叫分 paiju.call = -1; //主牌花色 paiju.flower = -1; //当前出牌情况 paiju.playproc = {}; //输赢结果 0:庄赢 1:闲赢 2:庄投降 3:中途解散 paiju.result = null; //结算包,供断线重连时使用,新开一局时清除 paiju.tmp_jiesuan_aset = null; //初始化座位列表 init_seatlist(); //初始化牌列表并发牌 init_cards(); //进入叫分阶段 start_call(); // ================= 实例的公有方法 ================= // paiju.method = {}; //获取某个位置玩家手上的牌 paiju.method.get_seat_cards = function(seat){ return cls_youle_erqiwang_paiju.get_seat_cards(paiju, seat); } //获取当前的叫分位置 paiju.method.get_callgrade_seat = function(){ return paiju.callproc[paiju.callproc.length - 1].seat; } //获取当前的叫分分数 paiju.method.get_callgrade_value = function(){ return cls_youle_erqiwang_paiju.get_callgrade_value(paiju); } //获取某个玩家的当前叫分情况 paiju.method.get_seat_callgrade = function(seat){ return cls_youle_erqiwang_paiju.get_seat_callgrade(paiju, seat); } //叫分或不叫 paiju.method.do_callgrade = function(call){ cls_youle_erqiwang_paiju.do_callgrade(paiju, call); } //获取底牌 paiju.method.get_bottomcards = function(){ return cls_youle_erqiwang_paiju.get_bottomcards(paiju); } //选主 paiju.method.do_choiceflower = function(flower){ cls_youle_erqiwang_paiju.do_choiceflower(paiju, flower); } //校验客户端提交的牌id列表本身是否合法(数组/整数/范围/无重复) paiju.method.check_cards_valid = function(cards){ return cls_youle_erqiwang_paiju.check_cards_valid(paiju, cards); } //判断牌是否在玩家手上 paiju.method.check_cards_inhand = function(cards, seat){ return cls_youle_erqiwang_paiju.check_cards_inhand(paiju, cards, seat); } //埋牌 paiju.method.do_burycard = function(cards){ cls_youle_erqiwang_paiju.do_burycard(paiju, cards); } //检查选中的牌是否可出,并出牌 paiju.method.do_playcard = function(cards){ return cls_youle_erqiwang_paiju.do_playcard(paiju, cards); } //是否有人报副 paiju.method.have_baofu = function(){ return (paiju.seatlist[0][4][0] == 0 || paiju.seatlist[1][4][0] == 0 || paiju.seatlist[2][4][0] == 0); } //在牌列表中获取分牌和分值 paiju.method.get_grade_incard = function(cards){ return cls_youle_erqiwang_paiju.get_grade_incard(paiju, cards); } //获取埋牌 paiju.method.get_burycard= function(){ return cls_youle_erqiwang_paiju.get_burycard(paiju); } //获取抠底包 paiju.method.get_bottom_account = function(msg){ return cls_youle_erqiwang_paiju.get_bottom_account(paiju, msg); } //小局结算,type 0正常结算 1投降结算 2解散结算 paiju.method.get_paiju_account = function(type, msg){ return cls_youle_erqiwang_paiju.get_paiju_account(paiju, type, msg); } //获取闲家当前的捡分分数和分牌 paiju.method.get_jian_grade = function(){ return cls_youle_erqiwang_paiju.get_jian_grade(paiju); } //获取庄家亮牌信息(design §8.2) paiju.method.get_liangpai = function(){ return cls_youle_erqiwang_paiju.get_liangpai(paiju); } return paiju; }, //下一个位置 get_nextseat: function(seat){ return (seat + 1) % 3; }, //获取某个位置玩家手上的牌 get_seat_cards: function(o_paiju, seat){ var aryCardIDs = []; for (var i = 0; i < o_paiju.cards.length; i++){ var o_card = o_paiju.cards[i]; if (o_card.playround == -1){ //未出的牌 if ((o_card.dealowner == seat + 1) || //发到手上 (o_card.dealowner == 0 && seat == o_paiju.banker)){ //庄家包括底牌 aryCardIDs.push(o_paiju.cards[i].id); } } } return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs); }, //获取某个位置玩家发到手上的牌 get_seat_cards_deal: function(o_paiju, seat){ var aryCardIDs = []; for (var i = 0; i < o_paiju.cards.length; i++){ var o_card = o_paiju.cards[i]; if (o_card.dealowner == seat + 1){ aryCardIDs.push(o_paiju.cards[i].id); } } return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs); }, //获取某个位置玩家发到手上的牌(庄家包含底牌) get_seat_cards_owner: function(o_paiju, seat){ var aryCardIDs = []; for (var i = 0; i < o_paiju.cards.length; i++){ var o_card = o_paiju.cards[i]; if ((o_card.dealowner == seat + 1) || //发到手上 (o_card.dealowner == 0 && seat == o_paiju.banker)){ //庄家包括底牌 aryCardIDs.push(o_paiju.cards[i].id); } } return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs); }, //获取算奖用的静态初始手牌快照(design §8) //庄家:埋牌后保留的28张(发牌+底,排除已埋 playround==0);闲家:发牌后的28张 get_seat_cards_award: function(o_paiju, seat){ var aryCardIDs = []; for (var i = 0; i < o_paiju.cards.length; i++){ var o_card = o_paiju.cards[i]; if (seat == o_paiju.banker){ if ((o_card.dealowner == seat + 1 || o_card.dealowner == 0) && o_card.playround != 0){ aryCardIDs.push(o_card.id); } } else { if (o_card.dealowner == seat + 1){ aryCardIDs.push(o_card.id); } } } return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs); }, //获取某个位置玩家手上的主牌 get_seat_zhucards: function(o_paiju, seat){ var aryCardIDs = []; for (var i = 0; i < o_paiju.cards.length; i++){ var o_card = o_paiju.cards[i]; if (o_card.playround == -1){ //未出的牌 if ((o_card.dealowner == seat + 1) || //发到手上 (o_card.dealowner == 0 && seat == o_paiju.banker)){ //庄家包括底牌 if (o_card.flower == o_paiju.flower || o_card.flower == 5 || o_card.number == 2 || o_card.number == 7){ aryCardIDs.push(o_paiju.cards[i].id); } } } } return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs); }, //获取当前的叫分分数 get_callgrade_value: function(o_paiju){ var value = 0; for (var i = o_paiju.callproc.length - 2; i >= 0; i--) { if (o_paiju.callproc[i].call) { value = o_paiju.callproc[i].call; break; } } return value; }, //获取某个位置的叫分 返回值:null未叫 0不叫 >0当前叫分 get_seat_callgrade: function(o_paiju, seat){ for (var i = o_paiju.callproc.length - 2; i >= 0; i--){ if (o_paiju.callproc[i].seat == seat){ return o_paiju.callproc[i].call; } } return null; }, //叫分 do_callgrade: function(o_paiju, call){ var o_call = o_paiju.callproc[o_paiju.callproc.length - 1]; o_call.call = call; //等待下一个叫分者叫分 var do_wait_nextseat_call = function(seat){ var o_call = {}; o_call.seat = seat; o_call.call = null; o_paiju.callproc.push(o_call); } //上庄 var do_up_banker = function(seat, call){ //庄家位置 o_paiju.banker = seat; //庄家叫分 o_paiju.call = call; //进入选主阶段 o_paiju.step = 2; } //叫了5分直接上庄 if (call == 5){ do_up_banker(o_call.seat, call); return; } //叫了分 if (call > 0){ //下一个位置 var nextseat = cls_youle_erqiwang_paiju.get_nextseat(o_call.seat); //下一个位置的叫分 var nextcall = cls_youle_erqiwang_paiju.get_seat_callgrade(o_paiju, nextseat); //下一个位置不叫分 if (nextcall == 0){ //再下一个位置 nextseat = cls_youle_erqiwang_paiju.get_nextseat(nextseat); } //等待下一个叫分者叫分 do_wait_nextseat_call(nextseat); return; } //不叫 if (call == 0){ //下一个位置 var nextseat_1 = cls_youle_erqiwang_paiju.get_nextseat(o_call.seat); //下一个位置的叫分 var nextcall_1 = cls_youle_erqiwang_paiju.get_seat_callgrade(o_paiju, nextseat_1); //再下一个位置 var nextseat_2 = cls_youle_erqiwang_paiju.get_nextseat(nextseat_1); //再下一个位置的叫分 var nextcall_2 = cls_youle_erqiwang_paiju.get_seat_callgrade(o_paiju, nextseat_2); if (nextcall_1 != 0 && nextcall_2 != 0){ //另外两家都没有不叫分 do_wait_nextseat_call(nextseat_1); return; } if (nextcall_1 != 0 && nextcall_2 == 0){ //nextseat_1上庄 do_up_banker(nextseat_1, nextcall_1); return; } if (nextcall_1 == 0 && nextcall_2 != 0){ //nextseat_2上庄 do_up_banker(nextseat_2, nextcall_2); return; } } }, //获取底牌 get_bottomcards: function(o_paiju){ var aryCardIDs = []; for (var i = 0; i < o_paiju.cards.length; i++){ if (o_paiju.cards[i].dealowner == 0){ aryCardIDs.push(o_paiju.cards[i].id); } } return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs); }, //选主 do_choiceflower: function(o_paiju, flower){ o_paiju.flower = flower; //主牌的牌编码在 arith.id_to_code 里按 mainflower 动态计算,选主时只需记录花色 //进入埋牌阶段 o_paiju.step = 3; }, //校验客户端提交的牌id列表本身是否合法:必须是非空数组、元素为本局牌表内的整数牌id、且互不重复 //(客户端入参不可信:重复id 会被 can_playcard 当成对子/拖拉机,也会让埋牌少埋;越界id 会取到 // undefined 而在后续解引用时抛异常中断 DoPack。此处显式失败,不做隐式兜底) check_cards_valid: function(o_paiju, cards){ if (Object.prototype.toString.call(cards) != "[object Array]"){ return false; } if (cards.length <= 0){ return false; } var _seen = {}; for (var i = 0; i < cards.length; i++){ var _id = cards[i]; //必须是落在本局牌表范围内的整数牌id if (typeof _id != "number" || _id != parseInt(_id) || _id < 0 || _id >= o_paiju.cards.length){ return false; } //同一张牌不得重复提交 if (_seen[_id]){ return false; } _seen[_id] = 1; } return true; }, //判断牌是否在玩家手上(入参合法性由 check_cards_valid 一并把关,本函数是唯一的持牌校验入口) check_cards_inhand: function(o_paiju, cards, seat){ if (!cls_youle_erqiwang_paiju.check_cards_valid(o_paiju, cards)){ return false; } for (var i = 0; i < cards.length; i++) { var o_card = o_paiju.cards[cards[i]]; if (o_card.playround != -1){ //已埋下或已打出的牌 return false; } if (seat == o_paiju.banker){ //是庄家 if (o_card.dealowner != seat + 1 && o_card.dealowner != 0){ return false; } } else { //是闲家 if (o_card.dealowner != seat + 1){ return false; } } } return true; }, //埋牌 do_burycard: function(o_paiju, cards){ for (var i = 0; i < cards.length; i++) { o_paiju.cards[cards[i]].playround = 0; }; //埋牌后直接进入出牌阶段,由庄家首出(投降已在选主阶段 step2 决定,无独立投降阶段) o_paiju.step = 5; cls_youle_erqiwang_paiju.new_playround(o_paiju, 1, o_paiju.banker); }, //新一轮出牌 new_playround: function(o_paiju, round, startseat){ o_paiju.playproc.round = round; //第几轮 o_paiju.playproc.start = startseat; //本轮的起始出牌位置 o_paiju.playproc.currseat = startseat; //本轮的当前的出牌位置 o_paiju.playproc.startcount = -1; //本轮第一个出牌的张数 o_paiju.playproc.startflower = -1; //本轮第一个出牌的花色 o_paiju.playproc.starttype = -1; //本轮第一个出牌的牌型 o_paiju.playproc.maxseat = -1; //谁的牌最大,位置 o_paiju.playproc.maxcard = -1; //满足第一个玩家出牌的牌型最大牌 o_paiju.playproc.cards = []; //本轮各位置出的牌,数组与位置序号对应 o_paiju.playproc.cards.length= 3; o_paiju.playproc.shuai_demand= null; //首家甩牌的分量需求(供跟牌逐分量匹配),非甩牌为null }, //在牌列表中获取分牌和分值 get_grade_incard: function(o_paiju, cards){ var re = {}; re.grade = 0; re.cards = []; for (var i = 0; i < cards.length; i++) { if (o_paiju.cards[cards[i]].score > 0){ re.grade = re.grade + o_paiju.cards[cards[i]].score; re.cards.push(cards[i]); } } return re; }, //检查选中的牌是否可出,并出牌 do_playcard: function(o_paiju, cards){ //出牌 var doplay = function(playindex){ for (var i = 0; i < cards.length; i++) { o_paiju.cards[cards[i]].playround = o_paiju.playproc.round; o_paiju.cards[cards[i]].playindex = playindex; }; o_paiju.playproc.cards[o_paiju.playproc.currseat] = cards; //进入出牌阶段 o_paiju.step = 5; //报无主处理(design §9) //先看出牌者自己的主牌是否已出空(出空即"报无主"); //一旦全场有人报无主,立即为【全体三人】刷新剩余主牌数/主对子数——design §9 要求 //"一旦有玩家的主牌全部打空,系统就为全体三人显示另外两家各自的主牌数量与对子数", //而不是等另外两家各自轮到出牌时才逐个补上。 var _self = o_paiju.playproc.currseat; if (cls_youle_erqiwang_paiju.get_seat_zhucards(o_paiju, _self).length == 0){ o_paiju.seatlist[_self][4][0] = 0; o_paiju.seatlist[_self][4][1] = 0; } if (o_paiju.method.have_baofu()){ for (var _s = 0; _s < 3; _s++){ var _zhu = cls_youle_erqiwang_paiju.get_seat_zhucards(o_paiju, _s); var _zhupair = cls_youle_erqiwang_arith.get_pairlist(o_paiju.flower, _zhu); o_paiju.seatlist[_s][4][0] = _zhu.length; o_paiju.seatlist[_s][4][1] = _zhupair.length; if (_zhu.length == 0){ //主牌出空,等同于主花色也没有了、也没有主花色的对子 o_paiju.seatlist[_s][o_paiju.flower - 1][0] = 1; o_paiju.seatlist[_s][o_paiju.flower - 1][1] = 1; } else if (_zhupair.length == 0){ o_paiju.seatlist[_s][o_paiju.flower - 1][1] = 1; } } } } //本轮中的第二个出牌位置 var nextseat_1 = cls_youle_erqiwang_paiju.get_nextseat(o_paiju.playproc.start); //本轮中的第三个出牌位置 var nextseat_2 = cls_youle_erqiwang_paiju.get_nextseat(nextseat_1); if (o_paiju.playproc.currseat == o_paiju.playproc.start){ //是本轮的第一个出牌 //两名对手未出的主牌,供甩牌最大性判定(§5.4.2) var opp_zhulist = []; for (var os = 0; os < 3; os++){ if (os != o_paiju.playproc.currseat){ opp_zhulist.push(cls_youle_erqiwang_paiju.get_seat_zhucards(o_paiju, os)); } } var can = cls_youle_erqiwang_arith.can_playcard(o_paiju.flower, cards, o_paiju.playproc.currseat, o_paiju.seatlist, opp_zhulist); var _shuaicuo = false; if (!can.result){ if (can.shuaicuo){ //甩错惩罚(§5.4.5):收回甩牌,本轮只强制打出最小一张 _shuaicuo = true; cards = [can.smallest]; can = cls_youle_erqiwang_arith.can_playcard(o_paiju.flower, cards, o_paiju.playproc.currseat, o_paiju.seatlist, opp_zhulist); if (!can.result){ var reErr = {}; reErr.result = false; return reErr; } } else { var re = {}; re.result = false; return re; } } //出牌(cards 在甩错时已被替换为仅最小一张) doplay(1); //记录下牌型、花色、牌型最大牌 o_paiju.playproc.startcount = cards.length; o_paiju.playproc.startflower = can.flower; 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 = {}; re1.result = true; re1.idx = 1; re1.count = cards.length; re1.flower = can.flower; re1.cardtype = can.cardtype; re1.cards = cards; //实际打出的牌(甩错时为单张) re1.shuaicuo = _shuaicuo; //合法甩牌的分量构成:cardtype 只能表达单一牌型,甩牌(单张/对子/拖拉机自由混搭, //design §5.4.3)会被压平成 1xx/2xx,客户端无从据此还原分组,故另行下发分量需求 if (o_paiju.playproc.shuai_demand){ re1.shuai = o_paiju.playproc.shuai_demand; } return re1; } //不是本轮中的第一个出牌,是跟牌 var cardsinhand = cls_youle_erqiwang_paiju.get_seat_cards(o_paiju, o_paiju.playproc.currseat); var can = cls_youle_erqiwang_arith.can_followcard(o_paiju.flower, cardsinhand, cards, o_paiju.playproc.startcount, o_paiju.playproc.startflower, o_paiju.playproc.starttype); if (!can.result){ var re = {}; 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){ o_paiju.playproc.maxseat = o_paiju.playproc.currseat; o_paiju.playproc.maxcard = can.cardvalue; } //无花色、无对子处理 if (can.nopair){ o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.playproc.startflower - 1][1] = 1; } if (can.noflower){ o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.playproc.startflower - 1][0] = 1; o_paiju.seatlist[o_paiju.playproc.currseat][o_paiju.playproc.startflower - 1][1] = 1; } //是本轮中的第二个出牌 if (o_paiju.playproc.currseat == nextseat_1){ //出牌 doplay(2); //下一个出牌者 o_paiju.playproc.currseat = nextseat_2; //返回结果 var re2 = {}; re2.idx = 2; re2.result = true; return re2; } //是本轮中的第三个出牌 //出牌 doplay(3); //本轮出牌结束 for (var i = 0; i < o_paiju.playproc.cards.length; i++) { for (var j = 0; j < o_paiju.playproc.cards[i].length; j++) { o_paiju.cards[o_paiju.playproc.cards[i][j]].playowner = o_paiju.playproc.maxseat; } } //返回结果 var re3 = {}; re3.result = true; re3.idx = 3; re3.maxseat = o_paiju.playproc.maxseat; if (o_paiju.playproc.maxseat != o_paiju.banker){ re3.grade = 0; // re3.gradecards = []; for (var i = 0; i < o_paiju.playproc.cards.length; i++) { var grade_incards = cls_youle_erqiwang_paiju.get_grade_incard(o_paiju, o_paiju.playproc.cards[i]); re3.grade = re3.grade + grade_incards.grade; // re3.gradecards = re3.gradecards.concat(grade_incards.cards); } } //判断是否还有牌在玩家手上未出 var havecard_inhand = function(){ for (var i = 0; i < o_paiju.cards.length; i++) { if (o_paiju.cards[i].playround == -1 && o_paiju.cards[i].number != 3 && o_paiju.cards[i].number != 4){ return true; } } return false; } // if (!havecard_inhand() || o_paiju.playproc.round == 2){ //测试用 if (!havecard_inhand()){ //进入结算阶段 o_paiju.step = 6; } else { //新一轮出牌 cls_youle_erqiwang_paiju.new_playround(o_paiju, o_paiju.playproc.round + 1, o_paiju.playproc.maxseat); } return re3; }, //获取埋牌 get_burycard: function(o_paiju){ var aryCardIDs = []; for (var i = 0; i < o_paiju.cards.length; i++){ if (o_paiju.cards[i].playround == 0){ aryCardIDs.push(o_paiju.cards[i].id); } } return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs); }, //获取庄家亮牌信息(design §8.2):基于庄家埋牌后 28 张手牌,达标才亮,只亮数量/结构 //返回 null 表示无需亮牌;否则返回 {zhu,zhupair,zhutuo?, wang?, qi?, er?} //手牌来源必须是 get_seat_cards_award 的【静态快照】(埋牌后 28 张,含之后已打出的), //不能用 get_seat_cards(只取未出的牌):出牌开始亮出的这份统计全局固定、不随出牌缩水, //否则闲家断线重连后 get_deskinfo 会按庄家当前剩余手牌重算,亮牌信息缩水甚至变成 null get_liangpai: function(o_paiju){ if (o_paiju.banker < 0){ return null; } var cards = cls_youle_erqiwang_paiju.get_seat_cards_award(o_paiju, o_paiju.banker); var wang = 0, qi = 0, er = 0; var zhucards = []; for (var i = 0; i < cards.length; i++){ var num = cls_youle_erqiwang_arith.id_to_number(cards[i]); var flw = cls_youle_erqiwang_arith.id_to_flower(cards[i]); if (num >= 53){ wang = wang + 1; } if (num == 7){ qi = qi + 1; } if (num == 2){ er = er + 1; } //主牌 = 主花色普通牌 或 王 或 任意 2/7 if (flw == o_paiju.flower || flw == 5 || num == 2 || num == 7){ zhucards.push(cards[i]); } } var guding = wang + qi + er; //固定主牌(王 + 全部 2 + 全部 7) var re = {}; var has = false; if (guding >= 10){ zhucards = cls_youle_erqiwang_arith.order_cards(o_paiju.flower, zhucards); var pairs = cls_youle_erqiwang_arith.get_pairlist(o_paiju.flower, zhucards); var comps = cls_youle_erqiwang_arith.decompose_trump(o_paiju.flower, zhucards); var tuo = 0; for (var c = 0; c < comps.length; c++){ if (comps[c].type == "tractor"){ tuo = tuo + 1; } } re.zhu = zhucards.length; //主牌总数 re.zhupair = pairs.length; //主对子数 re.zhutuo = tuo; //主拖拉机组数 has = true; } if (wang >= 3){ re.wang = wang; has = true; } if (qi >= 6){ re.qi = qi; has = true; } if (er >= 6){ re.er = er; has = true; } if (!has){ return null; } return re; }, //获取抠底包 get_bottom_account: function(o_paiju, msg){ msg.data.bottom = {}; msg.data.bottom.cards = o_paiju.method.get_burycard(); if (o_paiju.playproc.maxseat != o_paiju.banker){ //闲家抠底 var multiple = cls_youle_erqiwang_arith.get_bottom_multiple(o_paiju.flower, o_paiju.playproc.cards[o_paiju.playproc.maxseat]); if (multiple > 0){ msg.data.bottom.multiple = multiple; var get = o_paiju.method.get_grade_incard(msg.data.bottom.cards); msg.data.bottom.grade1 = get.grade; msg.data.bottom.grade2 = multiple * msg.data.bottom.grade1; } } return msg; }, //获取闲家捡分(不包含抠底) get_jian_grade: function(o_paiju){ var re = {}; re.grade = 0; re.cards = []; for (var i = 0; i < o_paiju.cards.length; i++){ if (o_paiju.cards[i].playowner > -1 && o_paiju.cards[i].playowner != o_paiju.banker){ if (o_paiju.cards[i].score > 0){ re.grade = re.grade + o_paiju.cards[i].score; re.cards.push(i); } } } return re; }, //获取小局结算包,type 0正常结算 1投降结算 2解散结算 get_paiju_account: function(o_paiju, type, msg){ o_paiju.endtime = min_now(); //第一局结算扣除房卡 if (o_paiju.idx == 1 && type != 2){ var do_deduct_roomcard = function(){ youle_erqiwang.import.deduct_roomcard(o_paiju.o_desk.o_room); } min_ontimeout(do_deduct_roomcard, 1000); } if (msg.rpc != "jiesuan"){ msg.rpc = "jiesuan"; } if (!msg.data){ msg.data = {}; } //主动推送的 data 必须自带成败标志(dev-guide server 03 §4:成败唯一是 data.success) msg.data.success = true; //读取房间可选规则(缺省:常规算子、不傍王;见 class.config.js 位串约定) var o_room = o_paiju.o_desk.o_room; var _cfg = cls_youle_erqiwang_config.parse(o_room.roomtype); var _climb = _cfg.climb; //爬坡 var _bangwang = _cfg.bangwang; //傍王 //闲家捡分(含抠底) var _call = o_paiju.call; var _jian = cls_youle_erqiwang_paiju.get_jian_grade(o_paiju); var _grade = _jian.grade; if (msg.data.bottom && msg.data.bottom.grade2){ _grade = _grade + msg.data.bottom.grade2; } //算子:_base 基础子数、_upgrade 判定倍率(带符号,送客户端)、_X 每对子子数、_win 胜负(1庄赢 -1闲赢 0无) var _base, _upgrade, _X, _win; switch (type){ case 0: _base = cls_youle_erqiwang_arith.get_base_bycall(_call, _climb); var _q = cls_youle_erqiwang_arith.get_qvalue(_call, _climb); _upgrade = cls_youle_erqiwang_arith.get_upgrade(_call, _grade, _q); _win = (_upgrade > 0) ? 1 : -1; _X = ((_upgrade < 0) ? (0 - _upgrade) : _upgrade) * _base; o_paiju.result = (_upgrade > 0) ? 0 : 1; break; case 1: //投降:70分坐庄,基础子数1,庄家直接输1个子 _base = 1; _upgrade = -99; //送客户端的投降标识 _win = -1; _X = 1; o_paiju.result = 2; break; case 2: //解散:本局不结算子数与算奖 _base = 0; _upgrade = 0; _win = 0; _X = 0; o_paiju.result = 3; break; } msg.data.aset = {}; msg.data.aset.banker = o_paiju.banker; msg.data.aset.call = _call; msg.data.aset.multiple = _base; msg.data.aset.flower = o_paiju.flower; msg.data.aset.grade = _grade; msg.data.aset.upgrade = _upgrade; msg.data.aset.bangwang = _bangwang ? 1 : 0; msg.data.aset.climb = _climb ? 1 : 0; //算奖:各家奖数 N =(庄家?常规算奖 : 0)+(傍王?王数 : 0) //静态初始手牌快照:庄家取埋牌后28张、闲家取发牌后28张 var _c0 = cls_youle_erqiwang_paiju.get_seat_cards_award(o_paiju, 0); var _c1 = cls_youle_erqiwang_paiju.get_seat_cards_award(o_paiju, 1); var _c2 = cls_youle_erqiwang_paiju.get_seat_cards_award(o_paiju, 2); var _cglist = [ cls_youle_erqiwang_arith.get_chongguan(o_paiju.flower, _c0), cls_youle_erqiwang_arith.get_chongguan(o_paiju.flower, _c1), cls_youle_erqiwang_arith.get_chongguan(o_paiju.flower, _c2) ]; var award_count = function(cgobj, seat){ var n = 0; if (seat == o_paiju.banker){ n = n + cgobj.count; //常规算奖只计庄家(design §8.1) } if (_bangwang){ n = n + cgobj.wang; //傍王勾选时庄闲都算,每张王1奖(design §8.3) } return n; } var _nlist = [ award_count(_cglist[0], 0), award_count(_cglist[1], 1), award_count(_cglist[2], 2) ]; //算奖并入结算(design §8.4):玩家i从另两家各收 X×Ni、各付 X×Nj,净得 X×(2Ni−Nj−Nk) var _aw = [ _X * (2 * _nlist[0] - _nlist[1] - _nlist[2]), _X * (2 * _nlist[1] - _nlist[0] - _nlist[2]), _X * (2 * _nlist[2] - _nlist[0] - _nlist[1]) ]; //捡分子数:_win=1庄赢→庄+X×2/闲−X;_win=-1闲赢→庄−X×2/闲+X var _jf = [0, 0, 0]; if (o_paiju.banker >= 0){ for (var s = 0; s < 3; s++){ if (s == o_paiju.banker){ _jf[s] = _win * _X * 2; } else { _jf[s] = _win * _X * -1; } } } msg.data.aset.seatlist = []; for (var i = 0; i < 3; i++){ var _p = {}; _p.cards = _cglist[i].cards; //算奖相关牌(王/冲关牌) _p.chongguan = _cglist[i].count; //常规算奖奖数(仅庄家计入 N) _p.wang = _cglist[i].wang; //王数 _p.naward = _nlist[i]; //该家总奖数 N _p.grade_aw = _aw[i]; //算奖得分 X×(2Ni−Nj−Nk) _p.grade_jf = _jf[i]; //捡分子数得分 _p.grade = _p.grade_aw + _p.grade_jf; //本局总得分 //大局累积得分 o_paiju.o_desk.seatlist[i][0] = o_paiju.o_desk.seatlist[i][0] + _p.grade; _p.score = o_paiju.o_desk.seatlist[i][0]; //保存每局得分 o_paiju.o_desk.seatlist[i][1].push(_p.grade); msg.data.aset.seatlist.push(_p); } if (o_paiju.idx >= o_paiju.o_desk.o_room.asetcount || type == 2){ msg = o_paiju.o_desk.method.get_desk_account(msg); } //保存结算包,供断线重连时使用 o_paiju.tmp_jiesuan_aset = msg.data.aset; return msg; } } //Node 导出;友乐/浏览器无 module 时跳过(dev-guide 01 §1)。min_random/min_now/min_ontimeout、youle_erqiwang.import 为平台全局 if (typeof module !== "undefined"){ module.exports = cls_youle_erqiwang_paiju; }