Advertisement
FluttyProger

navut2.lua

Apr 15th, 2017
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 35.97 KB | None | 0 0
  1. local robot = require("robot")
  2. local computer = require("computer")
  3. local component = require("component") -- TODO remove
  4. local clsNav = {}
  5. clsNav.title = "Navigation Library"
  6. clsNav.shortTitle = "nav"
  7. clsNav.version = 0.9000
  8.  
  9. local arrname={["air"]="a",["solid"]="s",["entity"]="ent",["replaceable"]="rep",["liquid"]="liq"}
  10.  
  11. function clsNav:new(oldObject)
  12.   local object
  13.   if oldObject
  14.     and type(oldObject.title) == "string"
  15.     and oldObject.title == self.title
  16.     and type(oldObject.title) == "number"
  17.     and oldObject.version <= self.version
  18.   then
  19.     object = oldObject
  20.   else
  21.     object = {}
  22.   end
  23.   setmetatable(object, self)
  24.   self.__index = self
  25.   return object
  26. end
  27.  
  28. ---------------- Dependencies -------------------------------------------------
  29. local logger = logger or {}
  30. if not logger.fatal then
  31.   logger.fatal = function (...) io.write(string.format(...)) end
  32. end
  33. if not logger.warning then
  34.     logger.warning = function (...) io.write(string.format(...)) end
  35. end
  36. if not logger.info then
  37.     logger.info = function (...) io.write(string.format(...)) end
  38. end
  39. if not logger.spam then
  40.     --local fs = require("filesystem")
  41.     --fs.makeDirectory("logs")
  42.     logger.spam = function (...)
  43.     --  local file = fs.open("logs/test.log","a")
  44.     --  file:write(string.format(...))
  45.     --  file:close()
  46.       io.write(string.format(...))
  47.     end
  48. end
  49. local utils = utils or {}
  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.     _u = 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._u = 1
  213.   --self.map[chunkName]._ac = 1
  214.   --self.map[chunkName]._u = 1
  215.   --value._u = 1
  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]._ac = os.time()
  294.   return utils.deepCopy(self.map[chunkName][posNo])
  295. end
  296. function clsNav:detectAround() -- TODO: add other detections, repair tagging
  297.     --logger.Check("detectAround:%s,%s",location,value)
  298.   local _,s = robot.detect()
  299.   self:putMap(self:getPos(self:getPos().f),{["s"]=arrname[s]})
  300.   _,s = robot.detectUp()
  301.   self:putMap(self:getPos(4),{["s"]=arrname[s]})
  302.   _,s = robot.detectDown()
  303.   self:putMap(self:getPos(5),{["s"]=arrname[s]})
  304. end
  305. function clsNav:getPath(targets, options)
  306.     --This code (Aug, 2014) is written by Akuukis
  307.     --  who based on code (Sep 21, 2006) by Altair
  308.     --      who ported and upgraded code of LMelior
  309.  
  310.   local options = options or {}
  311.   local upvalues = {} -- upvalue, anti-crashing mechanism
  312.   local pathtries = 3
  313.   local function serializePos(pos)
  314.     return string.format("%05s",pos.x)..string.format("%05s",pos.y)..string.format("%03s",pos.z)
  315.   end
  316.   local function getHeuristic(startPos, targetPoslist, flag)
  317.     local minCost = math.huge
  318.     for _,targetPos in pairs(targetPoslist) do
  319.       if flag == "manhattan" then
  320.         minCost = math.min(minCost, 1 * (
  321.           (targetPos.pathWeight or 0) +
  322.           math.abs(startPos.x-targetPos.x) +
  323.           math.abs(startPos.y-targetPos.y) +
  324.           math.abs(startPos.z-targetPos.z) )
  325.         )
  326.       elseif flag == "euclidean" then
  327.         minCost = math.min(minCost, (targetPos.pathWeight or 0) + math.abs((
  328.           math.abs(startPos.x-targetPos.x) +
  329.           math.abs(startPos.y-targetPos.y) +
  330.           math.abs(startPos.z-targetPos.z) ) ^ (1/3) )
  331.         )
  332.       else -- manhattan
  333.         minCost = math.min(minCost, 1 * (
  334.           (targetPos.pathWeight or 0) +
  335.           math.abs(startPos.x-targetPos.x) +
  336.           math.abs(startPos.y-targetPos.y) +
  337.           math.abs(startPos.z-targetPos.z) )
  338.         )
  339.       end
  340.     end
  341.     if minCost ~= math.huge then return minCost else error(debug.traceback()) end
  342.   end
  343.   local function getCostBlocks(nextBase, flag)
  344.     local defaultMoveCost = 1 -- cost in time to move 1 square, no diagonal movement.
  345.     local defaultWanderCost = 1 -- average cost to move in unexplored block
  346.     local defaultDestroyCost = 3 -- average cost in time to move 1 square with sol block inside
  347.     if nextBase.s == "a" then
  348.       if flag == "careful" then
  349.         return defaultMoveCost
  350.       elseif flag == "normal" then
  351.         return defaultMoveCost
  352.       elseif flag == "simple" then
  353.         return defaultMoveCost
  354.       elseif flag == "brutal" then
  355.         return defaultMoveCost
  356.       end
  357.     elseif nextBase.s == "rep" then
  358.       if flag == "careful" then
  359.         return defaultMoveCost
  360.       elseif flag == "normal" then
  361.         return defaultMoveCost
  362.       elseif flag == "simple" then
  363.         return defaultMoveCost
  364.       elseif flag == "brutal" then
  365.         return defaultMoveCost
  366.       end
  367.     elseif nextBase.s == "s" then
  368.       if flag == "careful" then
  369.         return false
  370.       elseif flag == "normal" then
  371.         return defaultDestroyCost
  372.       elseif flag == "simple" then
  373.         return defaultMoveCost
  374.       elseif flag == "brutal" then
  375.         return defaultMoveCost
  376.       end
  377.     elseif nextBase.s == "liq" then
  378.       if flag == "careful" then
  379.         return false
  380.       elseif flag == "normal" then
  381.         return defaultDestroyCost
  382.       elseif flag == "simple" then
  383.         return defaultMoveCost
  384.       elseif flag == "brutal" then
  385.         return defaultDestroyCost
  386.       end
  387.     elseif nextBase.s == "ent" then
  388.       if flag == "careful" then
  389.         return defaultMoveCost
  390.       elseif flag == "normal" then
  391.         return defaultMoveCost
  392.       elseif flag == "simple" then
  393.         return defaultMoveCost
  394.       elseif flag == "brutal" then
  395.         return defaultMoveCost
  396.       end
  397.     else
  398.       return defaultWanderCost
  399.     end
  400.   end
  401.   local function getCostTurn(curBase, dir, flag)
  402.     if flag == "simple" then return 0 end
  403.     local defaultTurnCost = 0.9 -- cost in time to turn 90 degrees
  404.     local turnCost = 0
  405.     if (not curBase.f) or curBase.f == dir or dir == 4 or dir == 5 then
  406.       return 0
  407.     elseif (dir-curBase.f)%4 == 2 then
  408.       return 2 * defaultTurnCost
  409.     else
  410.       return 1 * defaultTurnCost
  411.     end
  412.   end
  413.   local function getModTags(nextBase, flag)
  414.     local defaultRoadMod = 0.1 -- cost modifier for moving along roads
  415.     if nextBase.tag then
  416.       if flag == "careful" or flag == "normal" then
  417.         if nextBase.tag.evade then
  418.           return false
  419.         elseif nextBase.tag.road then
  420.           return defaultRoadMod
  421.         end
  422.       elseif flag == "simple" then
  423.         if nextBase.tag.evade then
  424.           return false
  425.         end
  426.       elseif flag == "brutal" then
  427.         -- ignore tags
  428.       end
  429.     end
  430.     return 1
  431.   end
  432.   local function tryPath(starts, targets, options)
  433.  
  434.     starts = checkPositions(starts)
  435.     if (not starts) or (not next(starts)) then return false, "No legal start provided" end
  436.     --for k,v in pairs(starts) do
  437.     --  logger.spam("tryPath: Start %s/%s (%2s,%2s,%2s,%2s,%2s)\n", k,#starts,v.x,v.y,v.z,v.f,v.weight)
  438.     --end
  439.     targets = checkPositions(targets)
  440.     if (not targets) or (not next(targets)) then return false, "No legal target provided" end
  441.     --for k,v in pairs(targets) do
  442.     --  logger.spam("tryPath: Target %s/%s (%2s,%2s,%2s,%2s,%2s)\n", k,#targets,v.x,v.y,v.z,v.f,v.weight)
  443.     --end
  444.    
  445.     local defaultHeurMod = 1 -- cost modifier for heuristics. Keep it big enough!
  446.     local flag = checkOptions(options, "careful", "normal", "simple", "brutal") or "careful"
  447.     local flag2 = checkOptions(options, "manhattan", "euclidean") or "manhattan"
  448.     local flag3 = checkOptions(options, "forwards", "backwards", "bidirectional") or "bidirectional"
  449.     --for k,v in pairs(options) do
  450.     --  logger.spam("tryPath: Option %s/%s %s\n", k,#options,v)
  451.     --end
  452.    
  453.     logger.info("Pathfinding: %s starts, %s targets, %s options.\n",#starts,#targets,#options + ((options._path and 1) or 0))
  454.    
  455.     options = nil
  456.     local SCL, SNL, SOL, TCL, TNL, TOL = {}, {}, {min={weight=math.huge}}, {}, {}, {min={weight=math.huge}}
  457.     for i=1,#starts do
  458.       local key = serializePos(starts[i])
  459.       if not SNL[key] then
  460.         local nextBase = self:getMap(starts[i])
  461.         local costBlocks = getCostBlocks(nextBase, flag)
  462.         local modTag = getModTags(nextBase, flag)
  463.         if costBlocks and modTag then
  464.           SNL[key] = starts[i] -- x,y,z,f
  465.           SNL[key].pathWeight = (SNL[key].weight or 0) + costBlocks * modTag
  466.           SNL[key].heurWeight = getHeuristic(starts[i], targets, flag2) * defaultHeurMod
  467.           SNL[key].weight = SNL[key].pathWeight + SNL[key].heurWeight
  468.           SNL[key].parent = false
  469.           SNL.min = SNL.min or {}
  470.           SNL.min.weight = math.min(SNL.min.weight or math.huge, SNL[key].weight)
  471.         end
  472.       end
  473.     end
  474.     for i=1,#targets do
  475.       local key = serializePos(targets[i])
  476.       if not TNL[key] then
  477.         local nextBase = self:getMap(targets[i])
  478.         local costBlocks = getCostBlocks(nextBase, flag)
  479.         local modTag = getModTags(nextBase, flag)
  480.         if costBlocks and modTag then
  481.           TNL[key] = targets[i] -- x,y,z,f
  482.           TNL[key].pathWeight = (TNL[key].weight or 0) + costBlocks * modTag
  483.           TNL[key].heurWeight = getHeuristic(targets[i], starts, flag2) * defaultHeurMod
  484.           TNL[key].weight = TNL[key].pathWeight + TNL[key].heurWeight
  485.           TNL[key].parent = false
  486.           TNL.min = TNL.min or {}
  487.           TNL.min.weight = math.min(TNL.min.weight or math.huge, TNL[key].weight)
  488.         end
  489.       end
  490.     end
  491.    
  492.     if (not starts) or (not next(starts)) then return false, "No legal start left, all filtered" end
  493.     if (not targets) or (not next(targets)) then return false, "No legal target left, all filtered" end
  494.     logger.info("Searching.")
  495.    
  496.     local side = false
  497.     local closedk = 0
  498.     local timer = os.time()
  499.     while true do
  500.      
  501.       if flag3 == "forwards" then
  502.         side = true
  503.       elseif flag3 == "backwards" then
  504.         side = false
  505.       elseif flag3 == "bidirectional" then
  506.         side = not side -- switch sides
  507.       end
  508.      
  509.       local openlist = (side and SOL) or TOL
  510.       local openlistNew = (side and SNL) or TNL
  511.       local closedlist = (side and SCL) or TCL
  512.       local targetlist = (side and {TCL.last, table.unpack(targets)}) or {SCL.last, table.unpack(starts)}
  513.      
  514.       local temp1, temp2 = openlist.min, openlistNew.min
  515.       openlist.min, openlistNew.min = nil, nil
  516.       if (not next(openlist)) and (not next(openlistNew)) then return false, "trapped in box or illegal targets" end -- trapped in box, cannot find path
  517.       openlist.min, openlistNew.min = temp1, temp2
  518.      
  519.       do  -- Find next node with the lowest weight (Prefer newer nodes)
  520.         local searchlist = (openlist.min.weight < openlistNew.min.weight and openlist) or openlistNew
  521.         local lowestDS = math.huge
  522.         searchlist.min.weight = math.huge
  523.         local basis = nil
  524.         for k,node in pairs(searchlist) do
  525.           if (node.weight or math.huge) < searchlist.min.weight then
  526.             if node.weight < lowestDS then
  527.               searchlist.min.weight = lowestDS
  528.               lowestDS = node.weight
  529.               basis = k
  530.             else
  531.               searchlist.min.weight = node.weight
  532.             end
  533.           end
  534.         end
  535.         closedlist[basis] = searchlist[basis]
  536.         closedlist[basis].key = basis
  537.         closedlist.last = closedlist[basis]
  538.         closedk = closedk + 1
  539.         searchlist[basis] = nil
  540.         for k,node in pairs(openlistNew) do
  541.           if k ~= "min" then
  542.             openlist[k] = node
  543.             openlistNew[k] = nil
  544.           else
  545.             openlist.min.weight = math.min(node.weight,openlist.min.weight)
  546.             openlistNew.min.weight = math.huge
  547.           end
  548.         end
  549.       end
  550.       local curBase = closedlist.last
  551.  
  552.       if closedk%25==0 then logger.info(".") end
  553.         -- logger.spam("%s %0s: %4s,%2s,%2s %3s+%4s=%4s ",
  554.           -- (side and "=>") or "<=",
  555.           -- closedk,
  556.           -- curBase.x,
  557.           -- curBase.y,
  558.           -- curBase.z,
  559.           -- math.floor(curBase.pathWeight*10)/10,
  560.           -- math.floor(curBase.heurWeight*10)/10,
  561.           -- math.floor((curBase.weight)*10)/10
  562.           -- --self:getMap(self:getPos(curBase)).s
  563.           -- --utils.freeMemory() --GC!
  564.         -- )
  565.      
  566.       if self:comparePos(curBase,targetlist,false) then -- check if we have reached one of targetlist..
  567.         SOL, TOL, SNL, TNL, openlist, openlistNew, targetlist = nil, nil, nil, nil, nil, nil, nil -- free some RAM
  568.        
  569.         local startlist, endlist = SCL, TCL
  570.         local startBase, endBase = nil, nil
  571.         for k,target in pairs(startlist) do
  572.           if self:comparePos(curBase,target,false) then startBase = target end
  573.         end
  574.         for k,target in pairs(endlist) do
  575.           if self:comparePos(curBase,target,false) then endBase = target end
  576.         end
  577.        
  578.         if
  579.           ((not startBase) or startBase.parent == false)
  580.           and ((not endBase) or endBase.parent == false)
  581.         then return {}, {}, 0 end -- if we started at target
  582.        
  583.         local function extractPath(list,base)
  584.           if not base then return {} end
  585.           local path = {[1] = base}
  586.           --logger.spam("(%s,%s,%s|%s)",base.x,base.y,base.z,base.parent)
  587.           while path[#path].parent do
  588.             path[#path+1] = list[path[#path].parent]
  589.             list[path[#path-1].parent] = nil
  590.             --logger.spam("(%s,%s,%s|%s)",path[#path].x,path[#path].y,path[#path].z,path[#path].parent)
  591.             if #path%100==0 then thread.yield() end
  592.           end
  593.           --logger.spam("\n")
  594.           return path
  595.         end
  596.        
  597.         local startPath = extractPath(startlist, startBase) -- backwards
  598.         local endPath = extractPath(endlist, endBase) -- backwards
  599.        
  600.         local path = {}
  601.         for i=1,#startPath do path[#path+1] = startPath[#startPath-i+1] end
  602.         path[#path]=nil
  603.         for i=1,#endPath do path[#path+1] = endPath[i] end
  604.        
  605.         -- Change list of XZ coordinates into a list of directions
  606.         --logger.spam("dirPath. ")
  607.         thread.yield()
  608.         local dirPath = {}
  609.         for i=1,#path-1 do
  610.           if path[i+1].x > path[i].x then dirPath[i]=0 -- North
  611.           elseif path[i+1].y > path[i].y then dirPath[i]=1 -- East
  612.           elseif path[i+1].x < path[i].x then dirPath[i]=2 -- South
  613.           elseif path[i+1].y < path[i].y then dirPath[i]=3 -- West
  614.           elseif path[i+1].z > path[i].z then dirPath[i]=4 -- Up
  615.           elseif path[i+1].z < path[i].z then dirPath[i]=5 -- Down
  616.           end
  617.           --logger.spam("%s,", dirPath[i])
  618.         end
  619.         --logger.spam("\n")
  620.         return dirPath, path
  621.       end  
  622.      
  623.       for dir=0,5 do
  624.         local nextPos = self:getPos(curBase,dir)
  625.         local nextKey = serializePos(nextPos)
  626.         if not closedlist[nextKey] then
  627.           local nextBase = self:getMap(nextPos)
  628.           local costBlocks = getCostBlocks(nextBase, flag)
  629.           local costTurn = getCostTurn(curBase, dir, flag)
  630.           local modTag = getModTags(nextBase, flag)
  631.           if costBlocks and costTurn and modTag then
  632.             openlistNew[nextKey] = nextPos -- x,y,z,f
  633.             openlistNew[nextKey].pathWeight = curBase.pathWeight + (costBlocks + costTurn) * modTag
  634.             openlistNew[nextKey].heurWeight = getHeuristic(nextPos, targetlist, flag2) * defaultHeurMod
  635.             openlistNew[nextKey].weight = openlistNew[nextKey].pathWeight + openlistNew[nextKey].heurWeight
  636.             if openlist[nextKey] and openlist[nextKey].weight < openlistNew[nextKey].weight then
  637.               openlistNew[nextKey] = nil
  638.             else
  639.               openlist[nextKey] = nil
  640.               openlistNew[nextKey].parent = curBase.key
  641.               openlistNew.min.weight = math.min(openlistNew.min.weight or math.huge, openlistNew[nextKey].weight)
  642.             end
  643.           end
  644.         end
  645.       end
  646.  
  647.       do  -- find lowest weight for closedlist to add as lastTarget
  648.         local lowestDS = curBase.weight or math.huge
  649.         local lowestHeurWeight = curBase.heurWeight or math.huge
  650.         local basis = "last"
  651.         for k,node in pairs(closedlist) do
  652.           if (node.weight or math.huge) < lowestDS then
  653.             lowestDS = node.weight
  654.             basis = k
  655.           end
  656.           if (node.heurWeight or math.huge) < lowestHeurWeight then
  657.             lowestHeurWeight = node.heurWeight
  658.           end
  659.         end
  660.         if side then
  661.           upvalues.starts = {closedlist[basis], curBase}
  662.         else
  663.           upvalues.targets = {closedlist[basis], curBase}
  664.         end
  665.         upvalues.minHeurWeight = math.min(upvalues.minHeurWeight or math.huge, lowestHeurWeight)
  666.         upvalues.cycles = (upvalues.cycles or 0) + 1
  667.       end
  668.      
  669.       thread.yield()
  670.     end
  671.     return false
  672.   end
  673.   local oldUpvalues = {starts={},targets={},minHeurWeight=math.huge}
  674.  
  675.   repeat
  676.        
  677.     local ok, dirPath, path = pcall(
  678.       tryPath,
  679.       utils.deepCopy(upvalues.starts or {self:getPos()}),
  680.       utils.deepCopy(upvalues.targets or targets),
  681.       options
  682.     )
  683.     logger.info("\n")
  684.    
  685.     if ok then
  686.       if not dirPath then
  687.         return false, path -- error
  688.       elseif self:comparePos(path[1],self:getPos(),false) then
  689.         if self:comparePos(path[#path],targets,false) then
  690.           return dirPath, path, true, upvalues.cycles -- full path
  691.         else
  692.           return dirPath, path, false, upvalues.cycles -- partial path
  693.         end
  694.       else
  695.         logger.spam("Found another start: %s,%s,%s,%s\n",path[1].x,path[1].y,path[1].z,path[1].f)
  696.         targets = path[1] -- search to new target that leads to real target
  697.         upvalues.starts = nil
  698.         pathtries = 3
  699.         options._path = nil
  700.       end
  701.     else
  702.       logger.spam("Searching failed: %s\n",dirPath)
  703.     end
  704.    
  705.     if pathtries == 3 then
  706.       logger.spam("Retry with 'backwards'\n")
  707.       pathtries = pathtries - 1
  708.       options._path = "backwards"
  709.     elseif pathtries == 2 then
  710.       if upvalues.minHeurWeight < oldUpvalues.minHeurWeight then
  711.         logger.spam("Continue with 'backwards' %s/%s\n",upvalues.minHeurWeight,oldUpvalues.minHeurWeight)
  712.         oldUpvalues.minHeurWeight = upvalues.minHeurWeight
  713.       else
  714.         logger.spam("Retry with 'forwards'\n")
  715.         pathtries = pathtries - 1
  716.         options._path = "forwards"
  717.       end
  718.     elseif pathtries == 1 then
  719.       if upvalues.minHeurWeight < oldUpvalues.minHeurWeight then
  720.         logger.spam("Continue with 'forwards' %s/%s\n",upvalues.minHeurWeight,oldUpvalues.minHeurWeight)
  721.         oldUpvalues.minHeurWeight = upvalues.minHeurWeight
  722.       else
  723.         logger.spam("Retry with 'bidirectional'\n")
  724.         pathtries = pathtries - 1
  725.         options._path = "bidirectional"
  726.       end
  727.     elseif pathtries == 0 then
  728.       if upvalues.minHeurWeight < oldUpvalues.minHeurWeight then
  729.         logger.spam("Continue with 'bidirectional' %s/%s\n",upvalues.minHeurWeight,oldUpvalues.minHeurWeight)
  730.         oldUpvalues.minHeurWeight = upvalues.minHeurWeight
  731.       else
  732.         logger.spam("lastTargets %s at (%s,%s,%s)\n",lastTargets, lastTargets and lastTargets.x, lastTargets and lastTargets.y, lastTargets and lastTargets.z)
  733.         return false
  734.       end
  735.     end
  736.    
  737.     thread.yield()
  738.   until pathtries < 0
  739.  
  740.  
  741. end
  742. function clsNav:geMap()
  743.   return self.map
  744. end
  745. function clsNav:move(dir, options) -- dir={0=North|1=East|2=South|3=West|4=up|5=down}, returns true if succeeded
  746.  
  747.   local flag = checkOptions(options, "careful", "normal", "simple", "brutal") or "careful"
  748.   local flag2 = checkOptions(options, "fast", "explore", "patrol") or "explore"
  749.  
  750.     if not self:turnTo(dir) then return false, "no dir provided" end
  751.   for dir=0,3 do -- look around
  752.     if
  753.       (flag2 == "patrol") or
  754.       (flag2 == "explore" and not self:getMap(self:getPos(dir)).s)
  755.     then
  756.       self:turnTo(dir)
  757.     end
  758.   end
  759.     if dir ~= self:getPos().f then self:turnTo(dir) end
  760.  
  761.     if flag == "careful" or flag == "normal" or flag == "simple" then
  762.     local pos = self:getMap(self:getPos(dir))
  763.     if pos and pos.tag then
  764.       return false, "Tagged as "..pos.tag
  765.     end
  766.   end
  767.    
  768.     if flag == "careful" then
  769.     local s
  770.     if dir == 4 then _, s = robot.detectUp()
  771.     elseif dir == 5 then _, s = robot.detectDown()
  772.     else _, s = robot.detect()
  773.     end
  774.     if s ~= "air" and s ~= "replaceable" then return false, s end
  775.   end
  776.  
  777.   local ok, reason
  778.   if dir == 4 then
  779.     while flag ~= "careful" and robot.detectUp() do robot.swingUp() end
  780.     ok, reason = robot.up()
  781.   elseif dir == 5 then
  782.     while flag ~= "careful" and robot.detectDown() do robot.swingDown() end
  783.       ok, reason = robot.down()
  784.   else
  785.     while flag ~= "careful" and robot.detect() do robot.swing() end
  786.     ok, reason = robot.forward()
  787.   end
  788.   if ok then
  789.     self:setPos(dir)
  790.     --logger.spam("Nav.Move() Return true\n")
  791.     return true
  792.     else
  793.     self:detectAround()
  794.     self:step()
  795.     return false, reason
  796.     end
  797.     return false, "Unexpected error"
  798. end
  799.  
  800. -- Core methods
  801. function clsNav:turnRight()
  802.     robot.turnRight()
  803.     --logger.spam("Nav.turnRight() Nav.Pos.f. %s => ",self:getPos().f)
  804.     self.pos.f = (self:getPos().f+1)%4
  805.     --logger.spam("%s\n",self:getPos().f)
  806.     self:detectAround()
  807.     return true
  808. end
  809. function clsNav:turnLeft()
  810.     robot.turnLeft()
  811.     --logger.spam("Nav:turnLeft() Nav.Pos.f. %s => ",self:getPos().f)
  812.     self.pos.f = (self:getPos().f+3)%4
  813.     --logger.spam("%s\n",self:getPos().f)
  814.     self:detectAround()
  815.     return true
  816. end
  817. function clsNav:turnAround()
  818.     if 1==math.floor(math.random(0,1)+0.5) then
  819.         self:turnRight()
  820.         self:turnRight()
  821.     else
  822.         self:turnLeft()
  823.         self:turnLeft()
  824.     end
  825.     return true
  826. end
  827. function clsNav:go(targets, options) -- table target1 [, table target2 ...] text option1 [, text option2 ...]
  828.    
  829.   local timer = os.time()
  830.   targets = checkPositions(targets) -- Filters legal targets
  831.   if (not targets) or (not next(targets)) then targets = {checkPos({0,0,0,0})} end
  832.  
  833.     local flag = checkOptions(options, "absolute", "relative", "relFace") or "absolute"
  834.     if flag == "relative" then
  835.         for j in ipairs(targets) do
  836.             targets[j].x = targets[j].x + self:getPos().x
  837.             targets[j].y = targets[j].y + self:getPos().y
  838.             targets[j].z = targets[j].z + self:getPos().z
  839.         end
  840.     end
  841.     if flag == "relFace" then
  842.         for j in ipairs(targets) do
  843.             targets[j].x = targets[j].x + self:getPos().x
  844.             targets[j].y = targets[j].y + self:getPos().y
  845.             targets[j].z = targets[j].z + self:getPos().z
  846.             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
  847.             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
  848.             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
  849.             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
  850.         end
  851.     end
  852.    
  853.     local tries = 2 -- TODO
  854.   local count = 0
  855.     --if #targets > 0 then logger.spam("Nav.Go(x%s)(%s,%s,%s)\n", #targets, targets[1].x, targets[1].z, targets[1].y) end
  856.     while tries <= 2 do
  857.     count = count + 1
  858.     logger.info("Go: Try No.%s, (%2s,%2s,%2s,%2s) -> ...\n",
  859.       count,self:getPos().x,self:getPos().y,self:getPos().z,self:getPos().f)
  860.     for k,v in pairs(targets) do
  861.       logger.info("  %s: (%2s,%2s,%2s,%2s)\n", k,v.x,v.y,v.z,v.f)
  862.     end
  863.    
  864.         local destination = self:comparePos(targets, self:getPos())
  865.         if destination then
  866.             self:turnTo(destination.f)
  867.       logger.info("Go: Arrived with %s try in %s seconds!\n",count,(os.time()-timer)/100)
  868.             return true
  869.         end
  870.     local br=false
  871.     local dirPath,err,ok,cycles = self:getPath(targets,options)
  872.         if not dirPath then -- TODO: Cannot find path!
  873.       logger.info("Go: Failed to find path after %s tries in %s seconds because %s\n",count,(os.time()-timer)/100,err)
  874.       if tries == 1 then
  875.         logger.info("Go: Retry in 10 seconds...\n")
  876.         os.sleep(10)
  877.         self:detectAround()
  878.         self:turnAround()
  879.         self:turnAround()
  880.       end
  881.       tries = tries + 1
  882.         else
  883.       tries = 1
  884.       if ok then
  885.         logger.info("Go: Found %s step full path (of %s nodes)\n",#dirPath,cycles)
  886.       else
  887.         logger.info("Go: Found %s step partial path (of %s nodes)\n",#dirPath,cycles)
  888.       end
  889.       local i = 1
  890.       local ok, err = true, ""
  891.       while i <= #dirPath and (ok or err == "no dir provided") do
  892.         thread.yield()
  893.         ok, err = self:move(dirPath[i], options)
  894.         if ok then
  895.           logger.info("Go: Step %s/%s done (%2s,%2s,%2s,%2s)\n",i,#dirPath,
  896.             self:getPos().x,self:getPos().y,self:getPos().z,self:getPos().f)
  897.         else
  898.           logger.info("Go: Step failed because %s\n",err)
  899.           br=true
  900.           break
  901.         end
  902.         i = i + 1
  903.       end
  904.       if br then
  905.         break
  906.       end
  907.     end
  908.     end
  909.     return false, "Go: Aborting, exceeded retries"
  910. end
  911.  
  912. -- Shortcut methods
  913. function clsNav:turnTo(dir)
  914.     if type(dir) ~= "number" then return false end
  915.   if type(dir) == "nil" or dir >= 4 then return true end
  916.     if dir==self:getPos().f or dir==self:getPos().f+4 then return true
  917.     elseif dir==self:getPos().f-1 or dir==self:getPos().f+3 then return self:turnLeft()
  918.     elseif dir==self:getPos().f-2 or dir==self:getPos().f+2 then return self:turnAround()
  919.     elseif dir==self:getPos().f-3 or dir==self:getPos().f+1 then return self:turnRight()
  920.     end
  921. end
  922. function clsNav:step(options)
  923.     return self:move(self:getPos().f,options)
  924. end
  925. function clsNav:stepUp(options)
  926.     return self:move(4,options)
  927. end
  928. function clsNav:stepDown(options)
  929.     return self:move(5,options)
  930. end
  931. function clsNav:goNextTo(center, options)
  932.     local SixTargets = {}
  933.     --logger.spam("Center: ")
  934.     --for i,v in pairs(center) do logger.spam("'%s'=%s, ",i,v) end
  935.     --logger.spam("SixTargets: %s\n",SixTargets)
  936.     for i=0,5 do
  937.         SixTargets[i+1] = self:getPos(center,i)
  938.         --logger.spam("SixTargets: %s\n",SixTargets)
  939.         --for j,v in pairs(self:getPos(center,i)) do logger.spam("'%s'=%s, ",j,v) end
  940.         --for j,v in pairs(SixTargets[1]) do logger.spam("'%s'=%s, ",j,v) end
  941.         --logger.spam("%s: %s,%s,%s,%s\n",SixTargets[i+1], SixTargets[i+1].x,SixTargets[i+1].z,SixTargets[i+1].y,SixTargets[i+1].f)
  942.     end
  943.     --logger.Check("")
  944.     SixTargets[1].f = 2
  945.     SixTargets[2].f = 3
  946.     SixTargets[3].f = 0
  947.     SixTargets[4].f = 1
  948.     SixTargets[5].f = nil
  949.     SixTargets[6].f = nil
  950.     self:go( SixTargets[1], SixTargets[2], SixTargets[3], SixTargets[4], SixTargets[5], SixTargets[6], options )
  951. end
  952. function clsNav:drawMap(offset)
  953.   offset = checkPos(offset)
  954.   local resX, resY = component.gpu.getResolution()
  955.  
  956.   local lowLeft = self:getPos()
  957.   lowLeft.x = lowLeft.x - (resY+resY%2)/2
  958.   lowLeft.y = lowLeft.y - (resX+resX%2)/2
  959.   if offset then
  960.     lowLeft.x = lowLeft.x + offset.x
  961.     lowLeft.y = lowLeft.y + offset.y
  962.     lowLeft.z = lowLeft.z + offset.z
  963.   end
  964.  
  965.   for yScreen=1,resY do
  966.     local output = ""
  967.     for xScreen=1,resX do
  968.       local subst = self:getMap({lowLeft.x+yScreen,lowLeft.y+xScreen,lowLeft.z}).s
  969.       if subst == nil then output = output.." "
  970.       elseif subst == "a" then output = output.."."
  971.       elseif subst == "s" then output = output.."#"
  972.       elseif subst == "ent" then output = output.."e"
  973.       elseif subst == "liq" then output = output.."~"
  974.       elseif subst == "rep" then output = output..","
  975.       else error(debug.traceback())
  976.       end
  977.     end
  978.     component.gpu.set(1,resY+1-yScreen,output)
  979.   end
  980.   return true
  981. end
  982.  
  983. return clsNav
  984.  
  985. ---------------- Details & Notes ----------------------------------------------
  986.  
  987. --[[ Tutorials
  988. General: http://www.lua.org/pil/contents.html
  989. Varargs: http://lua-users.org/wiki/VarargTheSecondClassCitizen
  990. Intro to A* - http://www.raywenderlich.com/4946/introduction-to-a-pathfinding
  991. Try it out! - http://zerowidth.com/2013/05/05/jump-point-search-explained.html
  992. Better version to try out! - https://qiao.github.io/PathFinding.js/visual/
  993. Useful - http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
  994. --]]
  995. --[[ Coord system
  996. # MC logic
  997. -15 65 2
  998. -9 67 0
  999. 6-5
  1000. (recv[7]-apos[1])-lpos.y,(recv[9]-apos[3])-lpos.x,(recv[8]-apos[2])-lpos.z
  1001.  
  1002. Coords... North: z--, East: x++, South: z++, West: x--, Up: y++, Down: y--
  1003. Coords... X points East, Z points South, Y points Up
  1004. Facing... 0: South, 1: West, 2: North, 3: East
  1005. Facing... Starts at South, goes clockwise
  1006. # Nav logic
  1007. Coords... North: x++, East: y++, South: x--, West: y--, Up: z++, Down: z--
  1008. Coords... X points North, Y points East, Z points Up
  1009. Facing... 0: North, 1: East, 2: South, 4: West
  1010. Facing... Starts at North, goes clockwise
  1011.  
  1012. use getNavFromMC() or getMCfromNav() to switch if needed, returns table
  1013. --]]
  1014. --[=[ Map structure
  1015. self.map[chunkID][SerializedCoord] = nil --[[unknown--]] or childID --[[if lended--]] or {
  1016.   s = "a" or "s" or "ent" or "rep" or "liq"
  1017.   content = nil --[[unknown--]] or {--[[anything useful--]]}
  1018.   updated = 0 -- time in seconds from Colony start
  1019.   tag = {[string][,...]} -- RoCoWa related only, like "road", "evade", etc.
  1020. --]=]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement