【详解】贪吃蛇游戏----上篇(介绍控制台和API等知识)

目录

知识点:

Win32 API

宽字符的打印

控制台操作:

(1)调整控制台大小

(2)控制台屏幕上的坐标COORD

GetStdHandle

GetConsoleCursorInfo

CONSOLE_CURSOR_INFO

SetConsoleCursorInfo

SetConsoleCursorPositio

SetPos:

GetAsyncKeyState

游戏实现 

GameStart

WelcomeToGame 

 CreateMap

 InitSnake

CreateFood

结语:


背景:随着c语言的学习,我们已经可以用c语言来完成一些小项目,贪吃蛇项目是对C语言语法做⼀个基本的巩固,帮助大家查缺补漏。

整个项目设计流程:

设计要求

(1)贪吃蛇地图绘制

(2)蛇吃食物的功能 (上、下、左、右⽅向键控制蛇的动作)

(3)蛇撞墙死亡

(4)蛇撞自身死亡

(5)计算得分

(6)蛇身加速、减速 

(7)暂停游戏

知识点:

C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。

前面几个大家都不陌生,为了完成贪吃蛇下面我带大家简要的了解一下Win32 API。下面设计API中的函数大家不必了解它是怎么写的,只要掌握怎么用即可。

Win32 API

Win32 API即为Microsoft 32位平台的应用程序编程接口(Application Programming Interface)。所有在Win32平台上运行的应用程序都可以调用这些函数。(简单来说就是win提供的函数)。

宽字符的打印

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

 在要打印的宽字符前加上L,wprintf(L"%c", WALL);,用wprintf打印,这里用宏定义只是为了下面方便。例如:

控制台操作:

贪吃蛇是在屏幕上显示的故我们要控制控制台的大小及一些基本操作。

(1)调整控制台大小

我们可以在编译器中用system("mode con cols=100 lines=10");语句改变控制台大小。

cols是列,lines是行。

(2)控制台屏幕上的坐标COORD

COORD是windowsAPI中定义的⼀个结构体,表示⼀个字符在控制台屏幕上的坐标。

typedef struct _COORD {

                SHORT X;

                SHORT Y;

} COORD, *PCOORD;

用法如下:COORD pos = { 10, 15 };

GetStdHandle

GetStdHandle 返回的句柄可供需要在控制台中进行读取或写入的应用程序使用。它是一个windows提供的函数。用法如下:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

GetConsoleCursorInfo

检索有关指定控制台屏幕缓冲区的游标大小和可见性的信息。用法如下:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

CONSOLE_CURSOR_INFO

这个结构体,包含有关控制台光标的信息,包含成员如下

typedef struct _CONSOLE_CURSOR_INFO {
        DWORD dwSize;
        BOOL bVisible;
    } CONSOLE_CURSOR_INFO, * PCONSOLE_CURSOR_INFO;

dwSize:由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从全填充单元格到单元底部的水平线条。 

bVisible:游标的可见性。 如果光标可见,则此成员为TRUE。

SetConsoleCursorInfo

设置指定控制台屏幕缓冲区的光标的大小和可见性。

该函数有两个参数,左边是传入GetStdHandle获得的手柄,右边是CONSOLE_CURSOR_INFO控制台光标的指针。

用法如下:

int main()
{HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
}

效果如下:

我们可以看到光标不见了。 

SetConsoleCursorPositio

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。

用法如下:

int main()
{COORD pos = { 10, 5 };HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);char ch = getchar();
}

效果如下:

SetPos:

与前面几个函数不同,这个函数由于用的经常且系统没有提供故要自己实现。

实现如下:

先获得输出句柄再进行操作。

//设置光标的坐标
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}

 用法:

int main()
{SetPos(10, 5);char ch = getchar();
}

效果:

GetAsyncKeyState

获取按键情况。

如果函数成功,则返回值指定自上次调用 GetAsyncKeyState 以来是否按下了该键,以及该键当前是启动还是关闭。如果设置了最高有效位,则密钥关闭,如果设置了最低有效位,则在上次调用 GetAsyncKeyState 后按下该密钥。

可以看到只需看最低为即可,如果最低为为1表示按键被按过,0表示没被按过。

我们可以用这样的写法来简便(封装)&1判断最低为是否为1.

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

游戏实现 

 由于代码较多故分4个模块来讲分别是1. 游戏开始 - 初始化游戏GameStart,2. 游戏运行 - 游戏的正常运行过程GameRun,3. 游戏结束 - 游戏善后(释放资源)GameEnd,4.test-把上面三个模块调用,并实现多次输入。

GameStart

代码如下:

GameStart里面我们要做好游戏的开始工作,先初始化控制台窗口大小和隐藏屏幕光标,打印欢迎界面,创建地图,初始化蛇身,创建食物用函数封装。

void GameStart(pSnake ps)
{//控制台窗口设置system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏屏幕光标HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//光标隐藏SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态//打印欢迎界面WelcomeToGame();//创建地图CreateMap();//初始化蛇身InitSnake(ps);//创建食物CreateFood(ps);
}

WelcomeToGame 

打印游戏开始界面,用SetPos设置控制台位置。下面的system("pause");system("cls");,pause是暂停程序,cls是清楚控制台。可以就可以有两重界面。

void WelcomeToGame()
{SetPos(40, 14);printf("欢迎来到贪吃蛇小游戏\n");SetPos(40, 25);system("pause");system("cls");SetPos(20, 14);printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, A是加速,B是减速");SetPos(40, 25);system("pause");system("cls");//char ch = getchar();
}

效果如下:

随便按下一个键。

 CreateMap

打印地图,由于要使用到宽字符,故要用wprintf来打印,%c前面要加一个L。

void CreateMap()
{SetPos(0, 0);int i = 0;for (i = 0; i <= 56; i += 2){wprintf(L"%c", WALL);}SetPos(0, 26);for(i=0;i<=56;i+=2){wprintf(L"%c", WALL);}for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%c", WALL);}for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%c", WALL);}
}

 InitSnake

初始化蛇,其实整个游戏的过程都是在维护贪吃蛇。贪吃蛇结构体如下:

pSnakeNode _pSnake;//指向贪吃蛇头结点的指针.

pSnakeNode _pFood;//指向食物结点的指针

int _Score;//贪吃蛇累计的总分

int _FoodWeight;//一个食物的分数

int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢

enum DIRECTION _Dir;//描述蛇的方向

enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己

下面两个是枚举类型。

 enum DIRECTION//方向
{
    UP = 1,
    DOWN,
    LEFT,
    RIGHT
};
enum GAME_STATUS//状态
{
    OK,
    KILL_BY_WALL,
    KILL_BY_SELF,
    END_NOMAL
};

typedef struct Snake//蛇
{
    pSnakeNode _pSnake;
    pSnakeNode _pFood;
    enum DIRECTION _Dir;
    enum GAME_STATUS _Status;
    int _Score;
    int _FoodWeight;
    int _SleepTime;
}Snake, * pSnake;

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 0;}cur->next = NULL;cur->x = POS_X + 2 * i;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->_Status = OK;ps->_Score = 0;ps->_SleepTime = 200;ps->_pFood = NULL;ps->_FoodWeight = 10;ps->_Dir = RIGHT;}

CreateFood

 创建食物,使用rand随机生成一个数至于为什么要模上53.

可以看到我们设置的控制台大小,游戏界面大小为56x26,这里要特别注意的是,大家可以看看自己编译器后面的控制台窗口,x轴和y轴的比例是1:2,也就是说一个宽字符占一个y即可,而一个宽字符要占两个x。故x的坐标必须为偶数,防止食物和蛇身一半在墙里一半在地图里。

故坐标为:              

(0,0)(56,0)
(0,26)(56.26)
(0,1)(0,25)
(56,1)(56,25)

void CreateFood(pSnake ps)
{int x = 0;int y = 0;
again: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;}pFood->x = x;pFood->y = y;ps->_pFood = pFood;SetPos(x, y);wprintf(L"%c", FOOD);//getchar();
}

效果如下蛇先不用看后面会讲。

总结:由于代码量较多为了给大家解释清楚贪吃蛇项目分为两篇文章来描述。

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/650024.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cesium工具应用

文章目录 0.引言1.场景截图2.卷帘对比3.反选遮罩4.鹰眼视图5.指南针与比例尺6.坐标测量7.距离测量8.面积测量9.热力图10.视频投影11.日照分析12.淹没分析13.通视分析14.可视域分析15.缓冲区分析16.地形开挖17.要素聚合18.开启地下模式19.开启等高线20.坡度坡向21.填挖方量计算2…

排序(插入排序)

现在&#xff0c;我们学习了之前数据结构的部分内容&#xff0c;即将进入一个重要的领域&#xff1a;排序&#xff0c;这是一个看起来简单&#xff0c;但是想要理清其中逻辑并不简单的内容&#xff0c;让我们一起加油把&#xff01; 排序的概念及其运用 排序的概念 排序&…

解释性人工智能(XAI)—— AI 决策的透明之道

在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为我们生活中不可或缺的一部分。AI 系统的决策和行为对我们的生活产生了深远的影响&#xff0c;从医疗保健到金融服务再到自动驾驶汽车。 然而&#xff0c;有时候 AI 的决策似乎像黑盒子一样难以理解&am…

[C#]winform部署yolov5实例分割模型onnx

【官方框架地址】 https://github.com/ultralytics/yolov5 【算法介绍】 YOLOv5实例分割是目标检测算法的一个变种&#xff0c;主要用于识别和分割图像中的多个物体。它是在YOLOv5的基础上&#xff0c;通过添加一个实例分割模块来实现的。 在实例分割中&#xff0c;算法不仅…

Redis2-事务 连接Java 整合springboot 注解缓存

一、订阅和发布 Redis 发布订阅 (pub/sub) 是一种消息通信模式&#xff1a;发送者 (pub) 发送消息&#xff0c;订阅者 (sub) 接收消息。 Redis 客户端可以订阅任意数量的频道。 Redis的发布和订阅 客户端订阅频道发布的消息 频道发布消息 订阅者就可以收到消息 发布订阅的代…

ENVI下基于知识决策树提取地表覆盖信息

基于知识的决策树分类是基于遥感影像数据及其他空间数据,通过专家经验总结、简单的数学统计和归纳方法等,获得分类规则并进行遥感分类。分类规则易于理解,分类过程也符合人的认知过程,最大的特点是利用的多源数据。 决策树分类主要的工作是获取规则,本文介绍使用CART算法…

NQA测试机制—UDP Jitter测试

概念 UDP Jitter是以UDP报文为承载&#xff0c;通过记录在报文中的时间戳信息来统计时延、抖动、丢包的一种测试方法。Jitter&#xff08;抖动时间&#xff09;是指相邻两个报文的接收时间间隔减去这两个报文的发送时间间隔。 UDP Jitter测试的过程如下&#xff1a; 1. 源端&a…

shell编程之循环语句与函数

一 echo命令 echo -n 表示不换行输出 echo -e 表示输出转义符 常用的转义符 二 date date查看当前系统时间 -d 你描述的日期&#xff0c;显示指定字符串所描述的时间&#xff0c;而非当前时间 %F 完整日期格式&#xff0c;等价于 %Y-%m-%d % T 时间&#xff08;24小时…

1.26学习总结

连通性判断 DFS连通性判断步骤&#xff1a; 1.从图上任意一点u开始遍历&#xff0c;标记u已经走过 2.递归u的所有符合连通条件的邻居点 3.递归结束&#xff0c;找到了的所有与u的连通点&#xff0c;就是一个连通块 4.然后重复这个步骤找到所有的连通块 BFS连通性判断步骤…

linux 查看zookeeper server运行版本号

zookeeper版本查看运行命令&#xff1a;echo stat|nc localhost 2181 显示如下图所示&#xff1a; Zookeeper version: 3.4.5-cdh6.3.2--1, built on 11/08/2019 13:15 GMT Clients: /127.0.0.1:44814[0](queued0,recved1,sent0) Latency min/avg/max: 0/0/0 Received: 9 Se…

防火墙的NAT

目录 1. NAT 概念解析 2. 配置NAT策略&#xff1a; 1. NAT 概念解析 静态NAT --- 一对一 动态NAT --- 多对多 NAPT --- 一对多的NAPT --- easy ip --- 多对多的NAPT 服务器映射 源NAT --- 基于源IP地址进行转换。我们之前接过的静态NAT&#xff0c;动态NAT&#xff0c;NAPT都属…

*【艺恩娱数】Python爬虫+数据分析可视化中国影院票房*¶

文章目录 一、记得登入才能看到所有的数据二、使用步骤艺恩数据可视化艺恩影院票房Top10艺恩影院票房销售额对比艺恩影院票房省份人次分析艺恩影院场次top10榜单 这个里面的影院名称&#xff0c;省份&#xff0c;城市&#xff0c;票房&#xff0c;场次&#xff0c;人次&#xf…

ESXI 本地和虚拟机之间可以自由复制和粘贴

文章目录 ESXI 本地和虚拟机之间可以自由复制和粘贴 ESXI 本地和虚拟机之间可以自由复制和粘贴 web访问esxi&#xff0c;然后&#xff1a; 1、右击新建的虚拟机&#xff0c;确保是在关机状态下&#xff0c;点击编辑设置 2. 找到 虚拟机选项→高级→常规→配置参数 3、点击添加…

Unity3d C#实现三维场景中图标根据相机距离动态缩放功能

前言 如题的需求&#xff0c;其实可以通过使用UI替代场景中的图标来实现&#xff0c;不过这样UI的处理稍微麻烦&#xff0c;而且需要在图标上添加粒子特效使用SpriteRender更方便快捷。这里就根据相机离图标的位置来计算图标的缩放大小即可。这样基本保持了图标的大小&#xf…

Vulnhub靶场DC-3

本机192.168.223.128 靶机192.168.223.139 目标发现nmap -sP 192.168.223.0/24 端口扫描nmap -p- 192.168.223.139 之开启了一个80端口 看一下是什么服务 nmap -sV -p- -A 192.168.223.139是一个apache服务&#xff0c;joomla模板 看一下web 没什么有用信息。 扫描一下后台…

华为HCIP Datacom H12-831 卷18

判断题 1、对于同一个MAC地址,手工配置的MAC表项优先级高于动态的表项,某二层报文的源MAC地址已经绑定在了交换机的GEO/0/1接口,当交换机从GEO/0/2收到该报文时,会丢弃该报文 A 对 B 错 正确答案 A 解析:为了提高接口安全性,网络管理员可手工在MAC地址表中加入特定M…

银行数据仓库体系实践(3)--数据架构

狭义的数据仓库数据架构用来特指数据分布&#xff0c;广义的数据仓库数据架构还包括数据模型、数据标准和数据治理。即包含相对静态部分如元数据、业务对象数据模型、主数据、共享数据&#xff0c;也包含相对动态部分如数据流转、ETL、整合、访问应用和数据全生命周期管控治理。…

在 Vue 项目中,可以通过设置不同的环境变量来区分不同的环境,例如本地开发环境、测试环境和生产环境。以下是设置环境变量的步骤:

1、在src下新建三个文件夹 &#xff08;.env.local、.env.test 和 .env.prod&#xff09; 2、配置信息 .env.local VUE_APP_ENVlocal VUE_APP_API_URLhttp://localhost:8080.env.test VUE_APP_ENVtest VUE_APP_API_URLhttp://124.220.110.203:9090/ .env.prod VUE_APP_…

Android源码设计模式解析与实战第2版笔记(一)

第一章 走向灵活软件之路 — 面向对象的六大原则 优化代码的第一步 — 单一职责原则 单一职责原则的英文名称是Single Responsibility Principle&#xff0c;缩写是SRP。 SRP&#xff1a;就一个类而言&#xff0c;应该仅有一个引起它变化的原因。 一个类中应该是一组相关性很…

Redis创建集群

主要内容 搭建redis集群 能力目标 搭建redis集群 一 应用场景 为什么需要redis集群&#xff1f; 当主备复制场景&#xff0c;无法满足主机的单点故障时&#xff0c;需要引入集群配置。 一般数据库要处理的读请求远大于写请求 &#xff0c;针对这种情况&#xff0c;我们优…