数据结构与算法-项目实训-贪吃蛇

目录

一、编译环境

二、知识储备

        1、必备函数

        2、循环数组

        3、发牌算法

        4、二维数组的转化

三、核心代码

        1、结构体的介绍

        2、蛇的产生以及移动

        3、食物的产生

四、运行截图

五、总结


一、编译环境

        由于windows系统gcc编译器无法形成图像,故在进行贪吃蛇的项目完成时需要在turbo c 环境下运行。使用turbo c可以很容易实现贪吃蛇的显示。

二、知识储备

        1、必备函数

               1、 在turbo c存在一个头文件conin.h ,在这个头文件下存在三个我们需要的库函数:                              1)、clrscr()清除字符窗口函数,这个函数是简称清屏函数,来清除当前屏幕上的所有字符。在本项目中应用于在打印蛇头之前对屏幕上无用字符的清除,以免影响显示结果。
                        2)、 window()字符窗口函数。此函数用来对贪吃蛇边界判定以及背景颜色的设定。
                        3)、gotoxy()光标定位函数。用来移动光标实现贪吃蛇的移动、食物的产生以及贪吃蛇的增长。

                2、int bioskey (int cmd)函数:bioskey()完成直接键盘操作,cmd的值决定执行什么操作。
        cmd = 0:当cmd是0,bioskey()返回下一个在键盘键入的值(它将等待到按下一个键)。它返回一个16位的二进制数,包括两个不同的值。当按下一个普通键时,它的低8位数存放该字符的ASCII码;对于特殊键(如方向键、F1~F12等等),低8位为0,高8位字节存放该键的扫描码。
        cmd = 1:当cmd是1,bioskey()查询是否按下一个键,若按下一个键则返回非零值,否则返回0。
        cmd = 2:当cmd是2,bioskey()返回Shift、Ctrl、Alt、ScrollLock、NumLock、CapsLock、Insert键的状态。各键状态存放在返回值的低8位字节中。

        这个函数的定义与实现存在于bios.h中,我们在头文件中添加完毕后直接调用即可。

        2、循环数组

        在本项目中使用循环数组进行蛇头以及蛇身的移动,使用循环数组不仅可以降低移动算法的时间复杂度。还可以在之后的增长蛇身的长度中很容易实现。循环数组是对于数组的下标进行一定的运算,以保证仅仅用固定数量的数组空间来进行不定数量的存储。

具体表示 :index = (index + length - 1)% length;

        3、发牌算法

        发牌算法是一种可以选择出来不相同数据的选择算法。在本项目中主要应用在食物的产生函数中。对于发牌算法的具体介绍如下:

        下列有15组数据:
                0   1    2   3  4   5   6 7 8 9 10 11 12 13 14 15
                15 14 13 12 11 10 9 8 7 6  5   4   3   2   1  0
        现在随机取十个,要求不重复,可使用发牌算法,即选出来一个数字之后将此数字同最后一个数字下标交换,同时选取的数字范围减少一位,即将所选取过的数字发出去,最后一个未选取的数字存放在选取数字的下标所指向的空间中。
例证:从0-15中选择数字,现在选到下标为5的数字,
                0   1    2   3  4   5   6 7 8 9 10 11 12 13 14 15
                15 14 13 12 11 10 9 8 7 6  5   4   3   2   1  0
把10取出,并且与下标为十五的数字交换。
                0   1    2   3  4   5   6 7 8 9 10 11 12 13 14 15
                15 14 13 12 11 0   9 8 7 6  5   4   3   2   1  10
此时再一次选取数据时将选取范围减一,即从0-14中选择数字。这样做的好处是不会选到重复的数字。类似于玩扑克牌时发牌的原理,发完一张就少一张,且手里的扑克与发出去的牌不存在重复。 即发牌算法。

        4、二维数组的转化

        为了更好的表示整个贪吃蛇界面,我们可以使用将二维数组化为一维数组的思想,即将二维数组的横纵坐标使用一个坐标来表示,将整个屏幕化作一个地图,每一个点代表一个二维坐标,但是用一维数组来表示。

将整个屏幕作为地图

三、核心代码

        1、结构体的介绍

1)、贪吃蛇游戏的主要构成参数:游戏是否正常,游戏是否结束,蛇头的下标位置,蛇的初始设定长度,蛇的当前长度,蛇的移动方向,接收到的按键指令,蛇移动的速度,游戏结束的标志。

typedef struct SNACK_SEC {
	boolean ok;
	boolean finished;
	int headIndex;
	int length;
	int curlength;
	int direct;
	int key;
	int speed;
	boolean gameout;
	int map[2000];
}S_SEC;

2)、蛇的位置参数:横坐标,纵坐标以及地图上的坐标

typedef struct SNACK_LONG {//蛇的结构体
	int row;
	int col;
	int t;//地图下的一维坐标
}S_LONG;

3)食物参数:食物的横坐标和食物的纵坐标

typedef struct SNACK_FOOD {
	int row;
	int col;
}S_FOOD;

        2、蛇的产生以及移动

        蛇的产生是通过打印蛇头,在主程序中首先会赋初值给结构体S_SEC,如下:

S_SEC arg = {
		TRUE,            	/*boolean ok;*/
		FALSE,            	/*boolean finished;*/
		3,					/*int headIndex;*/
		4,					/*int length;*/
		1,					/*int curlength;*/
		RIGHT,				/*int direct;*/
		0,            		/*int key;*/
		DELAY_SIMPLE,		/*int speed;*/
		FALSE,              /*boolean gameout;*/
		0,                  /*int map[2000];*/

	};

        根据蛇的初始方向以及初始长度和当前长度来产生一条蛇。即通过打印蛇头的函数即可完成蛇的产生功能。

        对于蛇的移动功能,我采用了擦头去尾的方法:                                                                                        1)、去尾:如果蛇的当前长度小于初始长度不执行去尾程序,反之则去尾。                     2)、擦头:把当前的蛇头变为蛇身。                                                                                               3)、打印蛇头:再次执行打印蛇头程序。                                                                         这样便完成了蛇的移动功能!这样移动的好处不仅可以简单实现移动,而且在实现蛇吃食物后只需要不擦尾即可变长。

具体程序实现如下:

void movesnack(S_SEC *arg,S_LONG *lon,S_FOOD *food){
	int tailIndex;
	int i;
	
	if (arg->curlength < arg->length) {
		arg->curlength++;
		
	} else {
		tailIndex = (arg->headIndex + 200 - (arg->length) + 1) % 200;
		gotoxy(lon[tailIndex].row,lon[tailIndex].col);
		printf(" ");
		lon[tailIndex].t = (lon[tailIndex].row -1) * COL_COUNT + (lon[tailIndex].row -1);
		arg->map[lon[tailIndex].t]= 0;
	}
	gotoxy(lon[arg->headIndex].row,lon[arg->headIndex].col);
	printf("*");
	lon[arg->headIndex].t = (lon[arg->headIndex].row -1) * COL_COUNT + (lon[arg->headIndex].row -1);
	arg->map[lon[arg->headIndex].t]= 2;
	lon[(arg->headIndex + 200 + 1) % 200].row = lon[arg->headIndex].row + delta[arg->direct][0];
	lon[(arg->headIndex + 200 + 1) % 200].col = lon[arg->headIndex].col + delta[arg->direct][1];
	arg->headIndex = (arg->headIndex + 200 + 1) % 200;
	gotoxy(lon[arg->headIndex].row,lon[arg->headIndex].col);
	lon[arg->headIndex].t = (lon[arg->headIndex].row -1) * COL_COUNT + (lon[arg->headIndex].row -1);
	arg->map[lon[arg->headIndex].t]= 2;
	printfHead(arg);
	for (i = 0; i < 10; ++i) {
        if(food[i].row == lon[arg->headIndex].row && food[i].col == lon[arg->headIndex].col) {
			arg->length++;
			food[i].row = 0;			
			food[i].col = 0;			
		} 
	}
}

对于循环数组的使用具体图解如下:

        3、食物的产生

        蛇的食物的产生首先需要产生0到两千2000个随机数,然后用发牌算法选出十个不相同的地图上的点,作为食物产生的点。将一维坐标转化为二维坐标。

准备过程:这多次用到一维数组与二维数组下标转换的关系。

1)、例显示区域为80*25,则先准备一个含有2000个元素的数组,赋值为0-1999。

2)、把蛇的当前的蛇身坐标转换为一维数组的下标,并更改对应数组元素为-1.

3)、定义一个头指针,一个尾指针,分别从下标为0和1999相向遍历,若head对应的元素为-1,停下。若tail对应的元素不为-1,则停下。交换head和tail对应的元素,就这样一直找,知道把所有的-1移到数组的最后。

4)、准备工作已经做好,开始洗牌算法。在数组前面不为-1的元素里面,利用洗牌算法,取出一个随机数作为下标,把对应的元素再转换为坐标输出到屏幕上,即产生的食物。

代码如下:

void foodProduce(S_SEC *arg,S_FOOD *food) {
	int i;
	int j;
	int t;
	int k;
	int count = 0;
	int arr[10];
	int number[2000] = {-1};
	for(i = 0,j = 0; i < 2000; i++,j++) {
		if(arg->map[i] == 0){
			number[j] = i;
			
		}
	}
	for(i = 0; i < 1999; i++){
		if (number[i] > 0) {
			count++;
			
		}
	}
	
	srand(time( NULL ));
	for (t = count,i = 0; i < 10 ; --t,++i) {
	    	j = rand()%t;
	    	arr[i] = number[j];
		    k = number[t];
		    number[t] = number[j];
		    number[j] = k;
	    }

	    for (i = 0; i < 10; ++i) {
	  		food[i].row = (arr[i] % COL_COUNT) + 1;
			food[i].col = (arr[i] / COL_COUNT) + 1;
			gotoxy(food[i].row,food[i].col);
			printf("*");
			
			arg->map[arr[i]] = 1;
	    }
	
 }

四、运行截图

五、总结

       1、这个贪吃蛇在TC上可完美运行,控制上也较简单。 贪吃蛇是一个即时性要求高的程序,所以这里采用了空间换时间的方法(定义了两个大数组),例如洗牌算法那里时间复杂度最终为O(n)。精妙之处在于循环数组的运用和洗牌算法。

        2、这个贪吃蛇程序仅仅为数据结构练手的项目,通过这个练习,明显的感觉到在编程时,对函数的形参与实参的调用,指针的运用等等有了更加深刻的认识。

        3、项目=数据结构+算法。选取一个好的算法在特定的地方使用,会使自己的程序在效率上提升很多。在编程时,一定一定要组织好代码的大体框架,逻辑一定要清晰。否则,当程序慢慢变得复杂起来,出现问题时,逻辑不清晰,调试就很难很难,这时候不妨删掉以前的代码,重新组织编写。

        4、在这个项目中使用了地图化的思想,将二维坐标转化为一维的点,这个思想不仅可以帮助我们区分是否各种不同的元素,以及时间复杂度更低为O(n),更好的优化了我们的算法,在之后的编程中也应该多使用这样的思想,逐步优化算法,完善程序代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值