一、贪吃蛇游戏介绍
贪吃蛇是久负盛名的游戏,它也和俄罗斯方块,扫雷等游戏位列经典游戏的行列。用C语言来实现贪吃蛇游戏之前 要会 数据结构(链表)、枚举、结构体、动态内存管理、预处理指令、win32API
二、Win32API
1.Win32API
Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程式达到开启 视窗、描绘图形、使⽤周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以便 称之为ApplicationProgrammingInterface,简称API函数。WIN32API也就是MicrosoftWindows 32位平台的应用程序编程接⼝。
2.控制台程序
平常我们运行起来的黑框程序其实就是控制台程序 我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列
//函数system来执行不过在使用时要包含 #include <windows.h> mode con cols=100 lines=30 //也可以通过命令设置控制台窗⼝的名字 title 贪吃蛇
3.控制台屏幕上的坐标COORD
控制台屏幕上的坐标COORD
typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD; //给坐标赋值 COORD pos = { 10, 15 };
4.GetStdHandle
GetStdHandle是⼀个WindowsAPI函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(用来标识不同设备的数值),使⽤这个句柄可以操作设备。
HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
5.GetConsoleCursorInfo
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo;6 GetConsoleCursorInfo(hOutput, &CursorInfo); //获取控制台光标信息
6.CONSOLE_CURSOR_INFO
dwSize,由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完 全填充单元格到单元底部的水平线条。 bVisible,游标的可见性。如果光标可见,则此成员为TRUE。
//这个结构体,包含有关控制台游标的信息 typedef struct _CONSOLE_CURSOR_INFO { DWORD dwSize; BOOL bVisible; }CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO; CursorInfo.bVisible = false; //隐藏控制台光标
7.SetConsoleCursorInfo
设置指定控制台屏幕缓冲区的光标的大小和可见性
BOOL WINAPI SetConsoleCursorInfo ( HANDLE hConsoleOutput, const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo ); HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //影藏光标操作 CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 CursorInfo.bVisible = false; //隐藏控制台光标 SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
8.SetConsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调 ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition( HANDLE hConsoleOutput, COORD pos ); COORD pos = { 10, 5}; HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);
SetPos:封装⼀个设置光标位置的函数:
void SetPos(short x, short y) { COORD pos = { x, y }; HANDLE hOutput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos); }
9.GetAsyncKeyState
GetAsyncKeyState 的返回值是short类型,在上⼀次调用GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
三、贪吃蛇游戏设计与分析
1.地图
如果想在控制台的窗口中指定位置输出信息,我们得知道该位置的坐标,所以首先介绍⼀下控制台窗口的坐标知识。 控制台窗口的坐标如下所示,横向的是X轴,从左向右依次增长,纵向是Y轴,从上到下依次增长
LC_COLLATE • LC_CTYPE • LC_MONETARY • LC_NUMERIC • LC_TIME • LC_ALL-针对所有类项修改
在游戏地图上,我们打印墙体使用宽字符:■,打印蛇使⽤宽字符●,打印食物使用宽字符★ 普通的字符是占⼀个字节的,这类宽字符是占用2个字节。简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。 C语言最初假定字符都是但自己的。但是这些假定并不是在世界的任何地方都适用。后来为了使C语言适应国家化,C语言的标准中不断加⼊了国际化的支持。比如:加入和宽字符的类型 wchar_t 和宽字符的输入和输出函数,加⼊和头文件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语⾔的地理区域)调整程序行为的函数。
2.#include
本地化提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。在标准可以中,依赖地区的部分有以下几项:(1)数字量的格式 (2)货币量的格式 (3) 字符集 (4)日期期和时间的表示形式
3.类项
通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下⾯的⼀个宏, 指定⼀个类项:
LC_COLLATE LC_CTYPE LC_MONETARY LC_NUMERIC LC_TIME LC_ALL 针对所有类项修改
4. setlocale函数:
setlocale函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。setlocale的第⼀个参数可以是前面说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。C标准给第⼆个参数仅定义了2种可能取值:"C"和" "在任意程序执行开始,都会隐藏式执行调用
char* setlocale (int category, const char* locale); setlocale(LC_ALL, "C");
当地区设置为"C"时,库函数按正常方式执行,小数点是⼀个点。当程序运行起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。
5.宽字符的打印
#include <stdio.h> #include<locale.h> int main() { setlocale(LC_ALL, ""); wchar_t ch1 = L'●'; wchar_t ch2 = L'T'; wchar_t ch3 = L'U'; wchar_t ch4 = L'★'; printf("%c%c\n", 'a', 'b'); wprintf(L"%c\n", ch1); wprintf(L"%c\n", ch2); wprintf(L"%c\n", ch3); wprintf(L"%c\n", ch4); return 0; }
从输出的结果来看,我们发现⼀个普通字符占⼀个字符的位置但是打印⼀个汉字字符,占用2个字符的位置,那么我们如果 要在贪吃蛇中使⽤宽字符,就得处理好地图上坐标的计算。普通字符和宽字符打印出宽度的展示如下:
6.地图坐标
假设实现⼀个棋盘27行,58列的棋盘,再围绕地图画出墙
7.蛇身和食物
初始化状态,假设蛇的⻓度是5,蛇⾝的每个节点是●,在固定的⼀个坐标处,⽐如(24,5)处开始出现 蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半 ⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬。 关于⻝物,就是在墙体内随机⽣成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的⾝体重合,然 后打印★。
8.数据结构设计
在游戏运行的过程中,蛇每次吃⼀个⻝物,蛇的身体就会变长⼀节,如果我们使⽤链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好舍⾝节点在地图上的坐标就⾏, 所以舍蛇节点结构如下:
typedef struct SnakeNode { int x; int y; struct SnakeNode* next; }SnakeNode, * pSnakeNode;
//要管理整条贪吃蛇,我们再封装⼀个Snake的结构来维护整条贪吃蛇 typedef struct Snake { pSnakeNode _pSnake;//维护整条蛇的指针 pSnakeNode _pFood;//维护⻝物的指针 enum DIRECTION _Dir;//蛇头的⽅向默认是向右 enum GAME_STATUS _Status;//游戏状态 int _Socre;//当前获得分数 int _Add;//默认每个⻝物10分 int _SleepTime;//每⾛⼀步休眠时间 }Snake, * pSnake;
//蛇的⽅向,可以⼀⼀列举,使⽤枚举 //⽅向 enum DIRECTION { UP = 1, DOWN, LEFT, RIGHT };
//游戏状态 可以⼀⼀列举,使⽤枚举 enum GAME_STATUS { OK,//正常运⾏ KILL_BY_WALL,//撞墙 KILL_BY_SELF,//咬到⾃⼰ END_NOMAL//正常结束 };
9.游戏流程设计
四、核心逻辑实现
1.游戏主要部分
#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("再来⼀局吗?(X/T):"); ch = getchar(); getchar();//清理\n } while (ch == 'X'); SetPos(0, 27); } int main() { //修改当前地区为本地模式,为了⽀持中⽂宽字符的打印 setlocale(LC_ALL, ""); //测试 test(); return 0; }
2.游戏开始
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); }
2.1打印欢迎界面:
void WelComeToGame() { //定位光标 SetPos(40,14); printf("欢迎来到贪吃蛇游戏"); SetPos(40, 25); system("pause");//pause是暂停 system("cls");//清理屏幕 SetPos(20, 14); printf("使用↑,↓,←,→,分别控制蛇的移动,F3是加速,F4是减速"); SetPos(40, 25); system("pause"); system("cls"); }
2.2创建地图:创建地图就是将强打印出来,因为是宽字符打印,所有使用wprintf函数,打印格式串前使⽤L■打印地图的关键是要算好坐标,才能在想要的位置打印墙体。 墙体打印的宽字符:
#define WALL L'■'
创建地图函数CreateMap:
void CreateMap() { SetPos(0, 0); int i = 0; for (i = 0; i <= 56; i += 2)//上 { wprintf(L"%lc", WALL); } SetPos(0, 26); for (i = 0; i <= 56; i += 2)//下 { wprintf(L"%lc", WALL); } for (i = 0; i <= 25; i++)//左 { SetPos(0,i); wprintf(L"%lc",WALL); } for (i=1;i<=25;i++)//右 { SetPos(56,i); wprintf(L"%lc",WALL); } }
2.3初始化蛇身:蛇最开始长度为5节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇⾝后,将蛇的每⼀节打印在屏幕上再设置当前游戏的状态,蛇移动的速度,默认的⽅向,初始成绩,蛇的状态,每个⻝物的分数。 蛇⾝打印的宽字符
#define BODY L'●'
初始化蛇函数:InitSnake
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->x = POS_X + 2*i; cur->y = POS_Y; cur->next = NULL; //头插 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"%lc",BODY); cur = cur->next; } ps->_Status = OK; ps->_Score = 0; ps->_pFood = NULL; ps->_SleepTime = 200; ps->_FoodWeight = 10; ps->_Dir = RIGHT; }
2.4创建第⼀个食物:先随机生成食物的坐标,x坐标必须是2的倍数,食物的坐标不能和蛇身每个节点的坐标重复。创建食物节点,打印食物。
//食物打印的宽字符 #define FOOD L'★'
创建食物的函数:CreateFood
void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0);//x坐标必须是2的倍数 //坐标和蛇的身体冲突 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; } pFood->x = x; pFood->y = y; ps->_pFood = pFood; //打印食物 SetPos(x,y); wprintf(L"%lc",FOOD); }
3.游戏运行
游戏运行期间,右侧打印帮助信息,提示玩家 根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。确定了蛇的方向和速度,蛇就可以移动了
void GameRun(pSnake ps) { PrintHelpInfo(); do { SetPos(64,10); printf("得分:%04d",ps->_Score); SetPos(64,11); printf("每个食物:%2d",ps->_FoodWeight); 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_ESCAPE)) { ps->_Status = END_NORMAL; break; } else if (KEY_PRESS(VK_SPACE)) { Pause(); } else if (KEY_PRESS(VK_F3))//加速 { if (ps->_SleepTime >= 80) { ps->_SleepTime -= 30; ps->_FoodWeight += 2; } } else if (KEY_PRESS(VK_F4))//减速 { if (ps->_SleepTime < 320) { ps->_SleepTime += 30; ps->_FoodWeight -= 2; } } Sleep(ps->_SleepTime); SnakeMove(ps); } while (ps->_Status == OK); }
3.1:KEY_PRESS
//检测按键状态,封装了⼀个宏 #define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
PrintHelpInfo
void PrintHelpInfo() { SetPos(64, 15); printf("1.不能穿墙,不能咬自己"); SetPos(64, 16); printf("2.使用↑,↓,←,→,分别控制蛇的移动"); SetPos(64, 17); printf("3.F3加速,F4减速"); SetPos(64, 18); printf("4.ESC-退出,空格暂停游戏"); }
3.2 蛇身移动:先创建下⼀个节点,根据移动方向和蛇头的坐标,蛇移动到下⼀个位置的坐标。 确定了下⼀个位置后,看下⼀个位置是否是⻝物(NextIsFood),是食物就做吃食物处理 (EatFood),如果不是食物则做前进⼀步的处理(NoFood)。蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上⾃⼰蛇⾝(KillBySelf),从而影响游戏的状态
void SnakeMove(pSnake ps) { pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove()::malloc()"); return; } pNext->next = NULL; switch (ps->_Dir) { case UP: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y - 1; break; case DOWN: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y + 1; break; case LEFT: pNext->x = ps->_pSnake->x - 2; pNext->y = ps->_pSnake->y; break; case RIGHT: pNext->x = ps->_pSnake->x + 2; pNext->y = ps->_pSnake->y; break; } //判断蛇头到达的坐标处是否是食物 if (NextIsFood(ps,pNext)) { //吃掉食物 EatFood(ps, pNext); } else { //不是食物 NoFood(ps, pNext); } //蛇是否撞墙 KillByWall(ps); //蛇是否自杀 KillBySelf(ps); }
NextIsFood:
int NextIsFood(pSnake ps,pSnakeNode pnext) { if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y) { return 1; } else { return 0; } }
EatFood:
//吃掉食物 void EatFood(pSnake ps, pSnakeNode pnext) { //头插 pnext->next = ps->_pSnake; ps->_pSnake = pnext; //打印蛇 pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc",BODY); cur = cur->next; } free(ps->_pFood); ps->_Score += ps->_FoodWeight; CreateFood(ps);//新创建食物 }
NoFood:将下⼀个节点头插⼊蛇的⾝体,并将之前蛇身最后⼀个节点打印为空格,放弃掉蛇⾝的最后⼀个节点
//不吃食物 void NoFood(pSnake ps, pSnakeNode pnext) { //头插 pnext->next = ps->_pSnake; ps->_pSnake = pnext; //打印蛇 pSnakeNode cur = ps->_pSnake; while (cur->next->next) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } SetPos(cur->next->x, cur->next->y); printf(" "); free(cur->next); cur->next = NULL; }
KillByWall:判断蛇头的坐标是否和强的坐标冲突
//蛇是否撞墙 void 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; }
KillBySelf: 判断蛇头的坐标是否和蛇身体的坐标冲突
//蛇是否自杀 void 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; cur = cur->next; } }
4.游戏结束
游戏状态不再是OK(游戏继续)的时候,要告知游戏技术的原因,并且释放蛇身节点
void GameEnd(pSnake ps) { SetPos(20,12); switch (ps->_Status) { case END_NORMAL: printf("你主动退出游戏\n"); break; case KILL_BY_SELF: printf("游戏自杀,游戏结束\n"); break; case KILL_BY_WALL: printf("撞墙了,游戏结束\n"); break; } //释放蛇身的结点 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps->_pSnake = NULL; }
五、完整代码
https://gitee.com/jjawei/tu-data-structure/tree/master/Gluttonous%20snake
Snake.c
#include"snake.h" //设置光标的坐标 void SetPos(short x, short y) { COORD pos = { x,y }; HANDLE hOutput = NULL; //获得标准输出的句柄(用来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput,pos); } void WelComeToGame() { //定位光标 SetPos(40,14); printf("欢迎来到贪吃蛇游戏"); SetPos(40, 25); system("pause");//pause是暂停 system("cls");//清理屏幕 SetPos(20, 14); printf("使用↑,↓,←,→,分别控制蛇的移动,F3是加速,F4是减速"); SetPos(40, 25); system("pause"); system("cls"); } void CreateMap() { SetPos(0, 0); int i = 0; for (i = 0; i <= 56; i += 2)//上 { wprintf(L"%lc", WALL); } SetPos(0, 26); for (i = 0; i <= 56; i += 2)//下 { wprintf(L"%lc", WALL); } for (i = 0; i <= 25; i++)//左 { SetPos(0,i); wprintf(L"%lc",WALL); } for (i=1;i<=25;i++)//右 { SetPos(56,i); wprintf(L"%lc",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->x = POS_X + 2*i; cur->y = POS_Y; cur->next = NULL; //头插 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"%lc",BODY); cur = cur->next; } ps->_Status = OK; ps->_Score = 0; ps->_pFood = NULL; ps->_SleepTime = 200; ps->_FoodWeight = 10; ps->_Dir = RIGHT; } void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0);//x坐标必须是2的倍数 //坐标和蛇的身体冲突 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; } pFood->x = x; pFood->y = y; ps->_pFood = pFood; //打印食物 SetPos(x,y); wprintf(L"%lc",FOOD); } void GameStart(pSnake ps) { //控制台窗口的设置 system("mode con cols=100 lines=30"); 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 PrintHelpInfo() { SetPos(64, 15); printf("1.不能穿墙,不能咬自己"); SetPos(64, 16); printf("2.使用↑,↓,←,→,分别控制蛇的移动"); SetPos(64, 17); printf("3.F3加速,F4减速"); SetPos(64, 18); printf("4.ESC-退出,空格暂停游戏"); SetPos(64, 20); printf("游戏版权归@TU^"); } void Pause() { while (1) { Sleep(100); if (KEY_PRESS(VK_SPACE)) { break; } } } int NextIsFood(pSnake ps,pSnakeNode pnext) { if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y) { return 1; } else { return 0; } } //吃掉食物 void EatFood(pSnake ps, pSnakeNode pnext) { //头插 pnext->next = ps->_pSnake; ps->_pSnake = pnext; //打印蛇 pSnakeNode cur = ps->_pSnake; while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc",BODY); cur = cur->next; } free(ps->_pFood); ps->_Score += ps->_FoodWeight; CreateFood(ps);//新创建食物 } //不吃食物 void NoFood(pSnake ps, pSnakeNode pnext) { //头插 pnext->next = ps->_pSnake; ps->_pSnake = pnext; //打印蛇 pSnakeNode cur = ps->_pSnake; while (cur->next->next) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } SetPos(cur->next->x, cur->next->y); printf(" "); free(cur->next); cur->next = NULL; } //蛇是否撞墙 void 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; } //蛇是否自杀 void 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; cur = cur->next; } } void SnakeMove(pSnake ps) { pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove()::malloc()"); return; } pNext->next = NULL; switch (ps->_Dir) { case UP: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y - 1; break; case DOWN: pNext->x = ps->_pSnake->x; pNext->y = ps->_pSnake->y + 1; break; case LEFT: pNext->x = ps->_pSnake->x - 2; pNext->y = ps->_pSnake->y; break; case RIGHT: pNext->x = ps->_pSnake->x + 2; pNext->y = ps->_pSnake->y; break; } //判断蛇头到达的坐标处是否是食物 if (NextIsFood(ps,pNext)) { //吃掉食物 EatFood(ps, pNext); } else { //不是食物 NoFood(ps, pNext); } //蛇是否撞墙 KillByWall(ps); //蛇是否自杀 KillBySelf(ps); } void GameRun(pSnake ps) { PrintHelpInfo(); do { SetPos(64,10); printf("得分:%04d",ps->_Score); SetPos(64,11); printf("每个食物:%2d",ps->_FoodWeight); 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_ESCAPE)) { ps->_Status = END_NORMAL; break; } else if (KEY_PRESS(VK_SPACE)) { Pause(); } else if (KEY_PRESS(VK_F3))//加速 { if (ps->_SleepTime >= 80) { ps->_SleepTime -= 30; ps->_FoodWeight += 2; } } else if (KEY_PRESS(VK_F4))//减速 { if (ps->_SleepTime < 320) { ps->_SleepTime += 30; ps->_FoodWeight -= 2; } } Sleep(ps->_SleepTime); SnakeMove(ps); } while (ps->_Status == OK); } void GameEnd(pSnake ps) { SetPos(20,12); switch (ps->_Status) { case END_NORMAL: printf("你主动退出游戏\n"); break; case KILL_BY_SELF: printf("游戏自杀,游戏结束\n"); break; case KILL_BY_WALL: printf("撞墙了,游戏结束\n"); break; } //释放蛇身的结点 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps->_pSnake = NULL; }
Snake.h
#pragma once #include<locale.h> #include<stdlib.h> #include<windows.h> #include<stdbool.h> #include<stdio.h> #include<time.h> #define WALL L'■' #define BODY L'●' #define FOOD L'★' #define POS_X 24 #define POS_Y 5 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ) enum DIREDCTION // { UP = 1, DOWN, LEFT, RIGHT }; enum GAME_STATUS { OK,//正常运行 END_NORMAL,//按ESC退出 KILL_BY_WALL, KILL_BY_SELF }; typedef struct SnakeNode { //坐标 int x; int y; struct SnakeNode* next; }SnakeNode,* pSnakeNode; //typedef struct SnakeNode* pSnakeNode //贪吃蛇的结构 typedef struct Snake { pSnakeNode _pSnake;//指向贪吃蛇 pSnakeNode _pFood;//指向食物结点的指针 int _Score;//贪吃蛇累计的总分 int _FoodWeight;//一个食物的分数 int _SleepTime;//每一步休息的时间,时间越短,速度越快,时间越长,速度越慢 enum DIREDCTION _Dir;//描述蛇的方向 enum GAME_STATUS _Status;//游戏的状态:正常,退出,撞墙,吃到自己 }Snake,*pSnake; //游戏开始-完成游戏的初始化动作 void GameStart(pSnake ps); //定位坐标 void SetPos(short x, short y); //游戏开始的欢迎界面 void WelComeToGame(); //打印地图 void CreateMap(); //初始化贪吃蛇 void InitSnake(pSnake ps); //创建食物 void CreateFood(pSnake ps); //游戏正常运行 void GameRun(pSnake ps); //打印帮助信息 void PrintHelpInfo(); //游戏暂停和恢复 void Pause(); //蛇的移动 void SnakeMove(pSnake ps); //判断蛇头到达的坐标处是否是食物 int NextIsFood(pSnake ps, pSnakeNode pnext); //吃掉食物 void EatFood(pSnake ps, pSnakeNode pnext); //不吃食物 void NoFood(pSnake ps, pSnakeNode pnext); //蛇是否撞墙 void KillByWall(pSnake ps); //蛇是否自杀 void KillBySelf(pSnake ps); //游戏的善后处理 void GameEnd(pSnake ps);
test.c
#include"snake.h" int main() { //设置程序适应本地环境 setlocale(LC_ALL,""); srand((unsigned int )time(NULL)); //test(); int ch = 0; do { Snake snake = { 0 };//创建了贪吃蛇 //1,游戏开始——初始化游戏 GameStart(&snake); //2,游戏运行——游戏的正常运行过程 GameRun(&snake); //3,游戏结束——游戏善后(释放资源) GameEnd(&snake); SetPos(20, 15); printf("再来一局吗?(X/T)"); ch = getchar(); getchar();//清理掉\n } while (ch == 'x' || ch == 'X'); SetPos(0, 27); return 0; }
以上就是贪吃蛇的基本内容啦