📚 个人专栏:
目录
- 一、Win 32 API介绍
- 1.1 控制台设置
- 1.2程序中设置控制台
- 1.2.1 COORD控制台屏幕坐标
- 1.2.2 GetStdHandle 函数
- 1.2.3 GetConsoleCursorInfo 函数
- CONSOLE_CURSOR_INFO 结构
- 1.2.4 SetConsoleCursorPosition 函数
- 设置光标位置函数set_pos
- 1.2.5 GetAsyncKeyState 函数
- 二、游戏演示与界面设计
- 2.1 成果演示
- 2.2 游戏界面分解
- 2.2.1 欢迎界面和操作指南
- 2.2.2 主游戏界面布局
- 宽字符的打印
- 游戏边界的打印
- 蛇和食物的打印
- 2.3 数据结构设计
- 2.4 操作说明
- 2.5 游戏流程设计
- 三、核心逻辑实现分析
- 3.1 创建地图
- 3.2 初始化蛇身
- 3.3 创建食物
- 3.4 游戏状态
- 3.5 蛇的移动
- 3.5.1
EatFood函数详解 - 3.5.2
NoFood函数详解(未吃到食物) - 3.6 碰撞检测函数
- 3.5.1
- 3.6 游戏结束
- 四、总结
学习完C语言和链表,最好的方式就是通过项目实战来巩固知识。 贪吃蛇是一个经典的控制台游戏项目,它不仅能让你熟练掌握 链表这种重要的数据结构,还能让你体验 Win32 API在控制台程序中的强大应用。
这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(), 所以便称之为 ,简称 函数。也就是的应⽤程序编程接⼝。
函数用来执行系统命令。
GPT plus 代充 只需 145
1.2.1 COORD控制台屏幕坐标
是中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕上的坐标。
GPT plus 代充 只需 145
屏幕上任何一个位置就是一个坐标。那怎么定义一个坐标呢?
1.2.2 GetStdHandle 函数
要控制控制台窗口,首先要获得这个窗口。
是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备(也就是屏幕)。
句柄 其实是一个数字,只是叫句柄而已,简单理解为抓手。
函数定义(飞机票)
GPT plus 代充 只需 145
1.2.3 GetConsoleCursorInfo 函数
它的第一个参数就是刚才获取到的句柄。
第二个参数是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关控制台游标的信息。
CONSOLE_CURSOR_INFO 结构
GPT plus 代充 只需 145
- ,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条。
- ,游标的可⻅性。 如果光标可⻅,则此成员为 。
示例代码:
这里需要注意的是,不能只修改,不然将会不成功,而是设置 —> 修改 —> 再设置。
不是我没截,是真的没了。
1.2.4 SetConsoleCursorPosition 函数
GPT plus 代充 只需 145
设置指定控制台屏幕缓冲区中的光标位置,将想要设置的坐标信息放在类型的中,⽤将光标位置设置到指定的位置。
指定新光标位置(以字符为单位)的 COORD 结构。 坐标是屏幕缓冲区字符单元的列和行。 坐标必须位于控制台屏幕缓冲区的边界以内。
光标被定位到(5,5)。
设置光标位置函数set_pos
GPT plus 代充 只需 145
1.2.5 GetAsyncKeyState 函数
获取按键情况,GetAsyncKeyState的函数原型如下:
的返回值是short类型,在上⼀次调⽤ 函数后,如果返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测返回值的最低值是否为1.
GPT plus 代充 只需 145
那这几个页面我们需要全部实现,接下来就对每个页面实现进行拆解。
我们的贪吃蛇游戏界面主要由以下几个部分构成:
- 欢迎界面和操作指南:
在游戏开始前, 函数会打印出欢迎信息和详细的按键指南(↑, ↓, ←, → 移动;W 加速;S 减速;ESC 退出)。 - 主游戏界面布局:
函数绘制了游戏的边框( 字符 )。蛇身( 字符 )和食物( 字符 )在边界内部移动。 - 信息面板展示:
在游戏区域的右侧,实时显示游戏的当前总分数 和当前食物分数 ,以及操作提示。
2.2.1 欢迎界面和操作指南
这里为了便于后续设置光标位置和游戏边界,那就统一将我们的操作界面设置为一个大小,那么后续也就不回出现乱码的情况了。
GPT plus 代充 只需 145
这里我们将大小确定好之后,光标也已经进行隐藏了,那只需要大概找到屏幕正中间位置就可以开始打印信息了。这里可以一点一点去试试,不难,一会就好了,至于这里为啥长和宽比例如此离谱,请耐心往下读!!!
这里有个小细节,那就是切换界面时,需要让用户自己操作,那就进行一个暂停操作。前一个界面会影响第二个界面的观感,那就进行一次清屏操作。
这里给出伪码,部分可能看不懂,别着急,往下接着看:
2.2.2 主游戏界面布局
在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。
这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语⾔并不适合⾮英语国家(地区)使⽤。C语⾔最初假定字符都是但⾃⼰的。但是这些假定并不是在世界的任何地⽅都适⽤。C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7位,最⾼位是没有使⽤的,可表⽰为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够⽤的,但是,在其他国家语⾔中,⽐如,在法语中,字⺟上⽅有注⾳符号,它就⽆法⽤ ASCII 码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编⼊新的符号。⽐如,法语中的é的编码为130(⼆进制)。这样⼀来,这些欧洲国家使⽤的编码体系,可以表⽰最多256个符号。但是,这⾥⼜出现了新的问题。不同的国家有不同的字⺟,因此,哪怕它们都使⽤256个符号的编码⽅式,代表的字⺟却不⼀样。⽐如,130在法语编码中代表了é,在希伯来语编码中却代表了字⺟Gimel ,在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这些编码⽅式中,0–127表⽰的符号是⼀样的,不⼀样的只是128–255的这⼀段。⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号,肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是 GB2312,使⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰ 256 x 256 = 65536 个符号。
后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊和宽字符的类型wchar_t 和宽字符的输⼊和输出函数,加⼊和头⽂件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。
1、
- 数字量的格式
- 货币量的格式
- 字符集
- ⽇期和时间的表⽰形式
2、类项
通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,指定⼀个类项:
- :影响字符串比较函数和。
- :影响字符处理函数的行为。
- :影响货币格式。
- :影响的数字格式。
- :影响时间格式和。
- - 针对所有类项修改,将以上所有类别设置为给定的语言环境。
3、setlocale函数
GPT plus 代充 只需 145
当地区设置为”C”时,库函数按正常⽅式执⾏,⼩数点是⼀个点。
当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤作为第2个参数,调⽤setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。
GPT plus 代充 只需 145
- 字符集:基于 ASCII(或非常简单的字符集)。
- 格式化规则:
- 数字:小数点使用点号 。
- 时间/日期格式:遵循 C/POSIX 标准格式(例如 )。
- 货币符号:通常只有基本的美元符号 ,没有本地化格式。
- 字符串比较(排序):严格基于 ASCII 码值(例如 排在 前面)。
当调用 空字符串 :是程序主动“入乡随俗”,去适应操作系统用户所在的语言和文化环境。也就是。
宽字符的打印
- 前缀在单引号前面,表示宽字符,对应的占位符为;
- 前缀在双引号前面,表示宽字符串,对应的占位符为;
- 格式串前面也要加上前缀。
游戏边界的打印
那实际上在我们的终端上也就是这个页面(假设我标出边框的是终端界面):
蛇和食物的打印
贪吃蛇最核心的就是对蛇身的表示,我们选择使用单向链表,因为蛇的移动是典型的头部插入、尾部删除操作。
枚举类型定义:
我们使用枚举 () 来清晰地定义蛇的方向 () 和游戏的状态 ()。
结构体详解:
结构体是整个游戏的上下文,它包含了游戏的所有状态信息。
GPT plus 代充 只需 145
链表节点的设计:
每个节点代表蛇身的一个部分,存储其在屏幕上的坐标以及指向下一个节点的指针。
- 蛇的移动过程:
蛇每走一步,就相当于在头部方向多了一个新节点,并在尾部少了一个旧节点,从而实现“移动”的视觉效果。 - 食物生成和得分机制:
食物在非墙壁、非蛇身的位置随机生成。当蛇头移动到食物位置时,它会吃掉食物,然后长度会变长,总分数增加,并在地图上立即重新生成一个新的食物。 - 加速减速效果:
通过按 W 和 S 键,可以动态调整游戏的速度,这通过改变 参数来实现。 越短,蛇移动越快,同时食物分数 () 也会相应变化,实现了加速得分更高的Tips。
这里就是一些核心函数的实现了,就不一一展开了,只展示一些核心的内容,完了给出我的源码链接(文末),有兴趣的可以自己去拿一下。
GPT plus 代充 只需 145
创建地图函数
这里就需要根据个人定义的边界条件具体判断了,我定义的是30行。
蛇最开始长度为4节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。创建4个节点,然后将每个节点存放在链表中进⾏管理。创建完蛇⾝后,将蛇的每⼀节打印在屏幕上。(这里我定义的是四节,你可以自己改)
再设置当前游戏的状态,蛇移动的速度,默认的⽅向,初始成绩,蛇的状态,每个⻝物的分数。
蛇⾝打印的宽字符:
初始化蛇⾝函数:
GPT plus 代充 只需 145
我们使用头插法创建初始蛇身(例如4个节点)。初始时,蛇头位于链表的第一个节点。
先随机⽣成⻝物的坐标
- x坐标必须是2的倍数。
- ⻝物的坐标不能和蛇⾝每个节点的坐标重复。
这里说详细一些,为什么必须是2的倍数呢?
因为每一个蛇头都占据1个宽字符,两个字节,后续需要用食物坐标和蛇头坐标进行判断,判断是否吃到食物,那如果不是2的倍数,蛇头永远到不了这个食物的坐标,这游戏也就没法继续下去了。
为什么不能重复呢? 重复了你连食物都找不到,那还玩啥?
游戏运行期间,右侧打印帮助信息提示玩家,根据游戏状态检查游戏是否继续,如果是状态是,游戏继续,否则游戏结束。如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。确定了蛇的⽅向和速度,蛇就可以移动了。
这里最重要的就是把交互做好,也就是键盘响应,很多代码都是一样的,可以定义宏进行简化代码,完整代码就只给个伪码了。
GPT plus 代充 只需 145
这里加速减速,也实现的没有那么复杂,就是将沉睡时间加长或者减少,但也不能无限制,在范围内就可以。
蛇的移动算法 (): 这是游戏的核心逻辑。
- 根据当前方向 (),计算出下一个位置的坐标,并创建一个新节点。
- 判断下一个位置是否为食物 ()。
- 如果是食物 (): 将 节点直接连接到蛇头(头插),让它成为新的蛇头。不进行尾部删除,从而实现蛇身变长。然后重新创建食物。
- 如果不是食物 (): 将 连接到蛇头(头插)。找到链表的倒数第二个节点,释放它的 节点(即删除旧尾巴),并将其 置为 。
3.5.1 函数详解
函数处理蛇吃到食物的逻辑,其核心操作是让蛇身增长并更新游戏状态。
首先,它通过头插法将食物节点 () 插入到蛇头 () 的前面,使食物节点成为新的蛇头,从而实现蛇身的增长,因为这次移动没有移除尾部节点。接着,它释放了原本为下一个位置预分配的临时节点 ,避免内存泄漏。
随后,函数从新的蛇头开始遍历整个蛇身链表,并在控制台上重新打印蛇身的每个节点,确保蛇身的新状态在屏幕上正确显示 ()。
最后,它根据当前食物的分值 () 更新总得分 (),并调用 在地图上生成一个新的食物.
GPT plus 代充 只需 145
3.5.2 函数详解(未吃到食物)
当蛇移动到的下一个位置不是食物时, 函数执行蛇身平移逻辑:
- 头插法将代表新位置的节点 连接到蛇头 () 的前面,使其成为新的蛇头;
- 遍历链表,找到倒数第二个节点 (),并在遍历过程中重新打印除尾部外的所有蛇身节点;
- 函数定位到最后一个节点(旧尾巴),先在屏幕上将其位置打印成两个空格 () 来清除旧尾巴的痕迹,然后在内存中释放该节点 ();
- 将倒数第二个节点的 指针设置为 (),正式截断链表,使蛇的长度保持不变,完成一步移动。
这里打印空格的目的是为了在控制台屏幕上清除蛇身旧尾巴的痕迹,实现蛇的平滑移动效果。
3.6 碰撞检测函数
、 这两个函数共同实现了贪吃蛇游戏的碰撞检测机制,用于判断游戏是否结束。
函数检查蛇头 () 的坐标是否触及地图边界(X坐标为 0 0 0 或 56 56 56,Y坐标为 0 0 0 或 28 28 28)(这里就是判断边界,如果你的边界不是我的这个大小,自己修改一下就好了),如果触墙,则立即将游戏状态 设置为 。
函数则通过遍历蛇头后面的所有蛇身节点 (),检查是否有任何一个节点 () 的坐标与当前蛇头坐标完全重合;如果发生重合(即蛇头咬到自己),则将游戏状态 设置为 。
一旦任一函数检测到碰撞,就会设置相应的状态,导致 函数中的循环终止,游戏结束。
GPT plus 代充 只需 145
每次蛇移动都要检测是否结束哦!
游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,然后直接退出?
不不不!!!
你的蛇是链表啊!链表节点是动态申请的啊,它在堆上,直接退出只把栈区的内存空间归还给操作系统,你还得干啥呢?对喽,把节点释放了,把内存还给人家,有借有还,再借不难啊!
GPT plus 代充 只需 145
通过实现C语言贪吃蛇小游戏,不仅将理论知识转化为实际代码,更深入理解了几个关键点:
- 链表是处理动态、顺序数据的理想结构,其头部插入和尾部删除的特性完美契合了蛇的移动需求。
- Win32 API是控制台程序交互的关键,它让我们能够突破标准C库的限制,实现对光标、窗口和键盘的精细控制。
- 正确的内存管理至关重要,特别是对链表的动态操作。
- 链表的动态内存分配: 无论是初始化蛇身 ()、创建食物 ()、还是蛇移动时创建新的头节点 (),都需要使用 进行动态内存分配,确保每次操作都有足够的空间。
- 节点添加和删除: 在 中,我们通过 释放了尾部节点,防止内存泄漏。
- 游戏结束时的资源释放 (): 无论游戏是正常退出还是失败,都必须调用 函数。该函数会遍历整个蛇身链表,逐个释放所有动态分配的节点,彻底清理占用的内存。
如果你对这个感兴趣,可以尝试添加更多功能,例如墙体穿透、不同颜**分蛇头与蛇身,或实现多种不同的食物类型!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/239992.html