Poker time 2

Poker time 2 ,第1张

声明:

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

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

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

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

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


题目如下:

        背景:两个人每人发3张牌(各从一副牌中),每张牌包括花色(红桃(Heart)>黑桃(Spade)>方块(Diamond)>梅花(Club))和大小(从小到大依次是:2-10、J、Q、K、A),胜负规则如下:同花顺(3张同花色的连牌,先比大小,再比花色,后同)>炸d(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 的炸d
	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]!='        是插入过程的逆,重新输出符合要求的字符串'&&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]!='	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++]=' ';
			}
			else
			continue;
		}
	}
	a[--k]='// 返回差,类似与冒泡排序,前一个与后一个比,根据差调整数组中元素的位置
// 打一下,试一试就会了,这个函数,很容易上手
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;
			}
			else
			continue;
		}
	}
	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;    // 啥也不是,只有单牌
}';   // 数组结尾,最好加上,免得访问越界 '&&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;
		}
	}


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

       

 函数指针数组:


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

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

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

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

        函数如下:

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

 

 判断是否有胜者:

	if((flag2>flag3||flag2flag3)
		{
			printf("Winner is A!\n");
		}
		else if(flag2

 

 

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

        故名思意:根据牌型,选择不同的函数来判断最终的胜利,也称回调函数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},函数名,我乱起的,没别的意思 // 总的意思就是,一个数组,里面四个元素,分别指向一个独立的函数// 同花顺 判断 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
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]


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

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


 

 

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


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


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



六、写在最后的话

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

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

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


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

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


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


        

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/571852.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)