0


纯C实现的贪吃蛇(无EasyX,详解)

💦前言

或许厌倦了枯燥的做题,那就学学贪吃蛇,激发你的学习乐趣吧~
你将进一步加深对结构体,单链表,函数,循环等基础的理解。
希望对你有所帮助~

在这里插入图片描述

纯C实现的贪吃蛇🐍

🍎代码效果–视频

纯c语言实现的贪吃蛇小游戏

🍑学习新函数,让你的代码变得"高大上"~

提前说明必须是要

.cpp

后缀文件才可以使用,

.c

文件不支持。
因为这里的头文件中包含了c++的内容。

🍑任意位置输出

暂且学会如何使用,深入的理解对于现在的我来说还差了些。所以我只会讲如何使用。

#include<windows.h>#include<iostream>

以下代码粘贴使用即可

坐标说明

💡重点注意事项

要注意中文字符、图形,在控制台显示的时候x和y的比例使2:1! 比如:打印

长是2,宽是1。

在这里插入图片描述

voidPos(int x,int y){
    COORD pos;
    HANDLE hOutput;
    pos.X = x;//水平方向
    pos.Y = y;//垂直方向
    hOutput =GetStdHandle(STD_OUTPUT_HANDLE);//获取缓冲区中的数据,地址赋值给句柄SetConsoleCursorPosition(hOutput, pos);/*定位光标位置的函数,坐标为GetStdHandle()返回标准的输出的句柄,
    也就是获得输出屏幕缓冲区的句柄,并赋值给对象 pos*//*隐藏光标操作 */
    CONSOLE_CURSOR_INFO cursor;
    cursor.bVisible = FALSE;
    cursor.dwSize =sizeof(cursor);SetConsoleCursorInfo(hOutput,&cursor);}intmain(){Pos(2,2);printf("hello world");return0;}

💡使用说明

先给定你想要输出的位置,在去打印。

在这里插入图片描述
更多知识百度一下哦

🍑颜色的设置

voidtest(){
    HANDLE consolehwnd;//创建句柄
    consolehwnd =GetStdHandle(STD_OUTPUT_HANDLE);//实例化句柄,从缓冲区中获取数据SetConsoleTextAttribute(consolehwnd, FOREGROUND_INTENSITY | FOREGROUND_RED/*字体颜色*/);//设置字体颜色printf("hello");SetConsoleTextAttribute(consolehwnd, FOREGROUND_INTENSITY | FOREGROUND_GREEN);printf("world!\n");getchar();SetConsoleTextAttribute(consolehwnd, BACKGROUND_INTENSITY | BACKGROUND_BLUE/*背景颜色*/);printf("It is really beautiful!\n");}intmain(){test();return0;}

💡使用说明

运行效果

在这里插入图片描述
在这里插入图片描述

💡颜色

颜色那部分可以使用16进制或者10进制的数代替。
看下面的图片选取你想要的颜色

16进制表示的时候,前面一个数字代表字体背景的颜色,后面一个数字代表字体的颜色。比如0x0a意思是0:字体的背景颜色是黑色,a:字体的颜色是绿色。

在这里插入图片描述
百度一下获取更多知识哦

🍑获取字符的新函数

getch()

不需要按回车,直接获取你在键盘上输入的字符。

kbhit()

功能及返回值: 检查当前是否有键盘输入,若有则返回一个非0值,否则返回0。

如果直接使用报错的话,请添加下面的一句话

#pragma warning(disable: 4996)

作用:屏蔽4996报错

贪吃蛇🐍

在三子棋和五子棋的博客中有介绍到头文件,在关键字 一中详细介绍了多文件,以及头文件。

所以这里我就不在赘述了。
多文件的使用可以使函数归类,看起来更舒服一点。

🌴游戏思路

个人思路仅供参考

自己写的时候先把游戏框架打出来,在具体到内部的实现。

一开始的游戏效果–>游戏界面的设置–>蛇的创建–>食物的创建–>障碍物的创建–>游戏实现。

我的游戏思路:①局部–②整体–③局部。

①先将细小的“动作”(移动,吃食物)写出来–②程序运行过程,函数位置+按键操作–③按键操作,判断(输赢比较),运行调试修修改改。

一开始的游戏效果和游戏界面的设置我就不讲了,大家各有所爱,凭空想向即可。

还有一句要说的,多分装成函数。
根据功能分装,找错的时候,会很方便

🌱蛇的创建

我知道的两种形式,一种是二维数组,一种是单链表方式。
这里介绍的是单链表的方式。

不怎么会的可以看下这篇博客单链表的创建和删除

Snake* sHead;//全局变量
Snake* sEnd;//全局变量voidCreatSnake(){
    sHead =AllocSnakeHead();
    sEnd = sHead;int i =3;while(i--){InitSnake(&sEnd);
        SnakeLenth++;}ShowSnake(sHead);}

🌲先来一个结构体

typedefstructSnake{int x;//x,y 蛇的位置int y;constchar* head;constchar* body;structSnake* next;//链接蛇身(单链表的操作)}Snake;

那些图形符号的类型是

const char*[3]

,类型需要匹配,所以需要加const

在这里插入图片描述

🌴创建蛇头

这个地方有点讲究,你设计的巧妙的话你后面会省事很多。暂且先不讲,时机未到。

这里的蛇头就是作头节点

还需要提到的就是坐标要设置成偶数,控制台长和宽比例是2:1

不然你会出现这种情况,对应的食物的坐标也是如此。

在这里插入图片描述

Snake*AllocSnakeHead(){
    Snake* p =(Snake*)malloc(sizeof(Snake));//蛇头
    p->x =rand()% Diff_x + Dif_x;
    p->y =rand()% Diff_y + Dif_y;while(p->x %2)//x的坐标要为偶数{
        p->x =rand()% Diff_x + Dif_x;}
    p->head ="⊙";
    p->next =NULL;return p;}
rand() % Diff_x + Dif_x

rand()

应该不陌生吧,创建随机数用的,需要一个随机种子

srand((unsigned int )time(NULL))

,

Diff_x , Dif_x

这两个是在头文件中,通过宏定义来表示一个具体的数字

🌱创建蛇身

蛇身的创建,其实就是链表的创建。
你可采用头插入,也可以采用尾插入。

尾插入创建的时候,需要记录尾指针的指向(利于对蛇身初始化,后续操作中用尾指针会更方便,但我自己写的时候忘记了)因此需要传地址,所以需要二级指针去接收。

要和蛇头连上,一开始尾指针要指向蛇头

voidInitSnake(Snake** sEnd){
    Snake* p =(Snake*)AllocSnakeBody((*sEnd)->x,(*sEnd)->y);(*sEnd)->next = p;//sHead->next = p*sEnd = p;}

🌲蛇身初始化

Snake*AllocSnakeBody(int x,int y){
    Snake* p =(Snake*)malloc(sizeof(Snake));
    p->x = x -2;
    p->y = y;
    p->body ="●";
    p->next =NULL;return p;}

🌴展示蛇身

本质上就是打印链表。

voidShowSnake(Snake* sHead){
    Snake* p = sHead;int flag =1;while(p){gotoxy(p->x, p->y);//在哪个位置输出if(flag){SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x03);//颜色设置printf("%s", p->head);
            flag =0;}else{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x07);printf("%s", p->body);}
        p = p->next;}}

🍔食物、障碍物的创建

创建的思路和蛇创建的过程一样,这里就不在赘述了。

🚲蛇的移动 一

先不考虑按键的问题。

🍳步长设置

步长是多少?还记得说比例吗?2:1,在设置的时候,x方向以2为单位,y方向以1为单位去设置。

蛇头是在偶数位置,每次在x方向上移动步长是偶数的,食物位置在设置的时候也是设成偶数的,那吃食物的时候,就不会出现吃到半个的情况了。

🛴移动

蛇在动的时候,只要蛇头动。

创建新蛇头给上一个新位置的坐标,不就完成了蛇头动吗?
蛇身+旧蛇头的坐标不动,但是长度又不能改变因此最后一个蛇的身体,需要被释放掉。

蛇头只能存在一个,那旧蛇头转化为身体就可以喽

刚刚的疑惑是时候解开了。
刚刚不是写了蛇头的创建吗?可不可以再次使用呢?

当然可以,移动的时候涉及到坐标,那我们是不是需要改动一下函数参数呢?必须要。

📜改动蛇头的创建与移动搭配

conut

是全局变量,标记第一次使用和第二次以上使用。

//原有的调用需要改动int conut =0;//全局变量
Snake*AllocSnakeHead(int x,int y){
    Snake* p =(Snake*)malloc(sizeof(Snake));//蛇头//第一次if(!conut){
        p->x =rand()% Diff_x + Dif_x;
        p->y =rand()% Diff_y + Dif_y;while(p->x %2)//x的坐标要为偶数{
            p->x =rand()% Diff_x + Dif_x;}
        conut =1;}else//移动后的坐标{
        p->x = x;
        p->y = y;}
    p->head ="⊙";
    p->next =NULL;return p;}

删除最后一个蛇尾,本质是释放的过程。

但是原有的位置上还会残留之前的“印记”(蛇尾),还需要一个清理的工作,即将释放的尾巴处清空也就是打印空格

这里

Move

的形参

x,y

接收的是具体按某个键移动后的位置(坐标)

DeleBody

的的形参

x,y

是被释放的蛇尾坐标

//清理尾巴voidDeleBody(int x,int y){gotoxy(x, y);printf("  ");}//移动过程的实现voidMove(int x,int y){
     Snake* p =AllocSnakeHead(x, y);//新的头
     Snake* t = sHead;
     Snake* t1 = t->next;//记录旧的头

     t->body ="●";//原来的头变为身体

     p->next = t;//链接
     sHead = p;//新的头赋值给sHead//去掉最后一个节点while(t1->next){
         t = t->next;
         t1 = t1->next;}DeleBody(t1->x, t1->y);
     t->next =NULL;free(t1);
     p =NULL;}

🍿吃食物

吃完食物,蛇需要增长。 吃到食物你也可以打印一些东西,比如分数、一些文字之类的。

吃完食物我们不能不管了,还需要创建食物,你也可以增加点难度,添加障碍物的设置。

蛇的移动是靠打印显示出来的,这里的“吃”,本质上是覆盖,蛇头覆盖食物。

添加身体这部分,我写的比较复杂。

优化思路:尾插法。在设计尾指针时,用的是全局变量,所以尾指针创建完之后是指向尾巴的(即指向最后一个节点)。(使用尾指针可以把我这遍历最后一个节点的过程省略掉)

如果采用头插入的话,有些不方便。改动的东西有点多。每个蛇身的坐标都要改动,太复杂。

voidAddBody(){//也可以使用尾插入,会更简单。
    Snake* p = sHead;//找到最后的一个节点while(p->next){
        p = p->next;}
    Snake* t =AllocSnakeBody(p->x, p->y);
    p->next = t;}voidEatFoodSet(){//原来的食物,会被蛇头覆盖掉,因此是要添加蛇尾就可以。AddBody();//移动身体CreatFood();//产生新的食物
    play.score++;switch(play.score){case2:case4:case6:case8:case10:case12:case14:CreatObstacle();default:break;}gotoxy(68,13);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x05);printf("得分:  %d ( ̄︶ ̄) ", play.score);
    SnakeLenth++;}

🧁添加身体

Add()

函数中添加身体真能样写吗?我把

AllocSnakeBody()

函数放在这里,仔细考虑一下

Snake*AllocSnakeBody(int x,int y){
    Snake* p =(Snake*)malloc(sizeof(Snake));
    p->x = x -2;
    p->y = y;
    p->body ="●";
    p->next =NULL;return p;}

这里的是在刚刚创建的时候,这样做的。如果蛇运动起来那这样还是欠妥,为什么这么说呢看图。

在这里插入图片描述

如果食物在红色圆圈那,吃到食物后向

x-2

的地方去添加,那么墙会被新的蛇身覆盖掉因此不能这么简单的设置。
怎么考虑更全面呢?
我是这样考虑的,既然每面墙都要考虑到,我们不妨划分四个区域。
需要注意的是,添加身体的时候,是在蛇尾部分。

