贪吃蛇大作战【纯c语言】

如果有看到不懂的地方或者对c语言某些知识忘了的话,可以找我之前的文章哦!!!

个人主页:小八哥向前冲~-CSDN博客

所属专栏:c语言_小八哥向前冲~的博客-CSDN博客

贪吃蛇游戏演示:

贪吃蛇游戏动画演示

目录

游戏前期准备:

设置控制台相关信息

GetStdHanle

GetConsoleCursorInfo

SetConsoleCursorInfo

SetConsoleCursorPosition

GetAsynckeyState

贪吃蛇游戏设计与分析

本地化

地图,食物和蛇身设计

游戏的初始化

打印欢迎界面

绘制贪吃蛇地图

初始化蛇

初始化食物

游戏的运行

打印帮助信息

贪吃蛇的运行

游戏的结束

贪吃蛇的总代码


游戏前期准备:

需要注意的是,纯使用c语言实现贪吃蛇会使用到一些Win32 API知识,接下来我们一一介绍学习一下。

那么什么是Win32 API呢?

介绍:

Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外,它同时也是⼀个很⼤ 的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application),所以便 称之为ApplicationProgrammingInterface,简称API函数。WIN32API也就是MicrosoftWindows 32位平台的应⽤程序编程接口。

设置控制台相关信息

我们知道平常我们运行程序弹出来的那个框框就是控制台终端。(如图)

我们可以使用cmd命令来设置控制台窗口的大小。如:

//         列       行
mode con cols=100 lines=30

值得注意的是:

1.使用这个命令之前,需要把这个控制台改为让Windows决定或Windows 控制台主机。

2.使用system函数所需要的头文件既可以是stdlib.h,也可以是Windows.h(不区分大小,也可以使用windows.h或WINDOWS.H等形式引用头文件。

演示一下:

控制台的改变

同样我们也能通过命令来设置控制台名字

title 贪吃蛇

效果:

控制台上的坐标COORD

COORD是什么呢?其实它是Windows API中定义的结构体,表示一个字符在控制台屏幕缓冲区上的坐标,而坐标系(0,0)的原点位于缓冲区的顶部左端单元格。

COORD的结构体声明:

typedef struct _COORD
{SHORT X;SHORT Y;
}COORD,*PCOORD;

坐标赋值:

COORD pos={20,30};

GetStdHanle

GetStdHanle是一个Windows API函数。它用于从一个特定的标准设备(标准输入,标准输出等)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备

注意:标准输入是指键盘,标准输出指的是屏幕。

我们要知道的是只要得到的这个句柄,咋们就能操控设备。所以我们能用GetStdHanle函数来获得句柄,从而进行一系列操作。

我们来看看这个函数:

那么我们来尝试获取句柄:

HANDLE houtput=NULL;
//从标准输出获取句柄
houtput=GetStdHanle(STD_OUTPUT_HANDLE);

我们在程序运行跳出控制台的时候,是不是有一个光标在闪动?那么我们试想一下,倘若我们不将那个光标隐藏的话,蛇在移动的时候就会有一个光标一直在闪动,不美观。那么我们如何隐藏光标呢?接下来就要用到GetConsoleCursorInfo这个函数。

GetConsoleCursorInfo

同样的我们来看看这个函数的语法:

从中我们知道,这个函数就是用来检索控制台屏幕缓冲区的光标大小和光标可见性的信息

BOOL WINAPI GetConsoleCursorInfo(_In_  HANDLE               hConsoleOutput,_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
//注意:PCONSOLE_CURSOR_INFO是指向CONSOLE_CURSOR_INFO 结构的指针,该结构接受有关主机游标

CONSOLE_CURSOR_INFO 这个结构体这个结构体它是包含了光标信息。

我们来看看它的相关信息:

我们来使用一下:

 //获取标准输出的句柄(用来标识不同设备的数值)HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

SetConsoleCursorInfo

那么我们得到了光标信息,下一步就是设置我们想要的光标信息,设置光标相关信息的函数其实就是SetConsoleCursorInfo函数,它是用来设置控制台指定控制台屏幕缓冲区的光标大小和可见性

相关信息:

我们来使用看看:

//获取标准输出的句柄(用来标识不同设备的数值)HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标//设置光标状态SetConsoleCursorInfor(hOutput,&CursorInfo);

SetConsoleCursorPosition

设置好了光标的状态,那么能不能设置光标位置,让我们能在任意位置打印我们想要的信息呢?那么我们就要用到SetConsoleCursorPosition函数。我们将坐标位置放到COORD类型中,然后调用SetConsoleCursorPosition函数就能将光标设置指定位置。

看看详情:

使用一下:

    COORD pos = { 10, 20 };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);

需要注意的是:这个pos位置可能会设置不成功。

GetAsynckeyState

玩过贪吃蛇游戏的都知道,键盘上的上,下,左,右按键来控制蛇的方向。那么我们如何获取玩家是否按了哪个按键呢?GetAsynckeyState函数就能解决这个问题。

老样子,我们来看看它的详情:

详细解释:将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬 起;如果最低位被置为1则说明,该按键被按过,否则为0。 如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1

虚拟按键代码详细见:虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn

我们来使用看看:


#define KEY_PRESS(VK)  ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)int main(){ while (1){if (KEY_PRESS(0x30)){printf("0\n");}else if (KEY_PRESS(0x31)){printf("1\n");}else if (KEY_PRESS(0x32)){printf("2\n");}else if (KEY_PRESS(0x33)){printf("3\n");}else if (KEY_PRESS(0x34)){printf("4\n");}else if (KEY_PRESS(0x35)){printf("5\n");}}
}

好了,我们现在正式开始写游戏逻辑吧!!!

贪吃蛇游戏设计与分析

要实现的功能:

  • 贪吃蛇地图绘制
  • 蛇的动作(上 ,下,左,右方向按键控制蛇的动作)
  • 蛇撞墙死亡
  • 帮助信息的打印
  • 计算得分
  • 蛇加速,减速
  • 暂停游戏

我们可以打印墙体用宽字符:□,蛇身体用:●,食物用:★。

我们来科普一下宽字符:普通字符是占一个字节,而宽字符占2个字节。

这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语⾔并不适合⾮英语国家(地区)使⽤。 C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。 C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7 位,最⾼位是没有使⽤的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语 国家中,128个字符是基本够⽤的,但是,在其他国家语⾔中,⽐如,在法语中,字⺟上⽅有注⾳符 号,它就⽆法⽤ASCII码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编入新的符 号。⽐如,法语中的é的编码为130(⼆进制10000010)。这样⼀来,这些欧洲国家使⽤的编码体 系,可以表⽰最多256个符号。但是,这⾥⼜出现了新的问题。不同的国家有不同的字⺟,因此,哪 怕它们都使⽤256个符号的编码⽅式,代表的字⺟却不⼀样。⽐如,130在法语编码中代表了é,在希 伯来语编码中却代表了字⺟Gimel( ),在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这 些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。 ⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号, 肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是GB2312,使 ⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。 后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型 wchar_t 和宽字符的输⼊和输出函数,加⼊了头文件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

而打印宽字符需要本地化。

本地化

通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏, 指定⼀个类项:

• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm()

• LC_CTYPE:影响字符处理函数的⾏为。

• LC_MONETARY:影响货币格式。

• LC_NUMERIC:影响 printf() 的数字格式。

• LC_TIME:影响strftime() 和 wcsftime() 。

• LC_ALL-针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

setlocale函数:

 char* setlocale (int category, const char* locale);

setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。 setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。 C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。 在任意程序执⾏开始,都会隐藏式执⾏调用:

setlocale(LC_ALL, "C");

当地区设置为"C"时,库函数按正常⽅式执⾏,小数点是⼀个点。 当程序运行起来后想改变地区,就只能显示调用setlocale函数。用""作为第2个参数,调⽤setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。 比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。

setlocale(LC_ALL, " ");//切换到本地环境

那如果想在屏幕上打印宽字符,怎么打印呢?宽字符的字面量必须加上前缀“L”,否则C语言会把字面量当作窄字符类型处理。前缀“L”在单引 号前⾯,表示宽字符,对应 wprintf() 的占位符为 wprintf() 的占位符为 %ls

我们来举例一下:

#include<locale.h>
#include<stdio.h>
int main()
{//修改当前地区为本地模式,为了支持中文宽字符的打印setlocale(LC_ALL, "");wprintf(L"%s\n",L"小八哥向前冲");return 0;
}

地图,食物和蛇身设计

我们创建一个地图:27行,58列。再围绕这个地图画出墙。

由上图,我们不难知道一行的宽度是一列宽的两倍,只要注意这个咋们就能轻易画出强来!

