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

news2024/10/1 23:43:57

贪吃蛇游戏

🥕个人主页:开敲🍉

🔥所属专栏: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 结束游戏

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

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

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

相关文章

开启农业新篇章:山海鲸智慧农业解决方案全面解析

在农业领域&#xff0c;数字化转型已经成为推动农业发展的重要力量。山海鲸&#xff0c;作为农业科技创新的引领者&#xff0c;推出了全新的智慧农业解决方案&#xff0c;通过运用先进的物联网、大数据和人工智能等技术&#xff0c;为农业生产提供智能化、精准化的管理服务&…

c++模拟实现list——详讲双链表--链表

在C语言中我们已经模拟实现了list&#xff0c;现在对比c看看二者的区别 双链表————详讲 个人博客主页&#xff1a; 个人主页 个人码云 码云代码 文章目录 目录 文章目录 ​编辑 前言 一、list是什么&#xff1f; 二、list的使用 三、模拟实现list和搭建list的结构 1.节点结…

孩子用什么样的灯对眼睛没有伤害?分享五款防近视护眼台灯

随着生活条件逐渐提升&#xff0c;对台灯的需求也越来越大&#xff0c;不管在生活中还是工作中&#xff0c;灯具是必不可少的照明工具了&#xff0c;尤其是对于学生而言。很多家长都在寻找孩子用什么样的灯对眼睛没有伤害&#xff1f;建议最好选择一款合格、专业的护眼台灯&…

SpringBootWeb请求

文章目录 前言一、Postman介绍 二、简单参数三、实体参数四、数组集合参数五、日期参数六、JSON参数七、路径参数 前言 在上一篇文章中&#xff0c;已经基于SpringBoot的方式开发一个web应用&#xff0c;浏览器发起请求 /hello 后 &#xff0c;给浏览器返回字符串 “Hello Wor…

STM32之HAL开发——FSMC—扩展外部SRAM

SRAM读写时序 对SRAM进行读写数据时&#xff0c;它各个信号线的时序流程如下图 &#xff08;图一&#xff09;SRAM的读时序 &#xff08;图二&#xff09;SRAM的写时序 流程解释 主机使用地址信号线发出要访问的存储器目标地址&#xff1b;控制片选信号CS1#及CS2#使能存储器…

力扣HOT100 - 25. K 个一组翻转链表

解题思路&#xff1a; class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode dum new ListNode(0, head);ListNode pre dum;ListNode end dum;while (end.next ! null) {for (int i 0; i < k && end ! null; i) {end end.next;}if …

生成式AI产品图谱全览:投资人、产品经理必备指南

以下是生成式AI产品图谱的核心要点&#xff0c;供投资人、产品经理等关注生成性AI领域的专业人士参考&#xff1a; 技术领域细分&#xff1a;依据AI技术所处理的媒介类型进行划分&#xff0c;包括文本处理、代码生成、图像处理、语音识别、视频分析、3D模型构建、音乐创作和游戏…

深度学习项目设置超参数 parser or dictionary

见惯了parser 有的人却是用字典读的&#xff1a; 将配置文件config.yaml读取到一个dictionary类型的变量cfg中&#xff0c; 后面出现了这样的语句&#xff1a;cfg["trainer"].get("sup_only_epoch", 1): 意思是&#xff1a;在config.yaml文件里key为trai…

Java客户端如何直接调用es的API

Java客户端如何直接调用es的API 一. 问题二. withJson 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一. 问题 今天做项目的时候&#xff0c;想要直接通过java客户端调用es的api…

PHP反序列化漏洞原理(附带pikachu靶场演示)

1.反序列化概念 序列化:是将变量转换为可保存或传输的字符串的过程;实现函数是serialize()反序列化:就是在适当的时候把这个字符串再转化成原来的变量使用&#xff0c;就是序列化的逆过程。实现函数是unserialize() 直白一点就是&#xff1a;序列化是把对象转换成字节流&#…

基于java+springboot+vue实现的图书借还管理系统小程序(文末源码+Lw+ppt)23-1

摘 要 随着社会的发展&#xff0c;图书借还的管理形势越来越严峻。越来越多的借阅者利用互联网获得信息&#xff0c;但图书借还信息量大。为了方便借阅者更好的获得本图书借还信息&#xff0c;因此&#xff0c;设计一种安全高效的“共享书角”图书借还管理系统极为重要。 为…

python安装pytorch@FreeBSD

先上结论&#xff0c;最后在conda下安装成功了&#xff01; PyTorch是一个开源的人工智能深度学习框架&#xff0c;由Facebook人工智能研究院&#xff08;FAIR&#xff09;基于Torch库开发并维护。PyTorch提供了一个高效、灵活且易于使用的工具集&#xff0c;用于构建和训练深…

Matlab进阶绘图第51期—带填充等高线的三维特征渲染散点图

带填充等高线的三维特征渲染散点图是填充等高线图与特征渲染三维散点图的组合。 其中&#xff0c;填充等高线图与特征渲染的三维散点图的颜色用于表示同一个特征。 由于填充等高线图无遮挡但不直观&#xff0c;特征渲染的三维散点图直观但有遮挡&#xff0c;而将二者组合&…

【MySQL探索之旅】多表查询

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

快速排序题目SelectK问题(力扣75.颜色分类、力扣215.数组中的第K个最大元素、面试题17.14最小K个数)

力扣75.颜色分类 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums &#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sor…

使用Nexus搭建npm私服库

优质博文&#xff1a;IT-BLOG-CN 【1】下载nexus http://www.sonatype.com/download-oss-sonatype解压到本地即可&#xff1b; 【2】打开nexus-3.2.0-01-win64\nexus-3.2.0-01\bin&#xff1b;打开cmd&#xff08;必须使用cmd&#xff09; 执行nexus.exe /run&#xff1b;需要使…

贪吃蛇的简单实现(c语言)

前言&#xff1a;学完了C语言的基础语法&#xff0c;和一点数据结构的知识&#xff0c;拿贪吃蛇来练练手&#xff0c;并熟悉以前的知识。写完之后&#xff0c;有一种成就感&#xff0c;为以后的学习饱满激情。 注意这里的讲解是由部分到整体的思路。 目录 控制台不能是终端&am…

OpenCV-复数矩阵点乘ComplexMatrixDotMultiplication

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 需求说明 一般用到FFT&#xff0c;就涉及到复数的计算&#xff0c;为了便于调用&#xff0c;我自行封装了一个简单的复数矩阵点乘…

《从零开始的Java世界》08集合框架

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…

YOLO世界:实时开放词汇对象检测

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;YOLO世界&#xff1a;实时开放词汇对象检测1、研究背景2、提出方法3、相关技术3.1、Re-parameterizable Vision-Language Path Ag…