C/C++实现迷宫游戏(进阶版)!深度优先算法实现案例
yund56 2025-05-09 09:59 26 浏览
每天一个C语言小项目,提升你的编程能力!
玩家被困在一个迷宫里,拥有一盏油灯,油灯能够照亮以玩家为中心的一片圆形区域,随着时间的流逝,油灯的照明力会逐渐下降,迷宫内随机分布着一些加油站(黄色的圆角矩形),经过这些加油站能够恢复油灯的照明力,找到地图右下角的终点(绿色圆角矩形)。就算过关。
游戏采用图块(N * N 的正方形)的方式构建地图,且墙壁,地面,玩家,终点采用四个独立的函数绘制,如果想改变地图的风格,只需要修改这些函数的内容即可。
运行效果如下:
迷宫生成采用的深度优先算法,有明显的主路。
完整的游戏源代码如下:
//////////////////////////////////////////////
// 程序名称:迷宫
//
#include <graphics.h>
#include <stack>
#include <vector>
using std::stack; // 使用STL的栈
using std::vector; // 使用STL的数组容器
// 游戏信息
#define WIN_WIDTH 400 // 窗口的宽度(单位:像素)
#define WIN_HEIGHT 300 // 窗口的高度(单位:像素)
// !!注:由于随机生成算法的原因,地图宽高只能为奇数
#define GAME_WIDTH 41 // 地图的宽度(单位:块)
#define GAME_HEIGHT 51 // 地图的高度(单位:块)
#define WALL 1 // 墙壁的数字标记
#define GROUND 0 // 地面的数字标记
#define FILLSTATE 2 // 加油站的数字标记
#define ENDPOS 3 // 终点的数字标记
#define MAXVIEW 8.0 // 最大的视野
#define MINVIEW 1 // 最小的视野
#define FILLNUM 10 // 加油站的数量
#define DARKTIME 12 // 视野下降1图块所需的时间
// 全局变量列表
int g_BlockSize; // 块大小
int g_GameMap[GAME_HEIGHT][GAME_WIDTH]; // 地图(宽高单位为块)
POINT g_EndPos; // 终点位置
POINT g_PlayerPos; // 玩家在地图上的位置
POINT g_CameraPos; // 摄像机(屏幕左上角)在地图上的位置
IMAGE g_MapImage; // 地图的图片(由于地图是固定的,在不改变缩放的情况下只需要绘制一次)
double g_ViewArray; // 视野
UINT g_BeginTime; // 游戏开始时的时间
UINT g_LastFillTime; // 上次为油灯加油的时间
// 函数列表
void initGame(); // 初始化游戏
void endGame(); // 结束游戏
void draw(); // 绘制函数
bool upDate(); // 数据更新函数
void absDelay(int delay); // 绝对延迟
bool canMove(POINT pos); // 判断某个位置是否可以移动
void computeCameraPos(); // 计算摄像机在地图上的位置
void rePaintMap(); // 重绘地图
void drawWall(POINT pos); // 绘制墙壁图块的函数
void drawGround(POINT pos); // 绘制地面图块的函数
void drawFillState(POINT pos); // 绘制油灯图块的函数
void drawEndPos(POINT pos); // 绘制终点
void drawPlayer(); // 绘制人物的函数
void drawView(); // 绘制视野
int main()
{
initGame();
while (1)
{
if (!upDate()) break; // 更新
draw(); // 绘制
absDelay(16); // 绝对延迟 16 毫秒,控制每秒 60 帧
}
endGame();
return 0;
}
void initGame()
{
g_BlockSize = 32; // 初始图块大小为 32 个像素
srand(GetTickCount()); // 初始化随机数生成
// 初始化间隔室
for (int i = 0; i < GAME_HEIGHT; i++)
{
for (int j = 0; j < GAME_WIDTH; j++)
{
if (i % 2 == 0 || j % 2 == 0) // 奇数行奇数列设为墙壁
g_GameMap[i][j] = WALL;
else
g_GameMap[i][j] = GROUND;
}
}
// 随机生成地图(使用深度优先遍历)
stack<POINT> stepStack; // 步骤栈
vector<POINT> stepPoint; // 四周的点
POINT nowPoint; // 当前步的所在点
stepStack.push({ 1,1 }); // 写入初始点 (1,1) 作为起点
nowPoint = { 1,1 };
g_GameMap[1][1] = 0xFFFF; // 标记这个点
while (!stepStack.empty()) // 只要步骤栈不空就继续循环
{
// 得到四周的点
POINT tempPoint;
for (int i = -1; i <= 1; i += 2)
{
tempPoint = { nowPoint.x,nowPoint.y + i * 2 }; // 计算点
// 判断坐标是否合法
if (tempPoint.x >= 0 && tempPoint.x <= GAME_WIDTH - 1 &&
tempPoint.y >= 0 && tempPoint.y <= GAME_HEIGHT - 1 &&
g_GameMap[tempPoint.y][tempPoint.x] != 0xFFFF)
{
stepPoint.push_back(tempPoint);
}
tempPoint = { nowPoint.x + i * 2 ,nowPoint.y }; // 计算点
// 判断坐标是否合法
if (tempPoint.x >= 0 && tempPoint.x <= GAME_WIDTH - 1 &&
tempPoint.y >= 0 && tempPoint.y <= GAME_HEIGHT - 1 &&
g_GameMap[tempPoint.y][tempPoint.x] != 0xFFFF)
{
stepPoint.push_back(tempPoint);
}
}
// 根据周围点的量选择操作
if (stepPoint.empty()) // 如果周围点都被遍历过了
{
stepStack.pop(); // 出栈当前点
if (!stepStack.empty())
nowPoint = stepStack.top(); // 更新当前点
}
else
{
stepStack.push(stepPoint[rand() % stepPoint.size()]); // 入栈当前点
g_GameMap[(nowPoint.y + stepStack.top().y) / 2][(nowPoint.x + stepStack.top().x) / 2] = 0; // 打通墙壁
nowPoint = stepStack.top(); // 更新当前点
g_GameMap[nowPoint.y][nowPoint.x] = 0xFFFF; // 标记当前点
}
stepPoint.clear(); // 清空周围点以便下一次循环
}
// 清洗标记点
for (int i = 0; i < GAME_HEIGHT; i++)
{
for (int j = 0; j < GAME_WIDTH; j++)
{
if (g_GameMap[i][j] == 0xFFFF)
g_GameMap[i][j] = 0;
}
}
// 随机生成加油站的位置
for (int i = 0; i < FILLNUM; i++)
{
POINT fillPoint = { rand() % GAME_WIDTH,rand() % GAME_HEIGHT };
// 保证在空地生成加油站
while (g_GameMap[fillPoint.y][fillPoint.x] != GROUND)
fillPoint = { rand() % GAME_WIDTH,rand() % GAME_HEIGHT };
// 标记油灯
g_GameMap[fillPoint.y][fillPoint.x] = FILLSTATE;
}
g_GameMap[GAME_HEIGHT - 2][GAME_WIDTH - 2] = ENDPOS; // 标记终点
g_EndPos = { GAME_WIDTH - 2,GAME_HEIGHT - 2 }; // 确定终点位置
g_ViewArray = MAXVIEW; // 初始视野是最大的
g_BeginTime = GetTickCount(); // 开始计时
g_LastFillTime = GetTickCount(); // 油灯加油的时间
rePaintMap(); // 绘制地图
g_PlayerPos = { g_BlockSize * 3 / 2,g_BlockSize * 3 / 2 }; // 初始化人的位置
computeCameraPos(); // 计算摄像机的位置
initgraph(WIN_WIDTH, WIN_HEIGHT); // 初始化画布
setbkmode(TRANSPARENT); // 设置背景为透明
BeginBatchDraw(); // 开始缓冲绘制
}
void endGame()
{
EndBatchDraw(); // 结束缓冲绘制
closegraph(); // 关闭画布
}
void draw()
{
// 清空设备
cleardevice();
// 绘制视野
drawView();
// 绘制人
drawPlayer();
// 绘制时间
TCHAR timeStr[256];
int loseTime = GetTickCount() - g_BeginTime; // 计算流失的时间
_stprintf_s(timeStr, _T("游戏时间:%02d:%02d"), loseTime / 1000 / 60, loseTime / 1000 % 60);
settextcolor(RGB(140, 140, 140));
outtextxy((WIN_WIDTH - textwidth(timeStr)) / 2, 3, timeStr);
FlushBatchDraw(); // 刷新屏幕
}
bool upDate()
{
POINT nextPos = g_PlayerPos; // 下一个位置
// 计算下一个位置
if (GetKeyState(VK_UP) & 0x8000) nextPos.y -= 2;
if (GetKeyState(VK_DOWN) & 0x8000) nextPos.y += 2;
if (GetKeyState(VK_LEFT) & 0x8000) nextPos.x -= 2;
if (GetKeyState(VK_RIGHT) & 0x8000) nextPos.x += 2;
// 如果下一个位置不合法
if (!canMove(nextPos))
{
if (canMove({ g_PlayerPos.x, nextPos.y })) // y 轴移动合法
nextPos = { g_PlayerPos.x, nextPos.y };
else if (canMove({ nextPos.x, g_PlayerPos.y })) // x 轴移动合法
nextPos = { nextPos.x, g_PlayerPos.y };
else // 都不合法
nextPos = g_PlayerPos;
}
// 如果是油灯则更新时间
if (g_GameMap[nextPos.y / g_BlockSize][nextPos.x / g_BlockSize] == FILLSTATE)
g_LastFillTime = GetTickCount();
// 如果是终点则通关
else if (g_GameMap[nextPos.y / g_BlockSize][nextPos.x / g_BlockSize] == ENDPOS)
{
outtextxy(WIN_WIDTH / 2 - 40, WIN_HEIGHT / 2 - 12, _T("恭喜过关!"));
FlushBatchDraw();
Sleep(1000);
return false;
}
g_PlayerPos = nextPos; // 更新位置
computeCameraPos(); // 计算摄像机的位置
// 根据时间缩减视野
static unsigned int lastTime = GetTickCount();
int loseTime = GetTickCount() - g_LastFillTime; // 计算流失的时间
g_ViewArray = MAXVIEW - loseTime / 1000.0 / DARKTIME; // 每一段时间油灯的照明力会下降一个图块
if (g_ViewArray < MINVIEW) g_ViewArray = MINVIEW;
// 处理鼠标消息
MOUSEMSG mouseMsg; // 鼠标信息
int lastBlockSize = g_BlockSize; // 保存原本的大小
while (MouseHit())
{
mouseMsg = GetMouseMsg();
if (mouseMsg.uMsg = WM_MOUSEWHEEL) // 滚轮消息
{
g_BlockSize += mouseMsg.wheel / 120;
}
}
// 如果没有滚轮消息就退出
if (lastBlockSize == g_BlockSize) return true;
// 处理滚轮消息
if (g_BlockSize >= 10 && g_BlockSize <= 50) // 块大小没有达到极限值
{
// 保证缩放后的地图不会比窗口小
if (GAME_WIDTH * g_BlockSize < WIN_WIDTH ||
GAME_HEIGHT * g_BlockSize < WIN_HEIGHT)
g_BlockSize = lastBlockSize;
rePaintMap(); // 重绘地图
// 重新计算玩家在地图上的位置
POINT mapPos = { g_PlayerPos.x / lastBlockSize,g_PlayerPos.y / lastBlockSize }; // 计算在地图上的位置
g_PlayerPos.x = mapPos.x * g_BlockSize + g_BlockSize / 2; // 计算映射后的位置
g_PlayerPos.y = mapPos.y * g_BlockSize + g_BlockSize / 2; // 计算映射后的位置
computeCameraPos(); // 重新计算摄像机位置
}
// 保证图块不会过大和过小
if (g_BlockSize < 10) g_BlockSize = 10;
if (g_BlockSize > 50) g_BlockSize = 50;
return true;
}
void absDelay(int delay)
{
static int curtime = GetTickCount();
static int pretime = GetTickCount();
while (curtime - pretime < delay)
{
curtime = GetTickCount();
Sleep(1);
}
pretime = curtime;
}
bool canMove(POINT pos)
{
// 只要外接矩形的四个顶点不在墙壁内就必定合法
return g_GameMap[(pos.y - 3) / g_BlockSize][(pos.x - 3) / g_BlockSize] != WALL &&
g_GameMap[(pos.y + 3) / g_BlockSize][(pos.x + 3) / g_BlockSize] != WALL &&
g_GameMap[(pos.y - 3) / g_BlockSize][(pos.x + 3) / g_BlockSize] != WALL &&
g_GameMap[(pos.y + 3) / g_BlockSize][(pos.x - 3) / g_BlockSize] != WALL;
}
void computeCameraPos()
{
// 以人物位置为中心计算摄像机的理论位置
g_CameraPos.x = g_PlayerPos.x - WIN_WIDTH / 2;
g_CameraPos.y = g_PlayerPos.y - WIN_HEIGHT / 2;
// 防止摄像机越界
if (g_CameraPos.x < 0) g_CameraPos.x = 0;
if (g_CameraPos.y < 0) g_CameraPos.y = 0;
if (g_CameraPos.x > GAME_WIDTH * g_BlockSize - WIN_WIDTH) g_CameraPos.x = GAME_WIDTH * g_BlockSize - WIN_WIDTH;
if (g_CameraPos.y > GAME_HEIGHT * g_BlockSize - WIN_HEIGHT) g_CameraPos.y = GAME_HEIGHT * g_BlockSize - WIN_HEIGHT;
}
void rePaintMap()
{
g_MapImage.Resize(GAME_WIDTH * g_BlockSize, GAME_HEIGHT * g_BlockSize); // 重置地图图片大小
SetWorkingImage(&g_MapImage); // 设置地图图片为当前工作图片
for (int i = 0; i < GAME_HEIGHT; i++)
{
for (int j = 0; j < GAME_WIDTH; j++)
{
switch (g_GameMap[i][j])
{
case WALL:
drawWall({ j*g_BlockSize,i*g_BlockSize }); // 绘制墙壁
break;
case FILLSTATE:
drawFillState({ j*g_BlockSize,i*g_BlockSize }); // 绘制加油站
break;
case GROUND:
drawGround({ j*g_BlockSize,i*g_BlockSize }); // 绘制地面
break;
case ENDPOS:
drawEndPos({ j*g_BlockSize,i*g_BlockSize });
break;
}
}
}
SetWorkingImage(); // 复位工作图片
}
void drawWall(POINT pos)
{
setfillcolor(RGB(254, 109, 19));
solidrectangle(pos.x, pos.y, pos.x + g_BlockSize, pos.y + g_BlockSize);
}
void drawGround(POINT pos)
{
setfillcolor(RGB(255, 255, 255));
solidrectangle(pos.x, pos.y, pos.x + g_BlockSize, pos.y + g_BlockSize);
}
void drawFillState(POINT pos)
{
drawGround(pos);
// 绘制圆角矩形
pos.x += g_BlockSize / 5;
pos.y += g_BlockSize / 5;
setfillcolor(RGB(252, 213, 11));
solidroundrect(pos.x, pos.y, pos.x + g_BlockSize / 5 * 3, pos.y + g_BlockSize / 5 * 3, g_BlockSize / 8, g_BlockSize / 8);
}
void drawEndPos(POINT pos)
{
drawGround(pos);
// 绘制圆角矩形
pos.x += g_BlockSize / 5;
pos.y += g_BlockSize / 5;
setfillcolor(RGB(87, 116, 48));
solidroundrect(pos.x, pos.y, pos.x + g_BlockSize / 5 * 3, pos.y + g_BlockSize / 5 * 3, g_BlockSize / 8, g_BlockSize / 8);
}
void drawPlayer()
{
setfillcolor(RGB(252, 213, 11));
solidcircle(g_PlayerPos.x - g_CameraPos.x, g_PlayerPos.y - g_CameraPos.y, 3);
}
void drawView()
{
// 锁定视野
HRGN viewArr;
int r = int(g_BlockSize * g_ViewArray + 0.5); // 计算视野半径
POINT orgin = g_PlayerPos;
orgin.x -= g_CameraPos.x; // 计算在屏幕上的位置
orgin.y -= g_CameraPos.y; // 计算在屏幕上的位置
viewArr = CreateEllipticRgn(orgin.x - r, orgin.y - r, orgin.x + r, orgin.y + r); // 创建一个圆形的区域
setcliprgn(viewArr); // 锁定区域
// 绘制地图
putimage(0, 0, WIN_WIDTH, WIN_HEIGHT, &g_MapImage, g_CameraPos.x, g_CameraPos.y);
// 删除区域
DeleteObject(viewArr);
// 消除区域
setcliprgn(NULL);
}
大家赶紧去动手试试吧!
此外,我也给大家分享我收集的其他资源,从最零基础开始的教程到C语言C++项目案例,帮助大家在学习C语言的道路上披荆斩棘!
编程学习书籍分享:
编程学习视频分享:
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!
对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!
相关推荐
- 没有获得Windows 10 20H2升级通知,怎样直接升级
-
微软公司已经正式发布Windows1020H2操作系统,在正常情况下,微软只会首先推送到少量电脑,然后一边推送一边采集遥测数据。收集遥测数据可以确定哪些电脑可以更新,哪些电脑在更新后可能会失败,微...
- 不想让人随便卸载你安装的程序,用这四招,他将无计可施
-
Windows10不提供设置删除应用程序限制的功能,有几种间接方法可以防止用户删除操作系统中的程序和游戏。一、WindowsInstaller服务使用Windows工具,可以部分限制用户的权限。如...
- 一文看懂苹果全球开发者大会 五大系统全面升级
-
来源:环球网【环球网智能报道记者张阳】北京时间6月23日凌晨1点,苹果全球开发者大会(WWDC2020)如期举行,还是那个熟悉的乔布斯剧院,依旧是高水准的视频展示,但是这届WWDC,却是苹果历史...
- 无需等待微软分批推送,23H2可借助注册表快速获取Win11 24H2更新
-
IT之家10月15日消息,Windows1124H2正在分批推送,但由于存在多种Bug,微软已经开始放缓其推送节奏。WindowsLatest发现,Windows1123H2...
- 办公小技巧:剑走偏锋 PPT中打造动态图表
-
年底到了少不了又要制作各种总结报表,为了让自己的报表与众不同,我们可以借助PowerPoint动画组件+报表的方式,打造出更为出彩的动态图表。下面以PowerPoint2016为例,介绍如何使用三维...
- 文档表格 版本差异何在
-
在办公过程中,对文档或表格的修改是司空见惯的事。那么,一份文档做了内容改动,如何知道差异在哪里?一份表格改动部分数据,如何知道哪些有所变动?不要说审阅和修订功能,因为不是所有人都会用这些功能来标注的,...
- Excel VBA自制日历组件16色可选 完美替代VBA日期控件
-
本日期组件可跟随单元格跟随窗体中ActiveX文本框组合框控件16种配色可选私信回复880日历可体验效果使用说明1打开自己需要应用日历面板的Excel表,注意必须是启用VBA的格式2在...
- 如何从交互角度读懂产品需求文档
-
作为设计师,理解产品经理提供的需求文档是交互设计工作的重要前提与起点,然而对于很多设计师来说,需求文档内容通常非常复杂,设计师们需要花费大量时间去消化、理解和归纳。本文作者结合公司示例,分析设计师如何...
- 植入让文档变得更强大
-
有效地利用文档置入技术,会让我们的常用文档功能变得更加强大,实现更加高效或有趣的应用。1.写字板文档嵌入其他文档有时,我们要组织一个大型的文档,但是这些文档的内容可能来自于不同种类的文档编辑器,比如...
- Office 2016滚动文本框 顺手就来
-
【电脑报在线】如果一页PPT内容较多无法在完全显示,就需要用到滚动文本框,在PPT2016中借助控件即可快速制作滚动文本框。在“告诉我你想要做什么”输入“文本框控件”,在搜索结果点击“文本框(Acti...
- Axure的多状态复选树
-
本文将详细介绍如何在Axure中实现一种增强型的多状态复选树组件,它不仅支持全选、半选和未选等状态,还具备动态加载、关键字筛选等高级功能。多状态复选树(Multi-StateCheckboxTre...
- 办公小技巧:PPT中控件图表巧联动
-
在利用PPT进行图表演示时,操作者有可能要与图表进行交互联动,比如通过输入数据来预测产品的生产情况等,这时就需要用到“开发工具”中的控件了。几个控件配合几句VBA代码,就可以轻松实现上述交互联动效果(...
- 用好插件——找回火狐的旧功能
-
现在的软件,特别是浏览器类软件,更新换代速度都很快,而且无论是外观界面还是系统组件都会有较大的变化,这样会让很多朋友无所适从。以大家常用的火狐浏览器为例,它就已经升级到了最新的35版,而且在新版中对很...
- 重新认识控件(二)
-
图片和文字,都是一种数据形式。我平时对文本框的录入,报错和提交的设计比较多。最近涉及到图片控件的设计,细细琢磨一下,这玩意还有一些平时没太注意的细节点,感觉对于其他控件的设计有指导意义,特此总结一下传...
- JSA宏教程——在文档中添加复合框控件
-
上一期,我们初步认识了控件Control,本节我们将继续控件的相关内容。这几期我们将逐一介绍相关控制。本节先介绍复合框(也叫组合框)Combobox。复合框的作用复合框就是一个下拉选项框,一次显示一个...
- 一周热门
- 最近发表
- 标签列表
-
- filter函数js (37)
- filter函数excel用不了 (73)
- 商城开发 (40)
- 影视网站免费源码最新版 (57)
- 影视资源api接口 (46)
- 网站留言板代码大全 (56)
- java版软件下载 (52)
- java教材电子课本下载 (48)
- java技术的电子书去哪看 (33)
- 0基础编程从什么开始学 (50)
- java是用来干嘛的 (51)
- it入门应该学什么 (55)
- java线上课程 (55)
- 学java的软件叫什么软件 (38)
- 程序开发软件有哪些 (53)
- 软件培训 (59)
- 机器人编程代码大全 (50)
- 少儿编程教程免费 (45)
- 新代系统编程教学 (61)
- 共创世界编程网站 (38)
- 亲测源码 (36)
- 三角函数积分公式表 (35)
- 函数的表示方法 (34)
- 表格乘法的公式怎么设置 (34)
- sumif函数的例子 (34)