C语言实战项目:贪吃蛇(1)

作者:高山有多高日期:2025/10/3

前言:

通过持续数月的C语言系统学习,我们已经掌握了包括指针操作、结构体使用、文件IO等核心编程能力。为了检验学习成果并提升实战经验,在本篇技术博客中,我将带领大家开发一个具有里程碑意义的经典游戏项目 -- 贪吃蛇。

温馨提示:本篇博客为贪吃蛇游戏的前言准备。

一、贪吃蛇游戏效果演示

游戏效果演示:

二、贪吃蛇游戏设计

2.1 贪吃蛇游戏的最终目标

使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇。

贪吃蛇游戏实现基本的功能:

• 贪吃蛇地图绘制

• 蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作)

• 蛇撞墙死亡

• 蛇撞⾃⾝死亡

• 计算得分

• 蛇⾝加速、减速

• 暂停游戏

• 退出游戏

2.2贪吃蛇游戏的思维导图

贪吃蛇游戏的思维导图如下图所示:

2.3贪吃蛇游戏的核心逻辑

核心逻辑:循环内依次执行输入处理→蛇移动→碰撞检测→状态显示→休眠:

2.3.1核心数据结构

采用链表存储蛇身:每个SnakeNode节点包含坐标(x,y)和指向下一节点的指针,通过 “头增尾删” 实现蛇的移动(吃食物时只增不删,长度增长)。

2.3.2游戏流程循环

1. 初始化阶段

①控制台设置:调整窗口大小、标题,隐藏光标(提升视觉流畅度)。

②地图与蛇初始化:绘制边界(如上下左右的墙),生成初始蛇身(默认设置为 5 个节点,初始方向向右)。

③食物生成:随机生成坐标,确保不与蛇身重叠。

2. 运行循环(持续重复)

1.输入处理:

①监听键盘事件(方向键改方向、空格暂停 / 继续、F3 加速、F4 减速、ESC 退出)

②限制 “反向无效”(如当前向上时,按向下键不改变方向,避免瞬间自撞)。

2.蛇移动:

①按当前方向,在头部生成新节点(模拟 “前进”)。

②若吃到食物(新头节点坐标与食物坐标重合):不删除尾部节点,蛇长度 + 1,重新生成食物并加分。

③若没吃到食物:删除尾部节点(保持长度不变),并清除尾部节点的屏幕显示。

3.碰撞:

①撞墙:新头节点坐标超出地图边界。

②自撞:新头节点坐标与自身其他节点(非头、非尾)坐标重合。

③若碰撞,设置 “游戏结束” 状态,退出循环。

④状态显示:在屏幕右侧显示分数、速度等级、游戏状态(正常 / 暂停)。

⑤休眠控制:通过Sleep(速度)控制移动频率(速度越快,休眠时间越短,蛇移动越敏捷)

3. 结束与重玩

①游戏结束:释放蛇身链表的内存,显示 “Game Over”。

②重玩询问:提示 “是否重玩(Y/N)”,根据输入决定是否重启 “初始化→运行循环”。

2.3.3关键机制细节

①移动的本质:链表的 “头插(前进)+ 尾删(保持长度)”,视觉上呈现蛇的 “移动” 效果。

②食物系统:随机生成 + 避蛇身检测,保证食物可被吃到;吃食物后长度增长、分数增加,形成 “成长激励”。

③碰撞判定:通过坐标比对,快速判断 “撞墙” 或 “自撞”,一旦触发则终止游戏循环。

④速度与策略:F3/F4 调整Sleep时长实现 “加速 / 减速”,同时关联分数变化(加速加分、减速减分),让玩家在 “风险(速度快易撞)” 和 “收益(加分多)” 间做选择。

三、贪吃蛇游戏设计的技术栈

1. 编程语言

C 语言:游戏核心逻辑(如蛇的移动、碰撞检测、食物生成等)、数据结构定义、函数实现均使用 C 语言完成,包括结构体、枚举、指针、链表操作等 C 语言核心特性。

2. Windows API

游戏通过 Windows 系统提供的 API 实现控制台交互,主要涉及:

①控制台窗口控制:设置窗口大小,设置窗口标题。

②光标操作:隐藏和显示光标,定位光标位置(用于绘制蛇、食物、墙壁等元素)。

③键盘输入检测:实时获取键盘按键状态(如方向键、F3/F4、空格、ESC 等),实现对蛇的控制和游戏状态切换。

3. 数据结构

链表:

①蛇的身体通过链表连接,使用头插法添加新节点(蛇头移动)。

②通过遍历链表实现蛇身绘制、碰撞检测(撞自己)和内存释放。

结构体与枚举:

①存储蛇节点坐标,存储食物坐标和分数,整合蛇的核心信息(头节点、食物指针、方向、状态等)。

②用枚举定义蛇的移动方向(上下左右),用枚举定义游戏状态(正常运行、撞墙、撞自己、暂停等),使状态管理更清晰。

4. 控制台图形绘制

通过宽字符和光标定位在控制台绘制游戏元素:

①墙壁、蛇身、食物。

②游戏信息(分数、速度等级、操作提示)的文本绘制。

5. 游戏逻辑与状态管理

核心逻辑:

①蛇的移动:通过计算下一个节点坐标,结合方向枚举实现移动,并根据是否吃到食物决定是否增长蛇身或保持长度。

②碰撞检测:检测蛇头是否撞墙,检测蛇头是否撞到自身。

③分数与速度控制:吃食物增加分数,F3/F4 键调整速度(通过_speed控制休眠时间Sleep),并关联分数变化。

④状态检测:通过枚举管理游戏状态(正常运行、暂停、结束等),在循环中根据状态决定流程(继续运行、退出、重启等)

6. 内存管理

①动态内存分配:使用malloc为蛇节点分配内存,避免栈内存溢出。

②内存释放:通过遍历链表释放所有蛇节点内存,防止内存泄漏。

7. 标准库与工具

①C 标准库:stdio.h(输入输出)、stdlib.h(内存分配、随机数)、time.hsrand初始化随机数种子,确保食物位置随机)、assert.h(断言指针有效性,调试用)。

②随机数生成:rand()结合time(0)生成随机食物坐标,确保食物位置不与蛇身或墙壁重叠。

四、Windows API的详解

4.1 win32API

简单来说:Windows 是多作业系统,除了协调程序、分配内存、管资源,还像个 “服务站”—— 提供各种函数(服务)。应用程序调用这些函数,就能实现开窗口、画图形、用外设等操作,这类服务应用的函数叫 API;而 WIN32 API,就是 32 位 Windows 平台的这类编程接口。

4.2控制台主程序

平常我们运⾏起来的⿊框程序其实就是控制台程序,如下图所示:

4.2.1设置窗口大小

我们可以使用一些cmd指令来设置控制台的长宽,将控制台的长,宽设置为100 和 30

例如:通过这段指令:mode con cols=100 lines=30

4.2.2设置控制台名称

同时我们也可以设置,控制台的名称。

通过如下指令:title 贪吃蛇

4.2.3利用代码实现

当然我们也可以通过C语言代码,来实现控制台的大小和标题设置,通过system("指令")这个函数来实现

温馨提示:system("指令") 这个函数需要包含<windows.h>这个头文件

1void test01()
2{
3	system("mode con cols=130 lines=40");
4
5	system("title 贪吃蛇");
6
7	system("pause");
8}

4.3控制台屏幕上的坐标

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

在控制台上的坐标系如下图所示:

COORD类型的声明:

1typedef struct _COORD
2{
3    SHORT X;  // X坐标
4    SHORT Y;  // Y坐标
5} COORD, *PCOORD;
6    
7        
8int main()
9{
10   //例如给坐标赋值:
11   COORD  pos = { 10, 15 };
12   return 0;
13}
14

4.4通过句柄操作设备

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

简单来说就相当于一个手柄,通过该手柄就可以控制设备了,这里我们不需要过多与纠结其函数是如何实现,我们仅需要明白它的功能和如何调用就已经够用了。

GetStdHandle函数原型:

HANDLE GetStdHandle(DWORD nStdHandle);

它有三个参数:

1.STD_INPUT_HANDLE 获取标准输入设备

2.STD_OUTPUT_HANDLE 获取标准输出设备

3.STD_ERROR_HANDLE 获取标准错误设备

其中返回值HANDLE为一个void * 的指针,通过 typedef void *HANDLE 命名HANDLE。

这里我们只需要对控制台(标准输出)进行操作,所以我们仅需要用到获取标准输出设备,通过调用我们就可以进行操作控制台程序。

HANDLE GetStdHandle(STD_OUTPUT_HANDLE);

代码示例:

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

4.5获取控制台光标信息

GetConsoleCursorInfo函数原型:

BOOL WINAPI GetConsoleCursorInfo( HANDLE hConsoleOutput, PCONSOLE_CURSOR_INFO lpConsoleCursorInfo );

参数一:获取标准输出的句柄: HANDLE hConsoleOutput

