Win32 API
Win32 API是什么呢,它其实就是windows为了协调应用程序执行,分配内存管理资源而产生的函数,它能帮助应用程序不需要细致做到每一步,因为API的服务已经贴心的帮你做好了,你只需要调用一下这些服务即可,而这每一个服务都是一个函数,它们的服务对象就是我们的应用程序,所以我们简称它为API(Application Programming Interface).,而Win32 API就是Microsoft Window32位平台的应用程序编程接口 。
设置控制台的参数
既然咱们要实现贪吃蛇,那必然需要一块场地来给蛇以及食物,为了让他看起来更像是一个游戏,我们需要设置一下游戏的尺寸以及标题。
这样我们运行程序时左上角的标题就不再是路径了
控制台坐标
我们光有标题是不够的,表面功夫还需要完善,众所周知我们的打印一直是从左上角开始的,但咱们的蛇不能一直在左上角,食物也不能一直在左上角,那么我们就需要来更改起始位置了。
COORD 是Windows API中定义的一个结构体,它有两个参数,也很简单易懂 x,y 想必一眼就能看出来这俩是啥,坐标嘛。
那么我们只需要定义一个这种类型的结构体,再给他传值进去,这样还不行,此时需要我们的API函数出场了
GetStdHandle
这个API的功能是取得一个 句柄 而这个句柄就像是你想控制车的方向,得先握住方向盘,句柄则是获取你想使用的信息然后存放进一个变量里。
当然除了输出,还有输入和错误提示。
在我们取得句柄之后就需要来对闪烁的光标下手了,因为蛇旁边老有个东西在闪很不对劲,所以又要请API出手了。
GetConsoleCursorInfo
这个函数是取得光标信息的,它有两个参数,一个是句柄,另一个则是一个结构体:CONSOLE_CURSOR_INFO
这个结构体有两个参数,一个是dwSize 影响光标的大小,数值从1-100。
另一个参数bVisible,这个影响的是它的可见性,是一个bool类型,true为可见false则为不可见。
所以我们需要再来个变量定义一下这个结构体,再根据需要把它设置成不可见。
在这还没完呢,上面只是get出了它的信息,现在我们需要set一下把它确认执行。
这样我们的光标就彻底看不见了,可是光看不见可不行,我们还得打印东西呢,此时再来使用我们的COORD,输入我们要的坐标,再使用SetConsoleCursorPosition,把我们的句柄和pos传进去,光标就定位到了我们的坐标处,这时候我们想打印啥都行。鉴于这项操作我们可能会用到很多次,所以我们给它做成一个函数。
GetAsyncKeyState
我们还得用键盘来控制蛇的方向,所以我们就要用到这个API,它是用来获取按键情况的,它是一个short类型的值,如果按了,那么最高位是1,不然就是0 ,所以我们可以直接判断它,这里我们把它封装成一个宏。
而键盘上的键位都有虚拟键码定义,我们只需要输入虚拟键码作为参数即可。
游戏地图
当我们明白用来控制台也能用坐标系定位的时候,那么地图就简单了,在对应位置打印图案作为地图边缘即可,后期加个判断,如果蛇撞到这些地方就算游戏失败即可,不过有需要注意的地方是我们得明白咱的光标不是一个正方形,所以咱可能2x才等于1y,那么在框架的打印上,行得是列的2倍才看的没什么问题。再有一点,咱不能用句号这种来当地图边缘,所以它多少得是个图案,这就需要讲到宽字符的概念了。
宽字符概念
抽象点来说就是咱的中文一个顶俩字节,你想打印前得先本土化一下 用#include<locale.h>先包含个头文件。
然后再这样就可以切到本地模式了。
setlocale (LC_ALL, "C" );
这个函数它有两个参数,第一个呢是更改的项,一般直接全改了,不一般的情况可以一项一项改
第二个参数就两个取值,正常模式,本地模式,"C"就是正常模式,而我们用的本地模式就是一对双引号加个空格 " "
宽字符的打印
想要打印符号,光设置还不够,它连打印的函数都与众不太同,得用到wprintf,用法如图。
这样地图就打印完了,还顺便用宏把蛇身,地图边缘和食物的图案设置好了。一定需要注意的是咱用了宽字符之后,蛇的身体和食物现在都是2x和1y 如果不注意的话很有可能会出现只要一半的蛇在动另一半不动的情况。
蛇,蛇属性,食物
蛇
咱们的蛇其实可以看作是一个链表,我们只需要它的头结点坐标,然后依次找到这些坐标打印内容就好了,所以我们创建的蛇结构体长这样。
而光有蛇是不够的 我们还得设置一下蛇的属性。
蛇属性
我们至少需要知道蛇头在哪,食物在哪,蛇现在的方向,蛇现在的状态,以及蛇拿了多少分,现在一个食物多少分,以及咱多久刷一下屏
方向和状态,我们直接用枚举给它定义一下
为啥up设为1呢,因为待会咱们要用switch判断它的方向。
食物
首先我们的食物它的x得是随机2的倍数,还不能比我们的框架大,其次他不能直接出现在咱蛇肚子里,如果这些条件都满足,那么我们把光标定位到这个地方打印食物,再我们把坐标传到蛇属性的结构体里的食物。
至此,基本上游戏的基本框架已经有了 接下来就是实现蛇的运行了
游戏开始
运行
上来先把之前的操作整合起来一下,然后进入我们的第一个函数
游戏封面
先把游戏规则给他打印了,这一步基本上就是重复我们的宽字符打印环节,再之后的地图创建我们在宽字符实现环节已经打印了,直接调用即可。
创建蛇
之前提到我们把蛇看成一个链表,那么假设我们的蛇有五节,我们只需要for循环malloc五个节点即可,再把坐标x递增赋值进去即可,最后判断一下,如果蛇头为空,那么我们开的节点就作为蛇头,否则next往后赋值即可,然后再来一个循环遍历蛇的节点,在这些节点打印图形,最后设置一下蛇属性,我们的蛇就算创建完成了。
算上之前创建出的食物,我们的初始化已经完成,现在需要进入下一个阶段,蛇走的环节了。
蛇动
首先咱还是在左边打印游戏规则和提示
然后判断一下咱们要移动的方向以及是否是加减速
当然这里用的是do while循环,而条件则是我们开始设定的蛇的状态是不是ok,如果不是就停下循环。
判断完就该咱们蛇动起来了
由于在进来之前我们已经根据按键设定好了蛇的方向,有了方向之后就能预测到下一步的位置,那我们直接加减xy即可,只需要注意x是+-2;
但是我们还需要分两种情况,一种是下一步就是食物,那么我们采取的做法是把这个食物当成我们的新头接上就好,然后再创建一个新食物。那么我们先写一个判断下一步是不是食物的函数。
如果食物坐标和我们的下一步坐标对上,那么就是食物,返回1,否则返回0;而如果返回1
这样食物就成了咱的新头,而如果下一步不是我们的食物,那么我们就在下一步创建一个节点当头,但是我们删除掉最后一个节点,并且 打印俩空格把我们之前的内容给覆盖掉,实现蛇走一步的效果。
至此,我们只需要考虑一点,蛇怎么死,前面我们提到过不能撞墙和咬到自己,我们现在来实现它。
如果我蛇的xy不等于我的边缘坐标,那就是没撞死
如果我蛇头不等于我蛇身的坐标,那就表示没咬到自己
最后我们使用sleep函数来实现模拟蛇每走一步的停顿效果即可。
游戏结束
到这我们就算结束了。实现收尾就完了。
根据蛇的状态来判断一下是怎么结束的即可。
最后不要忘记释放蛇所开辟的空间即可。
代码
.c文件
#include "TCS.h"
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取输出句柄存放入变量中
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//把光标句柄和坐标传入
SetConsoleCursorPosition(hOutput, pos);
}
void WelcomeToGame()
{
SetPos(40, 15);
printf("欢迎来玩游戏");
SetPos(40, 25);
system("pause");
system("cls");
SetPos(25, 12);
printf("用↑,↓,→,←,控制蛇移动");
SetPos(25, 13);
printf("加速可以拿更多分\n");
SetPos(40, 25);
system("pause");
system("cls");
}
void CreateMap()
{
int i = 0;
//上(0,0)-(56, 0)
SetPos(0, 0);
for (i = 0; i < 58; i += 2)
{
wprintf(L"%c", WALL);
}
//下(0,26)-(56, 26)
SetPos(0, 26);
for (i = 0; i < 58; i += 2)
{
wprintf(L"%c", WALL);
}
//左
//x是0,y从1开始增⻓
for (i = 1; i < 26; i++)
{
SetPos(0, i);
wprintf(L"%c", WALL);
}
//x是56,y从1开始增⻓
for (i = 1; 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 (cur == NULL)
{
perror("InitSnake()::malloc()");
return;
}
//设置坐标
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->_SleepTime = 300;
ps->_Socre = 0;
ps->_Status = OK;
ps->_Dir = RIGHT;
ps->_foodWeight = 10;
}
//食物
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 (pFood == NULL)
{
perror("CreateFood::malloc()");
return;
}
else
{
pFood->x = x;
pFood->y = y;
SetPos(pFood->x, pFood->y);
wprintf(L"%c", FOOD);
ps->_pFood = pFood;
}
}
void PrintHelpInfo()
{
//打印提⽰信息
SetPos(64, 15);
wprintf(L"不能穿墙,不能咬到⾃⼰");
SetPos(64, 16);
wprintf(L"⽤↑.↓.←.→控制蛇的移动.");
SetPos(64, 17);
wprintf(L"F3加速,F4减速\n");
SetPos(64, 18);
wprintf(L"ESC :退出游戏.space:暂停游戏.");
}
void pause()//暂停
{
while (1)
{
Sleep(300);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//pSnakeNode psn 是下⼀个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(pSnakeNode psn, pSnake ps)
{
return (psn->x == ps->_pFood->x) && (psn->y == ps->_pFood->y);
}
//pSnakeNode psn 是下⼀个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(pSnakeNode psn, pSnake ps)
{
//头插法
ps->_pFood->next = ps->_pSnake;
ps->_pSnake = ps->_pFood;
free(psn);
psn = NULL;
pSnakeNode cur = ps->_pSnake;
//打印蛇
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%c", BODY);
cur = cur->next;
}
ps->_Socre += ps->_foodWeight;
CreateFood(ps);
}
//pSnakeNode psn 是下⼀个节点的地址
//pSnake ps 维护蛇的指针
void NoFood(pSnakeNode psn, pSnake ps)
{
//头插法
psn->next = ps->_pSnake;
ps->_pSnake = psn;
pSnakeNode cur = ps->_pSnake;
//打印蛇
while (cur->next->next!=NULL)
{
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;
}
//pSnake ps 维护蛇的指针
int 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;
return 1;
}
return 0;
}
//pSnake ps 维护蛇的指针
int 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;
return 1;
}
cur = cur->next;
}
return 0;
}
void SnakeMove(pSnake ps)
{
//创建下⼀个节点
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNextNode == NULL)
{
perror("SnakeMove()::malloc()");
return;
}
//确定下⼀个节点的坐标,下⼀个节点的坐标根据,蛇头的坐标和⽅向确定
switch (ps->_Dir)
{
case UP:
{
pNextNode->x = ps->_pSnake->x;
pNextNode->y = ps->_pSnake->y - 1;
}
break;
case DOWN:
{
pNextNode->x = ps->_pSnake->x;
pNextNode->y = ps->_pSnake->y + 1;
}
break;
case LEFT:
{
pNextNode->x = ps->_pSnake->x - 2;
pNextNode->y = ps->_pSnake->y;
}
break;
case RIGHT:
{
pNextNode->x = ps->_pSnake->x + 2;
pNextNode->y = ps->_pSnake->y;
}
break;
}
//如果下⼀个位置就是⻝物
if (NextIsFood(pNextNode, ps))
{
EatFood(pNextNode, ps);
}
else//如果没有⻝物
{
NoFood(pNextNode, ps);
}
KillByWall(ps);
KillBySelf(ps);
}
void GameStart(pSnake ps)
{
//设置窗⼝的⼤⼩,30⾏,100列
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 GameRun(pSnake ps)
{
//打印右侧帮助信息
PrintHelpInfo();
do
{
SetPos(64, 10);
printf("总分 %d", ps->_Socre);
SetPos(64, 11);
printf("食物分 %d", 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))
{
pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
ps->_Status = END_NOMAL;
break;
}
else if (KEY_PRESS(VK_F3))
{
if (ps->_SleepTime >= 50)
{
ps->_SleepTime -= 30;
ps->_foodWeight += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
if (ps->_SleepTime < 350)
{
ps->_SleepTime += 30;
ps->_foodWeight -= 2;
if (ps->_SleepTime == 350)
{
ps->_foodWeight = 1;
}
}
}
SnakeMove(ps);
Sleep(ps->_SleepTime);
} while (ps->_Status == OK);
}
void GameEnd(pSnake ps)
{
pSnakeNode cur = ps->_pSnake;
SetPos(24, 12);
switch (ps->_Status)
{
case END_NOMAL:
wprintf(L"GAME IS OVER");
break;
case KILL_BY_SELF:
wprintf(L"U EAT SELF");
break;
case KILL_BY_WALL:
wprintf(L"U IN WALL");
break;
}
//释放蛇
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
.h文件
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <windows.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
//⽅向
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
//游戏状态
enum GAME_STATUS
{
OK,//正常
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//咬⾃⼰
END_NOMAL//结束
};
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//蛇的初始位置
#define POS_X 24
#define POS_Y 5
//蛇
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
//蛇属性
typedef struct Snake
{
pSnakeNode _pSnake;//蛇头
pSnakeNode _pFood;//⻝物
enum DIRECTION _Dir;//默认⽅向右
enum GAME_STATUS _Status;//游戏状态
int _Socre;//当前分数
int _foodWeight;//每个⻝物10分
int _SleepTime;//休眠时间
}Snake, * pSnake;
//游戏开始前的初始化
void GameStart(pSnake ps);
//游戏运⾏过程
void GameRun(pSnake ps);
//游戏结束
void GameEnd(pSnake ps);
//设置光标的坐标
void SetPos(short x, short y);
//欢迎界⾯
void WelcomeToGame();
//打印帮助信息
void PrintHelpInfo();
//创建地图
void CreateMap();
//初始化蛇
void InitSnake(pSnake ps);
//创建⻝物
void CreateFood(pSnake ps);
//暂停响应
void pause();
//下⼀个节点是⻝物
int NextIsFood(pSnakeNode psn, pSnake ps);
//吃⻝物
void EatFood(pSnakeNode psn, pSnake ps);
//不吃⻝物
void NoFood(pSnakeNode psn, pSnake ps);
//撞墙检测
int KillByWall(pSnake ps);
//撞⾃⾝检测
int KillBySelf(pSnake ps);
//蛇的移动
void SnakeMove(pSnake ps);
//游戏初始化
void GameStart(pSnake ps);
//游戏运⾏
void GameRun(pSnake ps);
//游戏结束
void GameEnd(pSnake ps);
主函数部分
#include "TCS.h"
#include <locale.h>
void test()
{
int ch = 0;
srand((unsigned int)time(NULL));
do
{
Snake snake = { 0 };
GameStart(&snake);
GameRun(&snake);
GameEnd(&snake);
SetPos(20, 15);
wprintf(L"再来⼀局吗?(Y/N):");
ch = getchar();
} while (ch == 'Y' || ch == 'y');
SetPos(0, 27);
}
int main()
{
//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
setlocale(LC_ALL, "");
//测试逻辑
test();
return 0;
}