C/C++实现:迷宫游戏(进阶版),深度优先算法实现案例

Posted 一起学编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++实现:迷宫游戏(进阶版),深度优先算法实现案例相关的知识,希望对你有一定的参考价值。

每天一个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++实现:迷宫游戏(进阶版),深度优先算法实现案例的主要内容,如果未能解决你的问题,请参考以下文章

动画演示深度优先算法搜寻逃出迷宫的路径

10分钟教你用python动画演示深度优先算法搜寻逃出迷宫的路径

算法:深度优先搜索之迷宫寻径

C语言之迷宫小游戏2.0版(随机生成地图,可变颜色,优化游戏体验)

C语言之迷宫小游戏2.0版(随机生成地图,可变颜色,优化游戏体验)

C语言之迷宫小游戏2.0版(随机生成地图,可变颜色,优化游戏体验)