前言
之前不久在csdn论坛看到有人发帖子,寻求相关扑克牌的实现方法,但是时间问题,所有没有回,趁着疫情,而且我也没有写过相关的程序,所有就写了这篇博文(主要实现功能是根据论坛的求助帖子的要求写的),希望可以帮助那个需要帮助的人。
任务
具体的要求不怎么记得,主要是52张扑克牌,2-10,J-K,A,其中A最大,2最小,四种花色:梅花,黑桃,红桃,方块,13X4=52张牌。
要求有4个玩家,随机发牌,随机选择一个人开始出牌,可以出2张一样的或者3张四张。没有玩家打得过,就继续随机,打得过就继续下一个,直到有人全部出完。
至于其他扑克牌规则,主要是打不打得起的问题,主要修改一下识别和出牌方面的函数就可以,也可以看一下下面的博文延伸(主要是简洁思路和扩展思路)
设计思路
主要是通过采取一个带头结点的单项链表存储扑克牌信息(信息繁多,占用内存,不建议用数组),通过使用2-14个数字代表2-K-A 13个点数(方便识别谁大谁小,用字符就有点笨重),用0-3表示4个玩家即牌的属主,牌默认全部是0号属主的,通过随机分给3个其他属主,剩余的就是自己0号属主的。花色也采用0-3,没有采用字符串模式(方便且减少内存,没必要用“梅花”这样之类的),花色和扑克牌点数因为为了方便没有采用常见的扑克牌点,所有识别采用识别函数进行识别(内部结算不需要真正的扑克牌,只需知道点数或者花色),用一个标志记录这张牌是否出了,并且给每张牌进行编号,因为随机产生一个数,进行随机发牌。
补充
因为代码是没有精简的,所有可能有冗余的地方。其次,代码因为习惯原因写了基本上详细的注释,所以不是很理解的地方可以结合代码注释。基本流程,后面的函数流程和解释再写。
代码
list.h
#ifndef __LIST_H__
#define __LIST_H__
typedef struct node Card;
typedef struct hnode Hnode;
typedef struct look_elemp Elemp;
struct node
{
int num;//编号
int count;//点数
int flower_color;//花色
int owener;//属主
int state;//状态
Card * next;
};
struct hnode
{
Card * first;//编号为1开始的地址
int poker_num;//扑克牌张数
};
struct look_elemp
{
int count;//点数
int num;//牌的张数
int owner;//属主
};
Hnode * create_poker();
Hnode *increase_card(Hnode * h,Card * now_card);
int poker_init(Hnode *h);
Card *search_card(Hnode * h,int num);
void printf_card(Card * card);
void deal_poker(Hnode *h);//随机发牌
int sum_poker(Hnode *h);
/*
int have_or_not(Hnode *h,Elemp *elemp)
查找是否存在有elemp->count点数,属主是elemp->owner的牌
@h牌组,@elemp,查找牌的信息结构体
有,则返回点数,没有返回0;
*/
int have_or_not(Hnode *h,Elemp *elemp);
/*
int out_poker(Hnode *h,Elemp *elemp)
遍历是否能够出牌
@h:牌组
@elemp:出牌信息体
count;//需要出牌的最小点数
num;//牌的张数
owner;//属主
出牌成功返回出牌的大小
失败返回0
*/
int out_poker(Hnode *h,Elemp *elemp);
/*
int begin_randplay(Hnode *h)
第一次随机任意出牌
当elemp->owner属主为-1时表示选择随机出牌者
成功返回出牌点数
*/
int begin_randplay(Hnode *h,Elemp *elemp);
/*
int out_win_all(Hnode * h)
表示为出牌阶段
返回胜者
*/
int out_win_all(Hnode * h);
void poker_outinit(Hnode *head);
void kill_poker(Hnode *h);
#endif
list.c
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include"list.h"
#include <unistd.h>
/*
Hnode * create_poker()
创建一副空牌,返回空牌牌套(即头结点首地址)
*/
Hnode * create_poker()
{
Hnode *h=(Hnode *)malloc(sizeof(Hnode));
h->first=NULL;
h->poker_num=0;
return h;
}
/*
Hnode *increase_card(Hnode * h,Card * now_card)
生产一张牌
@h:表示牌套,即头结点首地址
@now_card:需要生产的牌的信息
{
int num;//编号
int count;//点数
int flower_color;//花色
int owener;//属主
int state;//状态
Card * next;//指向下一张牌
}
返回牌套的首地址
*/
Hnode *increase_card(Hnode * h,Card * now_card)
{
if(h==NULL)
{
return h;
}
Card *p=(Card *)malloc(sizeof(Card));
*p=*now_card;
Card *q=h->first;
int num=0;
if(h->first==NULL)
{
h->first=p;
p->next=p;
}
else
{
while(num<h->poker_num-1)
{
q=q->next;
num++;
}
q->next=p;
p->next=h->first;
}
h->poker_num++;
return h;
}
/*
int poker_init(Hnode *h)
生产一副牌
@h:表示要生产牌的头结点首地址(牌套)
成功返回1,失败返回0
*/
int poker_init(Hnode *h)
{
Card now_card;
now_card.flower_color=1;//代表梅花
now_card.owener=0;//默认属主是0号属主
now_card.state=0;//默认没有出牌
int num,count;
for(num=1;now_card.flower_color<=4;now_card.flower_color++)
for(count=2;count<=14;num++,count++)//14代表A
{
now_card.count=count;
now_card.num=num;
h=increase_card(h,&now_card);
}
if(h->poker_num==52)
{
return 1;
}
else
{
return 0;
}
}
/*
Card *search_card(Hnode * h,int num)
查找编号为@num的牌,返回其结点地址
*/
Card *search_card(Hnode * h,int num)
{
Card *p=h->first;
while(p->num!=num)
{
p=p->next;
}
return p;
}
/*
void printf_card(Card * card)
识别并且打印@card牌的信息
*/
void printf_card(Card * card)
{
printf("owner is %d\t",card->owener);
if(card->count>=2&&card->count<=10)//识别并且打印点数
{
printf("%d\t",card->count);
}
else
{
switch (card->count)
{
case 11:printf("%c\t",'J');break;
case 12:printf("%c\t",'Q');break;
case 13:printf("%c\t",'K');break;
case 14:printf("%c\t",'A');break;
default:printf("out count error\t");
}
}
switch (card->flower_color)//识别并且打印花色
{
case 1:printf("梅花\t");break;
case 2:printf("黑桃\t");break;
case 3:printf("红桃\t");break;
case 4:printf("方块\t");break;
default:printf("out flower_color error\t");//报错:花色识别出错
}
if(card->state==0)
{
printf("未出牌\n");
}
else
{
printf("已出牌\n");
}
}
/*
void deal_poker(Hnode *h)
随机发牌(牌组@h)
*/
void deal_poker(Hnode *h)
{
srand(time(0));//加入随机种子
int num,i,k;
for(k=1;k<4;k++)
{
for(i=0;i<13;)
{
num=rand()%52+1;
Card *p=search_card(h,num);
if(p->owener==0)
{
p->owener=k;
i++;
}
}
}
}
/*
int sum_poker(Hnode *h)
判断是否有人出完牌
成功返回属主,失败返回-1
*/
int sum_poker(Hnode *h)
{
Card *p=h->first;
int i,k;
int num[4]={0};
for(i=0;i<52;i++)
{
if(p->state==0)
{
num[p->owener]++;
}
p=p->next;
}
for(i=0;i<4;i++)
{
if(num[i]==0)
{
printf("0属主剩余:%d张牌\n",num[0]);
printf("1属主剩余:%d张牌\n",num[1]);
printf("2属主剩余:%d张牌\n",num[2]);
printf("3属主剩余:%d张牌\n",num[3]);
return i;
}
}
return -1;//都没有出完
}
/*
int have_or_not(Hnode *h,Elemp *elemp)
查找是否存在有elemp->count点数,属主是elemp->owner的牌
@h牌组,@elemp,查找牌的信息结构体
有,则返回点数,没有返回0;
*/
int have_or_not(Hnode *h,Elemp *elemp)
{
int n=0;
int i=0;
int k=0;
Card *p=h->first;
Card *q[4];
while (k!=52)
{
/*
判断点数,属主是否匹配,这张牌是否出出去了。
*/
if(p->count==elemp->count&&p->owener==elemp->owner&&p->state==0)
{
q[n]=p;//存储符号条件的牌的地址
n++;
}
if(n==elemp->num)
{
for(i=0;i<n;i++)
{
q[i]->state=1;
//printf_card(q[i]);
}
return elemp->count;
}
p=p->next;
k++;
}
return 0;
}
/*
int out_poker(Hnode *h,Elemp *elemp)
遍历是否能够出牌
@h:牌组
@elemp:出牌信息体,且换属主了
count;//需要出牌的最小点数
num;//牌的张数
owner;//属主
出牌成功返回出牌的大小
失败返回0
*/
int out_poker(Hnode *h,Elemp *elemp)
{
int i,n;
if(elemp->count==14)
{
return 0;
}
for(i=elemp->count++;elemp->count<=14;elemp->count++)
{
if(n=have_or_not(h,elemp))//@have_or_not会修改牌是否出了的状态
{
printf("%d属主出%d张",elemp->owner,elemp->num);
if(elemp->count>=2&&elemp->count<=10)//识别并且打印点数
{
printf("%d\n",elemp->count);
}
else
{
switch (elemp->count)
{
case 11:printf("%c\n",'J');break;
case 12:printf("%c\n",'Q');break;
case 13:printf("%c\n",'K');break;
case 14:printf("%c\n",'A');break;
default:printf("out count error\n");
}
}
elemp->count=i;
return n;
}
}
return 0;
}
/*
int begin_randplay(Hnode *h)
第一次随机任意出牌
当elemp->owner属主为-1时表示选择随机出牌者
成功返回出牌点数
*/
int begin_randplay(Hnode *h,Elemp *elemp)
{
srand(time(0));
int n=0;
if(elemp->owner==-1)
{
elemp->owner=rand()%4;//随机属主
}
elemp->count=1;//从小牌出
int k=0;
while(1)
{
elemp->num=rand()%4+1;//随机出牌张数
n=out_poker(h,elemp);
if(n)//判断是否出牌成功
{
elemp->count=n;
return n;
}
elemp->count=1;
}
}
/*
void poker_outinit(Hnode *head)
打牌结束后,打印所有牌的信息
*/
void poker_outinit(Hnode *head)
{
Card *p=head->first;
int i;
//printf("--------------------------------------------\n\n");
for(i=0;i<52;i++)
{
printf_card(p);
p=p->next;
}
}
/*
int out_win_all(Hnode * h)
表示为出牌阶段
返回胜者
*/
int out_win_all(Hnode * h)
{
Elemp elemp;
int winer,k,out_or_not,j;
elemp.owner=-1;
k=j=0;
printf("开始出牌\n");
begin_randplay(h,&elemp);//牌局开始随机出牌
j++;
while(1)
{
winer=sum_poker(h);
if(winer!=-1)
{
printf("%d号属主胜利\n",winer);
return winer;
}
elemp.owner=(++elemp.owner)%4;
if(j==4)//连续3个人都不要
{
elemp.count=1;
out_or_not=1;
printf("全部不要-----\n");
begin_randplay(h,&elemp);//随机出牌
j=1;
}
else
{
out_or_not=out_poker(h,&elemp);
if(out_or_not!=0)//出牌成功,切换点数
{
elemp.count=out_or_not;
j==1;
}
else
{
printf("%d属主不要\n",elemp.owner);
j++;
}
}
sleep(1);//保证用户有时间看出牌信息
}
}
/*
void kill_poker(Hnode *h)
销毁@h这幅牌
*/
void kill_poker(Hnode *h)
{
Card *p=h->first;
while(p)
{
p->count=p->flower_color=p->num=p->owener=p->state=0;
h->first=p->next;
p->next=NULL;
free(p);
p=h->first;
}
}
main.c
#include<stdio.h>
#include<string.h>
#include"list.h"
int main()
{
Hnode *head=create_poker();//创建牌套
int i=poker_init(head);//创建一副扑克牌
Card *p=head->first;
if(0==i)
{
printf("init error\n");//创建失败
return 0;
}
poker_outinit(head);//打印整个扑克牌信息
deal_poker(head);//随机发牌
poker_outinit(head);//打印整个扑克牌信息
out_win_all(head);//出牌阶段
poker_outinit(head);//打印整个扑克牌信息,验证出牌信息
kill_poker(head);//销毁这幅牌
return 0;
}
结果图
编辑环境:sources insight
运行环境:gcc
1.初始化图
随机发牌结束图
3.出牌图
验证图
主函数流程说明
int main()
{
Hnode *head=create_poker();//创建牌套
int i=poker_init(head);//创建一副扑克牌
Card *p=head->first;
if(0==i)
{
printf("init error\n");//创建失败
return 0;
}
poker_outinit(head);//打印整个扑克牌信息,主要是为了方便检验
//是否成功创建一个扑克牌,可以去除
deal_poker(head);//随机发牌
poker_outinit(head);//打印整个扑克牌信息,同样可以去除
out_win_all(head);//出牌阶段
poker_outinit(head);//打印整个扑克牌信息,验证出牌信息,可以去除
return 0;
}
创建基础扑克牌---->初始化扑克牌(即随机发牌)---->出牌---
-->结束并且销毁
扩展思考
代码采取的全部是链表操作,但是也可以采取链表存储,通过指针数组进行操作,即用数组保存玩家随机发到的牌的地址,然后通过操作相应的数组的元素的需要成员就可以判决,没有本博文用的那么复杂,冗余。
怎么通过这个代码实现现实生活中的或者其他类型要求的扑克牌游戏呢?
其他都差不多,只是在于怎么去实现找可以出的牌的问题。例如,如果在这个代码中加一个大小王,大小王一起是最大的,单个是最大,就只需要在每次找是否有比上一个玩家大的牌中加一个判断是否有大王或者小王及大小王,就可以。不同规则,不同实现,但是实际上都是差不多的。
扩展
相对而言不论怎么加都是有点呆板,如果会网络编程的也可以写一个扑克牌游戏,例如写一个斗地主,首先先找4个ip连接的(局域网的也可以),3个用于玩家,一个可以是有连接的局域网或者其他的电脑,有条件的也可以弄应该服务器(主要是保证游戏公平性,用于保存牌组相关信息),玩家之间可以通过一个私有协议,tcp,通信等等。
结束语
士不可以不弘毅,任重而道远。