前两篇我们一直在"猜"和"算",这一篇开始有真正的空间感。文本迷宫把地图存进二维数组,玩家用 WASD 控制 @ 穿行,走到出口 E 即通关。你将学到 C 语言最实用的数据结构——二维数组,以及"位置 + 碰撞检测"这套游戏开发的核心思想。

玩法介绍

终端打印由 #(墙)和 .(路)组成的地图,角色 @S 出发,按 W/A/S/D 上下左右移动,撞墙会收到提示,走到 E 通关。地图就是一段 10 行 × 20 列的字符数组,你可以亲手修改它,设计自己的迷宫。

第 1 步:用二维数组定义地图

把地图写成字符串数组:maze[ROWS][COLS + 1]+1 留给字符串结尾 \0。玩家坐标 px(列)、py(行)从 S 所在格出发。

#include <stdio.h>

#define ROWS 10
#define COLS 20

/* 地图: # 墙, . 路, S 起点, E 终点 */
char maze[ROWS][COLS + 1] = {
    "####################",
    "#S........#........#",
    "#.###.####.#.####..#",
    "#.#......#.#.#.....#",
    "#.#.####.#.#.#.###.#",
    "#.#.#....#...#...#.#",
    "#.#.#.######.###.#.#",
    "#...#...........####",
    "#####.#####.########",
    "#.....#..........E.#"
};

int px = 1, py = 1;   /* 玩家位置(列, 行) */

第 2 步:打印地图与玩家位置

双层 for 循环逐行逐列输出,轮到玩家所在格就打印 @ 而不是地图字符。这就是"渲染"——把游戏状态画到屏幕上。

void print_maze(void) {
    int x, y;
    for (y = 0; y < ROWS; y++) {
        for (x = 0; x < COLS; x++) {
            if (x == px && y == py)
                putchar('@');          /* 玩家所在位置 */
            else
                putchar(maze[y][x]);
        }
        putchar('\n');
    }
}

第 3 步:读取按键输入

getchar() 读一个字符,switch 把它翻译成坐标变化。注意输入后的回车会残留在缓冲区,要用 while (getchar() != '\n') 吃掉,否则下一轮会读到"幽灵回车"。

cmd = getchar();
while (getchar() != '\n');   /* 吃掉输入后的回车 */

switch (cmd) {
case 'w': case 'W': ny--; break;   /* 上: 行号减 1 */
case 's': case 'S': ny++; break;   /* 下: 行号加 1 */
case 'a': case 'A': nx--; break;   /* 左: 列号减 1 */
case 'd': case 'D': nx++; break;   /* 右: 列号加 1 */
case 'q': case 'Q': return 0;
default: continue;
}

第 4 步:移动与撞墙检测

先算出目标位置 (nx, ny),判断:目标格是 # 就不移动,只提示;否则才更新坐标。"先检测、后行动"是所有游戏碰撞处理的通用套路。

if (maze[ny][nx] == '#') {
    printf("前面是墙, 走不了!\n");
    continue;          /* 本次输入作废, 重新出方向 */
}

px = nx;
py = ny;

第 5 步:通关判定与完整代码

每次移动成功后检查当前位置:是 E 就打印地图、祝贺通关并结束循环。组装进 while (1) 主循环,就是完整的迷宫游戏:

#include <stdio.h>

#define ROWS 10
#define COLS 20

char maze[ROWS][COLS + 1] = {
    "####################",
    "#S........#........#",
    "#.###.####.#.####..#",
    "#.#......#.#.#.....#",
    "#.#.####.#.#.#.###.#",
    "#.#.#....#...#...#.#",
    "#.#.#.######.###.#.#",
    "#...#...........####",
    "#####.#####.########",
    "#.....#..........E.#"
};

int px = 1, py = 1;

void print_maze(void) {
    int x, y;
    for (y = 0; y < ROWS; y++) {
        for (x = 0; x < COLS; x++) {
            if (x == px && y == py)
                putchar('@');
            else
                putchar(maze[y][x]);
        }
        putchar('\n');
    }
}

int main(void) {
    char cmd;

    printf("文本迷宫: 用 W/A/S/D 移动, 走到 E 即可通关!\n\n");

    while (1) {
        int nx = px, ny = py;
        print_maze();

        printf("方向 (W上 A左 S下 D右, Q退出): ");
        cmd = getchar();
        while (getchar() != '\n');

        switch (cmd) {
        case 'w': case 'W': ny--; break;
        case 's': case 'S': ny++; break;
        case 'a': case 'A': nx--; break;
        case 'd': case 'D': nx++; break;
        case 'q': case 'Q': return 0;
        default: continue;
        }

        if (maze[ny][nx] == '#') {   /* 撞墙检测 */
            printf("前面是墙, 走不了!\n");
            continue;
        }

        px = nx;
        py = ny;

        if (maze[py][px] == 'E') {   /* 到达终点 */
            print_maze();
            printf("恭喜通关! 你成功走出了迷宫!\n");
            break;
        }
    }
    return 0;
}

编译运行:

gcc -o maze maze.c
./maze
💡 改进方向:用"递归回溯"算法随机生成迷宫,每次开局都不同;加入宝箱和陷阱;统计并显示步数;把地图存进外部 .txt 文件用 fscanf 读入;再进阶一点,加入一只会追人的怪物。