贪吃蛇游戏实现(VS编译环境)

贪吃蛇游戏

🥕个人主页:开敲🍉

🔥所属专栏:C语言🍓

🌼文章目录🌼

0. 前言

1. 游戏背景

2. 实现后游戏画面展示

3. 技术要求

4. Win32 API介绍

  4.1 Win32 API

  4.2 控制台程序

  4.3 控制台屏幕上的光标

  4.4 GetStdHandle

4.5 GetConsoleCursorInfo

    4.5.1 CONSOLE_CURSOR_INFO

  4.6 SetConsoleCursorPosition

  4.7 GetAsyncKeyState

5. 贪吃蛇游戏设计与分析

  5.1 地图

     5.1.1 本地化

    5.1.2 类项

    5.1.3 setlocale函数

    5.1.3 宽字符的打印

  5.2 蛇身和食物

  5.3 数据结构设计

  5.4 游戏流程设计

6. 核心逻辑实现分析

  6.1 游戏主逻辑

  6.2 初始化游戏

  6.3 游戏运行

    6.3.1 调整贪吃蛇的移动

  6.4 结束游戏

0. 前言

  游戏实现的源码放在了:贪吃蛇游戏源码(VS编译环境)-CSDN博客 中,需要的可以自行拷贝。

1. 游戏背景

  贪吃蛇是久负盛名的游戏,它和俄罗斯方块、扫雷等游戏位列经典游戏行列。游戏的玩法也非常简单,玩家操控一条蛇,通过不断地吃食物来延长自己的身体,如果途中蛇头撞到了墙壁或者自己的身体,游戏就失败了。

2. 实现后游戏画面展示

  

3. 技术要求

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

4. Win32 API介绍

  本次贪吃蛇的实现会用到一些Win32 API的知识,接下来我们一起学习一些Win32 API的知识。

  4.1 Win32 API

  Windows系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以便称之为ApplicationProgrammingInterface,简称API函数。

WIN32API也就是MicrosoftWindows32位平台的应用程序编程接口。

  4.2 控制台程序

  在我们电脑上搜索cmd后跳出来的黑框框就是控制台程序。

  我们可以使用cmd命令来设置控制台窗口的长、宽:mode命令

mode con cols=100 lines=30   //将控制台窗口行数设为30,列数设为100

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

  这些能在控制台窗口执行的命令,也可以通过调用C语言库函数system来执行。例如:

  4.3 控制台屏幕上的光标

  COORD是Windows API中定义的一个结构体,表示控制台光标在控制台屏幕上的坐标,坐标系的原点(0,0)在缓冲区的控制台屏幕的左上角。

  COORD类型的声明:

给坐标赋值:

COORD pos = {1,1};

  4.4 GetStdHandle

  GetStdHandle是⼀个Windows API函数。它用于从⼀个特定的标准设备(标准输入、标准输出或标准错误)中取得⼀个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。

HANDLE  GetStdHandle(DWORD  nStdHandle);//返回类型为HANDLE

使用实例:

HANDLE  houtput = NULL;    //创建一个HANDLE类型的变量

houtput = GetStdHandle(STD_OUTPUT_HANDLE)  //这里GetStdHandle中的参数表示获取当前控制台窗口的句柄(可以理解为拿到了当前控制台窗口的地址,从而能够操作当前控制台窗口)

  

4.5 GetConsoleCursorInfo

  用于检索指定控制台屏幕光标信息:

1  BOOL WINAPI GetConsoleCursorInfo(
2  HANDLE hConsoleOutput,
3  PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
4  );
5  PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构6  接收指定控制台屏幕光标

 

实例:

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

    4.5.1 CONSOLE_CURSOR_INFO

  这是个结构体类型,用于存放指定控制台屏幕光标的信息:

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

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

1  CursorInfo.bVisible  =  false; //隐藏控制台光标

  4.6 SetConsoleCursorPosition

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

实例:

1  COORD pos = { 10, 5};//设置光标坐标
2  HANDLE hOutput = NULL;
//获取标准输出的句柄(用来标识不同设备的数值)
4  hOutput  =  GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
6  SetConsoleCursorPosition(hOutput, pos);

封装一个设置光标的函数,以便实现贪吃蛇时能快速方便地设置光标位置:

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

  4.7 GetAsyncKeyState

  读取键盘按键情况,函数原型如下:

  将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。

  GetAsyncKeyState 的返回值是short类型,在上⼀次调用 GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。如果我们要判断⼀个键是否被按过,可以检测
GetAsyncKeyState返回值的最低值是否为1。

  这里我们可以使用一个宏来快速地判断某一案件是否被按过:

  其中VK传的就是想要知道有没有被按过的键的虚拟键值,键盘各键位虚拟键值表如下:

                            虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn      

实例:检测数字键

1   #include <stdio.h>
2   #include <windows.h>
3   int main()
4   {
5    while (1)
6     {
           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;
  }

 

5. 贪吃蛇游戏设计与分析

  5.1 地图

我们最终的贪吃蛇大纲要是这个样子,那我们的地图如何布置呢?

  这里不得不讲⼀下控制台窗口的⼀些知识,如果想在控制台的窗口中指定位置输出信息,我们得知道该位置的坐标,所以首先介绍⼀下控制台窗口普的坐标知识。
  控制台窗口的坐标如下所示,横向的是X轴,从左向右依次增长,纵向是Y轴,从上到下依次增长。

  在游戏地图上,我们打印墙体使用宽字符:'□',打印蛇使用宽字符'●',打印食物使用宽字符'★'
普通的字符是占⼀个字节的,这类宽字符是占用2个字节。
这里再简单的讲⼀下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。
  C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适用。
  C语言字符默认是采用ASCII编码的,ASCII字符集采用的是单字节编码,且只使用了单字节中的低7位,最高位是没有使用的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够用的,但是,在其他国家语言中,比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,⼀些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel(),在俄语编码中又会代表另⼀个符号。但是不管怎样,所有这些编码方式中,0--127表示的符号是一样的,不一样的只是128--255的这一段。
至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。⼀个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达⼀个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示⼀个汉字,所以理论上最多可以表示256x256 = 65536个符号。后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入了宽字符的类型wchar_t 和宽字符的输入和输出函数,加入了<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

     5.1.1 <locale.h>本地化

  <locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。
在标准中,依赖地区的部分有以下几项:

  数字量的格式

  ② 货币量的格式

  ③ 字符集

  ④ 日期和时间的表示形式

    5.1.2 类项

  通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中⼀部分可能是我们不希望修改的。所以C语言⽀持针对不同的类项进行修改,下面的⼀个宏,指定⼀个类项:

  ① LC_COLLATE:影响字符串比较函数 strcoll() 和 strxfrm() 。

  ② LC_CTYPE:影响字符处理函数的行为。

  ③ LC_MONETARY:影响货币格式。

  ④ LC_NUMERIC:影响 printf() 的数字格式。

  ⑤ LC_TIME:影响时间格式 strftime() 和 wcsftime() 。

  ⑥ LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语言环境。

每个类项的详细说明可以参考:setlocale,_wsetlocale | Microsoft Learn

    5.1.3 setlocale函数

1  char* setlocale (int category, const char* locale);

  setlocale函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。
setlocale的第⼀个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。
  C标准给第二个参数仅定义了2种可能取值:"C"(正常模式)和""(本地模式)。
在任意程序执行开始,都会隐藏式执行调用:

1  setlocale(LC_ALL, "C");

  当地区设置为"C"时,库函数按正常方式执行,小数点是⼀个点。
当程序运行起来后想改变地区,就只能显示调用setlocale函数。⽤""作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。

1  setlocale(LC_ALL, " ");//切换到本地环境

    5.1.3 宽字符的打印

  那如果想在屏幕上打印宽字符,怎么打印呢?
宽字符的字面量必须加上前缀“L”,否则C语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面,表示宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前面,表示宽字符串,对应wprintf() 的占位符为 %ls 。

1  #include <stdio.h>
2  #include<locale.h>
3  int main()

{
4  setlocale(LC_ALL, "");
5  wchar_t ch1 = L'●';

6  wchar_t ch2 = L'■';
7  wchar_t ch3 = L'★';
8  wprintf(L"%lc\n", ch1);
9  wprintf(L"%lc\n", ch2);
10  wprintf(L"%lc\n", ch3);
11  return 0;
 }

  5.2 蛇身和食物

  初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的⼀个坐标处,比如(24,5)处开始出现蛇,连续5个节点。
  注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半出现在墙体中,另外⼀半在墙外的现象,坐标不好对齐。
  关于食物,就是在墙体内随机生成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。

  5.3 数据结构设计

  在游戏运行的过程中,蛇每次吃⼀个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,所以蛇节点结构如下:

要管理整条贪吃蛇,我们再封装⼀个Snake的结构来维护整条贪吃蛇:

蛇的方向总共只有:上、下、左、右四个方向,因此我们可以使用枚举:

游戏的状态也无非就是:正常进行、撞到墙壁、撞到自己、正常退出四种状态,因此我们也可以使用枚举:

  5.4 游戏流程设计

6. 核心逻辑实现分析

  6.1 游戏主逻辑

  程序开始就设置程序支持本地模式,然后进入游戏的主逻辑。
  主逻辑分为3个过程:

  游戏初始化(InitGame)   完成游戏的初始化

  ② 游戏运行(GameRun)     完成游戏运行逻辑的实现

  ③ 游戏结束(GameOver)    完成游戏结束后的善后工作(释放动态开辟的空间)

  6.2 初始化游戏

  这个模块所需要完成的任务:

  控制台窗口大小的设置

  ② 控制台窗口名字的设置

  ③ 鼠标光标的隐藏

  ④ 打印欢迎界面

  ⑤ 创建地图

  ⑥ 初始化游戏开始时蛇的长度

  ⑦ 创建第一个食物

InitWelcome函数:

CreatGameMap函数:

InitSnake函数:

CreatFood函数:

  6.3 游戏运行

  游戏运行期间,右侧打印帮助信息,提示玩家,坐标开始位置(64,15)
根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。
如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。

  需要用到的虚拟键:

  ① 上:VK_UP

  ② 下:VK_DOWN

  ③ 左:VK_LEFT

  ④ 右:VK_RIGHT

  ⑤ W:0x57

  ⑥ A:0x41

  ⑦ S:0x53

  ⑧ D:0x44

  确定了蛇的方向以后,就可以实现蛇移动的函数了:

NextNodeWhetherFood函数:

EatFood函数:

NotFood函数:

KillByWall函数:

KillBySelf函数:

    6.3.1 调整贪吃蛇的移动

  需要根据玩家按下的键来调整贪吃蛇移动的方向、速度:

Pause函数:

  6.4 结束游戏

根据最终结束游戏时游戏的状态判断是因为什么而结束的游戏:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用脚本启动和关闭微服务

使用脚本启动和关闭微服务 一、前言二、启动1、处理每个服务2、编写启动脚本3、其他启动脚本&#xff08;无效&#xff0c;有兴趣可以看看&#xff09;4、启动 三、关闭1、测试拿服务进程id的命令是否正确2、编写关闭脚本3、关闭 一、前言 假如在服务器中部署微服务不使用 doc…

【C++类和对象】const成员函数及流插入提取

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

地图图源#ESRI ArcGIS XYZ Tiles系列(TMS)

目录 1、前言 2、地图图源网址 2.1、Satellite 卫星图源 2.2、Terrain 地形图源 2.3、Street 路网/标注图源 2.4、Specifity 特色设计图源 3、专业推荐”穿搭“ 4、图源配置下载及使用 图源名称图层类别特别注意谷歌 Google①地形 ②影像 ③矢量及标注 ④特色图源国内大…

Nessus【部署 03】Docker部署漏洞扫描工具Nessus详细过程分享(下载+安装+注册+激活)文末福利

Docker部署漏洞扫描工具Nessus 1.安装2.配置2.1 添加用户2.2 获取Challenge code2.3 获取插件和许可证2.4 注册 3.使用4.进阶 整体流程&#xff1a; 1.安装 # 1.查询镜像 docker search nessus# 2.拉取镜像 docker pull tenableofficial/nessus# 3.启动镜像【挂载目录用于放置…

【Java框架】Spring框架(一)——Spring基本核心(IOC/DI)

目录 Java企业级框架企业级系统EJB概念解析EJB与Spring的恩怨情仇 Spring系统架构1. Data Access/Integration&#xff08;数据访问&#xff0f;集成&#xff09;2. Web 模块3. Core Container&#xff08;Spring 的核心容器&#xff09;4. AOP、Aspects、Instrumentation 和 M…

冰达ROS机器人快速使用指南

欢迎来到《冰达ROS机器人极简使用指南》 Q&#xff1a;这份教程适合谁&#xff1f; A&#xff1a;适合完全0基础新手&#xff0c;需要快速跑起来机器人的基本功能。也适合技术大佬需要快速的了解冰达ROS机器人的使用方法。 Q&#xff1a;这份教程内容很少&#xff0c;是不是…

迅雷下载不了的资源怎么下载?

我想下载Boost库&#xff0c;但是下载不下来 用迅雷下载是一直卡在0k 后来尝试在centos上用wget进行下载&#xff0c;竟然可以 wget https://boostorg.jfrog.io/artifactory/main/release/1.85.0/source/boost_1_85_0.tar.gz

信息打点--语言框架

指纹识别 后端 CMS:一般php开发居多源码程序&#xff0c;其他语言也存在&#xff0c;但不易识别&#xff08;利用源码程序名去搜漏洞情况&#xff0c;源码下载进行后期的代码审计&#xff09; 前端 js框架&#xff08;爬取更多的js从里面筛选URL或敏感泄露key&#xff09;等…

docker-compose 安装MongoDB续:创建用户及赋权

文章目录 1. 问题描述2. 分析2.1 admin2.2 config2.3 local 3. 如何连接3.解决 1. 问题描述 在这一篇使用docker-compose创建MongoDB环境的笔记里&#xff0c;我们创建了数据库&#xff0c;但是似乎没有办法使用如Robo 3T这样的工具去连接数据库。连接的时候会返回这样的错误&…

C++参考手册使用说明

C参考手册使用说明 文章目录 C参考手册使用说明1 为什么要使用C参考手册2 网站3 C参考手册离线格式4 C参考手册使用说明1.1 离线C参考手册下载1.2 html离线C参考手册1.3 chm离线C参考手册1.4 linux安装包C参考手册&#xff08;只有英文版本&#xff09;1.5 qch离线C参考手册 更…

2 逻辑斯蒂回归(分类)

目录 1 理论 逻辑回归假设数据服从伯努利分布&#xff08;二分类&#xff09;,通过极大化似然函数的方法&#xff0c;运用梯度下降来求解参数&#xff0c;来达到将数据二分类的目的。 逻辑斯蒂回归&#xff08;Logistic Regression&#xff09;是一种用于解决分类问题的…

MySQL下载与安装

文章目录 1&#xff1a;MySQL下载与安装2&#xff1a;配置环境变量3&#xff1a;验证是否安装成功 1&#xff1a;MySQL下载与安装 打开MySQL官网&#xff0c;MySQL 下载链接选择合适的版本和操作系统&#xff0c;页面跳转之后选择No thanks, just start my download.等待下载即…

seatable部署之后network error【seatable】

这里写自定义目录标题 问题汇总 问题汇总 seatable服务部署后&#xff0c;组件显示正常运行&#xff0c;创建表单&#xff0c;显示Network error 点击错误信息&#xff0c;查看其跳转至另一个页面

详解JVM类加载

从类被加载到虚拟机内存中开始&#xff0c;到释放内存总共有7个步骤&#xff1a;加载&#xff08;Loading&#xff09;、验证&#xff08;Verification&#xff09;、准备&#xff08;Preparation&#xff09;、解析&#xff08;Resolution&#xff09;、初始化&#xff08;Ini…

Jenkins构建实用场景指南

1 总体说明 本文主要介绍在研发实战时,通过Jenkins解决企业级软件构建打包一些实用场景。通常是在打包构建前,通过命令和工具进行预处理,避免修改源码,可按需配置构建任务,自动持续集成。 2 Jenkins简介 2.1 复制任务 研发实战创建构建任务,推荐从已有的构建任务进行…

项目管理-项目成本管理

目录 一、成本管理概述 二、成本估算 2.1 定义 2.2 成本估算方法 2.2.1 自顶向下的估算 2.2.1.1 估算方法 2.2.1.2 优点 2.2.1.3 缺点 2.2.2 自底向上的估算 2.2.2.1 估算方法 2.2.2.2 优点 2.2.2.3 缺点 2.2.3 差别估算法 三、成本预算 3.1 定义 3.2 成本预算的…

vue+springboot+mybatis-plus改装

①添加依赖 <!-- mybatis-plus --> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.2</version> </dependency> pom.xml: <?xml version"…

springboot+java照相馆预约管理系统ssm

框架&#xff1a;ssm/springboot都有 jdk版本&#xff1a;1.8 及以上 ide工具&#xff1a;IDEA 或者eclipse 数据库: mysql 编程语言: java 前端&#xff1a;layuibootstrapjsp 详细技术&#xff1a;HTMLCSSJSjspspringmvcmybatisMYSQLMAVENtomcat 开发工具 IntelliJ IDEA: 一…

Oracle解析exp、imp及常见的问题

前言 在工作中经常需要不同数据库的导入和导出。exp和imp可以实现数据的迁移。 exo会转储产生对应的二进制文件,里面包括数据的定义信息、数据内容等,即为dump文件。 下面是使用exp和imp的一些场景 exp和imp主要有4中模式: 1)数据库模式 数据库模式也就是我们说的全备…

五分钟手撕“三大特性”<继承>(下)

目录 一、protected 关键字 二、继承方式 三、final 关键字 四、子类的构造方法 五、this和super &#xff08;一&#xff09;相同点&#xff1a; &#xff08;二&#xff09;不同点&#xff1a; 六、代码块的执行先后 一、protected 关键字 在类与对象中提到过&…