欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

Poker time 2 (enhanced version)

发布时间:2024/1/8 编程问答 53 豆豆
生活随笔 收集整理的这篇文章主要介绍了 Poker time 2 (enhanced version) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

声明:

        本文很长,并且最终不会有完整代码,仅提供算法;

        本文知识点中等偏难,但是看懂和学会之后有一定好处;

        本文绝对不是最优解,并且很多步骤待封装成函数,这里逐一分析;

        本文纯属个人刷题方法和总结,如有错误,欢迎指正;

        如果你做的时候有些吃力,真正想学点什么,请耐心看完,看懂。

题目如下:

        背景:两个人每人发3张牌(各从一副牌中),每张牌包括花色(红桃(Heart)>黑桃(Spade)>方块(Diamond)>梅花(Club))和大小(从小到大依次是:2-10、J、Q、K、A),胜负规则如下:同花顺(3张同花色的连牌,先比大小,再比花色后同)>炸弹(3张相同大小的牌)>连牌(3张不同花色的连牌)>对子(两张相同大小的牌)>单牌。例如,红桃QKA>黑桃QKA>梅花567>方块234>AAA(红桃、方块、梅花)>AAA(黑桃、方块、梅花)>JQK(红桃、红桃、方块)>JQK(黑桃、红桃、方块)>AA2(梅花黑桃梅花)>QQJ(红桃梅花方块)>JQA(红桃红桃红桃)。注:A23不算连牌。

输入:A的3张牌(未排序)和B的3张牌(未排序)。

输出:A的3张牌的排序后的输出和B的3张牌的排序后的输出,以及A和B谁获胜。

分析:

        这里继承了上一题,也就是Poker time的部分内容,这里不多赘述那题的做法,如果有需要的话可以私聊我,我视情况出第一题的算法。

        首先是排序,这里的最终排序取决于取决于牌的大小而不是花色(*);其次是胜利条件的判断,有明显的分级,对于每一级内部也有严格的先大小后花色的判断顺序(**);再者,顺子的QKA是否合理,这里没有给出解释,也是留了一手,不然题目的判断会更恶心(***);最后是让人最头疼的10,这个数字在第一题就作为最后一个隐藏示例存雷。

        不过有10必有‘1’,只需要找到这个1就行。

        这次编写,我不知道隐藏示例都是什么,主要是我一次就过了,除了一个本身就是错误的示例(凡尔赛)。

算法正文:

零、参数声明

        这里给出本文主要的参数

char a[20]; // 这里给20,防一下 10 的炸弹char b[20];gets(a);gets(b);int i=0;int j=0;int flag=1; // 循环进行指标 int flag1=1; // 是否双赢指标 int flag2=0; // 判断 A 赢 int flag3=0; // 判断 B 赢

一、建立在排序基础上的整体数组构造以及参数读入

        由于最终需要对牌进行先大小后花色的排序,我这里使用的是二位数组

        为什么定义成13*4呢?        例如:HA 就在数组 [0][0]的位置

        我的思路是:13行存储牌,4列存储花色,数字大、花色大的牌前,最后一次遍历输出牌序。

        这里需要将flag的指标置零,如果直接 return 0 也可以。

int As[13][4]={0};int Bs[13][4]={0};

         于是,可以对数组内读入参数,这里只给出其中一个字符串的数据读入

while(a[i]!='\0'&&flag) // A 的数据填充 {if(a[i]==' ') // 跳过空格{i++;continue;}if(a[i]=='H'||a[i]=='S'||a[i]=='D'||a[i]=='C'){int m=0; // 列指标switch(a[i]) // 判断花色{case 'H':m=0;break;case 'S':m=1;break;case 'D':m=2;break;case 'C':m=3;break;}int n=0; // 行指标//往后是 i+1 因为现在的 i 指向的是花色,往后以为才是牌的内容 // 向二维数组中填充数据 if(isdigit(a[i+1])||a[i+1]=='A'||a[i+1]=='J'||a[i+1]=='Q'||a[i+1]=='K') {if(isdigit(a[i+1])&&a[i+1]!='1') //判断2~9,小的在下往上填{n=12-(a[i+1]-'0'-2); // 回忆一下 ascii码 就知道这步在干啥}else if(a[i+1]=='A') // 其他牌的读入{n=0; }else if(a[i+1]=='K'){n=1;}else if(a[i+1]=='Q'){n=2;}else if(a[i+1]=='J'){n=3;} if(a[i+1]=='1'&&a[i+2]!='\0'&&a[i+2]=='0') // 判断是不是 10 {n=4;}i+=n==4?3:2; // 结合 i 的指向,如果不是10就跳过两位,是10就跳过三位}else // 不符合的剔除 {printf("Input Error!\n");flag=0;flag1=0;break; }if(As[n][m]==0) // 如果没有重复的牌,那就可以在数组中标记这张牌{As[n][m]++;}else // 否则报错{printf("Input Error!\n");flag=0; flag1=0;break;}}else{printf("Input Error!\n");flag=0;flag1=0;break;}}

