C语言小游戏-走迷宫 C语言
// maze.c
#include <stdio.h>
#include <stdlib.h>
#include <conio.h> // Windows _getch(),Linux可替换为getchar()
#define WIDTH 15
#define HEIGHT 15
// 预定义迷宫:0=路,1=墙,2=起点,3=终点
int mazeMap[HEIGHT][WIDTH] = {
{2,0,0,0,1,0,0,0,1,0,0,0,0,0,1},
{1,1,1,0,1,0,1,0,1,0,1,1,1,0,1},
{0,0,0,0,0,0,1,0,0,0,1,0,0,0,1},
{0,1,1,1,1,0,1,1,1,0,1,0,1,0,1},
{0,1,0,0,0,0,0,0,0,0,0,0,1,0,0},
{0,1,0,1,1,1,1,1,0,1,1,0,1,0,1},
{0,0,0,1,0,0,0,1,0,0,0,0,1,0,1},
{1,1,0,1,0,1,0,1,1,1,0,1,1,0,1},
{0,0,0,1,0,1,0,0,0,0,0,1,0,0,0},
{0,1,0,1,1,1,1,1,0,1,0,1,1,0,1},
{0,1,0,0,0,0,0,1,0,0,0,0,0,0,1},
{0,1,1,1,1,0,1,1,1,0,1,0,1,0,1},
{0,0,0,0,1,0,0,0,0,0,1,0,0,0,1},
{1,1,1,0,1,0,1,0,1,0,1,0,1,0,1},
{0,0,0,0,1,0,0,0,0,0,0,0,0,0,3}
};
int playerX, playerY; // 玩家当前坐标
int steps; // 移动步数
int startX, startY; // 起点坐标(用于重置)
int goalX, goalY; // 终点坐标
// 初始化/重置游戏状态(从地图中提取起点、终点)
void initGame() {
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
if (mazeMap[i][j] == 2) {
startX = i;
startY = j;
} else if (mazeMap[i][j] == 3) {
goalX = i;
goalY = j;
}
}
}
playerX = startX;
playerY = startY;
steps = 0;
}
// 重置到起点
void resetGame() {
playerX = startX;
playerY = startY;
steps = 0;
}
// 绘制迷宫界面
void drawMaze() {
system("cls"); // 清屏,Windows使用;Linux可改为 system("clear")
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
if (i == playerX && j == playerY) {
printf("P "); // 玩家
} else if (mazeMap[i][j] == 1) {
printf("█ "); // 墙壁
} else if (mazeMap[i][j] == 3) {
printf("G "); // 终点
} else {
printf(" "); // 道路(两个空格)
}
}
printf("\n");
}
printf("\n步数: %d [WASD移动] [R重置] [Q退出]\n", steps);
}
// 尝试移动
int move(int dx, int dy) {
int newX = playerX + dx;
int newY = playerY + dy;
// 边界检查
if (newX < 0 || newX >= HEIGHT || newY < 0 || newY >= WIDTH)
return 0;
// 墙壁检查(值为1)
if (mazeMap[newX][newY] == 1)
return 0;
// 执行移动
playerX = newX;
playerY = newY;
steps++;
return 1;
}
int main() {
initGame();
char ch;
int win = 0;
while (!win) {
drawMaze();
// 胜利判定(玩家站在终点格子上)
if (playerX == goalX && playerY == goalY) {
drawMaze(); // 最后一次显示胜利后的地图(玩家站在G上)
printf("\n🎉 恭喜!你在 %d 步内走出了迷宫!🎉\n", steps);
printf("按任意键退出...\n");
getch();
win = 1;
break;
}
ch = getch(); // 获取按键
switch (ch) {
case 'w': case 'W': move(-1, 0); break;
case 's': case 'S': move( 1, 0); break;
case 'a': case 'A': move( 0,-1); break;
case 'd': case 'D': move( 0, 1); break;
case 'r': case 'R': resetGame(); break;
case 'q': case 'Q': printf("\n游戏退出\n"); return 0;
default: break;
}
}
return 0;
}
重点讲解:DFS 递归回溯迷宫生成算法
1. 迷宫的数据结构
使用二维数组 maze[HEIGHT][WIDTH] 存储,每个元素取值:
1:墙壁(不可走)
0:道路(可走)
2:起点(实际上也是道路,仅用于标记)
3:终点(实际上也是道路)
为什么尺寸必须是奇数?
因为迷宫需要“墙壁 — 道路 — 墙壁 — 道路 …” 交替排列。
例如 5x5 的最小迷宫:
█ █ █ █ █
█ █ █
█ █ █ █ █
█ █ █
█ █ █ █ █
道路格子坐标都是奇数,墙壁格子坐标都是偶数(或反过来)。
使用奇数尺寸可以保证四周全是墙壁,且每个道路格子周围都是墙壁。
2. 算法核心思想(递归回溯 / “挖洞法”)
将整个迷宫初始化为全墙。
从起点 (1,1) 出发(该点必须是奇数坐标,保证是道路位置)。
把当前格子标记为道路。
随机打乱四个方向(上、下、左、右)的顺序,避免每次生成相同的迷宫。
对每个方向:
如果沿该方向前进两格后的格子仍在迷宫内且仍然是墙(未访问过),则:
将中间那格(即前进一格处的墙)也打通。
递归调用 carvePassages() 处理新格子。
当所有方向都无法继续时,回溯到上一层,继续探索其它方向。
为什么每次移动两格?
因为迷宫要求道路之间必须被一堵墙隔开。移动两格可以保证:当前格子 → 中间墙 → 下一个格子。打通中间墙后,两个道路格子就连通了。
这样最终生成的迷宫没有环路,且所有道路格子都是连通的。
3. 关键代码片段解读
(1) 随机打乱方向顺序
int order[4] = {0, 1, 2, 3};
shuffle(order, 4);
如果不打乱,每次生成的迷宫都会偏向于先探索“上”方向,导致迷宫形态固定。打乱后每次运行结果都不同。
(2) 打通墙壁
maze[x + dx/2][y + dy/2] = 0;
例如当前 (x,y) = (1,1),选择向下方向 (dx,dy) = (2,0),则目标格子 (3,1)。
中间格子 (1+1, 1+0) = (2,1) 原本是墙,将其设为道路。
(3) 递归条件
if (nx>0 && nx<HEIGHT-1 && ny>0 && ny<WIDTH-1 && maze[nx][ny]==1)
边界检查:(nx, ny) 不能在最外圈(最外圈全部是墙,保留作为迷宫边界)。
maze[nx][ny]==1 表示该格子尚未被访问(仍是墙)。
正是这个条件保证每个格子只被打通一次,从而避免形成环路。
4. 生成结果的特点
连通性:从起点可以到达任意一个道路格子(包括终点)。
无环:任意两个道路格子之间只有唯一路径。
完美迷宫:没有不可达的死角(除了故意保留的边界墙)。
5. 辅助理解:手动模拟小迷宫(5x5)
初始全墙(1):
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
从 (1,1) 开始,设为道路(0)。
随机选择方向,比如“下”:
目标 (3,1) 是墙,打通中间 (2,1),然后递归进入 (3,1)。
在 (3,1) 随机方向,可能打通 (3,3) 等。
最终可能生成:
1 1 1 1 1
1 0 1 0 1
1 0 1 0 1
1 0 0 0 1
1 1 1 1 1
(注:实际算法会在奇数坐标打通,这里仅为示意)
🧑🏫 教学建议
课堂演示步骤
先展示固定迷宫游戏(如预先定义的数组),让学生理解基本玩法。
引入随机生成,激发兴趣:“每次运行地图都不同!”
手动推演 DFS 过程:在黑板上画一个 7x7 网格,从 (1,1) 开始,随机选择方向,一步步画出打通的墙壁,展示递归回溯的过程。
代码逐段讲解:
为什么尺寸必须奇数?
shuffle 函数的作用。
递归函数 carvePassages 的边界条件和终止条件。
运行并观察:编译运行程序,按 N 多次生成不同迷宫,让学生体会随机性。
调试技巧(供教学演示)
在 carvePassages 开头添加 printf 输出当前坐标,观察递归顺序。
生成完毕后,用循环打印 maze 数组,对比显示效果。
课后拓展作业
修改尺寸:让学生改变 HEIGHT 和 WIDTH(例如 21、31),观察生成的迷宫形状。
改用栈实现非递归 DFS:避免递归深度过大(虽然此处尺寸小无影响)。
实现其他生成算法:如 Prim 算法(生成的迷宫更“树状”),对比两种风格。
增加计时功能:记录玩家完成时间。
保存最佳步数:使用文件存储历史最少步数。
📌 注意事项
// random_maze.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h> // Windows下使用 _getch()
#define HEIGHT 15 // 必须为奇数,且 >= 5
#define WIDTH 15 // 必须为奇数,且 >= 5
int mazeMap[HEIGHT][WIDTH]; // 0=路,1=墙,2=起点,3=终点
int playerX, playerY; // 玩家当前位置
int steps; // 移动步数
int startX, startY; // 起点坐标
int goalX, goalY; // 终点坐标
// 随机打乱方向顺序(用于DFS生成迷宫)
void shuffle(int *array, int n) {
for (int i = n - 1; i > 0; i--) {
int j = rand() % (i + 1);
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// 深度优先生成迷宫(递归回溯)
void carvePassages(int x, int y) {
mazeMap[x][y] = 0; // 当前格变为路
int dirs[4][2] = {{-2, 0}, {2, 0}, {0, -2}, {0, 2}};
int order[4] = {0, 1, 2, 3};
shuffle(order, 4);
for (int i = 0; i < 4; i++) {
int dx = dirs[order[i]][0];
int dy = dirs[order[i]][1];
int nx = x + dx;
int ny = y + dy;
// 边界检查(保留最外一圈为墙)
if (nx > 0 && nx < HEIGHT - 1 && ny > 0 && ny < WIDTH - 1 && mazeMap[nx][ny] == 1) {
// 打通中间的墙
mazeMap[x + dx / 2][y + dy / 2] = 0;
carvePassages(nx, ny);
}
}
}
// 生成完整迷宫(包括起点和终点标记)
void generateMaze() {
// 1. 初始化全部为墙 (1)
for (int i = 0; i < HEIGHT; i++)
for (int j = 0; j < WIDTH; j++)
mazeMap[i][j] = 1;
// 2. 从 (1,1) 开始生成通路
carvePassages(1, 1);
// 3. 设置起点 (1,1) 和终点 (HEIGHT-2, WIDTH-2)
startX = 1;
startY = 1;
goalX = HEIGHT - 2;
goalY = WIDTH - 2;
mazeMap[startX][startY] = 2; // 起点标记
mazeMap[goalX][goalY] = 3; // 终点标记
}
// 重置游戏状态(回到当前迷宫起点)
void resetGame() {
playerX = startX;
playerY = startY;
steps = 0;
}
// 重新生成新迷宫并重置游戏
void newMaze() {
generateMaze();
resetGame();
}
// 初始化(首次生成迷宫)
void initGame() {
srand((unsigned)time(NULL));
newMaze(); // 生成随机迷宫并重置
}
// 绘制界面
void drawMaze() {
system("cls"); // Windows清屏,Linux改为 system("clear")
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
if (i == playerX && j == playerY) {
printf("P "); // 玩家
} else if (mazeMap[i][j] == 1) {
printf("█ "); // 墙壁
} else if (mazeMap[i][j] == 3) {
printf("G "); // 终点
} else {
printf(" "); // 道路(起点也是道路,显示为空格)
}
}
printf("\n");
}
printf("\n步数: %d\n", steps);
printf("[WASD移动] [R重置] [N新迷宫] [Q退出]\n");
}
// 尝试移动
int move(int dx, int dy) {
int newX = playerX + dx;
int newY = playerY + dy;
// 边界检查
if (newX < 0 || newX >= HEIGHT || newY < 0 || newY >= WIDTH)
return 0;
// 墙壁检查
if (mazeMap[newX][newY] == 1)
return 0;
// 执行移动
playerX = newX;
playerY = newY;
steps++;
return 1;
}
int main() {
initGame();
char ch;
int win = 0;
while (!win) {
drawMaze();
// 胜利判定(站在终点格子上)
if (playerX == goalX && playerY == goalY) {
drawMaze();
printf("\n🎉 恭喜!你在 %d 步内走出了迷宫!🎉\n", steps);
printf("按任意键退出...\n");
getch();
win = 1;
break;
}
ch = getch();
switch (ch) {
case 'w': case 'W': move(-1, 0); break;
case 's': case 'S': move( 1, 0); break;
case 'a': case 'A': move( 0,-1); break;
case 'd': case 'D': move( 0, 1); break;
case 'r': case 'R': resetGame(); break;
case 'n': case 'N': newMaze(); break;
case 'q': case 'Q': printf("\n游戏退出\n"); return 0;
default: break;
}
}
return 0;
}
评论 (0)
登录后可以发表评论
立即登录还没有评论,快来发表第一条评论吧!