Advertisement
FluttyProger

nav.lua

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