node.js 棋牌算法(二)

本文介绍了一种实现麻将中癞子玩法胡牌算法的方法。针对癞子牌的特殊性,提出了通过构造特定序列来减少计算量的技术方案,并详细展示了具体的实现逻辑及测试案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上文中已经实现了基本胡法的算法,本章加入“癞子玩法”的判胡逻辑


对于癞子的处理方式无非就两种:

一,以枚举的方式使癞子转换为其他牌型进行进一步判断

二,在计算判胡时出现位置空缺时使用癞子进行补位


前者的优势是准确度相对容易把控,并且逻辑清晰。坏处是时间消耗较高,因为是指数级别,当出现3个癞子时(4个癞子直接判胡)需要大量的运算。

后者的优势是时间消耗可能会好一些,注意是可能!因为如果在补位是加入癞子,会极大的影响剪枝。因为你不确定当前非胡状态是否可能通过多个癞子进行补救


但是如果傻傻的将34个牌型逐次遍历一边无疑显得很low逼。。于是我们在枚举前构造一个满足癞子任命的序列(比如说 如果你没有一二三万,那么你肯定是不会把癞子当成一万的)

  1. function  GetAppointList(arr,Hun,AppoinList){  
  2.     /* 
  3.      #define  0~8     万 
  4.      #define  9~17    饼 
  5.      #define  18~26   条 
  6.      #define  27      东 
  7.      #define  28      南 
  8.      #define  29      西 
  9.      #define  30      北 
  10.      #define  31      中 
  11.      #define  32      发 
  12.      #define  33      白 
  13.      */  
  14.   
  15.     for(i=0;i<34;++i)  
  16.     {  
  17.   
  18.             if (i > 26)              //风牌  
  19.             {  
  20.                 if (arr[i] > 0) {  
  21.                     AppoinList.push(i);  
  22.                 }  
  23.             }  
  24.             else if (i == 0 || i == 9 || i == 18)              //一,当有本身或者同时有二三时  
  25.             {  
  26.                 if (arr[i] > 0 || (arr[i + 1] > 0 && arr[i + 2] > 0)) {  
  27.                     AppoinList.push(i);  
  28.                 }  
  29.             }  
  30.             else if (i == 8 || i == 17 || i == 26) {  
  31.                 if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i - 2] > 0))   //九,当有本身或者同时有二三时  
  32.                 {  
  33.                     AppoinList.push(i);  
  34.                 }  
  35.             }  
  36.             else if (i == 1 || i == 10 || i == 19) {  
  37.                 if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i + 1] > 0 && arr[i + 2] > 0))   //二,当有本身或者同时有一三或者有三四时  
  38.                 {  
  39.                     AppoinList.push(i);  
  40.                 }  
  41.             }  
  42.             else if (i == 7 || i == 16 || i == 15) {  
  43.                 if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i - 1] > 0 && arr[i - 2] > 0))   //八,当有本身或者同时有七九或者有六七时  
  44.                 {  
  45.                     AppoinList.push(i);  
  46.                 }  
  47.             }  
  48.             else {  
  49.                 if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i - 1] > 0 && arr[i - 2] > 0) || (arr[i + 1] > 0 && arr[i + 2] > 0))   //其他中间牌  
  50.                 {  
  51.                     AppoinList.push(i);  
  52.                 }  
  53.             }  
  54.     }  
  55. }  
function  GetAppointList(arr,Hun,AppoinList){
    /*
     #define  0~8     万
     #define  9~17    饼
     #define  18~26   条
     #define  27      东
     #define  28      南
     #define  29      西
     #define  30      北
     #define  31      中
     #define  32      发
     #define  33      白
     */

    for(i=0;i<34;++i)
    {

            if (i > 26)              //风牌
            {
                if (arr[i] > 0) {
                    AppoinList.push(i);
                }
            }
            else if (i == 0 || i == 9 || i == 18)              //一,当有本身或者同时有二三时
            {
                if (arr[i] > 0 || (arr[i + 1] > 0 && arr[i + 2] > 0)) {
                    AppoinList.push(i);
                }
            }
            else if (i == 8 || i == 17 || i == 26) {
                if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i - 2] > 0))   //九,当有本身或者同时有二三时
                {
                    AppoinList.push(i);
                }
            }
            else if (i == 1 || i == 10 || i == 19) {
                if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i + 1] > 0 && arr[i + 2] > 0))   //二,当有本身或者同时有一三或者有三四时
                {
                    AppoinList.push(i);
                }
            }
            else if (i == 7 || i == 16 || i == 15) {
                if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i - 1] > 0 && arr[i - 2] > 0))   //八,当有本身或者同时有七九或者有六七时
                {
                    AppoinList.push(i);
                }
            }
            else {
                if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i - 1] > 0 && arr[i - 2] > 0) || (arr[i + 1] > 0 && arr[i + 2] > 0))   //其他中间牌
                {
                    AppoinList.push(i);
                }
            }
    }
}


这样相对来说会减少大量的循环次数。


然后再说外面,之前的判胡函数,由于加入4癞子直接胡,所以在顶层进行判断,以免后续递归重复计算

相应的基本判胡与七小对判胡放进下一步函数处理

  1. function CanHuPai(arr,Hun){  
  2.   
  3.     if(arr[Hun]==4)  
  4.     {  
  5.         return true;  
  6.     }  
  7.     else  
  8.     {  
  9.         var AppoinList=new Array();  
  10.         GetAppointList(arr, Hun,AppoinList);           //混牌可以任命的序列  
  11.         return HunAppoint(arr,Hun,arr[Hun],AppoinList);  
  12.   
  13.     }  
  14.   
  15. }  
