Advertisement
CloneTrooper1019

Maze Generator/Solver in pure Lua

Jan 28th, 2016
503
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.58 KB | None | 0 0
  1. -------------------------------------------------------------------------------------------
  2. -- Vector2
  3.  
  4. Vector2 = {}
  5. local v2Meta = {}
  6.  
  7. local function isVector2(obj)
  8.     local success,hasValues = pcall(function ()
  9.         return obj.X + obj.Y + obj.magnitude
  10.     end)
  11.     return success
  12. end
  13.  
  14. function v2Meta.__tostring(v2)
  15.     return v2.X..", "..v2.Y
  16. end
  17.  
  18. function v2Meta.__eq(self,other)
  19.     return self.X == other.X and self.Y == other.Y
  20. end
  21.  
  22. local v2Methods =
  23. {
  24.     add = "+";
  25.     sub = "-";
  26.     mul = "*";
  27.     div = "/";
  28. }
  29.  
  30. local v2Format = "return Vector2.new(%d%s%d,%d%s%d)"
  31.  
  32. for key,met in pairs(v2Methods) do
  33.     v2Meta["__"..key] = function (self,other)
  34.         if type(other) == "number" then
  35.             other = Vector2.new(other,other)
  36.         end
  37.         local x = self.X
  38.         local y = self.Y
  39.         local ox = other.X
  40.         local oy = other.Y
  41.         local result = v2Format:format(x,met,ox,y,met,oy)
  42.         return loadstring(result)()
  43.     end
  44. end
  45.  
  46. function Vector2.new(x,y)
  47.     local x = tonumber(x) and x or 0
  48.     local y = tonumber(y) and y or 0
  49.     local vec =
  50.     {
  51.         X = x;
  52.         Y = y;
  53.         magnitude = math.sqrt(x^2+y^2);
  54.     }
  55.     if vec.magnitude ~= 1 and vec.magnitude ~= 0 then
  56.         vec.unit = Vector2.new(x/vec.magnitude,y/vec.magnitude)
  57.     else
  58.         vec.unit = vec
  59.     end
  60.     setmetatable(vec,v2Meta)
  61.     return vec
  62.  
  63. end
  64.  
  65. -------------------------------------------------------------------------------------------
  66. -- Generate Random Maze
  67.  
  68. print("Generating Maze...")
  69.  
  70. local maze = {}
  71. local width = 93
  72. local height = 50
  73. local cells = {}
  74.  
  75. if (width%2 == 0) then
  76.     print("\t| Warning: Maze Width should be an odd number.")
  77.     print("\t|          Changing from ".. width .." to ".. width+1 ..".")
  78.     width = width + 1
  79. end.
  80.  
  81. if (height%2 == 0) then
  82.     print("\t| Warning: Maze Height should be an odd number.")
  83.     print("\t|          Changing from ".. height .." to ".. height+1 ..".")
  84.     height = height + 1
  85. end
  86.  
  87. for x = 1,width do
  88.     cells[x] = {}
  89.     for y = 1,height do
  90.         cells[x][y] = "#"
  91.     end
  92. end
  93.  
  94. local visited = {}
  95.  
  96. local function visitCell(x,y)
  97.     if not visited[x] then
  98.         visited[x] = {}
  99.     end
  100.     visited[x][y] = true
  101.     cells[x][y] = " "
  102. end
  103.  
  104. local function didVisit(x,y)
  105.     return (visited[x] and visited[x][y])
  106. end
  107.  
  108. local function inRange(pos)
  109.     if pos.X > 1 and pos.Y > 1 then
  110.         if pos.X < width and pos.Y < height then
  111.             return true
  112.         end
  113.     end
  114.     return false
  115. end
  116.  
  117. local function getUnvisitedNeighbors(x,y)
  118.     local neighbors = {}
  119.     for ox = -1,1 do
  120.         for oy = -1,1 do
  121.             local isSame = (ox+oy==0)
  122.             local isDiag = (math.abs(ox)+math.abs(oy))==2
  123.             local cx = x+ox
  124.             local cy = y+oy
  125.             if not (isSame or isDiag) then
  126.                 cx = x+(ox*2)
  127.                 cy = y+(oy*2)
  128.                 if cells[cx] and cells[cx][cy] and not didVisit(cx,cy) then
  129.                     local pos = Vector2.new(cx,cy)
  130.                     table.insert(neighbors,pos)
  131.                 end
  132.             end
  133.         end
  134.     end
  135.     return neighbors
  136. end
  137.  
  138. local activeIndex = 0
  139. local backtrace = {}
  140.  
  141. local function depthSearch(currentCell)
  142.     local x,y = currentCell.X,currentCell.Y
  143.     visitCell(x,y)
  144.     local n = getUnvisitedNeighbors(x,y)
  145.     if #n > 0 then
  146.         activeIndex = activeIndex + 1
  147.         backtrace[activeIndex] = currentCell
  148.         local unvis = n[math.random(1,#n)]
  149.         -- break down wall
  150.         local wallBetween = currentCell + (unvis-currentCell)/2
  151.         local wx,wy = wallBetween.X,wallBetween.Y
  152.         visitCell(wx,wy)
  153.         depthSearch(unvis)
  154.     else
  155.         activeIndex = activeIndex - 1
  156.         if activeIndex > 0 then
  157.             depthSearch(backtrace[activeIndex])
  158.         end
  159.     end
  160. end
  161.  
  162. local startPos = Vector2.new(2,2)
  163. local endPos = Vector2.new(width-1,height)
  164.  
  165. cells[1][2] = "S"
  166. cells[width-1][height] = "E"
  167. cells[2][2] = " "
  168. cells[width-1][height-1] = " "
  169.  
  170. depthSearch(Vector2.new(2,2))
  171.  
  172. for y = 1,height do
  173.     local s = ""
  174.     for x = 1,width do
  175.         s = s .. cells[x][y]
  176.     end
  177.     table.insert(maze,s)
  178. end
  179.  
  180. ---------------------------------------------------------------------------------------------
  181. -- A* Maze Solver
  182.  
  183. print("Solving Maze...")
  184.  
  185. local data = {}
  186. local startPos
  187. local endPos
  188. local abs = math.abs
  189. local showBlocked = true
  190.  
  191. for y,row in pairs(maze) do
  192.     local x = 0
  193.     for char in row:gmatch(".") do
  194.         x = x + 1
  195.         if not data[x] then
  196.             data[x] = {}
  197.         end
  198.         if char == "S" then
  199.             startPos = Vector2.new(x,y)
  200.             data[x][y] = " "
  201.         elseif char == "E" then
  202.             endPos = Vector2.new(x,y)
  203.             data[x][y] = " "
  204.         else
  205.             data[x][y] = char
  206.         end
  207.     end
  208. end
  209.  
  210. if not startPos then
  211.     error("FATAL ERROR: START POS NOT DEFINED IN MAZE",2)
  212. end
  213.  
  214. if not endPos then
  215.     error("FATAL ERROR: END POS NOT DEFINED IN MAZE",2)
  216. end
  217.  
  218. local function surroundingCells(x,y)
  219.     if y == nil then
  220.         -- hack
  221.         x,y = x.X,x.Y
  222.     end
  223.     local cells = {}
  224.     for ox = -1,1 do
  225.         for oy = -1,1 do
  226.             local cx = x+ox
  227.             local cy = y+oy
  228.             local isSame = (ox+oy) == 0
  229.             local isDiag = (abs(ox)+abs(oy)) == 2
  230.             if not (isSame or isDiag) then
  231.                 local row = data[cx]
  232.                 if row then
  233.                     local cell = data[cx][cy]
  234.                     if cell then
  235.                         local data =
  236.                         {
  237.                             Coord = Vector2.new(cx,cy);
  238.                             Cell = cell;
  239.                         }
  240.                         table.insert(cells,data)
  241.                     end
  242.                 end
  243.             end
  244.         end
  245.     end
  246.     local currentKey
  247.     return function ()
  248.         local key,value = next(cells,currentKey)
  249.         if key and value then
  250.             currentKey = key
  251.             return value.Coord,value.Cell
  252.         end
  253.     end
  254. end
  255.  
  256. local function aStar(s,e)
  257.     local closedCache = {}
  258.     local closedList = {}
  259.  
  260.     local function addToClosedList(pos)
  261.         closedList[#closedList+1] = pos
  262.         local x,y = pos.X,pos.Y
  263.         if not closedCache[x] then
  264.             closedCache[x] = {}
  265.         end
  266.         closedCache[x][y] = true
  267.     end
  268.  
  269.  
  270.     addToClosedList(s)
  271.  
  272.     local function getOpenList(pos)
  273.         local openList = {}
  274.         for openPos,cell in surroundingCells(pos) do
  275.             if cell == " " or cell == "E" or cell == "S" then
  276.                 if not closedCache[openPos.X] then
  277.                     closedCache[openPos.X] = {}
  278.                 end
  279.                 if not closedCache[openPos.X][openPos.Y] then
  280.                     openList[#openList+1] = openPos
  281.                 end
  282.             end
  283.         end
  284.         return openList
  285.     end
  286.  
  287.     local foundPath = false
  288.  
  289.     while not foundPath do
  290.         local g = #closedList-1
  291.         local lastPos = closedList[g+1]
  292.         local openList = getOpenList(lastPos)
  293.         if #openList > 0 then
  294.             local lowestF,bestPos = math.huge,Vector2.new()
  295.             for _,pos in pairs(openList) do
  296.                 local h = (endPos-pos).magnitude
  297.                 if h == 0 then
  298.                     addToClosedList(e)
  299.                     foundPath = true
  300.                     print("Path found!")
  301.                     break
  302.                 end
  303.                 local f = g + h
  304.                 if f < lowestF then
  305.                     lowestF = f
  306.                     bestPos = pos
  307.                 end
  308.             end
  309.             if not foundPath then
  310.                 addToClosedList(bestPos)
  311.             end
  312.         else
  313.             break
  314.         end
  315.     end
  316.    
  317.     return foundPath,closedList
  318. end
  319.  
  320. local findingPath = true
  321.  
  322. local function deepCopy(t)
  323.     local new = {}
  324.     for k,v in pairs(t) do
  325.         if type(v) == "table" then
  326.             new[k] = deepCopy(v)
  327.         else
  328.             new[k] = v
  329.         end
  330.     end
  331.     return new
  332. end
  333.  
  334. local function printMaze(closedList,impossible)
  335.     print()
  336.     local dataCopy = deepCopy(data)
  337.  
  338.     local startPos = closedList[1]
  339.     local endPos = closedList[#closedList]
  340.     for index,node in pairs(closedList) do
  341.         if startPos == node then
  342.             dataCopy[node.X][node.Y] = "S"
  343.         elseif endPos == node then
  344.             dataCopy[node.X][node.Y] = "E"
  345.         else
  346.             dataCopy[node.X][node.Y] = string.char(165)
  347.         end
  348.     end
  349.  
  350.     local finalData = {}
  351.     local blockedNodes = 0
  352.     for y,row in pairs(dataCopy) do
  353.         for x,char in pairs(row) do
  354.             if not finalData[x] then
  355.                 finalData[x] = {}
  356.             end
  357.             if char == "B" then
  358.                 blockedNodes = blockedNodes + 1
  359.                 char = ((showBlocked or impossible) and "/" or " ")
  360.             end
  361.             finalData[x][y] = char
  362.         end
  363.     end
  364.  
  365.     for _,column in pairs(finalData) do
  366.         print(table.concat(column,""))
  367.     end
  368.     if not impossible then
  369.         print()
  370.         print("We had to block "..blockedNodes.." nodes to find a path")
  371.         print()
  372.     end
  373. end
  374.  
  375. local function block(node)
  376.     data[node.X][node.Y] = "B"
  377. end
  378.  
  379. local function checkIfImpossible()
  380.     for pos,cell in surroundingCells(startPos) do
  381.         if cell == " " then
  382.             return false
  383.         end
  384.     end
  385.     return true
  386. end
  387.  
  388. local backIterates = 0
  389.  
  390. local function backIterateBlock(nodes)
  391.     while true do
  392.         local node = nodes[#nodes]
  393.         local openCells = 0
  394.         for pos,cell in surroundingCells(node) do
  395.             if cell == " " then
  396.                 openCells = openCells + 1
  397.             end
  398.         end
  399.         if openCells ~= 1 then
  400.             backIterates = backIterates + 1
  401.             break
  402.         else
  403.             block(node)
  404.             table.remove(nodes,#nodes)
  405.         end
  406.     end
  407. end
  408.  
  409. while true do
  410.     local foundPath,nodes = aStar(startPos,endPos)
  411.     if foundPath then
  412.         printMaze(nodes)
  413.         print("It required "..backIterates.." A* scans to solve.")
  414.         break
  415.     else
  416.         local isImpossible = checkIfImpossible()
  417.         if isImpossible then
  418.             print("IMPOSSIBLE TO SOLVE")
  419.             printMaze({},true)
  420.             break
  421.         else
  422.             backIterateBlock(nodes)
  423.         end
  424.     end
  425. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement