简单的纸牌游戏——小猫钓鱼(详细解释实现)

说明

纸牌小游戏使用纯C语言实现,使用VS2015编译测试,涉及的主要数据结构为 栈和队列 及C语言基础语法,这个是适合数据结构的初学者作为练习的小程序,其原型来自《啊哈!算法》,我对其进行了一点改进,增加了相应的输入检查,区分了函数以及其他的一些细节。解释和注释我都写的非常详细了,希望可以帮助理解~ o( ̄▽ ̄)ブ

运行结果

这是我修改后的代码的运行结果:
运行结果

前言

先简单介绍一下这个简单的纸牌游戏——小猫钓鱼,就是我们将一副扑克牌(不含大小王)平均分成两份,两人每人一份,我们叫这两人分别为 甲 和 乙 吧,可以让 甲 先拿出手中的第一张扑克牌放到桌上,然后 乙 也拿出手中的第一张扑克牌,放在 甲 打出的扑克牌的上面,这样两人交替出牌,在出牌时,如果某人打出的牌,与桌面上的某张牌的牌面相同,即可将两张相同的牌及中间所夹的牌全部取走,并依次放到自己手牌的末尾,当任意一人手中的牌全部出完时,获得胜利,游戏结束。

实现分析

其实游戏规则很简答,下面我们来想一下,该怎样用代码来逐个实现游戏功能,我们来分析一下每个的基本操作:

比如以甲为例,它主要会有两种操作,出牌和赢牌,如果要抽象成我们可以解决的代码,我们就需要一个合适的“容器”来实现可以动态的表示 甲 手中牌的变化,不难想到,用队列*** 这个数据结构可以很好地实现我们所需要的功能,出牌就是出队*,赢牌就是入队,简直是完美符合游戏要求。
下面就是桌面上的牌,该用什么来实现呢,我们需要做的是将相同的牌匹配,涉及到匹配,当然是用 来实现啦,匹配括号算法还记得吗?在这里,每打出一张牌放到桌子上就相当于一个元素入栈,当有人赢牌从桌子上拿牌的时候,就相当于元素出栈。
最后我们来想一下,如何实现赢牌的操作呢,一个比较容易想到的就是枚举桌面上的每一张牌,与之进行比较,如果一样,则判定为赢牌,可以取走对应的牌数,(我感觉这个应该比较好实现,所以我在下面代码中使用下面这种方法)如果进一步想一下的话,我们也可以使用数组来记录牌的信息,毕竟牌是有限的嘛,如果桌面上有了一张牌,我们就将数组对应的值设置为1,如果某张牌被取走了,我们就将其设置为0,这样我们就不需要遍历数组了,而只是使用一个if条件判断就可以实现了,(这个思想是不是有点熟悉,和我之前写的一个排序算法思想很相似最简单的排序—桶排序的实现)当然了,要试下这个条件判断,我们还得有个对应规则:将牌与数组对应起来(当然是用纯数组容易实现,如果非要使用字符也不是不可以,就是在结果匹配那里会不如数组方便),下面是我的对应规则:
对应规则
好了,截止目前我们就已经分析清楚,我们要实现这个功能所需要的具体材料了:两个队列,一个栈,还有一个数组,下面我们就可以着手搭建了!

具体实现

  1. 首先来搭建一个框架:

我们需要一个简单的操作界面,显示一些简单的信息,然后构造出我们需要的主要数据结构——两个队列和一个栈,
下面是一个简单的显示界面:

queue q1, q2;  //甲 和 乙 手上的牌
	stack s;  //桌子上的牌
	int i = 0;

	printf(" ==============================\n");
	printf(" =======小猫钓鱼游戏开始=======\n");
	printf(" ==============================\n");
	

下面是需要用到的数据结构:关于代码的含义我在注释中都有比价详细的解释了,当然了,为了代码以后的可读性和修改度,我将一部分常量使用define定义了,关于大小我也在代码注释中解释了,这个思想其实在之前的最简单的排序—桶排序的实现中,就很好理解了,当然了在这里也很好理解( ̄︶ ̄*))

#define q_size 1000
#define s_size 14
typedef struct queue
{
	int data[q_size];  //存储队列中的元素,这里主要是为了防止越界,故设置的比较大
	int head; //存储队头
	int tail;  //存储队尾
}queue;

typedef struct stack
{
	int data[s_size];
	//存储栈中的元素,这里设置为14,因为只有13中不同的牌面,所以桌上最多有13张牌,14就足够了
	int top; //存储栈顶
}stack;

在实现基本的框架之后,就要进行简单的初始化了,关于初始化,我是采用不同的函数来实现的,这样可以很好的进行模块化设计。

  1. 数据结构初始化

