贪吃蛇-c语言版本

目录

前言

贪吃蛇游戏设计与分析

设计目标:

设计思想:

坐标问题: 

字符问题:

小拓展:C语⾔的国际化特性

本地化头文件:

类项

setlocale函数:

宽字符打印:

地图坐标:

 🐍和🍖:

初始化🐍:

初始化🍖:

数据结构的设计:

游戏主体流程设计:

游戏准备函数-GameStart:

游戏运行函数-GameRun:

游戏结束函数-GameEnd:

代码的具体实现:

创建头文件:

游戏准备函数GameStart():

打印欢迎界面:

创建地图:

初始化蛇身:

创建食物:

游戏运行函数-GameRun:

右侧打印帮助信息

进行蛇的移动:

​编辑

检测下一个是不是食物函数:

注意事项:

吃食物函数:

不吃食物函数:

自己撞自己函数:

实现步骤:

撞墙函数:

游戏结束函数-GameEnd:

全部代码:

Snake.c文件:

test.c文件:

Snake.h文件:

前言

        学习本篇之前建议将上一篇的关于《常用Win32 API的简单介绍》也打开......,同时此篇过长使用电脑观看效果更佳

贪吃蛇游戏设计与分析

设计目标:

设计思想:

坐标问题: 

        我们想在控制台的窗⼝中的指定位置输出我们想要的东西(墙体、食物、蛇、提示信息),我们得知道该位置的坐标,关于控制台窗口的坐标我们做出如下规定:

横向的是X轴,从左向右依次增⻓,纵向是Y轴,从上到下依次增⻓

字符问题:

此外,我们在打印这些想要输出的信息时将会使⽤一些”宽字符“

打印墙体使用宽字符:□

打印蛇使⽤宽字符:●

打印⻝物使⽤宽字符:★

注意事项:普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节

小拓展:C语⾔的国际化特性

        C语⾔最初的假定字符都是英美等以英语为官方语言的国家使用的,所以过去的C语⾔并不适合⾮英语国家使⽤。

        C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7 位,最⾼位是没有使⽤的,可表⽰为0xxxxxxxx;ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够⽤的。

        但是在其他国家⽐如:在法语中字⺟上⽅会有注⾳符号像é,它就⽆法⽤ ASCII 码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编⼊新的符号。⽐如,法语中的é的编码为130(⼆进制10000010)。这样⼀来,这些欧洲国家使⽤的编码体系,可以表⽰最多256个符号。但是即使是这样也还是不够满足全球所有国家的需求:130在法语编码中代表了é,在希伯来语编码中却代表了字⺟ג......,⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号, 肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是 GB231,使⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰ 256 x 256 = 65536 个符号。

注意所有编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段

        后来为了使C语⾔的适用范围更广,C语⾔的标准中不断加⼊了国际化的⽀持。加入了宽字符
数据类型、宽字符字符串函数以及宽字符的输入输出函数:
  1. 宽字符数据类型:

    • wchar_t:宽字符类型,用于表示一个宽字符。
    • wint_t:宽整数类型,用于表示一个宽字符或特殊值 WEOF
  2. 宽字符字符串函数:

    • wprintf():用于格式化输出宽字符字符串到标准输出。
    • wscanf():用于从标准输入读取宽字符数据。
    • wcslen():计算宽字符字符串的长度。
    • wcscpy():将一个宽字符字符串复制到另一个宽字符字符串。
    • wcsncpy():将指定数量的宽字符从一个宽字符字符串复制到另一个宽字符字符串。
    • wcscat():将一个宽字符字符串连接到另一个宽字符字符串的末尾。
    • wcsncat():将指定数量的宽字符连接到一个宽字符字符串的末尾。
  3. 宽字符输入输出函数:

    • getwchar():从标准输入读取一个宽字符。
    • putwchar():将一个宽字符输出到标准输出。
    • fgetwc():从指定的文件流读取一个宽字符。
    • fputwc():将一个宽字符写入到指定的文件流。
    • fwscanf():从指定的文件流读取宽字符数据。
    • fwprintf():将格式化的宽字符字符串写入到指定的文件流。

        此外,还加⼊了<locale.h>头⽂件,它是C语言标准库中的一个头文件,提供了与本地化相关的函数和类型定义。本地化是指根据不同的地区和语言环境,对程序进行适应和定制,以便正确显示日期、时间、货币、数字格式等,比如:

英语环境下的日期和钱:10/24/2023    $

中文环境下的日期和钱:2023/10/24   ¥

<locale.h>本地化头文件:
<locale.h>提供的函数⽤于控制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);
  • category:选择要修改的类项,如果要选择要修改全部类型请选择LC_ALL
  • locale:选择你想要修改类项的模式是"C"(正常模式)还是“ ”(本地模式)
所有程序开始前默认执行: setlocale (LC_ALL, "C" );
输入: setlocale (LC_ALL, " " ); //切换到本地环境,此时程序支 持宽字符(汉字)等本地字符的输出
这是 c语言官网 中提供的案例:
#define _CRT_SECURE_NO_WARNINGS  //记得加上这行不然localtime会报错显示不安全
/* setlocale example */
#include <stdio.h>      /* printf */
#include <time.h>       /* time_t, struct tm, time, localtime, strftime */
#include <locale.h>     /* struct lconv, setlocale, localeconv */int main()
{time_t rawtime;struct tm* timeinfo;char buffer[80];struct lconv* lc;time(&rawtime);timeinfo = localtime(&rawtime);int twice = 0;do {printf("Locale is: %s\n", setlocale(LC_ALL, NULL));strftime(buffer, 80, "%c", timeinfo);printf("Date is: %s\n", buffer);lc = localeconv();printf("Currency symbol is: %s\n-\n", lc->currency_symbol);setlocale(LC_ALL, "");} while (!twice++);return 0;
}
宽字符打印:
那如果想在屏幕上打印宽字符,怎么打印呢?
#include <stdio.h>
#include<locale.h>
int main() {setlocale(LC_ALL, "");wchar_t ch1 = L'●';//♥我不会啊┭┮﹏┭┮wchar_t ch2 = L'邓';wchar_t ch3 = L'紫';wchar_t ch4 = L'棋';printf("%c\n", 'I');wprintf(L"%c\n", ch1);wprintf(L"%c\n", ch2);wprintf(L"%c\n", ch3);wprintf(L"%c\n", ch4);return 0;
}
        从输出的结果来看,我们发现⼀个普通字符占⼀个字符的位置,但是打印⼀个汉字字符,占⽤2个字符的位置,那么我们如果要在贪吃蛇中使⽤宽字符,就得处理好地图上坐标的计算。普通字符和宽字符打印出宽度的展⽰如下:

棋盘坐标:
我们将棋盘设置为27行58列的大小:

 🐍和🍖:

初始化🐍:
⻓度为5,蛇⾝的每个节点是●,在固定的⼀个坐标处,⽐如(24, 5)处开始出现,连续5个节点
注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半出现在墙体中
初始化🍖:
在墙体内随机⽣成⼀个坐标,然后打印★
注意:x坐标必须是2的倍数,坐标不能和蛇的⾝体重合

数据结构的设计:

🐍身体的增加:        
        在游戏运⾏的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变⻓⼀节,如果我们使⽤链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点,所以组成🐍的链表结构如下:
//定义用与创建结点的链表
typedef struct SnakeNode
{int x;//结点横坐标int y;//结点纵坐标struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//重命名为SnakeNode类型,pSnakeNode指针指向该链表
此外,我们还需要封装⼀个Snake结构体来维护整个游戏中的各项数据:
//包含游戏各项数据的结构体类型
typedef struct Snake
{pSnakeNode _pSnake;        //用于维护链表结点的指针(规定它指向链表的第一个结点)pSnakeNode _pFood;         //用于维护食物的指针enum DIRECTION _Dir;       //_Dir是该枚举类型的变量,可以为其赋值//比如:enum DIRECTION _Dir = ok; 后续我们会利用switch语句与之配合enum GAME_STATUS _Status;int _Score;                //获得总分数int _Add;                  //每个食物的分数int _SleepTime;            //每进行一次状态转换(切换防线、吃掉食物等)都需要进行短暂的休息
}Snake, * pSnake;//重命名为Snake类型,pSnake指针指向该结构体
蛇的⽅向可以使⽤枚举一一表示:
//定义反应蛇运行⽅向的枚举类型
enum DIRECTION
{UP,            //向上DOWN,          //向下 LEFT,          //向左RIGHT          //向右
};
游戏状态可以使⽤枚举一一表示:
//定义反应游戏状态的枚举类型
enum GAME_STATUS
{OK,              //游戏正常运⾏KILL_BY_WALL,    //撞墙KILL_BY_SELF,    //自己撞到自己END_NOMAL        //正常结束(自己选择ESC结束游戏)
};
我们来简单演示一下它们该如何使用,有个大致印象即可:
#include <stdio.h>
enum DIRECTION {UP,DOWN,LEFT,RIGHT
};enum GAME_STATUS {IN_PROGRESS,GAME_OVER
};struct Snake {enum DIRECTION _Dir;enum GAME_STATUS _Status;
};int main() {struct Snake snake;snake._Dir = UP;snake._Status = IN_PROGRESS;if (snake._Dir == UP) {printf("Snake is moving UP\n");}if (snake._Status == IN_PROGRESS) {printf("Game is in progress\n");}return 0;
}

游戏主体流程设计:

这里只是大致逻辑,一些更加细节的逻辑设计被放在代码具体实现中进行讲解

游戏准备函数-GameStart:

  1. 设置游戏窗口大小
  2. 设置窗口名字
  3. 隐藏屏幕光标
  4. 打印欢迎界面
  5. 创建地图
  6. 初始化蛇身
  7. 创建食物

游戏运行函数-GameRun:

  1. 右侧打印帮助信息
  2. 打印当前分数和食物分数
  3. 按键获取情况
  4. 根据按键情况移动蛇
  5. 步骤2到步骤4循环,直到游戏为结束状态

游戏结束函数-GameEnd:

  1. 告知游戏结束原因
  2. 释放蛇身结点

ok,基本的设计思路我们已经解释过了,下面我们要开始实操了哦~

代码的具体实现:

创建头文件:

#include <stdio.h>
#include <windows.h>
#include <stdbool.h>
#include <locale.h>
#include <stdlib.h>
#include <time.h>#define WALL L'□'     //定义墙体的符号
#define BODY L'●'     //定义蛇身的符号
#define FOOD L'★'    //定义食物的符号
#define POS_X 24      //定义蛇尾的横坐标
#define POS_Y 5       //定义蛇尾的纵坐标//检测按键是否按下以及按的哪一个键(上一篇的Win32 API中提到过)
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)//定义反应蛇运行⽅向的枚举类型
enum DIRECTION
{UP,            //向上DOWN,          //向下 LEFT,          //向左RIGHT          //向右
};//定义反应游戏状态的枚举类型
enum GAME_STATUS
{OK,              //游戏正常运⾏KILL_BY_WALL,    //撞墙KILL_BY_SELF,    //自己撞到自己END_NOMAL        //正常结束(自己选择ESC结束游戏)
};//定义用与创建结点的链表
typedef struct SnakeNode
{int x;//结点横坐标int y;//结点纵坐标struct SnakeNode* next;
}SnakeNode,*pSnakeNode;//重命名为SnakeNode类型,pSnakeNode指针指向该链表//包含游戏各项数据的结构体类型
typedef struct Snake
{pSnakeNode _pSnake;        //用于维护链表结点的指针(规定它指向链表的第一个结点)pSnakeNode _pFood;         //用于维护食物的指针enum DIRECTION _Dir;       //_Dir是该枚举类型的变量,可以为其赋值//比如:enum DIRECTION _Dir = ok; 后续我们会利用switch语句与之配合enum GAME_STATUS _Status;  int _Score;                //获得总分数int _Add;                  //每个食物的分数int _SleepTime;            //每进行一次状态转换(切换防线、吃掉食物等)都需要进行短暂的休息
}Snake,*pSnake;//重命名为Snake类型,pSnake指针指向该结构体//下面是具体要声明的函数//游戏准备函数
void GameStart(pSnake ps);//设置光标位置
void SetPos(short x, short y);//打开欢迎界面void WelcomeToGame();//打印地图void CreateMap();//初始游戏各项数据void InitSnake(pSnake ps);//创造第⼀个⻝物void CreateFood(pSnake ps);//游戏运行函数
void GameRun(pSnake ps);//打印右侧帮助信息void PrintHelpInfo();//游戏暂停void pause();//蛇移动void SnakeMove(pSnake ps);//判断蛇头到达的坐标处是否为食物int NextIsFood(pSnake ps, pSnakeNode pnext);//吃掉食物void EatFood(pSnake ps,pSnakeNode pnext);//不吃食物void NoFood(pSnake ps,pSnakeNode pnext);//游戏结束函数
void GameEnd(pSnake ps);//撞墙检测void KillByWall(pSnake ps);//撞自身检测void KillBySelf(pSnake ps);

       后续描述中我们将用于创建结点的链表称为链表,将包含各项游戏数据的结构体称为结构体

游戏准备函数GameStart():

//游戏准备函数
void GameStart(pSnake ps)
{//控制台窗口设置system("mode con cols=100 lines=30");system("title 贪吃蛇");//光标隐藏HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取权限CONSOLE_CURSOR_INFO CursorInfo; //定义结构体类型变量GetConsoleCursorInfo(hOutput, &CursorInfo); // 获取控制台光标信息CursorInfo.bVisible = false; // 隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo); // 设置控制台光标状态//打开欢迎界面WelcomeToGame();//打印地图CreateMap();//初始化游戏各项数据InitSnake(ps);//创造第⼀个⻝物CreateFood(ps);                                               
}

这里的操作就不做细致描述了~

设置光标位置:
// 设置光标的坐标(程序输入时的位置)
void SetPos(short x, short y)
{COORD pos = { x, y };// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);// 设置标准输出上光标的位置为 posSetConsoleCursorPosition(hOutput, pos);
}

不做过多解释~

打印欢迎界面:
//欢迎界面
void WelcomeToGame()
{//显示一SetPos(38, 14);printf("欢迎来到贪吃蛇小游戏");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");//暂停操作system("cls");//清屏//显示二SetPos(20, 14);//重新定义光标位置,从该坐标处开始输入printf("使用↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");SetPos(40, 25);system("pause");system("cls");//显示三SetPos(37, 14);printf("加速将能得到更高的分数\n");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");system("cls");
}

实现步骤:

1、利用SetPos设置想要出现文字的位置(光标移动至此)

2、在SetPos指定的位置打印想要输入的文字

3、再次利用SetPos设置“请按任意键出现的位置”即system("pasue")起作用时文字显示的位置

4、利用system("cls")清空屏幕

注意事项:

1、请安任意键的文字是system("pause")后产生的效果请不要自行添加,同时也不能删除该行

2、必须执行system("cls")

最终效果:

创建地图:
//创建地图
void CreateMap()
{//地图的四个角的坐标为(0,0)       (56,0)//					  (0,1)        (56,1)  //在打印左/右侧墙体时,由于之前上下两行的打印已经将左/右侧墙体的第一个和左后一个打印过了,所以要注意坐标问题,左/右侧每列要少打两个//						...             ...//					  (0,25)     (56,25)//					  (0,26)     (56,26)//打印上边界(0,0)至(56,0)SetPos(0, 0);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);//wprintf函数的使用方式上面右描述,记得打印宽字符不是%c而是%lc}//打印下边界(0,26)至(56,26)SetPos(0, 26);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//打印左边界(0,1)至(0,25)for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//打印右边界(56,1)至(56,25)for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}

实现步骤:

1、按照之前规定好的坐标信息在地图上打印我们的用宽字符”□“围城的墙即可

注意事项:

1、打印宽字符要使用wprintf函数,且格式为wprintf(L"%lc",WALL),

2、上下两侧墙体全部打印完,左右两侧墙体都要少打两个避字符免在四个角落的重复

最终效果:

初始化蛇身:
//初始化游戏各项数据
void InitSnake(pSnake ps)//ps指向结构体
{//创建一个指向链表的的指针变量cur,利用该指针创建和连接结点pSnakeNode cur;//默认创建五个结点for (int i = 0; i < 5; i++){//令cur指向新开辟的结点内存空间cur = (pSnakeNode*)malloc(sizeof(SnakeNode));//关于malloc函数的使用不再过多描述,有疑问可以去看我的《动态内存管理》文章//开辟失败的报错if (cur == NULL){perror("InitSnake()-malloc()");return;}//分配完内存空间后就会为该结点分配初始坐标(x,y)//2*i实现可以实现横向创建一条蛇身的目的:(26,5)(28,5)(30,5)(32,5)(34,5)cur->x = POS_X + 2*i;//定义的变量POS_X和POS_Y便于后期切换初始坐标cur->y = POS_Y ;cur->next = NULL;//到这里已经完成一个结点的创建,但是该结点还没有连接//利用头插法进行蛇身体的链接//ps_pSnake相当于一个套娃,ps指向结构体,ps_pSnake指向该结构体中指向链表的指针if (ps->_pSnake == NULL){ps->_pSnake = cur;//交接工作}//如果链表不为空则进行头插else{cur->next = ps->_pSnake; ps->_pSnake = cur;}}//链表连接完成后,打印结点cur = ps->_pSnake;//令cur指向蛇的第一个结点while (cur)//只要cur指向结点不为空就继续循环打印{SetPos(cur->x, cur->y);//使用上面分配过的x和y坐标开始从该坐标处打印蛇身wprintf(L"%lc", BODY);//打印我们之前规定的符号●cur = cur->next;//cur指向下一个结点}//初始化游戏各类所需数据ps->_SleepTime = 200; //规定蛇每次移动都需要休息2秒ps->_Score = 0;//规定初始得分为0ps->_Status = OK;//规定初始游戏状态为okps->_Dir = RIGHT;//规定蛇开始的运行方向向右ps->_Add = 10;//规定吃掉一个食物的得到10分}

实现步骤:

1、创建五个的链表结点,令cur指向创建的一个结点

2、链表为空令新节点作为链表第一个结点进行地址交接工作,链表不为空进行头插操作

这里应该是ps->_pSnake而不是ps_pSnake,写错了懒得改了

3、打印结点,链表的逻辑位置和打印出来的位置相反(注意研究代码逻辑):

这里也写错了

最终效果:

创建食物:
//创建⻝物
void CreateFood(pSnake ps)//食物其实也相当于一个链表结点
{	//为食物设置随机的横纵坐标int x = 0;int y = 0;again://利用goto语句实现多次循环do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0); //产⽣的x坐标应该是2的倍数,这样才能与蛇头坐标对⻬pSnakeNode cur = ps->_pSnake;//获取链表的第一个结点//⻝物的结点不能和此时蛇身的某个结点重合,如果重合则利用goto语句重新分配食物结点的横纵坐标while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;//不重合就令cur指向下一个结点}//为食物结点申请内存空间pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建失败的报错if (pFood == NULL){perror("CreateFood::malloc()");return;}else{//创建成功,为结点分配实质的坐标pFood->x = x;pFood->y = y;SetPos(pFood->x, pFood->y);//令光标放在该节点的x和y坐标上wprintf(L"%c", FOOD);//打印食物结点ps->_pFood = pFood;//令_pFood指向该结点(将该结点的地址交给_pFood)}
}

实现步骤:

1、分配合理的横纵坐标

2、申请结点空间

3、令该结点空间的位置是合理分配的坐标处

注意事项:

 1、创建食物结点的过程通俗来讲就是你去上班,老板给你一个空缺的职位,然后为你分配了一块你的办公区域,最后你再在该区域中办公(有职位->分空间->去上任)

2、⻝物的结点不能和此时蛇身的某个结点重合,如果重合则利用goto语句重新分配食物结点的横纵坐标(注意:说此时,是因为蛇身结点在运动中并不会一直霸占某个坐标)

最终效果:

游戏运行函数-GameRun:

//游戏运行函数
void GameRun(pSnake ps)
{//打印右侧帮助信息(静态)PrintHelpInfo();do{//打印计分表(动态),蛇的吃食物、加减速都会引起计分表的变化SetPos(64, 8);printf("目前得分情况:%d", ps->_Score);SetPos(64, 9);if (ps->_Add < 10)printf("每个食物的分数: 0%d", ps->_Add);elseprintf("每个食物的分数:%d", ps->_Add);//且按下的方向不能与蛇当前移动方向相反(它头正在向上走你突然让它向下走是不行的)//对于如何使用_Dir赋值后的结果我们会在SnakeMove中实现if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN){ps->_Dir = UP;}else if(KEY_PRESS(VK_DOWN) && ps->_Dir != UP){ps->_Dir = DOWN;}else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT){ps->_Dir = RIGHT;}else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT){ps->_Dir = LEFT;}else if (KEY_PRESS(VK_SPACE))//按下空格键时暂停游戏{pause();}else if (KEY_PRESS(VK_ESCAPE))//按下ESC键时主动退出游戏{ps->_Status = END_NOMAL;  //主动切换游戏状态为END_NOMALbreak;}else if (KEY_PRESS(VK_F3))//按下F3加速{//速度越快得分越高,最多加速五次每次加速食物分数增加2,最高为20if (ps->_SleepTime >= 50){ps->_SleepTime -= 30;ps->_Add += 2;}}else if (KEY_PRESS(VK_F4))//按下F4减速{//速度越慢得分越低,最多减速四次每次减速食物分数减少2,最低为2if (ps->_SleepTime < 320){ps->_SleepTime += 30;ps->_Add -= 2;}}//蛇每次移动一定进行休眠,休眠时间越短,蛇移动的速度就越快Sleep(ps->_SleepTime);  //当蛇经历过休眠后,就要开始移动了SnakeMove(ps);			}//运动完成后检查游戏状态,游戏状态为OK时才会继续循环while (ps->_Status == OK);
}

实现步骤:

1、打印右侧帮助信息

2、打印计分表

3、完成按键检测功能

4、规定蛇每次移动的休眠时间

5、进行蛇的移动

6、循环2到5的过程直至游戏状态不为OK

注意事项:

1、右侧的帮助信息是静态的

2、计分表是动态的,它应该可以随着你蛇的移动后产生的结果(吃食物/加减速)而发生改变

3、在按键检测中:

  • 按下的方向不能与蛇当前移动方向相反
  • 按键检测为空格时游戏会进入程序暂停函数,按键检测为ESC时游戏结束
  • 按键检测为F3时游戏加速每个食物可获得分数增加,为F4时游戏减速可获得分数减少

4、蛇移动函数除了其本身外还会另外包含五个函数:

  • 检测下一个是不是食物函数
  • 吃食物函数
  • 不吃食物函数
  • 自己撞自己函数
  • 撞墙函数

最终效果:

        按上下左右键控制蛇的移动,F3加速蛇,F4减速蛇同时每个食物的分数也相应增加或减少,按下空格游戏暂停再次按下游戏继续,按下ESC游戏结束。

右侧打印帮助信息
//打印右侧帮助信息
void PrintHelpInfo()
{//打印提⽰信息SetPos(64, 15);printf("1、不能穿墙,不能咬到自己");SetPos(64, 16);printf("2、使用↑、↓、←、→控制蛇的移动");SetPos(64, 17);printf("3、按F3加速  按F4减速");SetPos(64, 18);printf("4、ESC:退出游戏  space:暂停游戏");
}

实现步骤:

1、SetPos函数确定要打印提示信息的位置

2、打印提示信息

最终效果:

进行蛇的移动:
//蛇移动函数
void SnakeMove(pSnake ps)
{pSnakeNode pNext =(pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove()::malloc()");return;}pNext->next = NULL;//利用switc判断ps->_Dir的不同方向switch (ps->_Dir){case UP://如果蛇是向上运动的,那么蛇运动的下一个结点的x轴坐标与蛇头保持一致,y轴坐标为蛇头y轴坐标减一,下面的就不一一写解释了pNext->x = ps->_pSnake->x;pNext->y = ps->_pSnake->y - 1;break;case DOWN:pNext->x = ps->_pSnake->x;pNext->y = ps->_pSnake->y + 1;break;case LEFT:pNext->x = ps->_pSnake->x - 2;pNext->y = ps->_pSnake->y;break;case RIGHT:pNext->x = ps->_pSnake->x + 2;pNext->y = ps->_pSnake->y;break;}//判断蛇头到达的坐标处是否是食物if (NextIsFood(ps, pNext)){//吃掉食物EatFood(ps,pNext);}else{//不吃食物NoFood(ps,pNext);}KillByWall(ps);KillBySelf(ps);
}

实现步骤:

1、创建新指针pNext,令其指向申请的蛇头下一个结点的地址

2、利用switch语句判断此时蛇的移动方向,如果前面出现了状态的切换此时switch就需要从原来方向的case语句切换成另一个方向的case语句

3、进行下一个结点是否是食物结点的判断

4、如果是食物就吃掉然后继续向前走

5、如果不是食物就不吃继续向前走

6、在最后还要设置游戏失败的两种方式:自己撞自己和撞墙

注意事项:

 1、在pNext指向下一个结点时,该节点虽然已经有了内存空间但是结点的具体横纵坐标需要经过switc语句的判断后进行分配

2、切换完成后进行的坐标更改如下图所示:

3、!!!注意这里对于x坐标的加减操作数是2,对y坐标的加减操作数是1!!!如果将2写成了1虽然程序正常运行但是由于蛇身是宽字符的原因所以当蛇在水平方向上移动时蛇身结点会重叠同时方向切换时也会有明显的延迟感

4、每个蛇身结点的内存空间所在的坐标一直在变化且不论蛇身有多长的每个结点都有自己的内存空间(在蛇移动函数中已经明确说明了蛇结点的x和y坐标会发生改变,其实就相当于将内存空间不断地移位)

最终效果:

错误的:

正确的:

检测下一个是不是食物函数:
//检查下一个是不是食物
int NextIsFood(pSnake ps, pSnakeNode pnext)
{return (pnext->x == ps->_pFood->x) && (pnext->y == ps->_pFood->y);
}
注意事项:

        如果之前创建的食物结点的横纵坐标与蛇运动方向(蛇的第一个结点)相同的下一个结点的横纵坐标相等,那么执行吃食物函数的操作,如果不相等那么就执行不吃食物的操作。

吃食物函数:
//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext)
{//头插法pnext->next = ps->_pSnake;ps->_pSnake = pnext;//打印蛇身pSnakeNode cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//原来的食物结点吃掉后,就要释放掉它的内存空间free(ps->_pFood);//吃掉食物后分数增加ps->_Score += ps->_Add;//食物吃掉后还要再次创建一个新的食物结点CreateFood(ps);
}

实现步骤:

1、利用头插法的原理将吃掉的食物结点作为蛇头(第一个结点)

2、遍历打印蛇身

3、由于我们在蛇移动函数的开头已经创建申请了蛇头的下一个结点的内存空间,在更早一点的时候我们还在创建食物时也申请了一块内存空间,所以当我们蛇头下一个节点为先前创建的食物结点时两块内存空间会在同一坐标下,所以当我们打印完新的蛇身时,需要将该坐标下申请的食物结点的内存空间释放掉,原来申请的蛇头下一个结点的内存空间被保留下来作为蛇头

4、在食物吃完后除了将得分进行增加,还需要创建一个新的食物

不吃食物函数:
//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext)
{//头插法pnext->next = ps->_pSnake;ps->_pSnake = pnext;//打印蛇身pSnakeNode cur = ps->_pSnake;while (cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//如果不吃食物蛇往前走的时候,最后//先设置为空格后再释放SetPos(cur->next->x,cur->next->y);//将光标置于最后一个结点的坐标处printf("  ");//在该结点处打印两个空格用来遮盖原来打印在这里的宽字符●free(cur->next);cur->next = NULL;
}

实现步骤:

1、依旧是头插法

2、依旧是打印蛇身

3、将原来最后一个结点的位置打印两个空格后,再释放为该结点申请的内存空间

注意事项:

1、打印蛇身时的while判断条件为cur->next->next,具体原因请看下图:

2、关于“    SetPos(cur->next->x,cur->next->y);”的解释:因为蛇头的下一个结点不为食物结点所以蛇身在向前移动时的结点个数并不会发生改变,但是我们之前已经为蛇头的下一个结点申请了内存空间,该结点也会作为新的蛇头存在此时最后的一个结点就不能存在了否则蛇身就会变长而非不变。通俗来讲就是:前面结点增加一,后面结点就应该减少一个以维持原状,蛇的移动过程如果将每一步都暂停的话其实可以看作是一个在链表头部增加一个新结点后为保持原来节点个数不变所以再在链表尾部删除最后的结点。

3、如果只进行内存释放,虽然地图上打印了多个结点但蛇穿过去后并不会结束游戏

这是因为每次移动后虽然蛇身的最后一个结点的空间已经被释放了,但是他仍然会被打印在屏幕上,所以当我们蛇头穿过那些未被覆盖掉的●时它们其实已经是空有”外表“没有“内核”

4、如果只打印空格而不释放就会:

可以发现此时的打印也并未产生应有的效果......

自己撞自己函数:
//自杀的死亡方式(自己撞自己)
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y){ps->_Status = KILL_BY_SELF;}cur = cur->next;}
}
实现步骤:

1、令cur指向蛇头的下一个结点并开始遍历,直到满足有一次蛇在运动过程中蛇身的某个结点的横纵坐标与cur此时指向的坐标相同那么游戏状态就会被切换至KILL_BY_SELF

(大概思路就是这样具体何时会出现这样的情况,可以画图检验一下)

撞墙函数:
//它杀的死亡方式(撞墙)
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 ||ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 ||ps->_pSnake->y == 26)ps->_Status = KILL_BY_WALL;
}

这里就不作过多解释了~

游戏结束函数-GameEnd:

//游戏结束函数
void GameEnd(pSnake ps)
{pSnakeNode cur = ps->_pSnake;SetPos(24, 12);switch (ps->_Status){case END_NOMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞上自己了 ,游戏结束!\n");break;case KILL_BY_WALL:printf("您撞墙了,游戏结束!\n");break;}//释放蛇身的节点while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

全部代码:

Snake.c文件:

#define _CRT_SECURE_NO_WARRINGS#include "snake.h"// 设置光标的坐标(程序输入时的位置)
void SetPos(short x, short y)
{COORD pos = { x, y };// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);// 设置标准输出上光标的位置为 posSetConsoleCursorPosition(hOutput, pos);
}//欢迎界面
void WelcomeToGame()
{//显示一SetPos(38, 14);printf("欢迎来到贪吃蛇小游戏");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");//暂停操作system("cls");//清屏//显示二SetPos(20, 14);//重新定义光标位置,从该坐标处开始输入printf("使用↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");SetPos(40, 25);system("pause");system("cls");//显示三SetPos(37, 14);printf("加速将能得到更高的分数\n");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");system("cls");
}//创建地图
void CreateMap()
{//地图的四个角的坐标为(0,0)       (56,0)//					  (0,1)        (56,1)  //在打印左/右侧墙体时,由于之前上下两行的打印已经将左/右侧墙体的第一个和左后一个打印过了,所以要注意坐标问题,左/右侧每列要少打两个//						...             ...//					  (0,25)     (56,25)//					  (0,26)     (56,26)//打印上边界(0,0)至(56,0)SetPos(0, 0);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);//wprintf函数的使用方式上面右描述,记得打印宽字符不是%c而是%lc}//打印下边界(0,26)至(56,26)SetPos(0, 26);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//打印左边界(0,1)至(0,25)for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//打印右边界(56,1)至(56,25)for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}//初始化游戏各项数据
void InitSnake(pSnake ps)//ps指向结构体
{//创建一个指向链表的的指针变量cur,利用该指针创建和连接结点pSnakeNode cur;//默认创建五个结点for (int i = 0; i < 5; i++){//令cur指向新开辟的结点内存空间cur = (pSnakeNode*)malloc(sizeof(SnakeNode));//关于malloc函数的使用不再过多描述,有疑问可以去看我的《动态内存管理》文章//开辟失败的报错if (cur == NULL){perror("InitSnake()-malloc()");return;}//分配完内存空间后就会为该结点分配初始坐标(x,y)//2*i实现可以实现横向创建一条蛇身的目的:(26,5)(28,5)(30,5)(32,5)(34,5)cur->x = POS_X + 2 * i;//定义的变量POS_X和POS_Y便于后期切换初始坐标cur->y = POS_Y;cur->next = NULL;//到这里已经完成一个结点的创建,但是该结点还没有连接//利用头插法进行蛇身体的链接//ps_pSnake相当于一个套娃,ps指向结构体,ps_pSnake指向该结构体中指向链表的指针if (ps->_pSnake == NULL){ps->_pSnake = cur;//交接工作}//如果链表不为空则进行头插else{cur->next = ps->_pSnake;ps->_pSnake = cur;}}//链表连接完成后,打印结点cur = ps->_pSnake;//令cur指向蛇的第一个结点while (cur)//只要cur指向结点不为空就继续循环打印{SetPos(cur->x, cur->y);//使用上面分配过的x和y坐标开始从该坐标处打印蛇身wprintf(L"%lc", BODY);//打印我们之前规定的符号●cur = cur->next;//cur指向下一个结点}//初始化游戏各类所需数据ps->_SleepTime = 200; //规定蛇每次移动都需要休息2秒ps->_Score = 0;//规定初始得分为0ps->_Status = OK;//规定初始游戏状态为okps->_Dir = RIGHT;//规定蛇开始的运行方向向右ps->_Add = 10;//规定吃掉一个食物的得到10分}//创建⻝物
void CreateFood(pSnake ps)//食物其实也相当于一个链表结点
{//为食物设置随机的横纵坐标int x = 0;int y = 0;again://利用goto语句实现多次循环do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0); //产⽣的x坐标应该是2的倍数,这样才能与蛇头坐标对⻬pSnakeNode cur = ps->_pSnake;//获取链表的第一个结点//⻝物的结点不能和此时蛇身的某个结点重合,如果重合则利用goto语句重新分配食物结点的横纵坐标while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;//不重合就令cur指向下一个结点}//为食物结点申请内存空间pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//创建失败的报错if (pFood == NULL){perror("CreateFood::malloc()");return;}else{//创建成功,为结点分配实质的坐标pFood->x = x;pFood->y = y;SetPos(pFood->x, pFood->y);//令光标放在该节点的x和y坐标上wprintf(L"%c", FOOD);//打印食物结点ps->_pFood = pFood;//令_pFood指向该结点(将该结点的地址交给_pFood)}
}//游戏准备函数
void GameStart(pSnake ps)
{//控制台窗口设置system("mode con cols=100 lines=30");system("title 贪吃蛇");//光标隐藏HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取权限CONSOLE_CURSOR_INFO CursorInfo; //定义结构体类型变量GetConsoleCursorInfo(hOutput, &CursorInfo); // 获取控制台光标信息CursorInfo.bVisible = false; // 隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo); // 设置控制台光标状态//打开欢迎界面WelcomeToGame();//打印地图CreateMap();//初始化游戏各项数据InitSnake(ps);//创造第⼀个⻝物CreateFood(ps);
}//打印右侧帮助信息
void PrintHelpInfo()
{//打印提⽰信息SetPos(64, 15);printf("1、不能穿墙,不能咬到自己");SetPos(64, 16);printf("2、使用↑、↓、←、→控制蛇的移动");SetPos(64, 17);printf("3、按F3加速  按F4减速");SetPos(64, 18);printf("4、ESC:退出游戏  space:暂停游戏");
}//暂停游戏函数
void pause()
{while (1){Sleep(300);//只要开始暂停就会一直休息,这里的Sleep你可以设置为任意值if (KEY_PRESS(VK_SPACE))//只有当再次点击空格时游戏才会继续执行(类似于看视频按空格键暂停和继续)break;}
}//检查下一个是不是食物
int NextIsFood(pSnake ps, pSnakeNode pnext)
{return (pnext->x == ps->_pFood->x) && (pnext->y == ps->_pFood->y);
}//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext)
{//头插法pnext->next = ps->_pSnake;ps->_pSnake = pnext;//打印蛇身pSnakeNode cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//原来的食物结点吃掉后,就要释放掉它的内存空间free(ps->_pFood);//吃掉食物后分数增加ps->_Score += ps->_Add;//食物吃掉后还要再次创建一个新的食物结点CreateFood(ps);
}//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext)
{//头插法pnext->next = ps->_pSnake;ps->_pSnake = pnext;//打印蛇身pSnakeNode cur = ps->_pSnake;while (cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//如果不吃食物蛇往前走的时候,最后//先设置为空格后再释放SetPos(cur->next->x, cur->next->y);//将光标置于最后一个结点的坐标处printf("  ");//在该结点处打印两个空格用来遮盖原来打印在这里的宽字符●free(cur->next);cur->next = NULL;
}//它杀的死亡方式(撞墙)
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 ||ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 ||ps->_pSnake->y == 26)ps->_Status = KILL_BY_WALL;
}//自杀的死亡方式(自己撞自己)
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y){ps->_Status = KILL_BY_SELF;}cur = cur->next;}
}//蛇移动函数
void SnakeMove(pSnake ps)
{pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove()::malloc()");return;}pNext->next = NULL;//利用switc判断ps->_Dir的不同方向switch (ps->_Dir){case UP://如果蛇是向上运动的,那么蛇运动的下一个结点的x轴坐标与蛇头保持一致,y轴坐标为蛇头y轴坐标减一,下面的就不一一写解释了pNext->x = ps->_pSnake->x;pNext->y = ps->_pSnake->y - 1;break;case DOWN:pNext->x = ps->_pSnake->x;pNext->y = ps->_pSnake->y + 1;break;case LEFT:pNext->x = ps->_pSnake->x - 2;pNext->y = ps->_pSnake->y;break;case RIGHT:pNext->x = ps->_pSnake->x + 2;pNext->y = ps->_pSnake->y;break;}//判断蛇头到达的坐标处是否是食物if (NextIsFood(ps, pNext)){//吃掉食物EatFood(ps, pNext);}else{//不吃食物NoFood(ps, pNext);}KillByWall(ps);KillBySelf(ps);
}//游戏运行函数
void GameRun(pSnake ps)
{//打印右侧帮助信息(静态)PrintHelpInfo();do{//打印计分表(动态),蛇的吃食物、加减速都会引起计分表的变化SetPos(64, 8);printf("目前得分情况:%d", ps->_Score);SetPos(64, 9);if (ps->_Add < 10)printf("每个食物的分数: 0%d", ps->_Add);elseprintf("每个食物的分数:%d", ps->_Add);//且按下的方向不能与蛇当前移动方向相反(它头正在向上走你突然让它向下走是不行的)//对于如何使用_Dir赋值后的结果我们会在SnakeMove中实现if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN){ps->_Dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP){ps->_Dir = DOWN;}else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT){ps->_Dir = RIGHT;}else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT){ps->_Dir = LEFT;}else if (KEY_PRESS(VK_SPACE))//按下空格键时暂停游戏{pause();}else if (KEY_PRESS(VK_ESCAPE))//按下ESC键时主动退出游戏{ps->_Status = END_NOMAL;  //主动切换游戏状态为END_NOMALbreak;}else if (KEY_PRESS(VK_F3))//按下F3加速{//速度越快得分越高,最多加速五次每次加速食物分数增加2,最高为20if (ps->_SleepTime >= 50){ps->_SleepTime -= 30;ps->_Add += 2;}}else if (KEY_PRESS(VK_F4))//按下F4减速{//速度越慢得分越低,最多减速四次每次减速食物分数减少2,最低为2if (ps->_SleepTime < 320){ps->_SleepTime += 30;ps->_Add -= 2;}}//蛇每次移动一定进行休眠,休眠时间越短,蛇移动的速度就越快Sleep(ps->_SleepTime);//当蛇经历过休眠后,就要开始移动了SnakeMove(ps);}//运动完成后检查游戏状态,游戏状态为OK时才会继续循环while (ps->_Status == OK);
}//游戏结束函数
void GameEnd(pSnake ps)
{pSnakeNode cur = ps->_pSnake;SetPos(24, 12);switch (ps->_Status){case END_NOMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞上自己了 ,游戏结束!\n");break;case KILL_BY_WALL:printf("您撞墙了,游戏结束!\n");break;}//释放蛇身的节点while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

test.c文件:

这里不做过多解释~

#include "snake.h"
void test()
{int ch = 0;do{Snake snake = { 0 };//创建一个Snake结构体类型的变量snake,{0}表示此时获得的总分score、以及每个食物的分数add等成员列表中的各项内容全部为0//游戏开始GameStart(&snake);//游戏运行GameRun(&snake);//游戏结束GameEnd(&snake);SetPos(20, 15);printf("再来一句吗?(Y/N)");ch = getchar();getchar();} while (ch == 'Y' || ch == 'y');SetPos(0, 27);}
int main()
{srand((unsigned int)time(NULL));setlocale(LC_ALL, "");test();return 0;
}

Snake.h文件:

#include <stdio.h>
#include <windows.h>
#include <stdbool.h>
#include <locale.h>
#include <stdlib.h>
#include <time.h>#define WALL L'□'     //定义墙体的符号
#define BODY L'●'     //定义蛇身的符号
#define FOOD L'★'    //定义食物的符号
#define POS_X 24      //定义蛇尾的横坐标
#define POS_Y 5       //定义蛇尾的纵坐标//检测按键是否按下以及按的哪一个键(上一篇的Win32 API中提到过)
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)//定义反应蛇运行⽅向的枚举类型
enum DIRECTION
{UP,            //向上DOWN,          //向下 LEFT,          //向左RIGHT          //向右
};//定义反应游戏状态的枚举类型
enum GAME_STATUS
{OK,              //游戏正常运⾏KILL_BY_WALL,    //撞墙KILL_BY_SELF,    //自己撞到自己END_NOMAL        //正常结束(自己选择ESC结束游戏)
};//定义用与创建结点的链表
typedef struct SnakeNode
{int x;//结点横坐标int y;//结点纵坐标struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//重命名为SnakeNode类型,pSnakeNode指针指向该链表//包含游戏各项数据的结构体类型
typedef struct Snake
{pSnakeNode _pSnake;        //用于维护链表结点的指针(规定它指向链表的第一个结点)pSnakeNode _pFood;         //用于维护食物的指针enum DIRECTION _Dir;       //_Dir是该枚举类型的变量,可以为其赋值//比如:enum DIRECTION _Dir = ok; 后续我们会利用switch语句与之配合enum GAME_STATUS _Status;int _Socre;                //获得总分数int _Add;                  //每个食物的分数int _SleepTime;            //每进行一次状态转换(切换防线、吃掉食物等)都需要进行短暂的休息
}Snake, * pSnake;//重命名为Snake类型,pSnake指针指向该结构体//下面是具体要声明的函数//游戏准备函数
void GameStart(pSnake ps);//设置光标位置
void SetPos(short x, short y);//打开欢迎界面
void WelcomeToGame();//打印地图
void CreateMap();//初始游戏各项数据
void InitSnake(pSnake ps);//创造第⼀个⻝物
void CreateFood(pSnake ps);//游戏运行函数
void GameRun(pSnake ps);//打印右侧帮助信息
void PrintHelpInfo();//游戏暂停
void pause();//蛇移动
void SnakeMove(pSnake ps);//判断蛇头到达的坐标处是否为食物
int NextIsFood(pSnake ps, pSnakeNode pnext);//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext);//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext);//游戏结束函数
void GameEnd(pSnake ps);//撞墙检测
void KillByWall(pSnake ps);//撞自身检测
void KillBySelf(pSnake ps);

~over~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/122085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA中的垃圾回收器(1)

一)垃圾回收器概述: 1.1)按照线程数来区分: 串行回收指的是在同一时间端内只允许有一个CPU用于执行垃圾回收操作&#xff0c;此时工作线程被暂停&#xff0c;直至垃圾回收工作结束&#xff0c;在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合&#xff0c;出行…

Kafka KRaft模式探索

1.概述 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;它可以处理消费者在网站中的所有动作流数据。其核心组件包含Producer、Broker、Consumer&#xff0c;以及依赖的Zookeeper集群。其中Zookeeper集群是Kafka用来负责集群元数据的管理、控制器的选举等。 2.内容…

python爬虫request和BeautifulSoup使用

request使用 1.安装request pip install request2.引入库 import requests3.编写代码 发送请求 我们通过以下代码可以打开豆瓣top250的网站 response requests.get(f"https://movie.douban.com/top250"&#xff09;但因为该网站加入了反爬机制&#xff0c;所以…

非遗主题网站的设计与实现基于PHP实现

包括源码参考论文 下载地址: https://juzhendongli.store/commodity/details/18

Linux Centos7安装后,无法查询到IP地址,无ens0,只有lo和ens33的解决方案

文章目录 前言1 查看network-scripts目录2 创建并配置 ifcfg-ens33 文件3 禁用NetworkManager4 重新启动网络服务总结 前言 在VMware中&#xff0c;安装Linux centos7操作系统后&#xff0c;想查询本机的IP地址&#xff0c;执行ifconfig命令 ifconfig结果如下&#xff1a; 结…

【Linux】权限完结

个人主页点击直达&#xff1a;小白不是程序媛 系列专栏&#xff1a;Linux被操作记 目录 前言 chown指令 chgrp指令 文件类型 file指令 目录的权限 粘滞位 umask指令 权限总结 前言 上篇文章我们说到对于一个文件所属者和所属组都是同一个人时&#xff0c;使用所属者身…

【NLP】word复制指定内容到新的word文档

目录 1.python代码 2.结果 需求&#xff1a; 复制word文档里的两个关键字&#xff08;例如“起始位置”到“结束位置”&#xff09;之间的内容到新的word文档。 前提&#xff1a;安装win32包&#xff0c;通过pip install pywin32命令直接安装。话不多说&#xff0c;直接上代码…

RSA:基于小加密指数的攻击方式与思维技巧

目录 目录 目录 零、前言 一、小加密指数爆破 [FSCTF]RSA签到 思路&#xff1a; 二、基于小加密指数的有限域开根 [NCTF 2019]easyRSA 思路&#xff1a; 三、基于小加密指数的CRT [0CTF 2016] rsa 思路&#xff1a; 零、前言 最近&#xff0c;发现自己做题思路比较…

设计模式之桥梁模式

什么是桥梁模式 桥梁模式&#xff08;Bridge Pattern&#xff09;也称为桥接模式&#xff0c;属于结构型模式&#xff0c;它主要目的是通过组合的方式建立两个类之间的联系&#xff0c;而不是继承。桥梁模式将抽象部分与它的具体实现部分分离&#xff0c;使它们都可以独立地变…

GnuTLS recv error (-110): The TLS connection was non-properly terminated

ubuntu git下载提示 GnuTLS recv error (-110): The TLS connection was non-properly terminated解决方法 git config --global --unset http.https://github.com.proxy

Day13力扣打卡

打卡记录 奖励最顶尖的 k 名学生(哈希表排序) 用哈希表对所有的positive与negative词条进行映射&#xff0c;然后遍历求解。tip&#xff1a;常用的分割字符串的操作&#xff1a;1.stringstream配合getline() [格式buf, string, char]2.string.find()[find未找到目标会返回npos…

别处拿来的VUE项目 npm run serve报错

问题现象&#xff1a; 从别处拷贝来的VUE项目&#xff0c;根据说明通过npm install 加载了项目依赖 &#xff0c;但是运行npm run serve里报错&#xff1a; npm ERR! Missing script: "serve" npm ERR! npm ERR! To see a list of scripts, run: npm ERR! npm ru…

Java 将数据导出到Excel并发送到在线文档

一、需求 现将列表数据&#xff0c;导出到excel,并将文件发送到在线文档&#xff0c;摒弃了以往的直接在前端下载的老旧模式。 二、pom依赖 <!-- redission --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-…

ActiveMQ消息中间件简介

一、ActiveMQ简介 ActiveMQ是Apache出品&#xff0c;最流行的&#xff0c;能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE1.4规范的JMS Provide实现。尽管JMS规范出台已经是很久的事情了&#xff0c;但是JMS在当今的J2EE应用中仍然扮演这特殊的地位。 二、Active…

云计算模式的区域LIS系统源码,基于ASP.NET+JQuery、EasyUI+MVC技术架构开发

云计算模式的区域LIS系统源码 云LIS系统源码&#xff0c;自主版权 LIS系统是专为医院检验科的仪器设备能与计算机连接。可通过LIS系统向仪器发送指令&#xff0c;让仪器自动操作和接收仪器数据。并快速的将检验仪器中的数据导入到医生工作站中进行管理&#xff0c;且可将检验结…

springboot项目打jar包,运行时提示jar中没有主清单属性

可能性一&#xff1a; 没有在pom中加入maven插件 在pom中加入下方代码即可。 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</ve…

039-第三代软件开发-PDF阅读器

第三代软件开发-PDF阅读器 文章目录 第三代软件开发-PDF阅读器项目介绍PDF阅读器1 初始化PDF view2 qml 中使用3 创建模块 关键字&#xff1a; Qt、 Qml、 pdf、 LTDev、 本地 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff08;Qt Met…

如何快速排查SSD IO延迟抖动问题?

一块固态硬盘设计背后&#xff0c;有硬件控制器&#xff0c;NAND闪存颗粒、DRAM&#xff0c;还有固件FTL算法等。SSD设计的本身其实是一件特别复杂的过程&#xff0c;需要考虑各种客户需求且要保证可靠性、性能、稳定性。 针对SSD的相关性能测试&#xff0c;SNIA也有专门针对SS…

【RabbitMQ 实战】12 镜像队列

一、镜像队列的概念 RabbitMQ的镜像队列是将消息副本存储在一组节点上&#xff0c;以提高可用性和可靠性。镜像队列将队列中的消息复制到一个或多个其他节点上&#xff0c;并使这些节点上的队列保持同步。当一个节点失败时&#xff0c;其他节点上的队列不受影响&#xff0c;因…

漏洞复现-showdoc文件上传_v2.8.3_(CNVD-2020-26585)

showdoc文件上传_v2.8.3_CNVD-2020-26585 漏洞信息 showdoc 2.8.3 以下版本中存在安全漏洞CNVD-2020-26585文件上传漏洞 描述 ShowDoc是一个非常适合IT团队的在线API文档、技术文档工具。通过showdoc&#xff0c;你可以方便地使用markdown语法来书写出美观的API文档、数据字…