高性能的贪吃蛇C语言实现

                                                           高性能的贪吃蛇C语言实现(西安微易码科技暑期项目实训课程)

        贪吃蛇是一个非常火爆的经典的小游戏,由于其实现起来较为简单,而且对界面的要求程度不高,经常被初学者当做提升自身编程能力的一个例子,而且由于它带有一定的趣味性,对于培养编程爱好者的兴趣和提高自身信心有很大的帮助。但是由于它有游戏的体验以及带给人很大的成就感,使得人们在编写贪吃蛇时,经常会不顾一切的去实现贪吃蛇的功能以便于尽快满足自身的成就感,以至于不愿意踏踏实实的去研究其内部的代码实现以及去思考如何改良自己的算法。本文会对贪吃蛇的不同的模块进行详细的讲解,也会更加注重代码的质量。


一、蛇的初始化问题

        要谈一个软件的运行,初始化是必不可少的,初始化是一个 软件运行的开始,也是基础,后续的代码都是在初始化的数据基础上进行的。

        要谈贪吃蛇的初始化问题,就要谈到贪吃蛇所需要用到的数据存储结构,以及描述一个贪吃蛇所需要的数据,这里我们给出存储控制贪吃蛇的数据的总结构体:

typedef struct SNAKE {
	POINT snake[MAX_LENGTH];    //真正存储蛇的实体空间(用循环数组存储)
	int isAlive;                //判断蛇是否活着(是否应该继续移动)
	int headIndex;              //蛇头所在的snake数组中的下标
	int tailIndex;              //蛇尾所在的snake数组中的下标
	int snakeLength;            //蛇的长度
	int level;                  //游戏等级(控制速度)
	POINT food;                 //食物所在的点坐标
}SNAKE;
        这里的POINT snake[MAX_LENGTH]数组是用来存储蛇的身体的实体空间(蛇的每一节的位置以及方向信息),因此我们给一个POINT结构体来表示蛇的每一节的位置信息以及方向信息(这里的方向信息仅仅用于表示蛇的每一节的输出方式,即头部向右走表示为‘>’,,向左走表示为‘<’,向上‘^’,向下‘V’,身体向左右走表示为‘-’,向上下走为‘|’,具体的移动是由循环数组实现的,具体的实现方案会在后续的讲解中做详细解释)。

        因此我们给出POINT结构体:

typedef struct POINT {
	int x;
	int y;
	int direct;
}POINT;
        有了数据存储的基础了,我们就可以开始编写我们的初始化代码了(具体的宏定义见程序源代码):

void initSnake(SNAKE *snake) {
	int i;

	snake->isAlive = TRUE;                                                //初始化蛇的各项控制数据;
	snake->headIndex = DEFAULT_LENGTH - 1;
	snake->tailIndex = 0;
	snake->snakeLength = DEFAULT_LENGTH;
	snake->level = DEFAULT_LEVEL;

	for(i = 0; i < snake->snakeLength; i++) {                             //生成一条长度为DEFAULT_LENGTH(默认长度)的蛇,将其存储到循环数组里,并显示它
		snake->snake[i].x = i + 1;
		snake->snake[i].y = 1;
		snake->snake[i].direct = DEFAULT_DIRECT;
		DEFAULT_LENGTH - i - 1 == 0 ? showHeadPoint(snake->snake[i]) ://判断当前要存储的位置是否为蛇头,并决定按蛇头方式输出还是按身体方式输出;
			showBodyPoint(snake->snake[i]);
	}
	snake->food = getFood(snake);                                         //生成第一个食物(具体过程后面会做详细的讲解);
	getchar();
}
        初始化完成了将默认蛇长数量的点按从末尾到头部的顺序依次存储到我们的循环数组里,并在屏幕上显示他们。由于输出头部和输出蛇的身体有区别,而且要根据方向的不同来判读应该输出什么,因此我们给出两个函数用来专门在屏幕上输出蛇的头或者身体。

void showHeadPoint(POINT point) {
	gotoxy(point.x, point.y);

	printf("%c", HOLE_headShow[point.direct]);
}

void showBodyPoint(POINT point) {
	gotoxy(point.x, point.y);

	printf("%c", HOLE_bodyShow[(point.direct + 1) % 2]);
}
        我们用gotoxy()函数来定位要在哪里输出字符;

        这里的输出POINT类型的数据,我们用了一个全局数组来存储每个方向上应该输出什么样的字符,而用方向的值作为这个全局数组的下标来确定在该方向上应该输出什么样的字符。

