小白入门 C语言版贪吃蛇 (三个字 “好看”)

链接: 贪吃蛇源码及程序 提取码: jfsg

话不多说,先上一张贪吃蛇的流程图。给你三分钟时间,好好看看这张图,从这个图里先简单理解下整个游戏的执行过程。
小白入门 C语言版贪吃蛇 (三个字 “好看”)_第1张图片
那么从上面的图里,我们大概就能够知道贪吃蛇整个程序功能大致可以拆分成以下几个基本部分。

  1. 游戏开始界面
  2. 游戏地图界面(包括墙、活动区域以及)
  3. 获取蛇移动方向(捕获键盘输入)
  4. 蛇移动方向控制
  5. 判断蛇是否吃到食物
  6. 生成新的食物
  7. 判断蛇是否撞到自身或墙壁
  8. 游戏结束界面

在正式讲解程序之前我们先普及点关键知识点:

1> 更改控制台输出的文本颜色

很多人都以为控制台只能显示黑白色,其实在window下是支持彩色输出的,为了不让我们的游戏显得枯燥无聊,可以通过修改这一设定来实现我们想要的颜色效果。

在windows系统下,我们可以调用Windows.h头文件下的SetConsoleTextAttribute函数改变文字和背景颜色。

调用形式为:SetConsoleTextAttribute( HANDLE hConsoleOutput, WORD wAttributes );

HANDLE是long整型的别名,hConsoleOutput是一个整数标识符,每一个整数与程序窗口一一对应,这里我们可以通过GetStdHandle(STD_OUTPUT_HANDLE)来获得控制台窗口对应的整数标识符;

WORDunsigned short整型的别名,wAttributes表示文字颜色和背景颜色。使用低4位表示文字(前景)颜色,高4位表示文字背景颜色,所以它的取值为0x00~0xFF

0~F 分别代表的颜色如下:

0 = 黑色 1 = 淡蓝 2 = 淡绿 3 = 湖蓝
4 = 淡红 5 = 紫色 6 = 黄色 7 = 白色
8 = 灰色 9 = 蓝色 A = 绿色 B = 浅绿
C = 红色 D = 淡紫 E = 淡黄 F = 亮白

例如:将背景设置为亮白色,文字设置为黄色。

#include 
#include 
int main(){
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);//获取控制台标识符
    SetConsoleTextAttribute(hConsole, 0xF6);//将控制台背景设置为亮白色,文字设置为黄色
    puts("这是一个更改的文本颜色");
    return 0;
}

小白入门 C语言版贪吃蛇 (三个字 “好看”)_第2张图片

2> 在任意位置输出文本

在一般情况下,我们都是按照顺序依次输出想要的数据到控制台上,而不去设定每次数据输出的位置。在运行贪吃蛇游戏的过程中,大部分位置的字符并不发生变化。为了提高程序效率,只需要对特定位置的字符进行更新即可。同样的,windows本身就为我们提供了这样的函数接口,即SetConsoleCursorPosition函数。其包含在头文件windows.h中,它的调用方式为:

SetConsoleCursorPosition(HANDLE hConsoleOutput, COORD dwCursorPosition);

hConsoleOutput的含义我们已经在前面解释过了,这里不再赘述;dwCursorPosition是光标位置,也就是第几行第几列,它是 COORD 类型的结构体,包含两个整型变量x和y,其中x表示列,y表示行。

例如:在第5行,第10列输出“■”;在第3行,第6列输出“★”;在第1行,第1列输出“◆”;最后,将坐标重新定位到第7行,第0列的位置。

#include 
#include 

//自定义的光标定位函数
void setCursorPosition(int x, int y){
    COORD coord;
    coord.X = x;
    coord.Y = y;
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 
    SetConsoleCursorPosition(hConsole, coord);
}

int main(){
    setCursorPosition(10, 5);
    puts("■");
	setCursorPosition(6, 3);
    puts("★");
	setCursorPosition(1, 1);
    puts("◆");
    setCursorPosition(0, 7);
    return 0;
}

