麻将通用胡牌算法详解(拆解法)
1.背景
前几天刚好有项目需要胡牌算法,查阅资料后,大部分胡牌算法的博客都是只讲原理,实现太过简单,且没有给出测试用例。然后就有了下面的这个胡牌算法,我将从算法原理和算法实现两部分展开,想直接用的,直接跳到算法部分即可。
2.数据结构
这里麻将是108张牌,也就是只带万,条,筒。数据结构可抽象为两种形式
- 分别将牌的类型(万,条,筒)类型(type) 和值( value)设置为牌的属性
- 将牌的值写成十六进制(十六进制一个数字可以同时表示牌值和牌型)
下面将给出牌值的数据结构
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, //万
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, //条
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29 //筒
下面是牌对象的数据结构
module.exports = class CardInfoBO {isShow = null; //是否显示value = null; //牌值isTouch = null; //是否可以选中
}
3.模型分析
每个人搬牌后,手中的牌必是14张(或8张),胡牌时需满足以下条件
- 手中有一对
将牌
(两张一样的牌) - 剩下的牌满足
刻子
(三张相同的牌)或顺子
(三张连续的牌)
所以胡牌的数学模型可被抽象成以下公式:
N*ABC + M*DDD + EE
4.流程分析
搞清楚如何能够胡牌之后,下面谈一下判断胡牌的流程
- 找出所有可能的
将牌
- 去除
将牌
,得到所有去除所有将牌之后得到的数组集(去除两张相同牌得到的不同组合) - 遍历去除将牌之后的数组集,将数组集中的剩余手牌根据是否连续分为不同的断点**([3,4,5,7,7,7]转换为{[3,4,5],[7,7,7]}**)
- 根据刚才得到的断点数组,判断其中断点内的牌是否满足
3n
的格式(顺子或刻子
),不满足,直接false
- 若断点断的牌符合
3n
的格式(顺子或刻子
),继续检查所有断点内容是否符合胡牌规则(下面代码中有详述)
5.代码分析
不想看的直接复制(复制可直接运行)!!!
先给出整体的判胡算法
/*** 胡牌(满足公式:N*ABC + M*DDD + EE)* @param {*} card_list (自己原来的手牌)* @param {*} cacheCard(别人打出的牌或自己摸到的牌)*/
check_can_hu(index, cacheCard, card_list)
{var hand_holds = card_list; //测试用//判断手牌张数var first = hand_holds.length;//新建数组,来判断是否可以胡牌var card_copy = hand_holds.slice(0);card_copy.push(cacheCard);card_copy.sort((a, b) => a.value - b.value);//手中牌不符合胡牌规则 3n*2if ((first + 1) % 3 != 2) {return false;}else{//找出所有可能的将牌,其余长度用0补全var jiangs = this.get_jiang(card_copy, first + 1);//去除将牌var mains = [];var mains = this.qu_jiang_arrs(card_copy, jiangs, first + 1);//当前去除将牌后牌的长度var size = mains.length;//将去除将牌后分段是否符合可胡牌型的bool值var ones = [];//每一个断点的所有牌的集合var breaks = [];//根据打的断点 获取到第一个长度的所有牌型breaks = this.get_breaks(mains[i], first - 2);for (var i = 0; i < size; i++){if (this.breaks_check(breaks, breaks.length)){//获取到的牌不符合3n的格式、直接为falseones[i] = false;}else{//如果断点断的牌符合顺子或者刻子(3n)的格式var mainsparts = this.all_parts(mains[i], breaks);ones[i] = this.ping_hu(mainsparts, mainsparts.length);}}//满足任意条件即可胡牌for (var i = 0; i < size; i++){if (ones[i]){ return true;}}}return false;
}
获取到所有将牌
/*** 获取到所有的将牌(2张相同的),未来可能加个去重*/
get_jiang(card, cardLength)
{var count = 1;var arr = new Array();for (var i = 1; i < cardLength; i++){if (card[i].value == card[i - 1].value){if (count == 1) arr[i - 1] = card[i].value;count += 1;}else{count = 1;arr[i-1] = 0; } }return arr;
}
去除将牌,返回所有可能的情况
/*** 去除将牌,返回所有牌的情况*/
qu_jiang_arrs(card, jiangs, length)
{var list = [];var cardInfoBO = new CardInfoBO();cardInfoBO.isShow = true;cardInfoBO.value = 0x00;cardInfoBO.isTouch = true;cardInfoBO.isCard = false;for(let i = 0; i < length; i++) { if (jiangs[i] != 0 && jiangs[i] != null){ var src = new Array();var arr = new Array();for (var j = 0; j < length; j++){src[j] = card[j];}src[i] = cardInfoBO;src[i + 1] = cardInfoBO;src.sort((a, b) => a.value - b.value);for(let k = 2; k < length; k++) {arr[k-2] = src[k];}list.push(arr);}}return list;
}
获取去除将牌后的断点
/*** 查找去除将牌后的断点(没有连续的牌就是一个断点)*/
get_breaks(card, card_length)
{var breaks = [];var count = 1;breaks[0] = 0;for (var i = 1; i < card_length; i++){if ((card[i].value - card[i - 1].value) > 1 && i < card_length-1){breaks[count] = i;count += 1;} else if(i == card_length-1) //最后一次{if ( (card[i+1].value - card[i].value) > 1 ) {breaks[count] = i+1;count += 1;}if((card[i].value - card[i-1].value) > 1){breaks[count] = i;count += 1;}}}breaks[count] = card_length+1;var breakss = [];breakss[0] = 0;for (var i = 0; i < count + 1; i++){breakss[i] = breaks[i];}return breakss;}
断点后,检查所断的牌型是不是顺子或者刻子
/*** 断点后,检查所断的牌型是不是顺子或者刻子* false 表示断牌为顺子或者刻字 true表示不符合胡牌规则*/
breaks_check(breaks, length)
{for (var i = 1; i < length; i++){if ( (breaks[i] - breaks[i-1]) % 3 != 0)return true; }return false;
}
根据手牌断点数组,返回经过处理的牌型,例如[5,5,5,6,7,7,8,8,9]->[3,1,2,2,1]
/**
* 根据手牌和断点的数组 返回经过处理的牌型
*/
all_parts(card, breaks)
{var partnum = breaks.length - 1; //断点个数var parts = [];var arr = [];for (var i = 0; i < partnum; i++){for (var j = 0; j < breaks[i + 1] - breaks[i]; j++){j == 0 ? arr = [] : null;arr[j] = card[breaks[i] + j];}parts[i] = this.postion_translate(arr, arr.length);}return parts;
}/**
* 拆解位转换
* (五万、五万、五万、六万、七万、七万、八万、八万、九万)=》(3 1 2 2 1)
*/
postion_translate(arr,arr_length)
{var count = 1;var split = [];var status = 0; //0状态下为单数状态,1状态下为计数状态for(var i = 0; i < arr_length-1;) {var index_value = arr[i].value;if (index_value == arr[i+1].value){status = 1;i++;count += 1;} else {status == 1 ? split.push(count) : count = 1 ;i++;count = 1;status == 0 ? split.push(count) : count = 1;status = 0; }i == arr_length - 1 ? split.push(count) : count = count;}return split;
}
根据拆解位检测最终是否能胡
/*** 根据拆解位判断最终是否能平胡*/
ping_hu(mainsparts, mainsparts_length)
{for(var i = 0; i < mainsparts_length; i++){if (!this.is_main_part(mainsparts[i], mainsparts[i].length)) {return false;}}return true;
}/*** 判断断点处理后的值是否符合胡牌规则(3,1,2,2,1)*/
is_main_part(arr, arr_length)
{var cache = [];cache = arr;var count = 0;for(var i = 0; i < arr_length; i++) {//遇到三位直接拆if(cache[i] >= 3) {cache.splice(i,1,cache[i]-3);cache[i] != 0 ? i-- : i=i ;}else if(cache[i] != 0 && cache[i] > 0 && i < arr_length-2) //按顺子拆{cache[i] -= 1;cache[i+1] -= 1;cache[i+2] -= 1;cache[i] != 0 ? i-- : i=i ;} }for(var i = 0; i < arr_length; i++) {count += Math.abs(cache[i]);}return count == 0 ? true : false;
}
6.胡牌算法测试
function check_can_hu_test()
{var cards = [0x17,0x18,0x19,0x06,0x06,0x06,0x07,0x07,0x08,0x08,0x09,0x09,0x03];var card_list = [];var cardInfoBO = new CardInfoBO();cardInfoBO.value = 0x03;cardInfoBO.isTouch = true;cardInfoBO.isCard = false;for (var i = 0; i < cards.length; i++) {var cib = new CardInfoBO();cib.value = cards[i];cib.isTouch = true;cib.isCard = false;card_list.push(cib);}card_list.sort((a, b) => a.value - b.value);var bool = gamesMgr.check_can_hu("10010", cardInfoBO, card_list);console.log(bool);
}
7.测试结果
var cards = [0x17,0x18,0x19,0x06,0x06,0x06,0x07,0x07,0x08,0x08,0x09,0x09,0x03];
结果打印: truevar cards = [0x06,0x06,0x06,0x06,0x07,0x08,0x11,0x12,0x013,0x21,0x21,0x21,0x03];
结果打印: truevar cards = [0x11,0x19,0x19,0x04,0x09,0x06,0x25,0x07,0x27,0x08,0x09,0x09,0x03];
结果打印:false
8.总结
大概用了两天的时间完成的,感觉搞清流程和原理之后,再按着步骤去做,就很简单了。只是原来写习惯java了,用node.js实现时要格外注意内存泄露的问题。
麻将通用胡牌算法详解(拆解法)相关推荐
- 麻将普通胡牌算法JS版(含癞子,非轮训)
记录一下麻将的通用胡牌算法实现,只要满足X*ABC + Y*DDD + EE 即可胡牌. 在这里先分析一下最简单的胡牌思路:先找出所有可能的将牌,若除去两张将牌之外的所有牌都能成刻或顺,则可胡牌. 将 ...
- 会排序吗_洗牌算法详解:你会排序,但你会打乱吗?
预计阅读时间: 8 分钟 我知道大家会各种花式排序,但是如果叫你打乱一个数组,你是否能做到胸有成竹?即便你拍脑袋想出一个算法,怎么证明你的算法就是正确的呢?乱序算法不像排序算法,结果唯一可以很容易检验 ...
- C语言中数组的排序算法详解——选择法、冒泡法、交换法、插入法、折半法
选择法排序 选择法排序是指:如果要把一个数组从小到大排列,那么就从该数组中依次选择最小的数字来排序.从第一个数字开始,将第一个数字与数组中剩下数字中最小的那一个交换位置,然后将第二个数字与剩下数字中最 ...
- 包含癞子的麻将胡牌算法
记录一下麻将的通用胡牌算法实现,只要满足M x ABC + N x DDD + EE 即可胡牌. 在这里先分析一下最简单的胡牌思路:先找出所有可能的将牌,若除去两张将牌之外的所有牌都能成扑,则可胡牌. ...
- 麻将胡牌算法带癞子 python实现
姐姐:你去帮我和闺蜜打麻将? 学霸哥哥:可是我不会打麻将呀! 姐姐:你不是学霸吗?我教你一个麻将公式,我闺蜜可是单身哟! 学霸哥哥:什么公式? 姐姐:麻将胡牌公式: AAA*M+ABC*N+BB,WM ...
- 麻将胡牌算法的一种设计及其分析
马勇波 陈欣庆 (解放军理工大学工程兵工程学院研究生二队,南京 210007) 摘 要 文章通过一个二维数组定义麻将的数据结构,并在此基础上设计了一种判断麻将是否胡牌的算法,该算法主 ...
- 快排亲兄弟:快速选择算法详解
后台回复进群一起刷力扣???? 点击下方卡片可搜索文章???? 读完本文,可以去力扣解决如下题目: 215.数组中的第 K 个最大元素(Medium) 快速选择算法是一个非常经典的算法,和快速排序算法 ...
- 可带癞子的通用麻将胡牌算法
本文原创文章,转载注明出处,博客地址 https://segmentfault.com/u/to... 第一时间看后续精彩文章.觉得好的话,顺手分享到朋友圈吧,感谢支持. 笔者前段时间做过一款地方麻将 ...
- 麻将胡牌算法(遍历+剪枝)
麻将胡牌算法(遍历+剪枝) 简介 麻将胡牌算法及代码 1. 方法引入 2. 类型定义 2.1 牌定义 2.2 牌特征定义 3. 计算胡牌 3.1 检测十三幺牌型 3.2 检测七小对牌型 3.3 检测普 ...
最新文章
- sass文件编译的三种方式【舒】
- react的导出是怎么实现的_22 个让 React 开发更高效更有趣的工具
- 335b装配程序流程图_某建设项目装配式框架结构PC构件吊装监理细则(方案)
- 矩阵相乘原理与C实现(实矩阵)
- 无线网络的网速很慢_家里无线网络每天不定时段出现网速很慢或者直接无连接,这是怎么回事?...
- windowsphone开发_APP软件开发用哪些软件比较好
- java io流大全_Java IO流系统整理
- Java顺序IO性能
- ios label文字行间距_iOS- 设置label的行间距字体间距
- 小于三位的正整数 正则式_正则表达式
- 【华为云技术分享】物体检测yolo3算法 学习笔记2
- 粤嵌gec6818项目设计_西安市幸福林带景观及亮化设计国际竞赛终期评审会顺利举行...
- 蚂蚁森林:不存在网友反馈的“没有造林”的情况 干旱造成梭梭矮小
- WebStrom的学习使用 H5开发
- JS前端加密JAVA后端解密详解
- JVM内存模型和结构详解(五大模型图解)
- 【工具封装】不用 for 循环, 教你如何向MySQL数据库批量插入数据
- AD17报错:InvalidParameter Exception Occurred In Copy
- 全新交通大动脉!华为智慧铁路解决方案助力中老铁路正式开通;马瑞利任命樊坚强先生为中国区总裁 | 美通社头条...
- 最新版勤哲Excel服务器V2017.13.0.1无限用户支持手机APP,微信,任意安装,支持后续升级