C++ EasyX 实现贪吃蛇游戏完整代码与逻辑解析
本文介绍了如何利用C++标准库配合EasyX图形库开发一款经典的贪吃蛇控制台游戏。文章从需求分析出发,详细阐述了蛇的身体结构、食物生成机制、移动算法以及边界与自身碰撞的检测逻辑。通过分段解析关键代码并展示完整的可运行源文件,帮助读者理解面向对象思想在游戏逻辑中的应用,最终实现支持键盘控制、速度调节及分数统计的完整游戏体验。
系统设计与需求分析
开发一个标准的贪吃蛇游戏,需要将整体功能拆分为四个核心模块:用户界面、游戏地图、蛇实体与食物实体。
- 界面管理:提供开始、结束按钮及难度(速度)选择,提升交互体验。
- 地图坐标:作为全局定位基准,通常设定为固定宽高的矩形区域(如640x480像素)。
- 蛇属性:核心数据包括当前坐标数组、移动方向、身体长度及移动速度。行为逻辑涵盖自动移动、进食增长及死亡判定。
- 食物生成:在地图范围内随机分布,并与蛇头坐标进行碰撞判断。
数据结构定义与初始化
使用 struct 分别定义蛇和食物的状态。其中,蛇通过二维坐标数组记录每一节身体的位置,食物则记录其当前位置及玩家得分。
// 蛇的结构体定义
typedef struct Snake {
int speed; // 速度等级
pair<int, int> coor[MAXSIZE]; // 身体各节点坐标
int dir; // 方向标识:1上, 2右, 3下, 4左
int length; // 当前长度
};
// 食物结构体定义
typedef struct food {
pair<int, int> place[MAXSIZE]; // 预设食物池或动态更新
int score; // 累计分数
};
初始化流程:
- 食物初始化:重置分数,利用
srand(time(NULL))生成随机种子,计算食物在屏幕范围内的初始坐标。 - 蛇初始化:设定初始长度为3,头部朝向右侧,初始坐标位于地图左侧中部区域。
void Initfood(food& F)
{
F.score = 0;
srand(time(NULL));
// 预生成多个食物坐标备用
for (int i = 0; i < 100; i++)
{
F.place[i].first = rand() % (640 - R) + R;
F.place[i].second = rand() % (480 - R) + R;
}
}
void hatch(Snake &S)
{
S.length = 3;
S.dir = 2; // 默认向右
S.speed = 0;
// 设置初始三节身体坐标
for (int i = 0; i < 3; i++)
{
S.coor[i].first = 40 - i * 10;
S.coor[i].second = 30;
}
核心运动与绘制逻辑
游戏的视觉效果由绘制函数负责,而逻辑变化由移动函数驱动。两者结合形成平滑的游戏循环。
第1步:实现蛇身与食物绘制
遍历蛇的坐标数组,绘制头部与身体。头部使用绿色实心矩形区分,身体使用白色空心矩形。随后绘制当前目标食物。
// 绘制蛇的主体部分
for (int i = 0; i < S.length; i++)
{
int x = S.coor[i].first;
int y = S.coor[i].second;
if (i == 0) // 蛇头
{
setfillcolor(GREEN);
solidrectangle(x - R, y - R, x + R, y + R);
}
else // 蛇身
{
setcolor(WHITE);
rectangle(x - R, y - R, x + R, y + R);
}
}
第2步:处理蛇的运动轨迹
蛇的移动本质是坐标数据的偏移。新头的坐标由当前方向决定,而其余身体节点的坐标依次继承前一个节点的位置(即 coor[i] = coor[i-1]),从而实现尾部跟随效果。
void movesnake(Snake& S)
{
// 从倒数第二节开始,坐标向前一位继承
for (int i = S.length - 1; i > 0; i--)
{
S.coor[i].first = S.coor[i - 1].first;
S.coor[i].second = S.coor[i - 1].second;
}
// 根据方向更新蛇头坐标
switch (S.dir)
{
case 1: S.coor[0].second -= 10; break; // 上
case 2: S.coor[0].first += 10; break; // 右
case 3: S.coor[0].second += 10; break; // 下
case 4: S.coor[0].first -= 10; break; // 左
}
}
第3步:键盘输入控制
使用 _getch() 同步获取按键状态。为防止蛇直接反向移动导致身体重叠(如向右移动时不能直接按左键),在改变方向时需增加校验逻辑。支持方向键及WASD键位。
void control_dir(int& DIR)
{
char ch = _getch();
switch (ch)
{
case 72: case 'W': case 'w': // 向上
if (DIR != 3) DIR = 1; break;
case 77: case 'D': case 'd': // 向右
if (DIR != 4) DIR = 2; break;
case 80: case 'S': case 's': // 向下
if (DIR != 1) DIR = 3; break;
case 75: case 'A': case 'a': // 向左
if (DIR != 2) DIR = 4; break;
}
}
胜负判定与游戏循环
碰撞检测机制
游戏结束条件主要包含两类:碰壁与自噬。
- 碰壁:蛇头横纵坐标超出地图范围(小于0或大于最大宽高)。
- 自噬:蛇头坐标与身上任意一节(从头节之后的第一节算起)重合。
int isdead(Snake S)
{
// 1. 检测是否撞墙
if (S.coor[0].first > 640-R || S.coor[0].first < R ||
S.coor[0].second > 480-R || S.coor[0].second <= R)
return 1;
// 2. 检测是否咬到自己 (忽略蛇头索引0,从索引4开始检测,因为短于4节的蛇不可能咬到自己)
for (int i = 4; i < S.length; i++)
{
if (abs(S.coor[0].first - S.coor[i].first) <= 2*R &&
abs(S.coor[0].second - S.coor[i].second) <= 2*R)
return 1;
}
return 0;
}
进食逻辑
当蛇头中心坐标落入食物所在矩形区域内时判定为吃到。触发后增加分数、延长蛇身长度,并消耗下一个预生成的食物坐标。
void Eating(Snake& S, food& F)
{
// 简单的矩形碰撞检测
if (S.coor[0].first >= F.place[F.score].first - R &&
S.coor[0].first <= F.place[F.score].first + R &&
S.coor[0].second >= F.place[F.score].second - R &&
S.coor[0].second <= F.place[F.score].second + R)
{
F.score++; // 加分
S.length++; // 变长
}
}
完整代码整合
将上述所有模块整合进主循环,利用 BeginBatchDraw() 开启双缓冲绘图以减少闪烁,并通过 Sleep() 控制刷新率来实现不同等级的游戏速度。以下是合并后的完整可执行源码:
#include <iostream>
#include <graphics.h>
#include <conio.h>
#include <time.h>
using namespace std;
#define MAXSIZE 100
#define R 5
typedef struct Snake {
int speed;
pair<int, int> coor[MAXSIZE];
int dir;
int length;
};
typedef struct food {
pair<int, int> place[MAXSIZE];
int score;
};
// ... (此处省略前述已列出的子函数定义:Initfood, hatch, control_speed, control_dir, movesnake, isdead, Eating) ...
void drawsnake(Snake& S, food F)
{
char out[16] = {};
BeginBatchDraw();
while (1)
{
cleardevice();
// 绘制蛇
for (int i = 0; i < S.length; i++)
{
int x = S.coor[i].first;
int y = S.coor[i].second;
if (i == 0)
{
setfillcolor(GREEN);
solidrectangle(x - R, y - R, x + R, y + R);
}else
{
setcolor(WHITE);
rectangle(x - R, y - R, x + R, y + R);
}
}
// 绘制食物
int m = F.place[F.score].first;
int n = F.place[F.score].second;
setfillcolor(WHITE);
solidrectangle(m-R, n-R, m+R, n+R);
// 显示得分
sprintf_s(out, "%d", F.score * S.speed);
setbkmode(0);
outtextxy(570, 20, "得分");
outtextxy(620, 20, out);
FlushBatchDraw();
// 控制游戏帧率/速度
Sleep(200-S.speed*30);
// 检查按键输入
while(_kbhit())
{
control_dir(S.dir);
}
movesnake(S);
Eating(S, F);
if (isdead(S))
break;
}
cleardevice();
sprintf_s(out, "%d", F.score * S.speed);
outtextxy(280, 180, "游戏结束");
outtextxy(280, 200, "按Enter键返回主页");
FlushBatchDraw();
getchar();
EndBatchDraw();
}
void menu()
{
initgraph(640, 480);
int flag = 1;
while (flag)
{
cleardevice();
outtextxy(280, 180, "贪吃蛇游戏");
outtextxy(280, 200, "按1开始游戏");
outtextxy(280, 220, "按2结束游戏");
char ch = _getch();
switch (ch)
{
case '1':
{
food F;
Initfood(F);
Snake S;
hatch(S);
control_speed(S.speed); // 提示用户选择速度
drawsnake(S, F);
break;
}
case '2':
flag = 0;
break;
default : break;
}
}
closegraph();
}
int main(void)
{
menu();
return 0;
}
以上就是利用 C/C++ 和 EasyX 实现贪吃蛇游戏的全部内容。通过拆解数据结构、渲染逻辑与碰撞检测,您可以清晰了解基于网格移动的经典游戏是如何构建的。更多关于 C++ 图形化编程的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









