扑克牌游戏——老牛拉破车

目录

引子

构思以及代码部分

初始菜单以及变量的初始化

判断牌的花色及牌面数字

洗牌

帮助界面

出牌堆

主函数

游戏界面效果展示

存在的问题以及未来展望


引子

不知道大家有没有玩过一个叫做“老牛拉破车”的扑克牌游戏啊(大概率是没有玩过)

这是一个非常简(wu)单(nao)的游戏,游戏规则见下图:

扑克牌游戏——老牛拉破车_第1张图片

(请原谅我的语言表达能力……还有,电脑首先出牌是为了玩家好,真的!) 

这个游戏纯属靠运气取胜,但是是我小时候最喜欢的扑克牌游戏(小孩子还不会打斗地主……)

作为一个C语言新手,当然是要找一切可能的机会练习敲代码啦但是我才学到数组,还没有学字符串,怎么办呢?

那当然是不用字符串了呗!

于是在一个作业题目敲完了(摸鱼)的夜晚,我开始敲起了这个小游戏的代码,但没想到,这一敲就是——

“三天三夜,三更半夜……”(请自行脑补“全世界最高的高音”~)

期中季终于临近了末尾,我的摸鱼时间又回来啦!我感觉我再不把这篇文章写出来,我就该看不懂当时写的代码了QAQ

所以就有了现在这篇文章!恳请大佬指正Orz

构思以及代码部分

不想看巨长文章的友友们请移步我的主页,我以资源的形式上传了*.exe文件,大家可以试玩一下,如果有兴趣再过来看看我的文章(只要1积分哦)!

初始菜单以及变量的初始化

/*为了纪念我童年时和家里人一起玩“拉牛拉破车的”快乐时光*/
#include
#include
#include
int cards[54];//54张牌
int discard[15];//出牌堆
int card_number[15];
int m, score_player = 0, score_computer = 0, score = 2;
char s, n;

int Initial_menu()//初始菜单
{
	char key;
	printf("这个扑克牌游戏叫做 \"老牛拉破车\"。\n按 \"1\" 开始游戏,按 \"2\" 获取游戏规则……\n");
	key = getchar();
	switch (key)
	{
	case'1':return 1;break;
	case'2':return 2;break;
	}
}

头文件中stdio.h 用于输出(printf函数),stdlib.h用于暂停和清屏(system("pause")与system("cls")),而time.h用来生成随机数种子。

cards[54]是一个长度为54的一维数组,用来存储扑克牌的编号(1-54),discard[15]储存出牌堆当前的牌,而根据我们的规则可知,出牌堆的最大长度不会超过14(从A到K,再来一张大王或者小王),故这个数组的长度只需要15绝对就够了。

出牌堆数组里存的是牌的编号,我们还要根据相应的规则把编号变成牌面数字,并且存在另一个数组card_number[15]当中。

变量m的作用不大,用来获取玩家最开始按的键是1还是2(即直接进入游戏还是进入帮助菜单),还有两个用来存储得分的变量。变量score用来记录是谁出牌或得分。

字符变量s和n分别记录牌的花色和牌面数字。

初始菜单的代码写的非常“浅显易懂”,此处就不做过多解释了,我们进入下一个环节!

判断牌的花色及牌面数字

void suit(int i)//判断牌的花色
{
	switch (i / 13)
	{
	case 0:s = 'H';break;//红桃
	case 1:s = 'S';break;//黑桃
	case 2:s = 'D';break;//方片
	case 3:s = 'C';break;//梅花
	case 4:
		switch (i)
		{
		case 53:s = 'B';break;//小王
		case 54:s = 'R';break;//大王
		}
		break;
	}
}

