Problems with maze generator ported from JS to Lua?
A while back, I made a maze generator in JavaScript using a popular algorithm. It never fails to produce a solvable maze. I thought it would be interesting if I could port it to Lua, so I started working. This is what I came up with:
01 | local function genMaze() |
03 | local tilesGoal = width * height |
05 | pos = { math.random( 0 , width - 1 ), math.random( 0 , height - 1 ) } , |
06 | open = { false , false , false , false } |
09 | maze [ stack [ 1 ] .pos [ 2 ] * width + stack [ 1 ] .pos [ 1 ] ] = stack [ 1 ] |
10 | while tilesDone < tilesGoal do |
11 | local currentNode = stack [ #stack ] |
12 | local path = randPath(currentNode); |
15 | table.remove(stack, #stack) |
16 | currentNode = stack [ #stack ] |
17 | path = randPath(currentNode) |
19 | currentNode.open [ path ] = true |
21 | local pos, open = nil , { false , false , false , false } |
22 | open [ (path + 2 ) % 4 ] = true |
23 | if tonumber (path) = = 1 then |
24 | pos = { currentNode.pos [ 1 ] , currentNode.pos [ 2 ] - 1 } |
25 | elseif tonumber (path) = = 2 then |
26 | pos = { currentNode.pos [ 1 ] + 1 , currentNode.pos [ 2 ] } |
27 | elseif tonumber (path) = = 3 then |
28 | pos = { currentNode.pos [ 1 ] , currentNode.pos [ 2 ] + 1 } |
29 | elseif tonumber (path) = = 4 then |
30 | pos = { currentNode.pos [ 1 ] - 1 , currentNode.pos [ 2 ] } |
32 | local node = { pos = pos, open = open } |
33 | maze [ pos [ 2 ] * width + pos [ 1 ] ] = node |
34 | table.insert(stack, node) |
35 | tilesDone = tilesDone + 1 |
I rewrote the script line by line. Lua's 1-based indexing was a problem, but I got the mistakes ironed out... or so I thought. This version rarely produces a solvable maze. Isolated cells are made frequently, and large chunks of the map are often cut off. No errors are produced. What am I doing wrong?
Javascript version
Full script:
001 | local width, height = 10 , 10 |
002 | local function mapRange(v, x 1 , y 1 , x 2 , y 2 ) |
003 | return (v - x 1 ) * (y 2 - x 2 ) / (y 1 - x 1 ) + x 2 |
005 | local function line(v 1 , v 2 ) |
006 | local angle = math.atan 2 (v 2. Y - v 1. Y, v 2. X - v 1. X) |
007 | local frame = Instance.new( "Frame" , script.Parent.Puzzle) |
009 | frame.BackgroundColor 3 = Color 3. new( 0 , 0 , 0 ) |
010 | frame.BorderSizePixel = 0 |
011 | frame.Size = UDim 2. new( |
015 | script.Parent.AbsoluteSize.X, |
023 | local center = v 1 :Lerp(v 2 , 0.5 ) |
024 | frame.Position = UDim 2. new( |
028 | script.Parent.AbsoluteSize.X, |
036 | script.Parent.AbsoluteSize.Y, |
042 | frame.Rotation = math.deg(angle) |
043 | frame.AnchorPoint = Vector 2. new( 0.5 , 0.5 ) |
047 | local function tileInDir(node, dirA) |
048 | local dir = tonumber (dirA) |
050 | return (node.pos [ 2 ] = = 0 ) or maze [ width * (node.pos [ 2 ] - 1 ) + node.pos [ 1 ] ] |
052 | return (node.pos [ 1 ] = = width - 1 ) or maze [ width * node.pos [ 2 ] + node.pos [ 1 ] + 1 ] |
054 | return (node.pos [ 2 ] = = height - 1 ) or maze [ width * (node.pos [ 2 ] + 1 ) + node.pos [ 1 ] ] |
056 | return (node.pos [ 1 ] = = 0 ) or maze [ width * node.pos [ 2 ] + node.pos [ 1 ] - 1 ] |
058 | print ( "Failed to find tileInDir with dir " .. dirA) |
062 | local function randPath(node) |
063 | local open, bias = { } , { } |
064 | for i in ipairs (node.open) do |
066 | if not tileInDir(node, (i + 3 ) % 4 + 1 ) then table.insert(bias, (i + 3 ) % 4 + 1 ) end |
067 | if not tileInDir(node, (i + 1 ) % 4 + 1 ) then table.insert(bias, (i + 1 ) % 4 + 1 ) end |
069 | if not (node.open [ i ] or tileInDir(node, i)) then table.insert(open, tonumber (i)) end |
071 | if #open < 1 then return - 1 end |
073 | if #bias > 0 and math.random() > 0.5 then return bias [ math.random( 1 , #bias) ] end |
074 | return open [ math.random( 1 , #open) ] ; |
077 | local function genMaze() |
079 | local tilesGoal = width * height |
081 | pos = { math.random( 0 , width - 1 ), math.random( 0 , height - 1 ) } , |
082 | open = { false , false , false , false } |
085 | maze [ stack [ 1 ] .pos [ 2 ] * width + stack [ 1 ] .pos [ 1 ] ] = stack [ 1 ] |
086 | while tilesDone < tilesGoal do |
087 | local currentNode = stack [ #stack ] |
088 | local path = randPath(currentNode); |
091 | table.remove(stack, #stack) |
092 | currentNode = stack [ #stack ] |
093 | path = randPath(currentNode) |
095 | currentNode.open [ path ] = true |
097 | local pos, open = nil , { false , false , false , false } |
098 | open [ (path + 2 ) % 4 ] = true |
099 | if tonumber (path) = = 1 then |
100 | pos = { currentNode.pos [ 1 ] , currentNode.pos [ 2 ] - 1 } |
101 | elseif tonumber (path) = = 2 then |
102 | pos = { currentNode.pos [ 1 ] + 1 , currentNode.pos [ 2 ] } |
103 | elseif tonumber (path) = = 3 then |
104 | pos = { currentNode.pos [ 1 ] , currentNode.pos [ 2 ] + 1 } |
105 | elseif tonumber (path) = = 4 then |
106 | pos = { currentNode.pos [ 1 ] - 1 , currentNode.pos [ 2 ] } |
108 | local node = { pos = pos, open = open } |
109 | maze [ pos [ 2 ] * width + pos [ 1 ] ] = node |
110 | table.insert(stack, node) |
111 | tilesDone = tilesDone + 1 |
115 | local function drawMaze() |
116 | for _, v in pairs (script.Parent.Puzzle:GetChildren()) do |
117 | if v.Name = = "Line" then |
121 | local tileHeight, tileWidth = script.Parent.AbsoluteSize.Y / height, script.Parent.AbsoluteSize.X / width |
122 | for i, v in ipairs (maze) do |
123 | local tilePosition = { maze [ i ] .pos [ 1 ] * tileWidth, maze [ i ] .pos [ 2 ] * tileHeight } |
124 | if not maze [ i ] .open [ 1 ] then |
126 | Vector 2. new(tilePosition [ 1 ] , tilePosition [ 2 ] ), |
127 | Vector 2. new(tilePosition [ 1 ] + tileWidth, tilePosition [ 2 ] ) |
130 | if not maze [ i ] .open [ 2 ] then |
132 | Vector 2. new(tilePosition [ 1 ] + tileWidth, tilePosition [ 2 ] ), |
133 | Vector 2. new(tilePosition [ 1 ] + tileWidth, tilePosition [ 2 ] + tileHeight) |
136 | if not maze [ i ] .open [ 3 ] then |
138 | Vector 2. new(tilePosition [ 1 ] , tilePosition [ 2 ] + tileHeight), |
139 | Vector 2. new(tilePosition [ 1 ] + tileWidth, tilePosition [ 2 ] + tileHeight) |
142 | if not maze [ i ] .open [ 4 ] then |
144 | Vector 2. new(tilePosition [ 1 ] , tilePosition [ 2 ] ), |
145 | Vector 2. new(tilePosition [ 1 ] , tilePosition [ 2 ] + tileHeight) |
152 | for _, v in pairs (maze) do |
153 | if not v.open [ 1 ] and not v.open [ 2 ] and not v.open [ 3 ] and not v.open [ 4 ] then |
154 | print ( "Isolated cell" ) |