当前位置:   article > 正文

贪吃蛇(C语言超详细版)_c语言贪吃蛇

c语言贪吃蛇

目录

前言:

总览:

API:

控制台程序(Console):

设置坐标: 

COORD:

GetStdHandle:

STD_OUTPUT_HANDLE参数:

SetConsoleCursorPosition: 

隐藏光标: 

GetConsoleCursorInfo:

CONSOLE_CURSOR_INFO参数:

SetConsoleCursorInfo:

获取按键信息: 

GetAsyncKeyState:

设置宽字符: 

setlocale函数:

宽字符的打印:

贪吃蛇的实现: 

初始化游戏: 

初始化蛇结构体:

初始化游戏:

打印地图: 

初始化蛇并打印: 

创建食物: 

游戏运行:

打印帮助信息:

判断按键: 

蛇移动的函数:

判断蛇的状态:

游戏善后工作: 

所有代码: 

总结:


前言:

        C语言到底能不能用来做游戏?今天我就要告诉你们,完全可以!但是用C语言来完成一个游戏,我们还需要很多预备知识才能将游戏运行。

        在我们要去完成一个项目时,都需要先想好它的逻辑,我们将一个大问题分为若干个子问题去解决,接下来我们就开始正式开始完成贪吃蛇

总览:

        我们先来看游戏的运行过程:

贪吃蛇运行过程

        可以看到控制台不是很大,而且有标题:贪吃蛇。最重要的是我们每次看到的光标不见了。这是为什么呢?

        实现贪吃蛇会使用一些Win32 API知识,我们需要学习一下。

API:

        什么是API?我喜欢先把它的全称说一下:Application Programming Interface。简称 API 函数。

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

        说人话就是函数,我们能直接使用(应该就是使用Windows系统都可以使用这些函数)。

控制台程序(Console):

        平时我们运行起来的黑框程序其实就是控制台程序。我们可以使用cmd命令来设置控制台窗口的长度:比如设置控制台窗口的大小, 30行,100列。

mode con cols=100 lines=30

        我们使用C语言在 Windows环境下模拟实现贪食蛇小游戏。

        注意,我们在控制终端以前一定要做到以下修改:

        游戏是否能正常运行起来,这一步至关重要! 

         我们可以修改控制台的标题,这些都是控制台的命令,我们都可以用C语言的 system 函数来执行这些系统命令,我们要包含stdlib.h头文件。

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. int main()
  4. {
  5. system("mode con cols=50 lines=20");
  6. system("title 贪吃蛇");
  7. return 0;
  8. }

         可以发现,控制台终端标题没有变成贪吃蛇,这是因为程序运行结束了,程序没有运行结束之前,标题是会变成我们设置的贪吃蛇的。

        所以为了方便观察,我们加上getchar来防止程序运行结束。

         当然,我们也可以使用系统命令pause来暂行程序。

system("pause");//暂停

设置坐标: 

COORD

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

GetStdHandle:

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

        也就是说,你要操作这个控制台程序,你要获得这个控制台权限。而这个GetStdHandle函数,就可以让我们从一个特定设备获取句柄。

        好比我们现在打开多个控制台程序:

         我们此时就可以在我们写的程序中获取自己控制台的句柄,以至于不会去干扰其他控制台。

STD_OUTPUT_HANDLE参数:

        当我们在控制台中输出,要使用STD_OUTPUT_HANDLE参数。

        我们可以看到GetStdHandle函数返回的是一个HANDLE,所以我们要接收一下。

SetConsoleCursorPosition: 

        SetConsoleCursorPosition:设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的光标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。

        也就是说这是一套组合拳,我们都要一起配合使用,我们要先使用GetStdHandle获取当前控制台句柄,并用HANDLE类型接收,之后使用COORD定义一个结构体设置光标的坐标,最后使用SetConsoleCursorPositon正式把坐标设置到控制台中(记得引入头文件)。

  1. #include<stdio.h>
  2. #include<windows.h>
  3. #include<stdlib.h>
  4. int main()
  5. {
  6. //获取句柄
  7. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置光标的第一个参数
  8. //根据句柄设置光标位置
  9. COORD pos = { 20, 5 };
  10. SetConsoleCursorPosition(handle, pos);//设置光标位置
  11. printf("hehe\n");
  12. return 0;
  13. }

         但是每次这样写很繁琐,我们为了方便定位,可以直接将其封装为一个函数。

  1. void SetPos(int x, int y)
  2. {
  3. //获取句柄
  4. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置光标的第一个参数
  5. //根据句柄设置光标位置
  6. COORD pos = { x, y };
  7. SetConsoleCursorPosition(handle, pos);//设置光标位置
  8. }
  9. int main()
  10. {
  11. SetPos(20, 5);
  12. printf("hehe\n");
  13. SetPos(25, 5);
  14. printf("haha\n");
  15. return 0;
  16. }

隐藏光标: 

GetConsoleCursorInfo:

        GetConsoleCursorInfo:检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。

        因为我们在运行程序时,是不会出现光标的,所以我们要通过此函数来隐藏光标。

         第一个参数是句柄,指定那个控制台;第二个参数是PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标(光标)的信息。

CONSOLE_CURSOR_INFO参数:

        CONSOLE_CURSOR_INFO:这个结构体,包含有关控制台光标信息。

        dwSize:由游标填充的字符单元的百分比。 该值介于 1 到 100 之间。 游标外观各不相同,范围从完全填充单元到显示为单元底部的横线。 

        bVisible:游标的可见性。 如果游标可见,则此成员为 TRUE。 

        我们可以将bVisible参数设置为false,这样就隐藏了光标。

        但是当我们修改完以后光标的信息,就像设置光标位置一样,还需要设置才能生效,此时就需要用到 SetConsoleCursorInfo 函数。

