C语言实现一个走迷宫小游戏(深度优先算法)

        补充一下,先前文章末尾给出的下载链接的完整代码含有部分C++的语法(使用Dev-C++并且文件扩展名为.cpp的没有影响),如果有的朋友使用的语言标准是VC6的话可能不支持,所以在修改过后再上传一版,这次直接放在文章末尾了,复制粘贴就行,希望对您有所帮助。

        转载于我的博客园:https://www.cnblogs.com/xiao-qi-w/p/13031637.html

        接上一篇万年历博文,还是那位朋友的练习题。这次是使用C语言做一个小游戏程序,三选一(2048、8皇后和迷宫游戏),我选择的是迷宫(文章末尾有源码下载链接以及演示视频链接)。个人认为这个程序的难点在于迷宫地图的绘制,也就是怎么建立一个迷宫。如果迷宫地图是在程序里写死的,那可玩性就大大降低了。那么能不能像正常游戏一样生成一个随机地图呢?当然有!在网上查到的结果不外乎这三种:深度优先算法、prim算法和递归分割算法。这三种算法的优劣比较可前往这篇博文一探究竟:

  三大迷宫生成算法 (Maze generation algorithm) -- 深度优先,随机Prim,递归分割

  至于代码实现我参考的是CSDN博主 jjwwwww 的三篇迷宫算法文章的第一篇,全部文章的链接如下:

  1. 随机迷宫生成算法——深度优先算法
  2. 随机迷宫生成算法——prime算法
  3. 随机迷宫生成算法——递归分割算法

  下面来看一下思路和代码:

  维基百科中给出的深度优先(递归回溯)算法思路如下:

  1.将起点作为当前迷宫单元并标记为已访问
  2.当还存在未标记的迷宫单元,进行循环
    1.如果当前迷宫单元有未被访问过的的相邻的迷宫单元
      1.随机选择一个未访问的相邻迷宫单元
      2.将当前迷宫单元入栈
      3.移除当前迷宫单元与相邻迷宫单元的墙
      4.标记相邻迷宫单元并用它作为当前迷宫单元
    2.如果当前迷宫单元不存在未访问的相邻迷宫单元,并且栈不空
      1.栈顶的迷宫单元出栈
      2.令其成为当前迷宫单元

  如果你觉得上面这个表述不太容易理解,那么来看看下面这个思路(还是上面那个博主的,也是我的代码采用的):

  1. 首先假设迷宫只有一条正确的道路。
  2. 假设自己是一只地鼠,要在这个区域不停的挖,直到任何一块区域再挖就会挖穿了为止。
  3. 我们挖的道路就像树结构,树上有很多的分支,分支也有子分支,每个子分支都不能相交,相交了就说明墙被挖穿了,那么此时的迷宫就可能存在多条正确道路,这不是我想看到的。
  4. 那么基于唯一道路的原则,我们向某个方向挖一块新的区域时,要先判断新区域是否有挖穿的可能,如果可能挖穿要立即停止并换个方向再挖。如图:

  

   有了思路,就有了下面的代码,创建迷宫:

void CreateMaze(int **maze, int x, int y) {//构建迷宫
	maze[x][y] = ROUTE;
	//确保四个方向随机,而不再是固定的上下左右这种排列 
	int direction[4][2] = { { 1,0 },{ -1,0 },{ 0,-1 },{ 0,1 } };
	for (int i = 0; i < 4; i++) {
		int r = rand() % 4;
		int temp = direction[0][0];
		direction[0][0] = direction[r][0];
		direction[r][0] = temp;
		temp = direction[0][1];
		direction[0][1] = direction[r][1];
		direction[r][1] = temp;
	} 
	//向四个方向开挖
	for (int i = 0; i < 4; i++) {
		int dx = x;
		int dy = y;
		//控制挖的距离,由Rank来调整大小
		int range = 1 + (Rank == 0 ? 0 : rand() % Rank);
		while (range > 0) {
			//计算出将要访问到的坐标 
			dx += direction[i][0];
			dy += direction[i][1];
			//排除掉回头路
			if (maze[dx][dy] == ROUTE) {
				break;
			}
			//判断是否挖穿路径
			int count = 0;
			for (int j = dx - 1; j < dx + 2; j++) {
				for (int k = dy - 1; k < dy + 2; k++) {
					//abs(j - dx) + abs(k - dy) == 1 确保只判断九宫格的四个特定位置
					if (abs(j - dx) + abs(k - dy) == 1 && maze[j][k] == ROUTE) {
						count++;
					}
				}
			}
			//count大于1表明墙体会被挖穿,停止 
			if (count > 1)
				break;
			//确保不会挖穿时,前进
			range -= 1;
			maze[dx][dy] = ROUTE;
		}
		//没有挖穿危险,以此为节点递归
		if (range <= 0) {
			CreateMaze(maze, dx, dy);
		}
	}
}

  当然这样还不够,为了防止挖路时挖出边界,同时为了保护迷宫主体外的一圈墙体被挖穿,我们把最外层全部设为路径。此外还需要设置入口以及寻找出口,如下:

 int init(int** Maze) {//初始化迷宫
     //最外围层设为路径的原因,为了防止挖路时挖出边界,同时为了保护迷宫主体外的一圈墙体被挖穿
     for (int i = 0; i < L; i++) {
         Maze[i][0] = ROUTE;
         Maze[0][i] = ROUTE;
         Maze[i][L - 1] = ROUTE;
         Maze[L - 1][i] = ROUTE;
     }
     //创造迷宫,(2,2)为起点
     CreateMaze(Maze, 2, 2);
     //画迷宫的入口和出口,给出玩家初始位置
     Maze[2][1] = PLAYER;
     //由于算法随机性,出口有一定概率不在(L-3,L-2)处,此时需要寻找出口
     for (int i = L - 3; i >= 0; i--) {
         if (Maze[i][L - 3] == ROUTE) {
             Maze[i][L - 2] = ROUTE;
             //返回出口所在的纵坐标 
             return i;
         }
     }
 }

   至此,我们已经能够生成一幅迷宫地图了,下面用函数把它展示出来:

 void print(int** Maze) {//画迷宫
     for (int i = 0; i < L; i++) {
         for (int j = 0; j < L; j++) {
             if (Maze[i][j] == ROUTE)
                 printf("  ");//表示道路
             else if(Maze[i][j] == WALL)
                 printf("回");//表示墙体
             else
                 printf("十");//表示玩家
         }
         printf("\n");
     }
 }

  实际绘制效果如下:

   至此我们已经完成80%的工作了,如何互动起来就看我们的输入了。我的想法是利用w,s,a,d四个键作为控制键控制角色 “十” 上下左右移动,当移动到出口处游戏结束。把这个想法转换为代码如下:

void start() { //开始一局游戏 
	char t;
	//y,x表示角色横纵坐标, out表示出口的纵坐标
	int x = 2, y = 1, out = 0;
	//随机数发生器初始化函数
	srand((unsigned)time(NULL));
	//申请数组空间
	int **Maze = (int**)malloc(L * sizeof(int *));
	for (int i = 0; i < L; i++) {
		Maze[i] = (int*)calloc(L, sizeof(int));
	}
	//得到出口纵坐标
	out = init(Maze);
	//游戏开始
	system("cls");
	print(Maze);
	while(t = getch()) {
		if(t == 27)   //如果输入为ESC键,结束游戏回到主菜单
			break;
		system("cls");//清屏
		move(Maze, t, x, y);//根据输入t进行移动
		print(Maze);//重新绘制迷宫
		if(x == out && y == L-2) {//已经到出口,游戏结束
			system("cls");
			printf("=============\n");
			printf("游 戏 胜 利!\n");
			printf("=============\n");
			printf("即将后返回主菜单……");
			Sleep(1500);//执行挂起一段时间,暂停1.5秒后打印
			break;
		}
	}
	//一局游戏结束,释放内存 
	for (int i = 0; i < L; i++) free(Maze[i]);
	free(Maze);
}

  为了游戏体验我使用了getch()这个库函数代替getchar(),好处就是不回显。为了让这个游戏看起来还比较像样,我就把他做成了下面这个样子:

  开始界面:           难度调整界面:

  

  说明界面:

  编辑工具:Dev-C++(版本:5.11.0.0)

  编译器:TDM-GCC 4.9.2 64-bit Release

  源码下载链接:https://download.csdn.net/download/qq_43464624/12489415

  程序演示视频链接:https://www.bilibili.com/video/BV1pz411i7Nz/

修改过后的VC6标准的完整代码:

#include<stdio.h>
#include<windows.h>
#include<conio.h>
#include<time.h>
#include<math.h>

//地图边长L,包括迷宫主体20,外侧的包围的墙体2,最外侧包围路径2(之后会解释)
//可根据需要修改,有上限 
#define L 24

#define WALL  0 //墙 
#define ROUTE 1 //路径 
#define PLAYER 2//玩家 

//控制迷宫的复杂度,数值越大复杂度越低,最小值为0
//默认为简单难度,可根据需要在degree函数里调整不同难度的复杂度 
int Rank = 6;

void menu();   //主菜单界面
void start();  //开始游戏
void degree(); //游戏难度 
void explain();//游戏说明
int init(int** Maze);  //初始化迷宫
void print(int** Maze);//画迷宫
void CreateMaze(int **maze, int x, int y);    //创建迷宫
void move(int** Maze, char t, int *x, int *y);//移动角色

int main() {
	menu();
	return 0;
}

void menu() { //主菜单
	while(1) {
		system("cls"); //清屏
		char t;
		printf("*******(走迷宫)*******");
		printf("\n======================\n");
		printf("\n||   1. 开始 游戏   ||\n");
		printf("\n||   2. 游戏 说明   ||\n");
		printf("\n||   3. 游戏 难度   ||\n");
		printf("\n||   4. 关闭 游戏   ||\n");
		printf("======================\n");
		t=getch();    //不回显函数
		switch(t) {
			case '1':
				start();
				break;  //开始一局游戏
			case '2':
				explain();
				break;  //进入游戏说明界面
			case '3':
				degree();
				break;  //调整游戏难度 
			case '4':
				printf("\n欢迎下次再玩,再见( ̄︶ ̄)↗");
				Sleep(1500);
				exit(0);
				break;  //结束程序
			default :
				break;
		}
	}
}

void CreateMaze(int **maze, int x, int y) {//构建迷宫
	maze[x][y] = ROUTE;
	//确保四个方向随机,而不再是固定的上下左右这种排列 
	int direction[4][2] = { { 1,0 },{ -1,0 },{ 0,-1 },{ 0,1 } };
	int i, j;
	for (i = 0; i < 4; i++) {
		int r = rand() % 4;
		int temp = direction[0][0];
		direction[0][0] = direction[r][0];
		direction[r][0] = temp;
		temp = direction[0][1];
		direction[0][1] = direction[r][1];
		direction[r][1] = temp;
	} 
	//向四个方向开挖
	for (i = 0; i < 4; i++) {
		int dx = x;
		int dy = y;
		//控制挖的距离,由Rank来调整大小
		int range = 1 + (Rank == 0 ? 0 : rand() % Rank);
		while (range > 0) {
			//计算出将要访问到的坐标 
			dx += direction[i][0];
			dy += direction[i][1];
			//排除掉回头路
			if (maze[dx][dy] == ROUTE) {
				break;
			}
			//判断是否挖穿路径
			int count = 0, k;
			for (j = dx - 1; j < dx + 2; j++) {
				for (k = dy - 1; k < dy + 2; k++) {
					//abs(j - dx) + abs(k - dy) == 1 确保只判断九宫格的四个特定位置
					if (abs(j - dx) + abs(k - dy) == 1 && maze[j][k] == ROUTE) {
						count++;
					}
				}
			}
			//count大于1表明墙体会被挖穿,停止 
			if (count > 1)
				break;
			//确保不会挖穿时,前进
			range -= 1;
			maze[dx][dy] = ROUTE;
		}
		//没有挖穿危险,以此为节点递归
		if (range <= 0) {
			CreateMaze(maze, dx, dy);
		}
	}
}

