【详解】贪吃蛇游戏----上篇(介绍控制台和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.hqwc.cn/news/433353.html

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

相关文章

Tortoise-tts Better speech synthesis through scaling——TTS论文阅读

笔记地址&#xff1a;https://flowus.cn/share/a79f6286-b48f-42be-8425-2b5d0880c648 【FlowUs 息流】tortoise 论文地址&#xff1a; Better speech synthesis through scaling Abstract: 自回归变换器和DDPM&#xff1a;自回归变换器&#xff08;autoregressive transfo…

RabbitMQ问题总结

:::info 使用场景 异步发送&#xff08;验证码、短信、邮件。。。&#xff09;MySQL 和 Redis、ES 之间的数据同步分布式事务削峰填谷… ::: 如何保证消息不丢失 上图是消息正常发送的一个过程&#xff0c;那在哪个环节中消息容易丢失&#xff1f;在哪一个环节都可能丢失 生…

C#用 DateAndTime.DateAdd方法和DateTime.Add(TimeSpan) 方法分别添加一段时间间隔

目录 一、基本方法 1.用 DateAndTime.DateAdd方法添加一段时间间隔 2.用DateTime.Add方法添加一段时间间隔 二、实例 1.实例1&#xff1a;用 DateAndTime.DateAdd方法 2.实例2&#xff1a;用DateTime.Add方法 一、基本方法 1.用 DateAndTime.DateAdd方法添加一段时间间隔…

【STM32】STM32学习笔记-BKP备份寄存器和RTC实时时钟(42)

00. 目录 文章目录 00. 目录01. BKP简介02. BKP特性03. BKP基本结构04. RTC简介05. RTC主要特性06. RTC框图07. RTC基本结构08. 硬件电路09. RTC操作注意事项10. 附录 01. BKP简介 备份寄存器是42个16位的寄存器&#xff0c;可用来存储84个字节的用户应用程序数据。他们处在备…

物联网IOT视频设备如何快速对接阿里云生活物联网(Link Visual)并成功上云?

原文永久更新地址&#xff1a;https://www.yundashi168.com/472.html 文章来源&#xff1a;猿视野 如果有图片看不清楚&#xff0c;加载不出来&#xff0c;请阅读原文。 什么是Link Visual、 Link Visual是生活物联网平台针对视频产品推出的增值服务&#xff0c;提供视频数据上…

操作系统(4)---虚拟机

虚拟机又叫虚拟机管理程序或者虚拟机监控程序&#xff08;Virtual Machine Monitor/Hypervisor&#xff0c;VMM&#xff09;&#xff0c;使用虚拟化技术&#xff0c;将一台物理机器虚拟化为多台虚拟机器 (Virtual Machine,VM)&#xff0c;每个虚拟机器都可以独立运行一个操作系…

2024 高级前端面试题之 CSS 「精选篇」

该内容主要整理关于 CSS 的相关面试题&#xff0c;其他内容面试题请移步至 「最新最全的前端面试题集锦」 查看。 CSS模块精选篇 1. 盒模型2. BFC3. 层叠上下文4. 居中布局5. 选择器权重计算方式6. 清除浮动7. link 与 import 的区别8. CSS3的新特性9. CSS动画和过渡10. 有哪些…

跨境防诈指南 | 了解美国电商持续遭遇的“超额支付”欺诈

目录 常见的“超额支付”欺诈类型 假支票诈骗 虚假信用卡欺诈 基于交易的洗钱诈骗 防止“超额支付”欺诈 增强交易安全保障 加强异常交易识别 借助反欺诈技术识别 加强团队欺诈培训 美国商业委员会的统计报告显示&#xff0c;2023年年1至6月&#xff0c;联邦贸易委员会&#xf…

【小白教程】幻兽帕鲁服务器一键搭建 | 支持更新 | 自定义配置

幻兽帕鲁刚上线就百万在线人数&#xff0c;官方服务器的又经常不稳定&#xff0c;所以这里给大家带来最快捷的搭建教程&#xff0c;废话不多说直接开始。 步骤一&#xff1a;准备服务器 服务器建议 Linux 系统&#xff0c;资源占用低&#xff0c;而且一键脚本只需要一条命令&am…

【unity小技巧】受伤屏幕闪红、死亡动画、死亡黑屏效果

文章目录 玩家受伤配置人物死亡动画死亡黑屏效果完结 玩家受伤 玩家受伤&#xff0c;屏幕显示血框UI&#xff0c;然后逐渐消失 //玩家受击时调用 void GetHit(){StartCoroutine(BloodyScreenEffect()); }private IEnumerator BloodyScreenEffect() {// 检查bloodyScreen是否处…

虚拟机内使用 archinstall 安装 arch linux 2024.01.01

文章目录 [toc]前言碎语安装 arch linuxArchinstall languageMirrorsLocalesDisk configurationBootloaderSwapHostnameRoot passwordUser accountProfileAudioKernelsAdditional packagesNetwork configurationTimezoneAutomatic time syncOptional repositoriesInstall 进入桌…

微软 Power Apps Canvas App 画布应用将上传的附件转化为base64编码操作

微软 Power Apps Canvas App 画布应用将上传的附件结合Power Automate转化为base64编码操作 在使用canvas app的过程中&#xff0c;我们有时需要将上传的文件转换为base64存入数据库或者&#xff0c;调用外部接口传参&#xff0c;那么看下如何将文件转化为base64编码格式。 首先…