c语言课程结束小项目:2048小游戏

《C语言程序设计基础》学习完毕,完成第一个项目:2048小游戏。

//思考:编程时,要先想整个框架还是先想每个函数的算法。

//思考:每一个函数要不要传参数(即数组)

//思考:每一个函数之间的联系是什么,谁调用谁…这里很容易搞得头晕,越想越觉得复杂,便不想完成项目了。

 

将游戏分为几个步骤,拆分来完成每一个函数。

一、定义一个4*4的二维数组arr[4][4],初始化为0;

二、打印界面函数:Show_game();将数组打印出来;

三、随机函数:Add_rand():在数组中随机选出一个元素赋值,并且赋值要随机2或者4。所以就要定义i,j两个数组下表变量,将随机值%4,(求4的余数)就会得到0、1、2、3,将随机值赋值给i、j。还需要随机的2和4,可以在定义一个随机数m,m%3,得到0、1、2,在m大于1的情况下将m赋值给数组元素arr[i][j]。

这里的arr[i][j] = (m>1) ?2:4;这句代码,一句解决,厉害厉害是我最开始没有想到的。都是c语言中最简单的语句,但自己在实际编程中用得少,下意识的只会用if语句去判断,导致代码可读性差。

srand((unsigned) time (NULL));//随机种子。Srand需要用到头文<stdlib.h>,

这句用到time()函数,所以需要用到头文件<time.h>

关于srand()函数:

    3.1设置随机的起点

    3.2Void srand(unsigned int seed);

    3.3参数seed,随机生成的种子。

    3.4用法:srand和rand()配合使用产生伪随机数序列。rand函数在产生随机数前,需要系统提供的生成伪随机数序列的种子,rand根据这个种子的值产生一系列随机数。如果系统提供的种子没有变化,每次调用rand函数生成的伪随机数序列都是一样的。srand(unsigned seed)通过参数seed改变系统提供的种子值,从而可以使得每次调用rand函数生成的伪随机数序列不同,从而实现真正意义上的“随机”。通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列

四、游戏初始化函数InitGame():游戏开始第一个随机值要定为2

五、*核心算法*上下左右移动函数:Move_****()

我们这里分析左移(其他移动方法几乎相同)

┏━┳━┳━┳━┓i

┣━╋━╋━╋━┫i

┣━╋━╋━╋━┫i

┣━╋━╋━╋━┫i

┗━┻━┻━┻━┛

 K    J   J   J

左移的结果:是将每行的元素移动到最左边。

将k列设置为当前列,遍历数组时:i++;j++

找到arr[i][j] == 0时,不管

找到arr[i][j]不为0时,分三个情况移动:

  1. arr[i][k] == arr[i][j]时
  2. arr[i][k] == 0时
  3. arr[i][k] != 0 && arr[i][k] != arr[i][j] 又分两种情况

a.j、k挨着

b.j、k不挨着

 

右移同理,将k列的当前放在最右边:

┏━┳━┳━┳━┓i

┣━╋━╋━╋━┫i

┣━╋━╋━╋━┫i

┣━╋━╋━╋━┫i

┗━┻━┻━┻━┛

 J    J   J    K

 

上移动:

┏━┳━┳━┳━┓k

┣━╋━╋━╋━┫j

┣━╋━╋━╋━┫j

┣━╋━╋━╋━┫j

┗━┻━┻━┻━┛

 i    i    i   i       

 

向下移动:

┏━┳━┳━┳━┓j

┣━╋━╋━╋━┫j

┣━╋━╋━╋━┫j

┣━╋━╋━╋━┫k

┗━┻━┻━┻━┛

i     i    i     i   

 

每一种情况都相似,所以函数只是在遍历是将条件略微根据k、j、i修改。

 

六、判断游戏是否结束函数:GameOver(). 游戏结束有两个条件:1.没有一个元素为0  2.没有相邻的元素相等。

条件一:定义一个变量n,初始化为0;写了一个bool函数,遍历每一个元素,如果数组中有一个0元素,n++;判断n的值,若n == 0,返回1,说明条件该成立。(这个写法我一开始没有想到用一个计数器n)。

条件二:同样的方法,定义变量n,判断数组相邻位置是否一样n++。若n == 0则没有相邻位置元素一样,返回1。