void number(int i)//判断牌面的数字
{
	if (i <= 52)
	{
		switch (i % 13)
		{
		case 1:n = 'A';break;//A
		case 2:n = '2';break;
		case 3:n = '3';break;
		case 4:n = '4';break;
		case 5:n = '5';break;
		case 6:n = '6';break;
		case 7:n = '7';break;
		case 8:n = '8';break;
		case 9:n = '9';break;
		case 10:n = 'T';break;//10
		case 11:n = 'J';break;//J
		case 12:n = 'Q';break;//Q
		case 0:n = 'K';break;//K
		}
	}
	else if (i > 52)
	{
		n = '*';
	}
}

“同样非常的浅显!我们就不解释……”(“你是不是又想偷懒,啊?!”)

“好好好,我不偷懒……”

这里我们顺便学习一下扑克牌各个花色的英语表达吧!扑克牌编号为1-13定义为♥(红桃,Hearts),编号为14-26定义为♠(黑桃,Spades),编号为27-39定义为♦(方片,Diamonds),编号为40到52定义为♣(梅花,Clubs),53和53分别为小王和大王(Black Joker与Red Joker)。此处直接用整数除法的取整性即可完成判断过程。

后面判断牌面的数字只用用牌的编号模13即可!

我们直接进入下一超级重要的环节!

洗牌

void shuffle()//洗牌程序
{
	int shuffled_cards[54], i, r;
	srand((int)time(NULL));//随机数种子
	for (i = 0;i < 54;i++)
		shuffled_cards[i] = 0;
	for (i = 0;i < 54;i++)
	{
		do
		{
			r = rand() % 54;
		} while (shuffled_cards[r] != 0);
		shuffled_cards[r] = cards[i];//随机找到一个空位置放置扑克牌
	}
	for (i = 0;i < 54;i++)
		cards[i] = shuffled_cards[i];
}

其实洗牌程序大可不必存在于此,但是为了以后再写关于扑克牌小游戏程序的方便,这里就先写好,以备不时之需嘛……

我用了一个极其简单粗暴的方法来写这段代码(估计大佬们看了得笑死)

首先,随机数种子是必不可少滴!局部变量的初始化啥的也先不说了。cards[54]的初始化(从1到54)写在了主函数中,此处不做赘述。

接下来是一个循环套循环的过程。外部循环用来遍历数字从1-54的过程,内部循环用来找一个随机位置。如果找到的这个位置恰好为空,就把数字填进去,否则,继续生成随机数,直到找到一个空的位置。

那这里就有好好说一下这个内部循环的必要了。它非常笨拙,可能也相当的耗时,因为它找到一个空位置也完全靠运气……

do
{
	r = rand() % 54;
} while (shuffled_cards[r] != 0);
shuffled_cards[r] = cards[i];

rand()用来生成一个随机数,因为我用了时间来做种子,那这个“随机数”就真正是随机的了!具体原理我还没有学到,这里只是借鉴网上其他朋友的做法。用生成的随机数模54,可以得到0-53之间的随机数,正好是长度为54的数组的下标。就这样完成一个填充数字的过程。虽然很奇怪,而且可能消耗的时间巨大,但是亲测有效!

所以这里希望大家多多指教,教我一些更简单的洗牌方法!这里先谢谢大家啦!

帮助界面

帮助界面真的没啥好说的,规则都在开头,这里直接上代码了:

void help()//帮助界面
{
	if (m == 2)
	{
		system("cls");
		printf("你将和电脑一起玩这个“无脑”小游戏,看你俩的运气谁更好。\n"
			"你和电脑轮流抽牌,并且把这张牌放在已出的一列牌的最后\n"
			"如果这一列牌中有一张牌和你所出的牌的数字相同,\n"
			"你就可以把这两张相同的牌之间的所有牌拿走,并获得相应的得分。\n"
			"然后你就又有一次抽牌机会。\n"
			"如果你没有得分,你就只能把牌放在已出牌的一列的最后。\n"
			"赢家是当所有牌都被出完之后得分高的那一个。\n"
			"特别提醒:如果你拿到的那张牌是小王或者大王,你就可以拿走已出牌从头到尾的所有牌。\n"
			"但是当这两张牌出现在已出牌列的第一张牌的位置,它们就失去了这种奖励加成的功能。\n"
			"电脑首先出牌……\n");
		system("pause");
		system("cls");
		printf("H代表红桃\n"
			"S代表黑桃\n"
			"D代表方片\n"
			"C代表梅花\n"
			"T代表10\n"
			"B代表小王\n"
			"R代表大王\n");
	}
	system("pause");
	system("cls");
}

