C语言程序设计100例之(77):扑克魔术
例77 扑克魔术
问题描述
下面的扑克魔术是由一位魔术师和他的助手表演的。助手要求观众从52张扑克牌中选择5张扑克(A、2、3、4、5、6、7、8、9、10、J、Q、K of C[lubs]、D[iamonds]、H[earts]和S[pades])。助手将其中1张扑克留给观众,然后将剩余的4张扑克一次一张地递给魔术师。在适当的表演之后,魔术师正确说出观众手中持有的扑克是什么。
魔术师按如下方式确定留在观众手中的扑克牌的:
扑克牌中牌的顺序首先由其点数决定,对于相同点数的牌,则由花色决定。52张扑克牌从小到大排序为:AC、AD、AH、AS、2C、2D、2H、2S、…、KC、KD、KH、KS。
魔术师记住第1张牌的花色和点数。
在剩下的3张牌中,找出最小牌的位置(按上述顺序),将此牌的位置值(1、2或3)加到第1张牌的点数中。
如果后面3张牌中较大的2张不符合从小到大顺序,则再第1张牌的点数上加上3。若点数超过了13,则从A开始重新循环。
此时,观众手中的牌就是魔术师心算出的第1张牌。
例如,观众选取了4D、5H、10C、10D和QH这5张牌,助手将5H留在观众手里,给魔术师的4张牌依次为QH、10D、10C、4D,后面3张牌中最小的1张是4D,位置值3,剩余的10D和10C不符合顺序要求,所以在第1张牌的点数Q上加上3+3,这样观众手里的牌是5H。
编写一个程序,实现助手的功能。
输入
输入的第一行是一个正整数n,它表示测试用例的个数。
以下n行中的每一行是由5张扑克牌组成的序列,由一个空格分隔。每张扑克牌由一个或两个字符所表示点数值和一个字符表示的花色组成。
输出
对于每个测试数据,用一行输出助手排好的5张扑克牌。序列中的第1张扑克是留给观众的。剩下的牌按次序依次给魔术师。有些问题可能有不止一种解决方案,在这种情况下,输出最小的一张。例如,观众选取的4D、5H、10C、10D和QH这5张牌,助手可以将10D留给观众,4D、QH、10C、5H依次交给魔术师,同样可以完成魔术表演。但5H QH 10D 10C 4D<10D 4D QH 10C 5H,因此输出应为5H QH 10D 10C 4D。
输入样例
2
4D 5H 10C 10D QH
7H 5C KS 6C 8D
输出样例
Problem 1: 5H QH 10D 10C 4D
Problem 2: 6C 5C 7H 8D KS
(1)编程思路。
这个魔术的关键在于助手给5张牌排序。其原理是这样的:
5张牌中必有至少2张牌是同样花色的,助手找出两张同样花色的牌,求得它们的点数差值(大的点数减去小的点数),这个差值最小为1(不可能为0,若为0,则两张牌相同),最大为12(点数为“K”和“A”时),12/2=6,按值6分成两种情况:
例如,点数是2和7,由于7-2=5<=6,把点数为7的牌放最前面,点数为2的放在其后,这样7-2=5,使得2+5=7;
再如,点数是2和9,由于9-2=7>6,那么把点数为2的牌放最前面,点数为9的放在其后,这样,2-9=-7,-7+13=6,使得9+6=15,点数15循环后对应点数2。
也就是排列前面2张牌时,要使前面的牌的点数a和后面的牌的点数b差值满足(a-b+13)%13<=6。
在后面的的3张牌中,通过排列顺序给出差值1~6的信息即可。找出最小的那张牌的位置值,这个值为1、2或3,再通过剩下的两张牌给出0(顺序排列)或3(逆序排列)的信息,从而给出1~6中某个值。
例如,设前面2张牌的点数(a-b+13)%13= 5,在后面3张牌中,把最小的牌放在第2个位置,余下的两张牌,大的放在第1个位置,小的放在第3个位置,即逆序排列,这样2+3=5。魔术师一看就心里有数了。
这样,5张牌的顺序就排好了,把第1张留给观众,剩下4张依次交给魔术师。魔术师拿着4张牌,一看开头那张牌,就知道观众手里牌的花色(前面两张牌的花色相同),再根据后面3张牌的排列顺序,心算出差值(1~6之一),再将差值加到开头那张牌的点数上,就知道了观众手里牌的点数,从而成功表演。
将上面的原理用程序直接模拟实现即可。
在模拟时,由于需要对扑克牌进行排序,而采用牌面字符串不方便进行排序,因此可以编写函数int getVal(char card[5])将52张扑克牌中的某张由card字符串指定的牌面转换为对应的整数值(0~51之一)。转换时,牌面AC=0、AD=1、AH=2、AS=3、2C=4、2D=5、2H=6、2S=7、…,依次类推。
扑克牌的一张牌面包含点数和花色两个信息,可以将点数A取0,2~10分别取1~9,J、Q、K分别取10、11、12,四种花色C、D、H、S分别取0、1、2、3,这样每张牌按牌面可映射为一个整数值,映射公式为: 牌面值=点数*4+花色。
这样,对扑克牌的排序就可以转换为对整数的排序。
同时,采用这样映射转换,由一张扑克牌的值也容易得到其点数和花色,
点数=牌面值/4 花色=牌面值%4
若两张牌的牌面值除以4的余数相同,则这两种牌一定同花色。
(2)源程序。
#include <stdio.h> #include <string.h> int getVal(char card[5]) // 由牌面得到值0~51 { int p,s; if (card[1]=='0') p=9; else if (card[0]=='A') p=0; else if (card[0]=='J') p=10; else if (card[0]=='Q') p=11; else if (card[0]=='K') p=12; else p=card[0]-'0'-1; int len=strlen(card); if (card[len-1]=='C') s=0; else if (card[len-1]=='D') s=1; else if (card[len-1]=='H') s=2; else if (card[len-1]=='S') s=3; return p*4+s; } void output(int n) // 输出牌值0~51对应的牌面 { printf(" "); int p=n/4; if (p==9) printf("10"); else if (p==0) printf("A"); else if (p==10) printf("J"); else if (p==11) printf("Q"); else if (p==12) printf("K"); else printf("%c",p+1+'0'); int s=n%4; if (s==0) printf("C"); else if (s==1) printf("D"); else if (s==2) printf("H"); else printf("S"); } void setPos(int d, int a[]) // 根据规则,调整后三张牌顺序 { int t=a[0]; switch(d) { case 2: // 最小放第2张,1、3有序 a[0]=a[1]; a[1]=t; break; case 3: // 最小放第3张,1、2有序 a[0]=a[1]; a[1]=a[2]; a[2]=t;break; case 4: // 最小放第1张,2、3逆序 a[0]=a[2]; a[2]=a[1]; a[1]=a[0]; a[0]=t;break; case 5: // 最小放第2张,1、3逆序 a[0]=a[2]; a[2]=a[1]; a[1]=t;break; case 6: // 最小放第3张,1、2逆序 a[0]=a[2]; a[2]=t; break; } } int main() { int t,iCase=0; scanf("%d",&t); while (t--) { char str[5]; int cnt[4]; // 统计4种花色牌的张数 cnt[0]=cnt[1]=cnt[2]=cnt[3]=0; int i,j; int card[5]; for (i=0;i<5;i++) { scanf("%s",str); card[i]=getVal(str); cnt[card[i]%4]++; } for (i=0;i<4;i++) for (j=0;j<4-i;j++) if (card[j]>card[j+1]) { int temp=card[j]; card[j]=card[j+1]; card[j+1]=temp; } int one=52,two=52; // 序列前2张牌 int d; for (i=0;i<4;i++) { int g[5]; // 同色牌不会多于2组 int num=0; if (cnt[i]>1) { for (j=0;j<5;j++) // 将同色牌选出来 if (card[j]%4==i) g[num++]=j; int id2=1; // 同组中最小的牌一定在前2张中 for (j=num-1;j>1;j--) { d=card[g[0]]+52-card[g[j]]; if (d>0 && d<=24) id2=j; else break; } d=card[g[id2]]-card[g[0]]; if (d>24) { if (one>card[g[0]]) { one=card[g[0]] ; two=card[g[id2]]; } } else { if (one>card[g[id2]]) { one=card[g[id2]] ; two=card[g[0]]; } } } } int ans[5]; ans[0]=one; ans[1]=two; j=2; for (i=0;i<5;i++) if (card[i]!=one && card[i]!=two) ans[j++]=card[i]; d=(ans[0]-ans[1]+52)%52; d=d/4; setPos(d, &ans[2]); // 调整后3张牌顺序 printf("Problem %d:",++iCase); for (i=0;i<5;i++) output(ans[i]); printf("\n"); } return 0; }
习题77
77-1 魔术师
问题描述
魔术师拿出n(1<=n<=13)张黑桃纸牌,叠在一起正面朝下放在桌子上,变这样的一个魔术:(1)把最上面的1张牌移到最下面,然后翻开最上面的一张牌,这张牌是黑桃A,将其拿走,摆放在桌面上。(2)把最上面的2张牌依次移到最下面,然后翻开最上面的一张牌,这张牌是黑桃2,将其拿走,摆放在桌面上。(3)重复这个过程n次,直到所有的牌都被拿走并摆放在桌面上。此时,桌面上放的牌依次是黑桃A、2、3、…、n。
实际上,魔术师为完成这个魔术,一开始就将牌摆好了。例如,5张牌,开始的摆放顺序应该为3、1、4、5、2。
你的室友杨爱耀为了表示他比魔术师牛,拿出了一幅扑克牌中的n张(1<=n<=52)纸牌,来玩这个魔术。他规定1~13表示黑桃A~黑桃K、14~26代表红桃A~红桃K、27~39代表梅花A~梅花K、40~52代表方块A~方块K。但由于纸牌多了,杨爱耀又不是很灵光,事先无法摆好牌,他玩几次都失败了。
请你编写一个程序,帮杨爱耀摆好牌。
输入
输入包含若干组数据。每组数据是一个整数 N (1<=N <=52),表示杨爱耀取出的纸牌数。N = 0 时输入结束。
输出
对于每组输入数据,由于摆好序的纸牌可能较多,无需全部输出。只需输出排在第1张和最后1张的纸牌的花色和点数。每组数据的输出后换行。输出时,用字母H、S、C和D分别表示黑桃(Spade)、红桃(Heart)、梅花(Club)和方块(Diamond);用A、2~10、J、Q、K表示点数。
输入样例
5
20
32
0
输出样例
S3 S2
H4 S5
H5 H7
(1)编程思路。
为了排好纸牌,采用一个数组来进行模拟。
定义数组int a[53];,初始值全部置为0,a[i]=0表示第i张纸牌还在牌堆中,可以进行计数,a[i]=k(1<=k<=n)表示第i张牌排好了,是纸牌k,同时表示第i张牌已展示到桌面上了,不在牌堆中,后面不能进行计数,得跳过。
用循环变量i(1<=i<=n)表示当前需要摆放的纸牌i,j用于计数,纸牌i需要计数i次,变量p表示牌堆中位置的流动,p的初值为0,p的变化方式为:
p=p%n; p++; ,即1-->2-->3…-->n-1 ->n -> 1--> 2…。
当流动到了位置p后,该位置的牌已摆放到桌面上(a[p]!=0),显然不能计数,跳过该位置;若该位置的牌还在牌堆中(a[p]==0),则计数(j++)。
当计数值j到了i,则将纸牌i放到位置p处,同时i加1,摆放下一张牌。当第n张牌也摆放好时循环结束。
(2)源程序。
#include <stdio.h> #include <string.h> int main() { int n,i,j,p,a[53]; char suit[6]="SHCDD"; char value[14]="A234567890JQK"; while (scanf("%d",&n) && n!=0) { memset(a,0,sizeof(a)); p=0; i=1; while (i<=n) { j=1; while (j<=i+1) { p=p%n; p++; if (a[p]==0) j++; } a[p]=i++; } printf("%c",suit[(a[1]-1)/13]); if ((a[1]-1)%13==9) printf("10 "); else printf("%c ",value[(a[1]-1)%13]); printf("%c",suit[(a[n]-1)/13]); if ((a[n]-1)%13==9) printf("10\n"); else printf("%c\n",value[(a[n]-1)%13]); } return 0; }
77-2 桥牌发牌
问题描述
桥牌是一种典型的纸牌游戏。打桥牌需要向4名玩家发一副标准的52张扑克牌,这样每位玩家都能拿到13张牌。玩家需要对拿到的牌进行排序,首先是按花色排序,然后是按花色中的点数排序。一般来说,花色没有固定的等级,但交替颜色是有用的,因此我们将假定以下顺序:C(梅花)<D(方块)<S(黑桃)<H(心形)。在一套花色中,点数A最大,顺序为2<3<4<5<6<7<8<9<T<J<Q<K<A。
玩家通常按落座的方位被称为北、南、东、西,一名玩家被指定为庄家,他向每位玩家发一张牌,从他左边的玩家开始,顺时针进行,直到他向自己发最后一张牌。
编写一个程序,读入一副扑克牌,对它们进行发牌处理,然后以指定的格式显示4名玩家拿到的排序过的手牌。
输入
输入包括多组测试用例。每个测试用例以一行开头,其中包含一个代表庄家的字母(“N”、“E”、“S”、“W”),然后是两行字符串代表一副待发的牌。输入中给出的第1张牌是第1名玩家要发的牌。文件将以一行结束,该行由一个字符“#”组成。
输出
每个测试用例的输出由24行组成,每名玩家手里的牌由六行组成,按花色和点数顺序显示分类的手牌。使用输出样例所示的格式。南方玩家手里的牌总是第一个显示。
输入样例
N
CQDTC4D8S7HTDAH7D2S3D6C6S6D9S4SAD7H2CKH5D3CTS8C9H3C3
DQS9SQDJH8HAS2SKD4H4S5C7SJC8DKC5C2CAHQCJSTH6HKH9D5HJ
#
输出样例
(1)编程思路。
在模拟时,由于需要对扑克牌进行排序,而采用牌面字符串不方便进行排序,因此可以编写函数int getValue(char a,char b)将52张扑克牌中的某张由字符a给定点数,字符b给定花色的牌面转换为对应的整数值(0~51之一)。转换时,牌面2C=0、3C=1、4C=2、…、KC=11、AC=12、2D=13、3D=14、…、KD=24、AD=25,……KH=50、AH=51。这个转换顺序是先按花色排序,同花色按点数排序。
扑克牌的一张牌面包含点数和花色两个信息,可以将点数2~9分别取2~9,T、J、Q、K分别取10、11、12、13,A取14, 4种花色C、D、S、H分别取0、1、2、3,这样每张牌按牌面可映射为一个整数值,映射公式为:牌面值=花色*13+点数-2。
这样,对扑克牌的排序就可以转换为对整数的排序。
同时,采用这样映射转换,由一张扑克牌的值也容易得到其点数和花色,
点数=牌面值%13+2 花色=牌面值/13
(2)源程序。
#include <stdio.h> #include <string.h> void sort(int *p,int n) { int i,j,tmp; for (i=0;i<n-1;i++) for (j=0;j<n-1-i;j++) if (*(p+j)>*(p+j+1)) { tmp=*(p+j); *(p+j)=*(p+j+1); *(p+j+1)=tmp; } } int getValue(char a,char b) { int p,s; if (a>='2' && a<='9') p=a-'0'; else if (a=='T') p=10; else if (a=='J') p=11; else if (a=='Q') p=12; else if (a=='K') p=13; else if (a=='A') p=14; if (b=='C') s=0; else if (b=='D') s=1; else if (b=='S') s=2; else if (b=='H') s=3; return 13*s+p-2; } char getPoint(int n) // 返回值0~51对应的点数 { int p=n%13; if (p==8) return('T'); else if (p==9) return('J'); else if (p==10) return('Q'); else if (p==11) return('K'); else if (p==12) return('A'); else return(p+2+'0'); } char getSuit(int n) // 返回牌值0~51对应的花色 { int s=n/13; if (s==0) return('C'); else if (s==1) return('D'); else if (s==2) return('S'); else return('H'); } int main() { char str[60],card[120]; while (scanf("%s",str) && str[0]!='#') { int begin; if (str[0]=='N') begin=3; else if (str[0]=='E') begin=0; else if (str[0]=='S') begin=1; else begin=2; scanf("%s",card); scanf("%s",str); strcat(card,str); int a[4][13]; int i,j,k=0; for (i=0;i<13;i++) for (j=0;j<4;j++) { a[(j+begin)%4][i]=getValue(card[k+1],card[k]); k+=2; } for (i=0;i<4;i++) sort(&a[i][0],13); // 排序 for(i=0;i<4;i++) { if (i==0) printf("South "); else if (i==1) printf("West "); else if (i==2) printf("North "); else printf("East "); printf("player:\n"); for (j=0;j<13;j++) printf("+---"); printf("+\n"); for(j=0;j<13;j++) printf("|%c %c",getPoint(a[i][j]),getPoint(a[i][j])); printf("|\n"); for(j=0;j<13;j++) printf("| %c ",getSuit(a[i][j])); printf("|\n"); for(j=0;j<13;j++) printf("|%c %c",getPoint(a[i][j]),getPoint(a[i][j])); printf("|\n"); for (j=0;j<13;j++) printf("+---"); printf("+\n"); } printf("\n"); } return 0; }
77-3 手牌评分
问题描述
在一个简单的扑克游戏中,玩家从52张不同的扑克牌中得到5张不同的扑克牌。一副52张扑克牌由4种花色各13张牌组成,每种花色的点数编码顺序为A、2、3、4、5、6、7、8、9、X、J、Q和K。4种花色分别为梅花(C)、心形(H)、黑桃(S)和方块(D)。
请编写一个程序,使用以下评分规则来确定一手牌的分数:
同花顺:1000分,同一花色的连续五张牌,如76543且均是红心。注意,AKQJX被视为连续序列。
四张相同的牌:750分,四张点数相同的牌,外加一张其他点数的牌,比如44442。
三带对:500分,三张点数相同的牌,另外两张牌点数也相同,如777JJ。
同花:350分,同一花色的五张牌,比如AJ942五张红心牌。
顺子:250分,五张点数连续的牌,如76543。注意,AKQJX也是顺子。
三张:200分,三张相同点数的牌和两个不同点数的其他牌,比如KKK84。
两对:100分,一种点数两张牌,另一种点数也两张牌,外加一张其他点数的牌,如KK449。
一对:50,只有两张牌点数相同,其他三张牌点数都不同,如AAK53。
以上任何一项都不适用:0分。任何不符合上述更好的手牌条件的手牌,例如,不同花色的KJ542。
注意,如果一手牌满足上述两个或多个规则,那么只应用得分最多的规则。例如,“三带对”、“三张”和“一对”都满足时,只算“三带对”的分数(即500分)。
输入
第一行包含手牌的数量w,w<=100。然后逐一列出w手。每一手牌都来自一副完整的52张牌。每手牌列在一行,共5张牌。每张牌由两个字符组成。第一个字符是它的花色,第二个字符是它的点数。两张牌之间有一个空格。
输出
对于每手牌,在一行中输出其得分。
输入样例
3
C3 D4 D5 S3 CX
CA C5 D4 D3 S2
HA HJ HX HQ HK
输出样例
50
250
1000
(1)编程思路。
定义结构体数组
struct PokerCard
{
char suit;
int point;
} card[5];
来保存5张手牌,其中,suit保存一张牌的花色,point保存一张牌的点数。输入时,一张牌由两个字符组成,第1个字符之后作为花色suit保存,第2个字符转换为一个整数值进行保存,方便后面对顺子进行判断,转换时,A取1,2~9分别取2~9,X、J、Q、K分别取10、11、12、13。
5张手牌输入并保存到card数组后,将数组按点数从小到大进行排列,之后按评分规则进行评分即可。
(2)源程序。
#include<stdio.h> struct PokerCard { char suit; int point; }; int cmp(struct PokerCard a,struct PokerCard b) { if (a.point==b.point) return a.suit<b.suit; else return a.point>b.point; } int calculatePoints(struct PokerCard card[5]) { int i,flag1=1,flag2=1; for (i=0;i<4;i++) if (card[i].suit!=card[i+1].suit) { flag1=0; break; } for (i=0;i<4;i++) if (card[i].point!=card[i+1].point-1) { flag2=0; break; } if (card[0].point==1 && card[1].point==10 && card[2].point==11 && card[3].point==12 && card[4].point==13) flag2=1; if (flag1 && flag2) return 1000; if (card[0].point!=card[1].point && card[1].point==card[2].point && card[2].point==card[3].point && card[3].point==card[4].point) return 750; if (card[0].point==card[1].point && card[1].point==card[2].point && card[2].point==card[3].point && card[3].point!=card[4].point) return 750; if (card[0].point==card[1].point && card[1].point!=card[2].point && card[2].point==card[3].point && card[3].point==card[4].point) return 500; if (card[0].point==card[1].point && card[1].point==card[2].point && card[2].point!=card[3].point && card[3].point==card[4].point) return 500; if (flag1) return 350; if (flag2) return 250; if (card[0].point!=card[1].point && card[1].point!=card[2].point && card[2].point==card[3].point && card[3].point==card[4].point) return 200; if (card[0].point==card[1].point && card[1].point==card[2].point && card[2].point!=card[3].point && card[3].point!=card[4].point) return 200; if (card[0].point!=card[1].point && card[1].point==card[2].point && card[2].point!=card[3].point && card[3].point==card[4].point) return 100; if (card[0].point==card[1].point && card[1].point!=card[2].point && card[2].point!=card[3].point && card[3].point==card[4].point) return 100; if (card[0].point==card[1].point && card[1].point!=card[2].point && card[2].point==card[3].point && card[3].point!=card[4].point) return 100; if (card[0].point==card[1].point && card[1].point!=card[2].point && card[2].point!=card[3].point && card[3].point!=card[4].point) return 50; if (card[0].point!=card[1].point && card[1].point==card[2].point && card[2].point!=card[3].point && card[3].point!=card[4].point) return 50; if (card[0].point!=card[1].point && card[1].point!=card[2].point && card[2].point==card[3].point && card[3].point!=card[4].point) return 50; if (card[0].point!=card[1].point && card[1].point!=card[2].point && card[2].point!=card[3].point && card[3].point==card[4].point) return 50; return 0; } int main() { int t,i,j; char str[3]; struct PokerCard card[5],temp; scanf("%d",&t); while (t--) { for (i=0;i<5;i++) { scanf("%s",str); card[i].suit=str[0]; if (str[1]>='0' && str[1]<='9') card[i].point=str[1]-'0'; else if (str[1]=='A') card[i].point=1; else if (str[1]=='X') card[i].point=10; else if (str[1]=='J') card[i].point=11; else if (str[1]=='Q') card[i].point=12; else if (str[1]=='K') card[i].point=13; } for (i=0;i<4;i++) for (j=0;j<4-i;j++) if (cmp(card[j],card[j+1])) { temp=card[j]; card[j]=card[j+1]; card[j+1]=temp; } printf("%d\n",calculatePoints(card)); } return 0; }