VV游戏

 找回密码
 立即注册
查看: 137|回复: 0

吴昊讲解游戏源码 —— 德州扑克

[复制链接]

2

主题

4

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2022-11-28 13:00:06 | 显示全部楼层 |阅读模式
德州扑克,又称沙蟹,学名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,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[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,5,sizeof(int),&compare); //将5张牌降序排序
50        p_deal=deal;
51        p_value=value;
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=6; break; //存在一个三条           
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
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|VV游戏

GMT+8, 2025-4-16 20:19 , Processed in 0.171374 second(s), 23 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表