char HOLE_headShow[4] = {'^', '<', 'V', '>'};
char HOLE_bodyShow[2] = {'-', '|'};
        在这里我们规定,对方向变量的赋值,必须保证要用下面所给的一系列的宏进行赋值,从而保证了向上的方向一定为0,用它作为下标对应的headShow[0]一定为‘^’,从而保证了根据当前POINT类型变量中direct成员的值所确定的输出字符的准确性。
#define DIRECT_UP		0
#define DIRECT_LEFT		1
#define DIRECT_DOWN		2
#define DIRECT_RIGHT		3
#define DIRECT_ERROR		-1
        这样,我们就完成了对蛇的初始化;

        接下来,就要讨论如何让蛇向指定位置移动起来了:

        说到移动,就是要让蛇中的点的位置不断地变化,以达到视觉上移动的效果,那么我们将不断地移动分解开来看,就是由一次一次的移动一个单位组成的。而现在的问题就集中在了如何让蛇在指定的方向移动一个单位。

        首先我们先给出比较容易想到,实现起来较为简单,但是时间复杂度很高的方案:我们以蛇身上的每一个点作为单位,根据得到的下一次要移动的方向确定头所在的下一个位置,并将身体的每一个点的POINT信息赋值为前一个点的POINT信息,然后再把原来的蛇删掉并把新得到的蛇显示一遍。这种方案的缺点就是每一次都需要将整条蛇遍历一遍并且输出一遍,当蛇变得很长的时候时间复杂度会变得非常高。不过这是一种最容易想到的让蛇移动的方法,因为在我们的主观想法上,一条蛇想要移动,当然是整体都要进行移动,一个食物运动起来了,当然是每一个部分的位置都要改变的。但是不同的是我们是在完成屏幕上的东西,是要让人看的,只要看起来一样,没人会在意你真正是怎么实现的。因此我们就要仔细研究一下在屏幕上显示蛇的移动时是如何做到的:

我们可以看出,每一次的移动用不着移动全部的蛇,只要做三次操作就可以实现蛇向指定方向移动,即,对蛇头、蛇尾、蛇颈的操作:只需要在根据蛇当前移动方向得到的头的下一个位置处显示头的字符,将原来头的位置显示为身体,将蛇尾的位置的字符删掉。将这种方式与上述方法进行对比就可以发现,无论蛇有多长,每一次移动只需要执行定量的代码,时间复杂度为1。

        但是,当你想要这样实现的时候你就会发现问题并没有这么简单,如果只更改头和尾的值,中间的数据一直没有发生变化,导致只有头和尾在动,中间点的数据一直没有变化,如果要存储它们的数据,又涉及到遍历蛇的数组。因此我们给出一种较为犀利的方法:在存储结构上做文章,也就是之前提到过的循环数组!

        首先先抛开贪吃蛇的问题讨论一下循环数组的实现:

        用循环数组存储数据有如下几个特点:

1、用两个下标确定数据存储在数组中的位置;

2、添加新的数据,原先的末尾的数据会丢失;

3、每增加一个数据,头下标和尾下标的值都会更改,因此数组中存储数据的部分在数组中的位置会不断地变化。

4、由于存储数据的部分在数组中的位置不断地变化,因此,当某一元素到了数组的末尾的时候,应该使其重新回到数组的下标为0的地方,从而构成循环数组。

        从图片我们可以看出对循环数组的控制主要是对head和tail两个记录存储数据的第一个值个最后一个值在循环数组中的下标值的控制,但是控制两个变量的困难之处显而易见,就是在head的值是数组的最后一个元素的下标的时候,下一次如何让head回到数组的开始的地方,这里我们通过取余运算来更改head和tail的值:每一次让head或tail向后移动的操作不能是简单的head++和tail++,而是head\tail = (head\tail + 1) % (数组长度);这样就实现了构成一个循环数组。

        在了解了循环数组的运作模式和实现方法后,我们再来分析其对于贪吃蛇的意义:前面我们得到了移动蛇只需要更新头部的POINT值,并把之后的部分的蛇的身体依次更改为其前一节身体的POINT值,而这里循环数组就显示出它的强大之处了,由于我们是用两个下标进行定位的,因此我们将head的值和tail的值向下一个位置移动后,蛇的整体都向前移动了一个单位,移动前倒数第二个元素变为了最后一个元素,倒数第三个变为了倒数第二个元素,只需要更改两个下标值,这中间的每一个元素就都向前移动了一个单位,这样就达到了我们想要的效果:只更改头和尾的下标,其他的元素就自然而然的到了它前一个位置。通过这幅图我们可以清晰地看到蛇移动时数据在存储角度的变化,也可以看出循环数组实现的功能和我们想要完成的贪吃蛇的数据的变化是吻合的。

        由此我们可以得出移动一次的代码要执行的过程:

1、将当前头的位置按照输出身体的方式更改为身体的字符;