这里意外发现system()这个函数有这么多好用的功能,也算是一个巨大的收获了,让我有了探索这个函数其他用法的巨大动力!

出牌堆

这是一个十分简短却非常重要的部分!

void DiscardPile()
{
	int i;
	if (discard[0] == 0)
	{
		printf("空\n");
	}
	else
	{
		for (i = 0;i < 15 && discard[i] != 0;i++)
		{
			suit(discard[i]);
			number(discard[i]);
			printf("[%c %c]\n", s, n);
		}
	}
}

如果牌堆里面没有牌,就输出“空”。如果有牌,就把它们一张张地列出来。

这应该是除了主函数以外最重要的函数了!先上代码!

void Round(int i)
{
	int m, n, place = 0, o = 0;
	for (m = 0;m < 15;m++)
	{
		if (discard[m] == 0)
		{
			place = m;
			break;
		}
	}
	discard[place] = cards[i - 1];
	if (discard[place] < 53)
		card_number[place] = discard[place] % 13;
	else
		card_number[place] = discard[place];//将扑克牌的代号转换为牌面数字
	printf("你的得分:%d 电脑得分:%d\n", score_player, score_computer);
	if (score == 1)
		printf("你的回合!\n");
	else if (score == 2)
		printf("电脑回合!\n");
	DiscardPile();
	for (m = 0;m < place;m++)//遍历
	{
		if (card_number[m] == card_number[place] && place != 0)
		{
			for (n = m;n <= place;n++)
			{
				discard[n] = 0;
				card_number[n] = 0;
			}//如果发现了相同的牌,就把这两张牌之间的数归零
			if (score == 1)
				score_player += place - m + 1;
			else
				score_computer += place - m + 1;//得分
			o = 1;//如果o=1,说明这轮有人得分,否则没有人得分
			printf(" $o$\n");
			break;
		}
		else if (card_number[place] == 53 || card_number[place] == 54)//大王或者小王
		{
			for (n = 0;n <= place;n++)
			{
				discard[n] = 0;
				card_number[n] = 0;
			}
			if (score == 1)
				score_player += place + 1;
			else
				score_computer += place + 1;
			o = 1;
			printf(" $o$\n");
			break;
		}
	}
	if (o == 1)
	{
		o = 0;
		system("pause");
		system("cls");
		printf("你的得分:%d 电脑得分:%d\n", score_player, score_computer);
		if (score == 1)
			printf("你的回合!\n");
		else if (score == 2)
			printf("电脑回合!\n");
		DiscardPile();
		printf(" ^o^\n");
	}//如果有人得分,需要把更新后的出牌列打印出来
	else if (o == 0)//如果没有人得分,需要交换出牌方
	{
		if (score == 1)
			score = 2;
		else if (score == 2)
			score = 1;
	}
	system("pause");
	system("cls");
}

这个函数包括第i轮的所有操作。首先,遍历discard数组,这样可以记录当前出牌堆中牌的数量,并且找到第一个可以放所出牌的空位置,记录下这个位置。

然后是抽牌的过程。从未出牌堆中拿出一张牌等价于取出card[i-1]对应的数据(第i轮,那么下标为i-1)。现在我们要把牌的编号转化为牌面的数字,具体过程就是取余,详见代码(开始偷懒)……特别注意大王和小王,即53和54不能取余(规则这么说的嘛,要不然没了bonus多难受啊)。

这里,变量Score开始发挥它的作用!其值为1时,是玩家的回合,如果赢了也应该玩家得分,否则就是电脑的回合。然后调用DiscandPile()函数(见上)进行一个输出。

接下来到了关键步骤——

