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

普通麻将的出牌AI如果不是要求特别高的话,其实蛮容易实现的,毕竟大多数人打牌都只是看自己的手牌。

所以作为简易版的AI,出牌的策略只要奔着胡牌去就可以了。我们能想到的就是把相邻或相同的牌凑到一起,把单独的牌打出去。以这个思路打牌,就会慢慢接近听牌至最终的胡牌。

我们简单举个例子,我们有1万2万,那么我们认为其打出去的优先级要高于单独的牌,因为其只需要1个三万就可以凑成一组了。

这种思路实际很像我们上一章讲的多赖子判胡的优化算法——插空法,如果没有看过上一博客的可以先看http://blog.csdn.net/sm9sun/article/details/77774722

 

我们看这里

 

[javascript] view plain copy
 
  1. for (var i = 0; i < 4; i++) {    
  2.        var needhun = 0;    
  3.        for (var j = 0; j < 4; j++) {    
  4.            needhun += j == i ? getneedhun(arr, j,false) : getneedhun(arr, j,true);    
  5.        }    
  6.        if (needhun <= Huncount) {    
  7.            return true;    
  8.        }    
  9.    }    


我们通过needhun是否大于本身拥有的混牌(赖子)来判断是否可以胡,那么其实这个needhun就可以代表不同牌型胡牌的难度,即needhun值越小其越容易胡,当needhun值为1时(手牌数为3n+1),其处于听牌阶段。

 

 

那么这就很简单了,我们只需要在此基础上返回这个needhun,然后在出牌时看出那张牌返回的needhun最小,其就是我们的最佳策略选择。

 

[javascript] view plain copy
 
  1. function get_needhun_for_hu(old_arr, Hun, special) {  
  2.     var fmin_data = function (data1, data2) {  
  3.         return data1.needhun > data2.needhun ? data2 : data1;  
  4.     };  
  5.     var del_list = function (old_arr, i, j, data) {  
  6.         var arr = old_arr.concat();  
  7.         for (var k = 0; k < 3; k++) {  
  8.             if (arr[i + k] > 0) {  
  9.                 arr[i + k]--;  
  10.             }  
  11.             else {  
  12.                 data.needhun++;  
  13.             }  
  14.         }  
  15.         return dfs(arr, i, j, data);  
  16.     };  
  17.     var del_same = function (old_arr, i, j, data) {  
  18.         var arr = old_arr.concat();  
  19.         arr[i] %= 3;  
  20.         switch (arr[i]) {  
  21.             case 0: {  
  22.                 break;  
  23.             }  
  24.             case 1: {  
  25.                 if (data.hasjiang) { data.needhun += 2; }  
  26.                 else { data.needhun++; data.hasjiang = true; }  
  27.                 break;  
  28.             }  
  29.             case 2: {  
  30.                 if (data.hasjiang) { data.needhun += 1; }  
  31.                 else { data.hasjiang = true; }  
  32.                 break;  
  33.             }  
  34.         }  
  35.         arr[i] = 0;  
  36.         return dfs(arr, i + 1, j, data);  
  37.     };  
  38.     var dfs = function (arr, i, j, data) {  
  39.         if (i > j) {  
  40.             if (!data.hasjiang) {  
  41.                 data.needhun += 2;  
  42.             }  
  43.             return data;  
  44.         }  
  45.   
  46.         if (i % 9 == 6 && i < 27 && arr[i + 1]%3 == 1 && arr[i + 2]%3 == 1)//8 9特殊情况,此时应该补个7  
  47.         {  
  48.             return del_list(arr, i, j, data);  
  49.         }  
  50.         else if (arr[i] == 0) {  
  51.             return dfs(arr, i + 1, j, data);  
  52.         }  
  53.         else if (i % 9 < 7 && i < 27 && (arr[i + 1] > 0 || arr[i + 2] > 0)) {  
  54.             var tmp1 = del_list(arr, i, j, { needhun: data.needhun, hasjiang: data.hasjiang });  
  55.             var tmp2 = del_same(arr, i, j, { needhun: data.needhun, hasjiang: data.hasjiang });  
  56.             return fmin_data(tmp1, tmp2);  
  57.         }  
  58.         else { return del_same(arr, i, j, data); }  
  59.   
  60.   
  61.     };  
  62.     var getneedhun = function (old_arr, type, hasjiang) {  
  63.         var data = {  
  64.             needhun: 0,  
  65.             hasjiang: hasjiang  
  66.         };  
  67.         var arr = old_arr.concat();  
  68.         var i, j;  
  69.         switch (type) {  
  70.             case 0: { i = 0; j = 8; break; }  
  71.             case 1: { i = 9; j = 17; break; }  
  72.             case 2: { i = 18; j = 26; break; }  
  73.             case 3: { i = 27; j = 33; break; }  
  74.         }  
  75.         data = dfs(arr, i, j, data);  
  76.         return data.needhun;  
  77.     };  
[javascript] view plain copy
 
  1.     var arr = old_arr.concat();  
  2.     var HunCount = 0;  
  3.     if (Hun >= 0) {  
  4.         HunCount = arr[Hun];  
  5.         arr[Hun] = 0;  
  6.   
  7.   
  8.     }  
  9.     var count = 0;  
  10.     for (var i = 0; i < arr.length; i++) {  
  11.         count += arr[i];  
  12.     }  
  13.     var min_needhun = 0x1f;  
  14.     if (special.H_7pair&&count + HunCount == 14) {  
  15.         var needhun = 0;  
  16.         for (var i = 0; i < arr.length; i++) {  
  17.             var c = arr[i];  
  18.             if (c % 2==1) {  
  19.                 needhun+=2;  
  20.             }  
  21.         }  
  22.         if (needhun<min_needhun) {  
  23.             min_needhun = needhun;  
  24.         }  
  25.     }  
  26.     if (special.H_13one&&count + HunCount == 14) {  
  27.         var ones = [0, 8, 9, 17, 18, 26, 27, 28, 29, 30, 31, 32, 33];  
  28.         for (var i = 0; i < ones.length; ++i) {  
  29.             if (arr[ones[i]] == 0) {  
  30.                 needhun+=2;  
  31.             }  
  32.         }  
  33.         if (needhun < min_needhun) {  
  34.             min_needhun = needhun;  
  35.         }  
  36.     }  
  37.     for (var i = 0; i < 4; i++) {  
  38.         var needhun = 0;  
  39.         for (var j = 0; j < 4; j++) {  
  40.             needhun += getneedhun(arr, j, j != i);  
  41.         }  
  42.         if (needhun < min_needhun) {  
  43.             min_needhun = needhun;  
  44.         }  
  45.     }  
  46.     return min_needhun - HunCount;  
  47. }  

 

 注:七小对&十三幺相对来说需要的混子数要少,原因在于当手牌过散时每张废牌都意味着需要2张混牌,而七小对&十三幺则只把废牌当作所需要替换的1张牌处理,这使得大部分散牌更适用于七小对&十三幺。故needhun需要+2以作平衡

 