2、根据此次移动方向,确定此次移动后头的位置;

3、将循环数组中头下标向前移动一个单位,并将新下标位置的数据存储为上一步得到的头的位置信息,并将该位置按照输出头的方式显示在屏幕上;

4、根据当前循环数组中尾下标处的POINT位置,在屏幕上输出空格;

5、将尾下标向前移动一个单位;

        将蛇移动每一步具体做了些什么讨论清楚后,就可以得出如下让蛇移动一次的代码了:

void moveOnceOnDirect(SNAKE *snake, int direct) {
	POINT newPoint;                    

        /*
        int HOLE_direction[4][2] = {
            0, -1,        /*UP*/
            -1, 0,      /*LEFT*/
            0, 1,       /*DOWN*/
            1, 0        /*RIGHT*/
        };
        */
	newPoint.x = snake->snake[snake->headIndex].x + HOLE_direction[direct][0];    // 根据方向direct得到新的头的位置;
	newPoint.y = snake->snake[snake->headIndex].y + HOLE_direction[direct][1];

        if (isPartOfSnake(*snake, newPoint)) {                  // 判断要移动到的位置是否是蛇的身体的一部分,从而判断蛇是否还活着(有没有咬到自己);
         snake->isAlive = FALSE;
     }

	showBodyPoint(snake->snake[snake->headIndex]);          // 把原来头的位置的字符变成身体的字符;

	snake->headIndex = (snake->headIndex + 1) % MAX_LENGTH; // 更改循环数组中头下标的值,并将新的点存进去;
	snake->snake[snake->headIndex] = newPoint;
	snake->snake[snake->headIndex].direct = direct;

	showHeadPoint(snake->snake[snake->headIndex]);          // 将新的头的坐标处显示头字符;

	deletePoint(snake->snake[snake->tailIndex]);            // 将原来尾部的坐标的位置变成空格(删除尾部的点)
	snake->tailIndex = (snake->tailIndex + 1) % MAX_LENGTH; // 更改尾部坐标的值;

}
        对于上面的代码,在根据方向得到新的头的位置的部分,用到了一个较为复杂的技巧需要进行说明:我们给了一个全局二维数组int HOLE_direction[4][2],存储的是四个方向的移动对应的x和y两个值的变化,这样,只需要直接给原来的x和y值加上根据方向值确定的全局数组的下标的值。这样直接定位数组的下标的方式带来的好处就是程序的耗时较少,如果理解不了也可以用if、else判断方向,再给新的坐标赋值。

        当我们完成了让蛇在一个方向上移动一次后,蛇在屏幕上的连续的移动也就已经做好了,只要将移动一次的代码放到循环里并控制每次循环所暂停的时间就可以完成蛇在屏幕上连续的移动,那现在我们开始着手解决控制方向的问题:

        说到控制方向我们就不得不提到一个C语言库函数——bioskey()函数;

int bioskey(int dos)函数给出了监听键盘的很强大的功能,当该函数的dos参数为0时它可以返回从键盘输入的键的键值,并且可以监听方向键、esc键、shift键等等,因此我们可以通过单独的实验来得到上下左右键和esc键的键值作为以后的判断依据。当参数dos为1时,函数会返回当前有没有键盘的按键被按下,如果有返回一个非零值,如果没有返回0;这也是我们所需要的功能,当用户没有按下键盘的时候,蛇应该按照原来的方向继续移动,也就是direct值不改变,一旦发现用户按下键盘了,就用bioskey(0)进行判断用户按下的是什么键,并根据按下的键值来得到下一次移动的方向。

if(bioskey(1)) {
	keyName = bioskey(0);

	if(DIRECT_ERROR != getDirectByKeyName(keyName)) {

	        direct = getDirectByKeyName(keyName);

 }}

        当有键盘被按下了,我们要确定按下的按键是什么键,如果是方向键,我们应该更改当前的蛇的运动方向,这里我们判断按键的函数getDirectByKeyName()函数如下:

/*
#define ARROW_UP      0x4800                // 各个方向键的键值
#define ARROW_DOWN    0x5000
#define ARROW_LEFT    0x4B00
#define ARROW_RIGHT    0x4D00
#define ESC        0x011B

#define DIRECT_UP        0               // 规定的方向的值
#define DIRECT_LEFT       1
#define DIRECT_DOWN       2
#define DIRECT_RIGHT        3
#define DIRECT_ERROR        -1

int HOLE_keyName[4] = {                        //下标值为该键值对应的方向值
    ARROW_UP, ARROW_LEFT, ARROW_DOWN, ARROW_RIGHT,
};
*/
int getDirectByKeyName(int keyName) {
	int i;

	for(i = 0; i < 4; i++) {
		if(keyName == HOLE_keyName[i]) {
			return i;
		}
	}

	return DIRECT_ERROR;
}

        这里我们给了一个全局数组,用来存储方向的键值,而它们在数组中的下标就是方向值。如果理解不了还是可以用if、else进行判断来确定。

        到了现在,蛇已经可以在屏幕上随着我们的方向键移动了,接下来就要处理蛇吃食物的问题了,我们首先解决蛇吃到食物后增长的问题。

        当蛇吃到食物后,我们希望将蛇增加一定的长度,比如说一次性增长三节或者五节。那么应该如何让蛇的长度增加呢,我们首先想到的是在视觉角度上在需要增加的时候尾部原来的位置的字符不删除,而头继续向前移动,这样蛇的长度就增长了,但是这样处理蛇的长度一次性增加多节就有一定的困难了,这里我么给出一种方法:首先将尾下标处的蛇的POINT信息想之后的空间赋值n份,n的值取决于蛇要增长的长度,然后将尾下标向后移动n个空间。这样我们的移动的代码不用做过多的修改,依然每次移动删除尾部的坐标的字符,当需要增加的时候,由于尾部的值被复制了好几份,使得之后的n次移动删除的都是同一个位置的字符,也就相当于没有删除,这样就达到了增长蛇的长度的效果。

        执行增长仅仅需要完成如图所设的操作,剩下的移动部分的代码不需要改变。


 蛇增长具体代码如下:

void addSnakeLength(SNAKE *snake, int addLength) {
	int i;
	POINT tailIndexData;

	tailIndexData = snake->snake[snake->tailIndex];

	for(i = 1; i <= addLength; i++) {
		snake->snake[(snake->tailIndex - i + MAX_LENGTH) % MAX_LENGTH] = tailIndexData;
	}
	snake->tailIndex = (MAX_LENGTH + snake->tailIndex - addLength) % MAX_LENGTH;
	snake->snakeLength += addLength;
}

        处理完蛇身增长后,我们就要思考如何在屏幕上随机出现食物了,这里我们很容易想到用rand()产生随机数,但是目前有两个问题需要考虑:一是随机数产生的是一个数如何生成x和y值并控制两者的范围,二是如何让出现的食物的点坐标不在蛇的身上。最先想到的是产生两个随机数,分别控制在最大行值之内和最大列值之内,然后再根据该点的坐标判断该点是不是蛇的一部分,如果是,再重新生成随机数直到生成的点不在蛇的身上为止。但是这种方式在概率角度分析,屏幕中的点如果大部分都是蛇,那么生成一个不在蛇身上的点的坐标所用的时间的数学期望是很大的,因此时间复杂度会随着蛇的增长而增加。于是我们希望找到更犀利的解决方案:

        这个算法的大体思路是这样的:首先用一维数组来记录蛇能运动到的所有的点(围墙内的点)的下标,我们可以用一维数组的下标以及蛇要运动的区域的列值来表示点在屏幕上的坐标,计算公式:index = col * (y - 1) + x - 1;如果有不理解可以参考我的另一篇博客《任意行列二维数组C语言实现》。 我们用这样一个一维数组来表示蛇运动范围的所有点,然后我们将是蛇的部分的点标记出来,如果是蛇就标志为1,定位方法还是用上文给出的公式。然后我们再用另一个同样长度的数组,将之前数组中值为0的元素的下标存放在第二个数组中。然后我们用rand()方法生成一个0到全部的点数减去蛇长的一个随机数,用这个随机数作为下标取得第二个数组中的值,再将这个值用之前的公式解释为x和y值,这样就得到了空间中不是蛇的身体的部分的一个随机的坐标。


        具体代码如下:

POINT getFood(SNAKE *snake) {
	char point[MAX_ROW * MAX_COL] = {0};
	int pointIndex[MAX_ROW *MAX_COL] = {0};
	int i;
	int j = 0;
	int foodIndex;
	POINT food;

	for(i = snake->headIndex; i != snake->tailIndex; i = (i - 1 + MAX_LENGTH) % MAX_LENGTH) {
		point[snake->snake[i].x + (snake->snake[i].y - 1) * MAX_COL - 1] = 1;
	}

	point[snake->snake[i].x + (snake->snake[i].y - 1) *MAX_COL - 1] = 1;

	for(i = 0; i < MAX_ROW *MAX_COL; i++) {
		if(0 == point[i]) {
			pointIndex[j++] = i;
		}
	}
	srand(time(0));
	foodIndex = pointIndex[rand() % (MAX_ROW * MAX_COL - snake->snakeLength)];
	food.x = foodIndex % MAX_COL + 1;
	food.y = foodIndex / MAX_COL + 1;

	showFoodPoint(food);

	return food;
}
        到了这里,贪吃蛇的几大块核心代码就已经全部讲解完了,里面还是包含了很多的高级的编程技巧的,理解起来也不是容易的事,但是编程就是需要这样的简单的思考考虑不到的思维才能够编出更加符合实用角度的代码,才能让自己的程序的算法更加优化,对整体性思路以及问题本质的把握才会更加深刻。其余的一些较为基础的比如说界面问题以及与用户的交互问题我们不做讲解,本文的主旨是介绍贪吃蛇的核心代码的优化问题,接下来给出程序的完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<dos.h>
#include<bios.h>
#include<conio.h>
#include<time.h>

#define ARROW_UP	0x4800
#define ARROW_DOWN	0x5000
#define ARROW_LEFT	0x4B00
#define ARROW_RIGHT	0x4D00
#define ESC			0x011B

#define DIRECT_UP		0
#define DIRECT_LEFT		1
#define DIRECT_DOWN		2
#define DIRECT_RIGHT	3
#define DIRECT_ERROR	-1

#define MAX_ROW		15
#define MAX_COL		20

#define MAX_LENGTH	50

#define DEFAULT_COUNT	5
#define DEFAULT_LEVEL	2
#define DEFAULT_DIRECT	DIRECT_RIGHT

char HOLE_headShow[4] = {'^', '<', 'V', '>'};
char HOLE_bodyShow[2] = {'-', '|'};

int HOLE_direction[4][2] = {
	0, -1,		/*UP*/
	-1, 0,      /*LEFT*/
	0, 1,       /*DOWN*/
	1, 0	    /*RIGHT*/
};

int HOLE_keyName[4] = {
	ARROW_UP, ARROW_LEFT, ARROW_DOWN, ARROW_RIGHT,
};

int const HOLE_delayTime[5] = {
	1000, 700, 300, 200, 100,
};

#define DEFAULT_LENGTH	4

#define TRUE	1
#define FALSE	0

typedef struct POINT {
	int x;
	int y;
	int direct;
}POINT;

typedef struct SNAKE {
	POINT snake[MAX_LENGTH];
	int isAlive;
	int headIndex;
	int tailIndex;
	int snakeLength;
	int level;
	POINT food;
}SNAKE;

void initSnake(SNAKE *snake);
void showBodyPoint(POINT point);
void showHeadPoint(POINT point);
void showFoodPoint(POINT food);
void moveSnake(SNAKE *snake);
void deletePoint(POINT point);
int getDirectByKeyName(int keyName);
void addSnakeLength(SNAKE *snake, int addLength);
POINT getFood(SNAKE *snake);
int isPartOfSnake(SNAKE snake, POINT point);
void moveOnceOnDirect(SNAKE *snake, int direct);

void moveOnceOnDirect(SNAKE *snake, int direct) {
	POINT newPoint;

	newPoint.x = snake->snake[snake->headIndex].x + HOLE_direction[direct][0];
	newPoint.y = snake->snake[snake->headIndex].y + HOLE_direction[direct][1];

	if (isPartOfSnake(*snake, newPoint)) {
		snake->isAlive = FALSE;
	}
	showBodyPoint(snake->snake[snake->headIndex]);

	snake->headIndex = (snake->headIndex + 1) % MAX_LENGTH;
	snake->snake[snake->headIndex] = newPoint;
	snake->snake[snake->headIndex].direct = direct;

	showHeadPoint(snake->snake[snake->headIndex]);

	deletePoint(snake->snake[snake->tailIndex]);
	snake->tailIndex = (snake->tailIndex + 1) % MAX_LENGTH;
}

int isPartOfSnake(SNAKE snake, POINT point) {
	int i;

	for(i = snake.headIndex; i !=  snake.tailIndex; i = (MAX_LENGTH + i - 1) % MAX_LENGTH) {
		if(point.x == snake.snake[i].x && point.y == snake.snake[i].y) {
			return TRUE;
		}
	}
	if(point.x == snake.snake[i].x && point.y == snake.snake[i].y) {
		return TRUE;
	}
	return FALSE;
}

POINT getFood(SNAKE *snake) {
	char point[MAX_ROW * MAX_COL] = {0};
	int pointIndex[MAX_ROW *MAX_COL] = {0};
	int i;
	int j = 0;
	int foodIndex;
	POINT food;

	for(i = snake->headIndex; i != snake->tailIndex; i = (i - 1 + MAX_LENGTH) % MAX_LENGTH) {
		point[snake->snake[i].x + (snake->snake[i].y - 1) * MAX_COL - 1] = 1;
	}

	point[snake->snake[i].x + (snake->snake[i].y - 1) *MAX_COL - 1] = 1;

	for(i = 0; i < MAX_ROW *MAX_COL; i++) {
		if(0 == point[i]) {
			pointIndex[j++] = i;
		}
	}
	srand(time(0));
	foodIndex = pointIndex[rand() % (MAX_ROW * MAX_COL - snake->snakeLength)];
	food.x = foodIndex % MAX_COL + 1;
	food.y = foodIndex / MAX_COL + 1;

	showFoodPoint(food);

	return food;
}

