贪吃蛇(C语言版) |
您所在的位置:网站首页 › 贪吃蛇的c语言代码 › 贪吃蛇(C语言版) |
在我们学习完C语言 和单链表知识点后 我们开始写个贪吃蛇的代码 目标:使用C语言在Windows环境的控制台模拟实现经典小游戏贪吃蛇 贪吃蛇代码实现的基本功能: 地图的绘制 蛇、食物的创建 蛇的状态(正常 撞墙 撞到自己 正常退出) 蛇移动的方向(上 下 左 右) 蛇的加速 减速 游戏暂停 ....... 该代码会运用到函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等 我们会在学习前 会引出一些必要的知识点 目录 Win32 API介绍 控制台程序 控制台屏幕上的坐标COORD GetStdHandle GetConsoleCursorInfo CONSOLE_CURSOR_INFO SetConsoleCursorInfo SetConsoleCursorPosition GetAsyncKeyState 贪吃蛇游戏设计与分析 地图的绘制 本地化 类项 setlocale函数 宽字符的打印 游戏流程设计 核⼼逻辑实现分析 游戏开始(GameStart) 1.设置控制台窗口大小 名称 隐藏光标 2.欢迎页面及功能介绍(WelcomeToGame) 3.绘制地图(CreatMap) 4.创建蛇(初始化蛇身)(InitSnake) 5.创建食物(CreateFood) 游戏运行(GameRun) 1.游戏运⾏期间,右侧打印帮助信息(PrintHelpInfo) 2.根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。 游戏有两种状态:i.蛇撞墙而亡 ii.蛇撞到自己 3.如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。 蛇身移动(SnakeMove) 游戏结束(GameEnd) 完整代码如下 Snake.h Snake.c Test.c Win32 API介绍 Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application) ,所以便称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows32位平台的应⽤程序编程接⼝。 控制台程序 平常我们运⾏起来的⿊框程序其实就是控制台程序 我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列(按住windows+R输入cmd 我们可以使用cmd命令来设置控制台窗口的长度:如 设置控制台窗口的大小 30行 100列![]() ![]() ![]() 给坐标赋值: COORD pos = { 2, 10 }; GetStdHandle GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊--键盘、标准输出--屏幕或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。(我们可以把句柄想象成一个钥匙 只有拿到钥匙才能打开锁 ---只有得到句柄后才能进行一些操作) HANDLE GetStdHandle(DWORD nStdHandle);在我们贪吃蛇代码中 函数的参数是STD_OUTPUT_HANDLE 标准输出 实例: HANDLE houtput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) houtput = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleCursorInfo 检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息 在该函数中 有一参数是 PCONSOLE_CURSOR_INFO 其是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标 (光标)的信息实例: //获得标准输出设备的句柄 HANDLE houtput = NULL; houtput=GetStdHandle(STD_OUTPUT_HANDLE);//houput获取标准输出的句柄 使得可以操作屏幕 //定义一个光标信息的结构体 CONSOLE_CURSOR_INFO cursor_info = { 0 }; //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中 GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息 CONSOLE_CURSOR_INFO 这个结构体,包含有关控制台光标的信息 dwSize,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完 全填充单元格到单元底部的⽔平线条。 bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 TRUE 在前一个代码中我们声明了一个结构体cursor_info cursor_info.bVisible = false; //隐藏控制台光标 SetConsoleCursorInfo 设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性 //获得标准输出设备的句柄 HANDLE houtput = NULL; houtput=GetStdHandle(STD_OUTPUT_HANDLE);//houput获取标准输出的句柄 使得可以操作屏幕 //定义一个光标信息的结构体 CONSOLE_CURSOR_INFO cursor_info = { 0 }; //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中 GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息 //修改光标占比 cursor_info.dwSize = 50; cursor_info.bVisible = false; //设置和houtput句柄相关的控制台上的光标信息 SetConsoleCursorInfo(houtput, &cursor_info); SetConsoleCursorPosition 设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置 实例: //获得标准输出设备的句柄 HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE);//句柄houput获取标准输出的句柄 使得可以操作屏幕 //定位光标的位置 COORD pos = { 10,20 }; SetConsoleCursorPosition(houtput, pos);SetPos:封装一个设置光标位置的函数 ----贪吃蛇地图 蛇 食物的绘制都需要移动光标 void SetPos(short x, short y)//封装一个函数SetPos 光标位置 { HANDLE houtput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) houtput = GetStdHandle(STD_OUTPUT_HANDLE); //定位光标位置 COORD pos = { x, y }; SetConsoleCursorPosition(houtput, pos); } GetAsyncKeyState 获取按键情况 将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态 GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。 如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1. 在贪吃蛇中 如:方向键......都需要该函数进行判断是否被按过 这里我们可以写一个宏: #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ) //这里的0x1也就是 1Win32 API我们就讲到这里 除了Win32 API外 我们还有其他知识需要学习 我这里是用VS2022写的代码 这里还需要对运行窗口做些改变 ![]() ![]() ![]() ![]() 程序开始就设置程序⽀持本地模式,然后进⼊游戏的主逻辑 主逻辑分为3个过程: • 游戏开始(GameStart)完成游戏的初始化 • 游戏运⾏(GameRun)完成游戏运⾏逻辑的实现 • 游戏结束(GameEnd)完成游戏结束的说明,实现资源释放 //Test.c #include #include"snake.h" //完成的是游戏的测试逻辑 void test() { int ch = 0; do {//创建贪吃蛇 Snake snake = { 0 }; //初始化游戏 //1.打印环境页面 //2.功能介绍 //3.绘制地图 //4.创建蛇 //5.创建食物 //6.设置游戏的相关信息 GameStart(&snake); //运行游戏 GameRun(&snake); //结束游戏--善后工作 GameEnd(&snake); SetPos(20, 15); printf("再来一局吗?(Y/N):"); ch = getchar();//按下回车后 其实接收到的是\n 所以需要清理掉\n //getchar();//清理掉\n while (getchar() != '\n') ; } while (ch=='Y' || ch == 'y'); SetPos(0, 26);//让代码结束的信息放在地图外 } int main() { //设置适配本地环境 setlocale(LC_ALL, "");//中文 宽字符的打印 srand((unsigned int)time(NULL)); test(); return 0; } 游戏开始(GameStart) 1.设置控制台窗口大小 名称 隐藏光标 void GameStart(pSnake ps) { //0.先设置窗口的大小 然后把光标隐藏 system("mode con cols=100 lines=30");//30行 100列 system("title 贪吃蛇"); HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE); //隐藏光标操作 //定义一个光标信息的结构体 CONSOLE_CURSOR_INFO cursor_info; //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中 GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息 cursor_info.bVisible = false; //设置和houtput句柄相关的控制台上的光标信息 SetConsoleCursorInfo(houtput, &cursor_info); //1.打印欢迎页面 //2.功能介绍 WelcomeToGame(); //3.绘制地图 CreatMap(); //4.创建蛇 InitSnake(ps); //5.创建食物 CreateFood(ps); } 2.欢迎页面及功能介绍(WelcomeToGame)打印欢迎页面 在我们使用光标位置 之前 我们封装一个函数SetPos void SetPos(short x, short y)//封装一个函数SetPos 光标位置 { HANDLE houtput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) houtput = GetStdHandle(STD_OUTPUT_HANDLE); //定位光标位置 COORD pos = { x, y }; SetConsoleCursorPosition(houtput, pos); } void WelcomeToGame() { SetPos(45, 14); wprintf(L"欢迎来到贪吃蛇小游戏\n"); SetPos(46, 20); system("pause"); system("cls");//清除控制台信息 SetPos(30, 14); wprintf(L"用↑ ↓ ← → 来控制蛇的移动 按F3加速 F4减速\n"); SetPos(34, 15); wprintf(L"加速能够得到更高的分数"); SetPos(46, 20); system("pause"); system("cls");//紧接着下面应该是地图绘制 }代码实现结果: 绘制地图就是将墙体打印出来 因为这里是宽字符打印 所有打印使用wprintf函数 打印格式串前使用L 绘制地图的关键是算好光标的坐标 才能在想要的位置打印墙体 为墙体打印的宽字符写个宏: #define WALL L'□'这里的易错点也就是坐标的计算 代码如下: void CreatMap() {//27行 58列的棋盘 //上 int i = 0; for (i = 0; i < 29; i++) {//□ wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (i = 0; i < 29; i++) wprintf(L"%lc", WALL); //左 for (i = 1; i 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"%lc", BODY); cur = cur->next; } //设置贪吃蛇的属性 ps->_dir = RIGHT;//默认向右 ps->_score = 0; ps->_food_weight = 10; ps->_sleep_time = 200;//毫秒 ps->_status = OK; /*getchar();*/为什么我的代码只打印一个● } 蛇⾝打印的宽字符: #define BODY L'●' 5.创建食物(CreateFood)先随机生成食物的坐标(rand和srand函数) 1.x坐标必须是2的倍数 2.食物的坐标不能和蛇身每个结点的坐标重复 不能越过墙体 创建食物结点 打印食物 食物打印的宽字符: #define FOOD L'★'CreateFood函数: void CreateFood(pSnake ps) { int x = 0; int y = 0; //x = rand()%53+2;//x:2~54---0~52 //还要保证x是偶数 //y = rand()%25+1;//y:1~25---0~24 again: do { x = rand() % 53 + 2;//x:2~54---0~52 //还要保证x是偶数 y = rand()%25+1;//y:1~25---0~24 } while (x % 2 != 0); //x和y的坐标不能跟蛇的身体相冲突 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("CreateFood()::malloc()"); } pFood->x = x; pFood->y = y; pFood->next = NULL; SetPos(x, y); wprintf(L"%lc", FOOD); ps->_pFood = pFood; } 游戏运行(GameRun) 1.游戏运⾏期间,右侧打印帮助信息(PrintHelpInfo) void PrintHelpInfo() { SetPos(64, 10); wprintf(L"%ls", L"不能穿墙,不能咬到自己"); SetPos(64, 11); wprintf(L"%ls", L"用↑ ↓ ← → 来控制蛇的移动 \n"); SetPos(64, 12); wprintf(L"%ls", L"按F3加速 F4减速"); SetPos(64, 13); wprintf(L"%ls", L"按ESC退出游戏 空格暂停游戏"); SetPos(64, 14); wprintf(L"%ls", L"made in LiFeiFei"); } 2.根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。 游戏有两种状态:i.蛇撞墙而亡 ii.蛇撞到自己 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;//cur指向蛇身 while (cur) { if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y) { ps->_status = KILL_BY_SELF; break; } cur = cur->next; } } 3.如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。 //获取按键情况 如果按键最小位为1 说明已按过 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ) 需要的虚拟按键的罗列: • 上:VK_UP • 下:VK_DOWN • 左:VK_LEFT • 右:VK_RIGHT • 空格:VK_SPACE • ESC:VK_ESCAPE • F3:VK_F3 • F4:VK_F4 蛇身移动(SnakeMove)蛇下一步移动方向会有几种情况: 1.下一步不是食物---游戏正常 在打印蛇时 我们还要将最后一个结点原先所在位置打印两个空格 并释放掉原先最后一个结点 2.下一步是食物---吃掉食物---蛇的结点数+1 这里我们使用头插---新增结点位于蛇头 void NoFood(pSnakeNode pn, pSnake ps) { //头插法 pn->next = ps->_pSnake; ps->_pSnake = pn; pSnakeNode cur = ps->_pSnake; while (cur->next->next != NULL) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //把最后一个结点打印成空格 SetPos(cur->next->x, cur->next->y); printf(" "); //释放最后一个结点 free(cur->next); //将倒数第二个结点的地址域置为NULL cur->next = NULL; } int NextIsFood(pSnakeNode pn, pSnake ps) { return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y); } void EatFood(pSnakeNode pn, pSnake ps) { //头插法 ps->_pFood->next = ps->_pSnake; ps->_pSnake = ps->_pFood; //释放下一个位置的结点(我们设置了一个食物结点 和下一个结点 ) free(pn); pn = NULL; pSnakeNode cur = ps->_pSnake; //打印蛇 while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } ps->_score += ps->_food_weight; //重新创建食物 CreateFood(ps); } void pause()//暂停 { while (1) { Sleep(200); if (KEY_PRESS(VK_SPACE)) { break; } } }在NoFood中 有一个易错点:在打印下一步的蛇身时 也就是将下一个结点当作蛇头 并将之前蛇身最后一个结点打印为空格 释放掉原本蛇身的最后一个结点 在释放掉最后一个结点后 还得将指向(原先蛇身的倒数第二个结点)最后一个结点的指针改为NULL 保证蛇尾打印可以正常结束 不会越界访问 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 GameRun(pSnake ps) { //打印帮助信息 PrintHelpInfo(); do { //打印总分数 和食物的分值 SetPos(64, 8); printf("总分数:%d\n", ps->_score); SetPos(64, 9); printf("当前食物的分值:%2d\n", ps->_food_weight); 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))//ESC {//正常退出游戏 ps->_status = END_NORMAL;//退出游戏 break; } else if (KEY_PRESS(VK_F3))//F3加速---休眠时间减少 { if (ps->_sleep_time >= 80)//初始休眠时间为200-80=120 减4次 {//最小休眠时间为50 ps->_sleep_time -= 30; ps->_food_weight += 2;//食物初始分值为10 ⼀个⻝物分数最⾼是20分 } } else if (KEY_PRESS(VK_F4))//F4减速 { if (ps->_sleep_time < 320)//200 230 260 290 320 { //10 8 6 4 2 ps->_sleep_time += 30; ps->_food_weight -= 2;//⼀个⻝物分数最低是2分 } } SnakeMove(ps);//蛇走一步的过程 Sleep(ps->_sleep_time); } while (ps->_status == OK); } 游戏结束(GameEnd)游戏状态不再是OK的时候 要告知游戏结束的原因 并且释放蛇身结点 void GameEnd(pSnake ps) { SetPos(24, 12); switch (ps->_status) { case END_NORMAL: printf("有事 不玩了\n"); break; case KILL_BY_WALL: printf("撞个墙玩玩\n"); break; case KILL_BY_SELF: printf("吃个自己吧\n"); break; } //释放蛇身的链表 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } } 完整代码如下 Snake.h //Snake.h #include #include #include #include #include #define POS_X 24 #define POS_Y 5 #define WALL L'□' #define BODY L'●' #define FOOD L'★' //类型的声明 // //蛇身的结点类型 typedef struct Snakenode { //坐标 int x; int y; //指向下一个节点的指针 struct Snakenode* next; }Snakenode,*pSnakeNode;//结构体指针pSnakeNode //贪吃蛇 typedef struct Snake { pSnakeNode _pSnake;//指向蛇头的指针 pSnakeNode _pFood;//指向食物节点的指针 enum DIRECTION _dir;//蛇的方向 enum GAME_STATUS _status;//游戏的状态 int _food_weight;//食物分数 int _score;//总分数 int _sleep_time;//休息时间 时间越短 速度越快 时间越长 速度越慢 }Snake,*pSnake; //枚举方向 enum DIRECTION { UP = 1, DOWN, LEFT, RIGHT }; //蛇的状态 //正常 撞墙 撞到自己 正常退出 enum GAME_STATUS { OK, KILL_BY_WALL, KILL_BY_SELF, END_NORMAL }; //函数的声明 //定位光标位置 void SetPos(short x, short y); //游戏的初始化 void GameStart(pSnake ps); //欢迎界面的打印 void WelcomeToGame(); //创建地图 void CreatMap(); //4.创建蛇 --初始化蛇身 void InitSnake(pSnake ps); //创建食物 void CreateFood(pSnake ps); //游戏运行逻辑 void GameRun(pSnake ps); //蛇走一步的过程 void SnakeMove(pSnake ps); //判断下一个坐标是否是食物 int NextIsFood(pSnakeNode pn, pSnake ps); //下一个位置是食物 就吃掉食物 void EatFood(pSnakeNode pn, pSnake ps); //下一个位置不是食物 void NoFood(pSnakeNode pn, pSnake ps); //检测蛇是否撞墙 void KillByWall(pSnake ps); //检测蛇是否撞到自己 void KillBySelf(pSnake ps); //游戏善后的工作 void GameEnd(pSnake ps); Snake.c //snake.c #include"snake.h" void SetPos(short x, short y)//封装一个函数SetPos 光标位置 { HANDLE houtput = NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) houtput = GetStdHandle(STD_OUTPUT_HANDLE); //定位光标位置 COORD pos = { x, y }; SetConsoleCursorPosition(houtput, pos); } void WelcomeToGame() { SetPos(45, 14); wprintf(L"欢迎来到贪吃蛇小游戏\n"); SetPos(46, 20); system("pause"); system("cls");//清除控制台信息 SetPos(30, 14); wprintf(L"用↑ ↓ ← → 来控制蛇的移动 按F3加速 F4减速\n"); SetPos(34, 15); wprintf(L"加速能够得到更高的分数"); SetPos(46, 20); system("pause"); system("cls");//紧接着下面应该是地图绘制 } void CreatMap() {//27行 58列的棋盘 //上 int i = 0; for (i = 0; i < 29; i++) {//□ wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (i = 0; i < 29; i++) wprintf(L"%lc", WALL); //左 for (i = 1; i 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"%lc", BODY); cur = cur->next; } //设置贪吃蛇的属性 ps->_dir = RIGHT;//默认向右 ps->_score = 0; ps->_food_weight = 10; ps->_sleep_time = 200;//毫秒 ps->_status = OK; /*getchar();*/为什么我的代码只打印一个● } void CreateFood(pSnake ps) { int x = 0; int y = 0; //x = rand()%53+2;//x:2~54---0~52 //还要保证x是偶数 //y = rand()%25+1;//y:1~25---0~24 again: do { x = rand() % 53 + 2;//x:2~54---0~52 //还要保证x是偶数 y = rand()%25+1;//y:1~25---0~24 } while (x % 2 != 0); //x和y的坐标不能跟蛇的身体相冲突 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("CreateFood()::malloc()"); } pFood->x = x; pFood->y = y; pFood->next = NULL; SetPos(x, y); wprintf(L"%lc", FOOD); ps->_pFood = pFood; } void GameStart(pSnake ps) { //0.先设置窗口的大小 然后把光标隐藏 system("mode con cols=100 lines=30");//30行 100列 system("title 贪吃蛇"); HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE); //隐藏光标操作 //定义一个光标信息的结构体 CONSOLE_CURSOR_INFO cursor_info; //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中 GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息 cursor_info.bVisible = false; //设置和houtput句柄相关的控制台上的光标信息 SetConsoleCursorInfo(houtput, &cursor_info); //1.打印欢迎页面 //2.功能介绍 WelcomeToGame(); //3.绘制地图 CreatMap(); //4.创建蛇 InitSnake(ps); //5.创建食物 CreateFood(ps); } void PrintHelpInfo() { SetPos(64, 10); wprintf(L"%ls", L"不能穿墙,不能咬到自己"); SetPos(64, 11); wprintf(L"%ls", L"用↑ ↓ ← → 来控制蛇的移动 \n"); SetPos(64, 12); wprintf(L"%ls", L"按F3加速 F4减速"); SetPos(64, 13); wprintf(L"%ls", L"按ESC退出游戏 空格暂停游戏"); SetPos(64, 14); wprintf(L"%ls", L"made in LiFeiFei"); } //获取按键情况 如果按键最小位为1 说明已按过 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ) void pause()//暂停 { while (1) { Sleep(200); if (KEY_PRESS(VK_SPACE)) { break; } } } int NextIsFood(pSnakeNode pn, pSnake ps) { return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y); } void EatFood(pSnakeNode pn, pSnake ps) { //头插法 ps->_pFood->next = ps->_pSnake; ps->_pSnake = ps->_pFood; //释放下一个位置的结点(我们设置了一个食物结点 和下一个结点 ) free(pn); pn = NULL; pSnakeNode cur = ps->_pSnake; //打印蛇 while (cur) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } ps->_score += ps->_food_weight; //重新创建食物 CreateFood(ps); } void NoFood(pSnakeNode pn, pSnake ps) { //头插法 pn->next = ps->_pSnake; ps->_pSnake = pn; pSnakeNode cur = ps->_pSnake; while (cur->next->next != NULL) { SetPos(cur->x, cur->y); wprintf(L"%lc", BODY); cur = cur->next; } //把最后一个结点打印成空格 SetPos(cur->next->x, cur->next->y); printf(" "); //释放最后一个结点 free(cur->next); //将倒数第二个结点的地址域置为NULL 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;//cur指向蛇身 while (cur) { if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y) { ps->_status = KILL_BY_SELF; break; } cur = cur->next; } } 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 GameRun(pSnake ps) { //打印帮助信息 PrintHelpInfo(); do { //打印总分数 和食物的分值 SetPos(64, 8); printf("总分数:%d\n", ps->_score); SetPos(64, 9); printf("当前食物的分值:%2d\n", ps->_food_weight); 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))//ESC {//正常退出游戏 ps->_status = END_NORMAL;//退出游戏 break; } else if (KEY_PRESS(VK_F3))//F3加速---休眠时间减少 { if (ps->_sleep_time >= 80)//初始休眠时间为200-80=120 减4次 {//最小休眠时间为50 ps->_sleep_time -= 30; ps->_food_weight += 2;//食物初始分值为10 ⼀个⻝物分数最⾼是20分 } } else if (KEY_PRESS(VK_F4))//F4减速 { if (ps->_sleep_time < 320)//200 230 260 290 320 { //10 8 6 4 2 ps->_sleep_time += 30; ps->_food_weight -= 2;//⼀个⻝物分数最低是2分 } } SnakeMove(ps);//蛇走一步的过程 Sleep(ps->_sleep_time); } while (ps->_status == OK); } void GameEnd(pSnake ps) { SetPos(24, 12); switch (ps->_status) { case END_NORMAL: printf("有事 不玩了\n"); break; case KILL_BY_WALL: printf("撞个墙玩玩\n"); break; case KILL_BY_SELF: printf("吃个自己吧\n"); break; } //释放蛇身的链表 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } } Test.c //test.c #include #include"snake.h" //完成的是游戏的测试逻辑 void test() { int ch = 0; do {//创建贪吃蛇 Snake snake = { 0 }; //初始化游戏 //1.打印环境页面 //2.功能介绍 //3.绘制地图 //4.创建蛇 //5.创建食物 //6.设置游戏的相关信息 GameStart(&snake); //运行游戏 GameRun(&snake); //结束游戏--善后工作 GameEnd(&snake); SetPos(20, 15); printf("再来一局吗?(Y/N):"); ch = getchar();//按下回车后 其实接收到的是\n 所以需要清理掉\n //getchar();//清理掉\n while (getchar() != '\n') ; } while (ch=='Y' || ch == 'y'); SetPos(0, 26);//让代码结束的信息放在地图外 } int main() { //设置适配本地环境 setlocale(LC_ALL, "");//中文 宽字符的打印 srand((unsigned int)time(NULL)); test(); return 0; } |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |