梭哈,又稱沙蟹,學名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中用到了很多位標志存儲的技巧(可以借鑒對溢出的一些處理)
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= 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
梭哈的變種——德州撲克
七張牌梭哈是五張牌梭哈的變體,大約誕生於20世紀初,因為上述其四明一暗的方式暴露過多信息,同時由於不易成牌,容易作弊等緣故,七張牌梭哈更為流行,並且在美國德州撲克誕生前是最為流行的玩法,而至今該玩法還有眾多玩家,在WSOP等世界級大賽中也有其項目。中國一直到1999年澳門的和記娛樂城才引進了這一歐洲流行游戲。
七張牌梭哈通常以有限注的形式游戲(當然無限注也可了),它沒有公共牌並可供2-9人游戲。開始時每人發兩張面朝下和一張面朝上的牌;兩張底牌和一張門牌。在游戲的過程中,每個游戲的玩家再發3張面朝上的牌和最后1張面朝下的牌,他們必須在攤牌時用其中的5張牌組成一手牌。拿到最大一手牌的玩家贏得本輪和底池。
德州撲克與梭哈的區別
梭哈是先發一張底牌一張明牌,根據牌面大小可以選擇加注或放棄,然后一次發剩余的3張牌,每發一張都有加注和放棄的權利。
德州撲克是先發兩張底牌,根據底牌下注過牌或放棄,然后在發5張明牌,明牌是所有人的牌,第一次發3張明牌,然后每次一張,每次都有加注或放棄的權利。最后由手里的兩張底牌加上5張明牌組合出你手里最大的牌的組合和其他對手比較。
比牌梭哈和德州撲克一樣,都是同花大順最大,其次四條,然后葫蘆(3+2),同花,順子,三條,兩對,一對,散牌最小。(如果是真人賭博的話,要詢問好同花和順子的大小,因為有些地方是順子贏同花,其他不變)
梭哈和德州撲克下注也有所區別;梭哈一般都要求有最低限度的籌碼,低於最低限度的籌碼不能進行游戲。德州撲克在這一點上有所改善,只要你還有超過底注的多余籌碼就可以繼續這個游戲,當然你贏得也是你底注+上你多余的籌碼。
壓注叫法:如果你想一次性贏光對手的籌碼或者將自己的籌碼全部作為賭注---梭哈叫梭哈,德州撲克叫all in