小白入门 C语言版贪吃蛇 (三个字 “好看”)_第3张图片

3> 键盘输入检测

为了实现对贪吃蛇的移动方向进行控制,我们必须能够检测电脑按键的输入并判断按下的具体按键。

在Windows系统下,conio.h头文件中的kbhit()函数就可以用来实现键盘输入检测。

用户每按下一个键,都会将对应的字符放到输入缓冲区中,kbhit()函数会检测缓冲区中是否有数据,如果有的话就返回非 0 值,没有的话就返回 0 值。但是kbhit()不会读取数据,数据仍然留在缓冲区,所以一般情况下我们还要结合输入函数将缓冲区中的数据读出。请看下面的例子:

#include 
#include 
#include 
int main(){
    char ch;
    int i = 0;
    //循环监听,直到按Esc键退出
    while(1){
        if(kbhit()){  //检测缓冲区中是否有数据
            ch = toupper(getch());  //将缓冲区中的数据以字符的形式读出
			printf("第%d次按下的按键是%c;\n", ++i,ch);
        }
    }
    return 0;
}

小白入门 C语言版贪吃蛇 (三个字 “好看”)_第4张图片

4> 获取随机数

在实际编程中,我们经常需要生成随机数,例如,贪吃蛇游戏中在随机的位置出现食物,扑克牌游戏中随机发牌。在C语言中,我们一般使用 头文件中的 rand() 函数来生成随机数,它的函数原型为:int rand (void);

rand() 会随机生成一个位于0 ~ RAND_MAX 之间的整数。

RAND_MAX 是 头文件中的一个宏,它用来指明 rand() 所能返回的随机数的最大值。C语言标准并没有规定 RAND_MAX 的具体数值,只是规定它的值至少为 32767。在实际编程中,我们也不需要知道 RAND_MAX 的具体值,把它当做一个很大的数来对待即可。

请看示例代码:

#include 
#include 
int main(){
    for(int i=0;i<5;i++){
    	int R= rand()%100;
    	printf("新生成的随机数是%d\n",R);
    }
    return 0;
}

小白入门 C语言版贪吃蛇 (三个字 “好看”)_第5张图片
可以看到我们成功得到了随机数,但是当你关掉控制台并且重复执行这段代码时,你会依旧得到这样的结果。

这是为什么?

实际上,rand() 函数产生的随机数是伪随机数,是根据一个数值按照某个公式推算出来的,这个数值我们称之为“种子”。种子和随机数之间的关系是一种正态分布,如下图所示:
小白入门 C语言版贪吃蛇 (三个字 “好看”)_第6张图片

种子在每次启动计算机时是随机的,但是一旦计算机启动以后它就不再变化了;也就是说,每次启动计算机以后,种子就是定值了,所以根据公式推算出来的结果(也就是生成的随机数)就是固定的。

那么如何解决这个问题呢?

为了程序能够重复获得不同的随机数,我们经常要使用srand()函数来重新“播种”,这样种子就会发生改变。srand() 的函数原型为:void srand (unsigned int seed);

它需要一个 unsigned int 类型的参数。在实际开发中,我们可以用时间作为参数,只要每次播种的时间不同,那么生成的种子就不同,最终的随机数也就不同。

在windows系统下,我们通常使用 头文件中的time() 函数即可得到当前的时间,就像下面这样:srand((unsigned)time(NULL));其中NULL表示以当前时间作为参数对种子进行更新。

下面是一个随机数生成的实例:

#include 
#include 
#include 
#include 
int main(){
    for(int i=0;i<5;i++){
        srand((unsigned)time(NULL));//以当前时间作为参数更新种子
    	int R= rand()%100;//生成100以内的随机数
    	printf("新生成的随机数是%d\n",R);
    	Sleep(1000);//暂停1秒,等待时间改变,否则太快时间一样得到的种子就不会变化,自然结果也不会变化
    }
    return 0;
}