SetConsoleCursorInfo:

        SetConsoleCursorInfo:设置指定控制台屏幕缓冲区的光标大小和可见性。

        和设置光标是一样的,也是一套组合拳: 

  1. #include<stdio.h>
  2. #include<windows.h>
  3. int main()
  4. {
  5. //COORD pos = { 40, 10 };//要包含头文件
  6. //设置坐标
  7. //获取句柄
  8. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置光标的第一个参数
  9. CONSOLE_CURSOR_INFO cursor_info = { 0 };//设置光标的第二个参数
  10. //包含有关控制台的游标信息
  11. GetConsoleCursorInfo(handle, &cursor_info);//设置光标信息
  12. cursor_info.dwSize = 100;
  13. SetConsoleCursorInfo(handle, &cursor_info);
  14. return 0;
  15. }

        比如此时我们将dwSize设置为100。 

        小总结: 我们使用API不用去纠结很多细节,我们可以直接使用,要做到会用,至于更多细节的东西,我们以后还会慢慢学习到的,决不能因噎废食!

获取按键信息: 

        我们运行贪吃蛇,肯定是根据按键来控制的,所以我们此时就来学习和按键有关的API函数。

GetAsyncKeyState:

        GetAsyncKeyState:获取按键情况。

        将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。

        GetAsyncKeyState的返回只是short类型,在上一次调用GetAsyncKeyState函数之后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下;如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明该按键被按过,否则为0。

        如果我们要判断一个件是否被按过,可以检测GetAsyncKeyState返回值的最低位是否为1。

        因为按键只很多,不方便展示,各位可以直接点击链接进入(虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn)。

  1. //获取键盘按键情况
  2. #define KEY_PRESS(vk) (GetAsyncKeyState(vk) & 0x1 ? 1 : 0)
  3. //封装成宏,检测这个键有没有被按过
  4. int main()
  5. {
  6. while (1)
  7. {
  8. if (KEY_PRESS(0x30))
  9. {
  10. printf("0\n");
  11. }
  12. else if (KEY_PRESS(0x31))
  13. {
  14. printf("1\n");
  15. }
  16. else if (KEY_PRESS(0x32))
  17. {
  18. printf("2\n");
  19. }
  20. else if (KEY_PRESS(0x33))
  21. {
  22. printf("3\n");
  23. }
  24. else if (KEY_PRESS(0x34))
  25. {
  26. printf("4\n");
  27. }
  28. else if (KEY_PRESS(0x35))
  29. {
  30. printf("5\n");
  31. }
  32. }
  33. //可以监控别人键盘
  34. return 0;
  35. }

设置宽字符: 

        为了方便贪吃蛇运行,我们可以发现,平时我们在控制台中的光标是长方形,但是贪吃蛇的身体每个节点都是一个规则的正方形,所以我们要使用宽字符。

        我们知道C语言默认的使用ASCII字符集编码的,采用的是单字节编码,但是ASCII字符集只包含128个字符,和明显不够用。

        为了使C语言国际化,C语言标准中不断加入了国际化的支持。比如:加入了宽字符的类型 wchar_t 和宽字符的输入和输出函数,加入了<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

        比如,简体中文常见的编码格式是GB2312,使用两个字节表示一个汉字。所以理论上最多可以表示256 × 256 = 65536 个符号。

        <locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。

        比如表示钱:¥(人民币) $(美元)。还有符号和日期等。

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

setlocale函数:

        这个函数也是API函数,它是设置本地化的函数,我们先来观察它的源码:

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

        第一个参数是控制哪些种类(比如符号、时间等),当然参数是以宏的方式传递的。

        如果读不懂的话,你只需要记住一点LC_ALL影响所有的就行了,至于其他的,可以用到再慢慢研究。

        第二个参数是决定了当前编译模式。

        分为: “C”(正常模式) 和 “”(本地模式)。

        在任意程序执行开始时,都会隐式执行调用 setlocale(LC_ALL, "C");

  1. #include<locale.h>
  2. //适应本地化
  3. //宽字符占据两个字节
  4. int main()
  5. {
  6. char* loc;
  7. loc = setlocale(LC_ALL, NULL);
  8. printf("默认的本地信息:%s\n", loc);
  9. loc = setlocale(LC_ALL, "");
  10. printf("设置后的本地信息:%s\n", loc);
  11. return 0;
  12. }

宽字符的打印:

        如果在屏幕上打印宽字符,宽字符的字面量必须加上前缀 L ,否则C语言会把字面量当做窄字符类型处理。前缀L在单引号前面,表示宽字符,宽字符的打印使用 wprintf ,对应 wprintf() 的占位符为 %lc ;在双引号前面,表示宽字符,对应 wprintf() 的占位符为 %ls。

  1. #include<locale.h>
  2. int main()
  3. {
  4. setlocale(LC_ALL, "");//适配本地模式
  5. wchar_t ch1 = L'中';//宽字符
  6. wchar_t ch2 = L'国';
  7. wchar_t ch3 = L'☆';
  8. wprintf(L"%lc\n", ch1);
  9. wprintf(L"%lc\n", ch2);
  10. wprintf(L"%lc\n", ch3);
  11. printf("ab\n");
  12. return 0;
  13. }

        可以发现,宽字符占据两个位置。

贪吃蛇的实现: 

        因为贪吃蛇是最终的目标,我们先将其分为若干个子问题。

        为了方便,我们还是分为3个文件(一个头文件,两个源文件)。

        test.c:贪吃蛇游戏的测试

        snake.c:函数的实现

        snake.h:贪吃蛇游戏中的类型声明,函数的声明。

初始化游戏: 

