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

回想三月份刚接触棋牌时写过一些麻将的算法,转眼间半年过去了,回顾下曾经的代码,写的还真是蛮low的

 

http://blog.csdn.net/sm9sun/article/details/65448140  以前无赖子判胡算法

 

针对于半年前写的算法,CanHuPai_norm函数第一步就是剔除对子(即将牌),由于是将全部牌数组整体考虑,所以每个大于等于2的牌都有可能成为将牌。为了优化筛选,我做了针对于不同个数的剪枝判断:(Cancutpair_2 Cancutpair_3 Cancutpair_4)。但其实我们可以在此之前把手牌分成若干组,牌连续的为一组。满足条件的组合牌数包含(3N,3N+2)其中唯一的3N+2组就存在将牌,看过上述帖子大家应该可以理解3N张牌判胡是很简单的,而3N+2需要枚举所有可以剔除的将牌然后再进行3N判胡,这样我们分组后,3N+2的内个小组可供选择的就很少了,这样会极大的优化此部分逻辑处理时间。

代码:

 

[javascript]  view plain  copy
 
  1. var groups = [];  
  2. var group = {  
  3.     arr: [],  
  4.     count:0  
  5. };  
  6. for (var i = 0; i < arr.length;i++)  
  7. {        
  8.   
  9.     if (arr[i] > 0) {  
  10.         group.arr.push(arr[i]);  
  11.         group.count += arr[i];  
  12.     }  
  13.     if (i > 26 || arr[i + 1] == 0||i%9==8)//风或9或不连续  
  14.     {  
  15.         if (group.count > 0) {  
  16.             groups.push({ arr: group.arr.concat(), count: group.count});  
  17.             group.arr = [];  
  18.             group.count = 0;  
  19.         }            
  20.     }        
  21. }  
  22. console.log('groups:', JSON.stringify(groups));  

 

 

当i>26是风牌,当风牌或到9或连续牌中断,我们将其视为一组,例如:

我们看111311这个组合是唯一的3N+2,那么其一定包含将牌,所以我们很快就可以定位到是将牌4

同时,30,31,32是风派,所以被分割成单独的小组,其个数count是1,很显然,若存在count为1的组,此牌是不能胡的。

当我们将牌分完组后,就可以根据每小组的牌数量count进行分支处理了,此时group里的arr只代表连续牌的个数,其具体是什么牌已经不重要了。

 

[javascript]  view plain  copy
 
  1. var haspair = false;  
  2. for (var i = 0; i < groups.length;i++)  
  3. {  
  4.     var group = groups[i];  
  5.      
  6.     if (group.count % 3 == 1)  
  7.     {  
  8.         return false;  
  9.     }  
  10.     else if(group.count % 3 == 0)  
  11.     {  
  12.         if (!allow_0({ arr: group.arr.concat(), count: group.count }))  
  13.         {  
  14.             return false;  
  15.         }  
  16.     }  
  17.     else if (group.count % 3 == 2 && !haspair)  
  18.     {  
  19.         if (!allow_2({ arr: group.arr.concat(), count: group.count })) {  
  20.             return false;  
  21.         }  
  22.         haspair = true;  
  23.     }  
  24.     else   
  25.     {  
  26.         return false;  
  27.     }  
  28.   
  29. }  
  30. return haspair;  


如果是3N+1的数量,直接返回false,如果3N+2有多个或没有,也返回false。在打牌过程中,分支的顺序是按照牌数出现几率调整的,一局牌平均下来每次抓牌后出现单张的概率最高,其实是3N组合,而将牌一般只是单独的一个对,所以放在最后。

 

所以经过上述的处理,我们allow_2里实际已经没多少>=2的牌了,也没什么优化的必要了,我们可以枚举所有可能大于2的牌,然后剔除调用allow_0再回溯即可:

 

[javascript]  view plain  copy
 
  1. var allow_2 = function (group) {  
  2.         for (var i = 0; i < group.arr.length;i++)  
  3.         {  
  4.             if (group.arr[i] >= 2)  
  5.             {  
  6.                 group.arr[i] -= 2;  
  7.                 group.count -=2;  
  8.                 var ret = allow_0({ arr: group.arr.concat(), count: group.count });  
  9.                 group.arr[i] += 2;  
  10.                 group.count += 2;  
  11.                 if (ret)  
  12.                 {  
  13.                     return ret;  
  14.                 }  
  15.             }  
  16.         }  
  17.         return false;  
  18.     };  