我们遍历数组,只为寻找相同的那个数;

我们细看牌堆,只为发现相同的那张牌;

我们搜寻世界,只为发现世界上的另一个我……

咳咳,跑偏了跑偏了,回来回来。如果找到了相同的数字,就把两个数字之间的全部数据归零,这就相当于把牌拿走的过程,表现在得分上就是分数加上相应的被归零的数据个数。如果发现这个数据(不在第一个位置)为53或者54,即牌面为小王或大王,就把牌堆里的所有牌归零。

这里我还加了一个小表情($o$)作为可以得分的提示,见钱眼开了属于是。

如果有人得分了,需要把更新后的出牌列打印出来,这里就要用一下system("cls")这个函数了,这也是从网友那里学来的,感谢网友!

如果没有人的得分,需要交换出牌权,详见代码。

主函数

终于到了主函数!加油,终于看到了胜利的曙光,只用把以上模块拼接起来就可以大功告成了!

int main()
{
	int i;
	srand((int)time(NULL));
	m = Initial_menu();
	if (m == 2)
	{
		help();
	}
	else if (m == 1)
	{
		system("cls");
	}
	for (i = 0;i < 54;i++)
	{
		cards[i] = i + 1;
	}
	for (i = 0;i < 15;i++)
	{
		discard[i] = 0;
	}
	shuffle();
	for (i = 1;i <= 54;i++)
	{
		Round(i);
	}
	if (score_player > score_computer)
	{
		printf("你赢啦!\n");
		printf("欢迎再来一局!\n");
	}
	else if (score_player < score_computer)
	{
		printf("电脑赢了!\n");
		printf("欢迎再来一局!\n");
	}
	else
	{
		printf("竟然打成平局了……\n");
		printf("欢迎再来一局!\n");
	}
	system("pause");
	return 0;
}

// ♥ ♠ ♦ ♣,这几个符号是“不可打印”字符……

这里涉及到了cards[54]的初始化过程,非常简单,循环一遍就好了。

然后洗牌,摸牌,看牌……胡了!

嘿嘿,其实进行一个54遍的循环,每循环一次调用一次Round()函数即可实现游戏过程啦。

最后进行一个得分高低的判断,游戏结束,大功告成!!!

游戏界面效果展示

扑克牌游戏——老牛拉破车_第2张图片

黑白界面,简单游戏,测试运气!这就是三个游戏关键词了!不愧是我最爱的老牛拉破车扑克牌小游戏! 

存在的问题以及未来展望

本人是实打实的C语言新手,没有学过算法啊、数据结构啊等等,所以有的地方写得很笨拙(菜鸟水平),也完全没有什么优化的思想,写这篇文章只是想和C语言新手们交流思路,并获得一些大佬的指点(真传),所以欢迎所有人批评指正!以下是一些具体的问题:

1.每次二者得分的差距都很悬殊,不知道是不是因为存在Bug,但我暂时还没找到……

2.我想让每次第一个出牌的人随机化,但是懒得改代码了……

3.在几十次测试中,有一次发牌的过程出现了乱码,我觉得是洗牌的那个复杂片段出了问题,但还不知道怎么修改……

4.我想让大王的Bonus提高一些,比如说双倍得分,但我懒得改代码了……还有还有,规则中那个奇怪的“大小王放在第一个就会失去加成功能”的规则其实是一个Bug,我知道咋改,但还是懒啊……

5.代码写得太复杂了,有的地方不规范。可能大佬两三行的写法,我写了十几行还是没写完……

未来的话,我可能会再改进一下这个小程序,比如说解决问题2和问题4。如果可能的话,我想让算法更智能一些,让电脑与人斗智斗勇。与人斗其乐无穷,那与电脑斗,会不会也其乐无穷呢?我期待我能把程序智能化这一天的到来!

今天我收获了我在CSDN的第一个粉丝,开心!欢迎大家关注我,和我交流经验哦!

你可能感兴趣的:(C语言新手,c语言)