初始化蛇结构体:

        我们如何去维护一个“蛇”呢?我们可以用链表的方式。所以我们先去创建一个蛇身节点的结构体。

  1. typedef struct SnakeNode
  2. {
  3. int x;//横坐标
  4. int y;//纵坐标
  5. struct SnakeNode* next;
  6. }SnakeNode, *pSnakeNode;
  7. //相当于
  8. typedef struct SnakeNode* pSnakeNode;

        但是此时我们只维护了一个蛇身节点,所以此时我们在定义一个Snake结构体。

        而这个Snake结构体中,我们需要找到蛇头的节点,维护食物,分数,方向,蛇的状态,蛇的速度,蛇的当前分数,和食物的分数。

        这里注意,其实食物也是蛇身上的节点,因为我们最终会把它吃掉;蛇的速度其实就是休眠时间。

  1. //游戏状态
  2. enum GAME_STATUS
  3. {
  4. OK = 1,//正常运行
  5. ESC = 2,//正常退出
  6. KILL_BY_WALL,//撞墙
  7. KILL_BY_SELF,//撞到自身
  8. VICTORY//获胜
  9. };
  10. //蛇的方向
  11. enum DIRECTION
  12. {
  13. UP = 1,
  14. DOWN,
  15. LEFT,
  16. RIGHT
  17. };
  18. typedef struct Snake
  19. {
  20. pSnakeNode pSnake;//维护整条蛇的指针
  21. pSnakeNode pFood;//指向食物的指针
  22. int Score;//当前累计分数
  23. int FoodWeight;//一个食物的分数
  24. int SleepTime;//蛇休眠的时间,休眠时间越短,速度越快;反之越慢
  25. enum GAME_STATUS status;//游戏当前的状态
  26. enum DIRECTION dir;//蛇当前的方向
  27. }Snake, *pSnake;

        这里还需要注意蛇的状态有5种(其实也是游戏的运行状态):

  1. 正常运行(OK)
  2. 正常退出(ESC)
  3. 撞墙而死(KILL_BY_WALL)
  4. 咬到自己而死(KILL_BY_SELF)
  5. 游戏获胜(VICTORY) 

        蛇的行驶方向:

  1. 向上(UP)
  2. 向下(DOWN)
  3. 向左(LEFT)
  4. 向右(RIGHT)

        为了方便使用,我们使用枚举类型来定义。

        此时我们主函数中要先适应本地化,之后创建一条“蛇”,之后初始化游戏。

初始化游戏:

  1. void test()
  2. {
  3. //分为3大步
  4. //我们先创建贪吃蛇
  5. Snake snake = { 0 };
  6. GameStart(&snake);//游戏开始前的初始化
  7. //GameRun();//玩游戏的过程
  8. //GameEnd();//善后工作
  9. }
  10. int main()
  11. {
  12. //首先适应本地化
  13. setlocale(LC_ALL, "");
  14. test();//完成贪吃蛇游戏的测试
  15. return 0;
  16. }

        首先我们要设置控制台大小和标题,之后我们要隐藏光标信息并且打印欢迎信息。

  1. //设置坐标
  2. void SetPos(int x, int y)
  3. {
  4. //获取设备句柄
  5. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
  6. //根据句柄设置光标位置
  7. COORD pos = { x, y };
  8. SetConsoleCursorPosition(handle, pos);
  9. }
  10. //打印欢迎信息
  11. void WelcomeToGame()
  12. {
  13. //打印欢迎信息
  14. SetPos(35, 10);
  15. printf("欢迎来到贪吃蛇小游戏\n");
  16. SetPos(36, 20);
  17. system("pause");
  18. system("cls");//清屏
  19. //打印功能介绍信息
  20. SetPos(15, 10);
  21. printf("用↑,↓,←,→来控制蛇的移动,F3是加速,F4是减速\n");
  22. SetPos(15, 11);
  23. printf("加速能得到更高的分数...\n");
  24. SetPos(38, 20);
  25. system("pause");
  26. system("cls");//清屏
  27. }
  28. //游戏开始前的初始化
  29. void GameStart(pSnake ps)
  30. {
  31. //设置控制台的信息:窗口大小,窗口名
  32. system("mode con cols=100 lines=30");
  33. system("title 贪吃蛇");
  34. //隐藏光标
  35. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄
  36. CONSOLE_CURSOR_INFO cursorInfor = { 0 };
  37. GetConsoleCursorInfo(handle, &cursorInfor);//获取控制台光标信息
  38. cursorInfor.bVisible = false;//包含stdbool头文件
  39. SetConsoleCursorInfo(handle, &cursorInfor);//设置光标信息
  40. //打印欢迎信息
  41. WelcomeToGame();
  42. //getchar();//暂停程序,方便观察
  43. }

 

打印地图: 

        我们适应本地化以后,就可以使用宽字符来打印地图了。我们先来总览一下地图:

         初始状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(26,4)处开始出现蛇,连续5个节点。

        注意,蛇的每个节点x坐标必须是2的倍数,否则蛇身节点可能会出现在墙体中,坐标没有对齐出现故障。

        食物也必须在墙体内生成(x坐标必须是2的倍数),坐标不能和蛇的身体重合,之后打印★。

        因为我们需要蛇的身体节点是正方形,所以一个节点占据两个空间,为了方便使用,我们将墙体定义为宏。我们要先设置光标位置之后用宽字符打印字符: 

  1. #define WALL L'□'
  2. //绘制地图
  3. void CreateMap()
  4. {
  5. //上
  6. SetPos(0, 0);
  7. int i = 0;
  8. for (i = 0; i <= 56; i += 2)
  9. {
  10. wprintf(L"%lc", WALL);
  11. }
  12. //下
  13. SetPos(0, 26);
  14. for (i = 0; i <= 56; i += 2)
  15. {
  16. wprintf(L"%lc", WALL);
  17. }
  18. //左
  19. for (i = 1; i <= 25; i++)
  20. {
  21. SetPos(0, i);
  22. wprintf(L"%lc", WALL);
  23. }
  24. //右
  25. for (i = 1; i <= 25; i++)
  26. {
  27. SetPos(56, i);
  28. wprintf(L"%lc", WALL);
  29. }
  30. getchar();
  31. }