void addSnakeLength(SNAKE *snake, int addLength) {
	int i;
	POINT tailIndexData;

	tailIndexData = snake->snake[snake->tailIndex];

	for(i = 1; i <= addLength; i++) {
		snake->snake[(snake->tailIndex - i + MAX_LENGTH) % MAX_LENGTH] = tailIndexData;
	}
	snake->tailIndex = (MAX_LENGTH + snake->tailIndex - addLength) % MAX_LENGTH;
	snake->snakeLength += addLength;
}

int getDirectByKeyName(int keyName) {
	int i;

	for(i = 0; i < 4; i++) {
		if(keyName == HOLE_keyName[i]) {
			return i;
		}
	}

	return DIRECT_ERROR;
}

void deletePoint(POINT point) {
	gotoxy(point.x, point.y);

	printf(" ");
}

void moveSnake(SNAKE *snake) {
	int keyName = ARROW_RIGHT;
	int direct = DIRECT_RIGHT;
	int isEating = FALSE;

	while(keyName != ESC && snake->isAlive && snake->snakeLength < MAX_LENGTH) {
		moveOnceOnDirect(snake, direct);

		if (snake->snake[snake->headIndex].x == snake->food.x && snake->snake[snake->headIndex].y == snake->food.y) {
			isEating = TRUE;
		}

		if(TRUE == isEating) {
			snake->food = getFood(snake);
			addSnakeLength(snake, 3);
			isEating = FALSE;
		}

		delay(HOLE_delayTime[snake->level]);

		if(bioskey(1)) {
			keyName = bioskey(0);

			if (DIRECT_ERROR != getDirectByKeyName(keyName)) {
				direct = getDirectByKeyName(keyName);
			}
		}
	}
	printf("isAlive:%d, snakeLength:%d, ", snake->isAlive, snake->snakeLength);
}

void showFoodPoint(POINT food) {
	gotoxy(food.x, food.y);

	printf("@");
}

void showHeadPoint(POINT point) {
	gotoxy(point.x, point.y);

	printf("%c", HOLE_headShow[point.direct]);
}

void showBodyPoint(POINT point) {
	gotoxy(point.x, point.y);

	printf("%c", HOLE_bodyShow[(point.direct + 1) % 2]);
}

void initSnake(SNAKE *snake) {
	int i;

	snake->isAlive = TRUE;
	snake->headIndex = DEFAULT_LENGTH - 1;
	snake->tailIndex = 0;
	snake->snakeLength = DEFAULT_LENGTH;
	snake->level = DEFAULT_LEVEL;

	for(i = 0; i < snake->snakeLength; i++) {
		snake->snake[i].x = i + 1;
		snake->snake[i].y = 1;
		snake->snake[i].direct = DEFAULT_DIRECT;
		DEFAULT_LENGTH - i - 1 == 0 ? showHeadPoint(snake->snake[i]) :
			showBodyPoint(snake->snake[i]);
	}
	snake->food = getFood(snake);
	getchar();
}

void main(void) {
	SNAKE snake = {0};

	system("cls");
	initSnake(&snake);
	moveSnake(&snake);
	getchar();
}








                
  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
