• 从0开始用C写贪吃蛇(基于链表)


    目录

    1. 游戏背景

    2. 游戏效果演示​编辑​编辑​编辑

    3. 实现目标

    4. 技术要点

    5. 控制台程序

    5.1  设置控制台窗口的长宽和名字

    5.2 控制台屏幕上的坐标COORD

    6.Win32 API  

    6.1 GetStdHandle 

    6.2 GetConsoleCursorInfo

    6.3 CONSOLE_CURSOR_INFO

    6.4 SetConsoleCursorInfo 

     6.5 SetConsoleCursorPosition

    6.6 GetAsyncKeyState

     7. 贪吃蛇游戏设计与分析

    7.1 地图

    7.1.1 本地化

    7.1.2 类项

    7.1.3 setlocale函数 

    7.1.4 宽字符的打印 

    7.1.5 地图坐标

     ​编辑

    7.2 蛇⾝和⻝物 

    7.3 数据结构设计

    7.4 游戏流程设计 

    8. 核⼼逻辑实现分析 

    8.1 游戏主逻辑

    8.2 游戏开始(GameStart)

    8.2.1 打印欢迎界⾯  

    8.2.2 创建地图  

    8.2.3 初始化蛇⾝  

    8.2.4 创建第⼀个⻝物

    8.3 游戏运⾏(GameRun)  

    8.3.1 KEY_PRESS

     8.3.2 PrintHelpInfo

    8.3.3 蛇⾝移动(SnakeMove)

     8.3.3.1 NextIsFood

    8.3.3.4 KillByWall 

    8.3.3.5 KillBySelf

    8.4 游戏结束

    9. 参考代码


    1. 游戏背景

    贪吃蛇是久负盛名的游戏,它也和俄 罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。

    2. 游戏效果演示

    3. 实现目标

    使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇。

    4. 技术要点

    C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。

    5. 控制台程序

    平常我们运⾏起来的⿊框程序其实就是控制台程序。

    5.1  设置控制台窗口的长宽和名字

     我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽和名字:设置控制台窗⼝的⼤⼩,30⾏,100列。设置名字为贪吃蛇。

     

    这些能在控制台窗⼝执⾏的命令,也可以调⽤C语⾔函数system来执⾏。如: 

    1. #include
    2. int main()
    3. {
    4. //设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
    5. system("mode con cols=100 lines=30");
    6. //设置cmd窗⼝名称
    7. system("title 贪吃蛇");
    8. return 0;
    9. }

    5.2 控制台屏幕上的坐标COORD

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

     COORD类型的声明:

    6.Win32 API  

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

    6.1 GetStdHandle 

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

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

    6.2 GetConsoleCursorInfo

    检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息
    1. BOOL WINAPI GetConsoleCursorInfo(
    2. HANDLE hConsoleOutput,
    3. PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
    4. );
    5. PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

     实例:

    1. HANDLE hOutput = NULL;
    2. //获取标准输出的句柄(⽤来标识不同设备的数值)
    3. hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    4. CONSOLE_CURSOR_INFO CursorInfo;
    5. GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

    6.3 CONSOLE_CURSOR_INFO

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

    dwSize,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条。
    bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 TRUE。

    6.4 SetConsoleCursorInfo 

    设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。
    1. BOOL WINAPI SetConsoleCursorInfo(
    2. HANDLE hConsoleOutput,
    3. const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
    4. );
    5. // 实例
    6. HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    7. //影藏光标操作
    8. CONSOLE_CURSOR_INFO CursorInfo;
    9. GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
    10. CursorInfo.bVisible = false; //隐藏控制台光标
    11. SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

     6.5 SetConsoleCursorPosition

    设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。
    1. BOOL WINAPI SetConsoleCursorPosition(
    2. HANDLE hConsoleOutput,
    3. COORD pos
    4. );
    5. // 实例:
    6. COORD pos = { 10, 5};
    7. HANDLE hOutput = NULL;
    8. //获取标准输出的句柄(⽤来标识不同设备的数值)
    9. hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    10. //设置标准输出上光标的位置为pos
    11. SetConsoleCursorPosition(hOutput, pos)

     SetPos:封装⼀个设置光标位置的函数

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

    6.6 GetAsyncKeyState

    获取按键情况,GetAsyncKeyState的函数原型如下: 

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

    GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果
    返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
    如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.

    参考:虚拟键码 (Winuser.h) - Win32 apps icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes

    实例:检测数字键

    1. #include
    2. #include
    3. int main()
    4. {
    5. while (1)
    6. {
    7. if (KEY_PRESS(0x30))
    8. {
    9. printf("0\n");
    10. }
    11. else if (KEY_PRESS(0x31))
    12. {
    13. printf("1\n");
    14. }
    15. else if (KEY_PRESS(0x32))
    16. {
    17. printf("2\n");
    18. }
    19. else if (KEY_PRESS(0x33))
    20. {
    21. printf("3\n");
    22. }
    23. else if (KEY_PRESS(0x34))
    24. {
    25. printf("4\n");
    26. }
    27. else if (KEY_PRESS(0x35))
    28. {
    29. printf("5\n");
    30. }
    31. else if (KEY_PRESS(0x36))
    32. {
    33. printf("6\n");
    34. }
    35. else if (KEY_PRESS(0x37))
    36. {
    37. printf("7\n");
    38. }
    39. else if (KEY_PRESS(0x38))
    40. {
    41. printf("8\n");
    42. }
    43. else if (KEY_PRESS(0x39))
    44. {
    45. printf("9\n");
    46. }
    47. }
    48. return 0;
    49. }

     7. 贪吃蛇游戏设计与分析

    7.1 地图

    这⾥不得不讲⼀下控制台窗⼝的⼀些知识,如果想在控制台的窗⼝中指定位置输出信息,我们得知道该位置的坐标,所以⾸先介绍⼀下控制台窗⼝的坐标知识。
    控制台窗⼝的坐标如下所⽰,横向的是X轴,从左向右依次增⻓,纵向是Y轴,从上到下依次增⻓。

     

    在游戏地图上,我们打印墙体使⽤宽字符:※,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★
    普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。
    这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语⾔并不适合⾮英语国家(地区)使⽤。
    C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。
    后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型wchar_t 和宽字符的输⼊和输出函数,加⼊了头⽂件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。

    7.1.1 本地化

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

    7.1.2 类项

    通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,指定⼀个类项:
    LC_COLLATE:影响字符串⽐较函数 strcoll() strxfrm()
    LC_CTYPE:影响字符处理函数的⾏为。
    LC_MONETARY:影响货币格式。
    LC_NUMERIC:影响 printf() 的数字格式。
    LC_TIME:影响时间格式 strftime() wcsftime()
    LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

    7.1.3 setlocale函数 

    setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
    setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。
    C标准给第⼆个参数仅定义了2种可能取值: "C" (正常模式)和 " " (本地模式)。

    当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。
    当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
    ⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。

    7.1.4 宽字符的打印 

    那如果想在屏幕上打印宽字符,怎么打印呢?
    宽字符的字⾯量必须加上前缀“L”,否则 C 语⾔会把字⾯量当作窄字符类型处理。
    前缀“L”在单引号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls
    1. #include
    2. #include
    3. int main() {
    4. setlocale(LC_ALL, "");
    5. wchar_t ch1 = L'●';
    6. wchar_t ch2 = L'于';
    7. wchar_t ch3 = L'本';
    8. wchar_t ch4 = L'淡';
    9. printf("%c%c\n", 'a', 'b');
    10. wprintf(L"%lc\n", ch1);
    11. wprintf(L"%lc\n", ch2);
    12. wprintf(L"%lc\n", ch3);
    13. wprintf(L"%lc\n", ch4);
    14. return 0;
    15. }

     

    7.1.5 地图坐标

    我们假设实现⼀个棋盘27⾏,58列的棋盘(⾏和列可以根据⾃⼰的情况修改),再围绕地图画出墙,如下:

     

    7.2 蛇⾝和⻝物 

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

    7.3 数据结构设计

    在游戏运⾏的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变⻓⼀节,如果我们使⽤链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇⾝节点在地图上的坐标就⾏,所以蛇节点结构如下:
    1. typedef struct SnakeNode
    2. {
    3. int x;
    4. int y;
    5. struct SnakeNode* next;
    6. }SnakeNode, * pSnakeNode;

    要管理整条贪吃蛇,我们再封装⼀个Snake的结构来维护整条贪吃蛇:
    1. typedef struct Snake
    2. {
    3. pSnakeNode _pSnake;//维护整条蛇的指针
    4. pSnakeNode _pFood;//维护⻝物的指针
    5. enum DIRECTION _Dir;//蛇头的⽅向,默认是向右
    6. enum GAME_STATUS _Status;//游戏状态
    7. int _Socre;//游戏当前获得分数
    8. int _foodWeight;//默认每个⻝物10分
    9. int _SleepTime;//每⾛⼀步休眠时间
    10. }Snake, * pSnake;

    蛇的⽅向,可以⼀⼀列举,使⽤枚举  :

    1. //⽅向
    2. enum DIRECTION
    3. {
    4. UP = 1,
    5. DOWN,
    6. LEFT,
    7. RIGHT
    8. };

     游戏状态,可以⼀⼀列举,使⽤枚举:

    1. //游戏状态
    2. enum GAME_STATUS
    3. {
    4. OK,//正常运⾏
    5. KILL_BY_WALL,//撞墙
    6. KILL_BY_SELF,//咬到⾃⼰
    7. END_NOMAL//正常结束
    8. };

    7.4 游戏流程设计 

     

    8. 核⼼逻辑实现分析 

    8.1 游戏主逻辑

    程序开始就设置程序⽀持本地模式,然后进⼊游戏的主逻辑。
    主逻辑分为3个过程:
    游戏开始(GameStart)完成游戏的初始化
    游戏运⾏(GameRun)完成游戏运⾏逻辑的实现
    游戏结束(GameEnd)完成游戏结束的说明,实现资源释放
    1. #include
    2. void test()
    3. {
    4. int ch = 0;
    5. srand((unsigned int)time(NULL));
    6. do
    7. {
    8. Snake snake = { 0 };
    9. GameStart(&snake);
    10. GameRun(&snake);
    11. GameEnd(&snake);
    12. SetPos(20, 15);
    13. printf("再来⼀局吗?(Y/N):");
    14. ch = getchar();
    15. getchar();//清理\n
    16. } while (ch == 'Y');
    17. SetPos(0, 27);
    18. }
    19. int main()
    20. {
    21. //修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
    22. setlocale(LC_ALL, "");
    23. //测试逻辑
    24. test();
    25. return 0;
    26. }

    8.2 游戏开始(GameStart)

    这个模块完成游戏的初始化任务:
    控制台窗⼝⼤⼩的设置
    控制台窗⼝名字的设置
    ⿏标光标的隐藏
    打印欢迎界⾯
    创建地图
    初始化第蛇
    创建第⼀个⻝物
    1. void GameStart(pSnake ps)
    2. {
    3. //设置控制台窗⼝的⼤⼩,30⾏,100列
    4. //mode 为DOS命令
    5. system("mode con cols=100 lines=30");
    6. //设置cmd窗⼝名称
    7. system("title 贪吃蛇");
    8. //获取标准输出的句柄(⽤来标识不同设备的数值)
    9. HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    10. //影藏光标操作
    11. CONSOLE_CURSOR_INFO CursorInfo;
    12. GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
    13. CursorInfo.bVisible = false; //隐藏控制台光标
    14. SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
    15. //打印欢迎界⾯
    16. WelcomeToGame();
    17. //打印地图
    18. Map();
    19. //初始化蛇
    20. InitSnake(ps);
    21. //创造第⼀个⻝物
    22. Food(ps);
    23. }

    8.2.1 打印欢迎界⾯  

    在游戏正式开始之前,做⼀些功能提醒

    1. void WelcomeToGame()
    2. {
    3. SetPos(40, 15);
    4. printf("欢迎来到贪吃蛇⼩游戏");
    5. SetPos(40, 25);//让按任意键继续的出现的位置好看点
    6. system("pause");
    7. system("cls");
    8. SetPos(25, 12);
    9. printf("⽤ ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");
    10. SetPos(25, 13);
    11. printf("加速将能得到更⾼的分数。\n");
    12. SetPos(40, 25);//让按任意键继续的出现的位置好看点
    13. system("pause");
    14. system("cls");
    15. }

    8.2.2 创建地图  

    创建地图就是将墙打印出来,因为是宽字符打印,所有使⽤wprintf函数,打印格式串前使⽤L。
    打印地图的关键是要算好坐标,才能在想要的位置打印墙体。
    这边非常容易出错,一定要算好坐标再写代码。

    创建地图函数Map 

    1. void Map()
    2. {
    3. int i = 0;
    4. //上(0,0)-(56, 0)
    5. SetPos(0, 0);
    6. for (i = 0; i < 58; i += 2)
    7. {
    8. wprintf(L"%c", WALL);
    9. }
    10. //下(0,26)-(56, 26)
    11. SetPos(0, 26);
    12. for (i = 0; i < 58; i += 2)
    13. {
    14. wprintf(L"%c", WALL);
    15. }
    16. //左
    17. //x是0,y从1开始增⻓
    18. for (i = 1; i < 26; i++)
    19. {
    20. SetPos(0, i);
    21. wprintf(L"%c", WALL);
    22. }
    23. //x是56,y从1开始增⻓
    24. for (i = 1; i < 26; i++)
    25. {
    26. SetPos(56, i);
    27. wprintf(L"%c", WALL);
    28. }
    29. }

    8.2.3 初始化蛇⾝  

    蛇最开始⻓度为5节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。
    创建5个节点,然后将每个节点存放在链表中进⾏管理。创建完蛇⾝后,将蛇的每⼀节打印在屏幕上。
    蛇的初始位置从 (24,5) 开始。
    再设置当前游戏的状态,蛇移动的速度,默认的⽅向,初始成绩,每个⻝物的分数。
    游戏状态是:OK
    蛇的移动速度:200毫秒
    蛇的默认⽅向:RIGHT
    初始成绩:0
    每个⻝物的分数:10

    初始化蛇⾝函数:InitSnake

    1. void InitSnake(pSnake ps)
    2. {
    3. pSnakeNode cur = NULL;
    4. int i = 0;
    5. //创建蛇⾝节点,并初始化坐标
    6. //头插法
    7. for (i = 0; i < 5; i++)
    8. {
    9. //创建蛇⾝的节点
    10. cur = (pSnakeNode)malloc(sizeof(SnakeNode));
    11. if (cur == NULL)
    12. {
    13. perror("InitSnake()::malloc()");
    14. return;
    15. }
    16. //设置坐标
    17. cur->next = NULL;
    18. cur->x = POS_X + i * 2;
    19. cur->y = POS_Y;
    20. //头插法
    21. if (ps->_pSnake == NULL)
    22. {
    23. ps->_pSnake = cur;
    24. }
    25. else
    26. {
    27. cur->next = ps->_pSnake;
    28. ps->_pSnake = cur;
    29. }
    30. }
    31. //打印蛇的⾝体
    32. cur = ps->_pSnake;
    33. while (cur)
    34. {
    35. SetPos(cur->x, cur->y);
    36. wprintf(L"%lc", BODY);
    37. cur = cur->next;
    38. }
    39. //初始化贪吃蛇数据
    40. ps->_SleepTime = 200;
    41. ps->_Socre = 0;
    42. ps->_Status = OK;
    43. ps->_Dir = RIGHT;
    44. ps->_foodWeight = 10;
    45. }

    8.2.4 创建第⼀个⻝物

    先随机⽣成⻝物的坐标
    x坐标必须是2的倍数
    ⻝物的坐标不能和蛇⾝每个节点的坐标重复
    创建⻝物节点,打印⻝物

    创建⻝物的函数:Food  

    1. void Food(pSnake ps)
    2. {
    3. int x = 0;
    4. int y = 0;
    5. again:
    6. //产⽣的x坐标应该是2的倍数,这样才可能和蛇头坐标对⻬。
    7. do
    8. {
    9. x = rand() % 53 + 2;
    10. y = rand() % 25 + 1;
    11. } while (x % 2 != 0);
    12. pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针
    13. //⻝物不能和蛇⾝冲突
    14. while (cur)
    15. {
    16. if (cur->x == x && cur->y == y)
    17. {
    18. goto again;
    19. }
    20. cur = cur->next;
    21. }
    22. pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建⻝物
    23. if (pFood == NULL)
    24. {
    25. perror("CreateFood::malloc()");
    26. return;
    27. }
    28. else
    29. {
    30. pFood->x = x;
    31. pFood->y = y;
    32. SetPos(pFood->x, pFood->y);
    33. wprintf(L"%c", FOOD);
    34. ps->_pFood = pFood;
    35. }
    36. }

    8.3 游戏运⾏(GameRun)  

    游戏运⾏期间,右侧打印帮助信息,提⽰玩家,坐标开始位置(64, 15)。
    根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。
    如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。
    需要的虚拟按键的罗列:
    上:VK_UP
    下:VK_DOWN
    左:VK_LEFT
    右:VK_RIGHT
    空格:VK_SPACE
    ESC:VK_ESCAPE
    F3:VK_F3
    F4:VK_F4
    确定了蛇的⽅向和速度,蛇就可以移动了。
    1. void GameRun(pSnake ps)
    2. {
    3. //打印右侧帮助信息
    4. PrintHelpInfo();
    5. do
    6. {
    7. SetPos(64, 10);
    8. printf("得分:%d ", ps->_Socre);
    9. printf("每个⻝物得分:%d分", ps->_foodWeight);
    10. if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN)
    11. {
    12. ps->_Dir = UP;
    13. }
    14. else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
    15. {
    16. ps->_Dir = DOWN;
    17. }
    18. else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
    19. {
    20. ps->_Dir = LEFT;
    21. }
    22. else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
    23. {
    24. ps->_Dir = RIGHT;
    25. }
    26. else if (KEY_PRESS(VK_SPACE))
    27. {
    28. pause();
    29. }
    30. else if (KEY_PRESS(VK_ESCAPE))
    31. {
    32. ps->_Status = END_NOMAL;
    33. break;
    34. }
    35. else if (KEY_PRESS(VK_F3))
    36. {
    37. if (ps->_SleepTime >= 80)
    38. {
    39. ps->_SleepTime -= 30;
    40. ps->_foodWeight += 2;//⼀个⻝物分数最⾼是20分
    41. }
    42. }
    43. else if (KEY_PRESS(VK_F4))
    44. {
    45. if (ps->_SleepTime < 320)
    46. {
    47. ps->_SleepTime += 30;
    48. ps->_foodWeight -= 2;//⼀个⻝物分数最低是2分
    49. }
    50. }
    51. //蛇每次⼀定之间要休眠的时间,时间短,蛇移动速度就快
    52. Sleep(ps->_SleepTime);
    53. SnakeMove(ps);
    54. } while (ps->_Status == OK);
    55. }

    8.3.1 KEY_PRESS

    检测按键状态,我们封装了⼀个宏:
    # define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1: 0 )

     8.3.2 PrintHelpInfo

     打印我们的帮助信息:

    1. void PrintHelpInfo()
    2. {
    3. //打印提⽰信息
    4. SetPos(64, 15);
    5. printf("不能穿墙,不能咬到⾃⼰\n");
    6. SetPos(64, 16);
    7. printf("⽤↑.↓.←.→分别控制蛇的移动.");
    8. SetPos(64, 17);
    9. printf("F3 为加速,F4 为减速\n");
    10. SetPos(64, 18);
    11. printf("ESC :退出游戏.space:暂停游戏.");
    12. SetPos(64, 20);
    13. printf("于本淡@版权");
    14. }

    8.3.3 蛇⾝移动(SnakeMove)

    先创建下⼀个节点,根据移动⽅向和蛇头的坐标,蛇移动到下⼀个位置的坐标。确定了下⼀个位置后,看下⼀个位置是否是⻝物(NextIsFood),是⻝物就做⻝物处理(EatFood),如果不是⻝物则做前进⼀步的处理(NoFood)。蛇⾝移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上⾃⼰蛇⾝(KillBySelf),从⽽影响游戏的状态。
    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. //确定下⼀个节点的坐标,下⼀个节点的坐标根据,蛇头的坐标和⽅向确定
    11. switch (ps->_Dir)
    12. {
    13. case UP:
    14. {
    15. pNextNode->x = ps->_pSnake->x;
    16. pNextNode->y = ps->_pSnake->y - 1;
    17. }
    18. break;
    19. case DOWN:
    20. {
    21. pNextNode->x = ps->_pSnake->x;
    22. pNextNode->y = ps->_pSnake->y + 1;
    23. }
    24. break;
    25. case LEFT:
    26. {
    27. pNextNode->x = ps->_pSnake->x - 2;
    28. pNextNode->y = ps->_pSnake->y;
    29. }
    30. break;
    31. case RIGHT:
    32. {
    33. pNextNode->x = ps->_pSnake->x + 2;
    34. pNextNode->y = ps->_pSnake->y;
    35. }
    36. break;
    37. }
    38. //如果下⼀个位置就是⻝物
    39. if (NextIsFood(pNextNode, ps))
    40. {
    41. EatFood(pNextNode, ps);
    42. }
    43. else//如果没有⻝物
    44. {
    45. NoFood(pNextNode, ps);
    46. }
    47. KillByWall(ps);
    48. KillBySelf(ps);
    49. }
     8.3.3.1 NextIsFood
    1. //pSnakeNode psn 是下⼀个节点的地址
    2. //pSnake ps 维护蛇的指针
    3. int NextIsFood(pSnakeNode psn, pSnake ps)
    4. {
    5. return (psn->x == ps->_pFood->x) && (psn->y == ps->_pFood->y);
    6. }
    8.3.3.2 EatFood
    1. //pSnakeNode psn 是下⼀个节点的地址
    2. //pSnake ps 维护蛇的指针
    3. void EatFood(pSnakeNode psn, pSnake ps)
    4. {
    5. //头插法
    6. psn->next = ps->_pSnake;
    7. ps->_pSnake = psn;
    8. //打印蛇
    9. pSnakeNode cur = ps->_pSnake;
    10. while (cur)
    11. {
    12. SetPos(cur->x, cur->y);
    13. wprintf(L"%c", BODY);
    14. cur = cur->next;
    15. }
    16. ps->_Socre += ps->_foodWeight;
    17. //释放⻝物节点
    18. free(ps->_pFood);
    19. //创建新的⻝物
    20. CreateFood(ps);
    21. }

    8.3.3.3 NoFood

    将下⼀个节点头插⼊蛇的⾝体,并将之前蛇⾝最后⼀个节点打印为空格,释放掉蛇⾝的最后⼀个节点。
    易错点: 这⾥最容易错误的是,释放最后⼀个结点后,还得将指向在最后⼀个结点的指针改为NULL,保证蛇尾打印可以正常结束,不会越界访问。
    1. //pSnakeNode psn 是下⼀个节点的地址
    2. //pSnake ps 维护蛇的指针
    3. void NoFood(pSnakeNode psn, pSnake ps)
    4. {
    5. //头插法
    6. psn->next = ps->_pSnake;
    7. ps->_pSnake = psn;
    8. //打印蛇
    9. pSnakeNode cur = ps->_pSnake;
    10. while (cur->next->next)
    11. {
    12. SetPos(cur->x, cur->y);
    13. wprintf(L"%c", BODY);
    14. cur = cur->next;
    15. }
    16. //最后⼀个位置打印空格,然后释放节点
    17. SetPos(cur->next->x, cur->next->y);
    18. printf(" ");
    19. free(cur->next);
    20. cur->next = NULL;
    21. }
    8.3.3.4 KillByWall 

     判断蛇头的坐标是否和墙的坐标冲突

    1. //pSnake ps 维护蛇的指针
    2. int KillByWall(pSnake ps)
    3. {
    4. if ((ps->_pSnake->x == 0)
    5. || (ps->_pSnake->x == 56)
    6. || (ps->_pSnake->y == 0)
    7. || (ps->_pSnake->y == 26))
    8. {
    9. ps->_Status = KILL_BY_WALL;
    10. return 1;
    11. }
    12. return 0;
    13. }
    8.3.3.5 KillBySelf
    判断蛇头的坐标是否和蛇⾝体的坐标冲突
    1. //pSnake ps 维护蛇的指针
    2. int KillBySelf(pSnake ps)
    3. {
    4. pSnakeNode cur = ps->_pSnake->next;
    5. while (cur)
    6. {
    7. if ((ps->_pSnake->x == cur->x)
    8. && (ps->_pSnake->y == cur->y))
    9. {
    10. ps->_Status = KILL_BY_SELF;
    11. return 1;
    12. }
    13. cur = cur->next;
    14. }
    15. return 0;
    16. }

    8.4 游戏结束

    游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇⾝节点。 

    1. void GameEnd(pSnake ps)
    2. {
    3. pSnakeNode cur = ps->_pSnake;
    4. SetPos(24, 12);
    5. switch (ps->_Status)
    6. {
    7. case END_NOMAL:
    8. printf("您主动退出游戏\n");
    9. break;
    10. case KILL_BY_SELF:
    11. printf("您撞上⾃⼰了 ,游戏结束!\n");
    12. break;
    13. case KILL_BY_WALL:
    14. printf("您撞墙了,游戏结束!\n");
    15. break;
    16. }
    17. //释放蛇⾝的节点
    18. while (cur)
    19. {
    20. pSnakeNode del = cur;
    21. cur = cur->next;
    22. free(del);
    23. }
    24. }

    9. 参考代码

    完整代码实现,分3个⽂件实现:

    1. // test.cpp
    2. #include "Snake.h"
    3. #include
    4. void test()
    5. {
    6. int ch = 0;
    7. srand((unsigned int)time(NULL));
    8. do
    9. {
    10. Snake snake = { 0 };
    11. GameStart(&snake);
    12. GameRun(&snake);
    13. GameEnd(&snake);
    14. SetPos(20, 15);
    15. printf("再来⼀局吗?(Y/N):");
    16. ch = getchar();
    17. getchar();//清理\n
    18. } while (ch == 'Y' || ch == 'y');
    19. SetPos(0, 27);
    20. }
    21. int main()
    22. {
    23. //修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
    24. setlocale(LC_ALL, "");
    25. //测试逻辑
    26. test();
    27. return 0;
    28. }
    29. // snake.h
    30. #pragma once
    31. #include
    32. #include
    33. #include
    34. #define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
    35. //⽅向
    36. enum DIRECTION
    37. {
    38. UP = 1,
    39. DOWN,
    40. LEFT,
    41. RIGHT
    42. };
    43. //游戏状态
    44. enum GAME_STATUS
    45. {
    46. OK,//正常运⾏
    47. KILL_BY_WALL,//撞墙
    48. KILL_BY_SELF,//咬到⾃⼰
    49. END_NOMAL//正常结束
    50. };
    51. #define WALL L'□'
    52. #define BODY L'●'
    53. #define FOOD L'★'
    54. //蛇的初始位置
    55. #define POS_X 24
    56. #define POS_Y 5
    57. //蛇⾝节点
    58. typedef struct SnakeNode
    59. {
    60. int x;
    61. int y;
    62. struct SnakeNode* next;
    63. }SnakeNode, * pSnakeNode;
    64. typedef struct Snake
    65. {
    66. pSnakeNode _pSnake;//维护整条蛇的指针
    67. pSnakeNode _pFood;//维护⻝物的指针
    68. enum DIRECTION _Dir;//蛇头的⽅向默认是向右
    69. enum GAME_STATUS _Status;//游戏状态
    70. int _Socre;//当前获得分数
    71. int _foodWeight;//默认每个⻝物10分
    72. int _SleepTime;//每⾛⼀步休眠时间
    73. }Snake, * pSnake;
    74. //游戏开始前的初始化
    75. void GameStart(pSnake ps);
    76. //游戏运⾏过程
    77. void GameRun(pSnake ps);
    78. //游戏结束
    79. void GameEnd(pSnake ps);
    80. //设置光标的坐标
    81. void SetPos(short x, short y);
    82. //欢迎界⾯
    83. void WelcomeToGame();
    84. //打印帮助信息
    85. void PrintHelpInfo();
    86. //创建地图
    87. void CreateMap();
    88. //初始化蛇
    89. void InitSnake(pSnake ps);
    90. //创建⻝物
    91. void CreateFood(pSnake ps);
    92. //暂停响应
    93. void pause();
    94. //下⼀个节点是⻝物
    95. int NextIsFood(pSnakeNode psn, pSnake ps);
    96. //吃⻝物
    97. void EatFood(pSnakeNode psn, pSnake ps);
    98. //不吃⻝物
    99. void NoFood(pSnakeNode psn, pSnake ps);
    100. //撞墙检测
    101. int KillByWall(pSnake ps);
    102. //撞⾃⾝检测
    103. int KillBySelf(pSnake ps);
    104. //蛇的移动
    105. void SnakeMove(pSnake ps);
    106. //游戏初始化
    107. void GameStart(pSnake ps);
    108. //游戏运⾏
    109. void GameRun(pSnake ps);
    110. //游戏结束
    111. void GameEnd(pSnake ps);
    112. // snake.cpp
    113. #include "Snake.h"
    114. //设置光标的坐标
    115. void SetPos(short x, short y)
    116. {
    117. COORD pos = { x, y };
    118. HANDLE hOutput = NULL;
    119. //获取标准输出的句柄(⽤来标识不同设备的数值)
    120. hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    121. //设置标准输出上光标的位置为pos
    122. SetConsoleCursorPosition(hOutput, pos);
    123. }
    124. void WelcomeToGame()
    125. {
    126. SetPos(40, 15);
    127. printf("欢迎来到贪吃蛇⼩游戏");
    128. SetPos(40, 25);//让按任意键继续的出现的位置好看点
    129. system("pause");
    130. system("cls");
    131. SetPos(25, 12);
    132. printf("⽤ ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");
    133. SetPos(25, 13);
    134. printf("加速将能得到更⾼的分数。\n");
    135. SetPos(40, 25);//让按任意键继续的出现的位置好看点
    136. system("pause");
    137. system("cls");
    138. }
    139. void CreateMap()
    140. {
    141. int i = 0;
    142. //上(0,0)-(56, 0)
    143. SetPos(0, 0);
    144. for (i = 0; i < 58; i += 2)
    145. {
    146. wprintf(L"%c", WALL);
    147. }
    148. //下(0,26)-(56, 26)
    149. SetPos(0, 26);
    150. for (i = 0; i < 58; i += 2)
    151. {
    152. wprintf(L"%c", WALL);
    153. }
    154. //左
    155. //x是0,y从1开始增⻓
    156. for (i = 1; i < 26; i++)
    157. {
    158. SetPos(0, i);
    159. wprintf(L"%c", WALL);
    160. }
    161. //x是56,y从1开始增⻓
    162. for (i = 1; i < 26; i++)
    163. {
    164. SetPos(56, i);
    165. wprintf(L"%c", WALL);
    166. }
    167. }
    168. void InitSnake(pSnake ps)
    169. {
    170. pSnakeNode cur = NULL;
    171. int i = 0;
    172. //创建蛇⾝节点,并初始化坐标
    173. //头插法
    174. for (i = 0; i < 5; i++)
    175. {
    176. //创建蛇⾝的节点
    177. cur = (pSnakeNode)malloc(sizeof(SnakeNode));
    178. if (cur == NULL)
    179. {
    180. perror("InitSnake()::malloc()");
    181. return;
    182. }
    183. //设置坐标
    184. cur->next = NULL;
    185. cur->x = POS_X + i * 2;
    186. cur->y = POS_Y;
    187. //头插法
    188. if (ps->_pSnake == NULL)
    189. {
    190. ps->_pSnake = cur;
    191. }
    192. else
    193. {
    194. cur->next = ps->_pSnake;
    195. ps->_pSnake = cur;
    196. }
    197. }
    198. //打印蛇的⾝体
    199. cur = ps->_pSnake;
    200. while (cur)
    201. {
    202. SetPos(cur->x, cur->y);
    203. wprintf(L"%c", BODY);
    204. cur = cur->next;
    205. }
    206. //初始化贪吃蛇数据
    207. ps->_SleepTime = 200;
    208. ps->_Socre = 0;
    209. ps->_Status = OK;
    210. ps->_Dir = RIGHT;
    211. ps->_foodWeight = 10;
    212. }
    213. void CreateFood(pSnake ps)
    214. {
    215. int x = 0;
    216. int y = 0;
    217. again:
    218. //产⽣的x坐标应该是2的倍数,这样才可能和蛇头坐标对⻬。
    219. do
    220. {
    221. x = rand() % 53 + 2;
    222. y = rand() % 25 + 1;
    223. } while (x % 2 != 0);
    224. pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针
    225. //⻝物不能和蛇⾝冲突
    226. while (cur)
    227. {
    228. if (cur->x == x && cur->y == y)
    229. {
    230. goto again;
    231. }
    232. cur = cur->next;
    233. }
    234. pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建⻝物
    235. if (pFood == NULL)
    236. {
    237. perror("CreateFood::malloc()");
    238. return;
    239. }
    240. else
    241. {
    242. pFood->x = x;
    243. pFood->y = y;
    244. SetPos(pFood->x, pFood->y);
    245. wprintf(L"%c", FOOD);
    246. ps->_pFood = pFood;
    247. }
    248. }
    249. void PrintHelpInfo()
    250. {
    251. //打印提⽰信息
    252. SetPos(64, 15);
    253. printf("不能穿墙,不能咬到⾃⼰\n");
    254. SetPos(64, 16);
    255. printf("⽤↑.↓.←.→分别控制蛇的移动.");
    256. SetPos(64, 17);
    257. printf("F3 为加速,F4 为减速\n");
    258. SetPos(64, 18);
    259. printf("ESC :退出游戏.space:暂停游戏.");
    260. SetPos(64, 20);
    261. printf("于本淡@版权");
    262. }
    263. void pause()//暂停
    264. {
    265. while (1)
    266. {
    267. Sleep(300);
    268. if (KEY_PRESS(VK_SPACE))
    269. {
    270. break;
    271. }
    272. }
    273. }
    274. //pSnakeNode psn 是下⼀个节点的地址
    275. //pSnake ps 维护蛇的指针
    276. int NextIsFood(pSnakeNode psn, pSnake ps)
    277. {
    278. return (psn->x == ps->_pFood->x) && (psn->y == ps->_pFood->y);
    279. }
    280. //pSnakeNode psn 是下⼀个节点的地址
    281. //pSnake ps 维护蛇的指针
    282. void EatFood(pSnakeNode psn, pSnake ps)
    283. {
    284. //头插法
    285. psn->next = ps->_pSnake;
    286. ps->_pSnake = psn;
    287. pSnakeNode cur = ps->_pSnake;
    288. //打印蛇
    289. while (cur)
    290. {
    291. SetPos(cur->x, cur->y);
    292. wprintf(L"%c", BODY);
    293. cur = cur->next;
    294. }
    295. ps->_Socre += ps->_foodWeight;
    296. free(ps->_pFood);
    297. CreateFood(ps);
    298. }
    299. //pSnakeNode psn 是下⼀个节点的地址
    300. //pSnake ps 维护蛇的指针
    301. void NoFood(pSnakeNode psn, pSnake ps)
    302. {
    303. //头插法
    304. psn->next = ps->_pSnake;
    305. ps->_pSnake = psn;
    306. pSnakeNode cur = ps->_pSnake;
    307. //打印蛇
    308. while (cur->next->next)
    309. {
    310. SetPos(cur->x, cur->y);
    311. wprintf(L"%c", BODY);
    312. cur = cur->next;
    313. }
    314. //最后⼀个位置打印空格,然后释放节点
    315. SetPos(cur->next->x, cur->next->y);
    316. printf(" ");
    317. free(cur->next);
    318. cur->next = NULL;
    319. }
    320. //pSnake ps 维护蛇的指针
    321. int KillByWall(pSnake ps)
    322. {
    323. if ((ps->_pSnake->x == 0)
    324. || (ps->_pSnake->x == 56)
    325. || (ps->_pSnake->y == 0)
    326. || (ps->_pSnake->y == 26))
    327. {
    328. ps->_Status = KILL_BY_WALL;
    329. return 1;
    330. }
    331. return 0;
    332. }
    333. //pSnake ps 维护蛇的指针
    334. int KillBySelf(pSnake ps)
    335. {
    336. pSnakeNode cur = ps->_pSnake->next;
    337. while (cur)
    338. {
    339. if ((ps->_pSnake->x == cur->x)
    340. && (ps->_pSnake->y == cur->y))
    341. {
    342. ps->_Status = KILL_BY_SELF;
    343. return 1;
    344. }
    345. cur = cur->next;
    346. }
    347. return 0;
    348. }
    349. void SnakeMove(pSnake ps)
    350. {
    351. //创建下⼀个节点
    352. pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
    353. if (pNextNode == NULL)
    354. {
    355. perror("SnakeMove()::malloc()");
    356. return;
    357. }
    358. //确定下⼀个节点的坐标,下⼀个节点的坐标根据,蛇头的坐标和⽅向确定
    359. switch (ps->_Dir)
    360. {
    361. case UP:
    362. {
    363. pNextNode->x = ps->_pSnake->x;
    364. pNextNode->y = ps->_pSnake->y - 1;
    365. }
    366. break;
    367. case DOWN:
    368. {
    369. pNextNode->x = ps->_pSnake->x;
    370. pNextNode->y = ps->_pSnake->y + 1;
    371. }
    372. break;
    373. case LEFT:
    374. {
    375. pNextNode->x = ps->_pSnake->x - 2;
    376. pNextNode->y = ps->_pSnake->y;
    377. }
    378. break;
    379. case RIGHT:
    380. {
    381. pNextNode->x = ps->_pSnake->x + 2;
    382. pNextNode->y = ps->_pSnake->y;
    383. }
    384. break;
    385. }
    386. //如果下⼀个位置就是⻝物
    387. if (NextIsFood(pNextNode, ps))
    388. {
    389. EatFood(pNextNode, ps);
    390. }
    391. else//如果没有⻝物
    392. {
    393. NoFood(pNextNode, ps);
    394. }
    395. KillByWall(ps);
    396. KillBySelf(ps);
    397. }
    398. void GameStart(pSnake ps)
    399. {
    400. //设置控制台窗⼝的⼤⼩,30⾏,100列
    401. //mode 为DOS命令
    402. system("mode con cols=100 lines=30");
    403. //设置cmd窗⼝名称
    404. system("title 贪吃蛇");
    405. //获取标准输出的句柄(⽤来标识不同设备的数值)
    406. HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    407. //影藏光标操作
    408. CONSOLE_CURSOR_INFO CursorInfo;
    409. GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
    410. CursorInfo.bVisible = false; //隐藏控制台光标
    411. SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
    412. //打印欢迎界⾯
    413. WelcomeToGame();
    414. //打印地图
    415. CreateMap();
    416. //初始化蛇
    417. InitSnake(ps);
    418. //创造第⼀个⻝物
    419. CreateFood(ps);
    420. }
    421. void GameRun(pSnake ps)
    422. {
    423. //打印右侧帮助信息
    424. PrintHelpInfo();
    425. do
    426. {
    427. SetPos(64, 10);
    428. printf("得分:%d ", ps->_Socre);
    429. printf("每个⻝物得分:%d分", ps->_foodWeight);
    430. if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN)
    431. {
    432. ps->_Dir = UP;
    433. }
    434. else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
    435. {
    436. ps->_Dir = DOWN;
    437. }
    438. else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
    439. {
    440. ps->_Dir = LEFT;
    441. }
    442. else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
    443. {
    444. ps->_Dir = RIGHT;
    445. }
    446. else if (KEY_PRESS(VK_SPACE))
    447. {
    448. pause();
    449. }
    450. else if (KEY_PRESS(VK_ESCAPE))
    451. {
    452. ps->_Status = END_NOMAL;
    453. break;
    454. }
    455. else if (KEY_PRESS(VK_F3))
    456. {
    457. if (ps->_SleepTime >= 50)
    458. {
    459. ps->_SleepTime -= 30;
    460. ps->_foodWeight += 2;
    461. }
    462. }
    463. else if (KEY_PRESS(VK_F4))
    464. {
    465. if (ps->_SleepTime < 350)
    466. {
    467. ps->_SleepTime += 30;
    468. ps->_foodWeight -= 2;
    469. if (ps->_SleepTime == 350)
    470. {
    471. ps->_foodWeight = 1;
    472. }
    473. }
    474. }
    475. //蛇每次⼀定之间要休眠的时间,时间短,蛇移动速度就快
    476. Sleep(ps->_SleepTime);
    477. SnakeMove(ps);
    478. } while (ps->_Status == OK);
    479. }
    480. void GameEnd(pSnake ps)
    481. {
    482. pSnakeNode cur = ps->_pSnake;
    483. SetPos(24, 12);
    484. switch (ps->_Status)
    485. {
    486. case END_NOMAL:
    487. printf("您主动退出游戏\n");
    488. break;
    489. case KILL_BY_SELF:
    490. printf("您撞上⾃⼰了 ,游戏结束!\n");
    491. break;
    492. case KILL_BY_WALL:
    493. printf("您撞墙了,游戏结束!\n");
    494. break;
    495. }
    496. //释放蛇⾝的节点
    497. while (cur)
    498. {
    499. pSnakeNode del = cur;
    500. cur = cur->next;
    501. free(del);
    502. }
    503. }

  • 相关阅读:
    Django如何配置数据库
    线性规划在多种问题形式下的应用
    大厂面试-好未来一面算法之求最长无重复子串长度
    一文教你在IDEA中使用Git
    基于SVM的功率分类,基于支持向量机SVM的功率分类识别,Libsvm工具箱详解
    Flink1.15源码解析--任务提交流程----flink run
    ROS键盘遥控机器人,通过参数服务器指定速度
    asp核酸检测预登记系统源码
    kubernetes_CoreDNS全解析
    11.21~11.28日学习总结
  • 原文地址:https://blog.csdn.net/2301_79409088/article/details/138040762