文章目录
- 前言
- 1.了解Win32API相关知识
- 1.1什么是Win32API
- 1.2设置控制台的大小、名称
- 1.3控制台上的光标
- 1.4 GetStdHandle(获得控制台信息)
- 1.5 SetConsoleCursorPosition(设置光标位置)
- 1.6 GetConsoleCursorInfo(获得光标信息)
- 1.7 SetConsoleCursorInfo(设置光标信息)
- 1.8 GetAsyncKeyState(获取按键信息)
- 2.游戏设计与分析
- 2.1地图设计
- 2.2数据结构的设计
- 2.2.1蛇身的设计
- 2.2.2蛇的方向
- 2.2.3游戏的状态
- 2.2.4管理整条蛇
- 2.3.游戏流程
- 3. 功能的具体实现
- 游戏开始前
- 3.1 初始化蛇
- 3.2 创建食物
- 游戏运行
- 3.3 检测按键
- 3.4 蛇移动
- 3.5 判断节点是否是食物
- 3.6 吃食物
- 3.7 不吃食物
- 3.8 撞墙
- 3.9 撞到自己
- 3.10 游戏结束
- 3.11 main函数
- 4.参考代码
- 4.1 main
- 4.2 Snake.h
- 4.3 Snake.c
前言
彻底学习完C语言之后,为巩固所学,我们来实现一个贪吃蛇小游戏。
该游戏涉及到的C语言的知识有:函数、枚举、结构体、动态内存管理、预处理指令。
另外,还包含数据结构(链表)以及Win32API相关知识。
先看一下游戏效果吧
目前该游戏还有很多可以改进的地方,由于蛇的移动使用的是Sleep函数,看起来有点卡顿,而且按键会有一点延迟。
代码仓库链接
1.了解Win32API相关知识
1.1什么是Win32API
Win32 API也就是Microsoft Windows32位平台的应用程序编程接口。调用各种接口,可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数。
1.2设置控制台的大小、名称
设置大小和名称的命令为:
- mode con cols=列 lines=行
- tiele 名称
如果想在C语言中设置窗口,需要使用system函数。
1.3控制台上的光标
COORD 是Windows API中定义的⼀个结构体,表示⼀个字符在控制台屏幕上的坐标,坐标系(0,0)的原点位于缓冲区的左侧顶部的单元格。使用需要window.h的头文件
COORD类型的声明:
typedef struct _COORD { SHORT X;//横坐标 SHORT Y;//纵坐标 } COOR, *PCOORD;
1.4 GetStdHandle(获得控制台信息)
GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使用这个句柄可以操作设备(控制台)。
HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle);
该函数需要一个参数,参数如下:
1.5 SetConsoleCursorPosition(设置光标位置)
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition( HANDLE hConsoleOutput, /要设置哪个控制台 COORD pos /将光标设置的位置 );
通过上述三个功能,实现设置光标位置
1.6 GetConsoleCursorInfo(获得光标信息)
GetConsoleCursorInfo:检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。然后将获得的信息放在一个结构体中
BOOL WINAPI GetConsoleCursorInfo( _In_ HANDLE hConsoleOutput, _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo );
_ Out_ PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构体的指针,
该结构体,包含有关控制台光标的信息
typedef struct _CONSOLE_CURSOR_INFO { DWORD dwSize; BOOL bVisible; } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
- dwSize,由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条。
- bVisible,光标的可见性。 如果光标可见,则此成员为 TRUE。
1.7 SetConsoleCursorInfo(设置光标信息)
SetConsoleCursorInfo:设置指定控制台屏幕缓冲区的光标的大小和可见性。
其参数和get方法相同
BOOL WINAPI SetConsoleCursorInfo( HANDLE hConsoleOutput, const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo );
设置前:
设置后:
隐藏光标:
1.8 GetAsyncKeyState(获取按键信息)
GetAsyncKeyState:获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState( int vKey );
- 将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。了解虚拟键值点击这里
- GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0
- 如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
#include
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"); else if (KEY_PRESS(0x36)) printf("6\n"); else if (KEY_PRESS(0x37)) printf("7\n"); } return 0; } 2.游戏设计与分析
2.1地图设计
由于我打印墙体和蛇想使用 □,但是它是宽字符,该如何打印呢?
C语言规定:宽字符的类型wchar_t 和宽字符的输⼊和输出函数。
头文件为:
宽字符与普通字符对比:
由于墙体使用的是宽字符,所以在打印时需要注意!
以下函数实现打印墙体的功能:
#define WALL L'□' void DrawMap() { SetPos(0, 0); //上 for (int i = 0; i <= 56; i += 2) wprintf(L"%lc", WALL); //左、右 for (int i = 1; i <= 25; i++) { SetPos(0, i); wprintf(L"%lc", WALL); SetPos(56, i); wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (int i = 0; i <= 56; i += 2) wprintf(L"%lc", WALL); }
再打印地图之前,我们需要设置窗口的大小,并且给玩家一些引导(Win32API的使用已在上面讲过)
//窗口相关设置 void ScreenPrepare() { //设置窗体大小 system("mode con cols=100 lines=40"); system("title 贪吃蛇小游戏"); //隐藏光标 HideCurSor(); //打印欢迎语 Welcome(); //打印地图 DrawMap(); //显示提示信息 HelpInfo(); }
具体函数的实现我们会在后面给出,先看一下效果吧~
2.2数据结构的设计
2.2.1蛇身的设计
在游戏运行的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变长⼀节,如果我们使用链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,所以蛇节点结构如下:
//蛇节点的定义 typedef struct SnakeNode { int x; int y; struct SnakeNode* next;//指向下一个节点 }SnakeNode,*pSnakeNode;//同时定义一个指向该节点的指针
2.2.2蛇的方向
蛇的方向可以一一列举,使用枚举
//蛇的方向 //上、下、做、左、右 enum Direction { UP = 1, DOWN, LEFT, RIGHT };
2.2.3游戏的状态
游戏状态,可以⼀⼀列举,使用枚举
//游戏的状态 enum GameState { OK,//正常 KILL_BY_WALL,//撞墙 KILL_BY_SELF,//撞到自己 END//结束 };
2.2.4管理整条蛇
我们再封装⼀个Snake的结构体来维护整条贪吃蛇。
//整条蛇 typedef struct Snake { pSnakeNode pSnake; //指向整条蛇的指针 pSnakeNode PFood; //指向食物的指针 enum Direction Dire; //蛇的方向 enum GameState State; //蛇的状态 int SleepTime; //蛇的休眠时间;休眠越短,蛇移动的越快 int Score;//游戏得分 int FoodScore;//一个食物的分数 }Snake,* pSnake;
2.3.游戏流程
3. 功能的具体实现
游戏开始前
3.1 初始化蛇
游戏开始,蛇的长度默认是3。
- 创建节点,头插法依次连接
- 节点的纵坐标相同,横坐标依次增加2,产生串的效果
- 蛇的方向默认为右
- 打印蛇,并且为蛇头设置颜色
此处颜色设置我们使用Win32API提供的函数:
BOOL WINAPI SetConsoleTextAttribute( _In_ HANDLE hConsoleOutput,//当前设备句柄 _In_ WORD wAttributes//颜色 );
//设置颜色 void SetColor(int HeadColor) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HeadColor); } //打印蛇 void PrintSnake(pSnake ps) { pSnakeNode cur = ps->pSnake; int flag = 1; while (cur) { if (flag == 1) { //为蛇头设置颜色 SetColor(12); } SetPos(cur->x, cur->y);//设置每个节点的位置 wprintf(L"%lc", BODY); cur = cur->next; flag = 0; SetColor(10); } } //初始化蛇 void InitSnake(pSnake ps) { int i = 0; pSnakeNode cur = NULL; //蛇的长度开始为3 for (i = 0; i < 3; i++) { cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake():malloc"); return; } cur->next = NULL; cur->x = POS_X + i * 2; //节点横坐标依次增加2 cur->y = POS_Y; //头插法将节点相连 if (ps->pSnake == NULL) { ps->pSnake = cur; } else { cur->next = ps->pSnake; ps->pSnake = cur; } } //打印蛇 PrintSnake(ps); //初始化蛇的其它信息 ps->Dire = RIGHT; ps->FoodScore = 10; ps->SleepTime = 200; ps->State = OK; ps->Score = 0; }
3.2 创建食物
食物要随机生成,有以下几个注意事项:
- 食物应该在墙体内
- 食物不可与蛇身重叠
- 由于我们蛇的打印使用的是宽字符,所以食物的坐标应该在2的倍数处
//创建食物 void CreatFood(pSnake ps) { int x = 0; int y = 0; //食物坐标应该在墙体内 again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0); //食物坐标不与蛇重叠 pSnakeNode cur = ps->pSnake; while (cur) { if (x == cur->x && y == cur->y) { goto again; } cur = cur->next; } //根据坐标创建食物 pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pFood == NULL) { perror("CreatFood():malloc()"); return; } //打印食物 pFood->x = x; pFood->y = y; SetColor(14); SetPos(pFood->x, pFood->y); wprintf(L"%lc", FOOD); SetColor(10); ps->pFood = pFood; }
游戏运行
3.3 检测按键
玩家按上下左右键控制蛇的移动,在检测时需要注意:
- 玩家按的键若与蛇的方向相反,则不做响应;玩家按键符合,则修改蛇的方向
- 玩家按F1或F2修改蛇的移动速度;蛇的速度应大于0,食物的分数最低为1分
- 按键的检测使用Win32API提供的功能:GetAsyncKeyState
//玩游戏 void GameRun(pSnake ps) { do { //显示分数 SetColor(12); SetPos(65, 12); printf("目前得分:%-5d",ps->Score); SetColor(10); SetPos(65, 13); printf("每个食物:%2d分",ps->FoodScore); //检测按键 if (KEY_PRESS(VK_UP) && ps->Dire != DOWN) { //按上键,且蛇的方向不能向下 ps->Dire = UP; } else if (KEY_PRESS(VK_DOWN) && ps->Dire != UP) { //按下键,且蛇的方向不能向上 ps->Dire = DOWN; } else if (KEY_PRESS(VK_LEFT) && ps->Dire != RIGHT) { //按左键,且蛇的方向不能向右 ps->Dire = LEFT; } else if (KEY_PRESS(VK_RIGHT) && ps->Dire != LEFT) { //按右键,且蛇的方向不能向左 ps->Dire = RIGHT; } else if (KEY_PRESS(VK_ESCAPE)) { //按ESC,退出游戏 ps->State = END; } else if (KEY_PRESS(VK_SPACE)) { //按空格,暂停 pause(); } else if (KEY_PRESS(VK_F1)) { //按F1加速,即睡眠时间变短 //休眠时间不能是负数,最快就是休眠30ms if (ps->SleepTime >= 50) { ps->SleepTime -= 20; //速度变快,食物的分数变高 ps->FoodScore += 2; } } else if (KEY_PRESS(VK_F2)) { //F2减速,睡眠时间变长 //食物的分数不能减到负数,最多减为1分 if (ps->FoodScore >= 3) { ps->SleepTime += 20; ps->FoodScore -= 2; } } //按照蛇的睡眠时间,真正实现休眠 Sleep(ps->SleepTime); //休眠后,蛇要移动 SnakeMove(ps); } while (ps->State == OK);//只有蛇的状态为OK,才能继续检测按键 }
3.4 蛇移动
蛇的移动就是根据蛇的方向,产生节点,判断节点是不是食物。
- 所产生的节点的位置根据蛇的方向而定
- 节点是食物,吃掉食物,蛇身变长
- 节点不是食物,也吃掉食物,但长度不变
//移动蛇 void SnakeMove(pSnake ps) { //先产生节点 pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove():malloc()"); return; } //根据蛇的方向,设定节点的位置 switch (ps->Dire) { case UP: //蛇的方向向上,节点应该在当前蛇头的上方;横坐标不变,纵坐标减1 pNext->x = ps->pSnake->x; pNext->y = ps->pSnake->y - 1; break; case DOWN: //蛇的方向向下,节点应该在当前蛇头的下方;横坐标不变,纵坐标加1 pNext->x = ps->pSnake->x; pNext->y = ps->pSnake->y + 1; break; case LEFT: //蛇的方向向左,节点应该在当前蛇头的左边;纵坐标不变,横坐标减2 pNext->x = ps->pSnake->x - 2; pNext->y = ps->pSnake->y; break; case RIGHT: //蛇的方向向右,节点应该在当前蛇头的右方;纵坐标不变,横坐标加2 pNext->x = ps->pSnake->x + 2; pNext->y = ps->pSnake->y; break; } //判断下一个节点是否是食物 if (JudgeNext(ps,pNext)) { //是食物,吃掉,长度增加 EatFood(ps, pNext); } else { //不是食物,吃掉,长度不增加 NoFood(ps, pNext); } //未完 //撞墙 //撞自己 }
3.5 判断节点是否是食物
将所产生的节点与食物的节点对比即可
//判断节点是否是食物 int JudgeNext(pSnake ps, pSnakeNode pNext) { return ((ps->pFood->x == pNext->x) && (ps->pFood->y == pNext->y)); }
3.6 吃食物
吃掉食物很简单,将所产生的节点与蛇想连;连接后得分增加,并释放所产生的节点,再次产生食物。
//是食物,吃掉,长度增加 void EatFood(pSnake ps, pSnakeNode pNext) { //吃掉食物,头插法将节点插入 pNext->next = ps; ps = pNext; //打印蛇 PrintSnake(ps_->snake); //加分 PrintSnake(ps->pSnake); //释放食物节点 free(ps->pFood); //再次创建食物 CreatFood(ps); }
3.7 不吃食物
不吃食物需要将产生的节点与蛇相连,然后删除蛇尾(将蛇尾打印尾空格,并释放蛇尾节点)
//不吃食物 void NoFood(pSnake ps, pSnakeNode pNext) { //头插法连接 pNext->next = ps->pSnake; ps->pSnake = pNext; //删除蛇尾 pSnakeNode cur = ps->pSnake; //找到蛇尾的前一个节点 while (cur->next->next) { cur = cur->next; } pSnakeNode del = cur->next; //将蛇尾打印尾空格 SetPos(del->x, del->y); printf(" "); //释放蛇尾节点 free(del); cur->next = NULL; //打印蛇 PrintSnake(ps); }
3.8 撞墙
若蛇头坐标与墙体坐标重合,则说明撞墙了。
//撞墙 void KillByWall(pSnake ps) { if (ps->pSnake->x == 0 || ps->pSnake->x == 56 || ps->pSnake->y == 0 || ps->pSnake->y == 26) { ps->State = KillByWall; } }
3.9 撞到自己
若蛇头坐标与蛇身节点的坐标重合,则说明撞到自己了。
//撞到自己 void KillBySelf(pSnake ps) { pSnakeNode cur = ps->pSnake->next; while (cur) { if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y) { ps->State = KILL_BY_SELF; } cur = cur->next; } }
3.10 游戏结束
给出游戏结束的原因,释放蛇身节点。
//游戏结束 void GameEnd(pSnake ps) { SetPos(20, 13); SetColor(12); switch (ps->State) { case END: printf("您结束了游戏"); break; case KILL_BY_SELF: printf("很遗憾!您撞到了自己"); break; case KILL_BY_WALL: printf("很遗憾!您撞墙了"); break; } pSnakeNode cur = ps->pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps = NULL; SetColor(10); }
3.11 main函数
让玩家选择
int main() { srand((unsigned int)time(NULL)); setlocale(LC_ALL, "");//适应本地中文环境 int ch = 0; do { Snake snake = { 0 };//创建贪吃蛇 //游戏开始前的初始化 GameStart(&snake); 玩游戏 GameRun(&snake); 游戏结束,善后工作 GameEnd(&snake); SetPos(20, 15); printf("还要再来一局吗?(Y/N)"); ch = getchar(); getchar();//吸收换行 } while (ch == 'Y' || ch == 'y'); SetPos(0, 30); return 0; }
4.参考代码
4.1 main
#define _CRT_SECURE_NO_WARNINGS 1 #include"Snake.h" int main() { srand((unsigned int)time(NULL)); setlocale(LC_ALL, "");//适应本地中文环境 int ch = 0; do { Snake snake = { 0 };//创建贪吃蛇 //游戏开始前的初始化 GameStart(&snake); 玩游戏 GameRun(&snake); 游戏结束,善后工作 GameEnd(&snake); SetPos(20, 15); printf("还要再来一局吗?(Y/N)"); ch = getchar(); getchar();//吸收换行 } while (ch == 'Y' || ch == 'y'); SetPos(0, 30); return 0; }
4.2 Snake.h
#define _CRT_SECURE_NO_WARNINGS 1 #include
#include #include #include #include #include #define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0) #define WALL L'□'//墙体 #define BODY L'●'//蛇身 #define FOOD L'★'//食物 //蛇初始位置 #define POS_X 24 #define POS_Y 12 //蛇的方向 //上、下、左、右 enum Direction { UP = 1, DOWN, LEFT, RIGHT }; //游戏的状态 enum GameState { OK,//正常 KILL_BY_WALL,//撞墙 KILL_BY_SELF,//撞到自己 END//结束 }; //蛇节点的定义 typedef struct SnakeNode { int x; int y; struct SnakeNode* next;//指向下一个节点 }SnakeNode,*pSnakeNode;//同时定义一个指向该节点的指针 //整条蛇 typedef struct Snake { pSnakeNode pSnake; //指向整条蛇的指针 pSnakeNode pFood; //指向食物的指针 enum Direction Dire; //蛇的方向 enum GameState State; //蛇的状态 int SleepTime; //蛇的休眠时间;休眠越短,蛇移动的越快 int Score;//游戏得分 int FoodScore;//一个食物的分数 }Snake,* pSnake; //设置光标位置 void SetPos(int x, int y); //设置颜色 void SetColor(int HeadColor); //游戏准备 void GameStart(pSnake psnake); //初始化蛇 void InitSnake(pSnake psnake); //创建食物 void CreatFood(pSnake ps); //打印蛇 void PrintSnake(pSnake ps); //玩游戏 void GameRun(pSnake ps); //移动蛇 void SnakeMove(pSnake ps); //判断节点是否是食物 int JudgeNext(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); 4.3 Snake.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"Snake.h" //隐藏光标 void HideCurSor() { CONSOLE_CURSOR_INFO cursor = { 0 }; HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleCursorInfo(handle, &cursor); cursor.bVisible = false; SetConsoleCursorInfo(handle, &cursor); } //设置光标位置 void SetPos(int x, int y) { COORD pos = { x,y }; //要设置的位置 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//哪个设备 SetConsoleCursorPosition(handle, pos);//设置 } void Welcome() { //显示欢迎语一 SetPos(38, 15); printf("欢迎来到贪吃蛇小游戏!"); SetPos(40, 24); system("pause"); system("cls");//清屏 //提示语 SetPos(25, 15); printf("按 ↑、↓、←、→ 控制蛇蛇的移动,F1为加速,F2为减速,"); SetPos(25, 16); printf("加速将能得到更高的分数"); SetPos(40, 24); system("pause"); } //打印地图 void DrawMap() { system("cls"); SetColor(6); SetPos(0, 0); //上 for (int i = 0; i <= 56; i += 2) wprintf(L"%lc", WALL); //左、右 for (int i = 1; i <= 25; i++) { SetPos(0, i); wprintf(L"%lc", WALL); SetPos(56, i); wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (int i = 0; i <= 56; i += 2) wprintf(L"%lc", WALL); SetColor(10); } //显示提示信息 void HelpInfo() { SetPos(65, 18); printf("不能穿墙,不能碰到自己,"); SetPos(65, 19); printf("按 ↑、↓、←、→ 控制蛇蛇的移动,"); SetPos(65, 21); printf("F1为加速,F2为减速,"); SetPos(65, 22); printf("ESC:退出游戏 SPACE:暂停"); } //窗口相关设置 void ScreenPrepare() { //设置窗体大小 system("mode con cols=100 lines=40"); system("title 贪吃蛇小游戏"); //隐藏光标 HideCurSor(); //打印欢迎语 Welcome(); //打印地图 DrawMap(); //显示提示信息 HelpInfo(); } //设置颜色 void SetColor(int HeadColor) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HeadColor); } //打印蛇 void PrintSnake(pSnake ps) { pSnakeNode cur = ps->pSnake; int flag = 1; while (cur) { if (flag == 1) { //为蛇头设置颜色 SetColor(12); } SetPos(cur->x, cur->y);//设置每个节点的位置 wprintf(L"%lc", BODY); cur = cur->next; flag = 0; SetColor(10); } } //初始化蛇 void InitSnake(pSnake ps) { int i = 0; pSnakeNode cur = NULL; //蛇的长度开始为3 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; //节点横坐标依次增加2 cur->y = POS_Y; //头插法将节点相连 if (ps->pSnake == NULL) { ps->pSnake = cur; } else { cur->next = ps->pSnake; ps->pSnake = cur; } } //打印蛇 PrintSnake(ps); //初始化蛇的其它信息 ps->Dire = RIGHT; ps->FoodScore = 10; ps->SleepTime = 200; ps->State = OK; ps->Score = 0; } //创建食物 void CreatFood(pSnake ps) { int x = 0; int y = 0; //食物坐标应该在墙体内 again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0); //食物坐标不与蛇重叠 pSnakeNode cur = ps->pSnake; while (cur) { if (x == cur->x && y == cur->y) { goto again; } cur = cur->next; } //根据坐标创建食物 pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pFood == NULL) { perror("CreatFood():malloc()"); return; } //打印食物 pFood->x = x; pFood->y = y; SetColor(14); SetPos(pFood->x, pFood->y); wprintf(L"%lc", FOOD); SetColor(10); ps->pFood = pFood; } //游戏准备 void GameStart(pSnake psnake) { //设置好窗口、地图 ScreenPrepare(); //初始化蛇 InitSnake(psnake); //创建食物 CreatFood(psnake); } //暂停 void pause() { while (1) { //一直休眠,直到再次按空格键 Sleep(200); if (KEY_PRESS(VK_SPACE)) { break; } } } //玩游戏 void GameRun(pSnake ps) { do { //显示分数 SetColor(12); SetPos(65, 12); printf("目前得分:%-5d",ps->Score); SetColor(10); SetPos(65, 13); printf("每个食物:%2d分",ps->FoodScore); //检测按键 if (KEY_PRESS(VK_UP) && ps->Dire != DOWN) { //按上键,且蛇的方向不能向下 ps->Dire = UP; } else if (KEY_PRESS(VK_DOWN) && ps->Dire != UP) { //按下键,且蛇的方向不能向上 ps->Dire = DOWN; } else if (KEY_PRESS(VK_LEFT) && ps->Dire != RIGHT) { //按左键,且蛇的方向不能向右 ps->Dire = LEFT; } else if (KEY_PRESS(VK_RIGHT) && ps->Dire != LEFT) { //按右键,且蛇的方向不能向左 ps->Dire = RIGHT; } else if (KEY_PRESS(VK_ESCAPE)) { //按ESC,退出游戏 ps->State = END; } else if (KEY_PRESS(VK_SPACE)) { //按空格,暂停 pause(); } else if (KEY_PRESS(VK_F1)) { //按F1加速,即睡眠时间变短 //休眠时间不能是负数,最快就是休眠30ms if (ps->SleepTime >= 50) { ps->SleepTime -= 20; //速度变快,食物的分数变高 ps->FoodScore += 2; } } else if (KEY_PRESS(VK_F2)) { //F2减速,睡眠时间变长 //食物的分数不能减到负数,最多减为1分 if (ps->FoodScore >= 3) { ps->SleepTime += 20; ps->FoodScore -= 2; } } //按照蛇的睡眠时间,真正实现休眠 Sleep(ps->SleepTime); //休眠后,蛇要移动 SnakeMove(ps); } while (ps->State == OK);//只有蛇的状态为OK,才能继续检测按键 } //移动蛇 void SnakeMove(pSnake ps) { //先产生节点 pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove():malloc()"); return; } //根据蛇的方向,设定节点的位置 switch (ps->Dire) { case UP: //蛇的方向向上,节点应该在当前蛇头的上方;横坐标不变,纵坐标减1 pNext->x = ps->pSnake->x; pNext->y = ps->pSnake->y - 1; break; case DOWN: //蛇的方向向下,节点应该在当前蛇头的下方;横坐标不变,纵坐标加1 pNext->x = ps->pSnake->x; pNext->y = ps->pSnake->y + 1; break; case LEFT: //蛇的方向向左,节点应该在当前蛇头的左边;纵坐标不变,横坐标减2 pNext->x = ps->pSnake->x - 2; pNext->y = ps->pSnake->y; break; case RIGHT: //蛇的方向向右,节点应该在当前蛇头的右方;纵坐标不变,横坐标加2 pNext->x = ps->pSnake->x + 2; pNext->y = ps->pSnake->y; break; } //判断下一个节点是否是食物 if (JudgeNext(ps, pNext)) { //是食物,吃掉,长度增加 EatFood(ps, pNext); } else { //不是食物,吃掉,长度不增加 NoFood(ps, pNext); } //撞墙 KillByWall(ps); //撞自己 KillBySelf(ps); } //判断节点是否是食物 int JudgeNext(pSnake ps, pSnakeNode pNext) { return ((ps->pFood->x == pNext->x) && (ps->pFood->y == pNext->y)); } //是食物,吃掉,长度增加 void EatFood(pSnake ps, pSnakeNode pNext) { //吃掉食物,头插法将节点插入 pNext->next = ps->pSnake; ps->pSnake = pNext; //打印蛇 PrintSnake(ps); //加分 ps->Score += ps->FoodScore; //释放食物节点 free(ps->pFood); //再次创建食物 CreatFood(ps); } //不是食物 void NoFood(pSnake ps, pSnakeNode pNext) { //头插法连接 pNext->next = ps->pSnake; ps->pSnake = pNext; //删除蛇尾 pSnakeNode cur = ps->pSnake; //找到蛇尾的前一个节点 while (cur->next->next) { cur = cur->next; } pSnakeNode del = cur->next; //将蛇尾打印为空格 SetPos(del->x, del->y); printf(" "); //释放蛇尾节点 free(del); cur->next = NULL; //打印蛇 PrintSnake(ps); } //撞墙 void KillByWall(pSnake ps) { if (ps->pSnake->x == 0 || ps->pSnake->x == 56 || ps->pSnake->y == 0 || ps->pSnake->y == 26) { ps->State = KILL_BY_WALL; } } //撞到自己 void KillBySelf(pSnake ps) { pSnakeNode cur = ps->pSnake->next; while (cur) { if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y) { ps->State = KILL_BY_SELF; } cur = cur->next; } } //游戏结束 void GameEnd(pSnake ps) { SetPos(20, 13); SetColor(12); switch (ps->State) { case END: printf("您结束了游戏"); break; case KILL_BY_SELF: printf("很遗憾!您撞到了自己"); break; case KILL_BY_WALL: printf("很遗憾!您撞墙了"); break; } pSnakeNode cur = ps->pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps = NULL; SetColor(10); }
猜你喜欢
网友评论
- 搜索
- 最新文章
- 热门文章