纸牌小游戏使用纯C语言实现,使用VS2015编译测试,涉及的主要数据结构为 栈和队列 及C语言基础语法,这个是适合数据结构的初学者作为练习的小程序,其原型来自《啊哈!算法》,我对其进行了一点改进,增加了相应的输入检查,区分了函数以及其他的一些细节。解释和注释我都写的非常详细了,希望可以帮助理解~ o( ̄▽ ̄)ブ
先简单介绍一下这个简单的纸牌游戏——小猫钓鱼,就是我们将一副扑克牌(不含大小王)平均分成两份,两人每人一份,我们叫这两人分别为 甲 和 乙 吧,可以让 甲 先拿出手中的第一张扑克牌放到桌上,然后 乙 也拿出手中的第一张扑克牌,放在 甲 打出的扑克牌的上面,这样两人交替出牌,在出牌时,如果某人打出的牌,与桌面上的某张牌的牌面相同,即可将两张相同的牌及中间所夹的牌全部取走,并依次放到自己手牌的末尾,当任意一人手中的牌全部出完时,获得胜利,游戏结束。
其实游戏规则很简答,下面我们来想一下,该怎样用代码来逐个实现游戏功能,我们来分析一下每个的基本操作:
比如以甲为例,它主要会有两种操作,出牌和赢牌,如果要抽象成我们可以解决的代码,我们就需要一个合适的“容器”来实现可以动态的表示 甲 手中牌的变化,不难想到,用队列*** 这个数据结构可以很好地实现我们所需要的功能,出牌就是出队*,赢牌就是入队,简直是完美符合游戏要求。
下面就是桌面上的牌,该用什么来实现呢,我们需要做的是将相同的牌匹配,涉及到匹配,当然是用栈 来实现啦,匹配括号算法还记得吗?在这里,每打出一张牌放到桌子上就相当于一个元素入栈,当有人赢牌从桌子上拿牌的时候,就相当于元素出栈。
最后我们来想一下,如何实现赢牌的操作呢,一个比较容易想到的就是枚举桌面上的每一张牌,与之进行比较,如果一样,则判定为赢牌,可以取走对应的牌数,(我感觉这个应该比较好实现,所以我在下面代码中使用下面这种方法)如果进一步想一下的话,我们也可以使用数组来记录牌的信息,毕竟牌是有限的嘛,如果桌面上有了一张牌,我们就将数组对应的值设置为1,如果某张牌被取走了,我们就将其设置为0,这样我们就不需要遍历数组了,而只是使用一个if条件判断就可以实现了,(这个思想是不是有点熟悉,和我之前写的一个排序算法思想很相似最简单的排序—桶排序的实现)当然了,要试下这个条件判断,我们还得有个对应规则:将牌与数组对应起来(当然是用纯数组容易实现,如果非要使用字符也不是不可以,就是在结果匹配那里会不如数组方便),下面是我的对应规则:
好了,截止目前我们就已经分析清楚,我们要实现这个功能所需要的具体材料了:两个队列,一个栈,还有一个数组,下面我们就可以着手搭建了!
我们需要一个简单的操作界面,显示一些简单的信息,然后构造出我们需要的主要数据结构——两个队列和一个栈,
下面是一个简单的显示界面:
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;
在实现基本的框架之后,就要进行简单的初始化了,关于初始化,我是采用不同的函数来实现的,这样可以很好的进行模块化设计。
先想一下我们需要初始化的量,首先是两个用户的队列,其次是桌面的栈
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");
}
好了,截止现在,我们关于游戏前期所有的准备工作都已经实现了:我们已经有了合适的 数据结构来表示各种数据,两个人手中已经有了可以打出的手牌,下面就是两个人轮流出牌,然后判断输赢的过程了。
我们要判断输赢,下面我用甲来作为一个例子说明一下,乙赢的情形和甲是一样的,只需要把甲的代码的所有信息都变成乙的信息就可以了。我们可以假设 甲 先出牌,用一个临时变量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循环。
要判断谁赢得了游戏,我们就只需要判断谁没有牌了,然后输出胜利者的手牌,并显示一下桌面的手牌,下面是实现代码,详细注释在代码中:
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
#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");
}
好了关于小猫钓鱼的完整实现就到这里了,其中对于队列和栈的使用,函数传递,数组思想的运行等都有不同程度的深入,这对于我们编程的提高有很大的帮助,当然了,这个程序现在也还有遗憾的地方,就是不能及时纠错退出,我会继续更新然后实现这个更完美的效果的!(>ω・* )ノ
感谢观赏,一起提高,慢慢变强。