迷宫生成算法

现在的迷宫生成主要有三种算法:DFS算法,随机Prim算法,Kruskal算法

  • DFS算法:即深度优先搜索算法,这种方法生成的迷宫具有较为明显的通路
  • Prim算法:这种算法生成的迷宫岔路较多,整体看上去也较为自然
  • Kruskal算法:这种算法同样不会产生明显的主路,岔路也较多

本篇主要采用Prim算法生成完美迷宫


完美迷宫

完美迷宫是指符合以下条件的迷宫:

  • 迷宫中任意两点之间存在通路

  • 任意两点见的通路是唯一的

  • 迷宫中不存在回路


Prim完美迷宫生成算法

以下讲述中使用的代码大部分是伪码

主要步骤

  • 设置一个二维数组,用于记录地图信息

    • 地图尺寸为 奇数×奇数
    • 地图最外侧一周是
    • 将坐标为(奇数,奇数)的点先看做路点(坐标从0开始
    • 这里用0表示路,用1表示墙
    1
    2
    3
    #define Width 11
    #define Height 11
    int roadOrWall[Width][Height] = 0;
  • 初始化地图信息

    • 将所有地图块标记为墙,即数组所有元素的值都为1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    for (int i = 0; i < Width; i++)
    {
    for (int j = 0; j < Height; j++)
    {
    roadOrWall[i][j] = 1;
    }
    }

    memset(roadOrWall,1,sizeof(roadOrWall));

    将路点用蓝色表示,墙用橙色表示,路用白色表示

    会得到下面的“迷宫”(11×11)

    image-20211128105829668
  • 现在在图的边缘部分选择一个路点,如图所示,选择(1,1)点,将其变成路

    1
    roadOrWall[1][1]=0;
    image-20211128110249416
  • 再将其周围的两个路点,加入待选路点列表中

    • 待选路点用绿色表示,得到以下图像
    • 待选路点在地图信息中用2表示
    1
    2
    roadOrWall[1][3]=2;
    roadOrWall[3][1]=2;
    image-20211128110314372
  • 创建路线备选池

    • 之后要变成的地图块要从路线备选池中随机选择
  • 初始工作准备完毕,现在进入循环

    • 循环条件
    1
    while(待选路点的个数>0)
    • 待选路点列表中随机选取一个路点A

      • 如选中了路点A(1,3)
    • 清空路线备选池

      • 进入路线备选池的路点将会形成迷宫的路线
    • 将A周围的四个路点中的已经变成路的路点加入路线备选池

    • 路线备选池中再随机选出一个路点B

      • 如选中了B(1,1)
    • 将AB两个路点打通,即路点A,路点B及其中间的部分全部变成路,得到下图迷宫

      1
      2
      3
      4
      //将A变成路
      roadOrWall[A->y][A->x] = 0;
      //将AB中间的方块变成路
      roadOrWall[(A->y + B->y) / 2][(A->x + B->x) / 2] = 0;
      image-20211128111224766
    • 路点A周围还不是路的路点加入待选路点列表中(不可重复添加),如下图

      image-20211128111953464
    • 待选路点列表中删除路点A

    • 重复进行以上操作,直到待选路点列表为空为止


循环步骤

以下是第二到第十步循环,太长不想看可以跳到最终结果

  • 进行第二次循环:A(3,3) B(1,3)

    image-20211128112301289
  • 进行第三次循环: A(5,3) B(3,3)

    image-20211128112421988
  • 进行第四次循环: A(1,3) B(1,1)

    image-20211128112659986
  • 进行第五次循环: A(3,5) B(3,3)

    image-20211128112921928
  • 进行第六次循环: A(7,3) B(5,3)

    image-20211128113025254
  • 进行第七次循环: A(5,1) B(3,1)

    image-20211128113133164
  • 进行第八次循环: A(5,5) B(5,3)

    image-20211128113235303
  • 进行第九次循环: A(3,7) B(3,5)

    image-20211128113451552
  • 进行第十次循环: A(5,7) B(3,7)

    image-20211128113617896

最终结果

  • 剩下的不一个一个写了,太累了,这里直接放最终结果

    image-20211128114006691

这个迷宫尺寸不大,看起来似乎不是很像迷宫,下面是由我的数据结构课设的程序生成的大小为41×63的迷宫

image-20211128114359176



在实际Cpp实现中的一些问题

众所周知,在cpp中,如果要实现随机数,就需要用到rand()函数

但是如果要实现真随机数,则需要srand()函数,在函数中传入种子即可

1
2
srand(114514);
int range = rand() 待选路点的数量 + 1 ;

但是在实际测试中,有一些种子产生的随机数会导致链表结点无法添加的问题

明明已经跑过了添加节点的代码,但是通过遍历得到的节点数量仍然不变

经过一上午几个小时的Debug,没有检查到问题所在


于是我便添加了种子检测机制,即:

  • 在使用种子生成随机数用来绘制地图之前,首先测试该种子生成的随机数能否完整地添加所有待选路点链表的节点
  • 如果不能,就进行种子的偏移,会在输入种子的基础上进行微小的偏移,如果仍然不行,则继续偏移,直到种子正常位置,也叫作检测种子的合理性


问题总结

目前待选路点的列表是由手写链表产生(经检查,代码在逻辑上没有问题)会产生这样的问题,除了使用种子检测机制避免使用不合理的种子进行绘图以外没有找到解决办法。使用其他数据结构如vector会不会产生一样的问题我还没有进行测试,等后续测试一波…