最后就是3N判断的方法allow_0了,和之前算法的CanHuPai_3N_recursive函数类似,不过由于在之前分割的时候已经做了处理,所以就省去了很多边缘判断。

 

 

[javascript]  view plain  copy
 
  1. var allow_0 = function (group) {  
  2.   
  3.         for (var i = 0; i < group.arr.length; i++)  
  4.         {  
  5.             var c = group.arr[i] %= 3;  
  6.   
  7.             if (i > group.arr.length-3&&c!=0)//最后两张牌  
  8.             {  
  9.                 return false;  
  10.             }  
  11.             switch (c)  
  12.             {  
  13.                 case 0: break;  
  14.                 case 1: {  
  15.   
  16.                     if (group.arr[i + 1] >= 1 && group.arr[i + 2] >= 1) {  
  17.                         group.arr[i + 1] -= 1;  
  18.                         group.arr[i + 2] -= 1;  
  19.                     }  
  20.                     else {  
  21.                         return false;  
  22.                     }  
  23.                     break;  
  24.                 }  
  25.                 case 2: {  
  26.                     if (group.arr[i + 1] >= 2 && group.arr[i + 2] >= 2)  
  27.                     {  
  28.                         group.arr[i + 1] -= 2;  
  29.                         group.arr[i + 2] -= 2;  
  30.                     }  
  31.                     else  
  32.                     {  
  33.                         return false;  
  34.                     }  
  35.                     break;  
  36.                  }  
  37.                       
  38.             }  
  39.         }  
  40.   
  41.         return true;  
  42.     };  


注:当一组只剩最后两种牌后,证明其之前的牌均已处理完毕,那么此时这两种牌必然是3N个数,我们经常见到的牌就是两张连续的牌(因为他处于听牌阶段)

 

所以在函数的开始判断i > group.arr.length-3&&c!=0作为剪枝效果还是蛮不错的。

 

至于3N牌判胡算法网上有很多种,其实效率相差不大,因为都是遍历一遍(On)。例如将剔除的方案打个表:

 

[javascript]  view plain  copy
 
  1. [3] = 3, [4] = 3,  
  2. [31] = 30, [32] = 30, [33] = 33, [34] = 33, [44] = 33,  
  3. [111] = 111, [112] = 111, [113] = 111, [114] = 114,  
  4. [122] = 111, [123] = 111, [124] = 111,  
  5. [133] = 111, [134] = 111,  
  6. [141] = 141, [142] = 141, [143] = 141, [144] = 144,  
  7. [222] = 222, [223] = 222, [224] = 222,  
  8. [233] = 222, [234] = 222,  
  9. [244] = 222,  
  10. [311] = 300, [312] = 300, [313] = 300, [314] = 300,  
  11. [322] = 300, [323] = 300, [324] = 300,  
  12. [331] = 330, [332] = 330, [333] = 333, [334] = 333,  
  13. [341] = 330, [342] = 330, [343] = 330, [344] = 333,  
  14. [411] = 411, [412] = 411, [413] = 411, [414] = 414,  
  15. [422] = 411, [423] = 411, [424] = 411,  
  16. [433] = 411, [434] = 411,  
  17. [441] = 441, [442] = 441, [443] = 441, [444] = 444  


大家仔细观察会发现:

 

一:4开头的处理方式等同于1开头,因为其就等于把4先剔除3个变成1,即我上述代码中的var c = group.arr[i] %= 3;同理,3开头的也一样

二:1开头的一定会扣除111,2开头的一定会扣除222,因为其不够3张牌嘛。3开头的我们可以按0算,即扣除300,因为扣除333等同于扣除三次300

 

测试截图:

 

 

 

最后一提,这种算法由于不考虑本身是什么牌,所以一些特殊的玩法例如红黑风等要求就很难处理。且只适合用于打牌过程中计算判胡,胡之后的番数牌型结算也需另加计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值