Advertisement
GopherAtl

ccmazegen

Mar 11th, 2015
291
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.01 KB | None | 0 0
  1. --local file=io.open("mazeBase","r")
  2. os.loadAPI("blittle")
  3. local maze={
  4.   candidates={},
  5.   cells={},
  6.   nodes={},
  7. }
  8.  
  9. function maze:getTypeAt(x,y)
  10.   if y<1 or y>#self or x<1 or x>#self[y] then
  11.     return "void"
  12.   end
  13.   return self[y][x].type
  14. end
  15.  
  16. function maze:getNodeAt(x,y)
  17.   if y<1 or y>#self or x<1 or x>#self[y] then
  18.     return 0
  19.   else
  20.     local cell=self[y][x].cell
  21.     if cell then
  22.       return self.cells[cell].node
  23.     else
  24.       return 0
  25.     end
  26.   end
  27. end
  28.  
  29. local tTypeChar={void=" ",wall="X",cell=" ",path=" ",candidate="."}
  30.  
  31. function maze:typeChar(x,y)
  32.   return tTypeChar[self:getTypeAt(x,y)]
  33. end
  34.  
  35. local function drawMaze(maze,mon,showSolution,redNode, blueNode,yellowPos)
  36. --win.setVisible(false)
  37.   if mon.setVisible then mon.setVisible(false) end
  38.   mon.setBackgroundColor(colors.black)
  39.   mon.clear()
  40.  
  41.   for y=1,#maze do
  42.     local row=maze[y]
  43.     for x=1,#row do
  44.       mon.setCursorPos(x,y)
  45.       local cell=maze[y][x]
  46.       local type=cell.type
  47.       local cellN=cell.cell
  48.       local fg=colors.white
  49.       local bg=colors.black
  50.       local char=tTypeChar[type]
  51.       if type=="wall" then
  52.         bg=colors.white
  53.       elseif type=="void" then
  54.         bg=colors.black
  55.       elseif cellN==maze.entrance then
  56.         bg=colors.green
  57.         char="s"
  58.       elseif cellN==maze.exit then
  59.         bg=colors.green
  60.         char="e"
  61.       elseif showSolution and cell.onPath==true then
  62.         bg=colors.green
  63.       elseif redNode and type=="cell" and maze.cells[cellN] and maze.cells[cellN].node==redNode then
  64.         bg=colors.red
  65.       elseif blueNode and type=="cell" and maze.cells[cellN] and maze.cells[cellN].node==blueNode then
  66.         bg=colors.blue
  67.       elseif yellowPos and x==yellowPos[1] and y==yellowPos[2] then
  68.         bg=colors.yellow        
  69.       end
  70.      
  71.       mon.setTextColor(fg)
  72.       mon.setBackgroundColor(bg)
  73.       mon.write(char)
  74.     end
  75.   end
  76.   if mon.setVisible then mon.setVisible(true) end
  77. end
  78.  
  79.  
  80. local mon=peripheral.wrap("right")
  81. mon.setTextScale(.5)
  82. w,h=mon.getSize()
  83. mon.setBackgroundColor(colors.black)
  84. mon.clear()
  85.  
  86. local win=mon
  87. --[[]]
  88. win=blittle.createWindow(mon,2,2,w-1,h-1,true)
  89. win.setBackgroundColor(colors.black)
  90. win.clear()
  91. w,h=win.getSize()
  92. w=w-2
  93. h=h-2
  94. --]]
  95.  
  96. if h%2==0 then h=h-1 end
  97. if w%2==0 then w=w-1 end
  98.  
  99. local base={}
  100.  
  101. for row=1,h do
  102.     local line=""
  103.     for col=1,w-1 do
  104.         if row==1 or col==1 or row==h or col==w then
  105.             line=line.."x"
  106.         elseif row%2==1 and col%2==1 then  
  107.             line=line.."x"
  108.         elseif row%2==1 or col%2==1 then
  109.             line=line.."."
  110.         else
  111.             line=line.."c"
  112.         end
  113.     end
  114.     base[row]=line.."x"
  115. end
  116. --replace last line to add start and end
  117. base[#base]="xs"..string.rep("x",w-4).."ex"
  118. --[[]]
  119.  
  120. local function addRoom(x,y,w,h)--make a room in the middle!
  121.     base[y]=base[y]:sub(1,x-1)..string.rep("x",(w-1)/2).."."..string.rep("x",(w-1)/2)..base[y]:sub(x+w)
  122.     local mid=string.rep("c",w-2)
  123.     for row=y+1,y+h-2 do        
  124.         base[row]=base[row]:sub(1,x-1)..(row==y+(h-1)/2 and ("."..mid..".") or ("x"..mid.."x"))..base[row]:sub(x+w)
  125.     end
  126.     base[y+h-1]=base[y+h-1]:sub(1,x-1)..string.rep("x",(w-1)/2).."."..string.rep("x",(w-1)/2)..base[y+h-1]:sub(x+w)
  127.     os.sleep()
  128. end
  129.  
  130. local function randomRoom()
  131.     local rw=math.random(5)*4+3
  132.     local rh=math.random(5)*4+3
  133.     local rx=math.random((w-6-rw)/2)*2+3
  134.     local ry=math.random((h-6-rh)/2)*2+3
  135.     addRoom(rx,ry,rw,rh)
  136. end
  137. for i=1,10 do
  138.     randomRoom()
  139. end
  140. --]]
  141.  
  142. local rowN=0
  143. for _,line in ipairs(base) do
  144.   local row={}
  145.   rowN=rowN+1
  146.   for i=1,#line do
  147.     local ch=line:sub(i,i)
  148.     if ch==" " then
  149.       row[i]={type="void"}
  150.     elseif ch=="x" then
  151.       row[i]={type="wall"}
  152.     elseif ch=="c" then
  153.       maze.cells[#maze.cells+1]={pos={i,rowN}, candidates={} }
  154.       row[i]={type="cell",num=#maze.cells}
  155.     elseif ch=="s" then
  156.       maze.cells[#maze.cells+1]={pos={i,rowN}, candidates={} }
  157.       row[i]={type="cell",num=#maze.cells}
  158.       maze.entrance=#maze.cells
  159.     elseif ch=="e" then
  160.       maze.cells[#maze.cells+1]={pos={i,rowN}, candidates={} }
  161.       row[i]={type="cell",num=#maze.cells}
  162.       maze.exit=#maze.cells
  163.     elseif ch=="." then
  164.       row[i]={type="candidate"}
  165.       maze.candidates[#maze.candidates+1]={pos={i,rowN}, cells={} }
  166.     end
  167.     row[i].pos={i,rowN}
  168.   end
  169.   maze[rowN]=row
  170.   os.sleep()
  171. end
  172. --file:close()
  173. drawMaze(maze,win,false)
  174. os.pullEvent("monitor_touch")
  175.  
  176. --if no entrance specified, place an entrance at the bottom-center
  177. if not maze.entrance then
  178.   local y=#maze
  179.   local x=math.ceil(#maze[y]/2)
  180.   maze[y][x]={type="cell",num=#maze.cells+1}
  181.   maze.cells[#maze.cells+1]={pos={x,y},candidates={}}
  182.   maze[y+1]={[x]={type="wall"}}
  183.   maze.entrance=#maze.cells
  184. end
  185.  
  186. if not maze.exit then
  187.   local x,y
  188.   enter=maze.cells[maze.entrance].pos
  189.   repeat
  190.     x=math.random(1,16)*2
  191.     y=math.random(1,16)*2
  192.   until (x-enter[1])^2+(y-enter[2])^2 > 10
  193.   --make it the exit
  194.   maze.exit=maze[y][x].num
  195.   --surround with walls!
  196.   local eCan={}
  197.   local function makeWall(y,x)
  198.     maze[y][x].type="wall"
  199.     --killing a cell, so, uh, pop from end and push here?
  200.     cells[maze[y][x].num]=cells[#cells]
  201.     --and point the pop'd cell's node to the new cell
  202.     maze[cells[#cells].pos[2]][cells[#cells].pos[1]].num=maze[y][x].num
  203.     cells[#cells]=nil
  204.   end
  205.   if maze[y-1] and maze[y-1][x] and maze[y-1][x].type~="wall" then
  206.     eCan[#eCan+1]={y-1,x}
  207.   end
  208.   if maze[y+1] and maze[y+1][x] and maze[y+1][x].type~="wall" then
  209.     eCan[#eCan+1]={y+1,x}
  210.   end
  211.   if maze[y] and maze[y][x-1] and maze[y][x-1].type~="wall" then
  212.     eCan[#eCan+1]={y,x-1}
  213.   end
  214.   if maze[y] and maze[y][x+1] and maze[y][x+1].type~="wall" then
  215.     eCan[#eCan+1]={y,x+1}
  216.   end
  217.   --pick one at random to remove
  218.   table.remove(eCan,math.random(1,#eCan))
  219.   --make the rest walls
  220.   for i=1,#eCan do
  221.     makeWall(eCan[i][2],eCan[i][1])
  222.   end
  223.  
  224. end
  225.  
  226. --TODO: identify and merge cells that are touching into single nodes
  227. --for now just making every cell a node containing only that cell
  228. local nextNode=1
  229. for i=1,#maze.cells do  
  230.   --is there a cell to my left?  
  231.   local pos=maze.cells[i].pos
  232.   local myNode=nextNode
  233.   local merging=nil
  234.   if maze[pos[2]-1][pos[1]].type=="cell" then
  235.     --merge
  236.     myNode=maze.cells[maze[pos[2]-1][pos[1]].cell].node
  237.     merging="up"
  238.   elseif maze[pos[2]][pos[1]-1].type=="cell" then
  239.     myNode=maze.cells[maze[pos[2]][pos[1]-1].cell].node
  240.     merging="left"
  241.   else
  242.     nextNode=nextNode+1
  243.     maze.nodes[myNode]={}
  244.   end
  245.   maze[pos[2]][pos[1]].cell=i
  246.   --[[
  247.   if merging then
  248.     print("merging "..merging)
  249.     maze.cells[i].node="dummy"
  250.     drawMaze(maze,win,false,"dummy",myNode)
  251.     os.pullEvent("monitor_touch")
  252.   end
  253.   --]]
  254.   table.insert(maze.nodes[myNode],i)
  255.   maze.cells[i].node=myNode
  256.  
  257. end
  258. os.sleep()
  259.  
  260.  
  261. do
  262.  
  263.   local function tryAddCell(canN,x,y)
  264.     local t=maze:getTypeAt(x,y)
  265.     if t=="cell" then
  266.       local candidate=maze.candidates[canN]
  267.       local c=maze[y][x].cell
  268.       table.insert(candidate.cells,c)
  269.       table.insert(maze.cells[c].candidates,canN)
  270.       return 1
  271.     end
  272.     return 0
  273.   end
  274.  
  275.   --figure out which nodes touch each candidate. Should only be two...
  276.   for i=1,#maze.candidates do
  277.     local x,y=unpack(maze.candidates[i].pos)
  278.     local numCells=0
  279.     local can=maze.candidates[i]
  280.     numCells=numCells+tryAddCell(i,x-1,y)
  281.     numCells=numCells+tryAddCell(i,x+1,y)
  282.     numCells=numCells+tryAddCell(i,x,y-1)
  283.     numCells=numCells+tryAddCell(i,x,y+1)
  284.     if numCells~=2 then
  285.       error("Encountered "..numCells.." cells touching candidate at ("..x..","..y..")")
  286.     end
  287.     --draw touching nodes
  288.     --drawMaze(maze,win,false,maze.candidates[i].cells[1],maze.candidates[i].cells[2])
  289.     --os.pullEvent("monitor_touch")
  290.   end
  291. end
  292.  
  293. local function tableCopy(source)
  294.   local copy={}
  295.   for k,v in pairs(source) do
  296.     if type(v)=="table" then
  297.       copy[k]=tableCopy(v)
  298.     else
  299.       copy[k]=v
  300.     end
  301.   end
  302.   return copy
  303. end
  304.  
  305.  
  306.  
  307.  
  308. local function genKruskal(maze,visualize)
  309.   --now generate the actual maze!
  310.   --copy candidates, nodes, and maze, since we change them
  311.   local maze=tableCopy(maze)
  312.   --while candidates remain
  313.   while #maze.candidates>0 do
  314.     --pick a random candidate
  315.     local candidateNum=math.random(1,#maze.candidates)
  316.     --print("candidate number "..candidateNum)
  317.     local can=table.remove(maze.candidates,candidateNum)
  318.     --print("position: ("..can.pos[1]..","..can.pos[2]..")")
  319.     local c1, c2=can.cells[1],can.cells[2]
  320.     --print("c1="..c1..", c2="..c2)
  321.     local n1, n2=maze.cells[c1].node, maze.cells[c2].node
  322.     --print("n1="..n1..", n2="..n2)
  323.     --compade nodes of neighboring cells
  324.     if n1~=n2 then
  325.       if visualize then
  326.         print(textutils.serialize(can.pos))
  327.         drawMaze(maze,win,false,n1,n2,can.pos)
  328.         os.pullEvent("monitor_touch")
  329.       end
  330.       --different, make type path
  331.       maze[can.pos[2]][can.pos[1]].type="path"
  332.       --merge nodes. For each cell in node n2
  333.       local node1,node2=maze.nodes[n1],maze.nodes[n2]
  334.       for i=1,#node2 do
  335.         --add to n1's cell list
  336.         local c=node2[i]
  337.         table.insert(node1,c)
  338.         --and move the cell to node n1
  339.         maze.cells[c].node=n1
  340.       end
  341.     else
  342.       --same, make type wall
  343.       maze[can.pos[2]][can.pos[1]].type="wall"
  344.     end
  345.   end
  346.   return maze;
  347. end
  348.  
  349. local function genGrowingTree(maze)
  350.   local maze=tableCopy(maze)
  351.  
  352.   local open={}
  353.   --start at entrance if set, otherwise random
  354.   if maze.entrance then
  355.     open[1]=maze.entrance
  356.   else
  357.     open[1]=math.random(#maze.cells)
  358.   end
  359.   maze.cells[open[1]].visited=true
  360.   --make all candidates walls first
  361.   for i=1,#maze.candidates do
  362.     local pos=maze.candidates[i].pos
  363.     maze[pos[2]][pos[1]].type="wall"
  364.   end
  365.  
  366.   while #open>0 do
  367.     --choose a cell - for now we'll do newest
  368.     local n
  369.     local r=math.random(100)
  370.     if r>25 then
  371.       n=#open
  372.     else
  373.       n=math.random(#open)
  374.     end
  375.     local cellN=table.remove(open,n)
  376.     local cell=maze.cells[cellN]
  377.     if #cell.candidates>0 then
  378.       --pick a random candidate from this cell
  379.       local validCandidates={}
  380.       --first identify valid candidates to unvisited cells
  381.       for i=1,#cell.candidates do
  382.         local can=maze.candidates[cell.candidates[i]]
  383.         local cell2N=can.cells[1]==cellN and can.cells[2] or can.cells[1]
  384.         local cell2=maze.cells[cell2N]
  385.         if not cell2.visited then
  386.           validCandidates[#validCandidates+1]={cell2N,cell.candidates[i]}
  387.         end
  388.       end
  389.       if #validCandidates>0 then
  390.         --there are valid candidates
  391.         --re-add myself to open
  392.         open[#open+1]=cellN
  393.         local next=validCandidates[math.random(#validCandidates)]
  394.         open[#open+1]=next[1]
  395.         maze.cells[next[1]].visited=true
  396.         --make this candidate not a wall
  397.         local wallPos=maze.candidates[next[2]].pos
  398.         maze[wallPos[2]][wallPos[1]].type="path"
  399.       end
  400.     end
  401.   end
  402.  
  403.   return maze
  404. end
  405.  
  406.  
  407.  
  408. local function analyze(maze)
  409.  
  410.   local open={}
  411.   if maze.entrance then
  412.     local c=maze.cells[maze.entrance]
  413.     open[1]=maze[c.pos[2]][c.pos[1]]
  414.   else
  415.     local c=maze.cells[math.random(#maze.cells)]
  416.     open[1]=maze[c.pos[2]][c.pos[1]]
  417.   end
  418.   open[1].cost=0
  419.  
  420.   local closed={}
  421.   local neighbors={{-1,0},{1,0},{0,-1},{0,1}}
  422.   local exit=maze.cells[maze.exit]
  423.   exit=maze[exit.pos[2]][exit.pos[1]]
  424.   local entrance=maze.cells[maze.entrance]
  425.   entrance=maze[entrance.pos[2]][entrance.pos[1]]
  426.  
  427.   while #open>0 do
  428.     --pop an open cell
  429.     local cell=table.remove(open)
  430.     local x,y=cell.pos[1],cell.pos[2]
  431.     --is this the end?
  432.     if cell==exit then
  433.       --print("found exit!")
  434.       --backtrack and mark as path
  435.       local c=cell
  436.       repeat
  437.         maze[c.pos[2]][c.pos[1]].onPath=true
  438.         c=c.prev
  439.       until c==nil
  440.     end
  441.  
  442.     --for each possible neighbor...
  443.     for k,v in pairs(neighbors) do
  444.       local nx, ny=x+v[1],y+v[2]
  445.       local c=maze[ny] and maze[ny][nx] or nil
  446.       local cost=cell.cost+1
  447.       --if it's not closed or closed cost was more
  448.       if c and (c.type=="path" or c.type=="cell") and (closed[c]==nil or c.cost>cell.cost) then
  449.         --set/update cost and prev
  450.         c.cost=cell.cost+1
  451.         c.prev=cell
  452.         --if not open already add to open
  453.         if not c.isOpen then
  454.           c.isOpen=true
  455.           open[#open+1]=c
  456.         end
  457.         --if was closed, unclose
  458.         if closed[c] then
  459.           closed[c]=nil
  460.         end
  461.       end
  462.     end
  463.     closed[cell]=true
  464.   end
  465.  
  466. end
  467.  
  468.  
  469.  
  470. local function saveMaze(maze,filename)
  471.   local file=fs.open(filename,"w")
  472.   for y=1,#maze do
  473.     for x=1,#maze[y] do
  474.       file.write(maze[y][x])
  475.     end
  476.     file.write("\n")
  477.   end
  478.   file.close()
  479. end
  480.  
  481.  
  482. while true do
  483.     local finalMaze=genKruskal(maze)
  484.     --local finalMaze=genGrowingTree(maze)
  485.     analyze(finalMaze)
  486.     drawMaze(finalMaze,win,false)
  487.     os.pullEvent("monitor_touch")
  488.     drawMaze(finalMaze,win,true)
  489.     os.pullEvent("monitor_touch")
  490. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement