吴昊品游戏核心算法 Round 15 —— 吴昊教你玩德州扑克(模拟+标志位存储)

 梭哈

  梭哈,又称沙蟹,学名Five Card Stud,五张种马,是扑克游戏的一种。以五张牌的排列、组合决定胜负。游戏开始时,每名玩家会获发一张底牌(此牌只能在最后才翻开);当派发第二张牌后,便由牌面较佳者决定下注额,其他人有权选择“跟”、“加注”、“放弃”或“清底”。当五张牌派发完毕后,各玩家翻开所有底牌来比较,梭哈在全世界纸牌游戏地位非常高,深受人们的喜爱。游戏在国内和港台地区广泛流传,其特点为:上手容易、对抗性强,既有技巧也有一定的运气成分,因此流传非常广泛,五张牌(梭哈)高手必须具备良好的记忆力、综合的判断力、冷静的分析能力再加上些许运气。该游戏紧张刺激,集益智和乐趣于一身。

 

  各种牌型

     ● High Card:杂牌(不属于下面任何一种)。根据牌从大到小的顺序依次比较。

     ● Pair:有一对,加3张杂牌组成。先比较对的大小,再从大到小的顺序比较杂牌。

     ● Two Pairs:有两对,加1帐杂牌。先从大到小比较对的大小,再比较杂牌。

     ● Three of a Kind:有3张值相同的牌。比较这个值即可。

     ● Straingt:一条龙。即5张牌连续。比较最大的一张牌即可。

     ● Flush:清一色。即5张牌花色相同。和杂牌一样比较。

     ● Full House:3张值相同的牌,加上一对。比较三张相同的值即可。

      ● Four of a kind:有4张牌相同,即相当于一副“炸弹”。

     ● Straight flush:同花顺。即5张牌花色相同,并且连续。例如同花色的34567。

  各种花色

  花(club),方块(diamond),红桃(heart)和黑桃(spade)—— 在后面的控制台输入中会以C,D,H,S表示

 各种花色的数值

  每种花色会有一个对应的数值,分别为2,3,4,5,6,7,8,9,10,jack,queen,ace(J,Q,K)牌型大小的比较

  牌型大小的比较

  牌型比较:Straight flush > Four of a kind > Full House > Flush > Straingt > Three of a Kind > Two Pairs > Pair > High Card。

 

  数字比较:A>K>Q>J>10>9>8>7>6>5>4>3>2

 

  花式比较:黑桃>红桃>草花>方块

 

  关于A2345,这手牌可以算顺子,但大小在各种扑克中不一样,梭哈里是第2大顺(例如赌神里就是这样),德州中却是最小的顺子。

  这里阐述了德州扑克和梭哈的一些区别(具体的区别我在后文中也会详细解释),这里说明一下,为了我们这里的控制台程序有一定的抽象性,就是说我们屏蔽了花色比较的细节,只考虑数字的比较和牌型的比较,这也是为了之后的计分方便。

 

  传统规则

  各家一张底牌,底牌要到决胜负时才可翻开。从发第二张牌开始,每发一张牌,以牌面大者为先,进行下注。有人下注,想继续玩下去的人,选择跟,跟注后会下注到和上家相同的筹码,或可选择加注,各家如果觉得自己的牌况不妙,不想继续,可以选择放弃,认赔等待牌局结束,先前跟过的筹码,亦无法取回。

  最后一轮下注是比赛的关键,在这一轮中,玩家可以进行梭哈,所谓梭哈是押上所有未放弃的玩家所能够跟的最大筹码。等到下注的人都对下注进行表态后,便掀开底牌一决胜负。这时,牌面最大的人可赢得桌面所有的筹码。

 现代规则

  在现代的拓展中可以有2到10个玩家同时玩这个游戏。发牌前,必须先下基本的注额。每位玩家发两张牌。一张暗牌,一张明牌。第一圈,拥有最大的明牌的玩家首先发言,他可以下注、不下注(让牌)或盖牌(放弃)也可以全压(梭哈)。其他玩家可以跟注(有玩家全压时必须全压)、加注或盖牌(放弃)。然后发明牌。第二圈和第三圈如此类推。第四圈玩家要以他们手上的牌组合成最大的牌型,拥有最大的明牌的玩家首先发言,他可以下注、梭哈(又称全压)(摊牌)或盖牌(放弃)。其他玩家可以跟注或盖牌(放弃)。最后,每位玩家要比牌型的大小以确定赢家。牌最大的玩家赢得所有桌上的赌金。

 各种术语

 

(1)全梭:以最小玩家的金币数目为每个玩家梭哈时下注的最大数目,但是最大下注数目由房间确定。

(2)封顶:以最小玩家的金币数目的50%为每个玩家梭哈时下注的最大数目。但是最大下注数目依然为房间确定。最高封顶为 100万金币。

 各种规则

(1)先发给各家一张底牌,底牌除本人外,要到决胜负时才可翻开。

(2)从发第二张牌开始,每发一张牌,以牌面发展最佳者为优先,进行下注。

(3)有人下注,想继续玩下去的人,要按“跟注”键,跟注后会下注到和上家相同的筹码,或可选择加注。根据房间的设定,可以在特定的时间选择“梭”,梭哈是加入桌面允许的最大下注。

(4)各家如果觉得自己的牌况不妙,不想继续,可以按“放弃”键放弃下注,先前跟过的筹码,亦无法取回。

(5)牌面最大的人可赢得桌面所有的筹码。当多家放弃,已经下的注不能收回,并且赢家的底牌不掀开。

(6)纸牌种类:港式五张牌游戏用的是扑克牌,取各门花色的牌中的“8、9、10、J、Q、K、A”,共28张牌。

  关于梭哈(或者以其改编的德州扑克)的具体技巧,我会在Round 15后面的具体AI中再阐述,其中还包括一些心理战,这些都是目前的AI所或缺的。

  我们这里先实现一个牌型比较程序,对于给定的两手牌,我们的程序可以将其进行相应的处理。这里,输入的每一行有十张牌(前面五张是黑方的,后面五张是白方的),借鉴我的Round 2之“吴昊教你玩斗地主”中的方式,我们利用“数字+字母”的方式来表征一张牌的两个标准特征,也就是点数和花色,而五张牌又可以构成一手牌。

  在输出中,如果黑方获胜,我们输出“Black wins”,如果白方获胜,我们输出“White wins”,如果是平局的话,我们输出“Tie”。

 

  我们Source(ZOJ 1111)中的独特的技巧

  关于Source的选择问题,我有考虑过一些常见的模拟算法,比如“yllfever的专栏”和“hoodlum1980(发发)的技术博客”,但是,其代码都过于冗长,所以这里给出了一种独特的方法,将字符运算转为了数字运算(利用数字来进行字符存储),所以,亮点在于数据结构的编排,算法的复杂程度也变高了一些。

  Source的数据结构剖析

  首先,如何利用一个32位的整型int变量来存储一副牌?

  我们用两个数组分别存储一副牌的点数和花色信息:

  char *deck=“23456789TJQKA”(T代表10,也就是ten)

  char *suit=“CDHS”( 梅花(club),方块(diamond),红桃(heart)和黑桃(spade))

  存储一张牌的时候,考虑到一个int类型的数值是32位的,那么,第0位和第1位可以存储花色信息(恰好有四种花色信息——2^2),后面四位来存储数值(2^4),所以,感觉在空间上面还是有很大的浪费的,毕竟前面26位都没有使用了。所以说,在读取扑克牌的数值的时候,要右移两位。

  黑白双方的牌利用数组来表示:int deal[2][6];

  利用count计数器数组来记录重复的值 int count[13];

  利用rank来标记梭哈游戏的每一种规则 int rank;

  一手牌中不同值的个数为 int number[9]={5,4,3,1,1,5,2,1,1},对应每一种规则的点数不同的牌的数目;

  利用二维数组int value[2][7]来保存牌的大小

  牌型剖析

