本文将用win32提供的API进行贪吃蛇小游戏的开发,用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。也就是说,你只要会用vs2019或者其他版本的vs即可。不用额外学习esayx等其他软件。win32的API是电脑系统自带的函数接口,通过调用相关函数,我们可以在窗口中实现贪吃蛇小游戏。
使用 Windows API,可以开发可在所有版本的 Windows 上成功运行的应用程序,同时利用每个版本特有的特性和功能。
这里不方便放视频,直接上图片了
system("mode con cols=100 lines=50");
原本大小
修改后大小
ps:一般控制台就是标准输出 STD_OUPUT表示标准输出
ps:代码栏的TypeScript是随便选的,为了能标示出函数和指针类型
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
其中HANDLE是个指针类型
示例
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleInfo(hOutput,&CursorInfo); //获取控制台光标信息
示例
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput,&CursorInfo); //获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput,&CursorInfo); //设置控制台光标状态
定义控制台屏幕缓冲区中字符单元的坐标。 坐标系 (0,0) 的原点位于控制台的顶部左侧单元格。
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
示例
COORD pos ={10.5};
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标位置为pos
SetConsoleCursorPosition(hOutput,pos);
为了后续方便,我们将设置光标的代码分装成一个函数SetPos
void SetPos(int x,int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = {x,y};
SetConsoleCursorPosition(handle,pos);
}
GetAsyncKeyState返回值类型是short,在上次调用GetAsyncKeyState函数后,如果返回的16位数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1,则说明键被按过,否则为0。
所以如果要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn
控制台窗口的坐标,横向是x轴,向右增长;纵向是y轴,向下增长;左上角为原点,原点坐标为(0,0)
原本的ascii码表只用了一个字节中的前7个比特位,最高位是不使用的。所以ascii码表的取值范围是 0~127
而这只能刚好用来表示英文的字符,对于中文字符来说是远远不够的。为了使c语言适应国际化,在c标准库中加入许多国际化支持。如加入了宽字符类型 wchar_t ,和宽字符的输入输出函数 。加入
char* setlocale(int categary , const char* locale)
setlocale函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。
categary表示类项。类项有
LC_CTYPE 影响字符处理函数行为
LC_MONETARY影响货币格式
LC_ALL 针对所有类项修改 等等。。。
c标准只给第二个参数定义了2种可能取值“C”(正常模式); “ ”(本地模式)
下面切换到本地模式,支持宽字符输入输出(理论上只输入空格也是本地模式)
setlocale(LC_ALL, "zh-CN");
宽字符的定义,其字符面量前必须加L 即单引号前要加L,否则c语言会当作窄字符处理。
其输出用wprintf(“%lc”,ch); 若是宽字符串 则为%ls
宽字符占两个坐标位置,而平常的窄字符只占一个坐标位置
设计实现一个27行,58列的棋盘当作地图,围绕地图画出墙面
部分示意图如下,围墙的墙由若干小方块组成,一个小方块在x轴占两个坐标位置,在y轴占一个坐标位置。所以棋盘的列数一定是2的倍数,因为一个方块的横坐标就占了两个单位了。
初始化状态,假设蛇长度为5,蛇身每个节点是●,在固定坐标点出生,比如(24,5),连续五个节点。
每个节点的x坐标也必须是2的倍数,否则可能出现蛇身的其中一个节点一半在墙外,一半在墙内的情况。坐标不好对齐。
食物应该作为蛇身的一部分,蛇吃到食物就能和蛇的身体融合。食物在墙内随机生成,但是坐标也必须是2的倍数,坐标不能和蛇身重合。用★表示(也可以自己替换其他字符)
设置为windows 控制台主机
#include
#include //system所需要的头文件
#include //支持宽字符的头文件
#include //调用win32API需要的头文件
#include //使用 false true需要的头文件
#define WALL L'■'
#define BODY L'●'
#define FOOD L'★'
#define POS_X 24 //蛇初始化位置
#define POS_Y 5
//封装KEY_PRESS 检测vk虚拟键值对应的按键是否被按过
//如果按过返回1 未按过返回0
#define KEY_PRESS(vk) (GetAsyncKeyState(vk) & 0x1 ? 1: 0)
贪吃蛇蛇身的数据结构设计为链表 --- 此处初始化链表的单个节点
同时创建结构体指针 pSnakeNode 便于后续使用
//蛇身节点
typedef struct SnakeNode
{
//坐标
int x;
int y;
struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
方向信息封装为枚举类型
//方向类型
enum DIRECTION
{
up = 1,
down,
right,
left
};
游戏状态也封装为枚举类型
//游戏状态类型
enum GameStatus
{
Normal, //正常运行
End_Normal, //正常结束
KILL_BY_WALL, //撞墙死亡
KILL_BY_SELF // 撞自己死亡
};
贪吃蛇游戏主要信息的封装 //与SnakeNode区分,SnakeNode是管理蛇身和蛇的位置信息的结构体。 //Snake则是存储贪吃蛇行动以及游戏运行信息的结构体
typedef struct Snake
{
pSnakeNode phead_snake; //指向贪吃蛇头结点的指针
pSnakeNode _pfood; //指向食物结点的指针
int Score; //总分
int FoodWeight; //一个食物的分数
int SleepTime; //停顿时间,控制蛇的行进速度
enum DIRECTION _Dir; //描述蛇行走方向
enum GameStatus _Status; //游戏运行状态
}Snake,*pSnake;
剩余函数的声明
//初始化游戏
void GameStart(pSnake ps);
//游戏欢迎界面
void WelcomeToGame();
//打印地图
void CreatMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物
void CreatFood(pSnake ps);
#include "snake.h"
void test()
{
Snake snake = { 0 }; // 创建贪吃蛇游戏信息
// 1、游戏开始 -- 初始化游戏
GameStart(&snake);
// 2、 游戏运行 -- 运行过程
// GameRun(&snake);
// 3、 游戏结束 -- 结束后的空间资源释放等
// GameEnd(&snake);
}
int main()
{
setlocale(LC_ALL, "zh-CN"); //设置程序适应本地环境
test();
return 0;
}
说明:分阶段封装函数。每个阶段都需要对贪吃蛇游戏信息进行使用和更改,所以参数传snake的地址
游戏可大致分为三个阶段,游戏的初始化,游戏的正常运行,游戏的结束。
#include "snake.h"
//封装设置光标位置的函数
void SetPos(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
COORD pos = { x,y }; //赋予光标位置
SetConsoleCursorPosition(handle, pos); //设置光标位置 -- 确定输入位置信息
}
//游戏启动界面函数
void WelcomeToGame()
{
//定位光标 -- 在窗口的中间打印信息
SetPos(40, 14);
printf("贪 吃 蛇\n");
SetPos(40, 25);
system("pause"); //pause是暂停 此命令会在控制台窗口留下 “请按任意键继续”
system("cls"); //清屏,准备打印下一条信息
SetPos(40, 14);
printf("启 动 !\n");
SetPos(40, 25);
system("pause");
system("cls");
}
//游戏界面初始化
void GameStart(pSnake ps)
{
//控制台窗口设置
system("mode con cols=100 lines=45");
system("title 贪吃蛇");
//隐藏光标信息
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo); //获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo); //设置控制台光标状态
//打印欢迎界面
WelcomeToGame();
//创建地图
CreatMap();
//初始化贪吃蛇
InitSnake(ps);
//创建食物
CreatFood(ps);
}
//创建地图
void CreatMap()
{
//上
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\n", WALL);
}
//右
for (i = 1; i <= 25; i ++)
{
SetPos(56, i);
wprintf(L"%lc\n", WALL);
}
}
void InitSnake(pSnake ps)
{
pSnakeNode cur = NULL;
int i = 0;
for (i = 0; i < 5; i++) //连续创建5个节点
{
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->phead_snake == NULL)
{
ps->phead_snake = cur;
}
else
{
cur->next = ps->phead_snake;
ps->phead_snake = cur;
}
}
//打印蛇身
cur = ps->phead_snake;
while (cur)
{
SetPos(cur->x, cur->y); // 蛇的每个节点都有记录位置信息
wprintf(L"%lc", BODY);
cur = cur->next;
}
//贪吃蛇游戏的其他信息的初始化
ps->_Status = Normal;
ps->Score = 0;
ps->_pfood = NULL; //食物节点尚未创建,先赋空
ps->SleepTime = 200; // 200ms
ps->FoodWeight = 10;
ps->_Dir = right;
}
要先在test.c文件中调用srand函数,设置随机值的起点(随电脑时间变化而变化)
因为墙的横坐标范围是(0,56)所以食物的坐标范围应该是[2,54]
墙纵坐标范围(0,26)食物纵坐标范围[1,25]
do
{
x = rand() % 53 + 2; // rand()%53 得到的是0~52 ;+ 2后 2~54
y = rand() % 25 + 1; // rand()%25 得到的是0~24 ;+ 1后 1~25
} while (x % 2 != 0); //x坐标必须是2的倍数
创建食物函数整体
void CreatFood(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->phead_snake;
while(cur)
{
//依次比较坐标
if (cur->x == x && cur->y == y)
{
goto again; //与蛇身重复了,重新生成坐标
}
cur = cur->next;
}
//坐标有效 为食物开辟空间,并将其地址给相应指针
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL)
{
perror("CreatFood()::malloc()"); //检查是否开辟成功
return;
}
//创建成功 更新食物的信息
pFood->x = x;
pFood->y = y;
ps->_pfood = pFood;
//打印食物
SetPos(x, y); //光标定位到随机生成的位置
wprintf(L"%lc", FOOD);
}
此函数主要是打印不会随游戏进程变化的协助信息
ps:每个函数都要在snake.h声明,此不赘述
void PrintHelpInfo()
{
SetPos(64, 15);
printf("1、不能撞墙,不能撞自己哦\n");
SetPos(64, 16);
printf("2、使用↑↓←→ 控制蛇的移动\n");
SetPos(64, 17);
printf("3、ESC 退出 空格 暂停游戏 再次按空格恢复\n");
}
封装个自己的暂停函数,当再次按空格键的时候结束暂停
在接收方向键时,要注意不能蛇往左走,按右键后立马掉到右来,只能先往下(上)再往右,这样掉头。
按下不同方向键后更新蛇前进的方向信息,然后在蛇的行动函数中实现蛇的前进。
相关虚拟键码可从下面链接寻找
虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn
void my_pause()
{
while(1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
void GameRun(pSnake ps)
{
PrintHelpInfo();
//游戏的按键接收逻辑
do
{
SetPos(64, 10);
printf("Score:%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_SPACE))
{
my_pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
ps->_Status = End_Normal;
break;
}
Sleep(ps->SleepTime); // Sleep用来控制蛇的行动速度,相当于游戏难度
SnakeMove(ps);
} while (ps->_Status == Normal);
}
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->phead_snake->x;
pNext->y = ps->phead_snake->y -1; //在图像上显示向上,但是在坐标上,y坐标是减小的
break;
case down:
pNext->x = ps->phead_snake->x;
pNext->y = ps->phead_snake->y + 1;
break;
case left:
pNext->x = ps->phead_snake->x - 2; //注意x坐标每次变化2个单位
pNext->y = ps->phead_snake->y ;
break;
case right:
pNext->x = ps->phead_snake->x + 2;
pNext->y = ps->phead_snake->y;
break;
}
//判断蛇头到达的坐标处是否是食物
if (NextIsFood(ps, pNext))
{
//吃掉食物
EatFood(ps, pNext);
}
else
{
//不吃食物
NoFood(ps, pNext);
}
}
int NextIsFood(pSnake ps, pSnakeNode pNext)
{
if (ps->_pfood->x == pNext->x && ps->_pfood->y == pNext->y)
{
return 1;
}
else
{
return 0;
}
}
注意吃掉食物时,要释放掉原本食物的空间,因为食物也是malloc生成的,不释放会造成空间污染
void EatFood(pSnake ps, pSnakeNode pNext)
{
//头插
pNext->next = ps->phead_snake;
ps->phead_snake = pNext;
//打印蛇身
pSnakeNode cur = ps->phead_snake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
free(ps->_pfood);//释放掉原本食物的空间
ps->Score += ps->FoodWeight; //加分
//蛇身变长,难度增加
if (ps->SleepTime >= 80)
{
ps->SleepTime -= 10;
}
if(ps->FoodWeight <= 30)
{
ps->FoodWeight += 1;
}
CreatFood(ps);
}
不吃食物时,蛇向前行动,蛇头节点更新为下一个要走的位置的节点,蛇尾设置为空
void NoFood(pSnake ps, pSnakeNode pNext)
{
//头插
pNext->next = ps->phead_snake;
ps->phead_snake = pNext;
//打印蛇身
pSnakeNode cur = ps->phead_snake;
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->phead_snake->x == 0 ||
ps->phead_snake->x == 56 ||
ps->phead_snake->y == 0 ||
ps->phead_snake->y == 26)
{
ps->_Status = KILL_BY_WALL;
}
}
现将蛇头节点的下一个节点给cur,然后用cur遍历蛇身,看是否和蛇头的位置重合
void KillBySelf(pSnake ps)
{
pSnakeNode cur = ps->phead_snake->next;
while (cur)
{
if (ps->phead_snake->x == cur->x && ps->phead_snake->y == cur->y)
{
ps->_Status = KILL_BY_SELF;
}
cur = cur->next;
}
}
判断是那种状态下结束游戏的
释放原本开辟的空间
void GameEnd(pSnake ps)
{
SetPos(20, 12);
switch (ps->_Status)
{
case End_Normal:
printf("您主动退出了游戏\n");
break;
case KILL_BY_WALL:
printf("笨(~ ̄(OO) ̄)ブ 撞墙啦\n");
break;
case KILL_BY_SELF:
printf("(⊙_⊙)? 怎么还有人能撞到自己啊\n");
break;
}
//释放蛇身节点空间
pSnakeNode cur = ps->phead_snake;
while(cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
ps->phead_snake = NULL;
}
最后结束GameEnd退出到test函数中
实现再开一把,将原本的游戏过程调用函数全放进do while函数中
void test()
{
char ch ;
do
{
Snake snake = { 0 }; // 创建贪吃蛇游戏信息
// 1、游戏开始 -- 初始化游戏
GameStart(&snake);
// 2、 游戏运行 -- 运行过程
GameRun(&snake);
// 3、 游戏结束 -- 结束后的空间资源释放等
GameEnd(&snake);
system("pause");
system("cls");
SetPos(40, 18);
printf("不服输?接着塔塔开!\n");
SetPos(40, 19);
printf(" Y -- 塔塔开\n ");
SetPos(40, 20);
printf(" N -- 不了\n");
ch = getchar();
while (getchar()!= '\n')//清空缓冲区
{
;
}
} while (ch == 'y' || ch == 'Y');
SetPos(0, 27);
}
#include
#include //system所需要的头文件
#include //支持宽字符的头文件
#include //调用win32API需要的头文件
#include //使用 false true需要的头文件
#include //使用时间戳的头文件
#define WALL L'■'
#define BODY L'●'
#define FOOD L'★'
#define POS_X 24 //蛇初始化位置
#define POS_Y 5
//封装KEY_PRESS 检测vk虚拟键值对应的按键是否被按过
//如果按过返回1 未按过返回0
#define KEY_PRESS(vk) (GetAsyncKeyState(vk) & 0x1 ? 1: 0)
//蛇身节点
typedef struct SnakeNode
{
//坐标
int x;
int y;
struct Snakenode* next;
}SnakeNode,*pSnakeNode;
//方向类型
enum DIRECTION
{
up = 1,
down,
right,
left
};
//游戏状态类型
enum GameStatus
{
Normal, //正常运行
End_Normal, //正常结束
KILL_BY_WALL,
KILL_BY_SELF
};
//与SnakeNode区分,SnakeNode是管理蛇身和蛇的位置信息的结构体。
//Snake则是存储贪吃蛇行动以及游戏运行信息的结构体
typedef struct Snake
{
pSnakeNode phead_snake; //指向贪吃蛇头结点的指针
pSnakeNode _pfood; //指向食物结点的指针
int Score; //总分
int FoodWeight; //一个食物的分数
int SleepTime; //停顿时间,控制蛇的行进速度
enum DIRECTION _Dir; //描述蛇行走方向
enum GameStatus _Status; //游戏运行状态
}Snake,*pSnake;
//初始化游戏
void GameStart(pSnake ps);
//设置光标位置
void SetPos(int x, int y);
//游戏欢迎界面
void WelcomeToGame();
//打印地图
void CreatMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物
void CreatFood(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//打印帮助信息
void PrintHelpInfo();
//暂停函数
void my_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(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
COORD pos = { x,y }; //赋予光标位置
SetConsoleCursorPosition(handle, pos); //设置光标位置 -- 确定输入位置信息
}
void WelcomeToGame()
{
//定位光标 -- 在窗口的中间打印信息
SetPos(40, 14);
printf("贪 吃 蛇\n");
SetPos(40, 25);
system("pause"); //pause是暂停
system("cls"); //清屏,准备打印下一条信息
SetPos(40, 14);
printf("启 动 !\n");
SetPos(40, 25);
system("pause");
system("cls");
}
void CreatMap()
{
//上
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\n", WALL);
}
//右
for (i = 1; i <= 25; i ++)
{
SetPos(56, i);
wprintf(L"%lc\n", WALL);
}
}
void InitSnake(pSnake ps)
{
pSnakeNode cur = NULL;
int i = 0;
for (i = 0; i < 5; i++) //连续创建5个节点
{
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->phead_snake == NULL)
{
ps->phead_snake = cur;
}
else
{
cur->next = ps->phead_snake;
ps->phead_snake = cur;
}
}
//打印蛇身
cur = ps->phead_snake;
while (cur)
{
SetPos(cur->x, cur->y); // 蛇的每个节点都有记录位置信息
wprintf(L"%lc", BODY);
cur = cur->next;
}
//贪吃蛇的其他信息的初始化
ps->_Status = Normal;
ps->Score = 0;
ps->_pfood = NULL; //食物节点尚未创建,先赋空
ps->SleepTime = 200; // 200ms
ps->FoodWeight = 10;
ps->_Dir = right;
}
void CreatFood(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->phead_snake;
while(cur)
{
//依次比较坐标
if (cur->x == x && cur->y == y)
{
goto again; //与蛇身重复了,重新生成坐标
}
cur = cur->next;
}
//坐标有效 为食物开辟空间,并将其地址给相应指针
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL)
{
perror("CreatFood()::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=45");
system("title 贪吃蛇");
//隐藏光标信息
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo); //获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo); //设置控制台光标状态
//打印欢迎界面
WelcomeToGame();
//创建地图
CreatMap();
//初始化贪吃蛇
InitSnake(ps);
//创建食物
CreatFood(ps);
}
void PrintHelpInfo()
{
SetPos(64, 15);
printf("1、不能撞墙,不能撞自己哦\n");
SetPos(64, 16);
printf("2、使用↑↓←→ 控制蛇的移动\n");
SetPos(64, 17);
printf("3、ESC 退出|空格 暂停游戏 \n");
}
void my_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 NoFood(pSnake ps, pSnakeNode pNext)
{
//头插
pNext->next = ps->phead_snake;
ps->phead_snake = pNext;
//打印蛇身
pSnakeNode cur = ps->phead_snake;
pSnakeNode cur_next = cur->next;
while (cur_next->next)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
cur_next = cur->next;
}
SetPos(cur_next->x, cur_next->y); //定位到蛇尾巴
printf(" "); //覆盖
free(cur->next);
cur->next = NULL;
}
void EatFood(pSnake ps, pSnakeNode pNext)
{
//头插
pNext->next = ps->phead_snake;
ps->phead_snake = pNext;
//打印蛇身
pSnakeNode cur = ps->phead_snake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
free(ps->_pfood);//释放掉原本食物的空间
ps->Score += ps->FoodWeight; //加分
//蛇身变长,难度增加
if (ps->SleepTime >= 80)
{
ps->SleepTime -= 10;
}
if(ps->FoodWeight <= 30)
{
ps->FoodWeight += 1;
}
CreatFood(ps);
}
void KillByWall(pSnake ps)
{
if (ps->phead_snake->x == 0 ||
ps->phead_snake->x == 56 ||
ps->phead_snake->y == 0 ||
ps->phead_snake->y == 26)
{
ps->_Status = KILL_BY_WALL;
}
}
void KillBySelf(pSnake ps)
{
pSnakeNode cur = ps->phead_snake->next;
while (cur)
{
if (ps->phead_snake->x == cur->x && ps->phead_snake->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->phead_snake->x;
pNext->y = ps->phead_snake->y -1; //在图像上显示向上,但是在坐标上,y坐标是减小的
break;
case down:
pNext->x = ps->phead_snake->x;
pNext->y = ps->phead_snake->y + 1;
break;
case left:
pNext->x = ps->phead_snake->x - 2; //注意x坐标每次变化2个单位
pNext->y = ps->phead_snake->y ;
break;
case right:
pNext->x = ps->phead_snake->x + 2;
pNext->y = ps->phead_snake->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("Score:%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_SPACE))
{
my_pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
ps->_Status = End_Normal;
break; //直接结束循环,不用再等到while来判断
}
Sleep(ps->SleepTime); // Sleep用来控制蛇的行动速度,相当于游戏难度
SnakeMove(ps);
} while (ps->_Status == Normal);
}
void GameEnd(pSnake ps)
{
SetPos(20, 12);
switch (ps->_Status)
{
case End_Normal:
printf("您主动退出了游戏\n");
break;
case KILL_BY_WALL:
printf("笨(~ ̄(OO) ̄)ブ 撞墙啦\n");
break;
case KILL_BY_SELF:
printf("(⊙_⊙)? 怎么还有人能撞到自己啊\n");
break;
}
//释放蛇身节点空间
pSnakeNode cur = ps->phead_snake;
while(cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
ps->phead_snake = NULL;
}
#include "snake.h"
void test()
{
char ch ;
do
{
Snake snake = { 0 }; // 创建贪吃蛇游戏信息
// 1、游戏开始 -- 初始化游戏
GameStart(&snake);
// 2、 游戏运行 -- 运行过程
GameRun(&snake);
// 3、 游戏结束 -- 结束后的空间资源释放等
GameEnd(&snake);
system("pause");
system("cls");
SetPos(40, 18);
printf("不服输?接着塔塔开!\n");
SetPos(40, 19);
printf(" Y -- 塔塔开\n ");
SetPos(40, 20);
printf(" N -- 不了\n");
ch = getchar();
while (getchar()!= '\n')//清空缓冲区
{
;
}
} while (ch == 'y' || ch == 'Y');
SetPos(0, 27);
}
int main()
{
setlocale(LC_ALL, "zh-CN"); //设置程序适应本地环境
srand((unsigned int)time(NULL));
test();
return 0;
}