#include <stdio.h> #include <windows.h> #include <conio.h> #include <time.h> //游戏窗口 #define FrameX 4 //游戏窗口左上角的X轴坐标 #define FrameY 4 //游戏窗口左上角的Y轴坐标 #define Frame_height 20 //游戏窗口的高度 #define Frame_width 20 //游戏窗口的宽度 //定义全局变量 int i,j; int a[2]; //用于记住蛇尾坐标,其中a[0]、a[1]分别表示横、竖坐标 //声明蛇的结构体 struct Snake { int x[100]; //蛇的横坐标,其中x[0]表示蛇尾的横坐标,x[N-1]表示蛇头的横坐标 int y[100]; //蛇的竖坐标,其中y[0]表示蛇尾的竖坐标,y[N-1]表示蛇头的竖坐标 int count; //蛇吃食物的个数 int length; //蛇的长度 int speed; //蛇的速度 }; //声明食物的结构体 struct Food { int x; //食物的横坐标 int y; //食物的竖坐标 }; /******光标移到指定位置**************************************************************/ void gotoxy(HANDLE hOut, int x, int y) //UNKNOW { COORD pos; pos.X = x; //横坐标 pos.Y = y; //纵坐标 SetConsoleCursorPosition(hOut, pos); } /******设置文本为绿色*****************************************************************/ void Set_TextColor_Green (void) { HANDLE Handle = GetStdHandle(STD_OUTPUT_HANDLE); //UNKNOW SetConsoleTextAttribute(Handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN); //UNKNOW } /******制作游戏窗口******************************************************************/ void make_frame() { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); //定义显示器句柄变量 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+13); //打印选择菜单 printf("Esc 退出游戏"); gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+15); printf("长按方向键:加速"); gotoxy(hOut,FrameX,FrameY); //打印框角 printf("╔"); gotoxy(hOut,FrameX+2*Frame_width-2,FrameY); printf("╗"); gotoxy(hOut,FrameX,FrameY+Frame_height); printf("╚"); gotoxy(hOut,FrameX+2*Frame_width-2,FrameY+Frame_height); printf("╝"); for(i=2;i<2*Frame_width-2;i+=2) { gotoxy(hOut,FrameX+i,FrameY); printf("═"); //打印上横框 } for(i=2;i<2*Frame_width-2;i+=2) { gotoxy(hOut,FrameX+i,FrameY+Frame_height); printf("═"); //打印下横框 } for(i=1;i<Frame_height;i++) { gotoxy(hOut,FrameX,FrameY+i); printf("║"); //打印左竖框 } for(i=1;i<Frame_height;i++) { gotoxy(hOut,FrameX+2*Frame_width-2,FrameY+i); printf("║"); //打印右竖框 } gotoxy(hOut,FrameX+Frame_width-5,FrameY-2); //打印游戏名称 Set_TextColor_Green (); //设置蛇为绿色 printf("贪吃蛇游戏"); } /******结束菜单*******************************************************************/ void over_game() { system("cls"); printf("\n\n\n\n\n\n\n\n\t\t\t\t游戏结束\n\n\n"); Sleep(2000); getch(); } /******菜单信息***************************************************************/ void print_information(HANDLE hOut,struct Snake *snake,struct Food *food) { gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+1); printf("level : %d",snake->count/5+1); //打印游戏等级 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+3); printf("score : %d",10*snake->count); //打印游戏得分 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+5); printf("eat food : %d",snake->count); //打印产生食物个数 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+7); printf("speed : %dms",snake->speed); //打印游戏速度 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+9); printf("foodX : %d",food->x); //打印食物的横坐标 gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+11); printf("foodY : %d",food->y); //打印食物的竖坐标 } /******初始化蛇**********************************************************************/ void init_snake(struct Snake *snake) { snake->x[0]=FrameX+2; //初始化蛇的横坐标 snake->y[0]=FrameY+Frame_height/2; //初始化蛇的竖坐标 snake->speed=300; //初始化蛇的速度为300ms snake->length=3; //初始化蛇的长度为3节 snake->count=0; //初始化蛇吃的个数为0 for(i=1;i<snake->length;i++) {/* 依次得到蛇身、蛇头的坐标 */ snake->x[i]=snake->x[i-1]+2; snake->y[i]=snake->y[i-1]; } } /******移动蛇*************************************************************************/ void move_snake(HANDLE hOut,struct Snake *snake) { gotoxy(hOut,snake->x[0],snake->y[0]); printf(" "); /* 清除蛇尾*/ for(i=1;i<snake->length;i++) {/* 后一节的坐标依次取代前一节的坐标 */ snake->x[i-1]=snake->x[i]; snake->y[i-1]=snake->y[i]; } } /******打印蛇*************************************************************************/ void print_snake(HANDLE hOut,struct Snake *snake) { for(i=0;i<snake->length;i++) { gotoxy(hOut,snake->x[i],snake->y[i]); if(i==0) { printf("○"); //打印蛇尾 } else if(i==snake->length-1) { printf("¤"); //打印蛇头 } else { printf("⊙"); //打印蛇身 } } } /******随机产生食物*******************************************************************/ void get_food(HANDLE hOut,struct Snake *snake,struct Food *food) { srand((unsigned)time(NULL)); //初始化随机数 while(1) {/* 产生食物的条件:1.在游戏窗口内 2.不在蛇的身上 */ food->x = rand() % (Frame_width-1); food->y = rand() % Frame_height; if( food->x==0 || food->y==0 ) { continue; } food->x = 2*food->x + FrameX; //得到食物的横坐标 food->y+=FrameY; //得到食物的竖坐标 for(i=0;i<snake->length;i++) {/* 判断食物是否在蛇的身上,如果在蛇身上,则重新产生;否则,打印蛇身 */ if( food->x==snake->x[i] && food->y==snake->y[i] ) { break; } } if(i==snake->length) { gotoxy(hOut,food->x,food->y); printf("⊙"); break; } } } /******吃食物***************************************************************************/ void eat_food(HANDLE hOut,struct Snake *snake,struct Food *food) { if( snake->x[snake->length-1]==food->x && snake->y[snake->length-1]==food->y ) {/* 如果蛇头位置与食物位置相同,吃食物 */ snake->length++; //吃一个食物,蛇身增长一节 for(i=snake->length-1;i>=1;i--) {/* 蛇后节坐标依次赋值给蛇前一节的坐标,依次得到蛇身及蛇头的坐标 */ snake->x[i]=snake->x[i-1]; snake->y[i]=snake->y[i-1]; } snake->x[0]=a[0]; //得到蛇尾移动前的横坐标 snake->y[0]=a[1]; //得到蛇尾移动前的竖坐标 get_food(hOut,snake,food); //重新产生食物 snake->count++; //食物的个数增1 if( snake->count%5==0 ) {/* 当蛇吃Up_level个食物时,速度加快Up_speed毫秒并且升一级 */ snake->speed-=50; } } } /******穿墙**********************************************************************************/ void through_wall(HANDLE hOut,struct Snake *snake,char ch) { if( ch==72 && snake->y[snake->length-1]==FrameY) { snake->y[snake->length-1] = FrameY+Frame_height-1; //如果蛇在上框且向上移动,穿墙 } if( ch==80 && snake->y[snake->length-1]==FrameY+Frame_height ) { snake->y[snake->length-1] = FrameY+1; //如果蛇在下框且向下移动,穿墙 } if( ch==75 && snake->x[snake->length-1]==FrameX ) { snake->x[snake->length-1] = FrameX+2*Frame_width-4; //如果蛇在左框且向左移动,穿墙 } if( ch==77 && snake->x[snake->length-1]==FrameX+2*Frame_width-2 ) { snake->x[snake->length-1] = FrameX+2; //如果蛇在右框且向右移动,穿墙 } } /******判断蛇是否死**************************************************************************/ int if_die(struct Snake *snake) {/* 当蛇头碰到自身时,蛇死 ,返回值为0 */ for(i=0;i<snake->length-1;i++) { if( snake->x[snake->length-1]==snake->x[i] && snake->y[snake->length-1]==snake->y[i] ) { return 0; } } return 1; } /******开始游戏*******************************************************************************/ void start_game() { unsigned char ch=77; //定义用于接收键盘输入的字符变量 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); //定义显示器句柄变量 struct Snake s,*snake=&s; //定义蛇的结构体指针并指向蛇的结构体 struct Food f,*food=&f; //定义食物的结构体指针并指向食物的结构体 make_frame(); //制作游戏窗口 init_snake(snake); //初始化蛇 get_food(hOut,snake,food); //随机产生食物 while(1) { print_information(hOut,snake,food); //打印菜单信息 a[0]=snake->x[0]; //记住蛇尾的横坐标 a[1]=snake->y[0]; //记住蛇尾的竖坐标 j=0; if(kbhit()) //unknow {/* 判断是否按下键盘,如果按下,ch接收键盘输入 */ ch=getch(); if(kbhit()) {/* 如果长按键盘,则加速 */ Sleep(20); //unknow j=1; } } switch(ch) { case 72: {/* 向上移动 */ move_snake(hOut,snake); //移动蛇 snake->y[snake->length-1]-=1; //蛇头的竖坐标向上移,即减1 break; } case 80: {/* 向下移动 */ move_snake(hOut,snake); //移动蛇 snake->y[snake->length-1]+=1; //蛇头的竖坐标向下移,即加1 break; } case 75: {/* 向左移动 */ move_snake(hOut,snake); //移动蛇 snake->x[snake->length-1]-=2; //蛇头的横坐标向左移,即减2 break; } case 77: {/* 向右移动 */ move_snake(hOut,snake); //移动蛇 snake->x[snake->length-1]+=2; //蛇头的横坐标向右移,即加2 break; } } through_wall(hOut,snake,ch); //穿墙 eat_food(hOut,snake,food); //吃食物 print_snake(hOut,snake); //打印蛇 if( if_die(snake)==0 || ch==27 || snake->speed==50 ) {/* 游戏结束条件:1.蛇碰到自身 2.按Esc键 3.速度为50ms */ gotoxy(hOut,FrameX+Frame_width-2,FrameY+Frame_height/2-1); printf("Game Over"); Sleep(2000); break; } if(j==0) { Sleep(snake->speed); //延迟时间 } else { Sleep(10); } } } int main() { system("color 0D"); //设置文本为粉红色 start_game(); //开始游戏 over_game(); //结束游戏 }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值