目录
- 一 、知识铺垫
- 1.win32API介绍
- 二、贪吃蛇的数据结构的设计
- 1.整体框架
- 2.初始化界面
- 3.贪吃蛇的运行
- 4.游戏的退出
- 三、整体代码
一 、知识铺垫
贪吃蛇涉及的知识:C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、win32API等
1.win32API介绍
Windows除了协调应用程序、分配内存、管理资源之外,同时也是一个很大的服务中心,(每一种服务就是一个函数),可以磅应用程序达到开启视窗,描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序,所以便称之为API,的应用程序编程接口。
C语言的system函数可以达到执行系统操作。
GetStdHandle是一个Windows API函数,它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用于标识不同设备的数值),使用这个句柄可以操作设备。
意思就是要操作这个设备,需要获取这个的操作权限,GetStdHandle就是起这个作用
CONSOLE_CURSOR_INFO:这个结构体,包含有关控制光标的信息
dwSize,由光标填充的字符单元格的百分比。由1到100之间。光标外观会变化,返回从完全填充单元格到单元底部的水平线条。
bVisible,游标的可见性。如果光标可见,则此成员为TRUE。
说这么多大家可能对函数GetStdHandle
有点懵,接下来我将用代码演示,让大家理解加深。
代码演示:
int main()
{
//先获取信息才能修改
//COORD pos = { 40,10 };//设置了一个坐标
CONSOLE_CURSOR_INFO cursor_info = { 0 };//创建一个变量
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleCursorInfo(handle, &cursor_info);//获取
//cursor_info.dwSize = 0;//将光标信息改掉,设置控制台信息
cursor_info.bVisible = false;//默认为true,将可见信息改为false
SetConsoleCursorInfo(handle, &cursor_info);//将光标信息设置进去,利用这个函数
return 0;
}
运行结果:
可以看见在运行界面上,没有光标了,对比一下有光标的默认界面:
可以看见有光标的界面很影响游戏体验,所以,在运行游戏之前需要把光标去掉。把结构体cursor_info
的一个成员bVisible
设置为false
。
接下来介绍识别键盘按键的函数:
GetAsyncKeyState
将键盘上每一个键的虚拟键值传递给函数,函数通过返回值分辨键盘的状态。
GetAsyncKeyState返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明键盘的状态是抬起,如果最低位被置为1则说明,该键被安国,否则为0.
意思就是:我们可以通过这个函数来检测键盘上的键是否被按过时,这个函数会返回一个shor类型的值,而short类型的值占十六个比特位,
00000000 00000000
最高位如果是1,则当前的状态是按下 1-按下
最高位如果是0,则当前的状态是抬起 0-抬起
最低位如果置为1,则说明按过 1-按过
最低位如果置为0,则说明没有按过 0-没有按过
对于GetAsyncKeyState
这个函数来说是识别键盘按键的,但是如何识别呢,按照上面的叙述我们先不管高位,如果低位是1就表示按过,如果低位是0就表示未按过,那我们可以将这个这个函数&1,如果是按过最后得到的结果就是1,因为不管高位是多少&0都是0,所以只用考虑最低位,如果最后结果是0就表示未按过
接下来我们用代码测试一下:
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1? 1:0)
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");
}
}
注意:这里运行界面上出现的12345并不是scanf
函数输入上去的,而是我们通过识别键盘按钮打印在屏幕上面的。
接下来介绍宽字符概念
宽字符
普通字符占一个字节,但是宽字符占两个字节,再简单一点讲一下,C语言的国际化特性相关的知识,过去C语言并不适合非英语国家使用,C语言最初假定字符都是单字节的,但是这些假定并不是再世界上任何地方都适用。
我们将宽字符和窄字符对比一下:
可以看到,上面的是两个窄字符,下面是一个宽字符,可以看出两个窄字符等于一个宽字符的
C语言默认采用ASCII编码的,ASCII字符采用的是单字节编码,且只使用了单字节的七位。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。
<locale.h>提供的函数用于控制c标准库中对于不同的地区会产生不一样行为的部分。
在标准中,依赖地区的部分有以下几项:
数字量的格式
货币量的格式
字符集
日期和时间的表示形式
类项:
通过修改地区,程序可以改变它的行为来适应世界的不同区域,但地区的改变可能影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下面的一个宏,指定一个类项:
LC_COLLATE:影响字符串比较函数等等……………
Setlocale函数
用于修改当前地区,可以针对一个类项进行修改,也可以针对所有类型进行修改。
Setlocale的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有类项。。
C标准给第二个参数仅定义了两种可能得取值:“c”(正常模式)和“本地模式”。
在任意程序执行开始,都会隐藏式直系那个调用这个函数并选择正常模式,默认就是正常模式,对应的所有项都是正常模式。
当程序运行起来之后想改变地区,就只能显示调用setlocale函数,用“”作为第二个参数,调用setlocale函数就切换为本地模式,这种模式下的程序适应本地环境。
Setlocale的返回值是一个字符串指针,表示已经设置好的格式,如果调用失败,则返回空指针。
Setlocale可以用来查询当前地区 ,这时第二个参数设为NULL就可以了。
下面代码演示的是 C语言默认的本地信息是什么?
宽字符的打印
宽字符的字面量必须加上前缀L,否则C语言会把字面量当做宅字符类型处理,前缀L在单引号前面,表示宽字符,宽字符的打印用wprintf,对应wprintf()的占位符为%lc;在双引号前面,表示宽字符串,对应wprintf()的占位符为%ls。
二、贪吃蛇的数据结构的设计
1.整体框架
void test()
{
//创建贪吃蛇
Snake snake = { 0 };
GameStart(&snake);//游戏开始前的初始化
GameRun(&snake);//玩游戏的过程
GameEnd(&snake);//善后工作
}
int main()
{
//修改适配本地中文环境
setlocale(LC_ALL, "");
test();//贪吃蛇游戏测试
}
整个游戏分为三个部分:游戏的初始化,游戏的运行过程、和最后游戏结束的界面
2.初始化界面
游戏需要搭建一个框架:
按照宽字符定义来说,墙的长度就应该是宽的两倍。
打印一个宽为28的棋盘长就必须是56,代码如下:
void CreateMap()
{
//上
SetPos(0, 0);
int i = 0;
for (i = 0;i < 56;i+=2)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 25);
for (i = 0;i < 56;i += 2)
{
wprintf(L"%lc", WALL);
}
//左
SetPos(0, 1);
for (i = 0;i < 24;i++)
{
wprintf(L"%lc\n", WALL);
}
//右
for (i = 0;i < 24;i++)
{
SetPos(54, 1 + i);
wprintf(L"%lc", WALL);
}
}
运行结果:
进入棋盘前的信息:
void SetPos(int x, int y)
{
//获得设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//根据句柄设置光标的位置
COORD pos = { x,y };
SetConsoleCursorPosition(handle, pos);
}
void WelcomeGame()
{
//打印欢迎信息
SetPos(38, 13);
printf("欢迎来到贪吃蛇小游戏\n");
SetPos(38, 15);
system("pause");
system("cls");
//功能介绍信息
SetPos(32, 12);
printf("用↑.↓.←.→来控制蛇的移动,F4是加速,F5是减速");
SetPos(32, 14);
printf("加速能得到更高的分数");
SetPos(32, 16);
system("pause");
system("cls");
}
我们还需要设置蛇的初始位置,对于蛇身,我们可以通过单向链表将其串起来,然后遍历逐个打印,
//游戏的状态
enum GAME_STATUS
{
OK = 1,//正常运行
ESC,//按了ESC键退出,正常退出
KILL_BY_WALL,//撞墙了
KILL_BY_SELF,//撞到自身了
};
//蛇行走的方向
enum DIRECTION
{
UP=1,//向上
DOWN,//向下
LEFT,//向左
RIGHT,//向右
};
//蛇身节点的定义
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode;
typedef struct SnakeNode* pSnakeNode;
//每一个节点在动的时候都在变化,所以每一个动的时候都要用坐标来记录其位置
//用结构体指针指向下一个蛇身的节点
//贪吃蛇---整个游戏的维护
typedef struct Snake
{
pSnakeNode pSnake;//维护整条蛇的指针
pSnakeNode pFood;//指向食物的指针
int score;//当前累计的分数
int foodweight;//一个食物的分数
int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,休眠的速度越慢
enum GAME_STATUS status;//游戏当前的状态
enum DIRECTION dir;
//......
}Snake,*pSnake;
出了蛇身需要管理,还有分数和单个食物的分数,还有蛇的休眠时间,还有当前游戏状态,蛇的方向都需要管理,我们一并将其封装在一个结构体中管理。
蛇的默认其实位置,我们就将其定在(24,5)这个坐标上。
接下来,打印蛇身并初始化分数还有状态等信息…
void InitSnake(pSnake ps)
{
//创建5个蛇身节点
pSnakeNode cur = NULL;
int i = 0;
for (i = 0;i < 5;i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
printf("InitSnake():malloc() fail\n");
return;
}
cur->x = POS_X + i * 2;
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;
for (i = 0;i < 5;i++)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//贪吃蛇的其他信息初始化
ps->dir = RIGHT;
ps->foodweight = 10;
ps->pFood = NULL;
ps->score = 0;
ps->SleepTime = 200;//200毫秒
ps->status = OK;
}
对于蛇身,我们起始位置,先创建五个节点,意思就是游戏开始蛇的长度便是5,然后我们利用头插法将其串起来。
创建食物:
void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
do
{
x = rand() % 53 + 2;//2~54
y = rand() % 24 + 1;//1~25
} while (x % 2 != 0);
pSnakeNode cur = ps->pSnake;
while (cur != NULL)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
//创建食物
pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
if (food == NULL)
{
perror("CreateFood():malloc() ");
return;
}
food->x = x;
food->y = y;
ps->pFood = food;
SetPos(food->x, food->y);
wprintf(L"%lc", FOOD);
}
对于创建食物我们需要注意的是,食物也是一个蛇的节点,因为蛇吃了食物之后,会变长,所以食物会变成蛇的新的节点。
我们将以上函数都封装在一个文件当中:
void GameStart(pSnake ps)
{
//设置控制台的信息,窗口大小,窗口名
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(handle, &CursorInfo);
//打印欢迎信息
WelcomeGame();
//绘制地图
CreateMap();
//初始化蛇
InitSnake(ps);
//创建食物
CreateFood(ps);
}
3.贪吃蛇的运行
void GameRun(pSnake ps)
{
//打印帮助信息
PrintHelpInfo();
do
{
//当前的分数情况
SetPos(65, 9);
printf("总分:%5d\n", ps->score);
SetPos(65, 10);
printf("食物的分值:%02d\n", ps->foodweight);
//监测按键
//上、下、左、右、ESC、space、F1、F2
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_ESCAPE))
{
ps->status = ESC;
break;
}
else if (KEY_PRESS(VK_SPACE))
{
//游戏暂停
pause();//暂停和恢复暂停
}
else if (KEY_PRESS(VK_F1))
{
if (ps->SleepTime >= 80)
{
ps->SleepTime -= 30;
ps->foodweight += 2;
}
}
else if (KEY_PRESS(VK_F2))
{
if (ps->foodweight > 2)
{
ps->SleepTime += 30;
ps->foodweight -= 2;
}
}
//睡眠一下
Sleep(ps->SleepTime);
//走一步
SnakeMove(ps);
} while (ps->status==OK);
}
先利用上面讲的识别键盘按键的函数把需要用到的按键给识别了,然后在最前面打印需要的信息:
void PrintHelpInfo()
{
SetPos(65, 14);
printf("不能穿墙,不能咬到自己\n");
SetPos(65, 15);
printf("用↑.↓.←.→分别控制蛇的移动\n");
SetPos(65, 16);
printf("F1:加速 F2:减速");
SetPos(65, 17);
printf("ESC:退出游戏 space:暂停游戏");
}
然后对蛇移动的函数进行封装:
==注意:对于蛇的移动,我们需要对他的异于其他方向的临近的坐标进行统计,然后用Switch case
语句把每一个方向对应的下一个坐标写出来:
坐标写出来之后,就需要判断是否是食物,如果是食物的话我们就需要将食物同化为蛇身的节点,如果不是食物的话,就需要将下一个节点作为蛇的头,然后将最后一个尾节点给删去,这样就达到了移动的效果
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 RIGHT:
pnext->x = ps->pSnake->x + 2;
pnext->y = ps->pSnake->y;
break;
case LEFT:
pnext->x = ps->pSnake->x - 2;
pnext->y = ps->pSnake->y;
break;
}
if (NextIsFood(ps,pnext))
{
//吃掉食物
EatFood(ps,pnext);
}
else
{
//不是食物正常走一步
NotEatFood(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;
}
ps->score += ps->foodweight;
free(ps->pFood);
CreateFood(ps);
}
如果不是食物:
void NotEatFood(pSnake ps, pSnakeNode pnext)
{
pnext->next = ps->pSnake;
ps->pSnake = pnext;
pSnakeNode cur = ps->pSnake;
while (cur->next->next != NULL)
{
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 ==54 )
{
ps->status = KILL_BY_WALL;
}
else if (ps->pSnake->y == 0 || ps->pSnake->y == 26)
{
ps->status = KILL_BY_WALL;
}
}
//撞到自己
void KillBySelf(pSnake ps)
{
pSnakeNode next = ps->pSnake->next;
while (next != NULL)
{
if (next->x == ps->pSnake->x && next->y == ps->pSnake->y)
{
ps->status = KILL_BY_SELF;
return;
}
next = next->next;
}
}
4.游戏的退出
游戏的退出需要根据游戏不同的状态来打印不同的信息,比如正常退出,又比如撞墙,又比如撞到自己的蛇身。
还有就是游戏结束后我们需要对蛇的蛇身进行内存释放,还有食物等等,将链表的每个节点进行遍历释放。
void GameEnd(pSnake ps)
{
SetPos(25, 12);
switch (ps->status)
{
//正常退出
case ESC:
printf("正常退出\n");
break;
//撞自己
case KILL_BY_WALL:
printf("很遗憾,撞墙了,游戏结束\n");
break;
//撞墙
case KILL_BY_SELF:
printf("很遗憾,咬到自己了,游戏结束\n");
break;
}
SetPos(25, 26);
//释放贪吃蛇的链表资源
pSnakeNode cur = ps->pSnake;
pSnakeNode next = NULL;
while (cur)
{
next = cur->next;
free(cur);
cur = next;
}
free(ps->pFood);
ps = NULL;
}
三、整体代码
//test.c
#include "snake.h"
void test()
{
//创建贪吃蛇
int ch = 0;
do
{
Snake snake = { 0 };
GameStart(&snake);//游戏开始前的初始化
GameRun(&snake);//玩游戏的过程
GameEnd(&snake);//善后工作
SetPos(25, 13);
printf("再来一局吗?(Y/N):");
ch = getchar();
getchar();//清理\n
} while (ch == 'Y' || ch == 'y');
}
int main()
{
//修改适配本地中文环境
setlocale(LC_ALL, "");
test();//贪吃蛇游戏测试
}
//snake.c
#include "snake.h"
void SetPos(int x, int y)
{
//获得设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//根据句柄设置光标的位置
COORD pos = { x,y };
SetConsoleCursorPosition(handle, pos);
}
void WelcomeGame()
{
//打印欢迎信息
SetPos(38, 13);
printf("欢迎来到贪吃蛇小游戏\n");
SetPos(38, 15);
system("pause");
system("cls");
//功能介绍信息
SetPos(32, 12);
printf("用↑.↓.←.→来控制蛇的移动,F4是加速,F5是减速");
SetPos(32, 14);
printf("加速能得到更高的分数");
SetPos(32, 16);
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, 25);
for (i = 0;i < 56;i += 2)
{
wprintf(L"%lc", WALL);
}
//左
SetPos(0, 1);
for (i = 0;i < 24;i++)
{
wprintf(L"%lc\n", WALL);
}
//右
for (i = 0;i < 24;i++)
{
SetPos(54, 1 + i);
wprintf(L"%lc", WALL);
}
}
void InitSnake(pSnake ps)
{
//创建5个蛇身节点
pSnakeNode cur = NULL;
int i = 0;
for (i = 0;i < 5;i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
printf("InitSnake():malloc() fail\n");
return;
}
cur->x = POS_X + i * 2;
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;
for (i = 0;i < 5;i++)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//贪吃蛇的其他信息初始化
ps->dir = RIGHT;
ps->foodweight = 10;
ps->pFood = NULL;
ps->score = 0;
ps->SleepTime = 200;//200毫秒
ps->status = OK;
}
void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
do
{
x = rand() % 53 + 2;//2~54
y = rand() % 24 + 1;//1~25
} while (x % 2 != 0);
pSnakeNode cur = ps->pSnake;
while (cur != NULL)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
//创建食物
pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
if (food == NULL)
{
perror("CreateFood():malloc() ");
return;
}
food->x = x;
food->y = y;
ps->pFood = food;
SetPos(food->x, food->y);
wprintf(L"%lc", FOOD);
}
void GameStart(pSnake ps)
{
//设置控制台的信息,窗口大小,窗口名
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(handle, &CursorInfo);
//打印欢迎信息
WelcomeGame();
//绘制地图
CreateMap();
//初始化蛇
InitSnake(ps);
//创建食物
CreateFood(ps);
}
void PrintHelpInfo()
{
SetPos(65, 14);
printf("不能穿墙,不能咬到自己\n");
SetPos(65, 15);
printf("用↑.↓.←.→分别控制蛇的移动\n");
SetPos(65, 16);
printf("F1:加速 F2:减速");
SetPos(65, 17);
printf("ESC:退出游戏 space:暂停游戏");
}
void pause()
{
while (1)
{
if (KEY_PRESS(VK_SPACE))
{
break;
}
Sleep(100);
}
}
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;
}
ps->score += ps->foodweight;
free(ps->pFood);
CreateFood(ps);
}
void NotEatFood(pSnake ps, pSnakeNode pnext)
{
pnext->next = ps->pSnake;
ps->pSnake = pnext;
pSnakeNode cur = ps->pSnake;
while (cur->next->next != NULL)
{
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 ==54 )
{
ps->status = KILL_BY_WALL;
}
else if (ps->pSnake->y == 0 || ps->pSnake->y == 26)
{
ps->status = KILL_BY_WALL;
}
}
//撞到自己
void KillBySelf(pSnake ps)
{
pSnakeNode next = ps->pSnake->next;
while (next != NULL)
{
if (next->x == ps->pSnake->x && next->y == ps->pSnake->y)
{
ps->status = KILL_BY_SELF;
return;
}
next = next->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 RIGHT:
pnext->x = ps->pSnake->x + 2;
pnext->y = ps->pSnake->y;
break;
case LEFT:
pnext->x = ps->pSnake->x - 2;
pnext->y = ps->pSnake->y;
break;
}
if (NextIsFood(ps,pnext))
{
//吃掉食物
EatFood(ps,pnext);
}
else
{
//不是食物正常走一步
NotEatFood(ps,pnext);
}
//监测撞墙
KillByWall(ps);
//撞到自己
KillBySelf(ps);
}
void GameRun(pSnake ps)
{
//打印帮助信息
PrintHelpInfo();
do
{
//当前的分数情况
SetPos(65, 9);
printf("总分:%5d\n", ps->score);
SetPos(65, 10);
printf("食物的分值:%02d\n", ps->foodweight);
//监测按键
//上、下、左、右、ESC、space、F1、F2
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_ESCAPE))
{
ps->status = ESC;
break;
}
else if (KEY_PRESS(VK_SPACE))
{
//游戏暂停
pause();//暂停和恢复暂停
}
else if (KEY_PRESS(VK_F1))
{
if (ps->SleepTime >= 80)
{
ps->SleepTime -= 30;
ps->foodweight += 2;
}
}
else if (KEY_PRESS(VK_F2))
{
if (ps->foodweight > 2)
{
ps->SleepTime += 30;
ps->foodweight -= 2;
}
}
//睡眠一下
Sleep(ps->SleepTime);
//走一步
SnakeMove(ps);
} while (ps->status==OK);
}
void GameEnd(pSnake ps)
{
SetPos(25, 12);
switch (ps->status)
{
//正常退出
case ESC:
printf("正常退出\n");
break;
//撞自己
case KILL_BY_WALL:
printf("很遗憾,撞墙了,游戏结束\n");
break;
//撞墙
case KILL_BY_SELF:
printf("很遗憾,咬到自己了,游戏结束\n");
break;
}
SetPos(25, 26);
//释放贪吃蛇的链表资源
pSnakeNode cur = ps->pSnake;
pSnakeNode next = NULL;
while (cur)
{
next = cur->next;
free(cur);
cur = next;
}
free(ps->pFood);
ps = NULL;
}
//snake.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<locale.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdbool.h>
#define WALL L'□'
#define FOOD L'★'
#define BODY L'●'
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1? 1:0)
//蛇默认的起始坐标
#define POS_X 24
#define POS_Y 5
//游戏的状态
enum GAME_STATUS
{
OK = 1,//正常运行
ESC,//按了ESC键退出,正常退出
KILL_BY_WALL,//撞墙了
KILL_BY_SELF,//撞到自身了
};
//蛇行走的方向
enum DIRECTION
{
UP=1,//向上
DOWN,//向下
LEFT,//向左
RIGHT,//向右
};
//蛇身节点的定义
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode;
typedef struct SnakeNode* pSnakeNode;
//每一个节点在动的时候都在变化,所以每一个动的时候都要用坐标来记录其位置
//用结构体指针指向下一个蛇身的节点
//贪吃蛇---整个游戏的维护
typedef struct Snake
{
pSnakeNode pSnake;//维护整条蛇的指针
pSnakeNode pFood;//指向食物的指针
int score;//当前累计的分数
int foodweight;//一个食物的分数
int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,休眠的速度越慢
enum GAME_STATUS status;//游戏当前的状态
enum DIRECTION dir;
//......
}Snake,*pSnake;
//游戏开始的准备环节
void GameStart(pSnake ps);
//欢迎界面
void WelcomeGame();
//绘制地图
void CreateMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
//游戏运行
void GameRun(pSnake snake);
//打印帮助信息
void PrintHelpInfo();
//蛇的移动
void SnakeMove(pSnake ps);
//判断蛇头的下一个节点是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext);
//下一步要走的位置处是食物,就吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext);
//下一步要走的位置不是食物,
void NotEatFood(pSnake ps, pSnakeNode pnext);
//监测撞墙
void KillByWall(pSnake ps);
//撞到自己
void KillBySelf(pSnake ps);
//善后工作
void GameEnd(pSnake ps);
//坐标定位
void SetPos(int x, int y);