cookie做网站访问量,网站备案模板,网站桥页怎么找,做简历哪个网站比较好一.游戏要实现基本的功能#xff1a;
• 贪吃蛇地图绘制
• 蛇吃食物的功能 #xff08;上、下、左、右方向键控制蛇的动作#xff09;
• 蛇撞墙死亡
• 蛇撞自身死亡
• 计算得分
• 蛇身加速、减速
• 暂停游戏 二.技术要点
C语言函数、枚举、结构体、动态内存管…一.游戏要实现基本的功能
• 贪吃蛇地图绘制
• 蛇吃食物的功能 上、下、左、右方向键控制蛇的动作
• 蛇撞墙死亡
• 蛇撞自身死亡
• 计算得分
• 蛇身加速、减速
• 暂停游戏 二.技术要点
C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。
三.补充知识Win32 API
1.Win32 API介绍
Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外 它同时也是⼀个很大的服务中心这个服务中心提供了多种服务每⼀种服务就是⼀个函数调用这些服务可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的由于这些函数服务的对象是应用程序(Application) 所以便称之为 Application Programming Interface简称 API 函数。WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口。
2.控制台程序
平常我们运行起来的命令提示符黑框框程序其实就是控制台程序 我们可以使用cmd命令来设置控制台窗口的长宽设置控制台窗口的大小lines来设置30行cols来设置100列
mode con cols100 lines30
也可以通过命令设置控制台窗口的名字
title 贪吃蛇 这些能在控制台窗口执行的命令也可以调用C语言函数system需要包含头文件windows.h来执行。
#includewindows.hint main()
{//设置控制台窗口的⻓宽设置控制台窗口的⼤小30行100列system(mode con cols100 lines30);//设置cmd窗口名称system(title 贪吃蛇);return 0;
} 0;因相关命令知识较多本文仅根据游戏需要处使用相关命令诸如换行、改标题不会具体讲解某个命令感兴趣的读者可自行了解
参考mode命令
参考title命令
3.控制台屏幕上的坐标COORD 正常我们运行程序输出信息仅仅通过‘\n’来实现不同行当输出并且输出默认其实位置时是都是定格开始但是如果我们要创建墙体在特定出打印相关信息我们就需要能够将光标移动到对应位置。这就涉及到窗体的坐标系了。 COORD 是Windows API中定义的⼀个结构体表示⼀个字符在控制台屏幕幕缓冲区上的坐标坐标系 (00) 的原点位于缓冲区的顶部左侧单元格。 但需要注意的是控制台屏幕上的坐标COORD的x,y的一个大小并不是一一对应关系由于宽窄字符的区别x的一个单位长度设置的较小在长度上y的一个单位差不多相当于x的2个单位。 COORD的声明
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
给坐标值赋值
1 COORD pos { 10, 15 };
4.GetStdHandle
在知道坐标系的概念后我们还不能直接的去操控光标的移动就像我们访问一些博物馆机密的地方时我们需要递交身份证或者一些凭证来获得访问权限一样为了能够对光标进行操作我们也需要凭证来获得权限。 GetStdHandle是⼀个Windows API函数。它用于从⼀个特定的标准设备标准输入、标准输出或标准错误中取得⼀个句柄用来标识不同设备的数值这个句柄就相当于凭证通过句柄我们可以操作对应设备。
GetStdHandle的返回值就是对应设备的句柄形参通过接受STD_INPUT_HANDLE、 STD_OUTPUT_HANDLE、STD_ERROR_HANDLE三个值来获得输入输出及错误设备的句柄
1 HANDLE GetStdHandle(DWORD nStdHandle);
HANDLE hOutput NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput GetStdHandle(STD_OUTPUT_HANDLE);
值含义STD_INPUT_HANDLE((DWORD)-10)标准输入设备。 最初这是输入缓冲区 CONIN$ 的控制台STD_OUTPUT_HANDLE((DWORD)-11)标准输出设备。 最初这是活动控制台屏幕缓冲区 CONOUT$STD_ERROR_HANDLE((DWORD)-12)标准错误设备。 最初这是活动控制台屏幕缓冲区 CONOUT$ 5.GetConsoleCursorInfo
在凭证后我们有了对光标操作的权限之后要实现对光标的一些操作我们就要找到存放光标信息的箱子将原有的信息按照我们想要的进行修改然后将修改的箱子在放回去。 GetConsoleCursorInfo 是检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息的函数
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针该结构接收有关主机游标
光标的信息
HANDLE hOutput NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息
它接受两个参数首先我们需要获得输出设备的句柄然后我们要创建CONSOLE_CURSOR_INFO类型的箱子用来存放GetConsoleCursorInfo获得的光标信息。
6.CONSOLE_CURSOR_INFO
CONSOLE_CURSOR_INFO 这个结构体包含有关控制台光标的信息
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
dwSize是由光标填充的字符单元格的百分比。 此值介于1到100之间。 改变dwsize光标外观会变化范围从完全填充单元格到单元底部的水平线条。
• bVisible是表示游标的可见性。 如果光标可见则此成员为 TRUE否则为FLASE(注意需要包含stdbool.h头文件)。
7.SetConsoleCursorInfo
设置指定控制台屏幕缓冲区的光标的大小和可见性
#includewindows.h
#includestdbool.h
int main()
{// 获得标准输出设备的句柄HANDLE houtput NULL;houtput GetStdHandle(STD_OUTPUT_HANDLE);// 定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info {0};// 获取和 houtput 句柄相关的控制台上的光标信息存放在 cursor_info 中GetConsoleCursorInfo(houtput, cursor_info);// 修改光标的占比cursor_info.dwSize 100;// 设置和 houtput 句柄相关的控制台上的光标信息SetConsoleCursorInfo(houtput, cursor_info);system(pause);return 0;
} #includewindows.h
#includestdbool.h
int main()
{// 获得标准输出设备的句柄HANDLE houtput NULL;houtput GetStdHandle(STD_OUTPUT_HANDLE);// 定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info {0};// 获取和 houtput 句柄相关的控制台上的光标信息存放在 cursor_info 中GetConsoleCursorInfo(houtput, cursor_info);cursor_info.bVisible false;// 设置和 houtput 句柄相关的控制台上的光标信息SetConsoleCursorInfo(houtput, cursor_info);system(pause);return 0;
} 8.SetConsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置我们将想要设置的坐标信息放在COORD类型的pos中调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
)
COORD pos { 10, 5};
HANDLE hOutput NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
8.1贪吃蛇游戏光标定位
如果直接使用SetConsoleCursorPosition步骤过于繁琐因此我们将上述步骤专门分装成用于光标定位的函数。
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos { x, y };
HANDLE hOutput NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
} 9.GetAsyncKeyState
在贪吃蛇游戏中我们会通过按上下左右键来改变方向通过按F3,F4改变速度等因此我们需要能够检测键是否被按过因为本游戏是按一次键相应作出变换因此我们不考虑一直按键的情况。
为了区分对应的键及相关信息的传递我们规定的虚拟键值这一概念对应键有对应的整型值(16进制形式)注意这不要与ASCII值混淆。
参考虚拟键码 (Winuser.h) - Win32 apps GetAsyncKeyState就是这个用途的函数函数原型如下
SHORT GetAsyncKeyState(
int vKey
)
将键盘上每个键的虚拟键值传递给函数函数通过返回值来分辨按键的状态。
GetAsyncKeyState 的返回值是short类型在上⼀次调用GetAsyncKeyState 函数后如果返回的16位的short数据中最高位是1说明按键的状态是按下如果最高是0说明按键的状态是抬起如果最低位被置为1则说明该按键被按过否则为0。
如果我们要判断⼀个键是否被按过可以检测GetAsyncKeyState返回值的最低值是否为1.
实例检测数字键:
#include stdio.h
#include windows.h
int main()
{
while (1)
{
if (KEY_PRESS(0x30))
{
printf(0\n);
}
else if (KEY_PRESS(0x31))
{
printf(1\n);
}
else if (KEY_PRESS(0x32))
{
printf(2\n);
}
else if (KEY_PRESS(0x33))
{
printf(3\n);
}
else if (KEY_PRESS(0x34))
{
printf(4\n);
}
else if (KEY_PRESS(0x35))
{
printf(5\n);
}
else if (KEY_PRESS(0x36))
{
printf(6\n);
}
else if (KEY_PRESS(0x37))
{
printf(7\n);
}
else if (KEY_PRESS(0x38))
{
printf(8\n);
}
else if (KEY_PRESS(0x39))
{
printf(9\n);
}
}
return 0;
}9.1贪吃蛇游戏按键识别
在贪吃蛇游戏中我们可以通过GetAsyncKeyState返回值按位与1的值是1还是0来判断键是否被按过为了方便在这里笔者定义了一个宏命令。
1 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 )
四.补充知识C语言的国际化与本地化 1.C语言国际化与本土化的由来
在游戏地图上我们打印墙体使用宽字符□打印蛇使用宽字符●打印食物使用宽字符★
但是我们发现我们无法直接打印出来这是为什么呢因为这类字符属于宽字符。而因为字符属于窄字符。
普通的字符是占⼀个字节的这类宽字符是占用2个字节。过去C语言并不适合非英语国家地区使用。C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。
C语言字符默认是采用ASCII编码的ASCII字符集采用的是单字节编码且只使用了单字节中的低7位最高位是没有使用的可表示为0xxxxxxxx可以看到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 和宽字符的输入和输出函数加入了locale.h头文件其中提供了允许程序员针对特定地区通常是国家或者说某种特定语言的地理区域调整程序行为的函数
2.locale.h本地化
locale.h提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分使用该头文文件会自动检测系统使用的地区、时间等。 在标准中依赖地区的部分有以下几项
• 数字量的格式
• 货币量的格式
• 字符集
• 日期和时间的表示形式
实验代码
#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;
} 3.类项
通过修改地区程序可以改变它的型为来适应世界的不同区域。但地区的改变可能会影响库的许多部分其中⼀部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改下面的⼀个宏指定⼀个类项
• LC_COLLATE影响字符串比较函数 strcoll() 和 strxfrm() 。
• LC_CTYPE影响字符处理函数的行为。
• LC_MONETARY影响货币格式。
• LC_NUMERIC影响 printf() 的数字格式。
• LC_TIME影响时间格式 strftime() 和 wcsftime() 。
• LC_ALL - 针对所有类项修改将以上所有类别设置为给定的语言环境。
参考每个类项
4.setlocale
1 char* setlocale (int category, const char* locale)
setlocale 函数用于修改当前地区可以针对⼀个类项修改也可以针对所有类项。
setlocale 的第⼀个参数可以是前面说明的类项中的⼀个那么每次只会影响⼀个类项如果第⼀个参数是LC_ALL就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值C正常模式和 本地模式。
在任意程序执行开始都会隐藏式执行调用:
setlocale(LC_ALL, C);
当地区设置为C时库函数按正常方式执行小数点是⼀个点。
当程序运行起来后想改变地区就只能显示调用setlocale函数。用 作为第2个参数调用setlocale函数就可以切换到本地模式这种模式下程序会适应本地环境。
比如切换到我们的本地模式后就支持宽字符汉字的输出等
1 setlocale(LC_ALL, );
4.宽字符的打印wprintf
那如果想在屏幕上打印宽字符怎么打印呢
宽字符的字面量必须加上前缀“L”否则 C 语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面表示宽字符对应 wprintf() 的占位符为 %lc 在双引号前⾯表示宽字符串对应wprintf() 的占位符为 %ls 。
#include stdio.h
#includelocale.h
int main() {setlocale(LC_ALL, );wchar_t ch1 L●;wchar_t ch2 L你;wchar_t ch3 L好;wchar_t ch4 L★;printf(%c%c\n, a, b);wprintf(L%lc\n, ch1);wprintf(L%lc\n, ch2);wprintf(L%lc\n, ch3);wprintf(L%lc\n, ch4);return 0;
} 附读者可能会疑惑我们平时使用的printf()不是也可以打印宽字符汉字吗两个函数的打印有什么区别吗
printf()打印的汉字在中国大陆常见的是 GB2312、GBK 或者 UTF-8 编码。这个编码通常由系统的 locale 设置决定。
wprintf()打印的汉字通常采用的编码方式是宽字符编码比如 UTF-16 或者 UTF-32。因此打印宽字符时选用wprintf()会更好
五.贪吃蛇游戏实现
1.规划思路如下 #includesnake.hvoid test()
{int ch 0;srand((unsigned int)time(NULL));//通过rand产生随机坐标来产生随机食物的效果do{snake sk { 0 };GameStart(sk);GameRun(sk);GameEnd(sk);SetPos(25, 15);printf(您是否再来一局(Y/N):);ch getchar();while (getchar() ! \n);} while (ch Y|| ch y);}int main()
{setlocale(LC_ALL, );//修改当前地区为本地模式为了支持中文宽字符及特殊字符的打印test();//测试逻辑SetPos(0, 27);//将光标移动至地图下方不会破坏地图return 0;
}
2.游戏主逻辑
程序开始就设置程序支持本地模式然后进入游戏的主逻辑。主逻辑分为3个过程
• 游戏开始GameStart完成游戏的初始化
• 游戏运行GameRun完成游戏运行逻辑的实现
• 游戏结束GameEnd完成游戏结束的说明实现资源释放
2.1游戏开始GameStart
这个模块完成游戏的初始化任务
• 控制台窗口大小的设置
• 控制台窗口名字的设置
• ⿏标光标的隐藏
• 打印欢迎界面
• 创建地图
• 初始化蛇
• 创建第⼀个食物
void GameStart(psnake ps)
{WelcomeToGame();//设置窗口名称大小、隐藏光标打印欢迎界面CreatMap();//打印欢迎界面InitSnake(ps);//初始化蛇及其相关信息CreateFood(ps);//创造第一个食物}
2.1.1打印欢迎界面
2.1.1.1.窗口设置
由于本项目是通过控制台窗口实现的但是win系统升级之类缘故C语言开发环境调用的窗口使用的初始设置无法很好的实现我们想要的效果因此这里要先更改一下设置。首先运行程序弹出黑框然后鼠标移动到如下区域右键点击设置。 将默认终端应用程序改为windows控制台主机。 更改完成后如果对颜色不太满意可以右键弹出窗口点击设置在颜色中调整窗体在这里笔者为了演示效果调成了灰色。
在游戏正式开始之前我们可以做⼀些功能提醒这里我们可以先设置窗体大小名称需要注意的是我们除了创建地图之外地图旁边还需留有空间显示游戏相关信息及提示。因此笔者设计窗体
行30列100.地图大小是实现⼀个棋盘26行58列的棋盘行和列可以根据自己的情况修改。 //设置控制台窗口的大小30行100列//mode为DOS命令system(mode con cols100 lines30);//设置cmd窗口名称system(title 贪吃蛇);//获取标准输出的句柄(用来标识不同设备的数值)HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, CursorInfo);//获取控制台光标信息CursorInfo.bVisible false;//隐藏控制台光标SetConsoleCursorInfo(houtput, CursorInfo);//设置控制台光标状态 void WelcomeToGame()
{//SetPos调整光标位置SetPos(40, 15);printf(欢迎来到贪吃蛇小游戏);SetPos(40, 25);system(pause);system(cls);SetPos(25, 12);printf(用↑ . ↓ . ← . → 分别控制蛇的移动 F3为加速F4为减速\n);SetPos(25, 13);printf(加速将能得到更高的分数。\n);SetPos(40, 25);system(pause);system(cls);
}
void SetPos(short x, short y)
{COORD pos { x,y };HANDLE houtput NULL;houtput GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(houtput, pos);}
在上述代码中我们使用pause指令实现程序不退出的效果即屏幕上”请按任意键退出“如果不加上这句换或者其他相似效果的语句程序直接退出我们是无法看到窗体标题的改变的同时为了实现换界面的效果我们可以在pause语句后加上cls清屏指令。
2.1.2.创建地图
创建地图就是将墙打印出来因为是宽字符打印所以使用wprintf函数打印格式串前使用L
打印地图的关键是要算好坐标才能在想要的位置打印墙体。为了方便修改如同蛇身、食物、
墙等特殊字符。笔者同一使用使用预处理指令定义。墙体定义如下
#define WALL L□
坐标设定如下上0,0到56,0 下0,26到56,26 左0,1到0,25 右56,1到56,25 创建地图函数CreateMap
void CreatMap()
{int i 0;SetPos(0,0);//宽字符一块墙体站X两个单位大小//上(0,0)-(56, 0)for(i 0;i 58; i i 2){wprintf(L%c, WALL);}SetPos(0, 26);//下(0,26)-(56, 26)for (i 0; i 58; i i 2){wprintf(L%c, WALL);}//左//x是0y从0开始增⻓for (i 0; i 26; i){SetPos(0, i);wprintf(L%c, WALL);}//x是56y从0开始增⻓for (i 0; i 26; i){SetPos(56, i);wprintf(L%c, WALL);}
}
2.1.3.蛇身创建及初始化蛇身
a.蛇身节点
在游戏运行的过程中蛇每次吃⼀个食物蛇的身体就会变长⼀节如果我们使用链表存储蛇的信息那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行然后根据坐标移动光标打印对应节点就行同样的对于食物来说也是这样我们只要创建结构体存储对应的位置信息就行了所以蛇节点食物节点结构如下
蛇身打印的宽字符 #define BODY L●
b.整条蛇及相关信息的维护
x,y就是对应一节身体的位置。这里我们还创建指向一节身体的指针方便调用。同时在本项目中与蛇有关的信息还有诸如蛇的状态撞墙、吃到自己、按ESC结束、移动速度、蛇前进的方向、得分、食物分数、等等如我们分开存放这些信息就会显得非常杂乱、不利于我们后期游戏的维护因此我们这里运用面向对象的思想创建一个结构体将这些与蛇有关的信息集中存放在一起。结构体如下
typedef struct Snake
{pSnakeNode _Psnake;//维护整条蛇的指针指向代表蛇头的节点pSnakeNode _Pfood;//维护⻝物的指针enum DIRECTION _Dir;//蛇头的方向默认是向右enum STATE _State;//游戏状态int _Score;//当前获得分数int _FoodScore;//默认每个⻝物10分int _SleepTime;//每走一步休眠时间
}snake,*psnake;
c.蛇的速度
需要特别说明的是蛇的速度的实现跟读者想象的不同如果将游戏运行我们发现蛇是走一步顿一步的其实我们并不是让蛇真的有个速度动起来我们只是让玩家觉得它动起来了蛇身移动的前进是依靠打印字符与空格实现的一系列的连续动作最终也不过是通过不断循环实现的如我们直接运行程序那么依赖打印实现的蛇身体的移动必然是迅速均衡的因此如果想实现蛇存在移动速度的感觉我们必须使得程序运行速度上存在差异因此在这里我们使用休眠Sleep指令改变程序的运行赋予蛇速度休眠时间短速度就快休眠时间长速度就慢
d.蛇的方向
方向仅用来判断用枚举存放不同方向就行
//方向
enum DIRECTION
{UP 1,DOWN,LEFT,RIGHT
};
e.蛇的状态游戏状态
状态仅用来判断用枚举存放不同状态就行
//游戏状态
enum STATE
{OK,//正常运行END_NORMAL,//撞墙KILL_BY_WALL,//咬到自己KILL_BY_SELF,//正常结束
};
d.初始化
蛇最开始长度设为5节每节对应链表的⼀个节点蛇身的每⼀个节点都有自己的坐标。
创建5个节点然后将每个节点存放在链表中进行管理。创建完蛇身后从一个固定的位置出发访问节点存储的坐标位置信息将蛇的每⼀节打印在屏幕上。
• 蛇的初始位置从 (24,5) 开始。
注意因为墙体为宽字符从0,0坐标开始打印X轴方向上一个墙体为站两个单位长度所以蛇的每个节点的x坐标必须是2个倍数否则可能会出现蛇的⼀个节点有⼀半出现在墙体中另外⼀般在墙外的现象坐标不好对齐。
再设置当前游戏的状态蛇移动的速度默认的方向初始成绩每个食物的分数。
• 游戏状态是OK
• 蛇的移动速度200毫秒
• 蛇的默认方向RIGHT
• 初始成绩0
• 每个食物的分数10
初始化蛇⾝函数InitSnake
void InitSnake(psnake ps)
{pSnakeNode cur NULL;int i 0;//创建蛇身节点并初始化坐标//头插法创建新的头插入原链表中并将新节点作为新蛇头for(i 0;i 5;i){//创建蛇身的节点cur (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL cur){perror(InitSnake:malloc());exit(1);}//设置坐标cur-next NULL;cur-x POS_X i*2;cur-y POS_Y;//头插法if (ps-_Psnake NULL){ps-_Psnake cur;}else{cur-next ps-_Psnake;ps-_Psnake cur;}}//打印蛇的身体改变光标位置在对应光标位置打印身体字符cur ps-_Psnake;while(cur){SetPos(cur-x,cur-y);wprintf(L%c,BODY);cur cur-next;}//初始化贪吃蛇数据ps-_Dir RIGHT;ps-_FoodScore 10;ps-_Score 0;ps-_SleepTime 200;ps-_State OK;
} 2.1.4.创建第⼀个食物
• 先随机生成食物的坐标食物必须生成在墙内且不能生成在蛇身上循环遍历蛇身节点根据坐标是否相等判断因为蛇头不可能吃到蛇头只需从蛇头之后开始循环即可。因此食物的节点坐标x必须在2~54内y坐标必须在1~25之内rand生成是按0~52,0~24在加上数来实现同时这个食物坐标的生成必须是随机不定的对此我们可以使用rand函数与srand函数。
• x坐标必须是2的倍数如果食物的x坐标不是2的倍数就会出现蛇一半碰到食物能一半未碰到食物但是这碰到的食物是宽字符比窄字符宽出的那部分这部分是多打印的我们蛇的节点并未存储相关坐标信息因此不好判断坐标是否相等蛇是否吃到食物因此食物X坐标必须是2的倍数
• 创建食物节点打印食物
CreateFood
void CreateFood(psnake ps)
{int x 0;int y 0;
again://产生的x坐标应该是2的倍数这样才可能和蛇头坐标对⻬do{x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);pSnakeNode cur ps-_Psnake; // 获取指向蛇头的指针//⻝物不能和蛇身冲突while(cur){if(cur-x x cur-y y){goto again;//goto语法如果⻝物和蛇身冲突则重新生成坐标}cur cur-next;}pSnakeNode pfood (pSnakeNode)malloc(sizeof(SnakeNode));//创建⻝物if (NULL pfood){perror(CreateFood:malloc());exit(1);}pfood-x x;//创键食物后在维护贪吃蛇相关信息的结构体内更新游戏信息方便维护管理pfood-y y;SetPos(x, y);wprintf(L%c, FOOD);ps-_Pfood pfood;
} 2.2.游戏运行GameRun
void GameRun(psnake ps)
{do{//打印右侧帮助信息PrintHelpInfo(ps);if (KEY_PRESS(VK_UP) ps-_Dir ! DOWN)//前进方向为上时不能向下转{ps-_Dir UP;//更新蛇的方向}else if (KEY_PRESS(VK_DOWN) ps-_Dir ! UP)//前进方向为下时不能向上转{ps-_Dir DOWN;}else if (KEY_PRESS(VK_LEFT) ps-_Dir ! RIGHT)//前进方向为左时不能向右转{ps-_Dir LEFT;}else if (KEY_PRESS(VK_RIGHT) ps-_Dir ! LEFT)//前进方向为右时不能向左转{ps-_Dir RIGHT;}else if (KEY_PRESS(VK_SPACE))//检测是否按过空格按过则游戏暂停{pause();}else if (KEY_PRESS(VK_F3))//检测是否按过F3按过则游戏加速{if (ps-_SleepTime 50){ps-_SleepTime - 30;//游戏加速就是休眠时间变短ps-_FoodScore 2;//游戏加速每个食物能获得分数上涨}}else if (KEY_PRESS(VK_F4))//检测是否按过F4按过则游戏减速{if (ps-_SleepTime 350){ps-_SleepTime 30;//游戏减速就是休眠时间变长ps-_FoodScore - 2;//游戏减速每个食物能获得分数下降}if (ps-_SleepTime 350)//蛇能减到的最低速度{ps-_FoodScore 1;}}else if (KEY_PRESS(VK_ESCAPE))//检测是否按过ESC键按过则游戏正常结束{ps-_State END_NORMAL;//更新蛇的状态break;}//蛇每次⼀定之间要休眠的时间时间短蛇移动速度就快Sleep(ps-_SleepTime);SnakeMove(ps);//蛇移动一步} while (ps-_State OK);//判断蛇的状态是否正常进行游戏
}
2.2.1帮助信息及得分打印
游戏运行期间右侧打印帮助信息提示玩家坐标开始位置(64, 15) PrintHelpInfo
void PrintHelpInfo(psnake ps)
{//打印提示信息SetPos(64, 10);printf(得分: %2d, ps-_Score);SetPos(64, 11);printf(每个食物得分: %2d, ps-_FoodScore);SetPos(64, 14);printf(游戏说明);SetPos(64,15);printf(不能穿墙不能咬到自己);SetPos(64, 16);printf(用↑ . ↓ . ← . → 分别控制蛇的移动);SetPos(64, 17);printf(F3为加速F4为减速);SetPos(64, 18);printf(ESC:退出游戏。Space:暂停游戏。);
} 2.2.2.方向、状态、速度判断按键检测
根据游戏状态检查游戏是否继续如果是状态是OK游戏继续否则游戏结束。
如果游戏继续就是根据前面介绍虚拟键值相关信息检测按键情况确定蛇下一步的方向或者是否加速减速是否暂停或者退出游戏。
需要的虚拟按键的罗列
• 上VK_UP
• 下VK_DOWN
• 左VK_LEFT
• 右VK_RIGHT
• 空格VK_SPACE
• ESCVK_ESCAPE
• F3VK_F3
• F4VK_F4
确定了蛇的方向和速度蛇就可以移动了。 if(KEY_PRESS(VK_UP) ps-_Dir ! DOWN)//前进方向为上时不能向下转{ps-_Dir UP;//更新蛇的方向}else if(KEY_PRESS(VK_DOWN) ps-_Dir ! UP)//前进方向为下时不能向上转{ps-_Dir DOWN;}else if (KEY_PRESS(VK_LEFT) ps-_Dir ! RIGHT)//前进方向为左时不能向右转{ps-_Dir LEFT;}else if (KEY_PRESS(VK_RIGHT) ps-_Dir ! LEFT)//前进方向为右时不能向左转{ps-_Dir RIGHT;}else if (KEY_PRESS(VK_SPACE))//检测是否按过空格按过则游戏暂停{pause();}else if (KEY_PRESS(VK_F3))//检测是否按过F3按过则游戏加速{if(ps-_SleepTime 50){ps-_SleepTime - 30;//游戏加速就是休眠时间变短ps-_FoodScore 2;//游戏加速每个食物能获得分数上涨}}else if (KEY_PRESS(VK_F4))//检测是否按过F4按过则游戏减速{if (ps-_SleepTime 350){ps-_SleepTime 30;//游戏减速就是休眠时间变长ps-_FoodScore - 2;//游戏减速每个食物能获得分数下降}if(ps-_SleepTime 350)//蛇能减到的最低速度{ps-_FoodScore 1;}}else if (KEY_PRESS(VK_ESCAPE))//检测是否按过ESC键按过则游戏正常结束{ps-_State END_NORMAL;//更新蛇的状态break;}a.Pause
void pause()//按一次空格游戏暂停
{while(1)//通过死循环的休眠程序达到暂停的效果{Sleep(200);if(KEY_PRESS(VK_SPACE)){break;///再按一次空格游戏继续}}
}
b.KEY_PRESS 检测按键状态笔者封装了⼀个宏
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) 1 ? 1 : 0)
2.2.3蛇身移动SnakeMove
2.2.3.1.SnakeMove:
蛇身移动是通过先创建下一个节点根据移动方向和蛇头的坐标获得下⼀个步位置上节点的坐标之后看下一个位置是否是食物NextIsFood是食物就做吃食处理EatFood将食物的节点直接作为新蛇头并销毁之前创建新节点如果不是食物则做前进⼀步的处理NoFood将创建节点作为新蛇头插入原链表循环遍历蛇身打印蛇身字符但是在原来蛇尾处应该打印两个空字符消除屏幕上原蛇尾否则蛇身会越来越长最后再释放原来的尾节点。 除此之外蛇身移动后还应该判断此次移动是否会造成撞墙KillByWall或者撞上蛇身KillBySelf从而影响游戏的状态导致游戏结束。
void SnakeMove(psnake ps)
{//创建下⼀个节点pSnakeNode cur (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL cur){perror(InitSnake:malloc());exit(1);}//确定下一个节点的坐标下一个节点的坐标根据蛇头的坐标和方向确定switch(ps-_Dir){case UP:cur-x ps-_Psnake-x;cur-y ps-_Psnake-y - 1;break;case DOWN:cur-x ps-_Psnake-x;cur-y ps-_Psnake-y 1;break;case LEFT:cur-x ps-_Psnake-x - 2;cur-y ps-_Psnake-y;break;case RIGHT:cur-x ps-_Psnake-x 2;cur-y ps-_Psnake-y;break;}//如果下一个位置就是⻝物if(NextIsFood(ps,cur)){EatFood(ps,cur);}else//如果没有⻝物{NotFood(ps, cur);}KillByWall(ps);KillBySelf(ps);
}
a.NextIsFood
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(psnake ps,pSnakeNode pn)
{return (ps-_Pfood-x pn-x ps-_Pfood-y pn-y);
}
b.EatFood
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(psnake ps, pSnakeNode pn)
{//头插法将属于食物的节点插入到原链表中作为新的蛇头ps-_Pfood-next ps-_Psnake;ps-_Psnake ps-_Pfood;pSnakeNode cur ps-_Psnake;//打印蛇while(cur){SetPos(cur-x,cur-y);wprintf(L%c,BODY);cur cur-next;}ps-_Score ps-_FoodScore;free(pn);pn NULL;CreateFood(ps);//吃掉食物后再创建一个食物
}
c.NotFood
将下⼀个节点头插入蛇的身体并将之前蛇身最后⼀个节点打印为空格释放掉蛇身的最后⼀个节点
需要注意的是释放最后⼀个结点后还得将指向在最后⼀个结点的指针改为NULL保证蛇尾打印可以正常结束不会越界访问。
//pSnakeNode pn 是下一节点的地址
//pSnake ps 维护蛇的指针
void NotFood(psnake ps, pSnakeNode pn)
{//头插法将创建的节点插入到原链表中作为新蛇头 pn-next ps-_Psnake;ps-_Psnake pn;pSnakeNode cur ps-_Psnake;//打印蛇while(cur-next-next){SetPos(cur-x, cur-y);wprintf(L%c, BODY);cur cur-next;}//最后⼀个位置打印空格然后释放节点,将最后节点的next置为NULL否则会越界访问SetPos(cur-next-x, cur-next-y);printf( );free(cur-next);cur-next NULL;
}
d.KillByWall
判断蛇头的坐标是否和墙的坐标冲突
//pSnake ps 维护蛇的指针
void KillByWall(psnake ps)
{if(ps-_Psnake-x 0 || ps-_Psnake-x 56 || ps-_Psnake-y 0 || ps-_Psnake-y 25){ps-_State KILL_BY_WALL;}
}
e.KillBySelf
判断蛇头的坐标是否和蛇身体的坐标冲突
//pSnake ps 维护蛇的指针
void KillBySelf(psnake ps)
{pSnakeNode cur ps-_Psnake-next;while(cur){if((ps-_Psnake-x cur-x) (ps-_Psnake-y cur-y)){ps-_State KILL_BY_SELF;return;}cur cur-next;}
}
2.3游戏结束 游戏状态不再是OK游戏继续的时候要告知游戏结束的原因并且释放蛇身节点。
void GameEnd(psnake ps)
{pSnakeNode cur ps-_Psnake;SetPos(24, 12);switch(ps-_State){case END_NORMAL:printf(您退出了游戏游戏结束);break;case KILL_BY_SELF:printf(您吃到了自己游戏结束);break;case KILL_BY_WALL:printf(您撞到了墙游戏结束);break;}//释放蛇身的节点while (cur){pSnakeNode next cur-next;free(cur);cur next;}}2.4重来一局相关问题
正常一场游戏不管怎么样一运行必然是直接开始游戏因此笔者这里采用do..while结构来实现这个效果而游戏结束后程序应该询问玩家是否再玩一局这时以输入Y/或N为重开的标志考虑到玩家可能出于手误或其他原因出现;”大小写不分“、”多个输入“等多种问题scanf使用不安全因此笔者这里采用getchar()来接受玩家的输入值同时判断重来的条件也大小写都可以。
但是需要注意的是getchar函数是自动的从输入缓冲区读取字符的因此假设当上一句我们输入Y
我们是会回车键确定输入的但是回车键按过的同时输入缓冲区是会存入一个\n因此下一句getchar()是不会等玩家输入的它会直接读取‘\n’,同时又相当于按了回车键确定输入因此游戏会出现直接结束的Bug.
对此我们通过while (getchar() ! \n);循环读取来清空缓冲区的输入就可避免这个问题同属由于getchar一次只读一个字符我们还解决了一次输入多个字符可能造成的问题。
最后我们在循环开头加上清屏指令清楚可能的残留。
void test()
{int ch 0;srand((unsigned int)time(NULL));//通过rand产生随机坐标来产生随机食物的效果do{system(cls);snake sk { 0 };GameStart(sk);GameRun(sk);GameEnd(sk);SetPos(25, 15);//使询问是否重来的句子位置美观一点printf(您是否再来一局(Y/N):);ch getchar();while (getchar() ! \n);} while (ch Y|| ch y);
}
六参考代码
1.snake.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#define WALL L□
#define BODY L■
#define FOOD L★//蛇的初始位置#define POS_X 24
#define POS_Y 5
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) 1 ? 1 : 0)
#includestdio.h
#includestdlib.h
#includewindows.h
#includelocale.h
#includestdbool.h
#includetime.h//方向
enum DIRECTION
{UP 1,DOWN,LEFT,RIGHT
};//游戏状态
enum STATE
{OK,//正常运行END_NORMAL,//撞墙KILL_BY_WALL,//咬到自己KILL_BY_SELF,//正常结束
};//蛇身节点typedef struct SnakeNode
{short x;short y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;typedef struct Snake
{pSnakeNode _Psnake;//维护整条蛇的指针pSnakeNode _Pfood;//维护⻝物的指针enum DIRECTION _Dir;//蛇头的方向默认是向右enum STATE _State;//游戏状态int _Score;//当前获得分数int _FoodScore;//默认每个⻝物10分int _SleepTime;//每走一步休眠时间
}snake,*psnake;//设置光标的坐标void SetPos(short x, short y);//游戏初始化void GameStart(psnake ps);//欢迎界面void WelcomeToGame();//创建地图void CreatMap();//创建⻝物void CreateFood(psnake ps);//初始化蛇void InitSnake(psnake ps);//游戏运行过程void GameRun(psnake ps);//打印帮助信息void PrintHelpInfo(psnake ps);//蛇的移动void SnakeMove(psnake ps);//下一个节点是⻝物int NextIsFood(psnake ps, pSnakeNode pn);//吃⻝物void EatFood(psnake ps, pSnakeNode pn);//不吃⻝物void NotFood(psnake ps, pSnakeNode pn);//暂停响应void pause();//撞墙检测void KillByWall(psnake ps);//撞自身检测void KillBySelf(psnake ps);//游戏结束void GameEnd(psnake ps);2.snake.c
#includesnake.h//设置光标的坐标void SetPos(short x, short y)
{COORD pos { x,y };HANDLE houtput NULL;//获取标准输出的句柄(用来标识不同设备的数值)houtput GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(houtput, pos);}void GameStart(psnake ps)
{WelcomeToGame();//设置窗口名称大小、隐藏光标打印欢迎界面CreatMap();//打印欢迎界面InitSnake(ps);//初始化蛇及其相关信息CreateFood(ps);//创造第一个食物}void GameRun(psnake ps)
{do{//打印右侧帮助信息PrintHelpInfo(ps);if (KEY_PRESS(VK_UP) ps-_Dir ! DOWN)//前进方向为上时不能向下转{ps-_Dir UP;//更新蛇的方向}else if (KEY_PRESS(VK_DOWN) ps-_Dir ! UP)//前进方向为下时不能向上转{ps-_Dir DOWN;}else if (KEY_PRESS(VK_LEFT) ps-_Dir ! RIGHT)//前进方向为左时不能向右转{ps-_Dir LEFT;}else if (KEY_PRESS(VK_RIGHT) ps-_Dir ! LEFT)//前进方向为右时不能向左转{ps-_Dir RIGHT;}else if (KEY_PRESS(VK_SPACE))//检测是否按过空格按过则游戏暂停{pause();}else if (KEY_PRESS(VK_F3))//检测是否按过F3按过则游戏加速{if (ps-_SleepTime 50){ps-_SleepTime - 30;//游戏加速就是休眠时间变短ps-_FoodScore 2;//游戏加速每个食物能获得分数上涨}}else if (KEY_PRESS(VK_F4))//检测是否按过F4按过则游戏减速{if (ps-_SleepTime 350){ps-_SleepTime 30;//游戏减速就是休眠时间变长ps-_FoodScore - 2;//游戏减速每个食物能获得分数下降}if (ps-_SleepTime 350)//蛇能减到的最低速度{ps-_FoodScore 1;}}else if (KEY_PRESS(VK_ESCAPE))//检测是否按过ESC键按过则游戏正常结束{ps-_State END_NORMAL;//更新蛇的状态break;}//蛇每次⼀定之间要休眠的时间时间短蛇移动速度就快Sleep(ps-_SleepTime);SnakeMove(ps);//蛇移动一步} while (ps-_State OK);//判断蛇的状态是否正常进行游戏
}void GameEnd(psnake ps)
{pSnakeNode cur ps-_Psnake;SetPos(24, 12);switch(ps-_State){case END_NORMAL:printf(您退出了游戏游戏结束);break;case KILL_BY_SELF:printf(您吃到了自己游戏结束);break;case KILL_BY_WALL:printf(您撞到了墙游戏结束);break;}//释放蛇身的节点while (cur){pSnakeNode next cur-next;free(cur);cur next;}}void SnakeMove(psnake ps)
{//创建下⼀个节点pSnakeNode cur (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL cur){perror(InitSnake:malloc());exit(1);}//确定下一个节点的坐标下一个节点的坐标根据蛇头的坐标和方向确定switch(ps-_Dir){case UP:cur-x ps-_Psnake-x;cur-y ps-_Psnake-y - 1;break;case DOWN:cur-x ps-_Psnake-x;cur-y ps-_Psnake-y 1;break;case LEFT:cur-x ps-_Psnake-x - 2;cur-y ps-_Psnake-y;break;case RIGHT:cur-x ps-_Psnake-x 2;cur-y ps-_Psnake-y;break;}//如果下一个位置就是⻝物if(NextIsFood(ps,cur)){EatFood(ps,cur);}else//如果没有⻝物{NotFood(ps, cur);}KillByWall(ps);KillBySelf(ps);
}//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(psnake ps,pSnakeNode pn)
{return (ps-_Pfood-x pn-x ps-_Pfood-y pn-y);
}//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(psnake ps, pSnakeNode pn)
{//头插法将属于食物的节点插入到原链表中作为新的蛇头ps-_Pfood-next ps-_Psnake;ps-_Psnake ps-_Pfood;pSnakeNode cur ps-_Psnake;//打印蛇while(cur){SetPos(cur-x,cur-y);wprintf(L%c,BODY);cur cur-next;}ps-_Score ps-_FoodScore;free(pn);pn NULL;CreateFood(ps);//吃掉食物后再创建一个食物
}//pSnakeNode pn 是下一节点的地址
//pSnake ps 维护蛇的指针
void NotFood(psnake ps, pSnakeNode pn)
{//头插法将创建的节点插入到原链表中作为新蛇头 pn-next ps-_Psnake;ps-_Psnake pn;pSnakeNode cur ps-_Psnake;//打印蛇while(cur-next-next){SetPos(cur-x, cur-y);wprintf(L%c, BODY);cur cur-next;}//最后⼀个位置打印空格然后释放节点SetPos(cur-next-x, cur-next-y);printf( );free(cur-next);cur-next NULL;
}void WelcomeToGame()
{//设置控制台窗口的大小30行100列//mode为DOS命令system(mode con cols100 lines30);//设置cmd窗口名称system(title 贪吃蛇);//获取标准输出的句柄(用来标识不同设备的数值)HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, CursorInfo);//获取控制台光标信息CursorInfo.bVisible false;//隐藏控制台光标SetConsoleCursorInfo(houtput, CursorInfo);//设置控制台光标状态//SetPos调整光标位置SetPos(40, 15);printf(欢迎来到贪吃蛇小游戏);SetPos(40, 25);//让按任意键继续的出现的位置好看点system(pause);system(cls);SetPos(25, 12);printf(用↑ . ↓ . ← . → 分别控制蛇的移动 F3为加速F4为减速\n);SetPos(25, 13);printf(加速将能得到更高的分数。\n);SetPos(40, 25);//让按任意键继续的出现的位置好看点system(pause);system(cls);
}void CreatMap()
{int i 0;SetPos(0,0);//宽字符一块墙体站X两个单位大小//上(0,0)-(56, 0)for(i 0;i 58; i i 2){wprintf(L%c, WALL);}SetPos(0, 26);//下(0,26)-(56, 26)for (i 0; i 58; i i 2){wprintf(L%c, WALL);}//左//x是0y从0开始增⻓for (i 0; i 26; i){SetPos(0, i);wprintf(L%c, WALL);}//x是56y从0开始增⻓for (i 0; i 26; i){SetPos(56, i);wprintf(L%c, WALL);}
}void InitSnake(psnake ps)
{pSnakeNode cur NULL;int i 0;//创建蛇身节点并初始化坐标//头插法创建新的头插入原链表中并将新节点作为新蛇头for(i 0;i 5;i){//创建蛇身的节点cur (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL cur){perror(InitSnake:malloc());exit(1);}//设置坐标cur-next NULL;cur-x POS_X i*2;cur-y POS_Y;//头插法if (ps-_Psnake NULL){ps-_Psnake cur;}else{cur-next ps-_Psnake;ps-_Psnake cur;}}//打印蛇的身体改变光标位置在对应光标位置打印身体字符cur ps-_Psnake;while(cur){SetPos(cur-x,cur-y);wprintf(L%c,BODY);cur cur-next;}//初始化贪吃蛇数据ps-_Dir RIGHT;ps-_FoodScore 10;ps-_Score 0;ps-_SleepTime 200;ps-_State OK;
}void CreateFood(psnake ps)
{int x 0;int y 0;
again://产生的x坐标应该是2的倍数这样才可能和蛇头坐标对⻬do{x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);pSnakeNode cur ps-_Psnake; // 获取指向蛇头的指针//⻝物不能和蛇身冲突while(cur){if(cur-x x cur-y y){goto again;}cur cur-next;}pSnakeNode pfood (pSnakeNode)malloc(sizeof(SnakeNode));//创建⻝物if (NULL pfood){perror(CreateFood:malloc());exit(1);}pfood-x x;pfood-y y;SetPos(x, y);wprintf(L%c, FOOD);ps-_Pfood pfood;
}void PrintHelpInfo(psnake ps)
{//打印提示信息SetPos(64, 10);printf(得分: %2d, ps-_Score);SetPos(64, 11);printf(每个食物得分: %2d, ps-_FoodScore);SetPos(64, 14);printf(游戏说明);SetPos(64,15);printf(不能穿墙不能咬到自己);SetPos(64, 16);printf(用↑ . ↓ . ← . → 分别控制蛇的移动);SetPos(64, 17);printf(F3为加速F4为减速);SetPos(64, 18);printf(ESC:退出游戏。Space:暂停游戏。);
}void pause()//按一次空格游戏暂停
{while(1)//通过死循环的休眠程序达到暂停的效果{Sleep(200);if(KEY_PRESS(VK_SPACE)){break;///再按一次空格游戏继续}}
}
//pSnake ps 维护蛇的指针
void KillByWall(psnake ps)
{if(ps-_Psnake-x 0 || ps-_Psnake-x 56 || ps-_Psnake-y 0 || ps-_Psnake-y 25){ps-_State KILL_BY_WALL;}
}//pSnake ps 维护蛇的指针
void KillBySelf(psnake ps)
{pSnakeNode cur ps-_Psnake-next;while(cur){if((ps-_Psnake-x cur-x) (ps-_Psnake-y cur-y)){ps-_State KILL_BY_SELF;return;}cur cur-next;}
}
3.test.c
#includesnake.hvoid test()
{int ch 0;srand((unsigned int)time(NULL));//通过rand产生随机坐标来产生随机食物的效果do{system(cls);snake sk { 0 };GameStart(sk);GameRun(sk);GameEnd(sk);SetPos(25, 15);printf(您是否再来一局(Y/N):);ch getchar();while (getchar() ! \n);} while (ch Y|| ch y);
}int main()
{setlocale(LC_ALL, );//修改当前地区为本地模式为了支持中文宽字符及特殊字符的打印test();//测试逻辑SetPos(0, 27);//将光标移动至地图下方不会破坏地图return 0;
} 七附录参考汉字字符集编码查询;中文字符集编码:GB2312、BIG5、GBK、GB18030、Unicode