小白入门 C语言版贪吃蛇 (三个字 “好看”)_第7张图片
在开始讲解程序之前,我们先来介绍下代码中的一些相关变量、结构体以及常用函数,以便于我们对后续程序的理解。

  • 头文件
    程序中必须包含的头文件。
#include   //贪吃蛇程序中会用到输入输出函数
#include  //贪吃蛇程序中会用到随机数生成函数
#include   //贪吃蛇程序中会用到按键监听函数
#include    //贪吃蛇程序中会用到获取系统当前时间的函数
#include //贪吃蛇程序中会用到对控制台设置的一些函数
  • 宏定义
    这是简单的宏定义,不做解释。
#define MAPW     (30)  //地图宽度 
#define MAPH     (30)  //地图高度 
#define SNAKELEN (3)   //贪吃蛇的初始长度 
  • 节点类型
    在贪吃蛇游戏中,我们可以把地图上每一个字符块看成一个节点。为了能够区分围墙、活动区域、贪吃蛇、食物这些节点,我们构建了以下数据类型。
//构建节点类型 
typedef struct{
    char *Char;//指向字符,由于本程序中使用是宽字符,这里用char *指向字符首地址
    char Type; //字符类型
	int  Color;//字符颜色
}MapData;

//创建并初始化背景地图中要显示的字符信息  
MapData BorderNode = {"□", 1, 0x4};    //用类型为1的颜色为砖红色(0x4)的"□"字符代表围墙节点
MapData BackgroundNode = {"■", 2, 0x2};//用类型为2的颜色为绿色(0x2)的"■"字符代表活动区域节点
MapData SnakeNode = {"■", 3, 0xb};     //用类型为3的颜色为浅绿(0xb)的"■"字符代表贪吃蛇节点
MapData FoodNode = {"●", 4, 0xc};      //用类型为4的颜色为红色(0xc)的"●"字符代表食物节点
  • 全局地图与蛇地图
    为了提高我们食物生成函数的效率以及更方便地判断贪吃蛇的是否吃到食物或死亡,我们构建了全局地图与蛇地图数据类型。
    小白入门 C语言版贪吃蛇 (三个字 “好看”)_第8张图片
    全局地图是指包含围墙以及围墙内的所有节点所组成的地图,可用二维数组GlobalMapInfo[x][y](x,y∈{0,30})索引地图任意节点位置;而蛇地图是指仅由蛇节点组成的线性地图,可用一维数组SnakePos[z](z∈{0,783})索引,通过记录并不断更新蛇头SnakeHeadPos与蛇尾SnakeTailPos下标,即可得到当前蛇在蛇地图中的位置以及长度,且其最大长度为蛇可活动区域的节点个数,即784((MAPW-2)*(MAPH-2))。
//构建存储全局地图各节点的类别及索引的数据类型
typedef struct{
    char Type;//存放全局地图上每一节点对应的类型(1=围墙,2=活动区域,3=贪吃蛇,4=食物)
    int  Pos;//Pos为活动区域上每一个点所对应的索引数(范围0~783),不理解的先跳过,后面有图哦
}GlobalMap;

GlobalMap GlobalMapInfo[MAPW][MAPH];//创建全局地图

//贪吃蛇有效活动范围地图的索引
typedef struct{
    int X;//节点行坐标
    int Y;//节点列坐标
}SnakeMap;

SnakeMap SnakePos[(MAPW-2)*(MAPH-2)]; //创建蛇地图
  • 其他变量
HANDLE HandleStdin;  				//控制台句柄
int Scores = 0;  					//记录得分情况
int SnakeMapLen = (MAPW-2)*(MAPH-2);//蛇的活动区域长度 
int SnakeHeadPos, SnakeTailPos;     //蛇头蛇尾对应的SnakeMap中的索引(下标)
char name[20];//保存玩家姓名
int  speed=500;//控制蛇移速 
  • 设置光标位置和文字颜色函数