function CanHuPai(arr,Hun){

    if(arr[Hun]==4)
    {
        return true;
    }
    else
    {
        var AppoinList=new Array();
        GetAppointList(arr, Hun,AppoinList);           //混牌可以任命的序列
        return HunAppoint(arr,Hun,arr[Hun],AppoinList);

    }

}


如果不是4癞子,则先求出可以任命的序列,然后进入癞子任命方法。



最后就是癞子任命方法的实现了,首先当癞子数为0时,调用原有的判胡算法进行判胡,如果有癞子,则使一个癞子成为某个牌,然后再递归调用。

function  HunAppoint(arr,Hun,HunCount,AppoinList)

arr为牌型状态数组,Hun为癞子ID,HunCount为未处理癞子个数  AppoinList为可以任命序列

这里可能有人会问,你的AppoinList难道不用刷新吗?  比如说 你有两个癞子,有一个孤独的一万, 你可以用两个癞子作为一二三万呀?

而你的序列不刷新,之前构造序列时判断也是必须当二三同时存在时才可以将一打入序列,这会不会有问题?

其实如果出现两个癞子与一个常牌组成一个组合的时候,我们可以只认为其算为3*该常牌,比如说我把他按照三个一万算,照样可以满足条件。

所以构造序列不存在两个癞子与一个常牌组成顺子的情况。


代码如下

  1. /* 
  2. 混牌任命牌型函数 
  3. 若无混牌,则通过麻将判胡算法判胡 
  4. 若有,则枚举可任命的牌序列,进行下一层递归 
  5.  */  
  6. function  HunAppoint(arr,Hun,HunCount,AppoinList) {  
  7.   
  8.   //  process.stdout.write(AppoinList+'\n');  
  9.     var ret=false;  
  10.     if (HunCount == 0) {  
  11.   
  12.   
  13.         if (CanHuPai__7pair(arr)) {  
  14.             return true;  
  15.         }  
  16.   
  17.         else if (CanHuPai_norm(arr)) {  
  18.             return true;  
  19.         }  
  20.         else {  
  21.             return false;  
  22.         }  
  23.     }  
  24.     else {  
  25.   
  26.         for(var i=0;i< AppoinList.length;++i) {  
  27.             arr[AppoinList[i]]++;  
  28.             arr[Hun]--;  
  29.             //console.log(e,AppoinList,HunCount);  
  30.             ret = HunAppoint(arr, Hun, HunCount - 1,AppoinList);  
  31.             arr[AppoinList[i]]--;  
  32.             arr[Hun]++;  
  33.             if (ret) {  
  34.                 return true;  
  35.             }  
  36.         }  
  37.     }  
  38.     return ret;  
  39. }  
/*
混牌任命牌型函数
若无混牌,则通过麻将判胡算法判胡
若有,则枚举可任命的牌序列,进行下一层递归
 */
function  HunAppoint(arr,Hun,HunCount,AppoinList) {

  //  process.stdout.write(AppoinList+'\n');
    var ret=false;
    if (HunCount == 0) {


        if (CanHuPai__7pair(arr)) {
            return true;
        }

        else if (CanHuPai_norm(arr)) {
            return true;
        }
        else {
            return false;
        }
    }
    else {

        for(var i=0;i< AppoinList.length;++i) {
            arr[AppoinList[i]]++;
            arr[Hun]--;
            //console.log(e,AppoinList,HunCount);
            ret = HunAppoint(arr, Hun, HunCount - 1,AppoinList);
            arr[AppoinList[i]]--;
            arr[Hun]++;
            if (ret) {
                return true;
            }
        }
    }
    return ret;
}


这里我要吐槽下,之前我用foreach来迭代AppoinList数组的,毕竟个人感觉arr[AppoinList[i]]可读性不是很好。。。

但是在foreach里用return竟然还会继续循环!!!!!!


例如:

[1, 2, 3, 4].forEach(function(e){
    console.log(e);
    if(e === 3) return;
})

照样输出1234


在这里浪费了大量的时间,我还以为我算法有问题= =

其应该算是一个独立的函数栈吧, return 只是从最里面的那个回调函数 return,不能从 forEach return

我也是日了狗了。


最后测试一下


input:

  1. arr[0]+=3;  
  2.   arr[1+9]+=1;  
  3.   arr[3+9]+=1;  
  4.   
  5.   arr[4+9]+=1;  
  6.   arr[5+9]+=0;  
  7.   arr[6+9]+=1;  
  8.   
  9.   arr[8+9]+=3;  
  10.   
  11.   arr[29]+=2  
  12.   arr[28]+=1  
  arr[0]+=3;
    arr[1+9]+=1;
    arr[3+9]+=1;

    arr[4+9]+=1;
    arr[5+9]+=0;
    arr[6+9]+=1;

    arr[8+9]+=3;

    arr[29]+=2
    arr[28]+=1



output

  1. 0,11,14,28,29  
  2. true  
  3. 2017-03-24T08:47:42.399Z 2017-03-24T08:47:42.515Z 116  
0,11,14,28,29
true
2017-03-24T08:47:42.399Z 2017-03-24T08:47:42.515Z 116

0(一万)是癞子,输出表示,可以胡一万,二饼,五饼,西风,南风。

应该是正确的。

116ms时间浪费的很多,不过后续还会进行很多优化。

原文地址:http://blog.youkuaiyun.com/sm9sun/article/details/65632646


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值