最后GameOver函数中判断条件一和二,一&&二,则返回1,表示游戏结束。

(这里我写复杂了,用一个bool函数就可以实现,只需要遍历一次,找出n和m的值,if(n==0 &&m==0),则返回true,表示游戏结束)

七、主函数中,接受键盘字符getchar,用到switch语句写接收到的case

八、用到清屏系统命令system(“cls”),需要加头文件<windows.h>。

九、getchar()getch()是有区别的。它们都是输入单个字符用的。getch()用户不需要按回车,是读取按键值用的,并且需要头文件conio.h。按键输入时,屏幕没有反应。getchar()需要回车,是在输入设备中得到数据用的。按键输入时,屏幕显示输入的字符.

十、上下左右箭头:72 80 75 77.就是数字,不是字符,写成【case 75’: 】就错了。这也导致我开始从键盘怎么也读不上箭头符号。

 

以下源代码:

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>
#include<conio.h>


int arr[4][4] = {0};//游戏抽象为二维数组

int if_add;//是否生成随机数 1表示生成 0不生成

void ShowArr()//和下例的函数功能一样,但是下面例子加了边框,游戏观赏性高
{
	for(int i = 0;i < 4;i++)
	{
		for(int j = 0;j < 4;j++)
		{
			printf("%5d",arr[i][j]);
		}
		printf("\n\n");
	}
}
void ShowGame()//游戏界面(参考网上,加了边框,真香)
{
	printf("\n\n  *****2048*****\n");
	printf("┏━━━┳━━━┳━━━┳━━━┓\n");
	for(int i = 0;i<4;i++)
	{
		printf("┃");
		for(int j = 0;j < 4;j++)
		{
			//二维数组元素为0
			if(arr[i][j] == 0)
			{
				printf("   ┃");//4个空格
			}
			else
			{
				printf("%3d┃",arr[i][j]);
			}
		}
		if(i<3)
		{
			printf("\n┣━━━╋━━━╋━━━╋━━━┫\n");
		}
		else
		{
			printf("\n┗━━━┻━━━┻━━━┻━━━┛\n");
			printf("****WELCOME****\n\n\n");
		}

	}
}

void Add_rand()//在随机数组中放入一个随机的2或者4,2的概率要大。
{
	srand((unsigned) time (NULL));//随机种子
	int i;
	int j;
	int m = rand() % 3;//m可能的结果为0、1、2。
	while(1)
	{
		i = rand() % 4;
		j = rand() % 4;
		if(arr[i][j] == 0)
		{
			arr[i][j] = (m>1) ? 4 :2;//m大于1为4,否则为2。这样2的概率为33.3%,4的概率为66.7%
			break;//生成一个数,则退出
		}
		else
		{
			continue;//否则执行下一次循环,重新生成随机值。
		}
	}
}
void InitGame()//初始化:游戏开始有两个数字
{
	if_add = 1;//需要生成一个随机数
	int i = rand()%4;
	int j = rand()%4;
	arr[i][j] = 2;//第一个数字为2
}

