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实现时要格外注意内存泄露的问题。

麻将通用胡牌算法详解(拆解法)相关推荐

  1. 麻将普通胡牌算法JS版(含癞子,非轮训)

    记录一下麻将的通用胡牌算法实现,只要满足X*ABC + Y*DDD + EE 即可胡牌. 在这里先分析一下最简单的胡牌思路:先找出所有可能的将牌,若除去两张将牌之外的所有牌都能成刻或顺,则可胡牌. 将 ...

  2. 会排序吗_洗牌算法详解:你会排序,但你会打乱吗?

    预计阅读时间: 8 分钟 我知道大家会各种花式排序,但是如果叫你打乱一个数组,你是否能做到胸有成竹?即便你拍脑袋想出一个算法,怎么证明你的算法就是正确的呢?乱序算法不像排序算法,结果唯一可以很容易检验 ...

  3. C语言中数组的排序算法详解——选择法、冒泡法、交换法、插入法、折半法

    选择法排序 选择法排序是指:如果要把一个数组从小到大排列,那么就从该数组中依次选择最小的数字来排序.从第一个数字开始,将第一个数字与数组中剩下数字中最小的那一个交换位置,然后将第二个数字与剩下数字中最 ...

  4. 包含癞子的麻将胡牌算法

    记录一下麻将的通用胡牌算法实现,只要满足M x ABC + N x DDD + EE 即可胡牌. 在这里先分析一下最简单的胡牌思路:先找出所有可能的将牌,若除去两张将牌之外的所有牌都能成扑,则可胡牌. ...

  5. 麻将胡牌算法带癞子 python实现

    姐姐:你去帮我和闺蜜打麻将? 学霸哥哥:可是我不会打麻将呀! 姐姐:你不是学霸吗?我教你一个麻将公式,我闺蜜可是单身哟! 学霸哥哥:什么公式? 姐姐:麻将胡牌公式: AAA*M+ABC*N+BB,WM ...

  6. 麻将胡牌算法的一种设计及其分析

    马勇波  陈欣庆 (解放军理工大学工程兵工程学院研究生二队,南京 210007)       摘  要  文章通过一个二维数组定义麻将的数据结构,并在此基础上设计了一种判断麻将是否胡牌的算法,该算法主 ...

  7. 快排亲兄弟:快速选择算法详解

    后台回复进群一起刷力扣???? 点击下方卡片可搜索文章???? 读完本文,可以去力扣解决如下题目: 215.数组中的第 K 个最大元素(Medium) 快速选择算法是一个非常经典的算法,和快速排序算法 ...

  8. 可带癞子的通用麻将胡牌算法

    本文原创文章,转载注明出处,博客地址 https://segmentfault.com/u/to... 第一时间看后续精彩文章.觉得好的话,顺手分享到朋友圈吧,感谢支持. 笔者前段时间做过一款地方麻将 ...

  9. 麻将胡牌算法(遍历+剪枝)

    麻将胡牌算法(遍历+剪枝) 简介 麻将胡牌算法及代码 1. 方法引入 2. 类型定义 2.1 牌定义 2.2 牌特征定义 3. 计算胡牌 3.1 检测十三幺牌型 3.2 检测七小对牌型 3.3 检测普 ...

最新文章

  1. sass文件编译的三种方式【舒】
  2. react的导出是怎么实现的_22 个让 React 开发更高效更有趣的工具
  3. 335b装配程序流程图_某建设项目装配式框架结构PC构件吊装监理细则(方案)
  4. 矩阵相乘原理与C实现(实矩阵)
  5. 无线网络的网速很慢_家里无线网络每天不定时段出现网速很慢或者直接无连接,这是怎么回事?...
  6. windowsphone开发_APP软件开发用哪些软件比较好
  7. java io流大全_Java IO流系统整理
  8. Java顺序IO性能
  9. ios label文字行间距_iOS- 设置label的行间距字体间距
  10. 小于三位的正整数 正则式_正则表达式
  11. 【华为云技术分享】物体检测yolo3算法 学习笔记2
  12. 粤嵌gec6818项目设计_西安市幸福林带景观及亮化设计国际竞赛终期评审会顺利举行...
  13. 蚂蚁森林:不存在网友反馈的“没有造林”的情况 干旱造成梭梭矮小
  14. WebStrom的学习使用 H5开发
  15. JS前端加密JAVA后端解密详解
  16. JVM内存模型和结构详解(五大模型图解)
  17. 【工具封装】不用 for 循环, 教你如何向MySQL数据库批量插入数据
  18. AD17报错:InvalidParameter Exception Occurred In Copy
  19. 全新交通大动脉!华为智慧铁路解决方案助力中老铁路正式开通;马瑞利任命樊坚强先生为中国区总裁 | 美通社头条...
  20. 最新版勤哲Excel服务器V2017.13.0.1无限用户支持手机APP,微信,任意安装,支持后续升级

热门文章

  1. Elasticsearch:如何使用 Elasticsearch ingest 节点来丰富日志和指标
  2. SAP 创建成本中心
  3. ARP攻击的原理与防范
  4. 高可用分布式存储 etcd 的实现原理
  5. 国内知名网络安全厂商介绍
  6. 战疫杯--奇奇怪怪的形状
  7. 2020百度提前批面试
  8. Java中的JSP是什么?如何实现JSP
  9. 解决500 Internal Privoxy Error问题
  10. PyQt5 程序多语言国际化的便捷实现