初始化状态:我们可以将蛇的身体设为5,每个节点为宽字符●,在固定的一个坐标处开始,我们这里假设在(24,5)处开始打印5个蛇身节点。值得注意的是:蛇每个节点的x坐标必须是2的倍数,否则可能出现蛇的某一个节点有一半出现在墙体,另一半出现在墙外。接下来就是食物,在墙内随机生成一个坐标(同样x坐标是2的倍数),再者坐标不能和蛇身体重合,才能打印★。

在游戏运行的时候,蛇每吃一个食物,蛇身就变长一节,这里我们使用链表存储蛇节点。每个节点记录蛇身节点在地图上的坐标以及指向下一个指针变量。蛇节点结构如下:

//蛇身节点
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

我们刚刚分析了,要的变量不止于这些,还需要:指向蛇头的指针,初始时蛇的速度,蛇方向,食物,食物分数,总分,蛇的状态。但是这些变量比较分散也比较麻烦,我们可以同时创建另一个结构体来管理这些变量。

typedef struct Snake
{pSnakeNode _pSnake;//维护整条蛇的指针pSnakeNode _pFood;//维护食物的指针enum DIRECTION _Dir;//蛇头的方向默认是向右enum GAME_STATUS _Status;//游戏状态int _Socre;//当前获得分数int _Add;//默认每个食物10分int _SleepTime;//每走一步休眠时间
}Snake, * pSnake;

游戏的状态有:正常运行,正常退出,撞墙死亡,撞到自己死亡。

//游戏状态
enum GAME_STATUS
{OK,//正常运行KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到自己END_NOMAL//正常结束
};

蛇的方向:向上,向下,向左,向右。

//方向
enum DIRECTION
{UP = 1,//上DOWN,//下LEFT,//左RIGHT//右
};

现在,我们正式设计游戏逻辑。

为了使游戏逻辑梳理更加清晰,我们封装三个大函数:GameStart()——游戏初始化,GameRun()——游戏运行,GameEnd()——游戏结束。

游戏的初始化

初始化的内容:

  1. 打印欢迎界面

  2. 绘制贪吃蛇身体

  3. 初始化贪吃蛇相关变量和食物

打印欢迎界面

我们首先分装函数来定位坐标:

//设置光标的坐标
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}

而system("pause")是一个用于暂停程序运行的函数。它会出现一个提示信息,直到用户按下任意键程序才会继续运行。

如:

而由上一个欢迎界面跳到这个界面,我们需要清理一下屏幕,要用到清理控制台界面函数。

//屏幕清理
system("cls");

打印欢迎界面:

void WelcomeToGame()
{SetPos(40, 15);printf("欢迎来到贪吃蛇小游戏");SetPos(42, 17);printf("@小八哥向前冲");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");system("cls");SetPos(25, 12);printf("用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");SetPos(25, 13);printf("加速将能得到更高的分数。\n");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");system("cls");
}

绘制贪吃蛇地图

我们先来看看地图:

由于墙体是宽字符,我们先将c语言环境转化到本地环境,再利用wprintf打印宽字符。

先打印上,下两行,再打印两列:上一行坐标(2*i,0),i 的范围:0~28。下一行坐标(2*i,25),i 的范围0~28。左一列坐标(0,i),i 的范围:1~25。 右一列坐标:(56,i ),i 范围:1~25。

注意:打印的时候要准确定位好我们的坐标,由于打印的时候默认是从左向右,而我们要从上到下

#define WALL L'□'//绘制地图
void CreateMap()
{int i = 0;//上(0,0)-(56, 0)SetPos(0, 0);for (i = 0; i < 58; i += 2){wprintf(L"%c", WALL);}//下(0,26)-(56, 26)SetPos(0, 26);for (i = 0; i < 58; i += 2){wprintf(L"%c", WALL);}//左//x是0,y从1开始增长for (i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%c", WALL);}//x是56,y从1开始增长for (i = 1; i < 26; i++){SetPos(56, i);wprintf(L"%c", WALL);}
}

初始化蛇

我们得先创建5个节点,并且初始化好节点坐标,然后将这些节点串起来就行!

 //蛇的初始位置
#define POS_X 24
#define POS_Y 5//创建蛇身的节点cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc()");return;}//设置坐标cur->next = NULL;cur->x = POS_X + i * 2;cur->y = POS_Y;//头插法if (ps->_pSnake == NULL){ps->_pSnake = cur;}else{cur->next = ps->_pSnake;ps->_pSnake = cur;}

然后打印蛇:

#define WALL L'□'
#define BODY L'●'  
#define FOOD L'★'   
//打印蛇的身体cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}

