基于Linux系统的C语言多关卡推箱子设计

基于Linux系统的C语言多关卡推箱子设计

前言

   经典推箱子是一个来自古老的游戏,目的是训练人们的逻辑思维能力,巧妙的利用有限的空间和通道,合理的安排上下左右移动的次序和位置,箱子只可以推,不可以拉,而且一次只能推动一个,胜利的条件是把所有的箱子都推到目的地,从而完成游戏的要求。
   现在市场上的推箱子游戏各种各样,有基于Windows下C语言编写的,有基于JAVA和Python设计的,而基于Linux环境下设计的推箱子游戏非常少,因为一些头文件不兼容和人们对Windows操作系统的依赖性,加上Linux操作系统一切都是文件,所有的操作基本上都是在Terminal里面进行操作的,而且Linux下的可视化界面相对没有Windows下更加美观,     
   而且软件的安装没有Windows下更加便捷,所有的操作都是通过命令去完成实现的,所以设计出一款基于Linux操作系统下的推箱子小游戏也是非常不错的,而且利用Linux下标准IO的操作使得游戏更加的充满趣味性和可玩性,更好的体验该环境下的特色,从而改变人们对于Linux的一些看法,认识到Linux系统下设计的游戏和其他环境下一样,有着显著的优势和特点。

以下是本篇文章正文内容,下面案例仅供参考

一、设计方案

   推箱子游戏在开始后,可以通过按Q键,此时调用程序退出函数,结束游戏,也可以按其他键进行游戏初始化,此时进入游戏第一关。
   第一关如果挑战失败,此时调用程序退出函数结束游戏,如果第一关挑战成功,显示本关完成挑战,显示所使用的游戏步数。
   此时给玩家一个交互的选择,按Q键结束游戏,按回车键进入第二关,依次执行相关的操作。
   直到第五关挑战成功,此时显示本关所用的步数,然后退出游戏,游戏结束。

图1方案设计图

二、功能设计

2.1功能简述

 由键盘中的w,s,a,d四个字母分别代表上下左右来控制人的运动;
 当本关通过时,可以选择回车进行下一关,也可以选择q键直接退出游戏。
 当本关完成后,会显示完成本关所使用的步数,而且每一关都比上一关增加难度,要完  成的推箱子数不同,当5关全部完成后退出游戏,游戏结束。
 当箱子碰到墙壁而无法推动时,会进入游戏已经结束,退出游戏。

其他几个方位键操作思想类似,因代码较长,仅以向上方位键展示
代码示例:

	switch (ch)
	{
    
    
	case 'w':
	case 'W':
		//下一个地方等于空地或者是目的 能走
		if(7 == map[cas][i-1][j]||3 == map[cas][i-1][j])
	{
    
    
		return;
	}
		if (map[cas][i - 1][j] == 0 || map[cas][i - 1][j] == 3)
		{
    
    
			//走的实质是交换连个位置的值
			//原来的地方(map[i-1][j])人(5)走了
			map[cas][i][j] -= 5;
			//新的地方(map[i][j])人(5)来了
			map[cas][i-1][j] += 5;
			step++;
		}
                  //相邻的地方
		if (map[cas][i - 1][j] == 4 || map[cas][i - 1][j] == 7)
		{
    
    
			//相邻的地方的相邻
			if (map[cas][i - 2][j] == 0 || map[cas][i - 2][j] == 3)
			{
    
           //怎么走,三步
				//原来的地方
				map[cas][i][j] -= 5;
				//相邻
				map[cas][i - 1][j] += 1;
				//隔壁的隔壁
				map[cas][i-2][j] += 4;
				step++;
			}
		}
	

2.2流的类型和操作

标准I/O中流的缓冲类型有3种,分别是全缓冲,行缓冲,无缓冲。
全缓冲。在这种情况下,当填满标准I/O缓冲区后才进行实际的I/O操作。对于存放在磁盘上的普通文件,用标准I/O打开时默认是全缓冲的。当缓冲区已满或执行flush操作时才会进行磁盘操作。
行缓冲。当在输入和输出中遇到换行符时执行I/O操作。
无缓冲。不对I/O操作进行缓冲,即在对流的读写时会立刻操作实际的文件。标准出错流是不带缓冲的。

流的开始代码示例:

void start_soko(void)
{
    
    
 FILE* frp = fopen("soko.bin","w+");
 if(NULL == frp)
 {
    
    
 	printf("数据加载错误!\n");
 	return;
 }
 fread(map,1,490,frp);
 fclose(frp);
}

流的结束代码示例:

void exit_soko(void)
{
    
    
 FILE* fwp = fopen("soko.bin","w+");
 printf("游戏结束,退出游戏");
 if(NULL == fwp)
 {
    
    
 	printf("数据保存错误!\n");
 }
 fwrite(map,1,490,fwp);
 fclose(fwp);
 exit(0);
}

2.3标准I/O

参数 功能简述
r或rb 打开存在的只读文件
r+或r+b 打开存在的可读写文件
w或wb 打开只写文件,若文件不存在创建文件,文件存在则擦写以前的内容。
w+或w+b 打开可读写文件,若文件不存在创建文件,文件存在则擦写以前的内容。
a或ab 附加的方式打开只写文件,若文件不存在创建文件,文件存在保留原先内容,数据附加在文件尾。
a+或a+b 附加的方式打开可读写文件,若文件不存在创建文件,文件存在保留原先内容,数据附加在文件尾。

2.4流的使用

Fopen函数常用于使用标准I/O打开文件,如果打开文件成功则指向FILE的指针,如果打开文件失败则返回NULL。
Fclose函数常用于使用标准I/O关闭文件,该函数将流的缓冲区内的数据全部写入文件中,并释放相关资源。如果关闭文件成功则返回0,如果关闭文件失败则返回错误。
Fread函数是当文件流被打开之后,可对文件流按照指定单位的大小进行读操作。
Fwrite函数是文件流被打开后,可对文件流按照指定的单位大小进行写操作。 

2.5两个主要问题

设计中的两个主要解决的问题是:怎样生成地图、怎样实现人或人和箱子的移动。
这也是本次设计的核心和难点,只有解决了这两个问题,推箱子的基本功能才能实现。    
然后整个操作都是在地图绘制的基础上进行的,解决了这些基本的问题,才能在此基础上设计更多新颖的附加功能,比如增加游戏的关卡数,计算完成游戏所使用的步数等。

2.6地图的生成

设计中用三维整形数组中的数字元素表示不同的物体,比如0表示空地,1表示墙,3表示目的地,4表示箱子,5表示人,7表示箱子推到目的地。
然后用一个switch语句给每个数字赋予不同的符号,再输出三维数组,生成地图。

2.7人或人和箱子的移动

设计中要想使人移动,首先要找到人的起始位置,然后才能开始移动。人向四个方向移动的原理是相同的,可以先研究一个方向的移动,找到人的位置后,以人向上走为例,可以通过数组下标表示。
首先要判断人的上面是否为空地,因为向上进行移动,是空地则可以进行移动,移动后判断并改变人员原位置的数值元素和空地原位置的数值元素;否则不移动。
如果人的上面是目的地,人也是可以移动的,同样判断人原位置数值元素,并改变人员位置的数值元素和前一位置的数值元素。
如果人前面是空地上的箱子。如果箱子前面是空地,则可以进行移动,改变箱子前位置的数值元素,还是判断原位置的数值元素,然后改之;如果箱子前为目的地,则一样移动和改变数值元素,使得箱子推到目的地。
如果人前面是箱子已经推到目的地,则不可推动已经到达目的地的箱子,如果人的另一面是空地,则可以进行移动,执行上面的一些操作。
每一次移动,在完成本关后,都会进行初始化,然后再一次显示相应关卡的地图,这样便可以实现人或人和箱子的移动。

三、模块设计与使用

    本设计包括5个模块,分别是初始化模块、图画模块、箱子移动模块、小人移动模块和功能控制模块。

图3-1模块设计图

3.1初始化模块

   该模块包括屏幕初始化和游戏每一关的初始化。屏幕初始化主要用于输出游戏的操作提示,游戏的每一关的初始化是构建每一关的关卡和箱子数。

图3-2初始化框图

初始化部分代码示例:

   system("clear");//清屏
  	if(1==lose())
  	{
    
    
  	       system("clear");
  		printf("游戏失败!\n");
  		exit_soko();return 0;
  	}

3.2图画模块

    该模块主要用于被其他模块调用,用于画墙、画箱子、画目的地、画小人。
    其中墙用“#”表示,箱子用“$”表示,目的地用“o”表示,小人用“@”表示。

图3-3地图框架图
地图模块(思想)的代码示例:

int  map[填空][填空][填空] =  
/*三维数组里第一个表示关卡数,后面两个二表示行和列*/
{
    
    
 //0:空 1:#:墙
 //3:0 4:$  //目的地和箱子
 //5:@	  //人
 //7:m		  //目的(3)和箱子(4)在一起
 //8:@	  //人(5)和目的(3)在一起
 /*二维数组示例*/
    {
    
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
     1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
     1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,
     1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,
     1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,0,0,0,0,1,1,1,1,
     1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
     1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,4,0,0,0,0,1,0,0,0,0,0,1,1,
     1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,3,0,0,0,0,1,
     1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,4,0,0,0,1,0,0,0,1,1,
     1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
     1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,
     1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,
     1,0,0,0,0,0,0,0,0,0,0,0,0,3,0,4,0,5,0,0,0,0,0,0,1,1,1,1,
     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,}

};
int cas = 0;	//为0表示第一关
//记录每一关的箱子数 或者是项目和目的在一起的总数
int boxSum[] = {
    
    第一关目标,第二关目标,......};

3.3箱子移动模块

   该模块用于实现对箱子的上下左右移动,包括目的地之间、空地之间、目的地与空地之间箱子的移动。

图3-4箱子模块图

3.4小人移动

该模块用于控制小人的移动,从而推动箱子的移动到达目的地。

图3-5小人移动框图

3.5功能控制模块

 该模块是几个函数功能的集合,包括在屏幕上输出功能信息,关卡数、指定位置状态等。

图3-6功能控制图

多关卡代码(思想)示例:

int cur(){
    
    
   int i,j,k=0;
   for(i=0;i<9;i++){
    
    
   	for(j=0;j<11;j++){
    
    
   		if(map[cas][i][j]==4){
    
    
   		}
   	}
   }//遍历整个二
}//计算地图中有多少个终点

判断输赢(思想)示例:

int lose(){
    
    
   int i,j;
   int k=0;
   for(i=0;i<14;i++){
    
    
   	for(j=0;j<28;j++){
    
    
   		if(i>0 && j>0 ){
    
    	
   		if(map[cas][i][j] == 4||map[cas][i][j] == 7){
    
    
if(((map[cas][i-1][j] == 1 || map[cas][i-1][j] == 4 || map[cas][i-1][j] == 7) &&(map[cas][i][j-1] == 1 || map[cas][i][j-1] == 4 || map[cas][i][j-1] == 7))
|| ((map[cas][i][j-1] == 1 || map[cas][i][j-1] == 4 || map[cas][i][j-1] == 7) && (map[cas][i+1][j] == 1 || map[cas][i+1][j] == 4 || map[cas][i+1][j] == 7))
|| ((map[cas][i+1][j] == 1 || map[cas][i+1][j] == 4 || map[cas][i+1][j] == 7) && (map[cas][i][j+1] == 1 || map[cas][i][j+1] == 4 || map[cas][i][j+1] == 7))
|| ((map[cas][i][j+1] == 1 || map[cas][i][j+1] == 4 || map[cas][i][j+1] == 7) && (map[cas][i-1][j] == 1 || map[cas][i-1][j] == 4 || map[cas][i-1][j] == 7))){
    
    
   			k++; }
   		}
   	}
   	}
   }

按键退出(思想)示例:

 case 'Q':
 case 'q':
 exit_soko();

按键移动(思想)示例:

void keyDown()
{
    
    
   //分析按键过程
   //定位人在哪里
   int i, j;
   for (i = 0; i < 填空; i++)
   {
    
    
   	for (j = 0; j < 填空; j++)
   	{
    
    
   		if (map[cas][i][j] == 填空 || map[cas][i][j] == 填空)
   			break;//beak只能跳出一条语句
   	}
   	if (map[cas][i][j] == 填空|| map[cas][i][j] == 填空)
   		break;//break只能跳出一条语句
   }

四、结果验证

游戏退出:
图4-1游戏退出图
游戏失败:
图4-2游戏挑战失败图
挑战成功:
图4-3第一关挑战成功图
所有关卡挑战成功:
图4-4游戏所有关卡挑战成功图

总结

   基于Linux的推箱子游戏设计在对对程序的编写过程中,设计最大的亮点是模块化设计,将执行功能的各个部分封装成一个个模块,即子函数。
   在主函数中编写需要调用功能的子函数名即可执行相应的功能,目的是便于修改,减小对程序大规模的修改,降低了程序编写过程中的出错率。
   最大的特点是可以实现对数据的记录修改,在Linux环境下,通过借用标准IO的打开、关闭、读写操作实现对整个游戏是否记忆上次游戏位置,是否退出、开始游戏,进行了操作。
   程序在编写的时候考虑到实际的应用需求和视觉审美效果,尽最大程度的在保证程序正常实现各功能的前提下,增加游戏的难度,使游戏的界面呈现出美的效果。
   为了提高游戏的难度实现多关卡模式,地图设计的过程中原来的二维数组优化成了三维数组,原来的符号元素,在地图设计中改成了1:2的对称的效果。
   设计最大的难点是c语言头文件在不同操作环境下不匹配的问题,要实现相应的功能需要借助已有的头文件,编写函数进行调用。

源代码文件

猜你喜欢

转载自blog.csdn.net/qq_45171581/article/details/113176250