void Move_left()//左移动
{
	for(int i = 0;i < 4;i++)
	{
		for(int j = 1,k = 0;j < 4;j++)
		{
			//先找到k项后面不为0的项
			if(arr[i][j] > 0)//分3个情况
			{
				//1.k项 == j项
				if(arr[i][k] == arr[i][j])
				{
					arr[i][k++] *= 2;//相当于arr[i][k++] <<= 1这样效率更高
					arr[i][j] = 0;
					if_add = 1;
				}
				//2.k项 == 0
				else if(arr[i][k] == 0)
				{
					arr[i][k] = arr[i][j];
					arr[i][j] = 0;
					if_add = 1;
				}
				//3.k项 != 0;j项 != k项
				else//分2种情况
				{
					arr[i][++k] = arr[i][j];
					if(j != k)
					{
						arr[i][j] = 0;
						if_add = 1;
					}
					
				}
			}

		}
	}

}
void Move_right()//右移
{
	for(int i = 0;i < 4;i++)
	{
		for(int j = 2,k = 3;j >= 0;j--)
		{
			//先找到k项前面不为0的项
			if(arr[i][j] > 0)//分3个情况
			{
				//1.k项 == j项
				if(arr[i][k] == arr[i][j])
				{
					arr[i][k--] *= 2;//相当于arr[i][k--] <<= 1这样效率更高
					arr[i][j] = 0;
					if_add = 1;
				}
				//2.k项 == 0
				else if(arr[i][k] == 0)
				{
					arr[i][k] = arr[i][j];
					arr[i][j] = 0;
					if_add = 1;
				}
				//3.k项 != 0;j项 != k项
				else//分2种情况
				{
					arr[i][--k] = arr[i][j];
					if(j != k)
					{
						arr[i][j] = 0;
						if_add = 1;
					}
					
				}
			}

		}
	}

}
void Move_up()//上移
{
	for(int i = 0;i < 4;i++)//列
	{
		for(int j = 1,k = 0;j < 4;j++)//行。
		{
			//先找到k项后面不为0的项
			if(arr[j][i] > 0)//分3个情况
			{
				//1.k项 == j项
				if(arr[k][i] == arr[j][i])
				{
					arr[k++][i] *= 2;
					arr[j][i] = 0;
					if_add= 1;
				}
				//2.k项 == 0
				else if(arr[k][i] == 0)
				{
					arr[k][i] = arr[j][i];
					arr[j][i] = 0;
					if_add= 1;
				}
				//3.k项 != 0;j项 != k项
				else//分2种情况
				{
					arr[++k][i] = arr[j][i];
					if(j != k)
					{
						arr[j][i] = 0;
						if_add = 1;
					}
					
				}
			}

		}
	}

}
void Move_down()//下移
{
	for(int i = 0;i < 4;i++)//列
	{
		for(int j = 2,k = 3;j >= 0;j--)//行
		{
			//先找到k项后面不为0的项
			if(arr[j][i] > 0)//分3个情况
			{
				//1.k项 == j项
				if(arr[k][i] == arr[j][i])
				{
					arr[k--][i] *= 2;
					arr[j][i] = 0;
					if_add= 1;
				}
				//2.k项 == 0
				else if(arr[k][i] == 0)
				{
					arr[k][i] = arr[j][i];
					arr[j][i] = 0;
					if_add= 1;
				}
				//3.k项 != 0;j项 != k项
				else//分2种情况
				{
					arr[--k][i] = arr[j][i];
					if(j != k)
					{
						arr[j][i] = 0;
						if_add = 1;
					}
					
				}
			}

		}
	}

}
bool TiaoJian_1()//游戏结束条件1:没有格子为0,游戏结束,return 1
{
	int n = 0;
	for(int i = 0;i < 4;i++)
	{
		for(int j = 0;j < 4;j++)
		{
			if(arr[i][j]  == 0)
			{
				n++;//有格子为零,游戏继续
			}
			
		}
	}
	if( n == 0)//游戏结束
	{
		return true;
	}
	else//游戏继续
	{
		return false;
	}
		
}
bool TiaoJian_2()//2.相邻(上下,左右)的值不相等,则游戏结束,return 1
{
	int n = 0;
	for(int i = 0;i < 4;i++)
	{
		for(int j = 0;j+1 < 4;j++)
		{
			if((arr[i][j] == arr[i][j+1]) || (arr[j][i] == arr[j+1][i]))
			{
				n++;
			}
		}
	}
	if(n == 0)//表示没有相等,结束
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
bool GameOver()//判断游戏:true表示结束。
{
	if(TiaoJian_1()  && TiaoJian_2())
	{
		return true;
	}
	else
	{
		return false;
	}
	
}


int main()
{
	InitGame();
	Add_rand();
	ShowGame();
	ShowArr();
	while(1)
	{
		int ch;
		if_add= 0;
		switch(ch = getch())//接收字符函数
		{
		    case 'a':
		    case 'A':
			case '4':
		    case 75:
				Move_left();
			    break;
			case 'd':
			case'D':
			case '6':
			case 77:
				Move_right();
				break;
			case'w':
			case 'W':
			case '8':
			case 72:
				Move_up();
				break;
			case 's':
			case 'S':
			case'2':
			case 80:
				Move_down();
				break;
			default:
				break;
		}

		if(if_add == 1)
		{
			
			Add_rand();
		    system("cls");//清屏系统命令 
			ShowGame();
			//ShowArr();
		}
		if(GameOver())
		{
			printf(" ***游戏结束***\n点击回车键退出程序");
			getchar();
			break;
		}
		
	}
}

 

  • 11
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值