当前位置: 首页 > news >正文

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

前言:

        通过持续数月的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>这个头文件         

void test01()
{system("mode con cols=130 lines=40");system("title 贪吃蛇");system("pause");
}

     

4.3控制台屏幕上的坐标

        

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

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

        

COORD类型的声明:

typedef struct _COORD
{SHORT X;  // X坐标SHORT Y;  // Y坐标
} COORD, *PCOORD;int main()
{//例如给坐标赋值:COORD  pos = { 10, 15 };return 0;
}

        

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);

        

代码示例:

        

HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)
hOutput = 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

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

        

如图所示:

        

4.7.2设置光标是否可见

        

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

        

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

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

        

4.8设置光标的位置

        

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

        

函数原型如下:

        

BOOL WINAPI SetConsoleCursorPosition(
        HANDLE hConsoleOutput,
        COORD  pos
);  

        

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

        

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

        

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

        

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

        

4.8.1设置光标到指定的位置

//获得控制台窗口,进行使用
HANDLE houtput = NULL;
houtput=GetStdHandle(STD_OUTPUT_HANDLE);//定义储存控制台光标信息的结构体
CONSOLE_CURSOR_INFO cursor_info = { 0 };//获得与houtput句柄相关的控制台光标的信息
GetConsoleCursorInfo(houtput, &cursor_info);//设置控制台坐标
COORD pos = { 10, 20 };//设置指定位置光标
SetConsoleCursorPosition(houtput, pos);//进行暂停观察
getchar();

        

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

//封装一个函数,用来设置光标位置
void set_pos(short x, short y)
{HANDLE houtput= GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x, y };SetConsoleCursorPosition(houtput, pos);
}

        

4.9获取按键情况

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

        

函数原型如下:

        

SHORT GetAsyncKeyState(int vKey);

        

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

        

返回值分析:

        

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

        

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

        

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

        

参考:虚拟键码表

        

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

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

        

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

//通过定义宏来判断
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk) & 1 ) ? 1 : 0 )
void test04()
{while (1){if (KEY_PRESS(0x30)){printf("0\n");}else if (KEY_PRESS(0x31)){printf("1\n");}else if (KEY_PRESS(0x32)){printf("2\n");}else if (KEY_PRESS(0x33)){printf("3\n");}else if (KEY_PRESS(0x34)){printf("4\n");}else if (KEY_PRESS(0x35)){printf("5\n");}else if (KEY_PRESS(0x36)){printf("6\n");}else if (KEY_PRESS(0x37)){printf("7\n");}else if (KEY_PRESS(0x38)){printf("8\n");}else if (KEY_PRESS(0x39)){printf("9\n");}}}

        

五、宽字符的打印

        

在贪吃蛇游戏中,我们采用宽字符进行界面渲染。游戏地图中的墙体使用宽字符□表示,蛇身使用●字符,食物则用★字符标识。与普通单字节字符不同,这些宽字符每个占据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:打印单个宽字符

#include <stdio.h> 
#include<locale.h>int main()
{setlocale(LC_ALL, "");char a = 'a';char b = 'b';printf("%c%c\n", a, b);wchar_t wc1 = L'★';wchar_t wc2 = L'我';wprintf(L"%lc \n%lc", wc1, wc2);return 0;
}

        

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

#include <stdio.h>#include<locale.h>int main()
{setlocale(LC_ALL, "");wprintf(L"Hello World\n");wchar_t wstr[] = L"宽字符字符串";wprintf(L"%ls",wstr);return 0;
}

        

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

http://www.dtcms.com/a/414859.html

相关文章:

  • 鸿蒙应用开发之ArkTs集成AI大模型与Markdown流式渲染教程(API 20)
  • 网站建设服务器环境配置关于网站开发的网店计划书范文
  • LeetCode-hot100——将有序数组转换为二叉搜索树
  • 网站速度对seo的影响修改wordpress登录背景图片
  • Kimi 灰度测试 OK Computer,AI 从 “问答” 迈入 “执行” 新阶段​
  • 什么是 mmap?
  • 民宿客用网络升级方案:从传统到现代化的跃迁
  • 湖南省住房和城乡建设厅老网站做网站分为竞价和优化
  • 品牌创意网站建设韩国手机网站模板
  • 力扣3679. 使库存平衡的最少丢弃次数
  • LeetCode:75.前K个高频元素
  • MySQL 基础指令全解析:从入门到熟练操作
  • 南华 NHZ-03/10/13 型滚筒反力式制动检验台:传统制动检测的核心设备
  • 企业网站开发注册网站开发目录结构
  • 做网站哪个公司好 快选宁陵建站宝互联网行业简介
  • cloudfared 内网穿透通过docker方式遇到的问题
  • 利用汇出与汇入函式协助自动化程式开发
  • iis网站域名访问辽宁工程招标网信息平台
  • 网站开发模板系统myphoto wordpress 国外
  • UART,IIC,SPI串行通信优缺点
  • debian12 安装pve
  • DM存储过程和函数中常用的变量、循环、游标、抛出异常
  • php网站开发怎么样西安网站维护 策划
  • 写csv测试
  • 网站访问不了的原因app运营
  • 一种物联网的节水灌溉系统(论文+源码)
  • 配置AC5(ARM Compiler 5)编译器
  • 合肥中科大网站开发wordpress5.0版本恢复到旧版本
  • 网站开发人员要求仿中国加盟网站源码
  • MySQL InnoDB持久化统计信息详解