参数二:指向存放光标信息的结构体: PCONSOLE_CURSOR_INFO lpConsoleCursorInfo

结构体_CONSOLE_CURSOR_INFO:主要用来存放控制台光标信息

typedef struct _CONSOLE_CURSOR_INFO

{

DWORD dwSize; //成员一 设置光标的大小

BOOL bVisible; //成员二 设置光标是否可见

} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

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

成员二:bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 TRUE

4.6设置控制台光标信息

SetConsoleCursorInfo函数:设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。

函数原型为:

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

参数一:获取标准输出的句柄: HANDLE hConsoleOutput

参数二:指向存放光标信息的结构体: PCONSOLE_CURSOR_INFO lpConsoleCursorInfo

4.7代码演示光标的设置

通过上面三个函数,我们就可以实现对光标大小和显示的操作

4.7.1设置光标大小

初始时光标的大小默认为25,如图所示:

代码示例:将默认的光标大小设置为100

1	//获得控制台窗口,进行使用
2	HANDLE houtput = NULL;
3	houtput=GetStdHandle(STD_OUTPUT_HANDLE);
4
5	//定义储存控制台光标信息的结构体
6	CONSOLE_CURSOR_INFO cursor_info = { 0 };
7
8	//获得与houtput句柄相关的控制台光标的信息
9	GetConsoleCursorInfo(houtput, &cursor_info);
10
11	//修改光标的占比值
12	cursor_info.dwSize = 100;
13
14
15	//设置光标大小和光标可见度的函数
16	SetConsoleCursorInfo(houtput, &cursor_info);

如图所示:

4.7.2设置光标是否可见

如图所示,在默认状态下光标为可见状态:

代码示例:将光标设置为不可见状态

1	//获得控制台窗口,进行使用
2	HANDLE houtput = NULL;
3	houtput=GetStdHandle(STD_OUTPUT_HANDLE);
4
5	//定义储存控制台光标信息的结构体
6	CONSOLE_CURSOR_INFO cursor_info = { 0 };
7
8	//获得与houtput句柄相关的控制台光标的信息
9	GetConsoleCursorInfo(houtput, &cursor_info);
10
11	//修改光标是否可见
12	cursor_info.bVisible = false;
13
14	//设置光标大小和光标可见度的函数
15	SetConsoleCursorInfo(houtput, &cursor_info);

4.8设置光标的位置

SetConsoleCursorPosition:设置指定控制台屏幕缓冲区中的光标位置

函数原型如下:

BOOL WINAPI SetConsoleCursorPosition( HANDLE hConsoleOutput, COORD pos );

参数一:获取标准输出的句柄: HANDLE hConsoleOutput

参数二:存放位置信息的坐标: COORD pos

通过该函数,我们就可以设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中。

调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。

4.8.1设置光标到指定的位置

1//获得控制台窗口,进行使用
2HANDLE houtput = NULL;
3houtput=GetStdHandle(STD_OUTPUT_HANDLE);
4
5//定义储存控制台光标信息的结构体
6CONSOLE_CURSOR_INFO cursor_info = { 0 };
7
8//获得与houtput句柄相关的控制台光标的信息
9GetConsoleCursorInfo(houtput, &cursor_info);
10    
11//设置控制台坐标
12COORD pos = { 10, 20 };
13    
14//设置指定位置光标
15SetConsoleCursorPosition(houtput, pos);
16    
17//进行暂停观察
18getchar();

4.8.2封装设置光标位置的函数

1//封装一个函数,用来设置光标位置
2void set_pos(short x, short y)
3{
4	HANDLE houtput= GetStdHandle(STD_OUTPUT_HANDLE);
5
6	COORD pos = { x, y };
7
8	SetConsoleCursorPosition(houtput, pos);
9}

4.9获取按键情况

GetAsyncKeyState:将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。

函数原型如下:

SHORT GetAsyncKeyState(int vKey);

参数分析:键盘上按键的虚拟键值 int vKey

返回值分析:

1.GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后。

2.如果返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;

3.可以将返回值&0x1来进行检测:GetAsyncKeyState返回值的最低值是否为1

参考:虚拟键码表

代码示例1:定义宏判断按键是否被按下

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

代码示例2:检测数字键0~9是否被按下