//设置光标位置,X为行,Y为列 
void SetPosition(int X, int Y){
    COORD Coord; 
    Coord.X = 2*Y;//Coord定义的行列与我们相反,又我们输出的是宽字符,故一次移动两个位置
    Coord.Y = X;
    SetConsoleCursorPosition(HandleStdin, Coord);
}

//设置颜色
void SetColor(int Color){
    SetConsoleTextAttribute(HandleStdin, Color);
}

下面我们结合程序代码进一步的做详细的解释,希望你能够理解并掌握整个代码的逻辑!

  1. 游戏欢迎界面
    小白入门 C语言版贪吃蛇 (三个字 “好看”)_第9张图片
    欢迎界面由方形边框以及必要提示文字组成,同时记录玩家姓名,以提高参与者的趣味性。代码详细注释如下,比较简单,故不在叙述。

    /********************************
    函数功能:打印游戏开始界面并记录玩家姓名
    参数 char *name:存放玩家姓名的数组首地址
    ********************************/
    void welcome(char *name) 
    {	
    	int x,y;//x表示控制台横坐标,y表示控制台纵坐标   (x,y)表示控制台上某个确定位置
    	HandleStdin = GetStdHandle(STD_OUTPUT_HANDLE);//获取控制台标识符,不懂的前文有介绍
        system("mode con: cols=96 lines=31");//设置控制台窗口大小,懂得意思就可以,不必完全搞懂
    	SetColor(0xb);//设置打印字符颜色为浅绿
        //start--------------------------------------------
        //在控制台相应位置处打印方形边框
    	for(x=8;x<21;x++){ 
    		for(y=9;y<38;y++){
    			if(x==8||x==20||y==9||y==37){
    				SetPosition(x,y);//设置控制台光标位置
    				printf("■");//打印方形字符	
    			}
    		}
    	} 
        //----------------------------------------------end
    	SetPosition(10,15);//设置控制台光标位置
    	printf("WELCOME TO THE GAME OF RETRO SNAKE\n");
    	SetPosition(14,10);//设置控制台光标位置
    	printf(" 请在英文输入法中操作 WSAD控制蛇身移动 空格键暂停游戏\n");
    	SetPosition(18,17);//设置控制台光标位置
    printf("PLEASE ENTER YOUR NAME:");
    	scanf("%s", name);//输入玩家姓名
    	system("cls"); //清空控制台屏幕
    }
    
  2. 游戏地图界面(包括墙、活动区域以及)
    小白入门 C语言版贪吃蛇 (三个字 “好看”)_第10张图片
    上图就是我们生成的贪吃蛇游戏的初始界面,该界面可以分成地图显示与文字显示两大部分。地图显示部分以红色方框为围墙,绿色方块为绿地,浅绿方块为蛇,红色圆点为食物共同构成游戏地图;文字显示部分通过输出玩家姓名(红色方框处)、得分情况以及必要提示信息来提升玩家与游戏的交互体验。

    /********************************
    函数功能:生成初始游戏地图
    参数 char *name:存放玩家姓名的数组首地址
    ********************************/
    void SnakeGameInit(char *name){
        //start---------------------------------------
        // 设置相关变量 
        int X, Y , Pos;//X表示控制台行标号,X表示列标号,Pos为绿色区域每一个点所对应的索引数
        int Center_X = MAPH%2 ? MAPH/2+1 : MAPH/2;//地图中心位置的X坐标,MAPH表示地图高度
        int Center_Y = MAPW%2 ? MAPW/2+1 : MAPW/2;//地图中心位置的Y坐标,MAPW表示地图宽度
        CONSOLE_CURSOR_INFO cci;//创建一个包含控制台光标信息的结构体变量
        //------------------------------------------end
        
        //隐藏闪烁的光标,固定操作,会用就行,想了解的可以自己查查
        GetConsoleCursorInfo(HandleStdin, &cci);
        cci.bVisible = 0;
        SetConsoleCursorInfo(HandleStdin, &cci);
       
        //判断地图大小是否合理,防止地图太小
        if(MAPW<16){//若地图宽度小于16
            printf("'MAXWIDTH' is too small!");
            //获取任意字符后退出
            getch();
            exit(0);
        }
    	//start------------------------------------------------------
        //打印地图以及保存地图对应位置的字符类型 
        for(X=0; X<MAPH; X++){//控制台行循环 
            for(Y=0; Y<MAPH; Y++){//控制台列循环 
                //如果满足以下条件,表示该位置应该显示围墙,即需要打印砖红色方框字符
                if(Y==0 || Y==MAPW-1 || X==0 || X==MAPH-1){ 
                    //设置该节点为墙类型
                    GlobalMapInfo[X][Y].Type = BorderNode.Type;
                    //设置该节点为墙颜色
                    SetColor(BorderNode.Color);
                    printf("%s", BorderNode.Char);
                }
                //否则该位置为蛇的活动区域,即应该打印绿色方块字符 
                else{
                    //计算蛇活动区域Pos位置索引(范围0~783)
                    Pos = (X-1)*(MAPW-2)+(Y-1);
                    //建立Pos与[x,y]的互相对应关系
                    SnakePos[Pos].X= X;
                    SnakePos[Pos].Y= Y;
                    GlobalMapInfo[X][Y].Pos = Pos;
                    //设置该节点为活动区域类型
                    GlobalMapInfo[X][Y].Type = BackgroundNode.Type;
                    //设置该节点为活动区域颜色
                    SetColor(BackgroundNode.Color);
                    printf("%s", BackgroundNode.Char);
                }
            }
            printf("\n");
        }
        //--------------------------------------------------------end
        
        //--------------------------初始化贪吃蛇-----------------------
        //设置中心位置的三个节点为蛇节点类型
        GlobalMapInfo[Center_X][Center_Y-1].Type = GlobalMapInfo[Center_X][Center_Y].Type = GlobalMapInfo[Center_X][Center_Y+1].Type = SnakeNode.Type;
    	//保存蛇头节点的Pos位置
        SnakeHeadPos = (Center_X-1)*(MAPW-2)+(Center_Y-1) - 1;
        //保存蛇尾节点的Pos位置
        SnakeTailPos = SnakeHeadPos+SNAKELEN-1;
        //设置输出蛇字符起始位置
        SetPosition(Center_X, Center_Y-1);
     //设置节点为蛇类型颜色
        SetColor(SnakeNode.Color);
        //打印三个蛇节点
        for(Y = Center_Y-1; Y<=Center_Y+1; Y++){
            printf("%s", SnakeNode.Char);
        }
        //--------------------------------------------------------end
        CreateFood();//生成食物函数,这块先略过,后面会单独讲
    
        //打印必要的显示信息
        SetColor(0xb);//设置输出文字颜色	
    	SetPosition(Center_X-3, MAPW+1);//设置输出文字起始位置
    	printf("Hello %s,Welcome to Play",name);
        SetPosition(Center_X-1, MAPW+1);//设置输出文字起始位置
        printf("Your Score is:  0   \\(*▽*)/");
        SetPosition(Center_X+1, MAPW+1);//设置输出文字起始位置
    	printf("This Game is Created by XiaoMi");
    	SetPosition(Center_X+3, MAPW+1);//设置输出文字起始位置
        printf("Press the Space to Pause the Game");
        //记录控制台显示得分的位置,后续对该位置进行更新即可
        ScoresPos.X = Center_X-1;
        ScoresPos.Y = MAPW+9;
    }
    
  3. 获取蛇移动方向

    通过以下函数对实时输入的贪吃蛇移动方向进行调整,以让其满足游戏规则。

    /********************************
    函数功能:判断并返回蛇移动方向
    参数 char NewDirection:当前输入方向
    参数 char OldDirection:上次输入方向
    ********************************/
    char NextDirection(char NewDirection, char OldDirection){
        int NoBack = NewDirection+OldDirection;//蛇后退标志
        NewDirection = tolower(NewDirection);//变输入字符为小写
        //若当前按下按键为方向键并且与上次方向不相反,则返回当前按键输入
        if( (NewDirection=='w' || NewDirection=='a' || NewDirection=='s' || NewDirection=='d') && NoBack!='a'+'d' && NoBack!='w'+'s' ){
            return NewDirection;
        }
        else{//否则返回上次输入方向
            return OldDirection;
        }
    }
    
  4. 蛇移动方向控制及判断蛇是否吃到食物或撞到自身或墙壁(难点)

    其实,移动贪吃蛇并不需要移动所有节点,只需要添加蛇头、删除蛇尾,就会有动态效果,而且这样也会大大提高程序的效率。
    小白入门 C语言版贪吃蛇 (三个字 “好看”)_第11张图片
    在地图初始化时,我们对全局地图与蛇地图也建立了对应关系。并设置了两个变量 SnakeHeadPos、SnakeTailPos,分别用来表示蛇头、蛇尾在数组中的下标位置,这样每次添加蛇头、删除蛇尾时只需要改变两个变量的值就可以。

    如上图所示,SnakeHeadPos和 SnakeTailPos每次向前移动一个位置,也就是每次减1。如果 SnakeHeadPos=0,也就是指向数组的头部,那么下次移动时 SnakeHeadPos= arrayLength - 1,也就是指向数组的尾部,就这样一圈一圈地循环,SnakeTailPos也是如此。这相当于把数组首尾相连成一个圆圈,贪吃蛇在这个圆圈中不停地转圈。

    这样一来在SnakeHeadPos与SnakeTailPos之间的都是蛇节点,每一个蛇节点都保存着其在全局地图中对应的(X,Y)坐标。随着贪吃蛇的不断移动和变长,我们必须不断维护更新 GlobalMap 和 SnakeMap 之间的对应关系。由于贪吃蛇每次的移动方向都有多种可能,这也导致了GlobalMap 和 SnakeMap 的对应关系表会越来越复杂,不过我们并不用太关心这些问题,下面还是看看程序理解下吧。

    /********************************
    函数功能:蛇移动控制
    参数 char Direction:蛇移动方向
    ********************************/
    void Move(char Direction){
        //全局地图中新蛇头节点坐标,就是蛇每移动一次后在全局地图中得到的新蛇头节点所对应的坐标
        int NewSnakeHeadPos_X, NewSnakeHeadPos_Y; 
        //新蛇头节点中存放的对应于蛇地图的Pos位置
        int OldIndex_In_NewSnakeHeadPos;  
        //蛇地图中新蛇头(SnakeHeadPos-1)位置处存储的对应全局地图中的坐标
        int OldPosX_In_NewSnakeHeadIndex, OldPosY_In_NewSnakeHeadIndex;
        int NewSnakeHead_OldType; 				   //新蛇头的旧类型(全局地图中)
        int OldSnakeTailPos_X, OldSnakeTailPos_Y;  //老蛇尾坐标
        //根据移动方向确定新蛇头的坐标
        switch(Direction){
            case 'w':
                NewSnakeHeadPos_X = SnakePos[SnakeHeadPos].X-1;
                NewSnakeHeadPos_Y = SnakePos[SnakeHeadPos].Y;
                break;
            case 's':
                NewSnakeHeadPos_X = SnakePos[SnakeHeadPos].X+1;
                NewSnakeHeadPos_Y = SnakePos[SnakeHeadPos].Y;
                break;
            case 'a':
                NewSnakeHeadPos_X = SnakePos[SnakeHeadPos].X;
                NewSnakeHeadPos_Y = SnakePos[SnakeHeadPos].Y-1;
                break;
            case 'd':
                NewSnakeHeadPos_X = SnakePos[SnakeHeadPos].X;
                NewSnakeHeadPos_Y = SnakePos[SnakeHeadPos].Y+1;
                break;
        }
        //新蛇头位置索引 
        SnakeHeadPos = SnakeHeadPos==0 ? SnakeMapLen-1 : SnakeHeadPos-1;
        //start-------------------------------------------------------------------
        //更新全局地图与蛇地图的对应关系,保证蛇地图的Pos位置与全局地图中蛇活动区域的(x,y)一一对应
        //新蛇头坐标对应的旧索引
        OldIndex_In_NewSnakeHeadPos = GlobalMapInfo[NewSnakeHeadPos_X][NewSnakeHeadPos_Y].Pos;
        //新蛇头索引对应的旧坐标
        OldPosX_In_NewSnakeHeadIndex = SnakePos[SnakeHeadPos].X;
        OldPosY_In_NewSnakeHeadIndex = SnakePos[SnakeHeadPos].Y;
    
        //双向绑定新蛇头索引与坐标的对应关系
        SnakePos[SnakeHeadPos].X = NewSnakeHeadPos_X;
        SnakePos[SnakeHeadPos].Y = NewSnakeHeadPos_Y;
        GlobalMapInfo[NewSnakeHeadPos_X][NewSnakeHeadPos_Y].Pos = SnakeHeadPos;
        //双向绑定旧的索引与坐标的对应关系
        SnakePos[OldIndex_In_NewSnakeHeadPos].X = OldPosX_In_NewSnakeHeadIndex;
        SnakePos[OldIndex_In_NewSnakeHeadPos].Y = OldPosY_In_NewSnakeHeadIndex;
        GlobalMapInfo[OldPosX_In_NewSnakeHeadIndex][OldPosY_In_NewSnakeHeadIndex].Pos = OldIndex_In_NewSnakeHeadPos;
    
        //新蛇头的旧类型
        NewSnakeHead_OldType = GlobalMapInfo[NewSnakeHeadPos_X][NewSnakeHeadPos_Y].Type;
        //设置新蛇头类型
        GlobalMapInfo[NewSnakeHeadPos_X][NewSnakeHeadPos_Y].Type = SnakeNode.Type; 
        //--------------------------------------------------------------------end
        
        // 判断是否出界或撞到自己
        if(NewSnakeHead_OldType==BorderNode.Type || NewSnakeHead_OldType==SnakeNode.Type ){
            Die();//打印死亡界面
        }
        //输出新蛇头
        SetPosition(NewSnakeHeadPos_X, NewSnakeHeadPos_Y);
        SetColor(SnakeNode.Color);
        printf("%s", SnakeNode.Char);
        //判断是否吃到食物
        if(NewSnakeHead_OldType==FoodNode.Type){  //吃到食物
            if(speed>=50){//加快蛇移动速度 
            	speed-=10;
            }
            CreateFood();//重新生成食物 
            //更改分数
            SetPosition(ScoresPos.X, ScoresPos.Y);
            printf("%d", ++Scores*10);
        }else{
            //老蛇尾坐标
            OldSnakeTailPos_X = SnakePos[SnakeTailPos].X;
            OldSnakeTailPos_Y = SnakePos[SnakeTailPos].Y;
            //删除蛇尾
            SetPosition(OldSnakeTailPos_X, OldSnakeTailPos_Y);
            SetColor(BackgroundNode.Color);
            printf("%s", BackgroundNode.Char);
            GlobalMapInfo[OldSnakeTailPos_X][OldSnakeTailPos_Y].Type = BackgroundNode.Type;
            SnakeTailPos = (SnakeTailPos==0) ? SnakeMapLen-1 : SnakeTailPos-1;
        }
    }
    
  5. 生成新的食物

    注解我已经尽力写的很详细了,基本看注解就可以,不懂得可以评论问,看到了我都会回答。

    /********************************
    函数功能:生成新的食物
    ********************************/
    void CreateFood(){
        int Pos, Rang, X, Y;
        //产生随机数,确定 SnakePos 数组的索引 
        srand((unsigned int)time(NULL));//引用当前时间作为随机数种子
    	//--------------------------随机生成食物位置------------------------- 
        //当SnakePos数组中蛇的位置像{X X X 尾。。。。。头 X X}时 
        if( SnakeTailPos < SnakeHeadPos ){
            Rang = SnakeHeadPos-SnakeTailPos-1;//不属于蛇的节点数 
            //让生成的食物落在 [尾-头] 之间的非蛇节点位置 
            Pos  = SnakeTailPos + 1 + rand()%Rang;
        }else{//当SnakePos数组中蛇的位置像{。。。头 X X X X 尾。。}时 
            Rang = SnakeMapLen - (SnakeTailPos - SnakeHeadPos+1);//不属于蛇的节点数  
            Pos = rand()%Rang;//先随机生成一个食物位置
            //若该位置与蛇身节点重合,则以该位置为基础,向前再移动一个当前蛇身长度
            if( Pos >= SnakeHeadPos ){
                //由于 Pos(最大=Rang)+当前蛇身长度 始终小于SnakeMapLen,故不用担心数组越界
    			Pos += (SnakeTailPos-SnakeHeadPos+1);
            }
        }
        //获取Pos位置对应的X、Y坐标 
        X = SnakePos[Pos].X;
        Y = SnakePos[Pos].Y;
        //设置光标位置和食物颜色 
        SetPosition(X,Y);
        SetColor(FoodNode.Color);
        printf("%s",FoodNode.Char);//打印食物 
        GlobalMapInfo[X][Y].Type=FoodNode.Type;//记录该节点类型到全局地图中 
    }
    
  6. 游戏结束界面

    和欢迎界面大同小异,很简单,我相信应该不用我解释了。

    /********************************
    函数功能:输出死亡信息 
    ********************************/
    void Die(){
        int x,y;
    	HandleStdin = GetStdHandle(STD_OUTPUT_HANDLE);//控制台句柄 
        //设置窗口大小 
        system("cls"); //清空控制台 
    	SetColor(0xb);
    	for(x=8;x<21;x++){//打印边框 
    		for(y=9;y<38;y++){
    			if(x==8||x==20||y==9||y==37){
    				SetPosition(x,y);
    				printf("■");	
    			}
    			
    		}
    	} 
    	SetPosition(10,14);
    	printf("You Die !!! GAME OVER     o(* ̄▽ ̄*)o");
    	SetPosition(14,19);
    	printf("Your Score is %d !",Scores*10);
    	SetPosition(18,15);
    	printf(" 还不错哦      继续努力O(∩_∩)O\n");
    	getch();
    	SetPosition(25,20);
    	exit(0);
    }
    

    最后,将以上函数写到主函数里,这样就大功告成了,哈哈,真是简单,有么有。

    int main(){
    	welcome(name);//欢迎页面 
        char Input, Direction = 'a';//默认贪吃蛇移动方向为左
        SnakeGameInit(name);//初始化游戏地图 
     	Input = tolower(getch());//等待按键输入 
        Direction = NextDirection(Input, Direction);//按照游戏规则修正移动方向并返回
        while(1){//死循环
            if(kbhit()){//检测是否有按键按下
                Input = tolower(getch());//取出字符,并变成小写
                if(Input == ' '){//空格暂停游戏 
                    Input = pause();
                }
                Direction = NextDirection(Input, Direction);//按照游戏规则修正移动方向并返回
            }
            Move(Direction);//蛇移动方向控制及判断蛇是否吃到食物或撞到自身或墙壁
           	Sleep(speed);//控制蛇移速,每吃到一次食物,蛇的移动速度就会加快一点
        }
        return 0;
    }
    

你可能感兴趣的:(小白入门 C语言版贪吃蛇 (三个字 “好看”))