还有就是如果当前处于可以听牌的阶段(needhun==1),那么可以在此之前选择听口最多的或者剩余牌最多的等策略

 

 

[javascript] view plain copy
 
  1.     var ret_needhun = 0x1a;  
  2.     var ret_pai = list[0];  
  3.     var ret_tinglist = [];  
  4.     for (var k = 0; k < list.length; k++) {  
  5.         arr[list[k]]--;        
  6.         var Tinglist = new Array();  
  7.         var canting = CanTingPai(arr, hun, Tinglist, special);  
  8.         if (canting)  
  9.         {  
  10.             //听牌数比对,也可以按其他方式比对,比如所听的牌接下来的剩余牌  
  11.             if (ret_tinglist.length < Tinglist.length)  
  12.             {  
  13.                 ret_tinglist = Tinglist;  
  14.                 ret_needhun = 1;  
  15.                 ret_pai = list[k];  
  16.             }  
  17.               
  18.         }  
  19.         else if (ret_tinglist.length==0)  
  20.         {  
  21.             var needhun = get_needhun_for_hu(arr, hun, special);  
  22.             if (needhun < ret_needhun) {  
  23.                 ret_needhun = needhun;  
  24.                 ret_pai = list[k];  
  25.             }  
  26.         }  
  27.         arr[list[k]]++;  
  28.           
  29.     }  
  30.     return ret_pai;  
  31. }  

 

关于判听的算法有很多,大家可以参考我的前几篇麻将算法博客。

 

 

如果不是听牌的状态且needhun和ret_needhun相同时可以根据不同的麻将玩法再融入自己的想法策略DIY

比如:优先打风牌、优先打19等。

 

[javascript] view plain copy
 
  1. else if (needhun == ret_needhun)  
  2. {  
  3.     if (list[k] > 26)//风牌优先打  
  4.     {  
  5.         ret_needhun = needhun;  
  6.         ret_pai = list[k];  
  7.     }  
  8.     if ((list[k] % 9 < 1 || list[k] % 9 > 7 )&& ret_pai<=26)//边牌优先打  
  9.     {  
  10.         ret_needhun = needhun;  
  11.         ret_pai = list[k];  
  12.     }  
  13. }  

 

再比如:优先打无关联单一的牌

 