设置蛇属性:

 //初始化贪吃蛇数据ps->_SleepTime = 200;ps->_Socre = 0;ps->_Status = OK;ps->_Dir = RIGHT;ps->_Add = 10;

初始化食物

既然创建好了蛇,就差食物了,我们首先创建一个节点给食物,然后将食物打印出来。

这个食物的x坐标必须是2的倍数,也要再在墙体中,且这个食物随机生成。

void CreateFood(pSnake ps)
{int x = 0;int y = 0;again://产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对齐。do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针//食物不能和蛇身冲突while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建食物if (pFood == NULL){perror("CreateFood::malloc()");return;}else{pFood->x = x;pFood->y = y;SetPos(pFood->x, pFood->y);wprintf(L"%c", FOOD);ps->_pFood = pFood;}
}

游戏的运行

游戏运行时我们需要做的事:

  1. 打印帮助信息
  2. 贪吃蛇运行信息
  3. 判断蛇的状态

游戏运行的界面:

打印帮助信息

void PrintHelpInfo()
{//打印提示信息SetPos(64, 15);printf("不能穿墙,不能咬到自己\n");SetPos(64, 16);printf("用↑.↓.←.→分别控制蛇的移动.");SetPos(64, 17);printf("F1 为加速,F2 为减速\n");SetPos(64, 18);printf("ESC :退出游戏.space:暂停游戏.");SetPos(64, 20);printf("小八哥向前冲@版权");
}

贪吃蛇的运行

蛇的运行,我们需要自己按键去控制蛇的方向。我们需要判断哪个按键是否摁过判断蛇的方向,只要蛇的状态不是OK,此时就不需要走了。

void GameRun(pSnake ps)
{//打印右侧帮助信息PrintHelpInfo();do{SetPos(64, 10);printf("得分:%d ", ps->_Socre);printf("每个食物得分:%d分", ps->_Add);if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN){ps->_Dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP){ps->_Dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT){ps->_Dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT){ps->_Dir = RIGHT;}else if (KEY_PRESS(VK_SPACE)){pause();}else if (KEY_PRESS(VK_ESCAPE)){ps->_Status = END_NOMAL;break;}else if (KEY_PRESS(VK_F3)){if (ps->_SleepTime >= 50){ps->_SleepTime -= 30;ps->_Add += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->_SleepTime < 350){ps->_SleepTime += 30;ps->_Add -= 2;if (ps->_SleepTime == 350){ps->_Add = 1;}}}//蛇每次一定之间要休眠的时间,时间短,蛇移动速度就快Sleep(ps->_SleepTime);SnakeMove(ps);KillByWall(ps);KillBySelf(ps);} while (ps->_Status == OK);
}

而暂停函数的实现只需要一直休眠就行,直到按了空格就跳出休眠。

void pause()//暂停
{while (1){Sleep(300);if (KEY_PRESS(VK_SPACE)){break;}}
}

通过蛇头的下一个位置和蛇身以及释放蛇的尾节点,如果下一个位置是食物,就要吃掉食物,且再创建一个食物,如果不是食物,就正常走就行,所以我们走一步就需要判断是否为食物。

void SnakeMove(pSnake ps)
{//创建下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}//确定下一个节点的坐标,下一个节点的坐标根据,蛇头的坐标和方向确定switch (ps->_Dir){case UP:{pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;}break;case DOWN:{pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;}break;case LEFT:{pNextNode->x = ps->_pSnake->x - 2;pNextNode->y = ps->_pSnake->y;}break;case RIGHT:{pNextNode->x = ps->_pSnake->x + 2;pNextNode->y = ps->_pSnake->y;}break;}//如果下一个位置就是食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else//如果没有食物{NoFood(pNextNode, ps);}
}

下一个位置是食物就吃掉食物,再创建新食物

void EatFood(pSnakeNode psn, pSnake ps)
{//头插法psn->next = ps->_pSnake;ps->_pSnake = psn;pSnakeNode cur = ps->_pSnake;//打印蛇while (cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}ps->_Socre += ps->_Add;free(ps->_pFood);CreateFood(ps);
}

下一个不是食物

void NoFood(pSnakeNode psn, pSnake ps)
{//头插法psn->next = ps->_pSnake;ps->_pSnake = psn;pSnakeNode cur = ps->_pSnake;//打印蛇while (cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}//最后一个位置打印空格,然后释放节点SetPos(cur->next->x, cur->next->y);printf("  ");free(cur->next);cur->next = NULL;
}