所以下面图中,红色圈圈表示蛇尾,那箭头是什么呢?我们还要把蛇运动的方向考虑进来,在边界上,蛇朝不同的方向运动添加的情况也是不同的。
箭头即表示蛇运动的方向

当蛇尾在左上角那个圈的时候,如果蛇是向右移动,那么在蛇尾添加新身体的坐标是x不变,y+1,如果蛇是向下移动,那么,蛇尾添加新身体的坐标是y不变,x+2
其他情况类似。

在这里插入图片描述

在创建地图的时候就把中间值可以用宏来代替。
蛇的方向,要在哪体现呢,

AllocSnakeBody()

如何改动才更好呢?

💧对蛇的方向做出的改动

typedefstructSnake{int x;//x,y 蛇的位置int y;constchar* head;constchar* body;structSnake* next;//链接蛇身(单链表的操作)int direction;}Snake;
voidCreatSnake(){
    sHead =AllocSnakeHead(0,0);
    sEnd = sHead;int i =3;while(i--){InitSnake(&sEnd);
        SnakeLenth++;}//蛇的随机方向
    sHead->direction =rand()%4+1;while(sHead->direction == LEFT){
        sHead->direction =rand()%4+1;}ShowSnake(sHead);}

💧对

AllocSnakeBody

的修改

通过参数z来选择执行哪种添加,传(1/0)

Snake*AllocSnakeBody(int x,int y,int z){
    Snake* p =(Snake*)malloc(sizeof(Snake));if(z){
        p->x = x -2;
        p->y = y;}else{Add(p ,x, y);}
    p->body ="●";
    p->next =NULL;return p;}voidAdd(Snake* p,int x,int y){if(x < Mid_x && y > Mid_y){if(sHead->direction == UP){
            p->x = x +2;
            p->y = y;}if(sHead->direction == RIGHT){
            p->x = x;
            p->y = y -1;}}elseif(x < Mid_x && y < Mid_y){if(sHead->direction == DOWN){
            p->x = x +2;
            p->y = y;}if(sHead->direction == RIGHT){
            p->x = x;
            p->y = y +1;}}elseif(x > Mid_x && y < Mid_y){if(sHead->direction == DOWN){
            p->x = x -2;
            p->y = y;}if(sHead->direction == LEFT){
            p->x = x;
            p->y = y +1;}}elseif(x < Mid_x && y < Mid_y){if(sHead->direction == UP){
            p->x = x -2;
            p->y = y;}if(sHead->direction == LEFT){
            p->x = x;
            p->y = y -1;}}}

🛫蛇的移动 二

这里是考虑按键后的移动。

设计的时候需要考虑到,蛇移动是不能往回走的。
我们知道移动是上下左右,设计的时候肯定会想到用数字去表示。

这里就能体现枚举的好处了
用枚举代替数字会更好。
你也可以选择用宏

enumMoveKey{
    LEFT =1,
    DOWN,
    RIGHT,
    UP,};

LEFT初始值赋值为1,那后面的值是依次以1递增的。值分别为1,2,3,4

移动之后,需要去打印,才能看到效果

voidSnakeMove(int x){//不可往相反的方向走switch(x){case UP:if(x != DOWN){Move(sHead->x,sHead->y -1);ShowSnake(sHead);}break;case DOWN:if(x != UP){Move(sHead->x, sHead->y +1);ShowSnake(sHead);}break;case LEFT:if(x != RIGHT){Move(sHead->x -2, sHead->y);ShowSnake(sHead);}break;case RIGHT:if(x != LEFT){Move(sHead->x +2, sHead->y);ShowSnake(sHead);}break;}}

🍓开始考虑整体

整个程序跑起来后,主要活动是靠按键,进行方向控制,设计的时候需要把握住这里。
移动不可能只移动一次,肯定需要用循环

GetKeyboard()

是按键操作的函数,通过这个函数的返回值,判断是继续,还是退出程序。同时也是通过这个函数,对蛇进行移动和操作。

GetKeyboard()

它的返回值看你如何设计,使它结束循环

不能忘记随机种子,把它在main函数的文件中。放在其他文件话,随机值可能不太妙

voidGame(){srand((unsignedint)time(NULL));//随机种子GameWelcome();//游戏欢迎界面GameDescription();//游戏说明while(1){DrawGameInterface();//绘制游戏运行界面if(GetKeyboard())//按键,进去游戏{break;}}}intmain(){Game();return0;}

🍎又局部—按键设计

这里就需要用上一开头上面讲的

getch()

kbhit()

下面那些数字75 80 72 70对应是键盘上的方向键(–>)这些键

真的如下所示吗?

intGetKeyboard(){while(1){
        key =getch();switch(key){case'a':case'A':case75:if(key1 != RIGHT)
                key = LEFT;SnakeMove(key);break;case's':case'S':case80:if(key1 != UP)
                key = DOWN;SnakeMove(key);break;case'd':case'D':case77:if(key1 != LEFT)
                key = RIGHT;SnakeMove(key);break;case'w':case'W':case72:if(key1 != DOWN)
                key = UP;SnakeMove(key);break;default:break;}}

🍉按键的细节事项

上面的代码看似通顺,跑起来的话还有很多问题的。

🍉细节一

蛇开始运动一开始是要运动的。

在创建蛇的时候是不是定了一个方向吗,这个时候可以派上用场了。

可以解释当初为何要这样设置了
具体代码如下

    sHead->direction =rand()%4+1;while(sHead->direction == LEFT){
        sHead->direction =rand()%4+1;}

这里的设置,需要看你实现的蛇头和蛇身的位置关系。
如果蛇头是在右边,蛇身是在左边,那么一开始自动走的时候,这个方向不能是左边,不然真的是落地成盒。

你也可以一开始把方向固定死了,同样也不要注意不能落地成盒。

🍉细节二

开篇有介绍到

kbhit()

函数,这里没有用合理嘛?
有人可能会疑惑,只要按键不就行了嘛,为什么还要

kbhit()

呢?

假设没有

kbhit()

去检测是否按键。那代码实现出来的效果是蛇的移动靠按键来完成。也就是说,只有按键了蛇移动,不按键蛇不移动,这和我们预期效果不符合。

我们要的效果是蛇自动走。按键只用来控制蛇方向(速度),蛇的移动是靠代码完成的。因此

kbhit()

是必须要用的。

这样的话,必须把

kbhit()

的返回值用起来(

kbhit()

的返回值是检测你有无按键,按键了返回非0的值,没按键返回0)怎么用?肯定是通过函数的返回值进行,条件判断。

🍉细节三

完成了上面所说的还不行哦。
是不是还需要考虑按键失败的情况呢?

仅仅需要判断按键是否合法,这样肯定还是不行。
为什么呢?

每次按键的话,(

key=getch()

)

key

的值都会刷新,去

switch

中进行匹配,如果按的键不是我们所需要的,尽管

default

出去了,但蛇是不会动的,那又如何继续呢?

为什么呢?因为key的值刷新了! 已经找不到原来的方向了!
所以我们需要新增一个变量

key1

记录前一次合法情况下运动的方向
什么时候使用

key1

呢?按键不匹配的时候都要用上。
同时也不能忘记更新蛇的方向

🔥不匹配的情况有哪些?

①按了和当前运动方向相反的键,比如蛇往上走,你按了向下的键

②按了其他键,也包括加速,减速,正常速的这些键。

🍉细节四

速度的设置

我们需要知道,这点代码对于电脑来说,太小了,一下就可以运行完。上面那样写,速度贼快,根本毫无游戏体验。
我们需要增加停屏来控制速度

#include<windows.h>Sleep(250);

里面数字的单位是毫秒

🍉细节五

蛇的移动函数放在哪?
上面有提到通过

kbhit()

的返回值来判断是否按键。
我们需要的效果是自动走。

什么情况下会自动走,我们没按键的情况下会自动走。
这不就和 细节 一中说的对应上了吗。蛇一开始随机向一个方向运动,通过

kbhit()

函数检测是否按键,没有按键,蛇运动方向不变,继续运动。

如果蛇开局不自动走,只有我们按了第一个方向键后蛇才会开始移动。
在明确一个点,就是按键是改变蛇的方向

🍉细节六

你可能会问能不能在

switch

也中放

SnakeMove(key)

,按照我写的这个逻辑,这样写会导致:你连续按某一个方向键时,会加速有加速效果。

int key =0;//当前方向的值int key1 =0;//只记录前一次蛇移动按键int j =0;//加速标记int i =0;//减速标记int o =1;//正常速度标记intGetKeyboard(){
    key = sHead->direction;while(1){//int b = kbhit();调试的时候检测用
        sHead->direction = key;if(kbhit()){//左右需要移动2个单位移动,上下1个单位移动if(key == UP || key == DOWN || key == LEFT || key == RIGHT)
                key1 = key;
            key =getch();switch(key){case'a':case'A':case75:if(key1 != RIGHT)
                    key = LEFT;else
                    key = key1;break;case's':case'S':case80:if(key1 != UP)
                    key = DOWN;else
                    key = key1;break;case'd':case'D':case77:if(key1 != LEFT)
                    key = RIGHT;else
                    key = key1;break;case'w':case'W':case72:if(key1 != DOWN)
                    key = UP;else
                    key = key1;break;case'j':case'J':
                j =1;//加速标记
                i =0;//减速标记
                o =0;//正常速度标记
                key = key1;break;case'I':case'i':
                j =0;//加速标记
                i =1;//减速标记
                o =0;//正常速度标记
                key = key1;break;case'o':case'O':
                j =0;//加速标记
                i =0;//减速标记
                o =1;//正常速度标记
                key = key1;break;default:
                key = key1;break;}}else//自动走的设置{SnakeMove(key);if(j)Sleep(150);if(i)Sleep(250);if(o)Sleep(200);}if(int ret =Judge())//判断是否吃到食物/是否死亡/赢{//返回值为真结束游戏 --- 死亡return ret;}}return0;}

🌱判断

什么时候判断呢?
当然是走一步判断一步,因此我们只要在移动后加上判断即可

局部代码

else//自动走的设置{SnakeMove(key);if(j)Sleep(150);if(i)Sleep(250);if(o)Sleep(200);}if(int ret =Judge())//判断是否吃到食物/是否死亡/赢{//返回值为真结束游戏 --- 死亡return ret;}
intJudge(){//判断输赢if(play.score == Win)returnJudgeWin();//判断是否撞墙if(sHead->x == Map_x_left +1|| sHead->x == Map_x_left ||
         sHead->x == Map_x_right -1|| sHead->x == Map_x_right ||
         sHead->y == Map_y_up || sHead->y == Map_y_down)returnJudgeHitWall();//判断是否迟到食物,吃到食物  将食物变为头,原来的头变为身体 -->Move函数if(sHead->x == food->x && sHead->y == food->y )returnJudgeFood();//判断是否撞到自己if(JudgeHitSelf()){;}else//是否撞到障碍物returnJudgeObstacle();return0;}

🌱判断什么

🌱判断赢

看你怎么设置,你可以根据得分去判断,也可以根据蛇的长度去判断

intJudgeWin(){gotoxy(68,16);//每一个输出对应位置要等长printf("游戏胜利      (●'-'●)       ");gotoxy(68,18);printf("成功获得蛇王的称号      ");gotoxy(68,20);printf("按Esc退出,按L继续");
    win =1;returnFinishSet();}

🌱判断吃到食物

voidAddBody(){
    Snake* p = sHead;//找到最后的一个节点while(p->next){
        p = p->next;}
    Snake* t =AllocSnakeBody(p->x, p->y,0);
    p->next = t;}voidEatFoodSet(){//原来的食物,会被蛇头覆盖掉,因此是要添加蛇尾就可以。AddBody();//移动身体CreatFood();//产生新的食物
    play.score++;switch(play.score){case2:case4:case6:case8:case10:case12:case14:CreatObstacle();//障碍物default:break;}gotoxy(68,13);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x05);printf("得分:  %d ( ̄︶ ̄) ", play.score);
    SnakeLenth++;}intJudgeFood(){EatFoodSet();//吃到食物设置return0;}

🌱判断撞墙

intJudgeHitWall(){returnFinishSet();}

🌱判断撞到自身/障碍物

两者实现的时候差不多,遍历链表 + 判断

撞自身的时候,是蛇头撞身体,所以需要从第一个节点开始判断

intJudgeHitSelf(){
    Snake* p = sHead->next;while(p){if(sHead->x == p->x && sHead->y == p->y)returnFinishSet();
        p = p->next;}return0;}intJudgeObstacle(){
    Obstacle* p = obstacle;while(p){if(sHead->x == p->x && sHead->y == p->y)returnFinishSet();
        p = p->next;}return0;}

💦结束设置

FinishSet()

这些代码重复出现分装成函数。
结束设置,要考虑结束游戏,或者重新开始。
重新开始的话,一些全局变量的值需要去改变,置为0
结束的话,很简单。

ObstacleConut

是全局变量,障碍物的个数。

intFinishSet(){if(ObstacleConut >0){
        Obstacle* p = obstacle->next;free(p);
        obstacle->next =NULL;}switch(win){case0:gotoxy(68,16);printf("游戏结束,继续加油 `(*>﹏<*)′");gotoxy(68,18);printf("==>按Esc退出,按L继续<==");gotoxy(68,20);printf("                  ");break;case1:break;}int b =getch();while(b){switch(b){case27:system("cls");return1;case'l':case'L':system("cls");
             conut =0;//蛇头创建处
             win =0;//输赢打印
             play.score =0;return2;default:break;}
         b =getch();}return0;}

💥又一细节

最后呢,需要提醒的是游戏重新开始的时候是要回到

Game()

函数中,通过循环去重新开始。尽量不要在

GetKeyboard()

中去执行

DrawGameInterface()

函数,执行的话会有点点问题。

voidGame(){srand((unsignedint)time(NULL));//随机种子GameWelcome();//游戏欢迎界面GameDescription();//游戏说明while(1){DrawGameInterface();//绘制游戏运行界面if(GetKeyboard())//按键,进去游戏{break;}}}

说到这,整个游戏的思路也就分析完了。

💤在说一些要注意的东西

💦多设置宏

在绘图的时候,数字用宏,这样写起来方便。
随机数设置也可以写成宏的形式

💦声明

如果你分装成多个文件的话,全局变量,以及函数都要申明一下。

💖获取自动的时间设置以及清屏操作

直接使用即可。

菜鸟教程更多记载

#include<stdio.h>#include<time.h>intmain(){time_t rawtime;structtm*info;char buffer[80];time(&rawtime );
 
   info =localtime(&rawtime );strftime(buffer,80,"%Y-%m-%d %H:%M:%S", info);printf("格式化的日期 & 时间 : |%s|\n", buffer );return(0);}

清屏

#include<windows.h>system("cls");

🎑完整代码

✨snake.h

#pragmaonce#include<stdio.h>#include<windows.h>#include<ctime>#include<string.h>#include<conio.h>//getch()函数的头文件#include<iostream>//输入、输出流的头文件#include<time.h>#include<stdlib.h>//屏蔽 4996 报错#pragmawarning(disable:4996)enumMoveKey{
    LEFT =1,
    DOWN,
    RIGHT,
    UP,};//蛇的属性typedefstructSnake{int x;//x,y 蛇的位置int y;constchar* head;constchar* body;structSnake* next;//链接蛇身(单链表的操作)int direction;}Snake;//食物typedefstructFood{int x;//x,y 食物的位置int y;constchar* head;}Food;//障碍物属性 随着分数的增加而增加typedefstructObstacle{int x;//x,y 障碍物的位置int y;constchar* body;structObstacle* next;//5}Obstacle;typedefstructPlayer{int score;}Player;//坠落星星的下标参数typedefstructStar{int x;int y;}Star;//全局变量externint j;//加速标记externint i;//减速标记externint o;//正常速度标记externint win;//输赢的判断extern Player play;//玩家属性externint SnakeLenth;//蛇长度extern Snake* sHead;//蛇头指针extern Snake* sEnd;//蛇尾指针创建的时候用extern Food* food;//食物extern Obstacle* obstacle;//障碍物externint conut;//AllocSnakeHead函数中的全局变量 添加身体所用externint ObstacleConut;//障碍物个数externint flagob;// 界面大小设置// 地图  #defineMap_x_left2#defineMap_x_right52#defineMid_x25#defineMap_y_up2#defineMap_y_down29#defineMid_y12//获胜个数#defineWin20//随机值的产生//蛇#defineDiff_x(Map_x_right - Map_x_left -4*2)//头+身 -- 4#defineDif_x(4*2+ Map_x_left)#defineDiff_y(Map_y_down  - Map_y_up -1)#defineDif_y(Map_y_up +1)//食物#defineFoodd_x(Map_x_right - Map_x_left -3)//4-50-->0-46-->47#defineFood_x4#defineFoodd_y(Map_y_down - Map_y_up -2)#defineFood_y4// 欢迎/说明 界面#defineInfa_x_left38//20#defineInfa_x_right78//60#defineInfa_y_up2#defineInfa_y_down23//玩家得分界面#definePlay_x_left60#definePlay_x_right98#definePlay_y_up9#definePlay_y_down22//函数声明//从指定位置输出externvoidgotoxy(int x,int y);//刚进入游戏的界面externvoidGameWelcome();//游戏说明externvoidGameDescription();//绘制游戏运行界面 (地图、玩家属性界面)externvoidDrawGameInterface();//从键盘获取信息externintGetKeyboard();//按键externintGetKeyboard();//Creat中函数声明extern  Snake*AllocSnakeHead(int x,int y);extern  Snake*AllocSnakeBody(int x,int y,int z);externvoidInitSnake(Snake** sEnd);externvoidShowSnake(Snake* sHead);externvoidCreatSnake();extern Food*AllocFood(Snake* sHead);externvoidShowFood(Food* food);externvoidCreatFood();externvoidShowObstacle(Obstacle* obstacle);extern Obstacle*AllocObstacle();externvoidInitObstacle(Obstacle* obstacle);externvoidCreatObstacle();//Paint中函数externvoidgotoxy(int x,int y);externvoidgotopaintWel(int x,int y);externvoidgotopaintDes(int x,int y);externvoidgotopaintWall(int x,int y);externvoidgotopaintPler(int x,int y);externvoidPaintInterface();externvoidGameWelcome();externvoidGameDescription();externvoidPaintWall();externchar*Gettime();externvoidPlayInfaAttr();externvoidPaintPlayInfa();

✨Creat.cpp

#include"snake.h"//初始化蛇头/蛇身int conut =0;
Snake*AllocSnakeHead(int x,int y){
    Snake* p =(Snake*)malloc(sizeof(Snake));//蛇头//第一次if(!conut){
        p->x =rand()% Diff_x + Dif_x;
        p->y =rand()% Diff_y + Dif_y;while(p->x %2)//x的坐标要为偶数{
            p->x =rand()% Diff_x + Dif_x;}
        conut =1;}else{
        p->x = x;
        p->y = y;}
    p->head ="⊙";
    p->next =NULL;return p;}//初始化蛇身voidAdd(Snake* p,int x,int y){if(x < Mid_x && y > Mid_y){if(sHead->direction == UP){
            p->x = x +2;
            p->y = y;}if(sHead->direction == RIGHT){
            p->x = x;
            p->y = y -1;}}elseif(x < Mid_x && y < Mid_y){if(sHead->direction == DOWN){
            p->x = x +2;
            p->y = y;}if(sHead->direction == RIGHT){
            p->x = x;
            p->y = y +1;}}elseif(x > Mid_x && y < Mid_y){if(sHead->direction == DOWN){
            p->x = x -2;
            p->y = y;}if(sHead->direction == LEFT){
            p->x = x;
            p->y = y +1;}}elseif(x < Mid_x && y < Mid_y){if(sHead->direction == UP){
            p->x = x -2;
            p->y = y;}if(sHead->direction == LEFT){
            p->x = x;
            p->y = y -1;}}}