先想一下我们需要初始化的量,首先是两个用户的队列,其次是桌面的栈

void init_q(queue *p);  //初始化牌
void init_s(stack *p)
void init_b(int p[],int *max_size);   //初始化

下面是具体实现的三个函数,这个其实算是在学习数据结构时必须要掌握的内容了,而且关于初始化,我们一定是要传递定义好变量的地址,否则的话是不能做到顺利初始化的(因为传递值的在离开被调函数时就直接被系统销毁了,根本不会回到main函数)。
当然了在下面的函数中可以注意到一点,每个初始时函数我都注释了一个 初始化完毕的 输出语句,其实我个人感觉,类似这样的输出语句其实在调试代码中很有用处,可以很好的知道每个函数的执行情况,是个很不错的宏观调试方法了(。・∀・)ノ゙

void init_q(queue *p)
{
	p->head = 1;
	p->tail = 1;
	//初始化队列为空,此时两人手中还没有牌
	//printf("队列初始化完毕!\n");

}

void init_s(stack *p)
{
	p->top = 0;
	//初始化栈为空,此时桌面上还没有牌
	//printf("栈初始化完毕!\n");
}
void init_b(int p[],int *max_size)
{
	for (int i = 0; i <= *max_size; i++)
	{
		p[i] = 0;
	}
	//printf(" 桌面初始化完毕!\n");

}

在初始化完成之后,我们就可以对其进行输入数据了,我在这里是把需要在屏幕上显示的框架类的内容放在main函数中,其具体的代码我都会选择封装在函数中调用,下面是向其中输入数据的main函数中的部分:

	int max_size = 6;
	printf(" 初始时每人手中的牌数: ");
	scanf("%d", &max_size);
	while (max_size <= 1 || max_size > 26)
	{
		printf(" 牌数设置错误(2-26中间的数字),请重新设置每人手中的牌数:");
		scanf("%d", &max_size);
	}
//人物摸牌
	printf(" 请输入甲手中的牌:\n");
	read_s(&q1, &max_size);
	printf(" 请输入乙手中的牌:\n");
	read_s(&q2, &max_size);

	printf(" 展示甲手中的牌:");
	show(&q1);
	printf(" 展示乙手中的牌:");
	show(&q1);

下面是上面涉及的函数的实现:

void read_s(queue *p, int *max_size)
{
	
	for (int i = 1; i <=*max_size; i++)
	{
		printf(" 请输入ta的第%d张牌:", i);
		scanf("%d", &p->data[p->tail]);
		while (p->data[p->tail]<0||p->data[p->tail]>13)
		{
			printf(" 输入数据有误(1-13),请重新输入:");
			scanf("%d", &p->data[p->tail]);
		}
		p->tail++;
	}
	printf("\n");
	printf("\n");
}
void show(queue *p)
{
	for (int i = p->head; i <= p->tail - 1; i++)
	{
		printf("%d\t", p->data[i]);
	}
	printf("\n");
}


好了,截止现在,我们关于游戏前期所有的准备工作都已经实现了:我们已经有了合适的 数据结构来表示各种数据,两个人手中已经有了可以打出的手牌,下面就是两个人轮流出牌,然后判断输赢的过程了。

  1. 轮流出牌判断输赢

我们要判断输赢,下面我用甲来作为一个例子说明一下,乙赢的情形和甲是一样的,只需要把甲的代码的所有信息都变成乙的信息就可以了。我们可以假设 甲 先出牌,用一个临时变量t 来表示甲出的牌,这个就作为q1 队列的队首,同时当这张牌被打出的时候,我们就可以使用桌面这个数组来存储它了,
还得判断甲打出的牌是否可以赢得桌子上的牌,就是判断桌子上的牌与t 是否有相同的,我所采用的是和数组(book[ ],这里的book是有记录、登记的意思,当然使用mark等也可以,主要是为了代码的可读性要选用易于理解的变量名)中是否有一样的来判断是否可以拿走桌子上的牌。
下面来简单解释一下这个数组的使用:如果桌面上有一个牌面为3的牌,那就需要把数组book[3] 的值设置为1,这样就表示桌面上牌面为 3 的值就已经有了,如果这张牌面为3 的牌被拿走,我们就需要及时的把book[3] 重新设置为0,这样就表示桌面上已经没有牌面为3 的值了,所以我们如果要判断甲打出的某张牌在桌面上有没有,我们只需要一个简单的if 条件判断就可以实现了,下面就是具体的实现代码:

