【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语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
183 9
|
2月前
|
存储 算法 C语言
通义灵码在考研C语言和数据结构中的应用实践 1-5
通义灵码在考研C语言和数据结构中的应用实践,体验通义灵码的强大思路。《趣学C语言和数据结构100例》精选了五个经典问题及其解决方案,包括求最大公约数和最小公倍数、统计字符类型、求特殊数列和、计算阶乘和双阶乘、以及求斐波那契数列的前20项和。通过这些实例,帮助读者掌握C语言的基本语法和常用算法,提升编程能力。
88 4
|
15天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
85 14
|
23天前
|
C语言 开发者
C语言中的模块化编程思想,介绍了模块化编程的概念、实现方式及其优势,强调了合理划分模块、明确接口、保持独立性和内聚性的实践技巧
本文深入探讨了C语言中的模块化编程思想,介绍了模块化编程的概念、实现方式及其优势,强调了合理划分模块、明确接口、保持独立性和内聚性的实践技巧,并通过案例分析展示了其应用,展望了未来的发展趋势,旨在帮助读者提升程序质量和开发效率。
45 5
|
23天前
|
存储 算法 C语言
用C语言开发游戏的实践过程,包括选择游戏类型、设计游戏框架、实现图形界面、游戏逻辑、调整游戏难度、添加音效音乐、性能优化、测试调试等内容
本文探讨了用C语言开发游戏的实践过程,包括选择游戏类型、设计游戏框架、实现图形界面、游戏逻辑、调整游戏难度、添加音效音乐、性能优化、测试调试等内容,旨在为开发者提供全面的指导和灵感。
37 2
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
75 16
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
95 8
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
108 7
|
1月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
129 8
|
1月前
|
C语言 Windows
C语言课设项目之2048游戏源码
C语言课设项目之2048游戏源码,可作为课程设计项目参考,代码有详细的注释,另外编译可运行文件也已经打包,windows电脑双击即可运行效果
32 1