int init(int** Maze) {//初始化迷宫
	int i;
	//最外围层设为路径的原因,为了防止挖路时挖出边界,同时为了保护迷宫主体外的一圈墙体被挖穿
	for (i = 0; i < L; i++) {
		Maze[i][0] = ROUTE;
		Maze[0][i] = ROUTE;
		Maze[i][L - 1] = ROUTE;
		Maze[L - 1][i] = ROUTE;
	}
	//创造迷宫,(2,2)为起点
	CreateMaze(Maze, 2, 2);
	//画迷宫的入口和出口,给出玩家初始位置
	Maze[2][1] = PLAYER;
	//由于算法随机性,出口有一定概率不在(L-3,L-2)处,此时需要寻找出口
	for (i = L - 3; i >= 0; i--) {
		if (Maze[i][L - 3] == ROUTE) {
			Maze[i][L - 2] = ROUTE;
			//返回出口所在的纵坐标 
			return i;
		}
	}
}

void print(int** Maze) {//画迷宫
	int i, j;
	for (i = 0; i < L; i++) {
		for (j = 0; j < L; j++) {
			if (Maze[i][j] == ROUTE)
				printf("  ");//表示道路
			else if(Maze[i][j] == WALL)
				printf("回");//表示墙体
			else
				printf("十");//表示玩家
		}
		printf("\n");
	}
}
//将原版的int &x改为int *x,把引用换成了由指针指向
void move(int** Maze, char t, int *x, int *y) {//移动角色
	int i = *x, j = *y;//记录原始位置
	switch(t) {
		case 'w':	//向上移动
			*x -= 1;
			break;
		case 's':	//向下移动
			*x += 1;
			break;
		case 'a':	//向左移动
			*y -= 1;
			break;
		case 'd':	//向右移动
			*y += 1;
			break;
		default:
			break;
	}
	if(*x>=0 && *x<L-1 && *y>=0 && *y<L-1 && Maze[*x][*y]!=WALL) {//符合条件,移动
		Maze[i][j] = 1;
		Maze[*x][*y] = 2;
	} else {//保持位置不变
		*x = i;
		*y = j;
	}
}

void start() { //开始一局游戏 
	char t;
	//y,x表示角色横纵坐标, out表示出口的纵坐标
	int *p, *q;
	int x = 2, y = 1, out = 0, i = 0;
	p = &x;
	q = &y;
	//随机数发生器初始化函数
	srand((unsigned)time(NULL));
	//申请数组空间
	int **Maze = (int**)malloc(L * sizeof(int *));
	for (i = 0; i < L; i++) {
		Maze[i] = (int*)calloc(L, sizeof(int));
	}
	//得到出口纵坐标
	out = init(Maze);
	//游戏开始
	system("cls");
	print(Maze);
	while(t = getch()) {
		if(t == 27)   //如果输入为ESC键,结束游戏回到主菜单
			break;
		system("cls");//清屏
		move(Maze, t, p, q);//根据输入t进行移动
		print(Maze);//重新绘制迷宫
		if(x == out && y == L-2) {//已经到出口,游戏结束
			system("cls");
			printf("=============\n");
			printf("游 戏 胜 利!\n");
			printf("=============\n");
			printf("即将后返回主菜单……");
			Sleep(1500);//执行挂起一段时间,暂停1.5秒后打印
			break;
		}
	}
	//一局游戏结束,释放内存 
	for (i = 0; i < L; i++) free(Maze[i]);
	free(Maze);
}

void explain() { //操作说明 
	while(1) {
		char t;
		system("cls");
		printf("=================================================\n");
		printf("感谢您体验本游戏,游戏的操作如下:\n");
		printf("\n1.将输入法调整为英文(小写)\n");
		printf("\n2.通过w,s,a,d四个键控制角色上下左右移动\n");
		printf("\n3.在任意界面均可按“ESC”键返回到主菜单\n");
		printf("\n胜利条件:移动角色到出口处,加油各位( ̄▽ ̄)\"!\n");
		printf("=================================================\n");
		t=getch();  //不回显函数 
		switch(t) {
				//ESC键的ASCII码值 
			case 27:
				//返回主菜单
				menu();
				break;
			default :
				break;
		}
	}
}

