维基百科文章中的描述确实需要改进。
文章的第一个令人困惑的部分是,随机 Prim 算法的描述没有详细说明算法所使用的假设数据结构。因此,“相反的单元格”等短语变得令人困惑。
基本上,“迷宫生成程序员”可以选择两种主要方法:
- 单元格有四个相邻的墙或通道。关于墙/通道的信息被存储和操作。
- 单元格可以是阻塞(墙)或通道,而不存储任何额外的连通性信息。
根据读者在阅读算法描述时心中的模型(1)或(2),他们要么理解要么不理解。
就我个人而言,我更喜欢将单元格用作墙或通道,而不是操纵专门的通道/墙信息。
然后,“边缘”块离通道的距离为2(而不是1)。从边缘块列表中随机选择一个边缘块,并通过使边缘块和相邻通道之间的单元格也变成通道来将其连接到随机相邻通道(距离为2)。
这是我的 F# 实现:
let rng = new System.Random()
type Cell = | Blocked | Passage
type Maze =
{
Grid : Cell[,]
Width : int
Height : int
}
let initMaze dx dy =
let six,siy = (1,1)
let eix,eiy = (dx-2,dy-2)
{
Grid = Array2D.init dx dy
(fun _ _ -> Blocked
)
Width = dx
Height = dy
}
let generate (maze : Maze) : Maze =
let isLegal (x,y) =
x>0 && x < maze.Width-1 && y>0 && y<maze.Height-1
let frontier (x,y) =
[x-2,y;x+2,y; x,y-2; x, y+2]
|> List.filter (fun (x,y) -> isLegal (x,y) && maze.Grid.[x,y] = Blocked)
let neighbor (x,y) =
[x-2,y;x+2,y; x,y-2; x, y+2]
|> List.filter (fun (x,y) -> isLegal (x,y) && maze.Grid.[x,y] = Passage)
let randomCell () = rng.Next(maze.Width),rng.Next(maze.Height)
let removeAt index (lst : (int * int) list) : (int * int) list =
let x,y = lst.[index]
lst |> List.filter (fun (a,b) -> not (a = x && b = y) )
let between p1 p2 =
let x =
match (fst p2 - fst p1) with
| 0 -> fst p1
| 2 -> 1 + fst p1
| -2 -> -1 + fst p1
| _ -> failwith "Invalid arguments for between()"
let y =
match (snd p2 - snd p1) with
| 0 -> snd p1
| 2 -> 1 + snd p1
| -2 -> -1 + snd p1
| _ -> failwith "Invalid arguments for between()"
(x,y)
let connectRandomNeighbor (x,y) =
let neighbors = neighbor (x,y)
let pickedIndex = rng.Next(neighbors.Length)
let xn,yn = neighbors.[pickedIndex]
let xb,yb = between (x,y) (xn,yn)
maze.Grid.[xb,yb] <- Passage
()
let rec extend front =
match front with
| [] -> ()
| _ ->
let pickedIndex = rng.Next(front.Length)
let xf,yf = front.[pickedIndex]
maze.Grid.[xf,yf] <- Passage
connectRandomNeighbor (xf,yf)
extend ((front |> removeAt pickedIndex) @ frontier (xf,yf))
let x,y = randomCell()
maze.Grid.[x,y] <- Passage
extend (frontier (x,y))
maze
let show maze =
printfn "%A" maze
maze.Grid |> Array2D.iteri
(fun y x cell ->
if x = 0 && y > 0 then
printfn "|"
let c =
match cell with
| Blocked -> "X"
| Passage -> " "
printf "%s" c
)
maze
let render maze =
let cellWidth = 10;
let cellHeight = 10;
let pw = maze.Width * cellWidth
let ph = maze.Height * cellHeight
let passageBrush = System.Drawing.Brushes.White
let wallBrush = System.Drawing.Brushes.Black
let bmp = new System.Drawing.Bitmap(pw,ph)
let g = System.Drawing.Graphics.FromImage(bmp);
maze.Grid
|> Array2D.iteri
(fun y x cell ->
let brush =
match cell with
| Passage -> passageBrush
| Blocked -> wallBrush
g.FillRectangle(brush,x*cellWidth,y*cellHeight,cellWidth,cellHeight)
)
g.Flush()
bmp.Save("""E:\temp\maze.bmp""")
initMaze 50 50 |> generate |> show |> render
生成的迷宫可能如下所示:
![enter image description here](https://istack.dev59.com/D5QgT.webp)
以下是我尝试用维基百科“算法”风格描述我的解决方案的方法:
- 网格由二维单元格数组组成。
- 单元格有两种状态:阻塞或通道。
- 从一个充满阻塞单元格的网格开始。
- 选择一个随机单元格,将其设置为通道状态并计算其前沿单元格。
前沿单元格是距离2个阻塞单元格以内且在网格内的单元格。
- 当前沿单元格列表不为空时:
- 从前沿单元格列表中随机选择一个前沿单元格。
- 让neighbors(frontierCell) = 距离2个通道单元格的所有单元格。
随机选择一个邻居,并通过将中间的单元格设置为通道状态将前沿单元格与邻居相连。
计算所选前沿单元格的前沿单元格并将它们添加到前沿列表中。
从前沿单元格列表中删除所选前沿单元格。