1//通过定义宏来判断
2#define KEY_PRESS(vk) ((GetAsyncKeyState(vk) & 1 ) ? 1 : 0 )
3void test04()
4{
5	while (1)
6	{
7		if (KEY_PRESS(0x30))
8		{
9			printf("0\n");
10		}
11		else if (KEY_PRESS(0x31))
12		{
13			printf("1\n");
14		}
15		else if (KEY_PRESS(0x32))
16		{
17			printf("2\n");
18		}
19		else if (KEY_PRESS(0x33))
20		{
21			printf("3\n");
22		}
23		else if (KEY_PRESS(0x34))
24		{
25			printf("4\n");
26		}
27		else if (KEY_PRESS(0x35))
28		{
29			printf("5\n");
30		}
31		else if (KEY_PRESS(0x36))
32		{
33			printf("6\n");
34		}
35		else if (KEY_PRESS(0x37))
36		{
37			printf("7\n");
38		}
39		else if (KEY_PRESS(0x38))
40		{
41			printf("8\n");
42		}
43		else if (KEY_PRESS(0x39))
44		{
45			printf("9\n");
46		}
47
48	}
49
50}

五、宽字符的打印

在贪吃蛇游戏中,我们采用宽字符进行界面渲染。游戏地图中的墙体使用宽字符□表示,蛇身使用●字符,食物则用★字符标识。与普通单字节字符不同,这些宽字符每个占据2个字节的存储空间。

对于宽字符的打印,需要进行本地化处理,通过如下函数进行本地化处理:

setlocale函数:进行本地化处理

函数原型如下所示:

char* setlocale (int category, const char* locale);

参数一:

• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。

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

• LC_MONETARY:影响货币格式。

• LC_NUMERIC:影响 printf() 的数字格式。

• LC_TIME:影响时间格式 strftime() 和 wcsftime() 。

• LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

一般而言我们进行传入LC_ALL对所有类型进行修改。

参数二:

C标准仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)

温馨提示:使用该函数,需要包含<locale.h>头文件

宽字符打印的注意事项:

1.宽字符的字⾯量必须加上前缀“L”,否则 C 语⾔会把字⾯量当作窄字符类型处理。

2.前缀“L”在单引号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;

3.在双引号前⾯,表⽰宽字符串,对应wprintf() 的占位符为 %ls

代码示例1:打印单个宽字符

1#include <stdio.h> 
2#include<locale.h>
3
4    
5int main()
6{
7    setlocale(LC_ALL, "");
8
9	char a = 'a';
10	char b = 'b';
11	printf("%c%c\n", a, b);
12
13	wchar_t wc1 = L'★';
14	wchar_t wc2 = L'我';
15	wprintf(L"%lc \n%lc", wc1, wc2);
16
17    return 0;
18}
19

代码示例2:打印宽字符串

1#include <stdio.h>
2     
3#include<locale.h>
4
5int main()
6{
7    setlocale(LC_ALL, "");
8
9	wprintf(L"Hello World\n");
10
11    wchar_t wstr[] = L"宽字符字符串";
12    
13    wprintf(L"%ls",wstr);
14		
15    return 0;
16}
17
18

既然看到这里了,不妨点赞+收藏,感谢大家,若有问题请指正。


C语言实战项目:贪吃蛇(1)》 是转载文章,点击查看原文


相关推荐


手把手部署 HFish 蜜罐:从防火墙配置到登录使用,新手也能轻松上手
着迷不白2025/10/2

​   在网络安全防护中,蜜罐工具能帮我们主动探测攻击行为,而 HFish 作为一款轻量易用的开源蜜罐,深受运维和安全从业者青睐。今天就带大家从 0 到 1 完成 HFish 的部署,全程步骤清晰,即使是新手也能跟着操作 —— 说不定部署完这套流程,老板看到规范的防护配置,还得给你加两千块工资呢!   一、前置准备:配置防火墙,开放关键端口 HFish 运行需要两个核心 TCP 端口:4433 端口用于 Web 管理界面访问,4434 端口用于节点与管理端的通信。为避免端口被防火墙拦截,我们需要


iOS 26 系统流畅度检测 从视觉特效到帧率稳定的实战策略
2501_916013742025/10/2

iOS 26 推出的 Liquid Glass 视觉语言,带来了全新的界面风格和动效体验,同时也给系统的渲染、合成、动画处理带来更高要求。在部分用户反馈中,升级 iOS 26 后出现系统卡顿、触控延迟、应用滑动不顺畅等问题。 要判断 iOS 26 是否真的“流畅”或在哪些场景有退化,需要有一套严谨的检测流程,而不是凭印象。 一、影响 iOS 26 流畅度的系统变化与挑战点 在 iOS 26 中,以下几个系统/界面变动是最可能牵扯到流畅性的问题点: Liquid Glass 界面开销 新系统的大


范式革命:RDMA 如何让网络成为 “分布式内存总线”
apple_ttt10/2/2025

摘要: RDMA技术通过内存访问范式革命,绕开远程CPU干预,实现设备间直接数据交互,显著降低延迟。其三大协议(InfiniBand、RoCE、iWARP)在性能、成本和兼容性上各有取舍:InfiniBand追求极致性能但成本高;RoCEv2兼容以太网但需精细配置;iWARP基于TCP,性能受限。RDMA虽解决了CPU瓶颈,但异构设备间的缓存一致性问题仍未解决,成为分布式计算向“单机化”演进的关键障碍。未来需结合CXL等一致性协议进一步突破。


学习Python中Selenium模块的基本用法(18:使用ActionChains操作鼠标)
gc_229910/1/2025

学习并验证使用Selenium模块的ActionChains操作鼠标的基本用法


在 VSCode 中运行 Vue.js 项目
小二爱编程·9/30/2025

这篇教程详细介绍了如何在VSCode中运行Vue.js项目。首先需要安装Node.js、Vue CLI和VSCode,然后通过Vue CLI创建新项目并安装依赖。接着在VSCode中打开项目文件夹,安装推荐的插件(如Volar)。最后使用npm run serve命令启动开发服务器,在浏览器访问即可查看运行效果。教程还说明了项目调试方法,包括自动刷新和断点调试。整个过程详细易懂,适合前端开发者和初学者学习使用VSCode开发Vue.js项目。


零基础从头教学Linux(Day 43)
小白银子2025/10/4

Nginx实现跨域与防盗链配置指南 四、 Nginx配置跨域 CORS 4.1 跨域的定义 同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。通常不允许不同源间的读操作。 4.2 同源的定义 如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。 与 URL http://store.company.com/dir/page.html 的源进行对比的示例: http://store.comp


Qt Theme —— 纯 qss 的 Qt 主题
hubenchang05152025/10/5

#Qt Theme —— 纯 qss 的 Qt 主题 源码地址:https://github.com/hubenchang0515/QtTheme/ Qt Theme 是一个纯 qss 的 Qt 主题项目,能够极为简单对已有项目的风格进行改进。 支持 C++、PyQt5、PyQt6、PySide2、PySide6,并以 WebAssembly 的方式在 GitHub Pages 上发布。 #安装 这里演示一下在 Python 上的使用,首先进行安装: pip install QtTheme


Flutter 开发:应用颜色使用 Class 还是 Enum?—— 你应该选择哪一个?
JarvanMo2025/10/7

在开始一个新的 Flutter 项目时,第一步就是定义你的颜色调色板(color palette) 。一个一致且可维护的颜色系统不仅能保持你的设计简洁,还能让你的应用扩展变得更加容易。 但这里有一个开发者经常面临的常见问题: 👉 在 Flutter 中,你是应该使用带有静态常量的 Class(类) ,还是使用 **Enum(枚举)**来管理颜色呢? 随着 Dart 2.17 中**增强型枚举(enhanced enums)**的到来,答案变得更有趣了。下面我们通过示例、优缺点来探讨这两种方法。


JavaScript性能优化实战:从指标到落地的全链路方案
weixin_439647792025/10/8

JavaScript性能优化实战:从指标到落地的全链路方案 实际项目中,性能优化往往不是单一手段的应用,而是“指标监测-瓶颈定位-方案实施-效果验证”的全链路过程。本文将结合电商、管理系统等真实场景,提供可落地的性能优化闭环方案。 一、性能指标体系:从“感觉卡顿”到“数据说话” 性能优化的第一步是建立可量化的指标体系,避免凭主观感受判断优化效果。前端核心性能指标可分为三类: 1. 加载性能指标 LCP(最大内容绘制):衡量首屏加载速度,目标值<2.5sTTI(交互时间):页面可完全交互


还在纠结用v-if还是v-show?看完这篇彻底搞懂Vue渲染机制!
良山有风来2025/10/10

你是不是也曾经在写Vue时纠结过:这里到底该用v-if还是v-show? 或者更惨的是,明明代码逻辑没问题,列表渲染却总是出现各种诡异bug:删除一个项,结果删错了;切换数据,页面状态全乱了... 别担心,今天我就来帮你彻底搞懂Vue的条件渲染和列表渲染,让你写出更优雅、更高效的代码! v-if和v-show:看似相似,实则大不相同 先来看个最简单的例子: <!-- v-if 的用法 --> <div v-if="isVisible">我会在条件为真时渲染</div> <!-- v-show

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0