跳到主要内容C++贪吃蛇游戏代码实现与核心逻辑解析 | 极客日志C++算法
C++贪吃蛇游戏代码实现与核心逻辑解析
基于C++控制台开发贪吃蛇游戏,利用Windows API控制光标与输入。通过结构体封装坐标、地图、蛇等核心数据,采用模块化设计实现初始化、绘制、移动、碰撞检测及食物生成等功能。文章详细解析了蛇身跟随逻辑、方向反向限制优化及随机种子处理等关键点,并提供了扩展建议如难度等级与最高分记录,适合C++初学者理解游戏循环与数据结构应用。
猫巷少女53 浏览 C++贪吃蛇游戏代码实现与核心逻辑解析
一、游戏核心概述
1.1 游戏原理
贪吃蛇游戏是经典的即时交互游戏,核心逻辑为:通过键盘控制蛇的移动方向,蛇在固定地图内追逐食物,吃到食物后身体变长、分数增加且移动速度加快,若蛇头触碰边界或自身身体则游戏结束。
本实现基于C++控制台开发,借助Windows API控制光标位置与隐藏,通过时间戳控制蛇的移动频率,采用结构化设计封装地图、蛇、坐标等核心要素,逻辑清晰且易扩展。
1.2 核心依赖库
| 库名 | 核心用途 |
|---|
iostream | 控制台输入输出(绘制地图、显示分数等) |
windows.h | 控制控制台光标(隐藏、移动)、获取标准输出句柄 |
chrono | 时间戳计算,控制蛇的移动频率(毫秒级精度) |
conio.h | 检测键盘输入(_kbhit())、获取按键(_getch()) |
ctime/cstdlib | 生成随机数种子(srand()),用于食物随机生成 |
sstream/cstring | 分数转换为字符串(实时更新分数显示) |
二、核心数据结构设计
代码采用结构体封装游戏核心要素,实现数据与操作的解耦,符合模块化编程思想,主要包含以下4种核心结构:
2.1 坐标结构体(pos)
struct pos { int x;
解析:作为基础数据结构,用于表示蛇头、蛇身、食物在地图上的位置,所有与'位置'相关的操作(移动、绘制、碰撞检测)均依赖此结构。
2.2 地图结构体(map)
enum blocktype { FOOD = 1,
struct map {
blocktype block[H][L];
bool hasfood;
};
关键说明:
- 用枚举
blocktype定义地图格子类型,使代码语义更清晰,避免魔法值;
- 二维数组
block存储整个地图的状态,每个元素表示对应位置是食物还是空区域;
hasfood作为食物生成的'开关',仅当地图无食物时才触发生成逻辑,提升效率。
2.3 蛇结构体(snake)
const int fangxiang[4][2] = {
{-1,0},
{1,0},
{0,-1},
{0,1}
};
struct snake {
pos aspos[H * L];
int sfangxiang;
int schangdu;
int movelasttime;
int movepinglu;
};
关键说明:
- 方向数组
fangxiang:将方向与坐标变化绑定,通过下标快速获取移动时的坐标偏移量,简化方向控制逻辑;
aspos数组:按'头在前、尾在后'的顺序存储蛇身位置,aspos[0]固定为蛇头;
- 移动控制相关属性:
movelasttime与movepinglu配合,实现蛇移动频率的精确控制,避免移动过快或过慢。
三、核心功能模块解析
代码按'初始化→绘制→交互控制→逻辑判断→主循环'的流程组织,各模块职责单一,相互配合完成游戏运行。
3.1 初始化模块
负责游戏启动时的环境准备,包括地图、蛇的初始化及光标隐藏,为游戏运行奠定基础。
3.1.1 地图初始化(initmap)
void initmap(map& Map) {
for (int y = 0; y < H; y++) {
for (int u = 0; u < L; u++) {
Map.block[y][u] = blocktype::EMPTY;
}
}
Map.hasfood = false;
}
解析:双重循环遍历地图二维数组,将所有位置设为空区域,同时标记无食物状态,确保游戏初始环境干净。
3.1.2 蛇初始化(initsnake)
void initsnake(snake& asnake) {
asnake.sfangxiang = 3;
asnake.schangdu = 3;
asnake.aspos[0] = { L / 2, H / 2 };
asnake.aspos[1] = { L / 2 - 1, H / 2 };
asnake.aspos[2] = { L / 2 - 2, H / 2 };
asnake.movelasttime = 0;
asnake.movepinglu = 200;
}
解析:初始化蛇的核心状态,包括方向、长度、初始位置和移动参数。初始位置设为地图居中,提升游戏体验;方向默认向右,符合多数玩家的操作直觉。
3.1.3 光标隐藏(hidecursor)
void hidecursor() {
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO curInfo = { 1, FALSE };
SetConsoleCursorInfo(hOutput, &curInfo);
}
解析:通过Windows API隐藏控制台光标,避免光标闪烁影响游戏画面的连贯性,提升视觉体验,是控制台游戏开发的常用优化手段。
3.2 绘制模块
负责将地图、蛇、食物、分数等游戏元素渲染到控制台,是玩家与游戏交互的视觉载体,核心为'精准定位+内容输出'。
3.2.1 地图绘制(drawmap)
void drawmap(map& tmap) {
system("cls");
cout << "┌";
for (int i = 0; i < L; i++) cout << "─";
cout << "┐" << endl;
for (int p = 0; p < H; p++) {
cout << "│";
for (int a = 0; a < L; a++) {
if (tmap.block[p][a] == blocktype::EMPTY) cout << " ";
}
cout << "│" << endl;
}
cout << "└";
for (int i = 0; i < L; i++) cout << "─";
cout << "┘" << endl;
}
关键说明:
- 用
system("cls")清空控制台,确保每帧画面干净,避免残留;
- 采用Unicode边框字符(┌、─、┐等),使地图边框更美观,区别于传统的*或#边框;
- 中间区域仅绘制边框和空区域,蛇和食物通过后续独立绘制函数渲染,实现'分层绘制',逻辑更清晰。
3.2.2 单个元素绘制(drawunit/drawunit2)
void drawunit(pos& p, const char unit[]) {
COORD coord;
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
coord.X = p.x + 1;
coord.Y = p.y + 1;
SetConsoleCursorPosition(houtput, coord);
cout << unit;
}
解析:游戏绘制的核心工具函数,通过SetConsoleCursorPosition精准移动光标到目标位置,再输出对应元素。关键在于'坐标偏移'——地图坐标(0,0)对应控制台的(1,1),因为边框占用了第一行和第一列。
3.2.3 蛇绘制(drawsnake)
void drawsnake(snake& snk) {
for (int g = 0; g < snk.schangdu; ++g) {
drawunit(snk.aspos[g], "■");
}
}
解析:遍历蛇身位置数组aspos,调用drawunit将每一节蛇身绘制为'■',实现蛇的可视化。由于蛇头和蛇身采用相同样式,通过位置(aspos[0])区分蛇头。
3.3 核心逻辑模块
是游戏的'大脑',包含蛇的移动、碰撞检测、食物生成、分数计算等核心规则,决定游戏的运行状态。
3.3.1 蛇的移动(movesnake)
void movesnake(snake& snk) {
for (int f = snk.schangdu - 1; f >= 1; f--) {
snk.aspos[f] = snk.aspos[f - 1];
}
snk.aspos[0].y += fangxiang[snk.sfangxiang][0];
snk.aspos[0].x += fangxiang[snk.sfangxiang][1];
}
关键逻辑(重点):
- 蛇身跟随规则:必须从尾部开始向前更新位置,若从头部开始会导致所有节都复制蛇头位置,蛇身'消失'。核心思想是'每一节都移动到前一节的旧位置';
- 蛇头移动规则:通过方向数组获取坐标偏移量,直接更新蛇头位置,无需遍历,效率为O(1)。
时间复杂度:O(n)(n为蛇的长度),仅与蛇身长度相关,效率较高。
3.3.2 碰撞检测(checkbianyuan + 吃食物检测)
碰撞检测是游戏结束和状态更新的核心触发条件,分为'边界碰撞''自身碰撞'和'食物碰撞'三类。
bool checkbianyuan(pos& p, snake &snk) {
if (p.x < 0 || p.x >= L || p.y < 0 || p.y >= H) {
return false;
}
for (int i = 1; i < snk.schangdu; i++) {
if (snk.aspos[0].x == snk.aspos[i].x && snk.aspos[0].y == snk.aspos[i].y) {
return false;
}
}
return true;
}
bool checkeatfood(snake& snk, map& Map, pos& tail) {
pos head = snk.aspos[0];
if (Map.block[head.y][head.x] == blocktype::FOOD) {
snk.aspos[snk.schangdu++] = tail;
Map.block[head.y][head.x] = blocktype::EMPTY;
Map.hasfood = false;
drawunit(tail, "■");
return true;
}
return false;
}
关键说明:
- 自身碰撞检测时,跳过蛇头(
i 从 1 开始),避免蛇头与自身比较导致误判;
- 吃食物时,利用移动前记录的蛇尾位置(
tail)追加蛇身,模拟'蛇身变长'的效果,同时清除食物标记,触发新食物生成。
3.3.3 食物生成(checkfoodgenerate)
void checkfoodgenerate(snake& snk, map& Map) {
if (!Map.hasfood) {
while (true) {
int x = rand() % L;
int y = rand() % H;
bool isSnake = false;
for (int i = 0; i < snk.schangdu; i++) {
if (snk.aspos[i].x == x && snk.aspos[i].y == y) {
isSnake = true;
break;
}
}
if (!isSnake) {
Map.block[y][x] = blocktype::FOOD;
Map.hasfood = true;
pos z = { x, y };
drawunit(z, "●");
return;
}
}
}
}
关键逻辑:
- 随机坐标生成:通过
rand() % L和rand() % H确保坐标在地图范围内;
- 位置合法性校验:循环检测随机位置是否在蛇身上,若在则重新生成,避免食物'嵌入'蛇身导致无法被吃;
- 生成开关控制:
hasfood确保同一时间地图上只有一个食物,符合游戏规则。
3.3.4 方向控制(controlfangxiang)
void controlfangxiang(snake& snk) {
if (_kbhit()) {
switch (_getch()) {
case 'w':
if (snk.sfangxiang != 1) snk.sfangxiang = 0;
break;
case 's':
if (snk.sfangxiang != 0) snk.sfangxiang = 1;
break;
case 'a':
if (snk.sfangxiang != 3) snk.sfangxiang = 2;
break;
case 'd':
if (snk.sfangxiang != 2) snk.sfangxiang = 3;
break;
}
}
}
核心优化:
添加'反向限制'——蛇不能直接向相反方向移动(如当前向下时不能直接向上),避免玩家误操作导致蛇头立即碰撞自身,提升游戏的容错性和体验。
3.4 游戏主循环
主循环是游戏的'心脏',不断循环执行'输入检测→状态更新→画面渲染'流程,直到游戏结束条件触发。
int main() {
map Map;
snake snk;
int score = 0;
int i = 4;
srand((unsigned int)time(NULL));
hidecursor();
initmap(Map);
drawmap(Map);
initsnake(snk);
drawsnake(snk);
pos scorePos = { L/2-5, 0 };
pos gameOverPos = { L/2-4, H/2 };
while (true) {
if (!checksnakemove(snk, Map, score,i)) break;
stringstream ss;
ss << "得分:" << score;
char scoreChar[100];
strcpy_s(scoreChar, ss.str().c_str());
drawunit(scorePos, scoreChar);
controlfangxiang(snk);
checkfoodgenerate(snk, Map);
}
drawunit(gameOverPos, "游戏结束");
while (true) {}
return 0;
}
主循环核心流程:
- 初始化阶段:完成地图、蛇、随机种子等初始化,绘制初始画面;
- 循环阶段:通过
checksnakemove控制蛇的移动频率,移动失败则退出循环(游戏结束);
- 将分数转换为字符串并实时更新显示;
- 检测方向键输入,更新蛇的移动方向;
- 检测食物状态,无食物则生成新食物。
- 结束阶段:显示'游戏结束'提示,通过无限循环保持控制台窗口,避免程序立即关闭。
四、关键优化与易错点总结
4.1 核心优化点
- 随机种子优化:
srand()放在main函数开头仅初始化一次,避免每次生成食物时重复初始化,导致食物位置集中或重复;
- 移动频率控制:通过
chrono库的时间戳计算移动间隔,实现毫秒级精确控制,比Sleep()更灵活(不阻塞输入检测);
- 方向反向限制:避免蛇直接反向移动撞自身,提升容错性;
- 分层绘制:地图、蛇、食物、分数分别绘制,逻辑清晰,便于维护和扩展。
4.2 常见易错点
- 坐标偏移错误:绘制时未考虑边框的1行1列偏移,导致蛇或食物'超出'地图边框;
- 蛇身移动顺序错误:从头部开始更新蛇身位置,导致蛇身覆盖,表现为'蛇身消失';
- 随机种子重复初始化:在
checkfoodgenerate中调用srand(),导致同一秒内生成的食物位置相同;
- 碰撞检测时机错误:移动前检测蛇头位置,未考虑移动后的新位置,导致碰撞判断延迟;
- 分数更新不及时:未实时刷新分数显示,导致玩家无法及时获取得分反馈。
五、扩展与改进方向
- 增加游戏难度等级:设置不同初始速度和加速幅度,满足不同玩家需求;
- 添加特殊食物效果:如'减速食物''加分食物''无敌时间'等,提升游戏趣味性;
- 记录最高分:将最高分存储到文件,游戏结束时对比并更新,增加游戏粘性;
- 优化画面效果:为蛇头和蛇身设置不同颜色(通过Windows API控制控制台文字颜色),提升视觉区分度;
- 添加暂停功能:支持空格键暂停/继续游戏,提升操作灵活性。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online