while (q1.head < q1.tail && q2.head < q2.tail) //只要一方手中还有牌,则游戏就继续
	{
		int t = q1.data[q1.head];  // 这里用t来表示 甲 要打出的牌
		if (book[t] == 0)  //桌面上没有牌面为t的牌
		{
		//甲没有赢牌
			q1.head++; //把打出的牌出列
			s.top++; 
			s.data[s.top] = t; //打出的牌放到桌子上,即入栈
			book[t] = 1;  //标记桌子上已经出现的牌
		}
		else
		{
		// 甲 赢牌
			q1.head++; //把打出的牌出列
			q1.data[q1.tail] = t; //因为赢牌了,所以要把该牌依次放到后面去用
			q1.tail++;
			while (s.data[s.top] !=t)  //把桌子上赢得的牌 统统依次放到手牌后面去
			{
				book[s.data[s.top]] = 0;  //取消标记
				q1.data[q1.tail] = s.data[s.top];  //放入队尾
				q1.tail++;
				s.top--;  //桌子上的牌减少了,所以要减1
			}
		}

乙赢牌和甲赢牌是一样的,就不再这里贴出了,我会把所有实现代码都放在最后的总体代码中。
出牌阶段实现完成之后,我们就可以思考游戏如何结束了,我所想的是只要有一个人的牌用完了,游戏就结束了,因此我在外面使用了一个while循环。

  1. 谁赢得了游戏

要判断谁赢得了游戏,我们就只需要判断谁没有牌了,然后输出胜利者的手牌,并显示一下桌面的手牌,下面是实现代码,详细注释在代码中:

if (q2.head==q2.tail)
	{
		printf(" 甲赢");
		printf(" 甲手中的牌为:");
		show(&q1);
		if (s.top > 0)
		{
			show_table(&s);
		}
		else
		{
			printf("\n 桌上已经没有牌了");
			printf("\n");
		}
	}
	else
	{
		printf(" 乙赢");
		printf(" 乙手中的牌为:");
		show(&q2);
		if (s.top > 0)
		{
			show_table(&s);
		}
		else
		{
			printf("\n 桌上已经没有牌了");
			printf("\n");
		}
	}

下面是上面涉及的show_table函数的具体实现,其实还是打印内容,只要遍历就可以了,下面是实现代码:

void show_table(stack *p)
{
	printf("\n 桌上的牌为:");
	for (int i = 1; i <= p->top; i++)
	{
		printf("%d\t", p->data[i]);
		
	}
	printf("\n");
	printf("\n");
}

好了,截止现在,关于小猫钓鱼这个具体实现我们就已经全部都实现完成了。

代码思考

我们在这个程序实现中,关于数据输入时的健壮性还是不错的,可以对输入数据进行有效的检查,但是如果实验足够多的数据的话,其实代码还是存在一个问题的,会导致游戏可能会永远运行下去,谁都无法赢得对方。比如我们如果输入这个数据,就会出现错误的结果:
输入数据结果错误这显然是错误的结果,这个问题还有待后续解决。

完整代码

下面是完整的具体实现代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define q_size 1000
#define s_size 14
//#define max_size //每日初始化时,手中的牌

typedef struct queue
{
	int data[q_size];  //存储队列中的元素,这里主要是为了防止越界,故设置的比较大
	int head; //存储队头
	int tail;  //存储队尾
}queue;

typedef struct stack
{
	int data[s_size];
	//存储栈中的元素,这里设置为14,因为只有13中不同的牌面,所以桌上最多有13张牌,14就足够了
	int top; //存储栈顶
}stack;

void init_q(queue *p);  //初始化牌
void init_s(stack *p);
void read_s(queue *p, int *max_size);  //摸牌
void init_b(int p[],int *max_size);   //初始化
void show(queue *p);
void show_table(stack *p);

int main()
{
	queue q1, q2;  //甲 和 乙 手上的牌
	stack s;  //桌子上的牌
	int i = 0;

	printf(" ==============================\n");
	printf(" =======小猫钓鱼游戏开始=======\n");
	printf(" ==============================\n");

	int max_size = 6;
	printf(" 初始时每人手中的牌数: ");
	scanf("%d", &max_size);
	while (max_size <= 1 || max_size > 26)
	{
		printf(" 牌数设置错误(2-26中间的数字),请重新设置每人手中的牌数:");
		scanf("%d", &max_size);
	}


	//初始化元素
	init_q(&q1); 
	init_q(&q2);
	init_s(&s);  

	//人物摸牌
	printf(" 请输入甲手中的牌:\n");
	read_s(&q1, &max_size);
	printf(" 请输入乙手中的牌:\n");
	read_s(&q2, &max_size);

	printf(" 展示甲手中的牌:");
	show(&q1);
	printf(" 展示乙手中的牌:");
	show(&q1);

	printf("\n");


	//桌牌记录
	int book[s_size];
	init_b(book,&max_size);

	printf(" 游戏运行中,下面是游戏运行结果:\n");
	printf("\n");

	//游戏开始
	while (q1.head < q1.tail && q2.head < q2.tail) //只要一方手中还有牌,则游戏就继续
	{
		int t = q1.data[q1.head];  // 这里用t来表示 甲 要打出的牌
		if (book[t] == 0)  //桌面上没有牌面为t的牌
		{
		//甲没有赢牌
			q1.head++; //把打出的牌出列
			s.top++; 
			s.data[s.top] = t; //打出的牌放到桌子上,即入栈
			book[t] = 1;  //标记桌子上已经出现的牌
		}
		else
		{
		// 甲 赢牌
			q1.head++; //把打出的牌出列
			q1.data[q1.tail] = t; //因为赢牌了,所以要把该牌依次放到后面去用
			q1.tail++;
			while (s.data[s.top] !=t)  //把桌子上赢得的牌 统统依次放到手牌后面去
			{
				book[s.data[s.top]] = 0;  //取消标记
				q1.data[q1.tail] = s.data[s.top];  //放入队尾
				q1.tail++;
				s.top--;  //桌子上的牌减少了,所以要减1
			}
		}

		t = q2.data[q2.head];  //乙出一张牌
		//判断是否能赢牌
		if (book[t] == 0)  //桌面上没有牌面为t的牌
		{
			//乙没有赢牌
			q2.head++; //把打出的牌出列
			s.top++;
			s.data[s.top] = t; //打出的牌放到桌子上,即入栈
			book[t] = 1;  //标记桌子上已经出现的牌
		}
		else
		{
			// 乙 赢牌
			q2.head++; //把打出的牌出列
			q2.data[q2.tail] = t; //因为赢牌了,所以要把该牌依次放到后面去用
			q2.tail++;
			while(s.data[s.top] != t)  //把桌子上赢得的牌 统统依次放到手牌后面去
			{
				book[s.data[s.top]] = 0;  //取消标记
				q2.data[q2.tail] = s.data[s.top];  //放入队尾
				q2.tail++;
				s.top--;  //桌子上的牌减少了,所以要减1
			}
		}

	}

	if (q2.head==q2.tail)
	{
		printf(" 甲赢");
		printf(" 甲手中的牌为:");
		show(&q1);
		if (s.top > 0)
		{
			show_table(&s);
		}
		else
		{
			printf("\n 桌上已经没有牌了");
			printf("\n");
		}
	}
	else
	{
		printf(" 乙赢");
		printf(" 乙手中的牌为:");
		show(&q2);
		if (s.top > 0)
		{
			show_table(&s);
		}
		else
		{
			printf("\n 桌上已经没有牌了");
			printf("\n");
		}
	}
	
	return 0;
}

void init_q(queue *p)
{
	p->head = 1;
	p->tail = 1;
	//初始化队列为空,此时两人手中还没有牌
	//printf("队列初始化完毕!\n");

}

void init_s(stack *p)
{
	p->top = 0;
	//初始化栈为空,此时桌面上还没有牌
	//printf("栈初始化完毕!\n");
}

void read_s(queue *p, int *max_size)
{
	
	for (int i = 1; i <=*max_size; i++)
	{
		printf(" 请输入ta的第%d张牌:", i);
		scanf("%d", &p->data[p->tail]);
		while (p->data[p->tail]<0||p->data[p->tail]>13)
		{
			printf(" 输入数据有误(1-13),请重新输入:");
			scanf("%d", &p->data[p->tail]);
		}
		p->tail++;
	}
	printf("\n");
	printf("\n");
}

void init_b(int p[],int *max_size)
{
	for (int i = 0; i <= *max_size; i++)
	{
		p[i] = 0;
	}
	//printf(" 桌面初始化完毕!\n");

}

void show(queue *p)
{
	for (int i = p->head; i <= p->tail - 1; i++)
	{
		printf("%d\t", p->data[i]);
	}
	printf("\n");
}

void show_table(stack *p)
{
	printf("\n 桌上的牌为:");
	for (int i = 1; i <= p->top; i++)
	{
		printf("%d\t", p->data[i]);
		
	}
	printf("\n");
	printf("\n");
}

总结

好了关于小猫钓鱼的完整实现就到这里了,其中对于队列和栈的使用,函数传递,数组思想的运行等都有不同程度的深入,这对于我们编程的提高有很大的帮助,当然了,这个程序现在也还有遗憾的地方,就是不能及时纠错退出,我会继续更新然后实现这个更完美的效果的!(>ω・* )ノ

感谢观赏,一起提高,慢慢变强。

  • 7
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值