初始化蛇并打印: 

        之后根据我们之前的图形去初始化一个蛇。我们再看一眼就会爆炸的地图:

         这个蛇的节点是头插实现,我们设置一个宏先来固定其实坐标,之后创建5个节点,每次设置好横纵坐标后顺带打印节点。之后设置蛇的其他初始状态。

        和之前一样,为了方便打印,我们将蛇的身体节点设为一个宏。

  1. #define BODY L'●'
  2. //设默认的起始坐标
  3. #define POS_X 26
  4. #define POS_Y 4
  5. //初始化蛇
  6. void InitSnake(pSnake ps)
  7. {
  8. pSnakeNode cur = (pSnake)malloc(sizeof(SnakeNode));
  9. //创建5个蛇身节点
  10. int i = 0;
  11. for (i = 0; i < 5; i++)
  12. {
  13. cur = (pSnake)malloc(sizeof(SnakeNode));
  14. if (cur == NULL)
  15. {
  16. perror("InitSnake()::malloc()");
  17. return;
  18. }
  19. cur->x = POS_X + (i * 2);
  20. cur->y = POS_Y;
  21. cur->next = NULL;
  22. //创建同时顺便打印
  23. SetPos(cur->x, cur->y);
  24. wprintf(L"%lc", BODY);
  25. //头插
  26. if (ps->pSnake == NULL)
  27. {
  28. ps->pSnake = cur;
  29. }
  30. else
  31. {
  32. cur->next = ps->pSnake;
  33. ps->pSnake = cur;
  34. }
  35. }
  36. //贪吃蛇其他初始化信息
  37. ps->dir = RIGHT;//方向默认向右
  38. ps->FoodWeight = 10;//食物的分数
  39. ps->pFood = NULL;//默认食物信息
  40. ps->Score = 0;//默认0分
  41. ps->SleepTime = 200;//默认休眠200毫秒
  42. ps->status = OK;//开始游戏默认状态
  43. //getchar();
  44. }

创建食物: 

        之后我们就要来初始化食物,这个食物必须是随机出现的,坐标就是随机的;坐标必须在墙体内部;坐标不能出现在蛇身上。

  1. #define FOOD L'★'
  2. //创建食物
  3. void CreateFood(pSnake ps)
  4. {
  5. //随机生成坐标
  6. //观察墙体,必须在墙体内部生成
  7. //x : 2 ~ 54 0 ~ 52 + 2
  8. //y : 1 ~ 25 0 ~ 24 + 1
  9. int x = 0;
  10. int y = 0;
  11. again:
  12. //必须保证 x 是偶数
  13. do
  14. {
  15. x = rand() % 53 + 2;
  16. y = rand() % 25 + 1;
  17. } while (x % 2 != 0);
  18. //和蛇的每个节点比较
  19. pSnakeNode cur = ps->pSnake;
  20. while(cur)
  21. {
  22. if (x == cur->x && y == cur->y)
  23. {
  24. goto again;
  25. }
  26. cur = cur->next;
  27. }
  28. //创建食物
  29. pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
  30. if (food == NULL)
  31. {
  32. perror("CreateFood()::malloc()");
  33. return;
  34. }
  35. food->x = x;
  36. food->y = y;
  37. food->next = NULL;
  38. ps->pFood = food;
  39. //打印食物
  40. SetPos(x, y);//记住设置光标位置
  41. wprintf(L"%lc", FOOD);
  42. //getchar();
  43. }

        注意这里的食物横坐标必须是2的倍数! 

游戏运行:

打印帮助信息:

        这个就是纯打印,没啥说的。

  1. //打印帮助信息
  2. void PrintfHelpInfo()
  3. {
  4. SetPos(65, 15);
  5. printf("1.不能穿墙,不能咬到自己");
  6. SetPos(65, 16);
  7. printf("2.用↑,↓,←,→来控制蛇的移动");
  8. SetPos(65, 17);
  9. printf("3.F3是加速,F4是减速");
  10. SetPos(65, 18);
  11. printf("ESC:退出游戏 space:暂停游戏");
  12. SetPos(65, 20);
  13. printf("作者:假油淦");
  14. getchar();
  15. }

        注意这里的getchar是为了暂停程序方便我们观察,当我们正式运行游戏时一定要把所有的getchar函数去除。 

判断按键: 

         因为是循环判断按键,所以我们使用do while语句来检测按键。

        注意我们比如当前是向右走,就不能向左走;向上走就不能向下走,所以要判断一下。

        这里我们检测暂停时封装一个函数,只来用于睡眠,如果再次按到空格,则跳出循环。

        F3是加速,意味着睡眠时间变短,食物分数变高,但是要有上限。

  1. //玩游戏的过程
  2. void GameRun(pSnake ps)
  3. {
  4. //打印帮助信息
  5. PrintfHelpInfo();
  6. do
  7. {
  8. //当前的分数情况
  9. SetPos(65, 10);
  10. printf("总分:%5d\n", ps->Score);
  11. SetPos(65, 11);
  12. printf("食物的分值:%02d\n", ps->FoodWeight);
  13. //检测按键
  14. //上、下、左、右、ESC、空格、F3、F4
  15. //虚拟键码
  16. if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
  17. {//按了上键当前贪吃蛇方向不朝下走
  18. ps->dir = UP;
  19. }
  20. else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
  21. {
  22. ps->dir = DOWN;
  23. }
  24. else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
  25. {
  26. ps->dir = LEFT;
  27. }
  28. else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
  29. {
  30. ps->dir = RIGHT;
  31. }
  32. else if (KEY_PRESS(VK_ESCAPE))
  33. {
  34. ps->status = ESC;//正常退出
  35. break;
  36. }
  37. else if (KEY_PRESS(VK_SPACE))
  38. {
  39. //游戏要暂停
  40. pause();//暂停和恢复暂停
  41. }
  42. else if (KEY_PRESS(VK_F3))
  43. {
  44. //加速
  45. //睡眠时间变短
  46. if (ps->SleepTime > 80)
  47. {
  48. ps->SleepTime -= 30;
  49. ps->FoodWeight += 2;
  50. }
  51. }
  52. else if (KEY_PRESS(VK_F4))
  53. {
  54. //减速
  55. if (ps->FoodWeight > 2)
  56. {
  57. ps->SleepTime += 30;
  58. ps->FoodWeight -= 2;
  59. }
  60. }
  61. //睡眠一下
  62. Sleep(ps->SleepTime);
  63. //走一步
  64. } while (ps->status == OK);
  65. }

        · 此时,我们已经完成了二分之一了,就差蛇移动的函数未完成了(加把油!)。