void degree() { //调整游戏难度 
	while(1) {
		char t;
		system("cls");
		printf("=======================\n");
		printf("输入1,2,3进行难度调整:\n");
		printf("\n||    1.简  单    ||\n");
		printf("\n||    2.中  等    ||\n");
		printf("\n||    3.困  难    ||\n");
		printf("=======================\n");
		t=getch();  //不回显函数 
		switch(t) {
			case '1':
				Rank = 6;
				printf("\n当前难度:简单,即将返回主菜单……");
				Sleep(1500);
				menu();//返回主菜单
				break; 
			case '2':
				Rank = 3;
				printf("\n当前难度:中等,即将返回主菜单……");
				Sleep(1500);
				menu();//返回主菜单
				break;
			case '3':
				Rank = 0;
				printf("\n当前难度:困难,即将返回主菜单……");
				Sleep(1500);
				menu();//返回主菜单
				break;
			case 27:
				menu();
				break; 
			default :
				break;
		}
	}
}

  非常感谢您的观看,如果对你有所帮助的话点个赞再走呗!

  • 62
    点赞
  • 210
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
好的,很高兴回答你的问题。 在C语言中,实现深度优先搜索算法来解决迷宫问题,可以按照以下步骤进行: 1. 定义一个二维数组来表示迷宫,0表示通路,1表示障碍物或墙壁。 2. 定义一个栈,用来存储走过的路径。 3. 定义一个结构体来表示一个点的坐标。 ```c typedef struct { int x; int y; } Point; ``` 4. 定义一个函数,将起点入栈,并将其标记为已访问。 ```c void dfs(int maze[][MAX_SIZE], int visit[][MAX_SIZE], Point start, Point end, Stack *stack) { push(stack, start); visit[start.x][start.y] = 1; } ``` 5. 定义一个函数,判断当前点是否为终点,如果是,则输出路径并退出程序。 ```c void dfs(int maze[][MAX_SIZE], int visit[][MAX_SIZE], Point start, Point end, Stack *stack) { ... if (start.x == end.x && start.y == end.y) { printPath(stack); exit(0); } ... } ``` 6. 定义一个函数,遍历当前点的上下左右四个方向,如果该方向为通路且未被访问过,则将该点入栈,并将其标记为已访问。 ```c void dfs(int maze[][MAX_SIZE], int visit[][MAX_SIZE], Point start, Point end, Stack *stack) { ... // 上 if (start.x > 0 && maze[start.x - 1][start.y] == 0 && visit[start.x - 1][start.y] == 0) { Point next = {start.x - 1, start.y}; push(stack, next); visit[next.x][next.y] = 1; dfs(maze, visit, next, end, stack); pop(stack); } // 下 if (start.x < MAX_SIZE - 1 && maze[start.x + 1][start.y] == 0 && visit[start.x + 1][start.y] == 0) { Point next = {start.x + 1, start.y}; push(stack, next); visit[next.x][next.y] = 1; dfs(maze, visit, next, end, stack); pop(stack); } // 左 if (start.y > 0 && maze[start.x][start.y - 1] == 0 && visit[start.x][start.y - 1] == 0) { Point next = {start.x, start.y - 1}; push(stack, next); visit[next.x][next.y] = 1; dfs(maze, visit, next, end, stack); pop(stack); } // 右 if (start.y < MAX_SIZE - 1 && maze[start.x][start.y + 1] == 0 && visit[start.x][start.y + 1] == 0) { Point next = {start.x, start.y + 1}; push(stack, next); visit[next.x][next.y] = 1; dfs(maze, visit, next, end, stack); pop(stack); } ... } ``` 7. 在主函数中调用dfs函数,并传入起点、终点、迷宫数组和访问数组。 ```c int main() { int maze[MAX_SIZE][MAX_SIZE] = {...}; int visit[MAX_SIZE][MAX_SIZE] = {0}; Point start = {0, 0}; Point end = {MAX_SIZE - 1, MAX_SIZE - 1}; Stack *stack = createStack(); dfs(maze, visit, start, end, stack); return 0; } ``` 以上就是用深度优先搜索算法实现自动走迷宫C语言代码。希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值