【C语言】实践:贪吃蛇小游戏(附源码)(一)

简介: 【C语言】实践:贪吃蛇小游戏(附源码)

前言

       贪吃蛇小游戏想必大家都玩过吧,现在就要C语言代码来实现一下贪吃蛇小游戏

在实现之前,我们要对C语言结构体指针链表(单链表)有一定的基础

先来看一下预期运行效果

一、Win32 API

       这里实现贪吃蛇游戏会使用一些Win32 API的知识,这里简单学习一下

       Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗、绘制图形、使用周边设备等目的,由于这些函数服务的对象是应用程序,所以便称之为Appliccation Programming Interface,简称API。WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口。

1.1 控制台程序

      在我们的电脑中,windows系统使用快捷键win + R可以打开一个窗口,然后输入cmd就可以打开一个控制台程序,这个控制台可以输入一些命令来控制我们的电脑,这里输入cmd即可打开一个控制台程序窗口

       1.1.1 设置控制台程序

       本次贪吃蛇小游戏是在VS2022上来实现的,平常我们运行起来的黑框程序就是控制台层序

在VS2022上运行默认是以下情况

这里就需要先修改一个控制台

调出控制台(这里可以使用Win+R,输入cmd调出窗口),点击设置

在默认终端应用程序这里设置成Windows 控制台主机(默认是Windows 终端),点击保存

设置完成后,就是以下这种界面了

       1.1.2 设置控制台程序大小

这里我们控制台程序是默认大小,这里我们自己设置控制台程序大小,这里使用cmd控制台程序设置窗口的大小(设置大小为行33,列100)

mode con cols=100 lines=33

       1.1.3 设置控制台程序名称

我们设置控制台名称为 贪吃蛇,使用title 指令

title 贪吃蛇

       当然,这些能够在控制台窗口执行的命令,也可以通过调用C语言的system函数在中来完成

这里再补充一个指令,暂停控制台程序

system("pause");

       这个指令可以暂停程序运行,并会提示按下任意键继续...

int main()
{
  system("mode con cols=100 lines=33");
  system("title 贪吃蛇");
  system("pause");
  return 0;
}

       1.1.4 控制台屏幕上的坐标

COORD是Windows API中自定义的一个结构体,表示一个字符在控制台屏幕缓冲区的坐标,坐标(0,0)的原点位于缓冲区的顶部左侧单元格。

COORD类型声明

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD, *PCOORD;

给坐标赋值

COORD pos = { 10, 15 };

GetStdHandle

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

HANDLE WINAPI GetStdHandle(_In_ DWORD nStdHandle);

函数参数

函数使用

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

GetConsoleCursorInfo

       GetConsoleCursorInfo函数检索有关指定控制台屏幕缓冲区的光标的大小和可见性的信息

函数语法

BOOL WINAPI GetConsoleCursorInfo(
  _In_  HANDLE               hConsoleOutput,
  _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标(光标)的信息

       CONSOLE_CURSOR_INFO结构体

       这个结构体包含了有关控制台光标的信息

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

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

       bVisible 游标的可见性。如果光标可见,则此成员为true;如果不可见,此成员为false

函数参数

       这里就用到上面GetStdHandle函数获得的句柄了,还需要用到CONSOLE_CURSOR_INFO结构体(注意,这里第二个参数是指针)

函数使用

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

SetConsoleCursorInfo

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

函数参数

BOOL WINAPI SetConsoleCursorInfo(
  _In_       HANDLE              hConsoleOutput,
  _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

       与GetConsoleCursorInfo函数参数相同        

函数使用

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

SetConsoleCursorPosition

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

BOOL WINAPI SetConsoleCursorPosition(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwCursorPosition
);

函数参数

函数使用

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

这里为了方便后面定位屏幕坐标,单独封装一个函数来实现

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

GetAsyncKeyState

       GetAsyncKeyState函数获得按键情况

SHORT GetAsyncKeyState(
  [in] int vKey
);

函数参数

       这里函数参数是虚拟键码。

这里仅列出一些在游戏中可能用到的按键的虚拟键码,可以点击查看详细虚拟键码

VK_UP 0x26
VK_DOWN 0x28
VK_LEFT 0x25
VK_RIGHT 0x27
VK_F3 0x72 F3
VK_F4 0x73 F4
VK_ESCAPE 0x1B Esc
VK_SPACE 0x20 空格

函数返回值

       GetAsyncKeyState 函数返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,如果最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。

       在游戏中我们需要检测一个按键是否被按过,就检测 GetAsyncKeyState 函数返回值的最低值是否是1,可以写一个宏来实现:

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

二、本地化

在贪吃蛇游戏中,我们会涉及到墙体□ 和蛇的身体● 的打印,而在VS中我们输出出来的是?

这就是因为没有本地化设置,无法输出这些特殊字符(宽字符)。

       我们需要通过修改地区,让程序来适应不同的区域,我们就需要进行本地化设置

这里就要使用到C语言中的库函数  setlocale 函数

在C标准中,依赖地区的部分有以下几项

数字量的格式

货币量的格式

字符集

日期和时间的表示形式    

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

LC_COLLATE :影响字符串表函数 strcoll strxfrm

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

LC_MONETARY : 影响货币格式。

LC_NUMERIC : 影响 printf 的数字格式。

LC_TIME : 影响时间格式 strftimewcsftime  。

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

每一个类别都有详细说明,这里就不一一讲解了

       2.1 setlocale 函数

setlocale 函数用于修改当前地区,可以针对一个类项进行修改,也可以针对所有类项

函数的第一个参数可以是前面类项中的一个,也可以是LC_ALL(影响所以的类项)

函数的第二个参数

       C标准给第二个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地化模式)

这里我们需要进行本地化设置

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

       2.2 宽字符的打印

在屏幕中,我们需要打印宽字符        

       宽字符的字面量必须加上前缀“L”,否则C语言就会把字符量当成窄字符来处理。前缀“L”子啊单引号(或者双引号)的前面,表示宽字符,对于 wprintf 的占位符是 %lc;双引号的前面,对于wprintf 的占位符是 %ls。

可以看到,这里宽字符占两个窄字符的位置。

三、游戏分析和设计

       3.1 贪吃蛇数据结构设计

       在游戏运行的过程中,蛇每吃一次食物,蛇的身体就会变长;这样我们就可以使用链表来存储蛇的信息,蛇的每一个节身体其实就是链表的一个节点。每个节点只需记录蛇身节点在地图上的坐标就可以。

       蛇身节点的结构:

typedef struct Snakenode
{
  int x;
  int y;
  struct Snakenode* next;
}Snakenode, * pSnakenode;
//这里也可以写 typedef Snakenode* pSnakenode

       接下来,我们还需要记录游戏过程中的相关信息

贪吃蛇,食物的位置,蛇的方向,游戏状态,当前的分数,每一个食物的分数,蛇的速度等

而这里蛇的方向和游戏状态都可以一一列举出来,这里就使用枚举变量

//蛇的方向
enum DIRECT
{
  UP = 1,
  DOWN,
  LEFT,
  RIGHT
};
//蛇的状态——游戏状态
//正常、撞墙、撞到自己、正常退出
enum GAME_STATE
{
  OK,
  KILL_WALL,
  KILL_SELF,
  NORMAL_END
};
//贪吃蛇的相关信息
typedef struct Snake
{
  pSnakenode psnake; //指向蛇头部的指针
  pSnakenode pfood;   //指向食物的指针
  enum DIRECT dir;//蛇的方向
  enum GAME_STATE state;//蛇的状态
  int food_scores;//每个食物的分数
  int all_scores; //总分数
  int sleep_time;  //休息的时间 --即蛇的速度
}Snake;
typedef Snake* pSnake;

这样,我们就创建了一个Snake结构体来维护游戏相关信息(维护整条贪吃蛇)

       3.2 游戏流程分析

游戏大概分析如下



相关文章
|
25天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
17天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
4天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
1天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
251 12
|
19天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
21天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2579 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
3天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
169 2
|
1天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
101 65
|
21天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1578 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
4天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
256 2