[javascript] view plain copy
 
  1. exports.is_nexus = function(i, arr) {  
  2.     if (i > 26) {  
  3.         return arr[i] > 0;  
  4.     }  
  5.     else if (i % 9 == 8) {  
  6.         return arr[i] > 0 || arr[i - 1] > 0 || arr[i - 2] > 0;  
  7.     }  
  8.     else if (i % 9 == 7) {  
  9.         return arr[i] > 0 || arr[i - 1] > 0 || arr[i - 2] > 0 || arr[i + 1] > 0;  
  10.     }  
  11.     else if (i % 9 == 0) {  
  12.         return arr[i] > 0 || arr[i + 1] > 0 || arr[i + 2] > 0;  
  13.     }  
  14.     else if (i % 9 == 1) {  
  15.         return arr[i] > 0 || arr[i + 1] > 0 || arr[i + 2] > 0 || arr[i - 1] > 0;  
  16.     }  
  17.     else {  
  18.         return arr[i] > 0 || arr[i + 1] > 0 || arr[i + 2] > 0 || arr[i - 1] > 0 || arr[i - 2] > 0;  
  19.     }  
  20. }  

 

 

 

完整方法代码:

 

[javascript] view plain copy
 
  1. exports.GetRobotChupai = function (list, special, hun) {  
  2.   
  3.     if (hun == null) {  
  4.         hun = -1;  
  5.     }  
  6.     var arr = [];  
  7.     var Tingobj = [];  
  8.   
  9.     for (var i = 0; i < special.mj_count; i++) {  
  10.         arr[i] = 0;  
  11.     }  
  12.   
  13.     for (var j = 0; j < list.length; j++) {  
  14.         Tingobj[j] = {};  
  15.         if (arr[list[j]] == null) {  
  16.             arr[list[j]] = 1;  
  17.         }  
  18.         else {  
  19.             arr[list[j]]++;  
  20.         }  
  21.     }  
  22.   
  23.     var ret_needhun = 0x1a;//假设所有牌都需要2个混子补缺,即:13*2=26  
  24.     var ret_pai = list[0];  
  25.     var ret_tinglist = [];  
  26.     var has_single = false;  
  27.     for (var k = 0; k < list.length; k++) {  
  28.   
  29.         if (list[k] == hun) {  
  30.             continue;  
  31.         }  
  32.         arr[list[k]]--;  
  33.         var Tinglist = new Array();  
  34.         var canting = exports.CanTingPai(arr, hun, Tinglist, special);  
  35.         if (canting) {  
  36.             //听牌数比对,也可以按其他方式比对,比如所听的牌接下来的剩余牌  
  37.             if (ret_tinglist.length < Tinglist.length) {       
  38.                 ret_tinglist = Tinglist;  
  39.                 ret_needhun = 1;  
  40.                 ret_pai = list[k];  
  41.             }  
  42.   
  43.         }  
  44.         else if (ret_tinglist.length == 0) {  
  45.             var needhun = get_needhun_for_hu(arr, hun, special);  
  46.             if (!exports.is_nexus(list[k], arr))//如果是单一的手牌优先考虑  
  47.             {  
  48.                 if (!has_single)//如果之前没有过单一的牌  
  49.                 {  
  50.                     ret_needhun = needhun;  
  51.                     ret_pai = list[k];  
  52.                 }  
  53.                 has_single = true;  
  54.                 if (list[k] > 26)//风牌优先打  
  55.                 {  
  56.                     ret_needhun = needhun;  
  57.                     ret_pai = list[k];  
  58.                 }  
  59.                 if ((list[k] % 9 < 1 || list[k] % 9 > 7) && ret_pai <= 26)//边牌优先打  
  60.                 {  
  61.                     ret_needhun = needhun;  
  62.                     ret_pai = list[k];  
  63.                 }  
  64.             }  
  65.             else if (!has_single)//如果不是单一的手牌,且之前也没有过单一的牌  
  66.             {  
  67.                 if (needhun < ret_needhun)//判断此张牌需要的混牌数  
  68.                 {  
  69.                     ret_needhun = needhun;  
  70.                     ret_pai = list[k];  
  71.                 }  
  72.                 else if (needhun == ret_needhun) {  
  73.                     if (list[k] > 26)//风牌优先打  
  74.                     {  
  75.                         ret_needhun = needhun;  
  76.                         ret_pai = list[k];  
  77.                     }  
  78.                     if ((list[k] % 9 < 1 || list[k] % 9 > 7) && ret_pai <= 26)//边牌优先打  
  79.                     {  
  80.                         ret_needhun = needhun;  
  81.                         ret_pai = list[k];  
  82.                     }  
  83.                 }  
  84.             }  
  85.         }  
  86.         arr[list[k]]++;  
  87.   
  88.     }  
  89.     return ret_pai;  
  90. }  



 

 

 

 

如果你的手牌list是根据抓牌的顺序而没有进行过排序的话,也可以根据抓牌的顺序作为优先级参考等。

 

 

 

 

测试截图:

 

 

真实人机对战测试:(防作弊自摸胡、带赖子)

 

posted on 2018-06-12 22:25  &大飞  阅读(794)  评论(0)    收藏  举报

导航