Snake*AllocSnakeBody(int x,int y,int z){
    Snake* p =(Snake*)malloc(sizeof(Snake));if(z){
        p->x = x -2;
        p->y = y;}else{Add(p ,x, y);}
    p->body ="●";
    p->next =NULL;return p;}//蛇身和蛇头链接voidInitSnake(Snake** sEnd){
    Snake* p =(Snake*)AllocSnakeBody((*sEnd)->x,(*sEnd)->y,1);(*sEnd)->next = p;//sHead->next = p*sEnd = p;}//在地图上画蛇voidShowSnake(Snake* sHead){
    Snake* p = sHead;int flag =1;while(p){gotoxy(p->x, p->y);if(flag){SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x03);printf("%s", p->head);
            flag =0;}else{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x07);printf("%s", p->body);}
        p = p->next;}}

Snake* sHead;//全局变量--在创建食物的时候有用
Snake* sEnd;//全局变量int SnakeLenth =0;//蛇的长度//创建蛇voidCreatSnake(){
    sHead =AllocSnakeHead(0,0);
    sEnd = sHead;int i =3;while(i--){InitSnake(&sEnd);
        SnakeLenth++;}//蛇的随机方向
    sHead->direction =rand()%4+1;while(sHead->direction == LEFT){
        sHead->direction =rand()%4+1;}ShowSnake(sHead);}//初始化食物
Food*AllocFood(Snake* sHead){
    Food* p =(Food*)malloc(sizeof(Food));//食物

    p->x =rand()% Foodd_x + Food_x;
    p->y =rand()% Foodd_y + Food_y;while(p->x %2){
        p->x =rand()% Foodd_x + Food_x;}
    p->head ="★";int flag =0;//判断食物的坐标是否合法while(1){
        Snake* t = sHead;while(t){if(t->x == p->x && t->y == p->y){
                flag =1;break;}
            t = t->next;}if(flag){
            p->x =rand()% Foodd_x + Food_x;while(p->x %2){
                p->x =rand()% Foodd_x + Food_x;}
            p->y =rand()% Foodd_y + Food_y +1;}else{break;}}return p;}//在地图上显示食物voidShowFood(Food* food){gotoxy(food->x, food->y);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x0e);printf("%s", food->head);}//创建食物
Food* food;//初始化食物voidCreatFood(){
    food =AllocFood(sHead);
    flagob =1;ShowFood(food);//展示}voidShowObstacle(Obstacle* obstacle){
    Obstacle* p = obstacle;int flag =1;while(p){gotoxy(p->x, p->y);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x09);printf("%s", p->body);
        p = p->next;}}int flagob =0;//标记:食物创建完,在判断是否重复
Obstacle*AllocObstacle(){
    Obstacle* p =(Obstacle*)malloc(sizeof(Obstacle));//障碍物
    p->x =rand()% Foodd_x + Food_x;
    p->y =rand()% Foodd_y + Food_y;while(p->x %2){
        p->x =rand()% Foodd_x + Food_x;}
    p->body ="■";
    p->next =NULL;int flag =0;//判断障碍物的坐标是否合法while(1){
        Snake* t = sHead;//判断蛇坐标while(t){if(t->x == p->x && t->y == p->y){
                flag =1;break;}
            t = t->next;}//判断食物坐标if(flagob){if(food->x == p->x && food->y == p->y){
                flag =1;}}if(flag){
            p->x =rand()% Foodd_x + Food_x +1;while(p->x %2){
                p->x =rand()% Foodd_x + Food_x;}
            p->y =rand()% Foodd_y + Food_y +1;}else{break;}}return p;}voidInitObstacle(Obstacle* obstacle){
    Obstacle* p =AllocObstacle();
    p->next = obstacle->next;
    obstacle->next = p;}

Obstacle* obstacle =AllocObstacle();//全局变量//创建障碍物int ObstacleConut =0;voidCreatObstacle(){InitObstacle(obstacle);//头插法创建
    ObstacleConut++;ShowObstacle(obstacle);//展示}

✨main.cpp

