项目实践-贪吃蛇-上(作为C语言学习的收尾)
目录
前言
在前面几节我们学习了数据结构中的顺序表单链表以及双向链表,这些不仅仅是为后面继续学习数据结构打下扎实的基础,也是为接下来我将讲解的内容做好铺垫,不仅仅是这些,还要运用C语言绝大多数知识,那就是实现一个久负盛名的游戏:贪吃蛇。所以这个游戏的实现也算是对之前C语言的学习收一个尾。而由于这个项目内容量以及代码量非常之多,所设计的新内容也要详细为大家讲解,所以贪吃蛇游戏的实现我分为上下两节:上一节主要是把准备工作做好,而下一节就是对贪吃蛇游戏代码进行设计与分析,希望对大家学习编程提供帮助!
一、项目目标
使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。
实现基本的功能:
(1)贪吃蛇地图绘制
(2)蛇吃食物的功能(上、下、左、右方向键控制蛇的动作)
(3)蛇撞墙死亡
(4)蛇撞自身死亡
(5)计算得分
(6)蛇身加速、减速
(7)暂停游戏
二、项目技术要点
C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。
那这里有些朋友要问了前面的东西不管是在C语言还是数据结构都已经学习到了,但最后一个Win32 API又是什么呢?接下来我就为大家好好讲解一下。
三、Win32 API
1、Win32 API的介绍
Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的。
由于这些函数服务的对象是应用程序(Application),所以便称之为 Application Programming Interface,简称 API函数。WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口。
2、控制台程序(Console)
平常我们运行起来的黑框程序其实就是控制台程序
我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小:30行,100列
mode con cols=100 lines=30

也可以通过命令设置控制台窗口的名字:
title 贪吃蛇

而这些能在控制台窗口执行的命令,也可以调用C语言函数system来执行。但首先要说一下有关调试控制台的问题:当在运行代码完大家可能会有两种调试控制台:


调试控制台中的终端应用程序总共有三种情况:(1)让Windows 决定;(2)Windows 终端;(3)Windows 控制台主机
而我们想要实现贪吃蛇游戏项目就必须要用第二张图的效果,但我自己发现第二张图的效果可能是第一种情况:让Windows 决定,也可能是第三种情况:Windows 控制台主机。不管是哪种情况只要控制台效果为第二张图即可,那怎么去修改呢?
鼠标右击控制台上沿边框会有设置/属性,在里面进行设置即可

设置好后我们就可以调用C语言函数system来执行系统命令了:
//演示代码.c
#include <stdio.h>
#include <stdlib.h>
// system 函数可以用来执行系统命令
int main()
{
//设置控制台的相关属性
system("mode con cols=50 lines=30");
//设置控制台窗口名称
system("title 贪吃蛇");
system("pause");
return 0;
}

3、控制台屏幕上的坐标COORD
COORD是WindowsAPI中定义的一个结构体,表示一个字符在控制台屏幕上的坐标,坐标系(0,0)d的原点位于控制台屏幕的顶部左侧单元格。
![]()
COORD 结构体的声明为:
typedef struct _COORD {
SHORT X; //X轴坐标
SHORT Y; //Y轴坐标
} COORD, *PCOORD;
而想要调用这个坐标的结构体需要包含一个头文件:#include <windows.h>
//演示代码.c
#include <windows.h>
int main()
{
COORD pos1 = { 0, 0 };
COORD pos2 = { 10, 20 };
return 0;
}
4、GetStdHandle
GetStdHandle 是一个 Windows API 函数。它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。
句柄这个词比较抽象,可以理解成类似门的把手,你开关门要握着把手才能进行,句柄就类似一种权限或者能力,让我们能够利用句柄来操作特定的标准设备。
GetStdHandle的声明:
HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle);
形参 nStdHandle 有以下三种取值:
| 值 | 含义 |
| STD_INPUT_HANDLE | 标准输入设备 |
| STD_OUTPUT_HANDLE | 标准输出设备 |
| STD_ERROR_HANDLE | 标准错误设备 |
//演示代码.c
int main()
{
//获取标准输出设备的句柄
HANDLE houtput = NULL; // HANDLE 的类型就是 void* 类型,所以初始化可以NULL接收
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
return 0;
}
5、GetConsoleCursorInfo
首先这个函数翻译一下意思就是获取控制台光标信息,那就好理解了,但为什么我们要用这个函数呢?我们先看一个情况:

我们会发现每次运行代码在没有结束之前,末尾总是会有一个光标在闪烁,而我们要实现贪吃蛇游戏时,蛇在移动时总不能有个光标一直在闪烁吧,所以怎么样才能对光标进行修改甚至让它消失呢?这就要用到上面的函数了。
GetConsoleCursorInfo 函数指的是:
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。其声明为:
BOOL WINAPI GetConsoleCursorInfo(
_In_ HANDLE hConsoleOutput,
_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);;
第一个形参 hConsoleOutput 就是上面一个函数 GetStdHandle 的返回值,即获取到的特定的标准设备的句柄。
第二个形参 lpConsoleCursorInfo 的类型 PCONSOLE_CURSOR_INFO 我们之前是从来没有见过的,这个类型是Windows32 API自己定义的,指的是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关控制台游标的信息。那 CONSOLE_CURSOR_INFO 结构又是什么呢?
CONSOLE_CURSOR_INFO 其实是一个结构体,包含有关控制台光标的信息。
其声明为:
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
第一个成员变量dwSize:由光标填充的字符单元格的百分比。此值介于1到100之间,光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
第二个成员变量bVisible:游标的可见性。如果光标可见,则此成员为TRUE。
到此通过以上两个函数的讲解我们首先可以先查看一下控制台光标的信息:
//演示代码.c
#include <stdbool.h>
int main()
{
//获取标准输出设备的句柄
HANDLE houtput = NULL; // HANDLE 的类型就是 void* 类型,所以初始化可以NULL接收
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info = { 0 }; //创建类型为 CONSOLE_CURSOR_INFO 的结构体
GetConsoleCursorInfo(houtput, &cursor_info);
//第一个实参houtput意思就是我们是对“标准输出设备”的光标进行信息获取
//第二个实参&cursor_info意思就是我们获取到的光标信息会存放在cursor_info中
printf("%d\n", cursor_info.dwSize);
printf("%d\n", cursor_info.bVisible);
//由于成员bVisible的类型为BOOL,所以要包含头文件#include <stdbool.h>
return 0;
}

第一个值25指的就是光标填充的字符单元格的百分比为25%,第二个值为1指的就是TURE,也就是光标可见。由前面的光标样子发现的确如此。
6、SetConsoleCursorInfo
我们已经知道了初始默认光标的信息,那怎么去修改呢?是直接对结构体成员进行访问修改就可以完成吗?其实并不是,而是要用到另一个函数即 SetConsoleCursorInfo。
SetConsoleCursorInfo 函数指的是:
设置指定控制台屏幕缓冲区的光标的大小和可见性。其声明为:
BOOL WINAPI SetConsoleCursorInfo(
_In_ HANDLE hConsoleOutput,
_In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
当我们修改完光标信息后必须要调用该函数我们所修改的操作才可被执行:
#include <stdbool.h>
int main()
{
//获取标准输出设备的句柄
HANDLE houtput = NULL; // HANDLE 的类型就是 void* 类型,所以初始化可以NULL接收
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info = { 0 }; //创建类型为 CONSOLE_CURSOR_INFO 的结构体
GetConsoleCursorInfo(houtput, &cursor_info);
//第一个实参houtput意思就是我们是对“标准输出设备”的光标进行信息获取
//第二个实参&cursor_info意思就是我们获取到的光标信息会存放在cursor_info中
cursor_info.dwSize = 100;
SetConsoleCursorInfo(houtput, &cursor_info);
system("pause");
return 0;
}

那怎么将光标不可见呢?我们知道TRUE表示的是非0值,所以不可见FALSE表示为0
#include <stdbool.h>
int main()
{
//获取标准输出设备的句柄
HANDLE houtput = NULL; // HANDLE 的类型就是 void* 类型,所以初始化可以NULL接收
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info = { 0 };//创建类型为 CONSOLE_CURSOR_INFO 的光标信息结构体
GetConsoleCursorInfo(houtput, &cursor_info);
//修改光标的信息:
cursor_info.dwSize = 100;
cursor_info.bVisible = 0;
//再次设置和houtput句柄相关的控制台光标的信息
SetConsoleCursorInfo(houtput, &cursor_info);
system("pause");
return 0;
}

7、SetConsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
其声明为:
BOOL WINAPI SetConsoleCursorPosition(
_In_ HANDLE hConsoleOutput,
_In_ COORD dwCursorPosition
);
//演示代码.c
#include <windows.h>
int main()
{
//获取标准输出设备的句柄
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定位光标的位置
COORD pos = { 40, 10 };
SetConsoleCursorPosition(houtput, pos);
getchar();
return 0;
}

但是我们说贪吃蛇游戏不可能就只设置一次光标位置吧而且肯定非常多,那我们难道要重复去写以上代码吗?那肯定很麻烦,所以我们可以将设置光标位置单独封装成一个函数Setpos:
//演示代码.c
//将定义光标位置封装成函数:
void Setpos(short x, short y)//因为COORD结构体成员类型为short,所以要保持一致
{
//获取标准输出设备的句柄
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定位光标的位置
COORD pos = { x, y };
SetConsoleCursorPosition(houtput, pos);
}
int main()
{
Setpos(40, 10);
getchar();
return 0;
}
运行的结果光标位置是一样的,但封装成函数后就可以更加方便进行定位光标位置。
8、GetAsyncKeyState
获取按键情况。当看到这个函数的作用想必大家都应该知道这个函数的大致用处了吧,我们在玩电脑游戏,在键盘按下按键电脑里有响应时靠的就是这个函数。其声明为:
SHORT GetAsyncKeyState(
int vKey
);
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState 的返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1,则说明该按键被按过,否则为0。
如果我们要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1:
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 1) ? 1 : 0 )
//演示代码.c
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 1) ? 1 : 0 )
int main()
{
while (1)
{
if (KEY_PRESS(0x30))//对应键盘0键
{
printf("0\n");
}
else if (KEY_PRESS(0x31))//对应键盘1键
{
printf("1\n");
}
else if (KEY_PRESS(0x32))//对应键盘2键
{
printf("2\n");
}
else if (KEY_PRESS(0x33))//对应键盘3键
{
printf("3\n");
}
}
}

结束语
到此贪吃蛇游戏项目的上一节也就讲解完了,虽然还没涉及到贪吃蛇代码,但本篇主要就是准备工作,把贪吃蛇需要用到的Win32 API的相关知识为大家进行讲解。而接下来就是对贪吃蛇游戏进行设计与分析以及代码实现了,希望这篇文章对大家了解学习Win32 API能有所帮助!
更多推荐

所有评论(0)