(1)       在比较的时候,首先要比较最大的那个,所以,首先对扑克牌进行降序排序:qsort(deal[i],5,sizeof(int),&compare),其中i可以选择0或者是1来对应白方和黑方

(2)       统计5张牌地数值重复的个数,将统计的结果放在数组count中

(3)       分别对13张扑克进行枚举,级别放在变量rank中(这也就是牌型剖析的内容

(A)如果重复的次数为2,可以判定为1个对子,2个对子或者葫芦

(B)如果重复的次数为3,可以判定为1个条子或者葫芦

(C)如果重复的次数是4,可以判定为铁支

(D)如果重复的次数是5,相邻牌的值相差为1,可以判定为顺子(已经排序过了)

(E)如果重复的次数是5,五张牌的花色都一样,可以判定为同花

(F)同时满足条件(D)和条件(E),则可以判定为同花顺

 (4)   进行级别的判断的时候,可以保存不同牌的值

 (5)   在value中存放的是number的值,rank的值以及number个不同牌的值(位数由低到高)两家通过级别rank和牌的大小number进行比较,决定胜负。

 

  示范输入: 2H 3D 5S 9C KD 2C 3H 4S 8C AH

 示范输出: White wins.

  Source中用到了很多位标志存储的技巧(可以借鉴对溢出的一些处理)

 

  1   // 输入输出函数的控制 
  2   #include<stdio.h> 
  3   // 这两个头文件主要为了开启qsort和memset函数 
  4   #include<memory.h> 
  5  #include<stdlib.h> 
  6   
  7   // 一副牌的值和花色 
  8    char *deck= " 23456789TJQKA ",*suit= " CDHS "
  9   
 10   // 一手牌中不同值的个数(按照rank进行排列的) 
 11    int number[ 9]={ 5, 4, 3, 1, 1, 5, 2, 1, 1}; 
 12   
 13   // 这里定义了一个排序因子,后来会在qsort中调用 
 14    int compare( const  void *a, const  void *b) 
 15  { 
 16     // 在返回时,认定a,b为int类型的变量(最开始a,b未定型)按照int的宽度读指针所在的数值 
 17      return ((*(( int *)b))-(*(( int *)a)));    
 18  } 
 19   
 20   int main() 
 21  { 
 22     // 为主函数开启各种数据结构 
 23      int deal[ 2][ 6];  // 发两家的牌 
 24      int value[ 2][ 7];  // 两家牌的大小(包括number和rank) 
 25      int count[ 13];  // 扑克牌中每种牌值的重复次数 
 26      int *p_deal;  // 指向数组deal一行的指针 
 27      int *p_value;  // 指向数组value一行的指针 
 28      int i,j;  // 辅助变量 
 29      char card[ 10];  // 一张牌 
 30      while( 1// 持续不断地读到文件尾 
 31     { 
 32       // 存储每行的10张牌的点数和花色 
 33        for(i= 0;i< 2;i++) 
 34      { 
 35         for(j= 0;j< 5;j++) 
 36        { 
 37           // 说明读到文件尾 
 38            if(scanf( " %s ",card)==EOF)  return  0
 39           // 保存每张牌,利用strchr函数比对,前两位方花色信息,后四位放点数信息 
 40           deal[i][j]=((strchr(deck,card[ 0])-deck)<< 2)+(strchr(suit,card[ 1])-suit);                
 41        }                
 42      }         
 43       int rank;  // 等级信息 
 44        int k;  // 记录某种规则出现的次数 
 45       memset(value, 0, sizeof(value));  // 将value数组清0,对于有些编译器而言,这个过程可以忽略 
 46        // 分别处理每一家的牌 
 47        for(i= 0;i< 2;i++) 
 48      { 
 49        qsort(deal[i], 5, sizeof( int),&compare);  // 将5张牌降序排序 
 50         p_deal=deal[i]; 
 51        p_value=value[i]; 
 52        memset(count, 0, sizeof(count));  // 将计数器清0 
 53          // 利用计数器来记录每张牌重复的个数(这里的原理和麻将(吴昊系列的新年特别篇)的原理是一样的) 
 54          for(j= 0;j< 5;j++) 
 55          count[p_deal[j]>> 2]++; 
 56        rank= 0// rank初始置0 
 57          // 以下分别处理每一张牌,这里的点数一共13种,从最有威力的开始,相当于牌型分析的过程 
 58          for(j= 12;j>= 0;j--) 
 59        { 
 60           // 如果有重复的牌的话 
 61            if(count[j]> 1)      
 62          { 
 63             // 由于同一点数的牌只可能有四张,故不可能出现count[j]为5这种情况 
 64              switch(count[j]) 
 65            { 
 66               // 有一个对子 
 67                case  2:     
 68                    switch(rank) 
 69                   { 
 70                      case  0: rank= 1; p_value[ 2]=j;  break// 第一个对子 
 71                       case  1: rank= 2; p_value[ 3]=j;  break// 第二个对子 
 72                       case  3: rank= 6break// 存在一个三条           
 73                    }           
 74                    break
 75               case  3
 76                    // 这样写防止错误 
 77                     if( 0==rank) rank= 3; // 只有一个三条 
 78                     else 
 79                   { 
 80                      // 存在一个葫芦 
 81                      rank= 6;     
 82                      // 记录这个三条的值 
 83                      p_value[ 2]=j; 
 84                   } 
 85                    break
 86               case  4
 87                    // 存在一个铁支 
 88                    rank= 7
 89                    // 记录该铁支的值 
 90                    p_value[ 2]=j; 
 91                    break
 92            }                    
 93          }             
 94           // 剩下的可能性只有count[j]=1,也就是单张牌 
 95            // 现在来考虑这5张牌是否有可能为顺子,同花或者更进一步地,是同花顺这种情况 
 96            if(rank< 6
 97          { 
 98             // k有助于我们判断出是顺子,同花还是同花顺,这里置k的初始值为3 
 99              // 首先判断花色,利用k的第0位来判断 
100              for(j= 1;j< 5;j++) 
101               if((p_deal[j]& 3)!=(p_deal[ 0]& 3)) 
102              { 
103                k&= 2; // 因为2的二进制表示为"10",这样与了之后可以置第0位为0 
104                  break;                                
105              }          
106             // 然后我们来判断牌的点数是不是顺的,利用k的第1位为判断 
107              for(j= 1;j< 5;j++) 
108               if((p_deal[j]>> 2)!=(p_deal[j- 1]>> 2)- 1
109              { 
110                k&= 1; // 因为1的二进制表示为"01",这样与了之后可以置第1位为0 
111                  break;                                      
112              } 
113             // 现在我们可以加以判断了,因为一共四种情况,利用k的第0位和第1位就可以快速进行分类 
114              if(k== 1) rank= 5// 同花 
115              if(k== 2) rank= 4// 顺子 
116              if(k== 3) rank= 8// 同花顺 
117           } 
118           // 记录顺子的最大值,这里首先要判定确实是一个顺子的最小值(这里由于是顺子,最大值和最小值一样) 
119            if((rank== 4)||(rank== 8)) 
120          { 
121            p_value[ 2]=p_value[ 4]>> 2;                         
122          } 
123           // 保存散牌或者同花(散牌)的所有值 
124            if((rank== 0)||(rank== 5)) 
125          { 
126             for(j= 0;j< 5;j++) 
127            { 
128              p_value[j+ 2]=(p_deal[j]>> 2);                
129            }                        
130          } 
131           // 当只有一个对子的时候,为了防止对子相等的情况,还是需要保留除了对子以外的其余牌的值 
132            if(rank== 1
133          { 
134             // 这里的k有另外的含义,其标识数组下标的位置 
135             k= 3
136             for(j= 0;j< 5;j++) 
137            { 
138               if((p_deal[j]>> 2)!=p_value[ 2]) 
139                p_value[k++]=(p_deal[j]>> 2);                
140            }           
141          } 
142           // 当有两个对子的时候(也就是有一张牌是单牌),这里同上,保存除了对子以外的那张牌的值 
143            if(rank== 2
144          { 
145             // 还是先找到对应的数组下标 
146             k= 4
147             for(j= 0;j< 5;j++) 
148            { 
149               if((p_deal[j]>> 2)!=p_value[ 2]) 
150              { 
151                 if((p_value[j]>> 2)!=p_value[ 3]) 
152                { 
153                  p_value[k++]=(p_value[j]>> 2);                               
154                }                              
155              }                
156            }           
157          } 
158           // value的第一位放置等级值,而第0位放置这个等级所对应的牌的张数 
159           p_value[ 1]=rank; 
160          p_value[ 0]=number[rank]; 
161        } 
162      }                
163       int match=value[ 0][ 0];  // 读第一家牌中的不同值的个数 
164        int *hand1=value[ 0];  // 读第一家牌的具体情况 
165        int *hand2=value[ 1];  // 读第二家牌的具体情况 
166        // 两家牌比大小,每比较一次,将不同值牌的个数--,换一张不同值的牌,首先比较的还是等级rank 
167        while(*(++hand1)==*(++hand2)) 
168      { 
169         // 一直到所有的牌比完位置 
170          if((--match)< 0
171           break;                             
172      } 
173       // 最后的判断了! 
174        if(match< 0) printf( " Tie.\n "); 
175       else  if(*hand1>*hand2) printf( " Black wins.\n "); 
176       else printf( " White wins.\n "); 
177    } 
178     return  0;    
179  } 
180   

 

   梭哈的变种——德州扑克

  七张牌梭哈是五张牌梭哈的变体,大约诞生于20世纪初,因为上述其四明一暗的方式暴露过多信息,同时由于不易成牌,容易作弊等缘故,七张牌梭哈更为流行,并且在美国德州扑克诞生前是最为流行的玩法,而至今该玩法还有众多玩家,在WSOP等世界级大赛中也有其项目。中国一直到1999年澳门的和记娱乐城才引进了这一欧洲流行游戏。

  七张牌梭哈通常以有限注的形式游戏(当然无限注也可了),它没有公共牌并可供2-9人游戏。开始时每人发两张面朝下和一张面朝上的牌;两张底牌和一张门牌。在游戏的过程中,每个游戏的玩家再发3张面朝上的牌和最后1张面朝下的牌,他们必须在摊牌时用其中的5张牌组成一手牌。拿到最大一手牌的玩家赢得本轮和底池。

 

  德州扑克与梭哈的区别

 梭哈是先发一张底牌一张明牌,根据牌面大小可以选择加注或放弃,然后一次发剩余的3张牌,每发一张都有加注和放弃的权利。
  德州扑克是先发两张底牌,根据底牌下注过牌或放弃,然后在发5张明牌,明牌是所有人的牌,第一次发3张明牌,然后每次一张,每次都有加注或放弃的权利。最后由手里的两张底牌加上5张明牌组合出你手里最大的牌的组合和其他对手比较。
  比牌梭哈和德州扑克一样,都是同花大顺最大,其次四条,然后葫芦(3+2),同花,顺子,三条,两对,一对,散牌最小。(如果是真人赌博的话,要询问好同花和顺子的大小,因为有些地方是顺子赢同花,其他不变)
  梭哈和德州扑克下注也有所区别;梭哈一般都要求有最低限度的筹码,低于最低限度的筹码不能进行游戏。德州扑克在这一点上有所改善,只要你还有超过底注的多余筹码就可以继续这个游戏,当然你赢得也是你底注+上你多余的筹码。
  压注叫法:如果你想一次性赢光对手的筹码或者将自己的筹码全部作为赌注---梭哈叫梭哈,德州扑克叫all in

 

转载于:https://www.cnblogs.com/tuanzang/archive/2013/03/27/2984722.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值