蛇移动的函数:

        我们先判断下一个节点是不是食物。

        注意:因为我们是先检测按键的,所以我们判断当前蛇的行驶方向之后在特定的位置创建一个pNext节点让蛇吃掉,之后判断是不是食物。

        如果是食物,则把开辟的pNext节点吃掉,并采用头插,此时多出了食物节点,所以我们销毁食物节点。并把总分加上去。

  1. //判断蛇头的下一步要走的位置处是否是食物
  2. int NextIsFood(pSnake ps, pSnakeNode pNext)
  3. {
  4. if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y)
  5. {
  6. return 1;//下一个坐标是食物
  7. }
  8. else
  9. {
  10. return 0;
  11. }
  12. }
  1. //下一步要走的位置就是食物
  2. void EatFood(pSnake ps, pSnakeNode pNext)
  3. {
  4. //头插
  5. pNext->next = ps->pSnake;
  6. ps->pSnake = pNext;
  7. //打印蛇身
  8. pSnakeNode cur = ps->pSnake;
  9. while (cur)
  10. {
  11. SetPos(cur->x, cur->y);
  12. wprintf(L"%lc", BODY);
  13. cur = cur->next;
  14. }
  15. //把next节点吃掉了,此时分数要变
  16. ps->Score += ps->FoodWeight;
  17. //所以要释放食物节点
  18. free(ps->pFood);
  19. //还要创建食物
  20. CreateFood(ps);
  21. }

        如果不是食物,则把pNext节点挂在蛇身上,也就是把pNext头插,之后尾删一个元素。 此时我们可以头插完以后顺便打印蛇身,一定记住设置光标位置之后打印。

        我们将最后一个节点(就是删除的尾节点打印成两个空格)。

  1. //下一步要走的位置不是食物
  2. void NotEatFood(pSnake ps, pSnakeNode pNext)
  3. {
  4. //头插并尾删
  5. pNext->next = ps->pSnake;
  6. ps->pSnake = pNext;
  7. pSnakeNode prev = ps->pSnake;
  8. pSnakeNode cur = ps->pSnake;
  9. while (cur->next)
  10. {
  11. //顺带打印
  12. SetPos(cur->x, cur->y);
  13. wprintf(L"%lc", BODY);
  14. prev = cur;
  15. cur = cur->next;
  16. }
  17. //先将尾节点打印为空
  18. SetPos(cur->x, cur->y);
  19. printf(" ");//注意这里是两个空格
  20. //此时释放
  21. prev->next = NULL;
  22. free(cur);
  23. }

判断蛇的状态:

        蛇每走一步,就需要判断游戏的状态,因为蛇有可能撞墙而死;也有可能撞到自己;也有可能是游戏胜利。所以我们还是需要分情况讨论。

        我们先判断蛇是不是撞墙而死。

  1. //蛇撞墙
  2. void KillByWall(pSnake ps)
  3. {
  4. //直接判断蛇头位置即可
  5. if (ps->pSnake->x == 0 || ps->pSnake->x == 56
  6. || ps->pSnake->y == 0 || ps->pSnake->y == 26)
  7. {
  8. ps->status = KILL_BY_WALL;
  9. }
  10. }

        之后我们判断蛇是不是咬到自身,因为头是不可能碰到头的,所以我们从第二个节点开始判断。 

  1. //蛇撞到自己
  2. void KillBySelf(pSnake ps)
  3. {
  4. //判断蛇头是否与蛇自身其他节点重合
  5. //直接从第二个节点开始判断
  6. pSnakeNode cur = ps->pSnake->next;
  7. while (cur)
  8. {
  9. if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
  10. {
  11. ps->status = KILL_BY_SELF;
  12. return;
  13. }
  14. cur = cur->next;
  15. }
  16. }

        最后就是获胜,我们通过蛇身节点来判断是否获胜,通过观察,我们发现一个是675个宽字符占位符。 

  1. //计算蛇身节点个数
  2. int SnakeNodeCount(pSnake ps)
  3. {
  4. pSnakeNode cur = ps->pSnake;
  5. int count = 0;
  6. while (cur)
  7. {
  8. count++;
  9. cur = cur->next;
  10. }
  11. return count;
  12. }
  13. //判断获胜
  14. void IsVictory(pSnake ps)
  15. {
  16. //我们一共有675个宽字符的格子
  17. //我们直接去判断蛇身节点有多少个去判断输赢
  18. int ret = SnakeNodeCount(ps);
  19. if (ret == 675)
  20. {
  21. ps->status = VICTORY;
  22. }
  23. else
  24. {
  25. return;
  26. }
  27. }

        此时我们就需要去完成最后的善后工作了。

游戏善后工作: 

        使用switch语句来判断是那种情况结束了游戏,之后逐个释放蛇身节点。

  1. //善后工作
  2. void GameEnd(pSnake ps)
  3. {
  4. SetPos(18, 12);
  5. switch (ps->status)
  6. {
  7. case ESC:
  8. printf("主动退出游戏,正常退出游戏\n");
  9. break;
  10. case KILL_BY_WALL:
  11. printf("很遗憾,撞墙了,游戏结束\n");
  12. SetPos(22, 13);
  13. printf("最终得分为:%d\n", ps->Score);
  14. break;
  15. case KILL_BY_SELF:
  16. printf("很遗憾,咬到自己了,游戏结束\n");
  17. SetPos(22, 13);
  18. printf("最终得分为:%d\n", ps->Score);
  19. break;
  20. case VICTORY:
  21. printf("恭喜你,获得胜利!\n");
  22. break;
  23. }
  24. //释放贪吃蛇的链表资源
  25. pSnakeNode cur = ps->pSnake;
  26. pSnakeNode nextPos = ps->pSnake;
  27. while (cur)
  28. {
  29. nextPos = cur->next;
  30. free(cur);
  31. cur = nextPos;
  32. }
  33. free(ps->pFood);
  34. ps->pSnake = NULL;
  35. }

所有代码: 

snake.h头文件:

  1. #pragma once
  2. #include<stdio.h>
  3. #include<windows.h>
  4. #include<stdlib.h>
  5. #include<locale.h>
  6. #include<stdbool.h>
  7. #include<time.h>
  8. #define WALL L'□'
  9. #define BODY L'●'
  10. #define FOOD L'★'
  11. //设默认的起始坐标
  12. #define POS_X 26
  13. #define POS_Y 4
  14. //检测按键
  15. #define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
  16. //游戏状态
  17. enum GAME_STATUS
  18. {
  19. OK = 1,//正常运行
  20. ESC = 2,//正常退出
  21. KILL_BY_WALL,//撞墙
  22. KILL_BY_SELF,//撞到自身
  23. VICTORY//获胜
  24. };
  25. //蛇的方向
  26. enum DIRECTION
  27. {
  28. UP = 1,
  29. DOWN,
  30. LEFT,
  31. RIGHT
  32. };
  33. typedef struct SnakeNode
  34. {
  35. int x;//横坐标
  36. int y;//纵坐标
  37. struct SnakeNode* next;
  38. }SnakeNode, * pSnakeNode;
  39. //相当于
  40. //typedef struct SnakeNode* pSnakeNode;
  41. //贪吃蛇
  42. typedef struct Snake
  43. {
  44. pSnakeNode pSnake;//维护整条蛇的指针
  45. pSnakeNode pFood;//指向食物的指针
  46. int Score;//当前累计分数
  47. int FoodWeight;//一个食物的分数
  48. int SleepTime;//蛇休眠的时间,休眠时间越短,速度越快;反之越慢
  49. enum GAME_STATUS status;//游戏当前的状态
  50. enum DIRECTION dir;//蛇当前的方向
  51. }Snake, *pSnake;
  52. //游戏开始前的初始化
  53. void GameStart(pSnake ps);
  54. //打印欢迎信息
  55. void WelcomeToGame();
  56. //绘制地图
  57. void CreateMap();
  58. //初始化蛇
  59. void InitSnake(pSnake ps);
  60. //创建食物
  61. void CreateFood(pSnake ps);
  62. //玩游戏的过程
  63. void GameRun(pSnake ps);
  64. //打印帮助信息
  65. void PrintfHelpInfo();
  66. //蛇移动的函数
  67. void SnakeMove(pSnake ps);
  68. //判断蛇头的下一步要走的位置处是否是食物
  69. int NextIsFood(pSnake ps, pSnakeNode pNext);
  70. //下一步要走的位置就是食物
  71. void EatFood(pSnake ps, pSnakeNode pNext);
  72. //下一步要走的位不是食物
  73. void NotEatFood(pSnake ps, pSnakeNode pNext);
  74. //蛇撞墙
  75. void KillByWall(pSnake ps);
  76. //蛇撞到自己
  77. void KillBySelf(pSnake ps);
  78. //判断获胜
  79. void IsVictory(pSnake ps);
  80. //善后工作
  81. void GameEnd(pSnake ps);

test.c源文件:

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"snake.h"
  3. void test()
  4. {
  5. int ch = 0;
  6. do
  7. {
  8. //创建贪吃蛇
  9. Snake snake = { 0 };
  10. GameStart(&snake);//游戏开始前的初始化
  11. GameRun(&snake);//玩游戏过程
  12. GameEnd(&snake);//善后工作
  13. SetPos(20, 15);
  14. printf("再来一局吗?(Y/N):");
  15. ch = getchar();
  16. getchar();//清理\n
  17. } while (ch == 'Y' || ch == 'y');
  18. }
  19. int main()
  20. {
  21. //首先适应本地化
  22. setlocale(LC_ALL, "");
  23. //设置时间种子
  24. srand((unsigned int)time(NULL));
  25. test();//完成贪吃蛇游戏的测试
  26. SetPos(0, 26);
  27. return 0;
  28. }

