C# 迷宫生成:基于 Prim 算法的自己实现存在问题

3

首先,让我为篇幅道歉,我会尽量保持简短。

在尝试按照维基百科上的指示构建普里姆算法后,我发现它无法适应我的迷宫构建方式。因此,我尝试了同样的思路来适应我的迷宫,但是我遇到了一个奇怪的错误。

当游戏开始时,迷宫构建不正确,我无法找出原因。以下是偶尔发生的情况:

Maze Error picture

有时它可以完美地工作,所以我有一个 public Dictionary<int, Dictionary<int, MazeCellState>> maze,它保存了迷宫。当迷宫开始时,所有的墙都已经搭好了,然后我按以下方式构建路径。

private static void buildPath()
       {
           List<KeyValuePair<Misc.Cord, Misc.Cord>> ends = new List<KeyValuePair<Misc.Cord, Misc.Cord>>();
           ends.Add(new KeyValuePair<Misc.Cord, Misc.Cord>(new Misc.Cord() { X = 0, Y = 0 }, new Misc.Cord() { X = 0, Y = 0 }));
           Misc.Cord currentPos = null;

           while (ends.Count > 0)
           {
               int posKey = rand.Next(0, ends.Count);
               Misc.Cord lastPos = ends[posKey].Key;
               currentPos = ends[posKey].Value;
               maze[currentPos.X][currentPos.Y] = MazeCellState.Path;
               int currentCount = 0;

               MovingState moveTo1 = (MovingState)rand.Next(0, 4);
               MovingState moveTo2 = (MovingState)rand.Next(0, 4);
               while (moveTo1.Equals(moveTo2))
               {
                   moveTo1 = (MovingState)rand.Next(0, 4);
                   moveTo2 = (MovingState)rand.Next(0, 4);
               }

               // check left
               if (currentPos.X - 2 > 0 && maze[currentPos.X - 2][currentPos.Y] != MazeCellState.Path && currentCount < 2 && (moveTo1 == MovingState.Left || moveTo2 == MovingState.Left))
               {
                   if(!lastPos.Equals(new Misc.Cord() { X = currentPos.X - 2, Y = currentPos.Y }))
                   {
                       ends.Add(new KeyValuePair<Misc.Cord, Misc.Cord>(currentPos, new Misc.Cord() { X = currentPos.X - 2, Y = currentPos.Y }));
                       maze[currentPos.X - 1][currentPos.Y] = MazeCellState.Path;
                       currentCount++;
                   }
               }

               // check right
               if (currentPos.X + 2 < maze.Count && maze[currentPos.X + 2][currentPos.Y] != MazeCellState.Path && currentCount < 2 && (moveTo1 == MovingState.Right || moveTo2 == MovingState.Right))
               {
                   if (!lastPos.Equals(new Misc.Cord() { X = currentPos.X + 2, Y = currentPos.Y }))
                   {
                       ends.Add(new KeyValuePair<Misc.Cord, Misc.Cord>(currentPos, new Misc.Cord() { X = currentPos.X + 2, Y = currentPos.Y }));
                       maze[currentPos.X + 1][currentPos.Y] = MazeCellState.Path;
                       currentCount++;
                   }
               }

               // check Up
               if (currentPos.Y - 2 > 0 && maze[currentPos.X][currentPos.Y - 2] != MazeCellState.Path && currentCount < 2 && (moveTo1 == MovingState.Up || moveTo2 == MovingState.Up))
               {
                   if(!lastPos.Equals(new Misc.Cord() { X = currentPos.X, Y = currentPos.Y - 2}))
                   {
                       ends.Add(new KeyValuePair<Misc.Cord, Misc.Cord>(currentPos, new Misc.Cord() { X = currentPos.X, Y = currentPos.Y - 2 }));
                       maze[currentPos.X][currentPos.Y - 1] = MazeCellState.Path;
                       currentCount++;
                   }
               }

               // check Down
               if (currentPos.Y + 2 < maze[0].Count && maze[currentPos.X][currentPos.Y + 2] != MazeCellState.Path && currentCount < 2 && (moveTo1 == MovingState.Down || moveTo2 == MovingState.Down))
               {
                   if(!lastPos.Equals(new Misc.Cord() { X = currentPos.X, Y = currentPos.Y + 2}))
                   {
                       ends.Add(new KeyValuePair<Misc.Cord, Misc.Cord>(currentPos, new Misc.Cord() { X = currentPos.X, Y = currentPos.Y + 2 }));
                       maze[currentPos.X][currentPos.Y + 1] = MazeCellState.Path;
                       currentCount++;
                   }
               }
                ends.RemoveAt(posKey);
                ends = reorderList(ends);
           }

           maze[0][1] = MazeCellState.Path;
       }

我不确定为什么有时会出现上面的图片,我的理论是它会反向作用于自身。
一些快速笔记,MazeCellState此时只能是路径或栅栏两个选项,并且reorderList将重新索引任何类型的列表,迷宫大小是根据屏幕分辨率计算的,每个单元格为64x64像素。
            GraphicsDevice.Viewport.Width * 5 / 64,
            GraphicsDevice.Viewport.Height * 5 / 64
1个回答

2
那是一种在网格场中实现算法的艰难方式。网格的Prim算法可以更容易地表达。不用研究你的代码哪里出了错,我会告诉你简单的方法。
创建网格,并使用连续数字从零开始为每个单元格编号。每个单元格有两堵边界墙可打破;上和左,或下和右,或其他组合,只要选择(左/右)和(上/下)之一即可。
现在选择任何一个单元格,并选择其中一面墙。如果那堵墙另一侧的单元格有不同的编号(一个高一个低),打破那堵墙,然后在整个迷宫中重新编号所有高编号为低编号的出现次数。如果您选择的单元格和墙壁已经在另一侧具有相同的编号,则不要尝试另一面墙壁,而是按顺序移动到下一个单元格,重复每行和每列(可能几乎循环到最后)直到找到可以打破的墙壁的单元格。
如果您有N个单元格,则必须重复此打破墙壁的练习N-1次,直到最后一次所有单元格都被编号为零(因为每次打破时,您都会从领域中删除更高的编号),并且您有一个完整的迷宫。
如果您想要路径更多地从左到右而不是从上到下的迷宫,则在该方向上偏向于随机选择要打破的墙壁。这也适用于三维迷宫,其中您可能不希望有许多梯子;只需不要选择打破太多天花板/地板即可。
在我描述了这个算法之后,我的14岁儿子在3D Turbo-Pascal中实现了它,所以我知道该算法和此描述实际有效。这实际上是Prim算法的一个版本,除了所有弧的成本相同(或所有左-右弧和所有上-下弧都是如此等)。它的好处在于编号方式可以确定哪些单元格已经可以从其他单元格到达。

抱歉,但是你的第三段让我有些困惑,可能是因为我的阅读障碍。你所说的数字是什么意思?一个高一个低,但你并没有说明从0开始设置数字的方法。在一个包含y数组的x数组中有一个墙壁,其中包含0,但你并没有说明如何改变数字,那么你的数字系统中的墙壁是什么?根据你的解释,整个顶部行都将成为路径,这使得迷宫变得太容易了。请在此处观看视频:http://en.wikipedia.org/wiki/Prim's_algorithm - Barkermn01
另外,我刚刚加入了一个黑客程序来扫描迷宫,如果少于5条路径存在,则重新构建迷宫。这是一年前的事情了,我简直不敢相信你会选择这个,但很高兴知道Pascal以某种形式被使用,我甚至认为你再也找不到Turbo编译器了,因为我认为它已经随着Windows XP的消失而消亡了。最后,这是一个代码帮助网站,你所提供的内容与许多网站提供的内容相同,即使引用一些代码也可以展示你的意思,但在TP中构建与多线程C#应用程序有很大的不同。 - Barkermn01
正如我在第二段中所说,“将所有单元格按连续数字从零开始编号”。这些数字是您在第三段中使用的。 - cliffordheath
如果你决定打破底部和右侧的墙,那么显然最右侧的列不能打破右侧的墙,底行也不能打破底部的墙,但除此之外,每个单元格都可以打破一面或两面墙。每次只能打破一堵墙,而且只有在该墙两侧的数字不同时才能打破。然后随机选择另一堵墙来打破。在选择时,如果你选择了一堵不能打破的墙,那么只有在横向和纵向搜索以寻找可以打破的墙时才可以。如果不这样搜索,完成时间会太长。 - cliffordheath
那个维基百科视频展示了一个糟糕的迷宫。如果你总是从起点开始构建,你会得到一个辐射式的迷宫,它并不是真正随机分布的。对于最小路径问题很好,但对于迷宫来说很糟糕。在注意到我所有迷宫中都有辐射模式之前,我也犯了这个错误。 - cliffordheath
很遗憾,我们不再拥有1999年编写的TP代码。我的儿子现在已经28岁了 :) 不过我有一个1997年的旧版C++,如果你认为它有用的话。 - cliffordheath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接