C语言小游戏-走迷宫 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 的最小迷宫:
TEXT
█ █ █ █ █
█   █   █
█ █ █ █ █
█   █   █
█ █ █ █ █
道路格子坐标都是奇数,墙壁格子坐标都是偶数(或反过来)。 使用奇数尺寸可以保证四周全是墙壁,且每个道路格子周围都是墙壁。 2. 算法核心思想(递归回溯 / “挖洞法”) 将整个迷宫初始化为全墙。 从起点 (1,1) 出发(该点必须是奇数坐标,保证是道路位置)。 把当前格子标记为道路。 随机打乱四个方向(上、下、左、右)的顺序,避免每次生成相同的迷宫。 对每个方向: 如果沿该方向前进两格后的格子仍在迷宫内且仍然是墙(未访问过),则: 将中间那格(即前进一格处的墙)也打通。 递归调用 carvePassages() 处理新格子。 当所有方向都无法继续时,回溯到上一层,继续探索其它方向。 为什么每次移动两格? 因为迷宫要求道路之间必须被一堵墙隔开。移动两格可以保证:当前格子 → 中间墙 → 下一个格子。打通中间墙后,两个道路格子就连通了。 这样最终生成的迷宫没有环路,且所有道路格子都是连通的。 3. 关键代码片段解读 (1) 随机打乱方向顺序
C
int order[4] = {0, 1, 2, 3};
shuffle(order, 4);
如果不打乱,每次生成的迷宫都会偏向于先探索“上”方向,导致迷宫形态固定。打乱后每次运行结果都不同。 (2) 打通墙壁
C
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) 递归条件
C
if (nx>0 && nx<HEIGHT-1 && ny>0 && ny<WIDTH-1 && maze[nx][ny]==1)
边界检查:(nx, ny) 不能在最外圈(最外圈全部是墙,保留作为迷宫边界)。
C
maze[nx][ny]==1 表示该格子尚未被访问(仍是墙)。
正是这个条件保证每个格子只被打通一次,从而避免形成环路。 4. 生成结果的特点 连通性:从起点可以到达任意一个道路格子(包括终点)。 无环:任意两个道路格子之间只有唯一路径。 完美迷宫:没有不可达的死角(除了故意保留的边界墙)。 5. 辅助理解:手动模拟小迷宫(5x5) 初始全墙(1):
TEXT
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) 等。 最终可能生成:
TEXT
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 算法(生成的迷宫更“树状”),对比两种风格。 增加计时功能:记录玩家完成时间。 保存最佳步数:使用文件存储历史最少步数。 📌 注意事项
C
// 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)

登录后可以发表评论

立即登录
💬

还没有评论,快来发表第一条评论吧!