注意:将尾节点释放后,还需要将尾节点位置打印空格,否则蛇身只会越来越长。走一步,休眠一下,让我们知道蛇走到哪里了。

我们还需检测是否撞墙,是否撞墙检测头节点是否撞墙就行。

int KillByWall(pSnake ps)
{if ((ps->_pSnake->x == 0)|| (ps->_pSnake->x == 56)|| (ps->_pSnake->y == 0)|| (ps->_pSnake->y == 26)){ps->_Status = KILL_BY_WALL;return 1;}return 0;
}

而检测是否撞到自己,只需要检测蛇头和某一个身体节点相撞(是否重合)就行。

int KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if ((ps->_pSnake->x == cur->x)&& (ps->_pSnake->y == cur->y)){ps->_Status = KILL_BY_SELF;return 1;}cur = cur->next;}return 0;
}

游戏的结束

其实到这里基本的游戏就能运行起来了,只不过我们创建的节点需要释放掉,且玩家结束游戏的提示。

void GameEnd(pSnake ps)
{pSnakeNode cur = ps->_pSnake;SetPos(24, 12);switch (ps->_Status){case END_NOMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞上自己了 ,游戏结束!\n");break;case KILL_BY_WALL:printf("您撞墙了,游戏结束!\n");break;}//释放蛇身的节点while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

贪吃蛇的总代码

Snack.h文件

#pragma once
#include <windows.h>
#include <time.h>
#include <stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#define KEY_PRESS(VK)  ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)//方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//游戏状态
enum GAME_STATUS
{OK,//正常运行KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到自己END_NOMAL//正常结束
};#define WALL L'□'
#define BODY L'●'  //★○●◇◆□■
#define FOOD L'★'  //★○●◇◆□■//蛇的初始位置
#define POS_X 24
#define POS_Y 5//蛇身节点
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;typedef struct Snake
{pSnakeNode _pSnake;//维护整条蛇的指针pSnakeNode _pFood;//维护食物的指针enum DIRECTION _Dir;//蛇头的方向默认是向右enum GAME_STATUS _Status;//游戏状态int _Socre;//当前获得分数int _Add;//默认每个食物10分int _SleepTime;//每走一步休眠时间
}Snake, * pSnake;//游戏开始前的初始化
void GameStart(pSnake ps);//游戏运行过程
void GameRun(pSnake ps);//游戏结束
void GameEnd(pSnake ps);//设置光标的坐标
void SetPos(short x, short y);//欢迎界面
void WelcomeToGame();//打印帮助信息
void PrintHelpInfo();//创建地图
void CreateMap();//初始化蛇
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//暂停响应
void pause();//下一个节点是食物
int NextIsFood(pSnakeNode psn, pSnake ps);//吃食物
void EatFood(pSnakeNode psn, pSnake ps);//不吃食物
void NoFood(pSnakeNode psn, pSnake ps);//撞墙检测
int KillByWall(pSnake ps);//撞自身检测
int KillBySelf(pSnake ps);//蛇的移动
void SnakeMove(pSnake ps);//游戏初始化
void GameStart(pSnake ps);//游戏运行
void GameRun(pSnake ps);//游戏结束
void GameEnd(pSnake ps);

Snack.c文件

#include"Snake.h"//设置光标的坐标
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}void WelcomeToGame()
{SetPos(40, 15);printf("欢迎来到贪吃蛇小游戏");SetPos(42, 17);printf("@小八哥向前冲");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");system("cls");SetPos(25, 12);printf("用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");SetPos(25, 13);printf("加速将能得到更高的分数。\n");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");system("cls");
}void CreateMap()
{int i = 0;//上(0,0)-(56, 0)SetPos(0, 0);for (i = 0; i < 58; i += 2){wprintf(L"%c", WALL);}//下(0,26)-(56, 26)SetPos(0, 26);for (i = 0; i < 58; i += 2){wprintf(L"%c", WALL);}//左//x是0,y从1开始增长for (i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%c", WALL);}//x是56,y从1开始增长for (i = 1; i < 26; i++){SetPos(56, i);wprintf(L"%c", WALL);}
}void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;int i = 0;//创建蛇身节点,并初始化坐标//头插法for (i = 0; i < 5; i++){//创建蛇身的节点cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc()");return;}//设置坐标cur->next = NULL;cur->x = POS_X + i * 2;cur->y = POS_Y;//头插法if (ps->_pSnake == NULL){ps->_pSnake = cur;}else{cur->next = ps->_pSnake;ps->_pSnake = cur;}}//打印蛇的身体cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}//初始化贪吃蛇数据ps->_SleepTime = 200;ps->_Socre = 0;ps->_Status = OK;ps->_Dir = RIGHT;ps->_Add = 10;
}void CreateFood(pSnake ps)
{int x = 0;int y = 0;again://产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对齐。do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针//食物不能和蛇身冲突while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建食物if (pFood == NULL){perror("CreateFood::malloc()");return;}else{pFood->x = x;pFood->y = y;SetPos(pFood->x, pFood->y);wprintf(L"%c", FOOD);ps->_pFood = pFood;}
}void PrintHelpInfo()
{//打印提示信息SetPos(64, 15);printf("不能穿墙,不能咬到自己\n");SetPos(64, 16);printf("用↑.↓.←.→分别控制蛇的移动.");SetPos(64, 17);printf("F1 为加速,F2 为减速\n");SetPos(64, 18);printf("ESC :退出游戏.space:暂停游戏.");SetPos(64, 20);printf("小八哥向前冲@版权");
}void pause()//暂停
{while (1){Sleep(300);if (KEY_PRESS(VK_SPACE)){break;}}
}//pSnakeNode psn 是下一个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(pSnakeNode psn, pSnake ps)
{return (psn->x == ps->_pFood->x) && (psn->y == ps->_pFood->y);
}//pSnakeNode psn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(pSnakeNode psn, pSnake ps)
{//头插法psn->next = ps->_pSnake;ps->_pSnake = psn;pSnakeNode cur = ps->_pSnake;//打印蛇while (cur){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}ps->_Socre += ps->_Add;free(ps->_pFood);CreateFood(ps);
}//pSnakeNode psn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void NoFood(pSnakeNode psn, pSnake ps)
{//头插法psn->next = ps->_pSnake;ps->_pSnake = psn;pSnakeNode cur = ps->_pSnake;//打印蛇while (cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}//最后一个位置打印空格,然后释放节点SetPos(cur->next->x, cur->next->y);printf("  ");free(cur->next);cur->next = NULL;
}//pSnake ps 维护蛇的指针
int KillByWall(pSnake ps)
{if ((ps->_pSnake->x == 0)|| (ps->_pSnake->x == 56)|| (ps->_pSnake->y == 0)|| (ps->_pSnake->y == 26)){ps->_Status = KILL_BY_WALL;return 1;}return 0;
}//pSnake ps 维护蛇的指针
int KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if ((ps->_pSnake->x == cur->x)&& (ps->_pSnake->y == cur->y)){ps->_Status = KILL_BY_SELF;return 1;}cur = cur->next;}return 0;
}void SnakeMove(pSnake ps)
{//创建下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}//确定下一个节点的坐标,下一个节点的坐标根据,蛇头的坐标和方向确定switch (ps->_Dir){case UP:{pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;}break;case DOWN:{pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;}break;case LEFT:{pNextNode->x = ps->_pSnake->x - 2;pNextNode->y = ps->_pSnake->y;}break;case RIGHT:{pNextNode->x = ps->_pSnake->x + 2;pNextNode->y = ps->_pSnake->y;}break;}//如果下一个位置就是食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else//如果没有食物{NoFood(pNextNode, ps);}KillByWall(ps);KillBySelf(ps);
}void GameStart(pSnake ps)
{//设置控制台窗口的大小,30行,100列//mode 为DOS命令system("mode con cols=100 lines=30");//设置cmd窗口名称system("title 贪吃蛇");//获取标准输出的句柄(用来标识不同设备的数值)HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态//打印欢迎界面WelcomeToGame();//打印地图CreateMap();//初始化蛇InitSnake(ps);//创造第一个食物CreateFood(ps);
}void GameRun(pSnake ps)
{//打印右侧帮助信息PrintHelpInfo();do{SetPos(64, 10);printf("得分:%d ", ps->_Socre);printf("每个食物得分:%d分", ps->_Add);if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN){ps->_Dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP){ps->_Dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT){ps->_Dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT){ps->_Dir = RIGHT;}else if (KEY_PRESS(VK_SPACE)){pause();}else if (KEY_PRESS(VK_ESCAPE)){ps->_Status = END_NOMAL;break;}else if (KEY_PRESS(VK_F3)){if (ps->_SleepTime >= 50){ps->_SleepTime -= 30;ps->_Add += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->_SleepTime < 350){ps->_SleepTime += 30;ps->_Add -= 2;if (ps->_SleepTime == 350){ps->_Add = 1;}}}//蛇每次一定之间要休眠的时间,时间短,蛇移动速度就快Sleep(ps->_SleepTime);SnakeMove(ps);} while (ps->_Status == OK);
}void GameEnd(pSnake ps)
{pSnakeNode cur = ps->_pSnake;SetPos(24, 12);switch (ps->_Status){case END_NOMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞上自己了 ,游戏结束!\n");break;case KILL_BY_WALL:printf("您撞墙了,游戏结束!\n");break;}//释放蛇身的节点while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

test.c文件

#include"Snake.h"
#include<locale.h>
void test()
{int ch = 0;srand((unsigned int)time(NULL));do{Snake snake = { 0 };GameStart(&snake);GameRun(&snake);GameEnd(&snake);SetPos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();getchar();//清理\n} while (ch == 'Y');SetPos(0, 27);
}int main()
{//修改当前地区为本地模式,为了支持中文宽字符的打印setlocale(LC_ALL, "");//测试逻辑test();return 0;
}

好了!今天的贪吃蛇代码想必你看到这里已经恍然大悟了!下一期我们不见不散!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/829372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第一阶段--Day2--信息安全法律法规、网络安全相关标准

目录 1. 针对信息安全的规定 2. 网络安全相关标准 1. 针对信息安全的规定 《中华人民共和国计算机信息系统安全保护条例》1994年2月18日颁布并实施 中华人民共和国计算机信息系统安全保护条例__增刊20111国务院公报_中国政府网 《中华人民共和国国际联网安全保护管理…

笔记:编写程序,分别采用面向对象和 pyplot 快捷函数的方式绘制正弦曲线 和余弦曲线。 提示:使用 sin()或 cos()函数生成正弦值或余弦值。

文章目录 前言一、面向对象和 pyplot 快捷函数的方式是什么&#xff1f;二、编写代码面向对象的方法&#xff1a;使用 pyplot 快捷函数的方法&#xff1a; 总结 前言 本文将探讨如何使用编程语言编写程序&#xff0c;通过两种不同的方法绘制正弦曲线和余弦曲线。我们将分别采用…

图像处理ASIC设计方法 笔记18 轮廓跟踪算法的硬件加速方案

目录 1排除伪孤立点(断裂链表)方法1 限制链表的长度方法2 增加判断条件排除断裂链表方法3 排除不必要跟踪的轮廓(推荐用这个方法)P129 轮廓跟踪算法的硬件加速方案 1排除伪孤立点(断裂链表) 如果图像中某区域存在相邻像素之间仅有对角连接的部位,则对包围该区域的像素…

SOLIDWORKS Electrical 3D--精准的三维布线

相信很多工程师在实际生产的时候都会遇到线材长度不准确的问题&#xff0c;从而导致线材浪费甚至整根线材报废的问题&#xff0c;这基本都是由于人工测量长度所导致的&#xff0c;因此本次和大家简单介绍一下SOLIDWORKS Electrical 3D布线的功能&#xff0c;Electrical 3D布线能…

伙伴匹配(后端)-- 用户登录

文章目录 登录逻辑设计登录业务代码实现用户登录态如何知道是哪个用户登录了&#xff1f;cookie与session 逻辑删除配置添加TableLogic注解 &#xff08;现在做单机登录&#xff09; 后面修改为redis单点登录 登录逻辑设计 接收参数&#xff1a;用户接账户&#xff0c;密码 请…

【数据标注】使用LabelImg标注YOLO格式的数据(案例演示)

文章目录 LabelImg介绍LabelImg安装LabelImg界面标注常用的快捷键标注前的一些设置案例演示检查YOLO标签中的标注信息是否正确参考文章 LabelImg介绍 LabelImg是目标检测数据标注工具&#xff0c;可以标注两种格式&#xff1a; VOC标签格式&#xff0c;标注的标签存储在xml文…

目标检测——蔬菜杂草数据集

引用 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

架构师系列- 消息中间件(12)-kafka基础

1、应用场景 1.1 kafka场景 Kafka最初是由LinkedIn公司采用Scala语言开发&#xff0c;基于ZooKeeper&#xff0c;现在已经捐献给了Apache基金会。目前Kafka已经定位为一个分布式流式处理平台&#xff0c;它以 高吞吐、可持久化、可水平扩展、支持流处理等多种特性而被广泛应用…

22年全国职业技能大赛——Web Proxy配置(web 代理)

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; 系统服务&#xff08;22年国赛&#xff09;—— web Proxy服务&#xff08;web代理&#xff09;https://myweb.myskillstree.cn/114.html 目录 RouterSrv …

强复购、循环消费:排队复购模式助您在市场中脱颖而出

尊敬的各位读者&#xff0c;今天我很高兴向大家介绍一种新颖而又引人入胜的商业模式——排队复购模式。这个模式因其强大的复购属性和循环消费特性而备受瞩目&#xff0c;被誉为电商领域的新宠儿。 为何要介绍排队复购模式&#xff1f;因为它不仅操作简单、容易引起消费者的兴…

BUUCTF_[BSidesCF 2020]Had a bad day

[BSidesCF 2020]Had a bad day 1.一看题目直接尝试文件包含 2.直接报错&#xff0c;确实是存在文件包含漏洞 http://307b4461-36d6-443f-879a-68803a57f721.node5.buuoj.cn:81/index.php?categoryphp://filter/convert.base64-encode/resourceindex strpos() 函数查找字符串…

安卓玩机工具推荐----MTK芯片 简单制作线刷包 备份分区 备份基带 去除锁类 推荐工具操作解析

工具说明 在前面几期mtk芯片类玩机工具中解析过如何无官方固件从手机抽包 制作线刷包的步骤&#xff0c;类似的工具与操作有很多种。演示的只是本人片面的理解与一些步骤解析。mtk芯片机型抽包关键点在于..mt*****txt的分区地址段引导和 perloader临时分区引导。前面几期都是需…

【嵌入式Linux】STM32P1开发环境搭建

要进行嵌入式Linux开发&#xff0c;需要在Windows、Linux和嵌入式Linux3个系统之间来回跑&#xff0c;需要使用多个软件工具。经过了4小时的安装&#xff08;包括下载时间&#xff09;&#xff0c;我怕以后会忘记&#xff0c;本着互利互助的原则&#xff0c;我打算把这些步骤详…

java接口加密解密

这里写目录标题 controller加解密工具类加密&#xff08;本质是对ResponseBody加密&#xff09;解密&#xff08;本质是对RequestBody传参解密&#xff09;注解 controller Controller public class PathVariableController {GetMapping(value "/test")ResponseBod…

IDEA pom.xml依赖警告

IDEA中&#xff0c;有时 pom.xml 中会出现如下提示&#xff1a; IDEA 2022.1 升级了检测易受攻击的 Maven 和 Gradle 依赖项&#xff0c;并建议修正&#xff0c;通过插件 Package Checker 捆绑到 IDE 中。 这并不是引用错误&#xff0c;不用担心。如果实在强迫症不想看到这个提…

使用 FFmpeg 实现录屏和录音

FFmpeg 是一个非常强大的开源工具&#xff0c;可以用来处理音频和视频。可以实现录屏和录音&#xff0c;也可以进行简单的剪辑。 要使用 FFmpeg 进行录屏和录音&#xff0c;需要首先确保系统已经安装了 FFmpeg。在大多数 Linux 发行版中&#xff0c;可以通过包管理器&#xff0…

用友政务财务系统FileDownload接口存在任意文件读取漏洞

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 用友政务财务系统是由用友软件开发的一款针对政府机…

62、回溯-N皇后

思路&#xff1a; N皇后问题要求在一个nn的棋盘上放置n个皇后&#xff0c;使得它们不能相互攻击。皇后可以攻击同一行、同一列&#xff0c;以及两个对角线方向上的其他皇后。解决这个问题意味着找到所有可能的棋盘配置&#xff0c;每个配置都符合上述条件。 1、初始化数据结构…

前端更优雅的使用 jsonp

前端更优雅的使用 jsonp 背景&#xff1a;最近项目中又使用到了 jsonp 这一项跨域的技术&#xff0c;&#xff08;主要还是受同源策略影响&#xff09;&#xff0c;下面有为大家提供封装好的函数及对应使用示例&#xff0c;欢迎大家阅读理解 文章目录 前端更优雅的使用 jsonp同…

【系统架构师】-选择题(四)

1、“41”视图 ①逻辑视图(Logical View)&#xff0c;设计的对象模型(使用面向对象的设计方法时)。 ②过程视图(Process View)&#xff0c;捕捉设计的并发和同步特征。系统集成师 ③物理视图(Physical View)&#xff0c;描述了软件到硬件的映射&#xff0c;反映了分布式特性。系…