snake.c源文件:

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"snake.h"
  3. //设置坐标
  4. void SetPos(int x, int y)
  5. {
  6. //获取设备句柄
  7. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
  8. //根据句柄设置光标位置
  9. COORD pos = { x, y };
  10. SetConsoleCursorPosition(handle, pos);
  11. }
  12. //打印欢迎信息
  13. void WelcomeToGame()
  14. {
  15. //打印欢迎信息
  16. SetPos(35, 10);
  17. printf("欢迎来到贪吃蛇小游戏\n");
  18. SetPos(36, 20);
  19. system("pause");
  20. system("cls");//清屏
  21. //打印功能介绍信息
  22. SetPos(15, 10);
  23. printf("用↑,↓,←,→来控制蛇的移动,F3是加速,F4是减速\n");
  24. SetPos(15, 11);
  25. printf("加速能得到更高的分数...\n");
  26. SetPos(38, 20);
  27. system("pause");
  28. system("cls");//清屏
  29. }
  30. //绘制地图
  31. void CreateMap()
  32. {
  33. //上
  34. SetPos(0, 0);
  35. int i = 0;
  36. for (i = 0; i <= 56; i += 2)
  37. {
  38. wprintf(L"%lc", WALL);
  39. }
  40. //下
  41. SetPos(0, 26);
  42. for (i = 0; i <= 56; i += 2)
  43. {
  44. wprintf(L"%lc", WALL);
  45. }
  46. //左
  47. for (i = 1; i <= 25; i++)
  48. {
  49. SetPos(0, i);
  50. wprintf(L"%lc", WALL);
  51. }
  52. //右
  53. for (i = 1; i <= 25; i++)
  54. {
  55. SetPos(56, i);
  56. wprintf(L"%lc", WALL);
  57. }
  58. //getchar();
  59. }
  60. //初始化蛇
  61. void InitSnake(pSnake ps)
  62. {
  63. pSnakeNode cur = (pSnake)malloc(sizeof(SnakeNode));
  64. //创建5个蛇身节点
  65. int i = 0;
  66. for (i = 0; i < 5; i++)
  67. {
  68. cur = (pSnake)malloc(sizeof(SnakeNode));
  69. if (cur == NULL)
  70. {
  71. perror("InitSnake()::malloc()");
  72. return;
  73. }
  74. cur->x = POS_X + (i * 2);
  75. cur->y = POS_Y;
  76. cur->next = NULL;
  77. //创建同时顺便打印
  78. SetPos(cur->x, cur->y);
  79. wprintf(L"%lc", BODY);
  80. //头插
  81. if (ps->pSnake == NULL)
  82. {
  83. ps->pSnake = cur;
  84. }
  85. else
  86. {
  87. cur->next = ps->pSnake;
  88. ps->pSnake = cur;
  89. }
  90. }
  91. //贪吃蛇其他初始化信息
  92. ps->dir = RIGHT;//方向默认向右
  93. ps->FoodWeight = 10;//食物的分数
  94. ps->pFood = NULL;//默认食物信息
  95. ps->Score = 0;//默认0分
  96. ps->SleepTime = 200;//默认休眠200毫秒
  97. ps->status = OK;//开始游戏默认状态
  98. //getchar();
  99. }
  100. //创建食物
  101. void CreateFood(pSnake ps)
  102. {
  103. //随机生成坐标
  104. //观察墙体,必须在墙体内部生成
  105. //x : 2 ~ 54 0 ~ 52 + 2
  106. //y : 1 ~ 25 0 ~ 24 + 1
  107. int x = 0;
  108. int y = 0;
  109. again:
  110. //必须保证 x 是偶数
  111. do
  112. {
  113. x = rand() % 53 + 2;
  114. y = rand() % 25 + 1;
  115. } while (x % 2 != 0);
  116. //和蛇的每个节点比较
  117. pSnakeNode cur = ps->pSnake;
  118. while(cur)
  119. {
  120. if (x == cur->x && y == cur->y)
  121. {
  122. goto again;
  123. }
  124. cur = cur->next;
  125. }
  126. //创建食物
  127. pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
  128. if (food == NULL)
  129. {
  130. perror("CreateFood()::malloc()");
  131. return;
  132. }
  133. food->x = x;
  134. food->y = y;
  135. food->next = NULL;
  136. ps->pFood = food;
  137. //打印食物
  138. SetPos(x, y);//记住设置光标位置
  139. wprintf(L"%lc", FOOD);
  140. //getchar();
  141. }
  142. //游戏开始前的初始化
  143. void GameStart(pSnake ps)
  144. {
  145. //设置控制台的信息:窗口大小,窗口名
  146. system("mode con cols=100 lines=30");
  147. system("title 贪吃蛇");
  148. //隐藏光标
  149. HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄
  150. CONSOLE_CURSOR_INFO cursorInfor = { 0 };
  151. GetConsoleCursorInfo(handle, &cursorInfor);//获取控制台光标信息
  152. cursorInfor.bVisible = false;//包含stdbool头文件
  153. SetConsoleCursorInfo(handle, &cursorInfor);//设置光标信息
  154. //打印欢迎信息
  155. WelcomeToGame();
  156. //绘制地图
  157. CreateMap();
  158. //初始化蛇
  159. InitSnake(ps);
  160. //创建食物
  161. CreateFood(ps);
  162. //getchar();//暂停程序,方便观察
  163. }
  164. //打印帮助信息
  165. void PrintfHelpInfo()
  166. {
  167. SetPos(65, 15);
  168. printf("1.不能穿墙,不能咬到自己");
  169. SetPos(65, 16);
  170. printf("2.用↑,↓,←,→来控制蛇的移动");
  171. SetPos(65, 17);
  172. printf("3.F3是加速,F4是减速");
  173. SetPos(65, 18);
  174. printf("ESC:退出游戏 space:暂停游戏");
  175. SetPos(65, 20);
  176. printf("作者:假油淦");
  177. //getchar();
  178. }
  179. //暂停和恢复暂停
  180. void pause()
  181. {
  182. while (1)
  183. {
  184. Sleep(100);
  185. if (KEY_PRESS(VK_SPACE))
  186. {
  187. break;
  188. }
  189. }
  190. }
  191. //判断蛇头的下一步要走的位置处是否是食物
  192. int NextIsFood(pSnake ps, pSnakeNode pNext)
  193. {
  194. if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y)
  195. {
  196. return 1;//下一个坐标是食物
  197. }
  198. else
  199. {
  200. return 0;
  201. }
  202. }
  203. //下一步要走的位置就是食物
  204. void EatFood(pSnake ps, pSnakeNode pNext)
  205. {
  206. //头插
  207. pNext->next = ps->pSnake;
  208. ps->pSnake = pNext;
  209. //打印蛇身
  210. pSnakeNode cur = ps->pSnake;
  211. while (cur)
  212. {
  213. SetPos(cur->x, cur->y);
  214. wprintf(L"%lc", BODY);
  215. cur = cur->next;
  216. }
  217. //把next节点吃掉了,此时分数要变
  218. ps->Score += ps->FoodWeight;
  219. //所以要释放食物节点
  220. free(ps->pFood);
  221. //还要创建食物
  222. CreateFood(ps);
  223. }
  224. //下一步要走的位置不是食物
  225. void NotEatFood(pSnake ps, pSnakeNode pNext)
  226. {
  227. //头插并尾删
  228. pNext->next = ps->pSnake;
  229. ps->pSnake = pNext;
  230. pSnakeNode prev = ps->pSnake;
  231. pSnakeNode cur = ps->pSnake;
  232. while (cur->next)
  233. {
  234. //顺带打印
  235. SetPos(cur->x, cur->y);
  236. wprintf(L"%lc", BODY);
  237. prev = cur;
  238. cur = cur->next;
  239. }
  240. //先将尾节点打印为空
  241. SetPos(cur->x, cur->y);
  242. printf(" ");//注意这里是两个空格
  243. //此时释放
  244. prev->next = NULL;
  245. free(cur);
  246. }
  247. //蛇移动的函数
  248. void SnakeMove(pSnake ps)
  249. {
  250. //创建一个节点
  251. pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
  252. if (pNext == NULL)
  253. {
  254. perror("SnakeMove()::malloc()");
  255. return;
  256. }
  257. pNext->next = NULL;
  258. switch (ps->dir)
  259. {
  260. case UP:
  261. pNext->x = ps->pSnake->x;
  262. pNext->y = ps->pSnake->y - 1;
  263. break;
  264. case DOWN:
  265. pNext->x = ps->pSnake->x;
  266. pNext->y = ps->pSnake->y + 1;
  267. break;
  268. case LEFT:
  269. pNext->x = ps->pSnake->x - 2;
  270. pNext->y = ps->pSnake->y;
  271. break;
  272. case RIGHT:
  273. pNext->x = ps->pSnake->x + 2;
  274. pNext->y = ps->pSnake->y;
  275. break;
  276. }
  277. //判断下一个坐标是否是食物
  278. if (NextIsFood(ps, pNext))
  279. {
  280. //是食物吃掉
  281. EatFood(ps, pNext);
  282. }
  283. else
  284. {
  285. //不是食物
  286. NotEatFood(ps, pNext);
  287. }
  288. }
  289. //蛇撞墙
  290. void KillByWall(pSnake ps)
  291. {
  292. //直接判断蛇头位置即可
  293. if (ps->pSnake->x == 0 || ps->pSnake->x == 56
  294. || ps->pSnake->y == 0 || ps->pSnake->y == 26)
  295. {
  296. ps->status = KILL_BY_WALL;
  297. }
  298. }
  299. //蛇撞到自己
  300. void KillBySelf(pSnake ps)
  301. {
  302. //判断蛇头是否与蛇自身其他节点重合
  303. //直接从第二个节点开始判断
  304. pSnakeNode cur = ps->pSnake->next;
  305. while (cur)
  306. {
  307. if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
  308. {
  309. ps->status = KILL_BY_SELF;
  310. return;
  311. }
  312. cur = cur->next;
  313. }
  314. }
  315. //计算蛇身节点个数
  316. int SnakeNodeCount(pSnake ps)
  317. {
  318. pSnakeNode cur = ps->pSnake;
  319. int count = 0;
  320. while (cur)
  321. {
  322. count++;
  323. cur = cur->next;
  324. }
  325. return count;
  326. }
  327. //判断获胜
  328. void IsVictory(pSnake ps)
  329. {
  330. //我们一共有675个宽字符的格子
  331. //我们直接去判断蛇身节点有多少个去判断输赢
  332. int ret = SnakeNodeCount(ps);
  333. if (ret == 675)
  334. {
  335. ps->status = VICTORY;
  336. }
  337. else
  338. {
  339. return;
  340. }
  341. }
  342. //玩游戏的过程
  343. void GameRun(pSnake ps)
  344. {
  345. //打印帮助信息
  346. PrintfHelpInfo();
  347. do
  348. {
  349. //当前的分数情况
  350. SetPos(65, 10);
  351. printf("总分:%5d\n", ps->Score);
  352. SetPos(65, 11);
  353. printf("食物的分值:%02d\n", ps->FoodWeight);
  354. //检测按键
  355. //上、下、左、右、ESC、空格、F3、F4
  356. //虚拟键码
  357. if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
  358. {//按了上键当前贪吃蛇方向不朝下走
  359. ps->dir = UP;
  360. }
  361. else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
  362. {
  363. ps->dir = DOWN;
  364. }
  365. else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
  366. {
  367. ps->dir = LEFT;
  368. }
  369. else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
  370. {
  371. ps->dir = RIGHT;
  372. }
  373. else if (KEY_PRESS(VK_ESCAPE))
  374. {
  375. ps->status = ESC;//正常退出
  376. break;
  377. }
  378. else if (KEY_PRESS(VK_SPACE))
  379. {
  380. //游戏要暂停
  381. pause();//暂停和恢复暂停
  382. }
  383. else if (KEY_PRESS(VK_F3))
  384. {
  385. //加速
  386. //睡眠时间变短
  387. if (ps->SleepTime > 80)
  388. {
  389. ps->SleepTime -= 30;
  390. ps->FoodWeight += 2;
  391. }
  392. }
  393. else if (KEY_PRESS(VK_F4))
  394. {
  395. //减速
  396. if (ps->FoodWeight > 2)
  397. {
  398. ps->SleepTime += 30;
  399. ps->FoodWeight -= 2;
  400. }
  401. }
  402. //睡眠一下
  403. Sleep(ps->SleepTime);
  404. //走一步
  405. SnakeMove(ps);
  406. //此时蛇已经走了一步了,我们需要判断游戏是否结束
  407. //蛇撞墙
  408. KillByWall(ps);
  409. //蛇撞到自己
  410. KillBySelf(ps);
  411. //判断获胜
  412. IsVictory(ps);
  413. } while (ps->status == OK);
  414. }
  415. //善后工作
  416. void GameEnd(pSnake ps)
  417. {
  418. SetPos(18, 12);
  419. switch (ps->status)
  420. {
  421. case ESC:
  422. printf("主动退出游戏,正常退出游戏\n");
  423. break;
  424. case KILL_BY_WALL:
  425. printf("很遗憾,撞墙了,游戏结束\n");
  426. SetPos(22, 13);
  427. printf("最终得分为:%d\n", ps->Score);
  428. break;
  429. case KILL_BY_SELF:
  430. printf("很遗憾,咬到自己了,游戏结束\n");
  431. SetPos(22, 13);
  432. printf("最终得分为:%d\n", ps->Score);
  433. break;
  434. case VICTORY:
  435. printf("恭喜你,获得胜利!\n");
  436. break;
  437. }
  438. //释放贪吃蛇的链表资源
  439. pSnakeNode cur = ps->pSnake;
  440. pSnakeNode nextPos = ps->pSnake;
  441. while (cur)
  442. {
  443. nextPos = cur->next;
  444. free(cur);
  445. cur = nextPos;
  446. }
  447. free(ps->pFood);
  448. ps->pSnake = NULL;
  449. }

总结:

        如果你通过该文章完成了一次,其实你会发现,也没那么难,无非就是根据游戏的进程一步一步去实现每个最微小的步骤。其实所有的难题,都是聚沙成塔的,只要你一步一个脚印去实现,真的没有难题!

        API帮助了我们实现了贪吃蛇,我们可能不知道其内部的实现,但是我们只需要去学会使用它即可。如果你可以独立完成一次,那么恭喜你,你已经彻底学会了C语言,接下来就可以往更高的地点出发。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/1001487
推荐阅读
相关标签
  

闽ICP备14008679号