• 贪吃蛇(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列
    也可以通过命令设置控制台窗口的名字:
    这些能在控制台窗口执行的命令 也可以调用C语言函数system来执行
    代码实现:

    控制台屏幕上的坐标COORD

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

    给坐标赋值:

    COORD pos = { 2, 10 };
    

    GetStdHandle

    GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊--键盘、标准输出--屏幕或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。(我们可以把句柄想象成一个钥匙  只有拿到钥匙才能打开锁 ---只有得到句柄后才能进行一些操作)
     HANDLE GetStdHandle(DWORD nStdHandle);

    在我们贪吃蛇代码中  函数的参数是STD_OUTPUT_HANDLE  标准输出

    实例:

    1. HANDLE houtput = NULL;
    2. //获取标准输出的句柄(⽤来标识不同设备的数值)
    3. houtput = GetStdHandle(STD_OUTPUT_HANDLE);

    GetConsoleCursorInfo

    检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息
    在该函数中 有一参数是 PCONSOLE_CURSOR_INFO   其是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标 (光标)的信息

    实例:

    1. //获得标准输出设备的句柄
    2. HANDLE houtput = NULL;
    3. houtput=GetStdHandle(STD_OUTPUT_HANDLE);//houput获取标准输出的句柄 使得可以操作屏幕
    4. //定义一个光标信息的结构体
    5. CONSOLE_CURSOR_INFO cursor_info = { 0 };
    6. //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中
    7. GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息

    CONSOLE_CURSOR_INFO

    这个结构体,包含有关控制台光标的信息
    dwSize,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完
    全填充单元格到单元底部的⽔平线条。
    bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 TRUE
    在前一个代码中我们声明了一个结构体cursor_info
    cursor_info.bVisible = false; //隐藏控制台光标

    SetConsoleCursorInfo

    设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性
    1. //获得标准输出设备的句柄
    2. HANDLE houtput = NULL;
    3. houtput=GetStdHandle(STD_OUTPUT_HANDLE);//houput获取标准输出的句柄 使得可以操作屏幕
    4. //定义一个光标信息的结构体
    5. CONSOLE_CURSOR_INFO cursor_info = { 0 };
    6. //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中
    7. GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
    8. //修改光标占比
    9. cursor_info.dwSize = 50;
    10. cursor_info.bVisible = false;
    11. //设置和houtput句柄相关的控制台上的光标信息
    12. SetConsoleCursorInfo(houtput, &cursor_info);

    SetConsoleCursorPosition

    设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置
    实例:
    1. //获得标准输出设备的句柄
    2. HANDLE houtput = NULL;
    3. houtput = GetStdHandle(STD_OUTPUT_HANDLE);//句柄houput获取标准输出的句柄 使得可以操作屏幕
    4. //定位光标的位置
    5. COORD pos = { 10,20 };
    6. SetConsoleCursorPosition(houtput, pos);

    SetPos:封装一个设置光标位置的函数  ----贪吃蛇地图  蛇  食物的绘制都需要移动光标

    1. void SetPos(short x, short y)//封装一个函数SetPos 光标位置
    2. {
    3. HANDLE houtput = NULL;
    4. //获取标准输出的句柄(⽤来标识不同设备的数值)
    5. houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    6. //定位光标位置
    7. COORD pos = { x, y };
    8. SetConsoleCursorPosition(houtput, pos);
    9. }

    GetAsyncKeyState

    获取按键情况  将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态
    GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果
    返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
    如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
    在贪吃蛇中 如:方向键......都需要该函数进行判断是否被按过
    这里我们可以写一个宏:
    1. #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
    2. //这里的0x1也就是 1

    Win32 API我们就讲到这里

    除了Win32 API外 我们还有其他知识需要学习

    我这里是用VS2022写的代码  这里还需要对运行窗口做些改变

    贪吃蛇游戏设计与分析

    绘制地图(CreatMap)

    我们假设实现一个27行  58列的棋盘  再围绕地图画出墙

    这⾥不得不讲⼀下控制台窗⼝的⼀些知识,如果想在控制台的窗⼝中指定位置输出信息,我们得知道该位置的坐标,所以⾸先介绍⼀下控制台窗⼝的坐标知识。
    控制台窗⼝的坐标如下所⽰,横向的是X轴,从左向右依次增⻓,纵向是Y轴,从上到下依次增⻓
    在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★
    普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。
    这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语⾔并不适合⾮英语国家(地区)使⽤。
    C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。
    C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7位,最⾼位是没有使⽤的,可表⽰为0xxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够⽤的,但是,在其他国家语⾔中,⽐如,在法语中,字⺟上⽅有注⾳符号,它就⽆法⽤ ASCII 码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编⼊新的符号。⽐如,法语中的é的编码为130(⼆进制10000010)。这样⼀来,这些欧洲国家使⽤的编码体系,可以表⽰最多256个符号。但是,这⾥⼜出现了新的问题。不同的国家有不同的字⺟,因此,哪怕它们都使⽤256个符号的编码⽅式,代表的字⺟却不⼀样。⽐如,130在法语编码中代表了é,在希伯来语编码中却代表了字⺟Gimel  。 在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这 些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。
    ⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号,肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是 GB2312,使⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰ 256 x 256 = 65536 个符号。
    后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型wchar_t 和宽字符的输⼊和输出函数,加⼊了头⽂件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。

    本地化

    提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。
    在标准中,依赖地区的部分有以下⼏项:
    数字量的格式
    货币量的格式
    字符集
    ⽇期和时间的表⽰形式

    类项

    通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中⼀部分可能是我们不希望修改的。所以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函数。⽤" "作为第2个参数,调⽤setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
    ⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。
    setlocale(LC_ALL, " ");//切换到本地环境

    宽字符的打印

    那如果想在屏幕上打印宽字符,怎么打印呢?
    宽字符的字⾯量必须加上前缀“L”,否则 C 语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引
    号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应
    wprintf() 的占位符为 %ls
    代码实例:
    从输出结果就可以看出  打印一个普通字符 占一个字符的位置  但是打印一个宽字符  占用2个字符的位置  所以我们在贪吃蛇中使用宽字符时  得处理好地图上坐标的计算

    2.蛇身和食物(InitSnake) (CreateFood)

    初始化状态 ,假设蛇的⻓度是5,蛇⾝的每个节点是●,在固定的⼀个坐标处,⽐如(24, 5)处开始出现 蛇,连续5个节点
    注意:蛇的每个节点的x坐标必须是2的倍数,否则可能会出现蛇的⼀个节点有⼀半⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬。
    关于⻝物,就是在墙体内随机⽣成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的⾝体重合,也不能超出墙体,然后打印★。
    如下图:

    3.数据结构设计(蛇的结点  贪吃蛇的方向 状态  食物分数  总得分  加速  减速....)

    在游戏运⾏的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变⻓⼀节,如果我们使⽤链表存储蛇的信
    息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇⾝节点在地图上的坐标就⾏,所以蛇节点结构如下:
    1. typedef struct Snakenode
    2. {
    3. //坐标
    4. int x;
    5. int y;
    6. //指向下一个节点的指针
    7. struct Snakenode* next;
    8. }Snakenode,*pSnakeNode;//结构体指针pSnakeNode
    要管理整条贪吃蛇,我们再封装⼀个Snake的结构来维护整条贪吃蛇:
    1. typedef struct Snake
    2. {
    3. pSnakeNode _pSnake;//指向蛇头的指针
    4. pSnakeNode _pFood;//指向食物节点的指针
    5. enum DIRECTION _dir;//蛇的方向
    6. enum GAME_STATUS _status;//游戏的状态
    7. int _food_weight;//食物分数
    8. int _score;//总分数
    9. int _sleep_time;//休息时间 时间越短 速度越快 时间越长 速度越慢
    10. }Snake,*pSnake;
    蛇的⽅向,可以⼀⼀列举,使⽤枚举
    1. enum DIRECTION
    2. {
    3. UP = 1,
    4. DOWN,
    5. LEFT,
    6. RIGHT
    7. };
    游戏状态,可以⼀⼀列举,使⽤枚举
    1. //蛇的状态
    2. //正常 撞墙 撞到自己 正常退出
    3. enum GAME_STATUS
    4. {
    5. OK,
    6. KILL_BY_WALL,
    7. KILL_BY_SELF,
    8. END_NORMAL
    9. };

    游戏流程设计

    核⼼逻辑实现分析

    程序开始就设置程序⽀持本地模式,然后进⼊游戏的主逻辑

    主逻辑分为3个过程:
    游戏开始(GameStart)完成游戏的初始化
    游戏运⾏(GameRun)完成游戏运⾏逻辑的实现
    游戏结束(GameEnd)完成游戏结束的说明,实现资源释放
    1. //Test.c
    2. #include
    3. #include"snake.h"
    4. //完成的是游戏的测试逻辑
    5. void test()
    6. {
    7. int ch = 0;
    8. do
    9. {//创建贪吃蛇
    10. Snake snake = { 0 };
    11. //初始化游戏
    12. //1.打印环境页面
    13. //2.功能介绍
    14. //3.绘制地图
    15. //4.创建蛇
    16. //5.创建食物
    17. //6.设置游戏的相关信息
    18. GameStart(&snake);
    19. //运行游戏
    20. GameRun(&snake);
    21. //结束游戏--善后工作
    22. GameEnd(&snake);
    23. SetPos(20, 15);
    24. printf("再来一局吗?(Y/N):");
    25. ch = getchar();//按下回车后 其实接收到的是\n 所以需要清理掉\n
    26. //getchar();//清理掉\n
    27. while (getchar() != '\n')
    28. ;
    29. } while (ch=='Y' || ch == 'y');
    30. SetPos(0, 26);//让代码结束的信息放在地图外
    31. }
    32. int main()
    33. {
    34. //设置适配本地环境
    35. setlocale(LC_ALL, "");//中文 宽字符的打印
    36. srand((unsigned int)time(NULL));
    37. test();
    38. return 0;
    39. }

    游戏开始(GameStart)

    1.设置控制台窗口大小  名称  隐藏光标

    1. void GameStart(pSnake ps)
    2. {
    3. //0.先设置窗口的大小 然后把光标隐藏
    4. system("mode con cols=100 lines=30");//30行 100列
    5. system("title 贪吃蛇");
    6. HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);
    7. //隐藏光标操作
    8. //定义一个光标信息的结构体
    9. CONSOLE_CURSOR_INFO cursor_info;
    10. //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中
    11. GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
    12. cursor_info.bVisible = false;
    13. //设置和houtput句柄相关的控制台上的光标信息
    14. SetConsoleCursorInfo(houtput, &cursor_info);
    15. //1.打印欢迎页面 //2.功能介绍
    16. WelcomeToGame();
    17. //3.绘制地图
    18. CreatMap();
    19. //4.创建蛇
    20. InitSnake(ps);
    21. //5.创建食物
    22. CreateFood(ps);
    23. }

    2.欢迎页面及功能介绍(WelcomeToGame)

    打印欢迎页面

    在我们使用光标位置 之前  我们封装一个函数SetPos  

    1. void SetPos(short x, short y)//封装一个函数SetPos 光标位置
    2. {
    3. HANDLE houtput = NULL;
    4. //获取标准输出的句柄(⽤来标识不同设备的数值)
    5. houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    6. //定位光标位置
    7. COORD pos = { x, y };
    8. SetConsoleCursorPosition(houtput, pos);
    9. }
    1. void WelcomeToGame()
    2. {
    3. SetPos(45, 14);
    4. wprintf(L"欢迎来到贪吃蛇小游戏\n");
    5. SetPos(46, 20);
    6. system("pause");
    7. system("cls");//清除控制台信息
    8. SetPos(30, 14);
    9. wprintf(L"用↑ ↓ ← → 来控制蛇的移动 按F3加速 F4减速\n");
    10. SetPos(34, 15);
    11. wprintf(L"加速能够得到更高的分数");
    12. SetPos(46, 20);
    13. system("pause");
    14. system("cls");//紧接着下面应该是地图绘制
    15. }

    代码实现结果:

    3.绘制地图(CreatMap)

    绘制地图就是将墙体打印出来  因为这里是宽字符打印  所有打印使用wprintf函数  打印格式串前使用L   绘制地图的关键是算好光标的坐标  才能在想要的位置打印墙体

    为墙体打印的宽字符写个宏:

    #define WALL L'□'

    这里的易错点也就是坐标的计算

    代码如下:

    1. void CreatMap()
    2. {//27行 58列的棋盘
    3. //上
    4. int i = 0;
    5. for (i = 0; i < 29; i++)
    6. {//□
    7. wprintf(L"%lc", WALL);
    8. }
    9. //下
    10. SetPos(0, 26);
    11. for (i = 0; i < 29; i++)
    12. wprintf(L"%lc", WALL);
    13. //左
    14. for (i = 1; i <= 25; i++)
    15. {
    16. SetPos(0, i);
    17. wprintf(L"%lc", WALL);
    18. }
    19. //右
    20. for (i = 1; i <= 25; i++)
    21. {
    22. SetPos(56, i);
    23. wprintf(L"%lc", WALL);
    24. }
    25. //getchar();
    26. }

    4.创建蛇(初始化蛇身)(InitSnake)

    蛇最开始⻓度为5节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。
    创建5个节点,然后将每个节点存放在链表中进⾏管理。创建完蛇⾝后,将蛇的每⼀节打印在屏幕上。
    蛇的初始位置从 (24,5) 开始。
    再设置贪吃蛇的属性
    蛇的默认⽅向:RIGHT
    初始成绩:0
    每个⻝物的分数:10
    蛇的移动速度:200毫秒
    游戏状态是:OK
    1. void InitSnake(pSnake ps)
    2. {
    3. int i = 0;
    4. pSnakeNode cur = NULL;
    5. for (i = 0; i < 5; i++)
    6. {
    7. cur = (pSnakeNode)malloc(sizeof(Snakenode));
    8. if (cur == NULL)
    9. {
    10. perror("InitSnake()::malloc()");
    11. return;
    12. }
    13. cur->next = NULL;
    14. cur->x =POS_X +i*2;//显示 + 未使用的表达式结果
    15. cur->y = POS_Y;
    16. //头插法插入链表
    17. if (ps->_pSnake == NULL)//空链表 //蛇头
    18. {
    19. ps->_pSnake = cur;
    20. }
    21. else//非空
    22. {
    23. cur->next = ps->_pSnake;
    24. ps->_pSnake = cur;
    25. }
    26. }
    27. cur = ps->_pSnake;
    28. while (cur)
    29. {
    30. SetPos(cur->x, cur->y);
    31. wprintf(L"%lc", BODY);
    32. cur = cur->next;
    33. }
    34. //设置贪吃蛇的属性
    35. ps->_dir = RIGHT;//默认向右
    36. ps->_score = 0;
    37. ps->_food_weight = 10;
    38. ps->_sleep_time = 200;//毫秒
    39. ps->_status = OK;
    40. /*getchar();*/为什么我的代码只打印一个●
    41. }
    蛇⾝打印的宽字符:
    #define BODY L'●'

    5.创建食物(CreateFood)

    先随机生成食物的坐标(rand和srand函数)  

    1.x坐标必须是2的倍数

    2.食物的坐标不能和蛇身每个结点的坐标重复  不能越过墙体

    创建食物结点  打印食物

    食物打印的宽字符:

    #define FOOD L'★'

    CreateFood函数:

    1. void CreateFood(pSnake ps)
    2. {
    3. int x = 0;
    4. int y = 0;
    5. //x = rand()%53+2;//x:2~54---0~52 //还要保证x是偶数
    6. //y = rand()%25+1;//y:1~25---0~24
    7. again:
    8. do
    9. {
    10. x = rand() % 53 + 2;//x:2~54---0~52 //还要保证x是偶数
    11. y = rand()%25+1;//y:1~25---0~24
    12. } while (x % 2 != 0);
    13. //x和y的坐标不能跟蛇的身体相冲突
    14. pSnakeNode cur = ps->_pSnake;
    15. while (cur)
    16. {
    17. if (x == cur->x && y == cur->y)
    18. {
    19. goto again;
    20. }
    21. cur = cur->next;
    22. }
    23. //创捷食物结点
    24. pSnakeNode pFood=(pSnakeNode)malloc(sizeof(Snakenode));
    25. if (pFood == NULL)
    26. {
    27. perror("CreateFood()::malloc()");
    28. }
    29. pFood->x = x;
    30. pFood->y = y;
    31. pFood->next = NULL;
    32. SetPos(x, y);
    33. wprintf(L"%lc", FOOD);
    34. ps->_pFood = pFood;
    35. }

    游戏运行(GameRun)

    1.游戏运⾏期间,右侧打印帮助信息(PrintHelpInfo)

    1. void PrintHelpInfo()
    2. {
    3. SetPos(64, 10);
    4. wprintf(L"%ls", L"不能穿墙,不能咬到自己");
    5. SetPos(64, 11);
    6. wprintf(L"%ls", L"用↑ ↓ ← → 来控制蛇的移动 \n");
    7. SetPos(64, 12);
    8. wprintf(L"%ls", L"按F3加速 F4减速");
    9. SetPos(64, 13);
    10. wprintf(L"%ls", L"按ESC退出游戏 空格暂停游戏");
    11. SetPos(64, 14);
    12. wprintf(L"%ls", L"made in LiFeiFei");
    13. }

    2.根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。

    游戏有两种状态:i.蛇撞墙而亡    ii.蛇撞到自己

    1. void KillByWall(pSnake ps)
    2. {
    3. if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||
    4. ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
    5. {
    6. ps->_status = KILL_BY_WALL;
    7. }
    8. }
    9. void KillBySelf(pSnake ps)
    10. {
    11. pSnakeNode cur = ps->_pSnake->next;//cur指向蛇身
    12. while (cur)
    13. {
    14. if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
    15. {
    16. ps->_status = KILL_BY_SELF;
    17. break;
    18. }
    19. cur = cur->next;
    20. }
    21. }

    3.如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。

    1. //获取按键情况 如果按键最小位为1 说明已按过
    2. #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  这里我们使用头插---新增结点位于蛇头  
    1. void NoFood(pSnakeNode pn, pSnake ps)
    2. {
    3. //头插法
    4. pn->next = ps->_pSnake;
    5. ps->_pSnake = pn;
    6. pSnakeNode cur = ps->_pSnake;
    7. while (cur->next->next != NULL)
    8. {
    9. SetPos(cur->x, cur->y);
    10. wprintf(L"%lc", BODY);
    11. cur = cur->next;
    12. }
    13. //把最后一个结点打印成空格
    14. SetPos(cur->next->x, cur->next->y);
    15. printf(" ");
    16. //释放最后一个结点
    17. free(cur->next);
    18. //将倒数第二个结点的地址域置为NULL
    19. cur->next = NULL;
    20. }
    21. int NextIsFood(pSnakeNode pn, pSnake ps)
    22. {
    23. return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
    24. }
    25. void EatFood(pSnakeNode pn, pSnake ps)
    26. {
    27. //头插法
    28. ps->_pFood->next = ps->_pSnake;
    29. ps->_pSnake = ps->_pFood;
    30. //释放下一个位置的结点(我们设置了一个食物结点 和下一个结点 )
    31. free(pn);
    32. pn = NULL;
    33. pSnakeNode cur = ps->_pSnake;
    34. //打印蛇
    35. while (cur)
    36. {
    37. SetPos(cur->x, cur->y);
    38. wprintf(L"%lc", BODY);
    39. cur = cur->next;
    40. }
    41. ps->_score += ps->_food_weight;
    42. //重新创建食物
    43. CreateFood(ps);
    44. }
    45. void pause()//暂停
    46. {
    47. while (1)
    48. {
    49. Sleep(200);
    50. if (KEY_PRESS(VK_SPACE))
    51. {
    52. break;
    53. }
    54. }
    55. }

    在NoFood中 有一个易错点:在打印下一步的蛇身时  也就是将下一个结点当作蛇头  并将之前蛇身最后一个结点打印为空格  释放掉原本蛇身的最后一个结点  在释放掉最后一个结点后  还得将指向(原先蛇身的倒数第二个结点)最后一个结点的指针改为NULL  保证蛇尾打印可以正常结束  不会越界访问

    1. void SnakeMove(pSnake ps)
    2. {
    3. //创建一个结点 表示蛇即将到的下一个结点
    4. pSnakeNode pNextNode=(pSnakeNode)malloc(sizeof(Snakenode));
    5. if (pNextNode == NULL)
    6. {
    7. perror("SnakeMove()::malloc()");
    8. return;
    9. }
    10. switch (ps->_dir)
    11. {
    12. case UP:
    13. pNextNode->x = ps->_pSnake->x;
    14. pNextNode->y = ps->_pSnake->y-1;
    15. break;
    16. case DOWN:
    17. pNextNode->x = ps->_pSnake->x;
    18. pNextNode->y = ps->_pSnake->y+1;
    19. break;
    20. case LEFT:
    21. pNextNode->x = ps->_pSnake->x-2;
    22. pNextNode->y = ps->_pSnake->y;
    23. break;
    24. case RIGHT:
    25. pNextNode->x = ps->_pSnake->x+2;
    26. pNextNode->y = ps->_pSnake->y;
    27. break;
    28. }
    29. //检测下一个坐标处是否是食物
    30. if (NextIsFood(pNextNode,ps))
    31. {
    32. EatFood(pNextNode, ps);
    33. }
    34. else
    35. {
    36. NoFood(pNextNode, ps);
    37. }
    38. //检测蛇是否撞墙
    39. KillByWall(ps);
    40. //检测蛇是否撞到自己
    41. KillBySelf(ps);
    42. }
    确定了蛇的⽅向和速度,蛇就可以移动了。
    1. void GameRun(pSnake ps)
    2. {
    3. //打印帮助信息
    4. PrintHelpInfo();
    5. do
    6. {
    7. //打印总分数 和食物的分值
    8. SetPos(64, 8);
    9. printf("总分数:%d\n", ps->_score);
    10. SetPos(64, 9);
    11. printf("当前食物的分值:%2d\n", ps->_food_weight);
    12. if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
    13. {
    14. ps->_dir = UP;
    15. }
    16. else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
    17. {
    18. ps->_dir = DOWN;
    19. }
    20. else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
    21. {
    22. ps->_dir = LEFT;
    23. }
    24. else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
    25. {
    26. ps->_dir = RIGHT;
    27. }
    28. else if (KEY_PRESS(VK_SPACE))//空格
    29. {
    30. pause();
    31. }
    32. else if (KEY_PRESS(VK_ESCAPE))//ESC
    33. {//正常退出游戏
    34. ps->_status = END_NORMAL;//退出游戏
    35. break;
    36. }
    37. else if (KEY_PRESS(VK_F3))//F3加速---休眠时间减少
    38. {
    39. if (ps->_sleep_time >= 80)//初始休眠时间为200-80=120 减4次
    40. {//最小休眠时间为50
    41. ps->_sleep_time -= 30;
    42. ps->_food_weight += 2;//食物初始分值为10 ⼀个⻝物分数最⾼是20分
    43. }
    44. }
    45. else if (KEY_PRESS(VK_F4))//F4减速
    46. {
    47. if (ps->_sleep_time < 320)//200 230 260 290 320
    48. { //10 8 6 4 2
    49. ps->_sleep_time += 30;
    50. ps->_food_weight -= 2;//⼀个⻝物分数最低是2分
    51. }
    52. }
    53. SnakeMove(ps);//蛇走一步的过程
    54. Sleep(ps->_sleep_time);
    55. } while (ps->_status == OK);
    56. }

    游戏结束(GameEnd)

    游戏状态不再是OK的时候  要告知游戏结束的原因  并且释放蛇身结点

    1. void GameEnd(pSnake ps)
    2. {
    3. SetPos(24, 12);
    4. switch (ps->_status)
    5. {
    6. case END_NORMAL:
    7. printf("有事 不玩了\n");
    8. break;
    9. case KILL_BY_WALL:
    10. printf("撞个墙玩玩\n");
    11. break;
    12. case KILL_BY_SELF:
    13. printf("吃个自己吧\n");
    14. break;
    15. }
    16. //释放蛇身的链表
    17. pSnakeNode cur = ps->_pSnake;
    18. while (cur)
    19. {
    20. pSnakeNode del = cur;
    21. cur = cur->next;
    22. free(del);
    23. }
    24. }

    完整代码如下

    Snake.h

    1. //Snake.h
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #define POS_X 24
    8. #define POS_Y 5
    9. #define WALL L'□'
    10. #define BODY L'●'
    11. #define FOOD L'★'
    12. //类型的声明
    13. //
    14. //蛇身的结点类型
    15. typedef struct Snakenode
    16. {
    17. //坐标
    18. int x;
    19. int y;
    20. //指向下一个节点的指针
    21. struct Snakenode* next;
    22. }Snakenode,*pSnakeNode;//结构体指针pSnakeNode
    23. //贪吃蛇
    24. typedef struct Snake
    25. {
    26. pSnakeNode _pSnake;//指向蛇头的指针
    27. pSnakeNode _pFood;//指向食物节点的指针
    28. enum DIRECTION _dir;//蛇的方向
    29. enum GAME_STATUS _status;//游戏的状态
    30. int _food_weight;//食物分数
    31. int _score;//总分数
    32. int _sleep_time;//休息时间 时间越短 速度越快 时间越长 速度越慢
    33. }Snake,*pSnake;
    34. //枚举方向
    35. enum DIRECTION
    36. {
    37. UP = 1,
    38. DOWN,
    39. LEFT,
    40. RIGHT
    41. };
    42. //蛇的状态
    43. //正常 撞墙 撞到自己 正常退出
    44. enum GAME_STATUS
    45. {
    46. OK,
    47. KILL_BY_WALL,
    48. KILL_BY_SELF,
    49. END_NORMAL
    50. };
    51. //函数的声明
    52. //定位光标位置
    53. void SetPos(short x, short y);
    54. //游戏的初始化
    55. void GameStart(pSnake ps);
    56. //欢迎界面的打印
    57. void WelcomeToGame();
    58. //创建地图
    59. void CreatMap();
    60. //4.创建蛇 --初始化蛇身
    61. void InitSnake(pSnake ps);
    62. //创建食物
    63. void CreateFood(pSnake ps);
    64. //游戏运行逻辑
    65. void GameRun(pSnake ps);
    66. //蛇走一步的过程
    67. void SnakeMove(pSnake ps);
    68. //判断下一个坐标是否是食物
    69. int NextIsFood(pSnakeNode pn, pSnake ps);
    70. //下一个位置是食物 就吃掉食物
    71. void EatFood(pSnakeNode pn, pSnake ps);
    72. //下一个位置不是食物
    73. void NoFood(pSnakeNode pn, pSnake ps);
    74. //检测蛇是否撞墙
    75. void KillByWall(pSnake ps);
    76. //检测蛇是否撞到自己
    77. void KillBySelf(pSnake ps);
    78. //游戏善后的工作
    79. void GameEnd(pSnake ps);

    Snake.c

    1. //snake.c
    2. #include"snake.h"
    3. void SetPos(short x, short y)//封装一个函数SetPos 光标位置
    4. {
    5. HANDLE houtput = NULL;
    6. //获取标准输出的句柄(⽤来标识不同设备的数值)
    7. houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    8. //定位光标位置
    9. COORD pos = { x, y };
    10. SetConsoleCursorPosition(houtput, pos);
    11. }
    12. void WelcomeToGame()
    13. {
    14. SetPos(45, 14);
    15. wprintf(L"欢迎来到贪吃蛇小游戏\n");
    16. SetPos(46, 20);
    17. system("pause");
    18. system("cls");//清除控制台信息
    19. SetPos(30, 14);
    20. wprintf(L"用↑ ↓ ← → 来控制蛇的移动 按F3加速 F4减速\n");
    21. SetPos(34, 15);
    22. wprintf(L"加速能够得到更高的分数");
    23. SetPos(46, 20);
    24. system("pause");
    25. system("cls");//紧接着下面应该是地图绘制
    26. }
    27. void CreatMap()
    28. {//27行 58列的棋盘
    29. //上
    30. int i = 0;
    31. for (i = 0; i < 29; i++)
    32. {//□
    33. wprintf(L"%lc", WALL);
    34. }
    35. //下
    36. SetPos(0, 26);
    37. for (i = 0; i < 29; i++)
    38. wprintf(L"%lc", WALL);
    39. //左
    40. for (i = 1; i <= 25; i++)
    41. {
    42. SetPos(0, i);
    43. wprintf(L"%lc", WALL);
    44. }
    45. //右
    46. for (i = 1; i <= 25; i++)
    47. {
    48. SetPos(56, i);
    49. wprintf(L"%lc", WALL);
    50. }
    51. //getchar();
    52. }
    53. //4.创建蛇 --初始化蛇身
    54. void InitSnake(pSnake ps)
    55. {
    56. int i = 0;
    57. pSnakeNode cur = NULL;
    58. for (i = 0; i < 5; i++)
    59. {
    60. cur = (pSnakeNode)malloc(sizeof(Snakenode));
    61. if (cur == NULL)
    62. {
    63. perror("InitSnake()::malloc()");
    64. return;
    65. }
    66. cur->next = NULL;
    67. cur->x =POS_X +i*2;//显示 + 未使用的表达式结果
    68. cur->y = POS_Y;
    69. //头插法插入链表
    70. if (ps->_pSnake == NULL)//空链表 //蛇头
    71. {
    72. ps->_pSnake = cur;
    73. }
    74. else//非空
    75. {
    76. cur->next = ps->_pSnake;
    77. ps->_pSnake = cur;
    78. }
    79. }
    80. cur = ps->_pSnake;
    81. while (cur)
    82. {
    83. SetPos(cur->x, cur->y);
    84. wprintf(L"%lc", BODY);
    85. cur = cur->next;
    86. }
    87. //设置贪吃蛇的属性
    88. ps->_dir = RIGHT;//默认向右
    89. ps->_score = 0;
    90. ps->_food_weight = 10;
    91. ps->_sleep_time = 200;//毫秒
    92. ps->_status = OK;
    93. /*getchar();*/为什么我的代码只打印一个●
    94. }
    95. void CreateFood(pSnake ps)
    96. {
    97. int x = 0;
    98. int y = 0;
    99. //x = rand()%53+2;//x:2~54---0~52 //还要保证x是偶数
    100. //y = rand()%25+1;//y:1~25---0~24
    101. again:
    102. do
    103. {
    104. x = rand() % 53 + 2;//x:2~54---0~52 //还要保证x是偶数
    105. y = rand()%25+1;//y:1~25---0~24
    106. } while (x % 2 != 0);
    107. //x和y的坐标不能跟蛇的身体相冲突
    108. pSnakeNode cur = ps->_pSnake;
    109. while (cur)
    110. {
    111. if (x == cur->x && y == cur->y)
    112. {
    113. goto again;
    114. }
    115. cur = cur->next;
    116. }
    117. //创捷食物结点
    118. pSnakeNode pFood=(pSnakeNode)malloc(sizeof(Snakenode));
    119. if (pFood == NULL)
    120. {
    121. perror("CreateFood()::malloc()");
    122. }
    123. pFood->x = x;
    124. pFood->y = y;
    125. pFood->next = NULL;
    126. SetPos(x, y);
    127. wprintf(L"%lc", FOOD);
    128. ps->_pFood = pFood;
    129. }
    130. void GameStart(pSnake ps)
    131. {
    132. //0.先设置窗口的大小 然后把光标隐藏
    133. system("mode con cols=100 lines=30");//30行 100列
    134. system("title 贪吃蛇");
    135. HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);
    136. //隐藏光标操作
    137. //定义一个光标信息的结构体
    138. CONSOLE_CURSOR_INFO cursor_info;
    139. //获取和houtput句柄相关的控制台上的光标信息 并存放在cursor_info中
    140. GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
    141. cursor_info.bVisible = false;
    142. //设置和houtput句柄相关的控制台上的光标信息
    143. SetConsoleCursorInfo(houtput, &cursor_info);
    144. //1.打印欢迎页面 //2.功能介绍
    145. WelcomeToGame();
    146. //3.绘制地图
    147. CreatMap();
    148. //4.创建蛇
    149. InitSnake(ps);
    150. //5.创建食物
    151. CreateFood(ps);
    152. }
    153. void PrintHelpInfo()
    154. {
    155. SetPos(64, 10);
    156. wprintf(L"%ls", L"不能穿墙,不能咬到自己");
    157. SetPos(64, 11);
    158. wprintf(L"%ls", L"用↑ ↓ ← → 来控制蛇的移动 \n");
    159. SetPos(64, 12);
    160. wprintf(L"%ls", L"按F3加速 F4减速");
    161. SetPos(64, 13);
    162. wprintf(L"%ls", L"按ESC退出游戏 空格暂停游戏");
    163. SetPos(64, 14);
    164. wprintf(L"%ls", L"made in LiFeiFei");
    165. }
    166. //获取按键情况 如果按键最小位为1 说明已按过
    167. #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
    168. void pause()//暂停
    169. {
    170. while (1)
    171. {
    172. Sleep(200);
    173. if (KEY_PRESS(VK_SPACE))
    174. {
    175. break;
    176. }
    177. }
    178. }
    179. int NextIsFood(pSnakeNode pn, pSnake ps)
    180. {
    181. return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
    182. }
    183. void EatFood(pSnakeNode pn, pSnake ps)
    184. {
    185. //头插法
    186. ps->_pFood->next = ps->_pSnake;
    187. ps->_pSnake = ps->_pFood;
    188. //释放下一个位置的结点(我们设置了一个食物结点 和下一个结点 )
    189. free(pn);
    190. pn = NULL;
    191. pSnakeNode cur = ps->_pSnake;
    192. //打印蛇
    193. while (cur)
    194. {
    195. SetPos(cur->x, cur->y);
    196. wprintf(L"%lc", BODY);
    197. cur = cur->next;
    198. }
    199. ps->_score += ps->_food_weight;
    200. //重新创建食物
    201. CreateFood(ps);
    202. }
    203. void NoFood(pSnakeNode pn, pSnake ps)
    204. {
    205. //头插法
    206. pn->next = ps->_pSnake;
    207. ps->_pSnake = pn;
    208. pSnakeNode cur = ps->_pSnake;
    209. while (cur->next->next != NULL)
    210. {
    211. SetPos(cur->x, cur->y);
    212. wprintf(L"%lc", BODY);
    213. cur = cur->next;
    214. }
    215. //把最后一个结点打印成空格
    216. SetPos(cur->next->x, cur->next->y);
    217. printf(" ");
    218. //释放最后一个结点
    219. free(cur->next);
    220. //将倒数第二个结点的地址域置为NULL
    221. cur->next = NULL;
    222. }
    223. void KillByWall(pSnake ps)
    224. {
    225. if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||
    226. ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
    227. {
    228. ps->_status = KILL_BY_WALL;
    229. }
    230. }
    231. void KillBySelf(pSnake ps)
    232. {
    233. pSnakeNode cur = ps->_pSnake->next;//cur指向蛇身
    234. while (cur)
    235. {
    236. if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
    237. {
    238. ps->_status = KILL_BY_SELF;
    239. break;
    240. }
    241. cur = cur->next;
    242. }
    243. }
    244. void SnakeMove(pSnake ps)
    245. {
    246. //创建一个结点 表示蛇即将到的下一个结点
    247. pSnakeNode pNextNode=(pSnakeNode)malloc(sizeof(Snakenode));
    248. if (pNextNode == NULL)
    249. {
    250. perror("SnakeMove()::malloc()");
    251. return;
    252. }
    253. switch (ps->_dir)
    254. {
    255. case UP:
    256. pNextNode->x = ps->_pSnake->x;
    257. pNextNode->y = ps->_pSnake->y-1;
    258. break;
    259. case DOWN:
    260. pNextNode->x = ps->_pSnake->x;
    261. pNextNode->y = ps->_pSnake->y+1;
    262. break;
    263. case LEFT:
    264. pNextNode->x = ps->_pSnake->x-2;
    265. pNextNode->y = ps->_pSnake->y;
    266. break;
    267. case RIGHT:
    268. pNextNode->x = ps->_pSnake->x+2;
    269. pNextNode->y = ps->_pSnake->y;
    270. break;
    271. }
    272. //检测下一个坐标处是否是食物
    273. if (NextIsFood(pNextNode,ps))
    274. {
    275. EatFood(pNextNode, ps);
    276. }
    277. else
    278. {
    279. NoFood(pNextNode, ps);
    280. }
    281. //检测蛇是否撞墙
    282. KillByWall(ps);
    283. //检测蛇是否撞到自己
    284. KillBySelf(ps);
    285. }
    286. void GameRun(pSnake ps)
    287. {
    288. //打印帮助信息
    289. PrintHelpInfo();
    290. do
    291. {
    292. //打印总分数 和食物的分值
    293. SetPos(64, 8);
    294. printf("总分数:%d\n", ps->_score);
    295. SetPos(64, 9);
    296. printf("当前食物的分值:%2d\n", ps->_food_weight);
    297. if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
    298. {
    299. ps->_dir = UP;
    300. }
    301. else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
    302. {
    303. ps->_dir = DOWN;
    304. }
    305. else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
    306. {
    307. ps->_dir = LEFT;
    308. }
    309. else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
    310. {
    311. ps->_dir = RIGHT;
    312. }
    313. else if (KEY_PRESS(VK_SPACE))//空格
    314. {
    315. pause();
    316. }
    317. else if (KEY_PRESS(VK_ESCAPE))//ESC
    318. {//正常退出游戏
    319. ps->_status = END_NORMAL;//退出游戏
    320. break;
    321. }
    322. else if (KEY_PRESS(VK_F3))//F3加速---休眠时间减少
    323. {
    324. if (ps->_sleep_time >= 80)//初始休眠时间为200-80=120 减4次
    325. {//最小休眠时间为50
    326. ps->_sleep_time -= 30;
    327. ps->_food_weight += 2;//食物初始分值为10 ⼀个⻝物分数最⾼是20分
    328. }
    329. }
    330. else if (KEY_PRESS(VK_F4))//F4减速
    331. {
    332. if (ps->_sleep_time < 320)//200 230 260 290 320
    333. { //10 8 6 4 2
    334. ps->_sleep_time += 30;
    335. ps->_food_weight -= 2;//⼀个⻝物分数最低是2分
    336. }
    337. }
    338. SnakeMove(ps);//蛇走一步的过程
    339. Sleep(ps->_sleep_time);
    340. } while (ps->_status == OK);
    341. }
    342. void GameEnd(pSnake ps)
    343. {
    344. SetPos(24, 12);
    345. switch (ps->_status)
    346. {
    347. case END_NORMAL:
    348. printf("有事 不玩了\n");
    349. break;
    350. case KILL_BY_WALL:
    351. printf("撞个墙玩玩\n");
    352. break;
    353. case KILL_BY_SELF:
    354. printf("吃个自己吧\n");
    355. break;
    356. }
    357. //释放蛇身的链表
    358. pSnakeNode cur = ps->_pSnake;
    359. while (cur)
    360. {
    361. pSnakeNode del = cur;
    362. cur = cur->next;
    363. free(del);
    364. }
    365. }

    Test.c

    1. //test.c
    2. #include
    3. #include"snake.h"
    4. //完成的是游戏的测试逻辑
    5. void test()
    6. {
    7. int ch = 0;
    8. do
    9. {//创建贪吃蛇
    10. Snake snake = { 0 };
    11. //初始化游戏
    12. //1.打印环境页面
    13. //2.功能介绍
    14. //3.绘制地图
    15. //4.创建蛇
    16. //5.创建食物
    17. //6.设置游戏的相关信息
    18. GameStart(&snake);
    19. //运行游戏
    20. GameRun(&snake);
    21. //结束游戏--善后工作
    22. GameEnd(&snake);
    23. SetPos(20, 15);
    24. printf("再来一局吗?(Y/N):");
    25. ch = getchar();//按下回车后 其实接收到的是\n 所以需要清理掉\n
    26. //getchar();//清理掉\n
    27. while (getchar() != '\n')
    28. ;
    29. } while (ch=='Y' || ch == 'y');
    30. SetPos(0, 26);//让代码结束的信息放在地图外
    31. }
    32. int main()
    33. {
    34. //设置适配本地环境
    35. setlocale(LC_ALL, "");//中文 宽字符的打印
    36. srand((unsigned int)time(NULL));
    37. test();
    38. return 0;
    39. }

  • 相关阅读:
    机器学习笔记之条件随机场(四)建模对象描述
    leetcode 1742. 盒子中小球的最大数量
    Authing 入选 Gartner《2022 中国网络安全技术成熟度曲线》报告
    python Matplotlib绘制三维图
    1282_李杀_emacs示范配置文件init.el的配置简单分析
    Nebula数据库安装
    十五、Webpack打包图片-js-Vue、Label命令、resolve模块解析
    4个不限字数的AI智能写作网站,用好任意一个就可以了
    第九章《字符串》第5节:字符编码常识
    当食品制造业遇见数字化工具,如何借助S2B2C电商系统实现企业新增长
  • 原文地址:https://blog.csdn.net/L2770789195/article/details/138122449