From 6120580505bef4425766f64cce76952be7f6b26d Mon Sep 17 00:00:00 2001 From: Joywayer Date: Sat, 4 Jul 2026 22:32:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E4=B8=83=E7=8E=8B=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=AB=AF=EF=BC=9A=E6=8C=89=20design.md=20=E6=95=B4=E6=94=B9?= =?UTF-8?q?=E7=BB=93=E7=AE=97/=E7=AE=97=E5=A5=96/=E7=94=A9=E7=89=8C?= =?UTF-8?q?=EF=BC=8Croomtype=20=E6=94=B9=E4=BD=8D=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结算与算奖(§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) --- server/games/erqiwang/class.arith.js | 319 +++++++++++++++-------- server/games/erqiwang/class.config.js | 47 ++++ server/games/erqiwang/class.export.js | 139 +++------- server/games/erqiwang/class.paiju.js | 360 ++++++++++++++------------ server/games/erqiwang/mod.js | 123 ++++++++- 5 files changed, 601 insertions(+), 387 deletions(-) create mode 100644 server/games/erqiwang/class.config.js diff --git a/server/games/erqiwang/class.arith.js b/server/games/erqiwang/class.arith.js index 3a73979..5608b80 100644 --- a/server/games/erqiwang/class.arith.js +++ b/server/games/erqiwang/class.arith.js @@ -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; //过庄 } } diff --git a/server/games/erqiwang/class.config.js b/server/games/erqiwang/class.config.js new file mode 100644 index 0000000..8b81570 --- /dev/null +++ b/server/games/erqiwang/class.config.js @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////// +////// cls_youle_erqiwang_config: 房间配置解析 ////// +/////////////////////////////////////////////////// +// +// roomtype 采用定长数字位串(字符串),每一位对应一个开关,'0'/'1',方便前端逐项勾选: +// 位0 局数: '0'→6局 '1'→12局 +// 位1 扣卡方式: '0'→房主扣卡 '1'→AA每人扣卡 +// 位2 傍王: '0'→关 '1'→开 +// 位3 爬坡: '0'→常规算子 '1'→爬坡 +// 位4 查牌: '0'→可查牌 '1'→不查牌 +// 示例 "10100" = 12局 / 房主扣卡 / 傍王开 / 常规算子 / 可查牌 +// 缺省 "00000" = 6局 / 房主扣卡 / 无傍王 / 常规算子 / 可查牌(对齐 design 缺省) +// +// 本模块是 roomtype 位串格式的唯一解析入口(SSOT);游戏逻辑一律经 parse() 读取, +// 不得在别处按下标硬解析。 +// +var cls_youle_erqiwang_config = cls_youle_erqiwang_config || { + + //位索引常量 + IDX_ASET: 0, //局数 + IDX_DEDUCT: 1, //扣卡方式 + IDX_BANGWANG:2, //傍王 + IDX_CLIMB: 3, //爬坡 + IDX_NOCHECK: 4, //查牌 + + //取第 idx 位是否为 '1';roomtype 缺失/过短一律按 '0'(缺省)处理 + bit: function(roomtype, idx){ + if (!roomtype || typeof roomtype != "string"){ + return false; + } + if (idx < 0 || idx >= roomtype.length){ + return false; + } + return roomtype.charAt(idx) == "1"; + }, + + //解析 roomtype 位串为具名配置对象 + parse: function(roomtype){ + var cfg = {}; + cfg.aset12 = cls_youle_erqiwang_config.bit(roomtype, cls_youle_erqiwang_config.IDX_ASET); //true=12局 false=6局 + cfg.deductAA = cls_youle_erqiwang_config.bit(roomtype, cls_youle_erqiwang_config.IDX_DEDUCT); //true=AA每人 false=房主 + cfg.bangwang = cls_youle_erqiwang_config.bit(roomtype, cls_youle_erqiwang_config.IDX_BANGWANG);//true=开傍王 + cfg.climb = cls_youle_erqiwang_config.bit(roomtype, cls_youle_erqiwang_config.IDX_CLIMB); //true=爬坡 + cfg.nocheck = cls_youle_erqiwang_config.bit(roomtype, cls_youle_erqiwang_config.IDX_NOCHECK); //true=不查牌 + return cfg; + } +} diff --git a/server/games/erqiwang/class.export.js b/server/games/erqiwang/class.export.js index 8520d90..178cf23 100644 --- a/server/games/erqiwang/class.export.js +++ b/server/games/erqiwang/class.export.js @@ -7,79 +7,32 @@ var cls_youle_erqiwang_export = cls_youle_erqiwang_export || { var exp = {}; - //解析roomtype获取开房所需房卡 + //解析roomtype获取开房所需房卡(design §10.1) exp.get_needroomcard = function(roomtype){ - switch (roomtype[2]) - { - case 1: //房主扣卡 - switch (roomtype[0]) - { - case 1: - return 1; - break; - case 2: - return 2; - break; - default: - return 1; - break; - } - break; - case 2: //每人扣卡 - switch (roomtype[0]) - { - case 1: - return 1; - break; - case 2: - return 2; - break; - default: - return 1; - break; - } - break; + var cfg = cls_youle_erqiwang_config.parse(roomtype); + if (cfg.deductAA){ + //AA每人扣卡:6局每人1张 / 12局每人2张 + return cfg.aset12 ? 2 : 1; } + //房主扣卡:6局2张 / 12局4张 + return cfg.aset12 ? 4 : 2; } - //解析roomtype获取总局数 + //解析roomtype获取总局数(design §10.1:6局 / 12局) exp.get_asetcount = function(roomtype){ - switch (roomtype[0]) - { - case 1: - return 2; - break; - case 2: - return 4; - break; - default: - return 4; - break; - } + var cfg = cls_youle_erqiwang_config.parse(roomtype); + return cfg.aset12 ? 12 : 6; } //加入房间时根据roomtype获取需要的房卡数量 exp.get_needroomcard_joinroom = function(roomtype){ - switch (roomtype[2]) - { - case 1: //房主扣卡 - return 0; - break; - case 2: //每人扣卡 - switch (roomtype[0]) - { - case 1: - return 1; - break; - case 2: - return 2; - break; - default: - return 1; - break; - } - break; + var cfg = cls_youle_erqiwang_config.parse(roomtype); + if (cfg.deductAA){ + //AA每人扣卡:加入者按自己那份扣(6局1张 / 12局2张) + return cfg.aset12 ? 2 : 1; } + //房主扣卡:加入者不扣 + return 0; } //开战 @@ -138,11 +91,13 @@ var cls_youle_erqiwang_export = cls_youle_erqiwang_export || { //庄家叫分倍数 deskinfo.ChooseMain.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(paiju.call); //选主倒计时 - deskinfo.ChooseMain.countdown = o_room.o_desk.method.get_countdown_xuanzhu(); - //底牌 - deskinfo.ChooseMain.bottomcards = paiju.method.get_bottomcards(); + deskinfo.ChooseMain.countdown = o_room.o_desk.method.get_countdown_xuanzhu(); + //底牌:暗牌只有庄家可见(70分的3秒亮牌是上庄时的一次性事件,重连不重放) + if (seat == paiju.banker){ + deskinfo.ChooseMain.bottomcards = paiju.method.get_bottomcards(); + } //是否可投降 - if (paiju.call < 65){ + if (paiju.call != 70){ deskinfo.ChooseMain.touxiang = 0; } else { deskinfo.ChooseMain.touxiang = 1; @@ -157,40 +112,14 @@ var cls_youle_erqiwang_export = cls_youle_erqiwang_export || { //庄家叫分倍数 deskinfo.BuryCards.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(paiju.call); //埋牌倒计时 - deskinfo.BuryCards.countdown = o_room.o_desk.method.get_countdown_maipai(); - //底牌 - deskinfo.BuryCards.bottomcards = paiju.method.get_bottomcards(); + deskinfo.BuryCards.countdown = o_room.o_desk.method.get_countdown_maipai(); + //底牌:暗牌只有庄家可见 + if (seat == paiju.banker){ + deskinfo.BuryCards.bottomcards = paiju.method.get_bottomcards(); + } //主牌花色 deskinfo.BuryCards.flower = paiju.flower; - //是否可投降 - if (paiju.call < 65){ - deskinfo.BuryCards.touxiang = 0; - } else { - deskinfo.BuryCards.touxiang = 1; - } - break; - case 4: //投降 - deskinfo.Surrender = {}; - //庄家 - deskinfo.Surrender.banker = paiju.banker; - //庄家叫分 - deskinfo.Surrender.call = paiju.call; - //庄家叫分倍数 - deskinfo.Surrender.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(paiju.call); - //出牌倒计时 - deskinfo.Surrender.countdown = o_room.o_desk.method.get_countdown_chupai(); - //主牌花色 - deskinfo.Surrender.flower = paiju.flower; - if (seat == paiju.banker){ - //埋的牌 - deskinfo.Surrender.bottomcards = paiju.method.get_burycard(); - } - //是否可投降 - if (paiju.call < 65){ - deskinfo.Surrender.touxiang = 0; - } else { - deskinfo.Surrender.touxiang = 1; - } + //埋牌阶段无投降(投降是选主阶段与选主互斥的选择) break; case 5: //出牌 deskinfo.PushCards = {}; @@ -217,8 +146,16 @@ var cls_youle_erqiwang_export = cls_youle_erqiwang_export || { deskinfo.PushCards.grade = jian_grade.grade; //当前的出牌情况 deskinfo.PushCards.playproc = paiju.playproc; - //座位列表 - deskinfo.PushCards.seatlist = paiju.seatlist; + //座位列表(报副统计)与亮牌:仅可查牌模式下展示(§9/§8.2) + if (!cls_youle_erqiwang_config.parse(o_room.roomtype).nocheck){ + deskinfo.PushCards.seatlist = paiju.seatlist; + if (seat != paiju.banker){ + var _lp = paiju.method.get_liangpai(); + if (_lp){ + deskinfo.PushCards.liangpai = _lp; + } + } + } //出牌历史 deskinfo.PushCards.pushlist = []; for (var i = 0; i < paiju.cards.length; i++) { diff --git a/server/games/erqiwang/class.paiju.js b/server/games/erqiwang/class.paiju.js index 62c0f3a..2b947c6 100644 --- a/server/games/erqiwang/class.paiju.js +++ b/server/games/erqiwang/class.paiju.js @@ -133,7 +133,7 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { //牌列表 paiju.cards = []; //当前阶段 - paiju.step = -1; //1发完牌叫分 2选主 3埋牌 4投降 5出牌 6结算 + paiju.step = -1; //1发完牌叫分 2选主/投降 3埋牌 5出牌 6结算(无独立投降阶段,投降在step2) //叫分过程 paiju.callproc = []; //庄家位置 @@ -239,6 +239,11 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { 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; }, @@ -287,6 +292,25 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { 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 = []; @@ -413,12 +437,7 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { //选主 do_choiceflower: function(o_paiju, flower){ o_paiju.flower = flower; - //修改主牌的牌编码 - for (var i = 1; i <= 2; i++){ //两副牌 - for (var j = 0; j < 13; j++){ - var o_card = o_paiju.cards[(i - 1) * 54 + (flower - 1) * 13 + j]; - } - } + //主牌的牌编码在 arith.id_to_code 里按 mainflower 动态计算,选主时只需记录花色 //进入埋牌阶段 o_paiju.step = 3; }, @@ -450,8 +469,8 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { for (var i = 0; i < cards.length; i++) { o_paiju.cards[cards[i]].playround = 0; }; - //进入投降阶段 - o_paiju.step = 4; + //埋牌后直接进入出牌阶段,由庄家首出(投降已在选主阶段 step2 决定,无独立投降阶段) + o_paiju.step = 5; cls_youle_erqiwang_paiju.new_playround(o_paiju, 1, o_paiju.banker); }, @@ -524,13 +543,33 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { var nextseat_2 = cls_youle_erqiwang_paiju.get_nextseat(nextseat_1); if (o_paiju.playproc.currseat == o_paiju.playproc.start){ //是本轮的第一个出牌 - var can = cls_youle_erqiwang_arith.can_playcard(o_paiju.flower, cards, o_paiju.playproc.currseat, o_paiju.seatlist); - if (!can.result){ - var re = {}; - re.result = false; - return re; + //两名对手未出的主牌,供甩牌最大性判定(§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; @@ -546,6 +585,8 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { re1.count = cards.length; re1.flower = can.flower; re1.cardtype = can.cardtype; + re1.cards = cards; //实际打出的牌(甩错时为单张) + re1.shuaicuo = _shuaicuo; return re1; } @@ -642,6 +683,51 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { return cls_youle_erqiwang_arith.order_cards(o_paiju.flower, aryCardIDs); }, + //获取庄家亮牌信息(design §8.2):基于庄家埋牌后 28 张手牌,达标才亮,只亮数量/结构 + //返回 null 表示无需亮牌;否则返回 {zhu,zhupair,zhutuo?, wang?, qi?, er?} + get_liangpai: function(o_paiju){ + if (o_paiju.banker < 0){ + return null; + } + var cards = cls_youle_erqiwang_paiju.get_seat_cards(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 = {}; @@ -682,7 +768,7 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { var do_deduct_roomcard = function(){ youle_erqiwang.import.deduct_roomcard(o_paiju.o_desk.o_room); } - min_ontimeout(do_deduct_roomcard, 1000); + min_ontimeout(do_deduct_roomcard, 1000); } if (msg.rpc != "jiesuan"){ @@ -691,165 +777,121 @@ var cls_youle_erqiwang_paiju = cls_youle_erqiwang_paiju || { if (!msg.data){ msg.data = {}; } - msg.data.aset = {}; - msg.data.aset.banker = o_paiju.banker; - msg.data.aset.call = o_paiju.call; - msg.data.aset.multiple = cls_youle_erqiwang_arith.get_multiple_bycall(o_paiju.call); - msg.data.aset.flower = o_paiju.flower; - var jian_grade = cls_youle_erqiwang_paiju.get_jian_grade(o_paiju); - msg.data.aset.grade = jian_grade.grade; + + //读取房间可选规则(缺省:常规算子、不傍王;见 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){ - msg.data.aset.grade = msg.data.aset.grade + 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: - msg.data.aset.upgrade = cls_youle_erqiwang_arith.get_upgrade(o_paiju.call, msg.data.aset.grade); - if (msg.data.aset.upgrade > 0){ - o_paiju.result = 0; - } else { - o_paiju.result = 1; - } + _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: - msg.data.aset.upgrade = -99; + case 1: //投降:70分坐庄,基础子数1,庄家直接输1个子 + _base = 1; + _upgrade = -99; //送客户端的投降标识 + _win = -1; + _X = 1; o_paiju.result = 2; break; - case 2: - msg.data.aset.upgrade = 0; + case 2: //解散:本局不结算子数与算奖 + _base = 0; + _upgrade = 0; + _win = 0; + _X = 0; o_paiju.result = 3; break; } - - msg.data.aset.seatlist = []; - var _player0 = {}; - var _player1 = {}; - var _player2 = {}; - //获取玩家的牌 - var _card0 = cls_youle_erqiwang_paiju.get_seat_cards_owner(o_paiju, 0); - var _card1 = cls_youle_erqiwang_paiju.get_seat_cards_owner(o_paiju, 1); - var _card2 = cls_youle_erqiwang_paiju.get_seat_cards_owner(o_paiju, 2); - //计算冲关或王牌 - var _flower = o_paiju.flower; - if (type != 2){ - _flower = -1; - } - var _chongguan0 = cls_youle_erqiwang_arith.get_chongguan(_flower, _card0); - var _chongguan1 = cls_youle_erqiwang_arith.get_chongguan(_flower, _card1); - var _chongguan2 = cls_youle_erqiwang_arith.get_chongguan(_flower, _card2); - //冲关的牌或王牌 - _player0.cards = _chongguan0.cards; - _player1.cards = _chongguan1.cards; - _player2.cards = _chongguan2.cards; - //关数 - _player0.chongguan = _chongguan0.count; - _player1.chongguan = _chongguan1.count; - _player2.chongguan = _chongguan2.count; - //是否作废 - _player0.obsolete = 0; - _player1.obsolete = 0; - _player2.obsolete = 0; - //根据叫分判断冲关是否作废 - var do_obsolete_with_call = function(player, seat){ - var _dealcards = cls_youle_erqiwang_paiju.get_seat_cards_deal(o_paiju, seat); - var _dealchongguan = cls_youle_erqiwang_arith.get_chongguan(-1, _dealcards); - if (_dealchongguan.count == 0){ - return; + + 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) } - var _call = null; - for (var i = 0; i < o_paiju.callproc.length; i++){ - if (o_paiju.callproc[i].seat == seat){ - _call = o_paiju.callproc[i].call; - break; + 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; } } - if (!_call){ //冲关必叫 - player.obsolete = 1; - } else if (_dealchongguan.wang == 4 && _call > 60){ //四王必踢 - player.obsolete = 1; - } } - do_obsolete_with_call(_player0, 0); - do_obsolete_with_call(_player1, 1); - do_obsolete_with_call(_player2, 2); - //计算冲关得分 - var _cg0 = _player0.chongguan; - var _cg1 = _player1.chongguan; - var _cg2 = _player2.chongguan; - if (_player0.obsolete){ - _cg0 = 0; + 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 (_player1.obsolete){ - _cg1 = 0; - } - if (_player2.obsolete){ - _cg2 = 0; - } - _player0.grade_cg = _cg0 * 2 - _cg1 - _cg2; - _player1.grade_cg = _cg1 * 2 - _cg2 - _cg0; - _player2.grade_cg = _cg2 * 2 - _cg0 - _cg1; - //计算傍王得分 - var _w0 = _chongguan0.wang; - var _w1 = _chongguan1.wang; - var _w2 = _chongguan2.wang; - if (_player0.obsolete){ - _w0 = 0; - } - if (_player1.obsolete){ - _w1 = 0; - } - if (_player2.obsolete){ - _w2 = 0; - } - _player0.grade_w = _w0 * 2 - _w1 - _w2; - _player1.grade_w = _w1 * 2 - _w2 - _w0; - _player2.grade_w = _w2 * 2 - _w0 - _w1; - //计算捡分得分 - var _upgrade = msg.data.aset.upgrade; - if (_upgrade == -99){ - _upgrade = -2; - } - switch (o_paiju.banker){ - case -1: - _player0.grade_jf = 0; - _player1.grade_jf = 0; - _player2.grade_jf = 0; - break; - case 0: - _player0.grade_jf = _upgrade * msg.data.aset.multiple * 2; - _player1.grade_jf = _upgrade * msg.data.aset.multiple * -1; - _player2.grade_jf = _player1.grade_jf; - break; - case 1: - _player0.grade_jf = _upgrade * msg.data.aset.multiple * -1; - _player1.grade_jf = _upgrade * msg.data.aset.multiple * 2; - _player2.grade_jf = _player0.grade_jf; - break; - case 2: - _player0.grade_jf = _upgrade * msg.data.aset.multiple * -1; - _player1.grade_jf = _player0.grade_jf; - _player2.grade_jf = _upgrade * msg.data.aset.multiple * 2; - break; - } - //计算本局总得分 - _player0.grade = _player0.grade_cg + _player0.grade_w + _player0.grade_jf; - _player1.grade = _player1.grade_cg + _player1.grade_w + _player1.grade_jf; - _player2.grade = _player2.grade_cg + _player2.grade_w + _player2.grade_jf; - //大局累积得分 - o_paiju.o_desk.seatlist[0][0] = o_paiju.o_desk.seatlist[0][0] + _player0.grade; - o_paiju.o_desk.seatlist[1][0] = o_paiju.o_desk.seatlist[1][0] + _player1.grade; - o_paiju.o_desk.seatlist[2][0] = o_paiju.o_desk.seatlist[2][0] + _player2.grade; - _player0.score = o_paiju.o_desk.seatlist[0][0]; - _player1.score = o_paiju.o_desk.seatlist[1][0]; - _player2.score = o_paiju.o_desk.seatlist[2][0]; - //保存每局得分 - o_paiju.o_desk.seatlist[0][1].push(_player0.grade); - o_paiju.o_desk.seatlist[1][1].push(_player1.grade); - o_paiju.o_desk.seatlist[2][1].push(_player2.grade); - - msg.data.aset.seatlist.push(_player0); - msg.data.aset.seatlist.push(_player1); - msg.data.aset.seatlist.push(_player2); if (o_paiju.idx >= o_paiju.o_desk.o_room.asetcount || type == 2){ msg = o_paiju.o_desk.method.get_desk_account(msg); diff --git a/server/games/erqiwang/mod.js b/server/games/erqiwang/mod.js index ff1361d..c0992c8 100644 --- a/server/games/erqiwang/mod.js +++ b/server/games/erqiwang/mod.js @@ -4,6 +4,8 @@ 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(){ //牌局类 @@ -23,6 +25,7 @@ min_loadJsFile("games/erqiwang/class.export.js", function(){ }); }); }); +}); //对内的输入接口类(需要的外部接口) youle_erqiwang.import = null; @@ -60,10 +63,10 @@ youle_erqiwang.jiaofen = function(pack){ if (o_paiju.method.get_callgrade_seat() != seat){ return; } - //检查当前的叫分分值必须大于0,且是5的整数倍 - if (call < 0 || call % 5 != 0){ + //检查叫分: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){ @@ -106,21 +109,33 @@ youle_erqiwang.jiaofen = function(pack){ 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.bottomcards = o_paiju.method.get_bottomcards(); msg.data.countdown = o_desk.method.get_countdown_xuanzhu(); - if (o_paiju.call < 65){ + 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); } @@ -227,6 +242,13 @@ youle_erqiwang.maipai = function(pack){ //埋牌成功 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"; @@ -241,9 +263,16 @@ youle_erqiwang.maipai = function(pack){ 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); } @@ -271,16 +300,16 @@ youle_erqiwang.touxiang = function(pack){ if (!o_paiju){ return; } - //检查当前的游戏阶段是否是叫分、埋牌、投降阶段 - if (o_paiju.step != 2 && o_paiju.step != 3 && o_paiju.step != 4){ + //投降是选主阶段(step 2)与选主互斥的选择:点投降直接结算,不选主、不埋牌(design §4) + if (o_paiju.step != 2){ return; } //检查庄家位置 if (o_paiju.banker != seat){ return; } - //检查是否允许投降 - if (o_paiju.call < 65){ + //仅 70 分坐庄可投降(design §4 / §7.1) + if (o_paiju.call != 70){ return; } @@ -319,7 +348,7 @@ youle_erqiwang.chupai = function(pack){ return; } //检查当前的游戏阶段是否是出牌阶段 - if (o_paiju.step != 4 && o_paiju.step != 5){ + if (o_paiju.step != 5){ return; } //检查出牌位置 @@ -341,17 +370,26 @@ youle_erqiwang.chupai = function(pack){ 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 = cards; - msg.data.info = o_paiju.seatlist[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 (o_paiju.method.have_baofu()){ + 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; @@ -421,6 +459,63 @@ youle_erqiwang.chupai = function(pack){ } } +//明牌:查看另外两家手中全部主牌的具体牌面(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;