【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 游戏流程分析

游戏大概分析如下



【C语言】实践:贪吃蛇小游戏(附源码)(二)https://developer.aliyun.com/article/1621361


相关文章
|
1月前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
2月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
98 16
|
2月前
|
算法 C语言
【C语言程序设计——循环程序设计】求解最大公约数(头歌实践教学平台习题)【合集】
采用欧几里得算法(EuclideanAlgorithm)求解两个正整数的最大公约数。的最大公约数,然后检查最大公约数是否大于1。如果是,就返回1,表示。根据提示,在右侧编辑器Begin--End之间的区域内补充必要的代码。作为新的参数传递进去。这个递归过程会不断进行,直到。有除1以外的公约数;变为0,此时就找到了最大公约数。开始你的任务吧,祝你成功!是否为0,如果是,那么。就是最大公约数,直接返回。
107 18
|
2月前
|
Serverless C语言
【C语言程序设计——循环程序设计】利用循环求数值 x 的平方根(头歌实践教学平台习题)【合集】
根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码,求解出数值x的平方根;运用迭代公式,编写一个循环程序,求解出数值x的平方根。注意:不能直接用平方根公式/函数求解本题!开始你的任务吧,祝你成功!​ 相关知识 求平方根的迭代公式 绝对值函数fabs() 循环语句 一、求平方根的迭代公式 1.原理 在C语言中,求一个数的平方根可以使用牛顿迭代法。对于方程(为要求平方根的数),设是的第n次近似值,牛顿迭代公式为。 其基本思想是从一个初始近似值开始,通过不断迭代这个公式,使得越来越接近。
71 18
|
2月前
|
C语言
【C语言程序设计——循环程序设计】统计海军鸣放礼炮声数量(头歌实践教学平台习题)【合集】
有A、B、C三艘军舰同时开始鸣放礼炮各21响。已知A舰每隔5秒1次,B舰每隔6秒放1次,C舰每隔7秒放1次。编程计算观众总共听到几次礼炮声。根据提示,在右侧编辑器Begin--End之间的区域内补充必要的代码。开始你的任务吧,祝你成功!
76 13
|
2月前
|
存储 安全 C语言
【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】
分支的语句,这可能不是预期的行为,这种现象被称为“case穿透”,在某些特定情况下可以利用这一特性来简化代码,但在大多数情况下,需要谨慎使用。编写一个程序,该程序需输入个人数据,进而预测其成年后的身高。根据提示,在右侧编辑器补充代码,计算并输出最终预测的身高。分支下的语句,提示用户输入无效。常量的值必须是唯一的,且在同一个。语句的作用至关重要,如果遗漏。开始你的任务吧,祝你成功!,程序将会继续执行下一个。常量都不匹配,就会执行。来确保程序的正确性。
96 10
|
2月前
|
小程序 C语言
【C语言程序设计——基础】顺序结构程序设计(头歌实践教学平台习题)【合集】
目录 任务描述 相关知识 编程要求 测试说明 我的通关代码: 测试结果: 任务描述 相关知识 编程编写一个程序,从键盘输入3个变量的值,例如a=5,b=6,c=7,然后将3个变量的值进行交换,使得a=6,b=7,c=5。面积=sqrt(s(s−a)(s−b)(s−c)),s=(a+b+c)/2。使用输入函数获取半径,格式指示符与数据类型一致,实验一下,不一致会如何。根据提示,在右侧编辑器补充代码,计算并输出圆的周长和面积。
55 10
|
2月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
52 3
|
2月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
34 2
|
2月前
|
存储 C语言
【C语言程序设计——循环程序设计】利用数列的累加和求 sinx(头歌实践教学平台习题)【合集】
项的累加和,一般会使用循环结构,在每次循环中计算出当前项的值(可能基于通项公式或者递推关系),然后累加到一个用于存储累加和的变量中。在C语言中推导数列中的某一项,通常需要依据数列给定的通项公式或者前后项之间的递推关系来实现。例如,对于一个简单的等差数列,其通项公式为。的级数,其每一项之间存在特定的递推关系(后项的分子是其前项的分子乘上。,计算sinx的值,直到最后一项的绝对值小于。为项数),就可以通过代码来计算出指定项的值。对于更复杂的数列,像题目中涉及的用于近似计算。开始你的任务吧,祝你成功!
66 6