/////////////////////////////////////////////////////////////// ////// youle_erqiwang_shared_cards: 前后端共享牌值算法 ////////// /////////////////////////////////////////////////////////////// // 【权威源】本文件是前后端共享算法的唯一可修改处。 // 前端副本 client/js/01_SubGame/codes/shared/cards.js 由仓库根目录的 // sync_shared.cmd 单向同步生成,为只读;任何改动都必须落在本文件, // 改完跑一次 sync_shared.cmd,否则 client/tests/test_shared_sync.js 会红。 // // 内容:牌值编码与牌型分组的纯函数,不依赖对局状态、不依赖平台 API。 // 牌编码区间约定(id_to_code 的产物,各处判定据此): // 9554 大王 / 9553 小王 / 8000–9000 正7 / 7000–8000 副7 // 3000–4000 正2 / 2000–3000 副2 / 1000–2000 主花色普通牌 / 100–500 副牌普通牌 // ⇒ 主牌 ⟺ code > 1000 var youle_erqiwang_shared_cards = youle_erqiwang_shared_cards || { //——— 牌编码区间边界:上面那张区间表的机器可读形式,是【全部区间判定的唯一权威来源】——— //一律是【开区间】:CODE_ZHENG7_MIN < code < CODE_ZHENG7_MAX ⟺ 正7,其余同理。 //本文件内部(is_continuous / trump_rank / id_to_code)与前端 core/CardMark.js 都引用这些常量, //不再各处重抄 3000/4000/8000/9000 之类裸字面量(工程总则 §1 SSOT / §6 配置优先于硬编码)。 CODE_XIAOWANG: 9553, //小王(单值,非区间) CODE_DAWANG: 9554, //大王(单值,非区间) CODE_ZHENG7_MIN: 8000, //正7:8000 < code < 9000 CODE_ZHENG7_MAX: 9000, CODE_FU7_MIN: 7000, //副7:7000 < code < 8000 CODE_FU7_MAX: 8000, CODE_ZHENG2_MIN: 3000, //正2:3000 < code < 4000 CODE_ZHENG2_MAX: 4000, CODE_FU2_MIN: 2000, //副2:2000 < code < 3000 CODE_FU2_MAX: 3000, CODE_ZHU_MIN: 1000, //主牌 ⟺ code > CODE_ZHU_MIN;主花色普通牌:1000 < code < 2000 CODE_ZHU_MAX: 2000, CODE_A_IN_ZHU: 14, //主A 的 code % 100(A 在 id_to_code 里 +13 提到 14) //几对连续对子起算拖拉机(design §5.3)。group_tractor_runs 返回极大连续段, //段长 >= 本值即拖拉机——服务端分解甩牌与前端手牌标记共用这一个阈值 TRACTOR_MIN_PAIRS: 2, //牌id转花色 id_to_flower: function(cardid){ if (cardid == 52 || cardid == 106){ //小王 return 5; } if (cardid == 53 || cardid == 107){ //大王 return 5; } return parseInt((cardid % 54) / 13) + 1; }, //牌id转牌面数值 id_to_number: function(cardid){ if (cardid == 52 || cardid == 106){ //小王 return 53; } if (cardid == 53 || cardid == 107){ //大王 return 54; } return (cardid % 54) % 13 + 1; }, //牌id转牌编码 id_to_code: function(mainflower, cardid){ if (cardid == 52 || cardid == 106){ //小王 return youle_erqiwang_shared_cards.CODE_XIAOWANG; } if (cardid == 53 || cardid == 107){ //大王 return youle_erqiwang_shared_cards.CODE_DAWANG; } var flower = youle_erqiwang_shared_cards.id_to_flower(cardid); var number = youle_erqiwang_shared_cards.id_to_number(cardid); var code = flower * 100 + number; //特殊处理 switch (number) { case 1: //A 提到 K 之上(14) code = code + 13; break; case 2: //2 提到副2 段 code = code + youle_erqiwang_shared_cards.CODE_FU2_MIN; break; case 7: //7 提到副7 段 code = code + youle_erqiwang_shared_cards.CODE_FU7_MIN; break; } //主牌花色处理:整体再上移一个主牌段(副2→正2、副7→正7、普通牌→主花色普通牌) if (flower == mainflower){ code = code + youle_erqiwang_shared_cards.CODE_ZHU_MIN; } return code; }, //从大到小排序(冒泡排序) order_cards: function(mainflower, aryCardIDs){ if (aryCardIDs){ for (var i = 0; i < aryCardIDs.length; i++) { for (var j = i + 1; j < aryCardIDs.length; j++) { var i_code = youle_erqiwang_shared_cards.id_to_code(mainflower, aryCardIDs[i]); var j_code = youle_erqiwang_shared_cards.id_to_code(mainflower, aryCardIDs[j]); if (i_code < j_code) { var tmp = aryCardIDs[j]; aryCardIDs[j] = aryCardIDs[i]; aryCardIDs[i] = tmp; } } } } return aryCardIDs; }, //判断两个牌是否是连续的 is_continuous: function(bigcode, smallcode){ var S = youle_erqiwang_shared_cards; //是小王 var is_xiaowang = function(_code){ if (_code == S.CODE_XIAOWANG){ return true; } return false; } //是正7 var is_zheng7 = function(_code){ if (_code > S.CODE_ZHENG7_MIN && _code < S.CODE_ZHENG7_MAX){ return true; } return false; } //是负7 var is_fu7 = function(_code){ if (_code > S.CODE_FU7_MIN && _code < S.CODE_FU7_MAX){ return true; } return false; } //是正2 var is_zheng2 = function(_code){ if (_code > S.CODE_ZHENG2_MIN && _code < S.CODE_ZHENG2_MAX){ return true; } return false; } //是负2 var is_fu2 = function(_code){ if (_code > S.CODE_FU2_MIN && _code < S.CODE_FU2_MAX){ return true; } return false; } //是主A var is_zhuA = function(_code){ if (_code > S.CODE_ZHU_MIN && _code < S.CODE_ZHU_MAX && _code % 100 == S.CODE_A_IN_ZHU){ return true; } return false; } //是8 var is_8 = function(_code){ if (_code % 100 == 8){ return true; } return false; } //是6 var is_6 = function(_code){ if (_code % 100 == 6){ return true; } return false; } //普通的连牌 if (bigcode - smallcode == 1){ return true; } //小王、正7 if (is_xiaowang(bigcode) && is_zheng7(smallcode)){ return true; } //正7、负7 if (is_zheng7(bigcode) && is_fu7(smallcode)){ return true; } //负7、正2 if (is_fu7(bigcode) && is_zheng2(smallcode)){ return true; } //正2、负2 if (is_zheng2(bigcode) && is_fu2(smallcode)){ return true; } //负2、主A if (is_fu2(bigcode) && is_zhuA(smallcode)){ return true; } //8、6(design §5.3:7 不与 6/8 相连,6 与 8 相连) //必须是同一花色的 8 与 6:牌编码同花色相差 2(主花色 1408-1406=2、副牌 308-306=2), //跨花色则相差 100 的倍数。只判 %100 会把「♥8 + ♣6」误判为连对, //进而在 get_chongguan 扫描整手牌时让副牌的 6 对错误接续主 8、多算奖(design §8.1 连对链) if (is_8(bigcode) && is_6(smallcode) && (bigcode - smallcode == 2)){ return true; } return false; }, //获取对子列表,cards为同一花色的排过序的牌列表 get_pairlist: function(mainflower, flowercards){ var pairlist = []; var i = 0; while (i < flowercards.length - 1){ //牌编码,两张牌两张牌一取 var code_i = youle_erqiwang_shared_cards.id_to_code(mainflower, flowercards[i]); var code_j = youle_erqiwang_shared_cards.id_to_code(mainflower, flowercards[i + 1]); if (code_i == code_j){ pairlist.push([flowercards[i], flowercards[i + 1]]); i = i + 2; } else { i++; } } return pairlist; }, //把对子列表扫成【极大连续段】。pairlist 为同一牌组内、已按牌编码从大到小排好的对子列表 //(形如 get_pairlist 的返回值:[[cardid, cardid], ...])。 //返回 [[pair, ...], ...]:每一段是若干【相邻且 is_continuous】的对子,段与段之间必不连续; //孤立对子也作为长度 1 的段返回,故 concat 所有段 == 原 pairlist(顺序不变)。 //段长 >= TRACTOR_MIN_PAIRS 即拖拉机。 //【唯一实现】服务端 decompose_trump(甩牌分解)与前端 core/CardMark.js(手牌「拖」标记) //此前各写过一遍同样的扫描,规则内容(什么算连续、几对起算拖拉机)散在两处——现收敛到这里。 group_tractor_runs: function(mainflower, pairlist){ var runs = []; var i = 0; while (i < pairlist.length){ var run = [pairlist[i]]; var k = i; while (k + 1 < pairlist.length){ var code_k = youle_erqiwang_shared_cards.id_to_code(mainflower, pairlist[k][0]); var code_k1 = youle_erqiwang_shared_cards.id_to_code(mainflower, pairlist[k + 1][0]); if (!youle_erqiwang_shared_cards.is_continuous(code_k, code_k1)){ break; } run.push(pairlist[k + 1]); k++; } runs.push(run); i = k + 1; } return runs; }, //获取拖拉机列表,pairlist为同一花色的排过序的对子列表,cardtype为拖拉机牌型 get_tuolaji_list: function(mainflower, pairlist, cardtype){ var tuolaji = []; var count = cardtype - 300; //拖拉机的对子数量 var i = 0; for (var i = 0; i < pairlist.length - count + 1; i++){ //每次按拖拉机的对子数量取N对 var tmplist = pairlist.slice(i, i + count); //判断是否是cardtype类型的拖拉机 var is_tlj = true; for (var j = 0; j < tmplist.length - 1; j++) { var code_j1 = youle_erqiwang_shared_cards.id_to_code(mainflower, tmplist[j][0]); var code_j2 = youle_erqiwang_shared_cards.id_to_code(mainflower, tmplist[j+1][0]); if (!youle_erqiwang_shared_cards.is_continuous(code_j1, code_j2)){ is_tlj = false; break; } } if (is_tlj){ var tlj = []; for (var k = 0; k < tmplist.length; k++){ tlj = tlj.concat(tmplist[k]); } tuolaji.push(tlj); } } return tuolaji; }, //主牌归一化大小(副7 同级取 7000、副2 同级取 2000,其余用牌编码),仅用于甩牌分量比大小 trump_rank: function(mainflower, cardid){ var code = youle_erqiwang_shared_cards.id_to_code(mainflower, cardid); if (code > youle_erqiwang_shared_cards.CODE_FU7_MIN && code < youle_erqiwang_shared_cards.CODE_FU7_MAX){ //副7 return youle_erqiwang_shared_cards.CODE_FU7_MIN; } if (code > youle_erqiwang_shared_cards.CODE_FU2_MIN && code < youle_erqiwang_shared_cards.CODE_FU2_MAX){ //副2 return youle_erqiwang_shared_cards.CODE_FU2_MIN; } return code; }, } //Node 导出;友乐/浏览器无 module 时跳过(dev-guide 01 §1) if (typeof module !== "undefined"){ module.exports = youle_erqiwang_shared_cards; }