#include"snake.h"voidGame(){srand((unsignedint)time(NULL));//随机种子GameWelcome();//游戏欢迎界面GameDescription();//游戏说明while(1){DrawGameInterface();//绘制游戏运行界面if(GetKeyboard()==1)//按键,进去游戏{break;}}}intmain(){Game();return0;}

✨Paint.cpp

voidgotoxy(int x,int y){
    COORD pos;//位置 COORD是库里定义的类型。
    HANDLE hOutput;
    pos.X = x;//水平方向
    pos.Y = y;//垂直方向
    hOutput =GetStdHandle(STD_OUTPUT_HANDLE);//获取缓冲区中的数据,地址赋值给句柄SetConsoleCursorPosition(hOutput, pos);/*定位光标位置的函数,坐标为GetStdHandle()返回标准的输出的句柄,
    也就是获得输出屏幕缓冲区的句柄,并赋值给对象 pos*//*隐藏光标操作 */
    CONSOLE_CURSOR_INFO cursor;
    cursor.bVisible = FALSE;
    cursor.dwSize =sizeof(cursor);SetConsoleCursorInfo(hOutput,&cursor);}/*注意这里横坐标是每次+2 因为控制台字符宽高比为 1:2 *///游戏欢迎界面设置voidgotopaintWel(int x,int y){/*字体颜色*/gotoxy(x, y);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);printf("□");}//游戏说明界面设置voidgotopaintDes(int x,int y){gotoxy(x, y);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE);printf("□");}//游戏运行界面--围墙voidgotopaintWall(int x,int y){gotoxy(x, y);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x0f);printf("■");}//玩家属性界面voidgotopaintPler(int x,int y){gotoxy(x, y);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x0e);printf("□");}//绘制 欢迎/说明界面voidPaintInterface(){/*注意这里横坐标是每次+2 因为控制台字符宽高比为 1:2  x = 2y  */int i =0;staticint j =1;//每一次调用打印不同的形状//坐标// (20,2)-(60,2)// (20,23)-(60,23)for(i = Infa_x_left; i < Infa_x_right +1; i +=2)//上边{if(j ==1)gotopaintWel(i, Infa_y_up);if(j ==2)gotopaintDes(i, Infa_y_up);}for(i = Infa_y_up; i < Infa_y_down; i++)//左边{if(j ==1)gotopaintWel(Infa_x_left, i);if(j ==2)gotopaintDes(Infa_x_left, i);}for(i = Infa_x_left; i < Infa_x_right +1; i +=2)//下边{if(j ==1)gotopaintWel(i, Infa_y_down);if(j ==2)gotopaintDes(i, Infa_y_down);}for(i = Infa_y_up; i < Infa_y_down; i++)//右边{if(j ==1)gotopaintWel(Infa_x_right, i);if(j ==2)gotopaintDes(Infa_x_right, i);}
    j++;}//欢迎界面voidGameWelcome(){PaintInterface();//区域坐标范围   20,2 -- 60,2//               20,22-- 60,22 gotoxy(48,7);printf("▄︻┻┳═一 ○○○○○○");gotoxy(48,9);printf("Welcome to Snake Game");gotoxy(48,11);printf("你做好准备了嘛-.-");gotoxy(48,13);printf("游戏即将开始:");//倒计时int i =3;for(; i >=0; i--){gotoxy(66,13);printf("%d", i);Sleep(1000);}if(i ==-1)system("cls");}//游戏说明界面voidGameDescription(){PaintInterface();gotoxy(46,5);printf("按键说明:");gotoxy(46,7);printf("w ↑:上,s↓:下,a←:左 ,d→:右");gotoxy(46,9);printf("加速:J/j 减速:I/i 正常:O/o");gotoxy(46,11);printf("Esc:退出游戏 L:游戏继续");gotoxy(46,11);printf("按任意键进入游戏");gotoxy(46,15);printf("游戏说明:\n");gotoxy(46,17);printf("食物:★,障碍物:■\n");gotoxy(46,19);printf("小蛇不可回头,撞墙/障碍物死亡");Sleep(5000);system("cls");}//绘制墙voidPaintWall(){int i =0;//2,4-52,4//2,29-52,29for(i = Map_x_left; i < Map_x_right +1; i +=2)//上边{gotopaintWall(i, Map_y_up);}for(i = Map_y_up; i < Map_y_down; i++)//左边{gotopaintWall(Map_x_left, i);}for(i = Map_x_left; i < Map_x_right +1; i +=2)//下边{gotopaintWall(i, Map_y_down);}for(i = Map_y_up; i < Map_y_down; i++)//右边{gotopaintWall(Map_x_right, i);}}//绘制玩家属性界面char*Gettime(){time_t rawtime;structtm* info;//static char buffer[80];char* buffer =(char*)malloc(80);time(&rawtime);

    info =localtime(&rawtime);strftime(buffer,80,"%Y-%m-%d %H:%M:%S   %A", info);return  buffer;}//玩家属性
Player play ={0};//全局变量voidPlayInfaAttr(){gotoxy(58,2);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x0b);printf("贪吃蛇");SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x02);gotoxy(74,10);printf("o(*^@^*)o");gotoxy(72,16);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x07);//获取当前时间gotoxy(64,21);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x09);printf("%s",Gettime());SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x08);gotoxy(68,16);printf("游戏进行中                    ");gotoxy(68,18);printf("还不错哟,继续加油  ^_^  ");gotoxy(68,20);printf("                  ");gotoxy(64,28);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x03);printf("制作者:日向晚,声声慢");}//绘制玩家属性界面voidPaintPlayInfa(){int i =0;for(i = Play_x_left; i < Play_x_right +1; i +=2)//上边{gotopaintPler(i, Play_y_up);}for(i = Play_y_up; i < Play_y_down; i++)//左边{gotopaintPler(Play_x_left, i);}for(i = Play_x_left; i < Play_x_right +1; i +=2)//下边{gotopaintPler(i, Play_y_down);}for(i = Play_y_up; i < Play_y_down; i++)//右边{gotopaintPler(Play_x_right, i);}PlayInfaAttr();}

✨SnakeGame.cpp

#include"snake.h"voidMove(int x,int y);//函数声明voidSnakeMove(int x);//函数声明//删除移动时的尾巴voidDeleBody(int x,int y){gotoxy(x, y);// SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x00);printf("  ");}//移动过程的实现voidMove(int x,int y){
     Snake* p =AllocSnakeHead(x, y);//新的头
     Snake* t = sHead;
     Snake* t1 = t->next;//记录旧的头

     t->body ="●";//原来的头变为身体

     p->next = t;//链接
     sHead = p;//新的头赋值给sHead//去掉最后一个节点while(t1->next){
         t = t->next;
         t1 = t1->next;}DeleBody(t1->x, t1->y);
     t->next =NULL;free(t1);
     p =NULL;}//蛇移动voidSnakeMove(int x){//不可往相反的方向走switch(x){case UP:if(x != DOWN){Move(sHead->x,sHead->y -1);ShowSnake(sHead);}break;case DOWN:if(x != UP){Move(sHead->x, sHead->y +1);ShowSnake(sHead);}break;case LEFT:if(x != RIGHT){Move(sHead->x -2, sHead->y);ShowSnake(sHead);}break;case RIGHT:if(x != LEFT){Move(sHead->x +2, sHead->y);ShowSnake(sHead);}break;}}intFinishSet(){if(ObstacleConut >0){
        Obstacle* p = obstacle->next;free(p);
        obstacle->next =NULL;}switch(win){case0:gotoxy(68,16);printf("游戏结束,继续加油 `(*>﹏<*)′");gotoxy(68,18);printf("==>按Esc退出,按L继续<==");gotoxy(68,20);printf("                  ");break;case1:break;}int b =getch();while(b){switch(b){case27:system("cls");return1;case'l':case'L':system("cls");
             conut =0;//蛇头创建处
             win =0;//输赢打印
             play.score =0;return2;default:break;}
         b =getch();}return0;}//添加身体voidAddBody(){//也可以使用尾插入,会更简单。
    Snake* p = sHead;//找到最后的一个节点while(p->next){
        p = p->next;}
    Snake* t =AllocSnakeBody(p->x, p->y,0);
    p->next = t;}voidEatFoodSet(){//原来的食物,会被蛇头覆盖掉,因此是要添加蛇尾就可以。AddBody();//移动身体CreatFood();//产生新的食物
    play.score++;switch(play.score){case2:case4:case6:case8:case10:case12:case14:CreatObstacle();CreatObstacle();default:break;}gotoxy(68,13);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x05);printf("得分:  %d ( ̄︶ ̄) ", play.score);
    SnakeLenth++;}int win =0;intJudgeWin(){gotoxy(68,16);//每一个输出对应位置要等长printf("游戏胜利      (●'-'●)       ");gotoxy(68,18);printf("成功获得蛇王的称号      ");gotoxy(68,20);printf("按Esc退出,按L继续");
    win =1;returnFinishSet();}intJudgeHitWall(){returnFinishSet();}intJudgeFood(){EatFoodSet();//吃到食物设置return0;}intJudgeHitSelf(){
    Snake* p = sHead->next;while(p){if(sHead->x == p->x && sHead->y == p->y)returnFinishSet();
        p = p->next;}return0;}intJudgeObstacle(){
    Obstacle* p = obstacle;while(p){if(sHead->x == p->x && sHead->y == p->y)returnFinishSet();
        p = p->next;}return0;}//判断intJudge(){//判断输赢if(play.score == Win)returnJudgeWin();//判断是否撞墙if(sHead->x == Map_x_left +1|| sHead->x == Map_x_left ||
         sHead->x == Map_x_right -1|| sHead->x == Map_x_right ||
         sHead->y == Map_y_up || sHead->y == Map_y_down)returnJudgeHitWall();//判断是否迟到食物,吃到食物  将食物变为头,原来的头变为身体 -->Move函数if(sHead->x == food->x && sHead->y == food->y )returnJudgeFood();//判断是否撞到自己if(JudgeHitSelf()){;}else//是否撞到障碍物returnJudgeObstacle();return0;}//按键int key =0;//当前方向的值int key1 =0;//只记录前一次蛇移动按键int j =0;//加速标记int i =0;//减速标记int o =1;//正常速度标记intGetKeyboard(){
    key = sHead->direction;while(1){int b =kbhit();
        sHead->direction = key;if(kbhit()){//左右需要移动2个单位移动,上下1个单位移动if(key == UP || key == DOWN || key == LEFT || key == RIGHT)
                key1 = key;
            key =getch();switch(key){case'a':case'A':case75:if(key1 != RIGHT)
                    key = LEFT;else
                    key = key1;break;case's':case'S':case80:if(key1 != UP)
                    key = DOWN;else
                    key = key1;break;case'd':case'D':case77:if(key1 != LEFT)
                    key = RIGHT;else
                    key = key1;break;case'w':case'W':case72:if(key1 != DOWN)
                    key = UP;else
                    key = key1;break;case'j':case'J':
                j =1;//加速标记
                i =0;//减速标记
                o =0;//正常速度标记
                key = key1;break;case'I':case'i':
                j =0;//加速标记
                i =1;//减速标记
                o =0;//正常速度标记
                key = key1;break;case'o':case'O':
                j =0;//加速标记
                i =0;//减速标记
                o =1;//正常速度标记
                key = key1;break;default:
                key = key1;break;}}else//自动走的设置{SnakeMove(key);if(j)Sleep(150);if(i)Sleep(250);if(o)Sleep(200);}if(int ret =Judge())//判断是否吃到食物/是否死亡/赢{//返回值为真结束游戏 --- 死亡return ret;}}return0;}//游戏创建voidDrawGameInterface(){//绘制墙PaintWall();//绘制玩家属性PaintPlayInfa();//创建食物CreatFood();//创建蛇CreatSnake();//创建障碍物CreatObstacle();}

本文转载自: https://blog.csdn.net/m0_64212811/article/details/127145379
版权归原作者 日向晚,声声慢 所有, 如有侵权,请联系我们删除。

“纯C实现的贪吃蛇(无EasyX,详解)”的评论:

还没有评论