二、建立在先大小后花色基础上的排序

        是插入过程的逆,重新输出符合要求的字符串

int k=0;for(i=0;i<13;i++){for(j=0;j<4;j++){if(As[i][j]==1) //判断有没有这张牌{switch (j) // 以下的选择与刚才插入的时候刚好相反,属于反操作{case 0:a[k++]='H';break;case 1:a[k++]='S';break;case 2:a[k++]='D';break;case 3:a[k++]='C';break;} switch (i){case 0:a[k++]='A';break; // 根据牌所在的行数回推牌的内容case 1:a[k++]='K';break;case 2:a[k++]='Q';break;case 3:a[k++]='J';break;case 4:a[k++]='1';a[k++]='0';break;default:a[k++]=14-i+'0'; // 2~9 转回字符}a[k++]=' ';}elsecontinue;}}a[--k]='\0'; // 数组结尾,最好加上,免得访问越界

三、建立在规则上的初判断与初步输赢判定

        这里初判断 a 和 b 的牌型,如果牌型不同,直接就可以输出唯一胜者

        这里粗返回的 0~4 可以初步判断胜者,或者仍需比较

        这里返回的0~4可不是乱来的,后面有用到

        函数如下:

// 返回差,类似与冒泡排序,前一个与后一个比,根据差调整数组中元素的位置 // 打一下,试一试就会了,这个函数,很容易上手 int cmp(const void*e1,const void*e2) // 比较方式 {return *(int*)e1-*(int*)e2; }int search(int a[13][4]) // 初步判断牌的类型 {int i=0;int j=0;int c[3]={0}; // 创建新的数组,存储牌的 行指标 !!!int current=0; // 因为 行指标 标记了牌的大小for(i=0;i<13;i++) // 遍历查找牌{for(j=0;j<4;j++){if(a[i][j]==1){// 同花顺,占据连续的一列中的三个,最易判断if(a[i+1][j]==1&&a[i+2][j]==1&&i<=10) // 这里i防止越界访问{return 4;}c[current++]=i;}elsecontinue;}}qsort(c,3,sizeof(int),cmp); // 对牌进行排序,这里使用了 qsort 函数// qsort(数组名,元素个数,元素大小,比较函数)if(c[0]==c[1]&&c[1]==c[2]) // 炸弹(全部相等){return 3;}else if(c[2]-c[1]==1&&c[1]-c[0]==1) // 顺子(等差数列){return 2;}else if(c[0]==c[1]||c[1]==c[2]) // 对子{return 1;}return 0; // 啥也不是,只有单牌 }

 

 判断是否有胜者:

if((flag2>flag3||flag2<flag3)&&flag1) // 唯一胜者,且获胜方式单纯 {if(flag2>flag3){printf("Winner is A!\n");}else if(flag2<flag3){printf("Winner is B!\n");}printf("A: %s\n",a); // 打印排序后的牌内容printf("B: %s\n",b);}else if(flag2==flag3&&flag1) // 需要下一步比较

 

 四、建立在再次判断上的函数指针数组及其调用

        函数指针数组:一个数组(定语),数组内元素是函数指针(状语)

        故名思意:根据牌型,选择不同的函数来判断最终的胜利,也称回调函数

int (*pfarr[4])(char c[15],char d[15])={nmsl,wdnm,cnmd,mdzz};// *pfarr[4] 指针数组,运算符优先级决定它先与 [4] 结合成为数组 // (char c[15],char d[15]),参数类型,类似于函数的参数声明 // {nmsl,wdnm,cnmd,mdzz},函数名,我乱起的,没别的意思// 总的意思就是,一个数组,里面四个元素,分别指向一个独立的函数 else if(flag2==flag3&&flag1) // 需要下一步比较 {if(flag2) // 非零 {int (*pfarr[4])(char c[15],char d[15])={nmsl,wdnm,cnmd,mdzz};switch(flag2){case 4: // 这里的调用都是已经排序过的牌的字符串t=pfarr[0](a,b);break; // 函数调用类似于访问数组的元素case 3: // 不过记得带上函数的参数调用,就是后面的 (a,b)t=pfarr[1](a,b);break;case 2:t=pfarr[2](a,b);break;case 1:t=pfarr[3](a,b);break;}if(t==0){printf("Winner is A!\n"); }else if(t==1){printf("Winner is B!\n"); }else{printf("Draw!\n");}}else // 都是单牌,比较方法就是第一题的全部内容,不说了{int count=0;for(i=0;i<4&&flag;i++) // 对比两个数组判断输赢 {for(j=0;j<13&&flag;j++){if((As[i][j]==0&&Bs[i][j]==0)){continue;}else if(As[i][j]==1&&Bs[i][j]==1){count++;if(count==3){printf("Draw!\n");goto end;}}else if(As[i][j]>Bs[i][j]){printf("Winner is A!\n");flag=0;break;}else if(As[i][j]<Bs[i][j]){printf("Winner is B!\n");flag=0;break;}}}}

五、建立在近似判断机制上的四个函数

        刚才我们设定了四个函数,分别是{nmsl,wdnm,cnmd,mdzz},现在需要一一实现他们,达到我们预期的不同情况下的比较效果。

// 同花顺 判断 int nmsl(char a[15],char b[15]) {int i=0;if(strlen(a)==8) // 判断有没有万恶的 10,这里显然没有{for(i=0;i<15;i++){if(a[i]==b[i]) //相同跳过{continue;}else{if(a[i]=='H'&&b[i]!='H') // 花色碾压{return 1;}else if(a[i]!='H'&&b[i]=='H'){return 0;}else if(a[i]=='A'&&b[i]!='A') // 谨记,有A就没有10,这里是大小碾压{return 0;}else if(a[i]!='A'&&b[i]=='A'){return 1;}else // 其他牌的大小碾压{return b[i]-a[i]?1:0;}}}}else // 如果有万恶的10{for(i=0;i<15;i++){if(a[i]==b[i]){continue;}else{if(a[i]=='H'&&b[i]!='H'){return 1;}else if(a[i]!='H'&&b[i]=='H'){return 0;}else if(a[i]=='1'||b[i]=='1') // 出现了 10 !{if(a[i]=='1'){return b[i]<=57?0:1; // 10 跟另外一张牌比较,这里57,代表字符'9'} // 细想一下,是不是也合理?else{return a[i]<=57?1:0;}}else // 其他牌的大小碾压{return b[i]-a[i]?1:0;}}}}return 2; }

 

        其余的判断都是建立在刚才判断的基础上的,可以说比较部分几乎完全一致。

        这里多说一句,对子的判断,可以先找到是哪一对,创建一个两个元素的数组或者字符串,存下来这一个对子中的一张牌,然后跟另一个对子中的一张牌进行 皇城PK 即可。

        这里不给出余下的三个函数了,有心的同学看懂上面的思路一定信手拈来,不就是三个函数嘛?!何况这里已经给出了最核心的比较算法了。

六、写在最后的话

        我不否认出这道题的人有很多没有考虑到的地方,题目出的不是很严谨,但是在现有知识点的使用上,这绝对是综合的难题(就我的算法而言)

        如果你说你用一次遍历,哈希遍历,等等,那你可以完美的优化这道题的全部算法;

        但是就我而言,我希望写可读性更高的代码,不是说我不会写高效代码,hashmap我也可以写,但是这只是独善其身,不是每一个学C的人一开始就可以写0ms,2Mb的代码。

        如果你觉得这很难,那很抱歉,我已经尽我的一切可能,写最详细的注释,写最简单的代码了,谁又能刚开始一看就懂呢?

        如果你说,这博主怎么这么扣,这也不写,那也不赘述。

        不好意思,我都写了,是时候举一反三了。

        

        最后的最后,码字不易,要是本文多少学了点东西的友友们,点个赞让我看一下呗,至少我会觉得我的付出可以让大家获益

总结

以上是生活随笔为你收集整理的Poker time 2 (enhanced version)的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。