赞
踩
我们要用C语言来实现贪吃蛇游戏之前,得了解C语言函数、枚举、结构体、动态内存管理、预处理指令、链表和Win32 API等等的一些相关知识。
关于链表和函数,在之前的文章中都有写过了,友友们可以自行查看。剩下知识会在之后的博客中所提及,请大家多多关注。
这里主要介绍Win32 API,及如何一步一步实现贪吃蛇游戏。
下面介绍的结构体和函数在Win32 API 都是现成的,我们就了解一下如何使用就行。
Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是⼀个很大的服务中心,调⽤这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应用程序达到开启视窗、描绘图形、使⽤周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以便称之称ApplicationProgrammingInterface,简称API函数。WIN32API也就是MicrosoftWindows32位平台的应用程序编程接口。
不知道大家知不知道cmd的程序。
平常我们运行起来的黑框程序其实就是控制台程序
来看看它是什么样的。
我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小。像30行,100列,这些都是可以的。
也可以调用C语言函数system来执行不过在使用时要包含#include <windows.h>
。
system("mode con cols=20 lines=20");
我们也可以通过命令设置控制台窗⼝的名字:
system("title 贪吃蛇");
看看效果
COORD是WindowsAPI中定义的一个结构体,表示一个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0) 的原点位于缓冲区的顶部左侧单元格。
就是所示这样
GetStdHandle是一个WindowsAPI函数。它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。
就是GetStdHandle来获得某一种设备的控制权限。
HANDLE GetStdHandle(DWORD nStdHandle);
举个例子:如果我们想获得输出程序的句柄
那我们得调用GetStdHandle这个函数。
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE)
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向CONSOLE_CURSOR_INFO结构的指针,该结构接收有关主机游标(光标)的信息。
这个结构体,包含有关控制台光标的信息。
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
bVisible = false就是为了隐藏控制台光标。
CursorInfo.bVisible = false;
int main()
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(handle, &cursor_info);
cursor_info.dwSize = 100;
cursor_info.bVisible = true;
SetConsoleCursorInfo(handle, &cursor_info);
return 0;
}
把dwSize试着设置为dwSize = 100,把bVisible设置我bVisible = true。我们来看看效果:
设置指定控制台屏幕缓冲区的光标的大小和可见性。
就是在程序运行起来的时候,这个光标是有长度和宽度的。而这些属性是可以设置的,就是放在
CONSOLE_CURSOR_INFO结构体变量中的。
int main()
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(handle, &cursor_info);
return 0;
}
就是把控制台里面的光标信息放在 cursor_info放在里面,所以GetConsoleCursorInfo(handle, &cursor_info),里就传了地址。
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
我们来看一个示例:
假设把光标定位到pos = { 5, 20 },输入1后,打印是在下一行打印。
int main()
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { 5, 20 };
SetConsoleCursorPosition(handle, pos);
int ch = getchar();
putchar(ch);
return 0;
}
用上面这种方式设置一个坐标比较麻烦,那我们直接封装一个函数来确定一个坐标。
代码来实现一下
void SetPos(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x, y };
SetConsoleCursorPosition(handle, pos);
}
那以后需要设置坐标直接调用就行。
我们来试一下在(10,10)处打印一个hi
#include <stdio.h> #include <stdlib.h> #include <windows.h> #include <stdbool.h> void SetPos(int x, int y) { HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { x, y }; SetConsoleCursorPosition(handle, pos); } int main() { SetPos(10, 10); printf("hi\n"); return 0; }
显然是可以的。
因为我们需要知道玩家在键盘上按键,那我们怎么知道呢?
就是用GetAsyncKeyState获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState(
int vKey
);
这个函数就一个参数,这个参数指的是键盘上每一个键的虚拟值。
在Win32 API中给键盘上的每一个键都编了号。
我们来看看一部分:
这个函数将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState的返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1。
那我们如何检测呢?
不管这个数字是几,只要按位与1,结果得到1的话,说明被按过,返回的是0,说明没有被按过。
为了方便,我们封装一个PRESS_KET 来检测vk这虚拟键值对应的按键是否被按过
如果按过返回1,未按过返回0
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&0x1) ? 1:0)
我们最终的贪吃蛇大概要是这个样子,那我们的地图如何布置呢?
这里不得不讲一下控制台窗口的一些知识,如果想在控制台的窗口中指定位置输出信息,我们得知道该位置的坐标,所以首先介绍⼀下控制台窗口的坐标知识。
控制台窗口的坐标如图所示,横向的是X轴,从左向右依次增长,纵向是Y轴,从上到下依次增长。
在游戏地图上,我们打印墙体使用宽字符:□,打印蛇使用宽字符●,打印食物使用宽字符★
普通的字符是占一个字节的,这类宽字符是占用2个字节。
这里再简单的讲一下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。
C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入了宽字符的类型wchar_t
和宽字符的输入和输出函数,加入了<locale.h>
头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。
<locale.h>
本地化<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。
就像货币的格式:
在标准中,依赖地区的部分有以下几项:
通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下面的一个个宏,指定一个类项:
LC_COLLATE:影响字符串比较较函数strcoll()
和strxfrm()
。
LC_CTYPE:影响字符处理函数的⾏为。
LC_MONETARY:影响货币格式。
LC_NUMERIC:影响printf()
的数字格式。
LC_TIME:影响时间格式strftime()
和wcsftime()
。
LC_ALL-针对所有类项修改,将以上所有类别设置为给定的语⾔环境。
那如果想在屏幕上打印宽字符,怎么打印呢?
宽字符的字面量必须加上前缀“L”,否则C语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面,表示宽字符,对应wprintf()
的占位符为 %lc
;在双引号前面,表示宽字符串,wprintf()
的占位符为%ls
。
我们用代码来看看:
#include <locale.h> #include <stdio.h> #include <windows.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; }
而我们想得到这样的:
我们假设实现⼀个棋盘27行,58列的棋盘(行和列可以根据自己的情况修改),再围绕地图画出墙,
如下:
初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24,5)处开始出现蛇,连续5个节点。
注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的一个节点有一半出现在墙体中,另外⼀半在墙外的现象,坐标不好对齐。
关于食物,就是在墙体内随机生成一个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。
在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,所以蛇节点结构如下:
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
而我们要管理整条贪吃蛇,我们再封装一个Snake的结构来维护整条贪吃蛇:
typedef struct Snake
{
pSnakeNode _pSnake;//指向贪吃蛇头结点的指针
pSnakeNode _pFood;//指向食物结点的指针
int _Score;//贪吃蛇累计的总分
int _FoodWeight;//一个食物的分数
int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢
enum DIRECTION _Dir;//描述蛇的方向
enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
蛇就只有四个方向,向上,向下,向左和向右,,可以一一列举出来,所以使用枚举来描述蛇前进方向。
代码实现:
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
就像我们玩的一些游戏一样,要能够知道游戏运行的状态,像正常运行、撞到墙、蛇撞到自己和正常游戏结束,我们同样可以一一例举出来,也使用枚举来描述。
来看看代码实现:
enum GAME_STATUS
{
OK,//正常运行
END_NORMAL,//按ESC退出
KILL_BY_WALL,//撞到自己
KILL_BY_SELF//正常结束
};
同样设置三个文件,一个test.c用来测试代码,一个snake.h用来放函数声明,最后一个snake.c用来放相关函数的实现。
主逻辑分为3个过程:
在snake.c中来开始对游戏初始化:
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 WelComeToGame()
{
SetPos(40, 14);
printf("欢迎来到贪吃蛇小游戏");
SetPos(40, 25);
system("pause");//pause是暂停
system("cls");
SetPos(20, 14);
printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");
SetPos(40, 25);
system("pause");
system("cls");
}
当做好这些后,就要创建地图了。
创建地图就是将墙打印出来,因为是宽字符打印,所有使用wprintf
函数,打印格式串前使用L"%c"
打印地图的关键是要算好坐标,才能在想要的位置打印墙体。
先在屏幕上打印上和下,它们相差的是两个字符。
而左和右的墙,在循环时,就只相差1就行。
void CreateMap() { //上 SetPos(0, 0); int i = 0; for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //左 for (i = 1; i <= 25; i++) { SetPos(0, i); wprintf(L"%lc", WALL); } //右 for (i = 1; i <= 25; i++) { SetPos(56, i); wprintf(L"%lc", WALL); } }
最终实现的时候是这样的。
蛇最开始长度为5节,每节对应链表的一个节点,蛇身的每一个节点都有自己的坐标。
创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇身体后,将蛇的每一节打印在屏幕上。
在创建蛇身时,我们使用头插,将蛇的身体节点一个一个插入,但最终还要返回蛇头节点,方便之后进行操作。
void InitSnake(pSnake ps) { pSnakeNode cur = NULL; int i = 0; for (i = 0; i < 5; i++) { cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake()::malloc()"); return; } cur->x = POS_X + 2 * i; cur->y = POS_Y; cur->next = NULL; //头插法 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"%lc", BODY); cur = cur->next; } ps->_Status = OK; ps->_Score = 0; ps->_pFood = NULL; ps->_SleepTime = 200; ps->_FoodWeight = 10; ps->_Dir = RIGHT; }
要生成食物,先随机生成食物的坐标,而x坐标必须是2的倍数,而且食物物的坐标不能和蛇身体每个节点的坐标重复,也不能和墙的坐标重复。
void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0);//x坐标必须是2的倍数 //坐标不能和蛇的身体冲突 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 (pFood == NULL) { perror("CreateFood()::malloc()"); return; } pFood->x = x; pFood->y = y; ps->_pFood = pFood; //打印食物 SetPos(x, y); wprintf(L"%lc", FOOD); }
游戏运行期间,右侧打印帮助信息,提示玩家:
根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。
如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。
void GameRun(pSnake ps) { PrintHelpInfo(); do { SetPos(64, 10); printf("得分:%05d", ps->_Score); SetPos(64, 11); printf("每个食物的分数:%2d", ps->_FoodWeight); 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_ESCAPE)) { ps->_Status = END_NORMAL; break; } else if (KEY_PRESS(VK_SPACE)) { Pause(); } else if (KEY_PRESS(VK_F3))//加速 { if (ps->_SleepTime >= 80) { ps->_SleepTime -= 30; ps->_FoodWeight += 2; } } else if (KEY_PRESS(VK_F4))//减速 { if (ps->_SleepTime < 320) { ps->_SleepTime += 30; ps->_FoodWeight -= 2; } } Sleep(ps->_SleepTime); SnakeMove(ps); } while (ps->_Status == OK); }
先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。
确定了下一个位置后,需要看下一个位置是否是食物(NextIsFood),是食物就做吃食物物处理(EatFood),如果不是食物则做前进一步的处理(NoFood)。
蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。
我们来看看代码实现:
void SnakeMove(pSnake ps) { pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("SnakeMove()::malloc()"); return; } pNext->next = NULL; switch (ps->_Dir) { case UP: 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 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->_FoodWeight; 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; }
需要判断蛇头的坐标是否和墙的坐标冲突,如果冲突,那游戏就结束,不冲突,就继续前进。
就只需要判断蛇头节点的x是不是0或者56,或者是蛇头节点的y是不是0或26,就行。
相关代码实现:
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;
}
需要判断蛇头节点的坐标是不是与身体的坐标重合,重合则返回的状态为KILL_BY_SEL。
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;
}
}
游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,是主动退出,还是撞到墙了,还是撞到蛇自己了,最后要释放蛇身节点。
释放节点时,采用头删的方式,不要忘记把头节点置为NULL。
void GameEnd(pSnake ps) { SetPos(20, 12); switch (ps->_Status) { case END_NORMAL: printf("您主动退出游戏\n"); break; case KILL_BY_SELF: printf("自杀了,游戏结束\n"); break; case KILL_BY_WALL: printf("撞墙了,游戏结束\n"); break; } //释放蛇身的结点 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps->_pSnake = NULL; }
#include <locale.h> #include <stdlib.h> #include <windows.h> #include <stdbool.h> #include <stdio.h> #include <time.h> #define WALL L'□' #define BODY L'●' #define FOOD L'★' #define POS_X 24 #define POS_Y 5 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ) enum DIRECTION { UP = 1, DOWN, LEFT, RIGHT }; enum GAME_STATUS { OK,//正常运行 END_NORMAL,//按ESC退出 KILL_BY_WALL, KILL_BY_SELF }; //贪吃蛇结点的描述 typedef struct SnakeNode { //坐标 int x; int y; struct SnakeNode* next; }SnakeNode, * pSnakeNode; // //贪吃蛇的结构 // typedef struct Snake { pSnakeNode _pSnake;//指向贪吃蛇头结点的指针 pSnakeNode _pFood;//指向食物结点的指针 int _Score;//贪吃蛇累计的总分 int _FoodWeight;//一个食物的分数 int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢 enum DIRECTION _Dir;//描述蛇的方向 enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己 }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 KillByWall(pSnake ps); //蛇是否自杀 void KillBySelf(pSnake ps); //游戏结束后的善后处理 void GameEnd(pSnake ps);
#include "snake.h" //设置光标的坐标 void SetPos(short x, short y) { COORD pos = { x, y }; HANDLE hOutput = NULL; //获取标准输出的句柄(用来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos); } void WelComeToGame() { //定位光标 SetPos(40, 14); printf("欢迎来到贪吃蛇小游戏"); SetPos(40, 25); system("pause");//pause是暂停 system("cls"); SetPos(20, 14); printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速"); SetPos(40, 25); system("pause"); system("cls"); } //void CreateMap() //{ // //上 // SetPos(0, 0); // int i = 0; // for (i = 0; i <= 56; i += 2) // { // wprintf(L"%lc", WALL); // } // //下 // SetPos(0, 26); // for (i = 0; i <= 56; i += 2) // { // wprintf(L"%lc", WALL); // } // //左 // for (i = 1; i <= 25; i++) // { // SetPos(0, i); // wprintf(L"%lc", WALL); // } // //右 // for (i = 1; i <= 25; i++) // { // SetPos(56, i); // wprintf(L"%lc", WALL); // } //} void CreateMap() { //上 SetPos(0, 0); int i = 0; for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //下 SetPos(0, 26); for (i = 0; i <= 56; i += 2) { wprintf(L"%lc", WALL); } //左 for (i = 1; i <= 25; i++) { SetPos(0, i); wprintf(L"%lc", WALL); } //右 for (i = 1; i <= 25; i++) { SetPos(56, i); wprintf(L"%lc", WALL); } } void InitSnake(pSnake ps) { pSnakeNode cur = NULL; int i = 0; for (i = 0; i < 5; i++) { cur = (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur == NULL) { perror("InitSnake()::malloc()"); return; } cur->x = POS_X + 2 * i; cur->y = POS_Y; cur->next = NULL; //头插法 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"%lc", BODY); cur = cur->next; } ps->_Status = OK; ps->_Score = 0; ps->_pFood = NULL; ps->_SleepTime = 200; ps->_FoodWeight = 10; ps->_Dir = RIGHT; } void CreateFood(pSnake ps) { int x = 0; int y = 0; again: do { x = rand() % 53 + 2; y = rand() % 25 + 1; } while (x % 2 != 0);//x坐标必须是2的倍数 //坐标不能和蛇的身体冲突 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 (pFood == NULL) { perror("CreateFood()::malloc()"); return; } pFood->x = x; pFood->y = y; ps->_pFood = pFood; //打印食物 SetPos(x, y); wprintf(L"%lc", FOOD); } 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-退出, 空格-暂停游戏"); SetPos(64, 20); printf("zxctsclrjjjcph@版权"); } void Pause() { while (1) { Sleep(100); if (KEY_PRESS(VK_SPACE)) { break; } } } int NextIsFood(pSnake ps, pSnakeNode pnext) { if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y) { return 1; } else { return 0; } } //吃掉食物 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->_FoodWeight; 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; switch (ps->_Dir) { case UP: 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, 10); printf("得分:%05d", ps->_Score); SetPos(64, 11); printf("每个食物的分数:%2d", ps->_FoodWeight); 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_ESCAPE)) { ps->_Status = END_NORMAL; break; } else if (KEY_PRESS(VK_SPACE)) { Pause(); } else if (KEY_PRESS(VK_F3))//加速 { if (ps->_SleepTime >= 80) { ps->_SleepTime -= 30; ps->_FoodWeight += 2; } } else if (KEY_PRESS(VK_F4))//减速 { if (ps->_SleepTime < 320) { ps->_SleepTime += 30; ps->_FoodWeight -= 2; } } Sleep(ps->_SleepTime); SnakeMove(ps); } while (ps->_Status == OK); } void GameEnd(pSnake ps) { SetPos(20, 12); switch (ps->_Status) { case END_NORMAL: printf("您主动退出游戏\n"); break; case KILL_BY_SELF: printf("自杀了,游戏结束\n"); break; case KILL_BY_WALL: printf("撞墙了,游戏结束\n"); break; } //释放蛇身的结点 pSnakeNode cur = ps->_pSnake; while (cur) { pSnakeNode del = cur; cur = cur->next; free(del); } ps->_pSnake = NULL; }
#define _CRT_SECURE_NO_WARNINGS 1 #include "snake.h" void test() { Snake snake = { 0 };//创建了贪吃蛇 //1. 游戏开始 - 初始化游戏 GameStart(&snake); //2. 游戏运行 - 游戏的正常运行过程 //GameRun(&snake); //3. 游戏结束 - 游戏善后(释放资源) //GameEnd(&snake); int ch = 0; do { Snake snake = { 0 };//创建了贪吃蛇 //1. 游戏开始 - 初始化游戏 GameStart(&snake); //2. 游戏运行 - 游戏的正常运行过程 GameRun(&snake); //3. 游戏结束 - 游戏善后(释放资源) GameEnd(&snake); SetPos(20, 18); printf("再来一局吗?(Y/N):"); ch = getchar(); getchar();// 清理掉\n } while (ch == 'Y' || ch == 'y'); SetPos(0, 27); } int main() { //设置程序适应本地环境 setlocale(LC_ALL, ""); srand((unsigned int)time(NULL)); test(); return 0; }
有错误欢迎指出,大家一起进步。
如有转载请标注。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。