正文开始
1. 游戏背景
2. 游戏效果演示
3. 学习目标
4. 学习定位
5. 技术要点
6. Win32 API介绍
本次实现贪吃蛇会使用到的⼀些Win32 API知识,接下来我们就学习一下。
6.1 Win32 API
6.2 控制台程序
![](https://img-blog.csdnimg.cn/direct/220cfdf7e8ff4242b6f9fdbf9466dce6.png)
我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30列,100行
mode con cols=100 lines=30
//con 全称是 console 即控制台窗口
//cols 是 列,lines 是 行
参考:mode命令 mode命令https://learn.microsoft.com/zh-cn/windows-server/administration/windows-commands/mode
效果如下 ↓
但是我们可能会发现列和行根本不像是30和100的样子,这是为什么呢?
那是因为在C语言中,行是以列的2倍形式存在的。
以这个效果为例,虽然表面上是列100,行30。实际上却是列100,行60。
效果如下 ↓
title 贪吃蛇
效果如下 ↓
这些能在控制台窗口执行的命令,也可以调用C语言函数system来执行。例如:
#include <stdio.h>
int main()
{
//设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
system("mode con cols=100 lines=30");
//设置cmd窗⼝名称
system("title 贪吃蛇");
return 0;
}
6.3 控制台屏幕上的坐标COORD
![](https://img-blog.csdnimg.cn/direct/9fe6896ad57e4980862668cd5321b7ca.png)
COORD类型的声明:
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
给坐标赋值:
COORD pos = { 10, 15 };
6.4 GetStdHandle
实例:
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
6.5 GetConsoleCursorInfo
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标(光标)
参考:GetConsoleCursorInfo GetConsoleCursorInfohttps://learn.microsoft.com/zh-cn/windows/console/getconsolecursorinfo
实例:
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
6.5.1 CONSOLE_CURSOR_INFO
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
CursorInfo.bVisible = false; //隐藏控制台光标
6.6 SetConsoleCursorInfo
设置指定控制台屏幕缓冲区的光标的大小和可见性。
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
参考:SetConsoleCursorInfo SetConsoleCursorInfohttps://learn.microsoft.com/zh-cn/windows/console/setconsolecursorinfo
实例:
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
6.7 SetConsoleCursorPosition
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
参考:SetConsoleCursorPosition SetConsoleCursorPositionhttps://learn.microsoft.com/zh-cn/windows/console/setconsolecursorposition
实例:
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
我们在贪吃蛇中需要总是调整蛇的位置,这里我们就先来分装一个函数,用来移动贪吃蛇的地址。
6.8 GetAsyncKeyState
获取按键情况,GetAsyncKeyState 的函数原型如下:
SHORT GetAsyncKeyState(
int vKey
);
如果我们要判断⼀个键是否被按过,可以检测 GetAsyncKeyState 返回值的最低值是否为1。
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
// 按位与 & :只有双方都为 1 时才为 1
参考:虚拟键码 (Winuser.h) - Win32 apps 虚拟键码 (Winuser.h) - Win32 appshttps://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
实例:检测数字键
#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;
}
7. 贪吃蛇游戏设计与分析
7.1 地图
![](https://img-blog.csdnimg.cn/direct/b97c4351a80f47fda5a0fd1f48c956bb.png)
7.1.1 <locale.h>本地化
7.1.2 类项
7.1.3 setlocale函数
char* setlocale (int category, const char* locale);
参考:setlocale 函数 setlocalehttps://legacy.cplusplus.com/reference/clocale/setlocale/?kw=setlocale
setlocale(LC_ALL, "C");
//vs使用时默认正常模式
setlocale(LC_ALL, " ");//切换到本地环境
7.1.4 宽字符的打印
wprintf() 的占位符为 %ls 。
#include <stdio.h>
#include<locale.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;
}
结果如下:
普通字符和宽字符打印出宽度的展示如下:
7.1.5 地图坐标
7.2 蛇身和食物
![](https://img-blog.csdnimg.cn/direct/80ca0dafc1224c08926de2614e7cbe00.png)
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
要管理整条贪吃蛇,我们再封装一个Snake的结构来维护整条贪吃蛇:
typedef struct Snake
{
PSnakeNode headSnake;//创建指针的头节点即蛇的头节点
PSnakeNode pFood;//食物的指针
enum Direction Dir;//蛇的移动方向,默认向右
enum Snake_Status Status;//获取游戏状态
int Score;//分数
int Food_Score;//每个食物分数
int Sleep_Time;//休眠时间
}Snake,*PSnake;
蛇的方向,可以一一列举,使用枚举
//蛇移动的方向
enum Direction
{
Up=1,
Down=2,
Left=3,
Right=4
};
游戏状态,可以一一列举,使用枚举
//游戏状态
enum Snake_Status
{
Ok = 1,//正常状态
Kill_By_Self,//撞向自己,自杀
Kill_By_Wall,//撞墙
End_Normal//正常结束
};
7.4 游戏流程设计
8. 核心逻辑实现分析
8.1 游戏主逻辑
8.2 游戏开始(GameStart)
• 创建第一个食物
void GameStart(pSnake snake)
{
//设置控制台窗⼝的⼤⼩,30⾏,100列
//mode 为DOS命令
system("mode con cols=100 lines=30");
//设置cmd窗⼝名称
system("title 贪吃蛇");
//打印欢迎界⾯
WelcomeToGame();
//打印地图
CreateMap();
//初始化蛇
InitSnake(ps);
//创造第⼀个⻝物
CreateFood(ps);
}
8.2.1 打印欢迎界面
//封装一个设置光标位置的函数
void SetPos(short x,short y)
{
//获取标准输出的句柄
HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x, y };
SetConsoleCursorPosition(output, pos);//设置控制台光标位置
}
void WelcomeToGame()
{
SetPos(40, 12);//定义光标位置
printf("欢迎来到贪吃蛇⼩游戏");
SetPos(40, 20);//让按任意键继续的出现的位置好看点
system("pause");//暂停
system("cls");//清理屏幕
SetPos(30, 9);
printf("⽤ ↑ . ↓ . ← . → 分别控制蛇的移动);
SetPos(30, 11);
printf("F3为加速,F4为减速\n");
SetPos(30, 13);
printf("加速将能得到更⾼的分数。\n");
SetPos(30, 17);//让按任意键继续的出现的位置好看点
system("pause");
system("cls");
}
8.2.2 创建地图
#define WALL L'□'
我们将这个定义函数放在头文件中更好。
void CreateMap()
{
int i = 0;
//上(0,0)-(56, 0)
SetPos(0, 0);
for (i = 0; i < 29; i += 2)
{
wprintf(L"%c", WALL);
}
//下(0,26)-(56, 26)
SetPos(0, 26);
for (i = 0; i < 29; 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 SnakeInit(PSnake snake)
{
//假设初始化时蛇的身体分为5节
PSnakeNode cur = NULL;//蛇的头节点的指针
int i = 0;
for (i = 0; i < 5; i++)
{
//创建蛇的头指针
cur=(PSnakeNode*)malloc(sizeof(PSnakeNode));
if (cur == NULL)
{
perror("cur malloc fail!");
}
//设蛇从 (POS_X=24,POS_Y=25) 开始移动
cur->next = NULL;
cur->x = POS_X * 2 - 1;
cur->y = POS_Y ;
}
//在这里我们运用头插法
if (snake->headSnake == NULL)//若头指针为空
{
snake->headSnake = cur;//让cur成为头指针
}
else//头指针不为空
{
cur->next = snake->headSnake;
snake->headSnake = cur;//重置头指针
}
cur = snake->headSnake;
while (cur)//对 cur 进行循环
{
//打印蛇的身体
SetPos(POS_X, POS_Y);
wprintf(L"%lc", Body);
cur = cur -> next;
}
//设置贪吃蛇的属性
snake->Dir = Right;//默认贪吃蛇向右移动
snake->Food_Score = 0;//默认总食物分值为0分
snake->pFood = 10;//默认食物分值为5分
snake->Sleep_Time = 200;//默认睡眠时间为200毫秒
snake->Status = Ok;//默认状态正常
}
(稍后更新......)