贪吃蛇游戏实现(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 结束游戏

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

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

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

相关文章

Java之类和对象

一面向对象的初步认知 1.什么是面向对象 Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。用面向对象的思想…

嵌入式物联网实战开发笔记-乐鑫ESP32开发环境ESP-IDF搭建【doc.yotill.com】

乐鑫ESP32入门到精通项目开发参考百例下载&#xff1a; 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;4e33 3.1 ESP-IDF 简介 ESP-IDF&#xff08;Espressif IoT Development Framework&#xff09;是乐鑫&#xff08;Espressif Systems&#xff09;为 ESP 系列…

大型网站系统架构演化实例_2.使用缓存改善网站性能

1.使用缓存改善网站性能 网站访问的特点和现实世界的财富分配一样遵循二八定律&#xff1a;80%的业务访问集中在20%的数据上。既然大部分业务访问集中在一小部分数据上&#xff0c;那么如果把这一小部分数据缓存在内存中&#xff0c;就可以减少数据库的访问压力&#xf…

【Python】自定义修改pip下载模块默认的安装路径

因为电脑下载了Anaconda提供的默认Python 3.9 以及后期下载的python3.10所以在Pychram进行项目开发时&#xff0c;发现一些库怎么导入都导入不了&#xff0c;手动install也是失败&#xff0c;后期在cmd里面发现python以及pip配置有点儿混乱&#xff0c;导致执行命令时&#xff…

碳循环、人类、遥感之间的关联

1. 碳与碳循环 碳是自然界中很常见的一种元素&#xff0c;它以多种形式广泛存在于大气和地壳之中。碳单质很早就被人认识和利用&#xff0c;碳的一系列化合物——有机物是生命的根本。 1.1 自然界中的碳 地球上最大的两个碳库是岩石圈和化石燃料&#xff0c;含碳量约占…

在RISC-V64架构的CV1811C开发板上应用perf工具进行多线程程序性能分析及火焰图调试

CV1811C环境编译 SDK目录结构 . ├── build // 编译目录,存放编译脚本以及各board差异化配置 ├── buildroot-2021.05 // buildroot开源工具 ├── freertos // freertos系统 ├── fsbl // fsbl启动固件,prebuilt形式存在…

Android14 - WindowManagerService之客户端Activity布局

Android14 - WindowManagerService之客户端Activity布局 一、主要角色 WMS作为一个服务端&#xff0c;有多种客户端与其交互的场景。我们以常见的Activity为例&#xff1a; Activity&#xff1a;在ActivityThread构建一个Activity后&#xff0c;会调用其attach方法&#xff0c;…

[docker] volume 补充 环境变量 参数

[docker] volume 补充 & 环境变量 & 参数 这里补充一下 volume 剩下的内容&#xff0c;以及添加参数(ARG) 和 环境变量 ENV 的内容 read only volumes ❯ docker run-p 3000:80--rm--name feedback-app-v feedback:/app/feedback-v "$(pwd):/app"-v /app/…

【C++初阶】vector使用特性 vector模拟实现

1.vector的介绍及其使用 1.1 vector的介绍 vector文档介绍 1. vector是表示可变大小数组的序列容器。 2. 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#…

第24天:安全开发-PHP应用文件管理模块显示上传黑白名单类型过滤访问控制

第二十四天 一、PHP文件管理-显示&上传功能实现 如果被抓包抓到数据包&#xff0c;并修改Content-Type内容 则也可以绕过筛查 正常进行上传和下载 二、文件上传-$_FILES&过滤机制实现 无过滤机制 黑名单过滤机制 使用 explode 函数通过点号分割文件名&#xff0c;…

VTC视频时序控制器原理以及Verilog实现

文章目录 一、前言二、视频时序控制原理三、Verilog实现3.1 代码3.2 仿真以及分析 一、前言 VTC&#xff08;Video Timing Controller&#xff09;是一种用于产生视频时序的控制器&#xff0c;在FPGA图像领域经常用到。Xilinx Vivado 也有专门用于生成视频时序的 IP&#xff0c…

webpack-babel2

浏览器的兼容性问题 浏览器的兼容性问题不知包括随屏幕大小而变化&#xff0c;还包括针对浏览器支持的特性&#xff08;如css特性&#xff0c;js特性&#xff09; 做处理。 目前市场上有很多浏览器&#xff1a;Chrome,Safari,IE,Edge等&#xff0c;要根据它们的市场占有率来决…

vue 对axios二次封装,配置api层,基于mock测试数据

一、初始化环境&#xff08;默认都会安装vue3项目ts&#xff09; 安装mock&#xff1a;全局安装 # 使用 npm 安装 npm install mockjs vite-plugin-mock # 使用 yarn 安装 yarn add mockjs vite-plugin-mock 二、进行配置 在vite.config.ts中进行配置 import { UserConfigEx…

NodeRed节点编辑用于边缘计算和规则引擎,能做带UI界面和业务逻辑的上位机或前端应用吗?

先说结论&#xff0c;可以&#xff0c;但是需要有页面嵌套继承类似的技术&#xff0c;实现页面模块化封装&#xff0c;否则难以实现复杂应用。 相信目光敏锐的人都在关注节点编辑在自身行业的应用&#xff01; NodeRed在边缘计算做数据协议解析、以及物联网平台中作为规则链引…

推荐几本C#/.NET进阶书籍

前言 今天大姚给大家推荐7本C#/.NET进阶书籍&#xff0c;希望能帮助到有需要的小伙伴&#xff0c;当然假如你有更好的C#/.NET进阶书籍推荐欢迎文末留言。 C#/.NET/.NET Core推荐学习书籍&#xff08;已分类&#xff09;&#xff1a;C#/.NET/.NET Core推荐学习书籍&#xff08;…

春秋云境:CVE-2022-25578[漏洞利用]

通过题目标题查询漏洞信息 所以我们渗透的重点就要放在.htaccess文件上 这是一种分布式配置文件&#xff0c;所以我们先寻找web管理登录页面 打开主页就能看到右下角的“管理”&#xff0c;或者我们使用dirsearch进行扫描也可以 在登录页面尝试弱口令登录 输入该CMS相关的一…

【游戏专区】贪吃蛇

1&#xff0c;游戏背景 贪吃蛇&#xff08;Snake&#xff09;是一款经典的电子游戏&#xff0c;最初在1976年由 Gremlin 公司开发。它的游戏背景相对简单&#xff0c;但具有高度的成瘾性。 1. **游戏场景**&#xff1a;通常在一个有界的矩形区域内进行&#xff0c;可以是一个…

关于Android绘制这一遍就够了

Android绘制基础 Android平台提供了一套完整的UI框架&#xff0c;其中包括了绘制组件和绘制API。在Android中&#xff0c;绘制主要涉及到两个核心概念&#xff1a;Canvas和Paint。 Canvas Canvas是Android中的一个类&#xff0c;它代表了绘图的画布。你可以在这个画布上进行…

Android Studio实现页面跳转

建立文件 temp.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"…