吴昊品游戏核心算法(新年特别篇)—— 在你打麻将快要胡牌却不知道胡哪个字的时候可以用这个AI插件(模拟)

 

 图片选自著名的日本动画片——《天才麻将少女》

 全 国各地有不同的麻将,相同的麻将在不同的地域有着不同的规则,这不,今年过年,我就体会到了这一点。我爸爸的老家在湖南的长沙,而我妈的老家在湖南的邵 阳,虽然这两个城市都在湖南,但是由于地域的差异,长沙麻将中的“将”只能取为二,五,八,而邵阳麻将则可以乱来,什么都可以作为将(这个貌似也随着时间 的变化而变化,毕竟,时代在进步,规则也会随着改变)。我在《吴昊教你打麻将》中已经抽取CSDN的Source并补上了“七小队”和“十三幺”的胡牌办 法,这个插件,可以让你在拥有13张牌并且无限接近于胡牌(这个的术语我搞忘叫什么了,貌似长沙话好像是nuo-ting吧)的情况下,找到尽可能多的胡 牌方法(再任意将的牌中,这样的选择还是很多的,有时候甚至有10个左右的办法,只要你的牌尽可能“SUPER”)

 这个插件是用朴素的暴力算法实现的,虽然“很暴力”,但由于每个人所抓的牌的总数不算多(加上胡牌的那一张也就只有14张),所以即使是暴力,也不会消耗多长时间(普通的电脑1s内是足够的了),我们这里重新模拟一遍,并感悟一下check()函数的奥义。

 这里,我们为了简单起见,不考虑七小队和十三幺以及天胡,地胡等特殊的胡牌方式,实际上,对于智能取二五八为将的牌,也是非常好改的(加上条件判断就可以了)。

 现在,我们再来明确一下具体的规则(Source:HDOJ 3391),只要我们可以满足“4组+1对”,我们就赢了,其中,4组既可以是三张连续的(麻将术语为“吃”),也可以是三张相同的(麻将术语为碰”),吃吃碰碰,再加上一对“将”,就是胡牌的不二法门。这里,我们假设13张牌中,如果再补上一张牌就可以胡牌叫做“报警”,而即使补上一张也不行 的话,就只有输出“NONE”了。

 

 

 以上是万字(Character),我们这里用1c表示一万

 以上是筒字(Stone),我们这里用1s表示一筒

 以上是条字(Bamboo),我们这里用1b表示一条

 

 输入输出就不给出了,这里给出麻将的整个Source,以及亮点(Highlights,主要是check()函数的妙用)

 

 

  1  /*
  2    Highlights:
  3               (1)check()函数,套用fun函数,判断每一组牌是否可以组成1对4组
  4               (2)为了配合check()函数,在主函数中加入一个对种类牌为3的倍数时的剪枝
  5               (3)在fun()函数中,分别对吃和碰两种情况进行讨论
  6               (4)利用f[]数组,将int类型的数字重新转换为麻将的三个种类之一(万,筒,条)——f[ans[i]%10]
  7  */
  8  
  9  #include<stdio.h>
 10  
 11  //数组尽量开了一点,每一个类型的牌有9张,这里数组开到15
 12  int D[4][15];
 13  
 14  int fun(int *b)
 15  {
 16    int i;
 17    //如果都找完了,就返回
 18    if(b[0]==0)return 1;
 19    //找到第一张    
 20    for(i=1;i<=9;i++)
 21    {
 22      if(b[i])
 23        break;
 24    }
 25    if(b[i]>=3)
 26    {
 27      //如果某一张牌大于等于三张的话
 28      b[i]-=3;
 29      b[0]-=3;
 30      //由于每一种类型的牌都只考虑在四张以内的情况,所以三张如果相同,还有一张的话,会形成孤立
 31      if(fun(b)) return 1;
 32      b[i]+=3;
 33      b[0]+=3;
 34    }
 35    if(i<=7)
 36    {
 37      //拿掉最小牌i,和i+1,i+2 能配成n-1组吗?
 38      if(b[i+1]*b[i+2])
 39      {
 40        b[i]--;b[i+1]--;b[i+2]--; b[0]-=3;
 41        //同样是这个道理
 42        if(fun(b)) return 1;
 43        b[i]++;b[i+1]++;b[i+2]++; b[0]+=3;
 44      }
 45          
 46    }
 47    return 0;
 48  }
 49  
 50  int check()
 51  {
 52    int i,j,k,s,a[15];
 53    for(i=1;i<=3;i++)
 54    {
 55      if(D[i][0]==0continue;
 56      //考虑已经组好的情况
 57      if(D[i][0]%3==0)
 58      {
 59        //i色牌的张数 是3的倍数,能配成 D[i][0]/3组吗?
 60        for(j=0;j<=9;j++) a[j]=D[i][j];
 61        if(fun(a)==0return 0;                
 62      }
 63      else
 64      {
 65        for(j=0;j<=9;j++) a[j]=D[i][j];  
 66        //i色牌的张数是3n+2,拿掉相同2张,如果只有一张牌的话,就铁定不行了
 67        for(j=1;j<=9;j++)
 68        {
 69          if(a[j]>=2)
 70          {
 71            a[j]-=2;a[0]-=2;
 72            //i色牌的张数 是3n+2,拿掉1对 能配成 (D[i][0]-2)/3组吗?
 73            if(fun(a)==1break;
 74            a[j]+=2;a[0]+=2;         
 75          }
 76        }
 77        if(j>9return 0;
 78      }  
 79    }
 80    return 1;
 81  }
 82 
 83  int main()
 84  {
 85    int T,i,j,k,ca=0,a[5],num,ans[30];
 86    char ch[5],f[]={'0','s','b','c'};
 87    scanf("%d",&T);
 88    while (T--)
 89    {
 90      for(i=1;i<=3;i++)
 91        for(j=0;j<=10;j++)
 92          D[i][j]=0;
 93      for(i=1;i<=13;i++)        
 94      {
 95        //这13张牌中,第0列记录总数而之后的九列分别记录每一张牌的值
 96        scanf("%s",ch); j=ch[0]-'0';       
 97        if(ch[1]=='s'){D[1][0]++;D[1][j]++;}
 98        if(ch[1]=='b'){D[2][0]++;D[2][j]++;}     
 99        if(ch[1]=='c'){D[3][0]++;D[3][j]++;}          
100      }
101      printf("Case %d:",++ca);
102      //为什么只有这一种情况呢?主要是考虑到有将的原因,如果胡的牌不是将的话,那么可能存在两张牌都不是3的倍数的情况
103      if((D[1][0]%3) &&(D[2][0]%3) &&(D[3][0]%3))
104      {
105        //如3种牌的张数都不是3的倍数,无解
106        printf(" None\n");
107        continue;           
108      }
109      //有 13张麻将牌 D[][]
110      num=0;
111      for(i=1;i<=3;i++)
112      {
113        if(D[i][0]%3==0)
114        {
115          //第i色麻将有3n张,不增加,判断能否构成n组,这里将其重新导入到一个a[]中   
116          for(j=0;j<=9;j++)
117            a[j]=D[i][j];
118          if(fun(a)==0break//不能,就结束   
119          D[i][0]=0;  // 能,删除3n张牌,以后就不判断了        
120          continue;
121        }
122        for(k=1;k<=9 ;k++)     
123        {
124          //这种牌有4张,不能增加,这是题目的限制,我们只考虑4张以及以下的情况
125          if(D[i][k]>=4continue;            
126          //增加i色牌k,D[][]有14张
127          D[i][k]++;
128          D[i][0]++;
129          if(check()) // 这14张 能配成 1对 4组
130          {
131            //这里的代码可以重构一下,改成如下形式:ans[num++]=k*10+i
132            num++;
133            ans[num]=k*10+i;
134          }
135          //如果不符合条件的话,将这一张牌取消,继续暴力
136          D[i][k]--;
137          D[i][0]--;        
138        }               
139      }
140      //输出所有的情况
141      for(i=1;i<=num;i++)
142        //从这里可以看出f[]的使用主要是将int的值转化成麻将的类型值
143        printf(" %d%c",ans[i]/10,f[ans[i]%10]);
144      if(num==0)
145        printf(" None");
146      printf("\n");
147    }
148    return 0;
149  }
150 
151 

 

posted on 2013-02-28 12:34  吴昊系列  阅读(632)  评论(0编辑  收藏  举报

导航