Advertisement
FluttyProger

navforbuild.lua

Feb 25th, 2017
122
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 33.58 KB | None | 0 0
  1. local robot = require("robot")
  2. local computer = require("computer")
  3. local component = require("component") -- TODO remove
  4. local event = require("event")
  5. local clsNav = {}
  6. clsNav.title = "Navigation Library"
  7. clsNav.shortTitle = "nav"
  8. clsNav.version = 0.9000
  9. function clsNav:new(oldObject)
  10.   local object
  11.   if oldObject
  12.     and type(oldObject.title) == "string"
  13.     and oldObject.title == self.title
  14.     and type(oldObject.title) == "number"
  15.     and oldObject.version <= self.version
  16.   then
  17.     object = oldObject
  18.   else
  19.     object = {}
  20.   end
  21.   setmetatable(object, self)
  22.   self.__index = self
  23.   return object
  24. end
  25.  
  26. ---------------- Dependencies -------------------------------------------------
  27. local logger = logger or {}
  28. if not logger.fatal then
  29.   logger.fatal = function (...) io.write(string.format(...)) end
  30. end
  31. if not logger.warning then
  32.     logger.warning = function (...) io.write(string.format(...)) end
  33. end
  34. if not logger.info then
  35.     logger.info = function (...) --[[io.write(string.format(...))]] end
  36. end
  37. if not logger.spam then
  38.     --local fs = require("filesystem")
  39.     --fs.makeDirectory("logs")
  40.     logger.spam = function (...)
  41.     --  local file = fs.open("logs/test.log","a")
  42.     --  file:write(string.format(...))
  43.     --  file:close()
  44.      -- io.write(string.format(...))
  45.     end
  46. end
  47. local utils = utils or {}
  48. local stoping = false
  49. local moitarg
  50. if not utils.deepCopy then
  51.   function utils.deepCopy(t)
  52.     -- Credits to MihailJP @ https://gist.github.com/MihailJP/3931841
  53.     if type(t) ~= "table" then return t end
  54.     --local meta = getmetatable(t)
  55.     local target = {}
  56.     for k, v in pairs(t) do
  57.       if type(v) == "table" then
  58.         target[k] = utils.deepCopy(v)
  59.       else
  60.         target[k] = v
  61.       end
  62.     end
  63.     --setmetatable(target, meta)
  64.     return target
  65.   end
  66. end
  67. if not utils.deepMerge then
  68.   function utils.deepMerge(t1, t2) -- merges t1 over t2
  69.     if type(t1) ~= "table" then return t1 end
  70.     --local meta1 = getmetatable(t1)
  71.     local target = utils.deepCopy(t2)
  72.     for k, v in pairs(t1) do
  73.         if (type(v) == "table") and (type(target[k] or false) == "table") then
  74.             utils.deepMerge(target[k], t1[k])
  75.         else
  76.             target[k] = v
  77.         end
  78.     end
  79.     --setmetatable(target, meta1)
  80.     return target
  81.   end
  82. end
  83. if not utils.freeMemory then
  84.   function utils.freeMemory()
  85.     local result = 0
  86.     for i = 1, 10 do
  87.       result = math.max(result, computer.freeMemory())
  88.       os.sleep(0)
  89.     end
  90.     return result
  91.   end
  92. end
  93. local thread = thread or {}
  94. if not thread.yield then thread.yield = function() os.sleep() end end
  95.  
  96. ---------------- Local variables ----------------------------------------------
  97. local mapGrid = {
  98.   {"A","B","C","D","E",},
  99.   {"F","G","H","I","J",},
  100.   {"K","L","M","N","O",},
  101.   {"P","Q","R","S","T",},
  102.   {"U","V","W","X","Y",},
  103. }
  104. local mapMaxDepth = 1
  105.  
  106. ---------------- Local functions ----------------------------------------------
  107. local function checkOptions(unknowns, ...)
  108.     local wishlist = {...}
  109.     if type(unknowns) ~= "table" then unknowns = {unknowns} end
  110.   for _,wish in pairs(wishlist) do
  111.     if type(wish) == "string" then
  112.       for _,unknown in pairs(unknowns) do
  113.         if type(unknown) == "string" and wish == unknown then return unknown end
  114.       end
  115.     end
  116.   end
  117.   return false
  118. end
  119. local function checkPos(unknown)
  120.   if type(unknown) ~= "table" then return false end
  121.   if
  122.     type(unknown.x) == "number"
  123.     and type(unknown.y) == "number"
  124.     and type(unknown.z) == "number"
  125.   then
  126.     return {
  127.       x = unknown.x,
  128.       y = unknown.y,
  129.       z = unknown.z,
  130.       f = (type(unknown.f) == "number" and unknown.f) or nil,
  131.       weight = (type(unknown.weight) == "number" and unknown.f) or nil,
  132.     }
  133.   elseif
  134.     type(unknown[1]) == "number"
  135.     and type(unknown[2]) == "number"
  136.     and type(unknown[3]) == "number"
  137.   then
  138.     return {
  139.       x = unknown[1],
  140.       y = unknown[2],
  141.       z = unknown[3],
  142.       f = (type(unknown[4]) == "number" and unknown[4]) or nil,
  143.       weight = (type(unknown[5]) == "number" and unknown[5]) or nil,
  144.     }
  145.   end
  146.   return false
  147. end
  148. local function checkPositions(unknown)
  149.   if type(unknown) ~= "table" then return false end
  150.   local positions = {}
  151.     if checkPos(unknown) then positions[#positions+1] = checkPos(unknown) end
  152.   for k,v in pairs(unknown) do
  153.     local pos = checkPos(v)
  154.     if pos then
  155.       positions[#positions+1] = pos
  156.     end
  157.   end
  158.   if #positions > 0 then
  159.     return positions
  160.   else
  161.     return false
  162.   end
  163. end
  164.  
  165. ---------------- Object variables ---------------------------------------------
  166. clsNav.pos = {
  167.     x = 0, -- North
  168.     z = 0, -- East
  169.     y = 0, -- Height
  170.     f = 0, -- Facing direction, modulus of 4 // 0,1,2,3 = North, East, South, West
  171. }
  172. clsNav.map = {
  173.   _initialized = os.time(),
  174.     _updated = os.time(),
  175.   _maxDepth = 1,
  176. }
  177.  
  178. ---------------- Methods ------------------------------------------------------
  179. function clsNav:getVersion()
  180.     return self.version
  181.     end
  182. function clsNav:getMapFromPos(pos)
  183.   pos = checkPos(pos)
  184.   if not pos then return false end
  185.   local chunkName = ""
  186.   local chunkX = (pos.x-pos.x%16)/16
  187.   local chunkY = -(pos.y-pos.y%16)/16 -- invert so that (-1,-1) is at bottom left, NOT up left
  188.   local power = 1
  189.   while chunkX > 0 or chunkY > 0 or self.map._maxDepth > #chunkName do
  190.     local gridX = (chunkX+2)%5-2
  191.     local gridY = (chunkY+2)%5-2
  192.     chunkX = (chunkX+2 - (chunkX+2)%5)/5
  193.     chunkY = (chunkY+2 - (chunkY+2)%5)/5
  194.     chunkName = mapGrid[gridY+3][gridX+3]..chunkName
  195.   end
  196.   self.map._maxDepth = math.max(self.map._maxDepth, #chunkName)
  197.   local posNo = 10000*pos.z + 100*pos.x + pos.y
  198.   return chunkName, posNo
  199. end
  200. function clsNav:putMap(pos, value)
  201.   if type(value) ~= "table" then return false end -- to delete do putMap(pos,{})
  202.   local chunkName, posNo = self:getMapFromPos(pos)
  203.   if (not chunkName) or (not posNo) then return false end
  204.  
  205.   if not self.map[chunkName] then
  206.     -- check for filesystem files, otherwise...    
  207.     self.map[chunkName] = {}
  208.   end
  209.    
  210.   if not self.map[chunkName][posNo] then self.map[chunkName][posNo] = {} end
  211.  
  212.   self.map._updated = os.time()
  213.   self.map[chunkName]._accessed = os.time()
  214.   self.map[chunkName]._updated = os.time()
  215.   value._updated = os.time()
  216.   self.map[chunkName][posNo] = utils.deepMerge(value, self.map[chunkName][posNo])
  217.   return true
  218. end
  219. function clsNav:setPos(pos_or_face)
  220.   ----logger.spam("Nav:setPos(%s)\n",pos_or_face)
  221.   pos = checkPos(pos_or_face)
  222.   if pos then
  223.     self.pos = pos
  224.   else
  225.     self.pos = self:getPos(pos_or_face)
  226.   end
  227. end
  228. function clsNav:comparePos(poslist1,poslist2,isFaced) -- input either pos or tables of pos
  229.   poslist1 = checkPositions(poslist1)
  230.   if type(poslist1) ~= "table" then return false, 123, "comparePos: No legal pos in argument #1" end
  231.   poslist2 = checkPositions(poslist2)
  232.   if type(poslist2) ~= "table" then return false, 123, "comparePos: No legal pos in argument #2" end
  233.  
  234.     for i,pos1 in pairs(poslist1) do
  235.         ----logger.spam("Pos1(%s): %s,%s,%s,%s\n", i, Pos1.x, Pos1.z, Pos1.y, Pos1.f)
  236.     for j,pos2 in pairs(poslist2) do
  237.       ----logger.spam("  Pos2(%s): %s,%s,%s,%s\n", j, Pos2.x, Pos2.z, Pos2.y, Pos2.f)
  238.       if
  239.         pos1.x == pos2.x
  240.         and pos1.y == pos2.y
  241.         and pos1.z == pos2.z
  242.       then
  243.         if
  244.           (isFaced
  245.           and pos1.f
  246.           and pos2.f
  247.           and pos1.f == pos2.f)
  248.           or
  249.           (not isFaced)
  250.         then
  251.           return pos1, pos2
  252.         end
  253.       end
  254.         end
  255.   end
  256.  
  257.     return false
  258. end
  259. function clsNav:getPos(pos, dir) -- Input Position (table), FacingDirection (num) in any order
  260.   if type(dir) == "nil" and type(pos) == "number" then
  261.     dir = pos%6
  262.     pos = nil
  263.     elseif type(dir) == "number" then
  264.     dir = dir%6
  265.   else
  266.     dir = nil
  267.   end
  268.   pos = checkPos(pos or self.pos)
  269.   if not pos then return false, "Failed to lookup pos" end
  270.    
  271.     if dir == nil then return pos
  272.     elseif dir == 0 then return {["x"] = pos.x+1, ["y"] = pos.y, ["z"] = pos.z, ["f"] = dir}
  273.     elseif dir == 1 then return {["x"] = pos.x, ["y"] = pos.y+1, ["z"] = pos.z, ["f"] = dir}
  274.     elseif dir == 2 then return {["x"] = pos.x-1, ["y"] = pos.y, ["z"] = pos.z, ["f"] = dir}
  275.     elseif dir == 3 then return {["x"] = pos.x, ["y"] = pos.y-1, ["z"] = pos.z, ["f"] = dir}
  276.     elseif dir == 4 then return {["x"] = pos.x, ["y"] = pos.y, ["z"] = pos.z+1, ["f"] = pos.f}
  277.     elseif dir == 5 then return {["x"] = pos.x, ["y"] = pos.y, ["z"] = pos.z-1, ["f"] = pos.f}
  278.     end
  279.    
  280.     return false
  281. end
  282. function clsNav:getMap(pos)
  283.   local chunkName, posNo = self:getMapFromPos(pos)
  284.   if (not chunkName) or (not posNo) then return false, "Bad arguments" end
  285.  
  286.   if not self.map[chunkName] then
  287.     -- check for filesystem files, otherwise...    
  288.     return {}
  289.   end
  290.  
  291.   if not self.map[chunkName][posNo] then return {} end
  292.  
  293.   self.map[chunkName]._accessed = os.time()
  294.   return utils.deepCopy(self.map[chunkName][posNo])
  295. end
  296. function clsNav:detectAround()
  297.   local _,substance = robot.detect()
  298.   self:putMap(self:getPos(self:getPos().f),{["substance"]=substance})
  299.   _,substance = robot.detectUp()
  300.   self:putMap(self:getPos(4),{["substance"]=substance})
  301.   _,substance = robot.detectDown()
  302.   self:putMap(self:getPos(5),{["substance"]=substance})
  303. end
  304. function clsNav:getPath(targets, options)
  305.     --This code (Aug, 2014) is written by Akuukis
  306.     --  who based on code (Sep 21, 2006) by Altair
  307.     --      who ported and upgraded code of LMelior
  308.  
  309.   local options = options or {}
  310.   local upvalues = {} -- upvalue, anti-crashing mechanism
  311.   local pathtries = 3
  312.   local function serializePos(pos)
  313.     return string.format("%05s",pos.x)..string.format("%05s",pos.y)..string.format("%03s",pos.z)
  314.   end
  315.   local function getHeuristic(startPos, targetPoslist, flag)
  316.     local minCost = math.huge
  317.     for _,targetPos in pairs(targetPoslist) do
  318.       if flag == "manhattan" then
  319.         minCost = math.min(minCost, 1 * (
  320.           (targetPos.pathWeight or 0) +
  321.           math.abs(startPos.x-targetPos.x) +
  322.           math.abs(startPos.y-targetPos.y) +
  323.           math.abs(startPos.z-targetPos.z) )
  324.         )
  325.       elseif flag == "euclidean" then
  326.         minCost = math.min(minCost, (targetPos.pathWeight or 0) + math.abs((
  327.           math.abs(startPos.x-targetPos.x) +
  328.           math.abs(startPos.y-targetPos.y) +
  329.           math.abs(startPos.z-targetPos.z) ) ^ (1/3) )
  330.         )
  331.       else -- manhattan
  332.         minCost = math.min(minCost, 1 * (
  333.           (targetPos.pathWeight or 0) +
  334.           math.abs(startPos.x-targetPos.x) +
  335.           math.abs(startPos.y-targetPos.y) +
  336.           math.abs(startPos.z-targetPos.z) )
  337.         )
  338.       end
  339.     end
  340.     if minCost ~= math.huge then return minCost else error(debug.traceback()) end
  341.   end
  342.   local function getCostBlocks(nextBase, flag)
  343.     local defaultMoveCost = 1 -- cost in time to move 1 square, no diagonal movement.
  344.     local defaultWanderCost = 1 -- average cost to move in unexplored block
  345.     local defaultDestroyCost = 3 -- average cost in time to move 1 square with solid block inside
  346.     if nextBase.substance == "air" then
  347.       if flag == "careful" then
  348.         return defaultMoveCost
  349.       elseif flag == "normal" then
  350.         return defaultMoveCost
  351.       elseif flag == "simple" then
  352.         return defaultMoveCost
  353.       elseif flag == "brutal" then
  354.         return defaultMoveCost
  355.       end
  356.     elseif nextBase.substance == "replaceable" then
  357.       if flag == "careful" then
  358.         return defaultMoveCost
  359.       elseif flag == "normal" then
  360.         return defaultMoveCost
  361.       elseif flag == "simple" then
  362.         return defaultMoveCost
  363.       elseif flag == "brutal" then
  364.         return defaultMoveCost
  365.       end
  366.     elseif nextBase.substance == "solid" then
  367.       if flag == "careful" then
  368.         return false
  369.       elseif flag == "normal" then
  370.         return defaultDestroyCost
  371.       elseif flag == "simple" then
  372.         return defaultMoveCost
  373.       elseif flag == "brutal" then
  374.         return defaultMoveCost
  375.       end
  376.     elseif nextBase.substance == "liquid" then
  377.       if flag == "careful" then
  378.         return false
  379.       elseif flag == "normal" then
  380.         return defaultDestroyCost
  381.       elseif flag == "simple" then
  382.         return defaultMoveCost
  383.       elseif flag == "brutal" then
  384.         return defaultDestroyCost
  385.       end
  386.     elseif nextBase.substance == "entity" then
  387.       if flag == "careful" then
  388.         return defaultMoveCost
  389.       elseif flag == "normal" then
  390.         return defaultMoveCost
  391.       elseif flag == "simple" then
  392.         return defaultMoveCost
  393.       elseif flag == "brutal" then
  394.         return defaultMoveCost
  395.       end
  396.     else
  397.       return defaultWanderCost
  398.     end
  399.   end
  400.   local function getCostTurn(curBase, dir, flag)
  401.     if flag == "simple" then return 0 end
  402.     local defaultTurnCost = 0.9 -- cost in time to turn 90 degrees
  403.     local turnCost = 0
  404.     if (not curBase.f) or curBase.f == dir or dir == 4 or dir == 5 then
  405.       return 0
  406.     elseif (dir-curBase.f)%4 == 2 then
  407.       return 2 * defaultTurnCost
  408.     else
  409.       return 1 * defaultTurnCost
  410.     end
  411.   end
  412.   local function getModTags(nextBase, flag)
  413.     local defaultRoadMod = 0.1 -- cost modifier for moving along roads
  414.     if nextBase.tag then
  415.       if flag == "careful" or flag == "normal" then
  416.         if nextBase.tag.evade then
  417.           return false
  418.         elseif nextBase.tag.road then
  419.           return defaultRoadMod
  420.         end
  421.       elseif flag == "simple" then
  422.         if nextBase.tag.evade then
  423.           return false
  424.         end
  425.       elseif flag == "brutal" then
  426.         -- ignore tags
  427.       end
  428.     end
  429.     return 1
  430.   end
  431.   local function tryPath(starts, targets, options)
  432.  
  433.     starts = checkPositions(starts)
  434.     if (not starts) or (not next(starts)) then return false, "No legal start provided" end
  435.     --for k,v in pairs(starts) do
  436.     --  --logger.spam("tryPath: Start %s/%s (%2s,%2s,%2s,%2s,%2s)\n", k,#starts,v.x,v.y,v.z,v.f,v.weight)
  437.     --end
  438.     targets = checkPositions(targets)
  439.     if (not targets) or (not next(targets)) then return false, "No legal target provided" end
  440.     --for k,v in pairs(targets) do
  441.     --  --logger.spam("tryPath: Target %s/%s (%2s,%2s,%2s,%2s,%2s)\n", k,#targets,v.x,v.y,v.z,v.f,v.weight)
  442.     --end
  443.    
  444.     local defaultHeurMod = 1 -- cost modifier for heuristics. Keep it big enough!
  445.     local flag = checkOptions(options, "careful", "normal", "simple", "brutal") or "simple"
  446.     local flag2 = checkOptions(options, "manhattan", "euclidean") or "manhattan"
  447.     local flag3 = checkOptions(options, "forwards", "backwards", "bidirectional") or "bidirectional"
  448.     --for k,v in pairs(options) do
  449.     --  --logger.spam("tryPath: Option %s/%s %s\n", k,#options,v)
  450.     --end
  451.    
  452.     --logger.info("Pathfinding: %s starts, %s targets, %s options.\n",#starts,#targets,#options + ((options._path and 1) or 0))
  453.    
  454.     options = nil
  455.     local SCL, SNL, SOL, TCL, TNL, TOL = {}, {}, {min={weight=math.huge}}, {}, {}, {min={weight=math.huge}}
  456.     for i=1,#starts do
  457.       local key = serializePos(starts[i])
  458.       if not SNL[key] then
  459.         local nextBase = self:getMap(starts[i])
  460.         local costBlocks = getCostBlocks(nextBase, flag)
  461.         local modTag = getModTags(nextBase, flag)
  462.         if costBlocks and modTag then
  463.           SNL[key] = starts[i] -- x,y,z,f
  464.           SNL[key].pathWeight = (SNL[key].weight or 0) + costBlocks * modTag
  465.           SNL[key].heurWeight = getHeuristic(starts[i], targets, flag2) * defaultHeurMod
  466.           SNL[key].weight = SNL[key].pathWeight + SNL[key].heurWeight
  467.           SNL[key].parent = false
  468.           SNL.min = SNL.min or {}
  469.           SNL.min.weight = math.min(SNL.min.weight or math.huge, SNL[key].weight)
  470.         end
  471.       end
  472.     end
  473.     for i=1,#targets do
  474.       local key = serializePos(targets[i])
  475.       if not TNL[key] then
  476.         local nextBase = self:getMap(targets[i])
  477.         local costBlocks = getCostBlocks(nextBase, flag)
  478.         local modTag = getModTags(nextBase, flag)
  479.         if costBlocks and modTag then
  480.           TNL[key] = targets[i] -- x,y,z,f
  481.           TNL[key].pathWeight = (TNL[key].weight or 0) + costBlocks * modTag
  482.           TNL[key].heurWeight = getHeuristic(targets[i], starts, flag2) * defaultHeurMod
  483.           TNL[key].weight = TNL[key].pathWeight + TNL[key].heurWeight
  484.           TNL[key].parent = false
  485.           TNL.min = TNL.min or {}
  486.           TNL.min.weight = math.min(TNL.min.weight or math.huge, TNL[key].weight)
  487.         end
  488.       end
  489.     end
  490.    
  491.     if (not starts) or (not next(starts)) then return false, "No legal start left, all filtered" end
  492.     if (not targets) or (not next(targets)) then return false, "No legal target left, all filtered" end
  493.     --logger.info("Searching.")
  494.    
  495.     local side = false
  496.     local closedk = 0
  497.     local timer = os.time()
  498.     while true do
  499.      
  500.       if flag3 == "forwards" then
  501.         side = true
  502.       elseif flag3 == "backwards" then
  503.         side = false
  504.       elseif flag3 == "bidirectional" then
  505.         side = not side -- switch sides
  506.       end
  507.      
  508.       local openlist = (side and SOL) or TOL
  509.       local openlistNew = (side and SNL) or TNL
  510.       local closedlist = (side and SCL) or TCL
  511.       local targetlist = (side and {TCL.last, table.unpack(targets)}) or {SCL.last, table.unpack(starts)}
  512.      
  513.       local temp1, temp2 = openlist.min, openlistNew.min
  514.       openlist.min, openlistNew.min = nil, nil
  515.       if (not next(openlist)) and (not next(openlistNew)) then return false, "trapped in box or illegal targets" end -- trapped in box, cannot find path
  516.       openlist.min, openlistNew.min = temp1, temp2
  517.      
  518.       do  -- Find next node with the lowest weight (Prefer newer nodes)
  519.         local searchlist = (openlist.min.weight < openlistNew.min.weight and openlist) or openlistNew
  520.         local lowestDS = math.huge
  521.         searchlist.min.weight = math.huge
  522.         local basis = nil
  523.         for k,node in pairs(searchlist) do
  524.           if (node.weight or math.huge) < searchlist.min.weight then
  525.             if node.weight < lowestDS then
  526.               searchlist.min.weight = lowestDS
  527.               lowestDS = node.weight
  528.               basis = k
  529.             else
  530.               searchlist.min.weight = node.weight
  531.             end
  532.           end
  533.         end
  534.         closedlist[basis] = searchlist[basis]
  535.         closedlist[basis].key = basis
  536.         closedlist.last = closedlist[basis]
  537.         closedk = closedk + 1
  538.         searchlist[basis] = nil
  539.         for k,node in pairs(openlistNew) do
  540.           if k ~= "min" then
  541.             openlist[k] = node
  542.             openlistNew[k] = nil
  543.           else
  544.             openlist.min.weight = math.min(node.weight,openlist.min.weight)
  545.             openlistNew.min.weight = math.huge
  546.           end
  547.         end
  548.       end
  549.       local curBase = closedlist.last
  550.      
  551.       if self:comparePos(curBase,targetlist,false) then -- check if we have reached one of targetlist..
  552.         SOL, TOL, SNL, TNL, openlist, openlistNew, targetlist = nil, nil, nil, nil, nil, nil, nil -- free some RAM
  553.        
  554.         local startlist, endlist = SCL, TCL
  555.         local startBase, endBase = nil, nil
  556.         for k,target in pairs(startlist) do
  557.           if self:comparePos(curBase,target,false) then startBase = target end
  558.         end
  559.         for k,target in pairs(endlist) do
  560.           if self:comparePos(curBase,target,false) then endBase = target end
  561.         end
  562.        
  563.         if
  564.           ((not startBase) or startBase.parent == false)
  565.           and ((not endBase) or endBase.parent == false)
  566.         then return {}, {}, 0 end -- if we started at target
  567.        
  568.         local function extractPath(list,base)
  569.           if not base then return {} end
  570.           local path = {[1] = base}
  571.           while path[#path].parent do
  572.             path[#path+1] = list[path[#path].parent]
  573.             list[path[#path-1].parent] = nil
  574.             if #path%100==0 then thread.yield() end
  575.           end
  576.           return path
  577.         end
  578.        
  579.         local startPath = extractPath(startlist, startBase) -- backwards
  580.         local endPath = extractPath(endlist, endBase) -- backwards
  581.        
  582.         local path = {}
  583.         for i=1,#startPath do path[#path+1] = startPath[#startPath-i+1] end
  584.         path[#path]=nil
  585.         for i=1,#endPath do path[#path+1] = endPath[i] end
  586.        
  587.         -- Change list of XZ coordinates into a list of directions
  588.         --logger.spam("dirPath. ")
  589.         thread.yield()
  590.         local dirPath = {}
  591.         for i=1,#path-1 do
  592.           if path[i+1].x > path[i].x then dirPath[i]=0 -- North
  593.           elseif path[i+1].y > path[i].y then dirPath[i]=1 -- East
  594.           elseif path[i+1].x < path[i].x then dirPath[i]=2 -- South
  595.           elseif path[i+1].y < path[i].y then dirPath[i]=3 -- West
  596.           elseif path[i+1].z > path[i].z then dirPath[i]=4 -- Up
  597.           elseif path[i+1].z < path[i].z then dirPath[i]=5 -- Down
  598.           end
  599.           --logger.spam("%s,", dirPath[i])
  600.         end
  601.         --logger.spam("\n")
  602.         return dirPath, path
  603.       end  
  604.      
  605.       for dir=0,5 do
  606.         local nextPos = self:getPos(curBase,dir)
  607.         local nextKey = serializePos(nextPos)
  608.         if not closedlist[nextKey] then
  609.           local nextBase = self:getMap(nextPos)
  610.           local costBlocks = getCostBlocks(nextBase, flag)
  611.           local costTurn = getCostTurn(curBase, dir, flag)
  612.           local modTag = getModTags(nextBase, flag)
  613.           if costBlocks and costTurn and modTag then
  614.             openlistNew[nextKey] = nextPos -- x,y,z,f
  615.             openlistNew[nextKey].pathWeight = curBase.pathWeight + (costBlocks + costTurn) * modTag
  616.             openlistNew[nextKey].heurWeight = getHeuristic(nextPos, targetlist, flag2) * defaultHeurMod
  617.             openlistNew[nextKey].weight = openlistNew[nextKey].pathWeight + openlistNew[nextKey].heurWeight
  618.             if openlist[nextKey] and openlist[nextKey].weight < openlistNew[nextKey].weight then
  619.               openlistNew[nextKey] = nil
  620.             else
  621.               openlist[nextKey] = nil
  622.               openlistNew[nextKey].parent = curBase.key
  623.               openlistNew.min.weight = math.min(openlistNew.min.weight or math.huge, openlistNew[nextKey].weight)
  624.             end
  625.           end
  626.         end
  627.       end
  628.  
  629.       do  -- find lowest weight for closedlist to add as lastTarget
  630.         local lowestDS = curBase.weight or math.huge
  631.         local lowestHeurWeight = curBase.heurWeight or math.huge
  632.         local basis = "last"
  633.         for k,node in pairs(closedlist) do
  634.           if (node.weight or math.huge) < lowestDS then
  635.             lowestDS = node.weight
  636.             basis = k
  637.           end
  638.           if (node.heurWeight or math.huge) < lowestHeurWeight then
  639.             lowestHeurWeight = node.heurWeight
  640.           end
  641.         end
  642.         if side then
  643.           upvalues.starts = {closedlist[basis], curBase}
  644.         else
  645.           upvalues.targets = {closedlist[basis], curBase}
  646.         end
  647.         upvalues.minHeurWeight = math.min(upvalues.minHeurWeight or math.huge, lowestHeurWeight)
  648.         upvalues.cycles = (upvalues.cycles or 0) + 1
  649.       end
  650.      
  651.       thread.yield()
  652.     end
  653.     return false
  654.   end
  655.   local oldUpvalues = {starts={},targets={},minHeurWeight=math.huge}
  656.   repeat
  657.        
  658.     local ok, dirPath, path = pcall(
  659.       tryPath,
  660.       utils.deepCopy(upvalues.starts or {self:getPos()}),
  661.       utils.deepCopy(upvalues.targets or targets),
  662.       options
  663.     )
  664.     --logger.info("\n")
  665.    
  666.     if ok then
  667.       if not dirPath then
  668.         return false, path -- error
  669.       elseif self:comparePos(path[1],self:getPos(),false) then
  670.         if self:comparePos(path[#path],targets,false) then
  671.           return dirPath, path, true, upvalues.cycles -- full path
  672.         else
  673.         end
  674.       else
  675.         --logger.spam("Found another start: %s,%s,%s,%s\n",path[1].x,path[1].y,path[1].z,path[1].f)
  676.         targets = path[1] -- search to new target that leads to real target
  677.         upvalues.starts = nil
  678.         pathtries = 3
  679.         options._path = nil
  680.       end
  681.     else
  682.       --logger.spam("Searching failed: %s\n",dirPath)
  683.     end
  684.    
  685.     if pathtries == 3 then
  686.       --logger.spam("Retry with 'backwards'\n")
  687.       pathtries = pathtries - 1
  688.       options._path = "backwards"
  689.     elseif pathtries == 2 then
  690.       if upvalues.minHeurWeight < oldUpvalues.minHeurWeight then
  691.         --logger.spam("Continue with 'backwards' %s/%s\n",upvalues.minHeurWeight,oldUpvalues.minHeurWeight)
  692.         oldUpvalues.minHeurWeight = upvalues.minHeurWeight
  693.       else
  694.         --logger.spam("Retry with 'forwards'\n")
  695.         pathtries = pathtries - 1
  696.         options._path = "forwards"
  697.       end
  698.     elseif pathtries == 1 then
  699.       if upvalues.minHeurWeight < oldUpvalues.minHeurWeight then
  700.         --logger.spam("Continue with 'forwards' %s/%s\n",upvalues.minHeurWeight,oldUpvalues.minHeurWeight)
  701.         oldUpvalues.minHeurWeight = upvalues.minHeurWeight
  702.       else
  703.         --logger.spam("Retry with 'bidirectional'\n")
  704.         pathtries = pathtries - 1
  705.         options._path = "bidirectional"
  706.       end
  707.     elseif pathtries == 0 then
  708.       if upvalues.minHeurWeight < oldUpvalues.minHeurWeight then
  709.         --logger.spam("Continue with 'bidirectional' %s/%s\n",upvalues.minHeurWeight,oldUpvalues.minHeurWeight)
  710.         oldUpvalues.minHeurWeight = upvalues.minHeurWeight
  711.       else
  712.         --logger.spam("lastTargets %s at (%s,%s,%s)\n",lastTargets, lastTargets and lastTargets.x, lastTargets and lastTargets.y, lastTargets and lastTargets.z)
  713.         return false
  714.       end
  715.     end
  716.    
  717.     thread.yield()
  718.   until pathtries < 0
  719.  
  720. end
  721. function clsNav:move(dir, options) -- dir={0=North|1=East|2=South|3=West|4=up|5=down}, returns true if succeeded
  722.  
  723.   local flag = checkOptions(options, "careful", "normal", "simple", "brutal") or "simple"
  724.   local flag2 = checkOptions(options, "fast", "explore", "patrol") or "fast"
  725.  
  726.     if not self:turnTo(dir) then return false, "no dir provided" end
  727.   for dir=0,3 do -- look around
  728.     if
  729.       (flag2 == "patrol") or
  730.       (flag2 == "explore" and not self:getMap(self:getPos(dir))._updated)
  731.     then
  732.       self:turnTo(dir)
  733.     end
  734.   end
  735.     if dir ~= self:getPos().f then self:turnTo(dir) end
  736.  
  737.     if flag == "careful" or flag == "normal" or flag == "simple" then
  738.     local pos = self:getMap(self:getPos(dir))
  739.     if pos and pos.tag then
  740.       return false, "Tagged as "..pos.tag
  741.     end
  742.   end
  743.    
  744.     -- if flag == "careful" then
  745.  --    local substance
  746.  --    if dir == 4 then _, substance = robot.detectUp()
  747.  --    elseif dir == 5 then _, substance = robot.detectDown()
  748.  --    else _, substance = robot.detect()
  749.  --    end
  750.  --    if substance ~= "air" and substance ~= "replaceable" then return false, substance end
  751.  --  end
  752.  
  753.   local ok, reason
  754.   if dir == 4 then
  755.     --while flag ~= "careful" and robot.detectUp() do robot.swingUp() end
  756.     ok, reason = robot.up()
  757.   elseif dir == 5 then
  758.     --while flag ~= "careful" and robot.detectDown() do robot.swingDown() end
  759.     ok, reason = robot.down()
  760.   else
  761.     --while flag ~= "careful" and robot.detect() do robot.swing() end
  762.     ok, reason = robot.forward()
  763.   end
  764.   if ok then
  765.     self:setPos(dir)
  766.     ----logger.spam("Nav.Move() Return true\n")
  767.     return true
  768.     else
  769.     return false, reason
  770.     end
  771.     return false, "Unexpected error"
  772. end
  773.  
  774. -- Core methods
  775. function clsNav:turnRight()
  776.     robot.turnRight()
  777.     self.pos.f = (self:getPos().f+1)%4
  778.     return true
  779. end
  780. function clsNav:turnLeft()
  781.     robot.turnLeft()
  782.     self.pos.f = (self:getPos().f+3)%4
  783.     return true
  784. end
  785. function clsNav:turnAround()
  786.     if 1==math.floor(math.random(0,1)+0.5) then
  787.         self:turnRight()
  788.         self:turnRight()
  789.     else
  790.         self:turnLeft()
  791.         self:turnLeft()
  792.     end
  793.     return true
  794. end
  795.  
  796. function clsNav:stopgo()
  797.   stoping=true
  798. end
  799.  
  800. function clsNav:gettarg()
  801.   return moitarg
  802. end
  803.  
  804. function clsNav:go(targets, options) -- table target1 [, table target2 ...] text option1 [, text option2 ...]
  805.     moitarg=targets
  806.   local timer = os.time()
  807.   targets = checkPositions(targets) -- Filters legal targets
  808.   if (not targets) or (not next(targets)) then targets = {checkPos({0,0,0,0})} end
  809.  
  810.     local flag = checkOptions(options, "absolute", "relative", "relFace") or "absolute"
  811.     if flag == "relative" then
  812.         for j in ipairs(targets) do
  813.             targets[j].x = targets[j].x + self:getPos().x
  814.             targets[j].y = targets[j].y + self:getPos().y
  815.             targets[j].z = targets[j].z + self:getPos().z
  816.         end
  817.     end
  818.     if flag == "relFace" then
  819.         for j in ipairs(targets) do
  820.             targets[j].x = targets[j].x + self:getPos().x
  821.             targets[j].y = targets[j].y + self:getPos().y
  822.             targets[j].z = targets[j].z + self:getPos().z
  823.             if self:getPos().f == 0 then targets[j].x = self:getPos().x + targets[j].x; targets[j].y = self:getPos().y + targets[j].y end
  824.             if self:getPos().f == 1 then targets[j].x = self:getPos().x - targets[j].y; targets[j].y = self:getPos().y + targets[j].x end
  825.             if self:getPos().f == 2 then targets[j].x = self:getPos().x - targets[j].x; targets[j].y = self:getPos().y - targets[j].y end
  826.             if self:getPos().f == 3 then targets[j].x = self:getPos().x + targets[j].y; targets[j].y = self:getPos().y - targets[j].x end
  827.         end
  828.     end
  829.    
  830.     local tries = 2 -- TODO
  831.  
  832.   while tries <= 2 do
  833.  
  834.         local destination = self:comparePos(targets, self:getPos())
  835.         if destination then
  836.             self:turnTo(destination.f)
  837.             return true
  838.         end
  839.    
  840.     local dirPath,err,ok,cycles = self:getPath(targets,options)
  841.         if not dirPath then -- TODO: Cannot find path!
  842.       if tries == 1 then
  843.         os.sleep(2)
  844.         self:detectAround()
  845.         self:turnAround()
  846.         self:turnAround()
  847.         print("detected")
  848.       end
  849.       tries = tries + 1
  850.         else
  851.       tries=1
  852.       local i = 1
  853.       local ok, err = true, ""
  854.       while i <= #dirPath and (ok or err == "no dir provided") do
  855.         thread.yield()
  856.         if stoping then
  857.           break
  858.         end
  859.         ok, err = self:move(dirPath[i], options)
  860.         if err == "solid" then
  861.           robot.swing()
  862.         end
  863.         i = i + 1
  864.       end
  865.     end
  866.     if stoping then
  867.       break
  868.     end
  869.     end
  870.   stoping = false
  871.     return false, "Go: Aborting, exceeded retries"
  872. end
  873.  
  874. -- Shortcut methods
  875. function clsNav:turnTo(dir)
  876.     if type(dir) ~= "number" then return false end
  877.   if type(dir) == "nil" or dir >= 4 then return true end
  878.     if dir==self:getPos().f or dir==self:getPos().f+4 then return true
  879.     elseif dir==self:getPos().f-1 or dir==self:getPos().f+3 then return self:turnLeft()
  880.     elseif dir==self:getPos().f-2 or dir==self:getPos().f+2 then return self:turnAround()
  881.     elseif dir==self:getPos().f-3 or dir==self:getPos().f+1 then return self:turnRight()
  882.     end
  883. end
  884. function clsNav:step(options)
  885.     return self:move(self:getPos().f,options)
  886. end
  887. function clsNav:stepUp(options)
  888.     return self:move(4,options)
  889. end
  890. function clsNav:stepDown(options)
  891.     return self:move(5,options)
  892. end
  893. function clsNav:goNextTo(center, options)
  894.     local SixTargets = {}
  895.     for i=0,5 do
  896.         SixTargets[i+1] = self:getPos(center,i)
  897.     end
  898.     SixTargets[1].f = 2
  899.     SixTargets[2].f = 3
  900.     SixTargets[3].f = 0
  901.     SixTargets[4].f = 1
  902.     SixTargets[5].f = nil
  903.     SixTargets[6].f = nil
  904.     self:go( SixTargets[1], SixTargets[2], SixTargets[3], SixTargets[4], SixTargets[5], SixTargets[6], options )
  905. end
  906. function clsNav:drawMap(offset)
  907.   offset = checkPos(offset)
  908.   local resX, resY = component.gpu.getResolution()
  909.  
  910.   local lowLeft = self:getPos()
  911.   lowLeft.x = lowLeft.x - (resY+resY%2)/2
  912.   lowLeft.y = lowLeft.y - (resX+resX%2)/2
  913.   if offset then
  914.     lowLeft.x = lowLeft.x + offset.x
  915.     lowLeft.y = lowLeft.y + offset.y
  916.     lowLeft.z = lowLeft.z + offset.z
  917.   end
  918.  
  919.   for yScreen=1,resY do
  920.     local output = ""
  921.     for xScreen=1,resX do
  922.       local subst = self:getMap({lowLeft.x+yScreen,lowLeft.y+xScreen,lowLeft.z}).substance
  923.       if subst == nil then output = output.." "
  924.       elseif subst == "air" then output = output.."."
  925.       elseif subst == "solid" then output = output.."#"
  926.       elseif subst == "entity" then output = output.."e"
  927.       elseif subst == "liquid" then output = output.."~"
  928.       elseif subst == "replaceable" then output = output..","
  929.       else error(debug.traceback())
  930.       end
  931.     end
  932.     component.gpu.set(1,resY+1-yScreen,output)
  933.   end
  934.   return true
  935. end
  936.  
  937. return clsNav
  938.  
  939. ---------------- Details & Notes ----------------------------------------------
  940.  
  941. --[[ Tutorials
  942. General: http://www.lua.org/pil/contents.html
  943. Varargs: http://lua-users.org/wiki/VarargTheSecondClassCitizen
  944. Intro to A* - http://www.raywenderlich.com/4946/introduction-to-a-pathfinding
  945. Try it out! - http://zerowidth.com/2013/05/05/jump-point-search-explained.html
  946. Better version to try out! - https://qiao.github.io/PathFinding.js/visual/
  947. Useful - http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
  948. --]]
  949. --[[ Coord system
  950. # MC logic
  951. Coords... North: z--, East: x++, South: z++, West: x--, Up: y++, Down: y--
  952. Coords... X points East, Z points South, Y points Up
  953. Facing... 0: South, 1: West, 2: North, 3: East
  954. Facing... Starts at South, goes clockwise
  955. # Nav logic
  956. Coords... North: x++, East: y++, South: x--, West: y--, Up: z++, Down: z--
  957. Coords... X points North, Y points East, Z points Up
  958. Facing... 0: North, 1: East, 2: South, 4: West
  959. Facing... Starts at North, goes clockwise
  960.  
  961. use getNavFromMC() or getMCfromNav() to switch if needed, returns table
  962. --]]
  963. --[=[ Map structure
  964. self.map[chunkID][SerializedCoord] = nil --[[unknown--]] or childID --[[if lended--]] or {
  965.   substance = "air" or "solid" or "entity" or "replaceable" or "liquid"
  966.   content = nil --[[unknown--]] or {--[[anything useful--]]}
  967.   updated = 0 -- time in seconds from Colony start
  968.   tag = {[string][,...]} -- RoCoWa related only, like "road", "evade", etc.
  969. --]=]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement