最近在做一款叫做“卡五星”的三人麻将,来自湖北,麻将里只有筒和条(没有万)以及中发白这些牌。

其他的特殊功能暂且不提,其中有一个需求是玩家听牌后需要将与胡牌有关系的牌显示出来给其他玩家看。

举个例子,比如说我的手牌是1234677筒,此时我胡5筒(4,6),那么就要讲4筒,6筒显示出来。又比如7888筒(胡6,7,9筒),就要讲7888都显示出来。

放组样例图:

自己玩家视角

其他玩家视角

开始接到这个需求时候我是懵逼的,我们都知道大部分麻将胡牌算法都是将这个牌算进手牌中然后判断是否处于胡的状态,至于具体怎样判胡请参考之前的博客

http://blog.csdn.net/sm9sun/article/details/65448140

所以我们很难快速的定位究竟哪些牌是跟胡牌有关系的,如果你胡5筒,且你有4筒6筒,但是你不能确定就是4,6夹5筒,也有可能是234 67这样的牌型。

最起始的思路就是先把胡牌周围的牌拉出来,比如说5,如果手牌34同时存在的情况下,把34拉进来,如果46同时存在把46拉进来,如果67同时存在把67拉进来。同时5也要拉进来,因为可以是单吊或者对对胡等。但是又考虑到23467这样的牌型,所以把胡牌周边的牌拉进来的同时,也要把周边牌的周边也拉进来,因为你也不知道这些牌是跟胡的牌组成一个组合还是另有组合。那这就是一个扩展搜索的思路。至此,如果此思路实现了,那么至少我胡5筒时23467就都会显示出来,而不是显示3467。即使234是多余的,对于用户来说,也可以通过牌型分析出该玩家胡的牌。

//深度关联和胡牌有关系的牌
function dfs_Mingholds(PartMingholds, ismingMap, countMap,OldMingholds)
{if (OldMingholds.length == 0){return PartMingholds;}else{var NewMingholds = [];for (var k in  OldMingholds) {var c = OldMingholds[k];for (var i = 0; i < 34; i++) {if (i - c == 0 && !ismingMap[i] && countMap[i] > 0) {for (var j = 0; j < countMap[i]; j++) {PartMingholds.push(i);NewMingholds.push(i);}ismingMap[i] = true;}else if (i - c == 2 && !ismingMap[i] && countMap[i] > 0&&i!=9&&i!=10) {if (countMap[i - 1] > 0) {for (var j = 0; j < countMap[i]; j++) {PartMingholds.push(i);NewMingholds.push(i);}ismingMap[i] = true;}}else if (i - c == -2 && !ismingMap[i] && countMap[i] > 0 && i!=7&&i!=8) {if (countMap[i + 1] > 0) {for (var j = 0; j < countMap[i]; j++) {PartMingholds.push(i);NewMingholds.push(i);}ismingMap[i] = true;}}else if (i - c == 1 && !ismingMap[i] && countMap[i] > 0 && i != 9) {if (i - 2 >= 0) {if (countMap[i - 2] > 0 && !ismingMap[i]) {for (var j = 0; j < countMap[i]; j++) {PartMingholds.push(i);NewMingholds.push(i);}ismingMap[i] = true;}}if (i + 1 < 18) {if (countMap[i + 1] > 0 && !ismingMap[i]) {for (var j = 0; j < countMap[i]; j++) {PartMingholds.push(i);NewMingholds.push(i);}ismingMap[i] = true;}}}else if (i - c == -1 && !ismingMap[i] && countMap[i] > 0 && i != 8) {if (i + 2 < 18) {if (countMap[i + 2] > 0 && !ismingMap[i]) {for (var j = 0; j < countMap[i]; j++) {PartMingholds.push(i);NewMingholds.push(i);}ismingMap[i] = true;}}if (i - 1 >= 0) {if (countMap[i - 1] > 0 && !ismingMap[i]) {for (var j = 0; j < countMap[i]; j++) {PartMingholds.push(i);NewMingholds.push(i);}ismingMap[i] = true;}}}}}if (NewMingholds.length > 0){return  PartMingholds = dfs_Mingholds(PartMingholds, ismingMap, countMap,NewMingholds);}else{return PartMingholds;}     }
}

简单说明一下,PartMingholds为记录部分明牌的序列数组,ismingMap是存储该编号牌是否已经处于明牌状态,比如说我胡36,那么3会关联45,6也会关联45,防止重复计算。countMap是手牌,countMap[i]=k表示手里有k张第i号牌。OldMingholds是上一级维护的明牌序列。也就是当前层级搜索的范围。

麻将的编号说明:0-8表示筒,9-17表示条,18-26表示万(这里没有),中27 发28 白29 东30 南31 西32 北33 (东西南北也没有)

因为条和中并不连续,所以在做顺子关联判断是唯一要区分的就是筒和条,所以我们看到判断里会加上i!=8等限制。如果当前层级没有关联新的元素,即NewMingholds的长度为0,那么我们就返回PartMingholds即可,否则需要根据新关联的元素继续搜索。开始讲胡牌序列作为OldMingholds引入。

至此为止,我们已经过滤掉和胡牌毫无关系的牌了,那么接下来,我们就将这个PartMingholds稍微优化一下,剔除不需要显示的牌。

首先就是顺子的剔除,我们以顺子中间的牌为基准,什么样的顺子我们可以直接剔除呢?

1:将明牌序列排序后,如果该顺子位于序列的两端,且不两端的扩展牌不胡。那么就可以直接剔除。比如说,排完序后我的左端是4,5,6且我不胡3,4。那么就可以认为这4,5,6必定要组成一组且跟胡牌没有关系。右端同理。

2:如果该顺子的有一端不连续且也不胡扩展边的这张牌,那么其也一定是固定的一组,比如说2245677。已知4的左侧不是3,且也不胡3,也不胡4,那么4,5,6必然要组成顺子。此牌该显示2277

    //顺子剔除for (var i = 1; i <= PartMingholds.length - 2; ++i) {if (PartMingholds[i + 1] - PartMingholds[i] == 1 && PartMingholds[i] - PartMingholds[i - 1] == 1) {if ((PartMingholds[i] >= 1 && PartMingholds[i] <= 7) || (PartMingholds[i] >= 10 && PartMingholds[i] <= 16)) {if (((i == 1) && (tinglist.indexOf(PartMingholds[i] - 2) < 0) && (tinglist.indexOf(PartMingholds[i] - 1) < 0))|| ((i == PartMingholds.length - 2) && (tinglist.indexOf(PartMingholds[i] + 2) < 0) && (tinglist.indexOf(PartMingholds[i] + 1) < 0))) {PartMingholds.splice(i - 1, 3);}else {if ((PartMingholds[i - 2] < PartMingholds[i] - 2) && (tinglist.indexOf(PartMingholds[i] + 2) < 0) && (PartMingholds.indexOf(PartMingholds[i] + 2) < 0)&& (tinglist.indexOf(PartMingholds[i] - 2) < 0)) {PartMingholds.splice(i - 1, 3);}else if ((PartMingholds[i + 2] > PartMingholds[i] + 2) && (tinglist.indexOf(PartMingholds[i] - 2) < 0) && (PartMingholds.indexOf(PartMingholds[i] - 2) < 0)&& (tinglist.indexOf(PartMingholds[i] + 2) < 0)) {PartMingholds.splice(i - 1, 3);}}}}}

将无关联的顺子剔除后,我们进一步的判断牌型,首先是尝试寻找不能组成顺子的两张牌,因为很有可能就是这两张牌是我们最终要显示出来的牌。很简单, 我们遍历整个明牌的序列,如果只有2张牌无法与其他的牌组成顺子,那么一定就是他俩了,因为他俩得跟胡的那张牌组成一个组合。我们暂且称这两张牌为cp牌

    for (var i = 0; i < PartMingholds.length; ++i) {for (var j = i + 1; j < PartMingholds.length; ++j) {for (var k = j + 1; k < PartMingholds.length; ++k) {if ((PartMingholds[j] - PartMingholds[i] == 1 && PartMingholds[k] - PartMingholds[j] == 1)) {if (PartMingholds[j] != 9 && PartMingholds[k] != 9 && PartMingholds[i] < 18) {is_cp[i] = false;is_cp[j] = false;is_cp[k] = false;}}}}if (is_cp[i]) {delMinghold.push(PartMingholds[i]);cp_count++;}}

在cp牌计算完后,如果出现cp_count大于2的情况,那么一定是坎或者对子也在关联牌中,我们将其剔除。

 //坎牌剔除var t = 0;var ti = -1;if (cp_count > 3) {for (var i = 0; i < delMinghold.length - 2; ++i) {if (delMinghold[i] == delMinghold[i + 1] && delMinghold[i] == delMinghold[i + 2]&& delMinghold.length % 3 == 2){t++;ti = i;}}if (t == 1 && tinglist.indexOf(delMinghold[ti]) < 0) {cp_count -= 3;delMinghold.splice(ti, 3);}}//对子剔除var p = 0;var pi = -1;if (cp_count > 2) {for (var i = 0; i < delMinghold.length - 1; ++i) {if (delMinghold[i] == delMinghold[i + 1]) {p++;pi = i;}}if (p == 1 && tinglist.indexOf(delMinghold[pi]) < 0) {cp_count -= 2;delMinghold.splice(pi, 2);}}

这里可能有人不理解,对牌剔除需要判断p是否等于1,因为一副胡牌最后不能有2对,坎牌为什么也要判断呢?因为如果坎牌存在2个以上且都跟胡牌相关,那么其一定会组成特殊的牌型,比如说6777888或者5556777等。并且需要判断 delMinghold.length % 3 == 2。因为如果余数是1,那么这个坎牌一定是参与胡牌了,比如7888。因为如果不关联在之前的处理就已经过滤掉了,比如2555这样。只有余数是2的情况,才能剔除,因为剩下的内两个牌可以做成胡口。

最后我们判断cp的状态:

如果cp_count == 2且只胡一张牌,那么必定是夹胡或者边37胡,否则如果这两张cp牌不等,那么就是两端胡。例如34胡25这样。如果两张cp牌相等,那么必定是对对胡,且另一个对于其他牌型可以连成顺,比如2233456这样。

如果cp_count == 1且只胡一张牌,那么一定是夹胡且另一端可以连成顺,比如24567这样。

除此之外的牌型太过复杂,应该都会有所关联,那么将整个PartMingholds显示出来即可。

 if (cp_count == 2) {if (tinglist.length == 1) {return delMinghold;}else if (delMinghold[0] != delMinghold[1]) {return delMinghold;}else if (tinglist.length == 2 && (delMinghold[0] == tinglist[0] || delMinghold[0] == tinglist[1])) {return [tinglist[0], tinglist[0], tinglist[1], tinglist[1]];}else {return PartMingholds;}}else if (cp_count == 1) {if (tinglist.length == 1) {if (tinglist[0] - delMinghold[0] == 1) {delMinghold.push(tinglist[0] + 1);return delMinghold;}else if (tinglist[0] - delMinghold[0] == -1) {delMinghold.push(tinglist[0] - 1);return delMinghold;}else {return PartMingholds;}}else {return PartMingholds;}

补充一些变量的说明:

    var cp_count = 0;           //cp牌的数量var delMinghold = [];       //剔除后的明牌序列var is_cp = [];             //判断该编号牌是否为cpvar tinglist = [];          //听牌序列

另外可以加一些常见牌型的优先处理,比如57这样的夹胡,或者67这样的两端胡,以及七小对神马的

//优先处理,提高效率if (tinglist.length == 1) {var p1 = tinglist[0] + 1;var p2 = tinglist[0] - 1;if (PartMingholds.indexOf(p1) > 0 && PartMingholds.indexOf(p2) > 0 && (p1 != 9 && p1 != 10 && p2 != 8 && p2 != 7)) {return [p1, p2];}}if (tinglist.length == 2 && ((tinglist[1] - tinglist[0]) == 3 || (tinglist[0] - tinglist[1]) == 3)) {var p1 = tinglist[1] > tinglist[0] ? tinglist[1] - 1 : tinglist[0] - 1;var p2 = p1 - 1;if (PartMingholds.indexOf(p1) > 0 && PartMingholds.indexOf(p2) > 0 && (p1 < 8 || p2 > 9)) {return [p1, p2];}}

其他特殊牌型方法类似,就不一一写出来了。

……if (k.pattern == "7pairs"){return [parseInt(k)];}
……

运算前必须将 PartMingholds排好序,is_cp等相关状态数组初始化。

至此这部分算法就大功告成了,目前还没有发现特殊牌型的bug,但是从逻辑上来说不是100%的缜密。在之前测试的过程中也是经过了反复的修改。也一度很崩溃很绝望。我在【以明牌序列为主干进行剔除处理】还是以【听牌个数为主干进行分支处理】这两种方式纠结了很久,最终算是中和了一下,勉强过关。

这个需求优先级并不高,所以也不可能花太多时间去完成,等闲下来时我会再想想,或许会想到更好的解决方案吧。

node.js——麻将算法(三)胡牌相关明牌相关推荐

  1. node.js——麻将算法(五)胡牌算法的一些优化处理方案(有赖子版)

    以前有赖子判胡算法 http://blog.csdn.net/sm9sun/article/details/65632646 以前的帖子说明了处理赖子的两种方案:枚举代替及插空补缺,并最终选择了枚举遍 ...

  2. node.js——麻将算法(一)基本判胡

    首先带来的就是麻将胡牌.听牌的算法,不过大家都知道,麻将各个地方的规则都不同,所以相关算法也需要作出一定的调整. 先简单说一下本次demo的规则要求把. 1.不计番,也就是没那么多胡法,最后胡了就行. ...

  3. node.js——麻将算法(七)简易版麻将出牌AI2.0

    *文本为上一篇博客http://blog.csdn.net/sm9sun/article/details/77898734的部分追加优化 上一篇博客已经实现了基本的出牌逻辑,大部分情况能够给出正确的策 ...

  4. node.js——麻将算法(二)赖子玩法

    上篇博客传送门http://blog.csdn.net/sm9sun/article/details/65448140 上文中已经实现了基本胡法的算法,本章加入"癞子玩法"的判胡逻 ...

  5. node.js——麻将算法(四)胡牌算法的一些优化处理方案(无赖子版)

    回想三月份刚接触棋牌时写过一些麻将的算法,转眼间半年过去了,回顾下曾经的代码,写的还真是蛮low的 http://blog.csdn.net/sm9sun/article/details/654481 ...

  6. node.js——麻将算法(六)简易版麻将出牌AI1.0

    普通麻将的出牌AI如果不是要求特别高的话,其实蛮容易实现的,毕竟大多数人打牌都只是看自己的手牌. 所以作为简易版的AI,出牌的策略只要奔着胡牌去就可以了.我们能想到的就是把相邻或相同的牌凑到一起,把单 ...

  7. 麻将算法之 ------ 胡牌算法

    麻将数据牌集合 private int[] cardDataArray ={0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, //万子 1 2 ...

  8. node.js 学习笔记三:路由url

    一.修改server.js var http = require("http"); var url = require("url"); //导入内置url模块f ...

  9. 你不知道的Node.js性能优化,读了之后水平直线上升

    本文由云+社区发表 "当我第一次知道要这篇文章的时候,其实我是拒绝的,因为我觉得,你不能叫我写马上就写,我要有干货才行,写一些老生常谈的然后加上好多特技,那个 Node.js 性能啊好像 D ...

最新文章

  1. pycuda write complex numbers — errors:class “cuComplex” has no member “i”
  2. 记录java应用部署到k8s中
  3. 业内大佬怒喷 Windows 10 Cloud:最大的流氓软件!
  4. 第二章 变量、数据类型和运算符
  5. [react] 你对immutable有了解吗?它有什么作用?
  6. 【ES6(2015)】Class (类)
  7. 孤岛惊魂5服务器稳定吗,这才是《孤岛惊魂5》真正的“最低画质”
  8. Python中遍历指定目录
  9. 求解模糊运动角度matlab,动态模糊图像复原MATLAB程序
  10. Google Talk Testing(早期版本)
  11. 用Java实现向Cassandra数据库中插入和查询数据
  12. bootstrap学习笔记-(1-初识bootstrap)
  13. c# spire.xls 设置文字为微软雅黑_微软自带de白板应用,超好用
  14. 黑客是什么,什么是黑客,它起源于什么,黑客是干什么的 ,真的,有所谓的“黑客帝国”吗?
  15. WebRtc与P2P
  16. python识图坐标_python 识别minecraft截图坐标
  17. Tek DPO2024B示波器和电流探头A622的使用
  18. STM32CubeMX创建F429/L475 HAL库工程并移植UCOSIII (二)(文末附源码)
  19. vue 使用three.js 实现3D渲染
  20. recon-ng详细使用教程

热门文章

  1. 27 SD配置-主数据-信用管理-定义风险类别
  2. usnews美国大学计算机科学排名,2019年usnews美国大学计算机科学排名
  3. php通过smtp发送邮件源码_PHP SMTP发送邮件函数
  4. BiSeNet V2论文及源码
  5. 【pytorch】torch.range() 和 torch.arange() ==>以step为间隔输出从start到end的张量列表
  6. 3-3HDFS架构详解
  7. koa上传文件处理403
  8. vue 页面不置顶问题(页面内操作、页面跳转后) - 集合篇
  9. elementUI表格组件:自定义列模板(完整案例)
  10. python http get 请求_Python-Http请求库-Requests and AIOHTTP的使用