diff --git a/sys/apis/jumper/core/bheap.lua b/sys/apis/jumper/core/bheap.lua index d419540..e3b3c83 100644 --- a/sys/apis/jumper/core/bheap.lua +++ b/sys/apis/jumper/core/bheap.lua @@ -1,14 +1,14 @@ --- A light implementation of Binary heaps data structure. -- While running a search, some search algorithms (Astar, Dijkstra, Jump Point Search) have to maintains --- a list of nodes called __open list__. Retrieve from this list the lowest cost node can be quite slow, --- as it normally requires to skim through the full set of nodes stored in this list. This becomes a real --- problem especially when dozens of nodes are being processed (on large maps). +-- a list of nodes called __open list__. Retrieve from this list the lowest cost node can be quite slow, +-- as it normally requires to skim through the full set of nodes stored in this list. This becomes a real +-- problem especially when dozens of nodes are being processed (on large maps). -- -- The current module implements a binary heap --- data structure, from which the search algorithm will instantiate an open list, and cache the nodes being --- examined during a search. As such, retrieving the lower-cost node is faster and globally makes the search end +-- data structure, from which the search algorithm will instantiate an open list, and cache the nodes being +-- examined during a search. As such, retrieving the lower-cost node is faster and globally makes the search end -- up quickly. --- +-- -- This module is internally used by the library on purpose. -- It should normally not be used explicitely, yet it remains fully accessible. -- @@ -23,7 +23,7 @@ if (...) then -- Dependency local Utils = require((...):gsub('%.bheap$','.utils')) - + -- Local reference local floor = math.floor @@ -40,7 +40,7 @@ if (...) then else pIndex = (index-1)/2 end if not heap._sort(heap._heap[pIndex], heap._heap[index]) then - heap._heap[pIndex], heap._heap[index] = + heap._heap[pIndex], heap._heap[index] = heap._heap[index], heap._heap[pIndex] percolate_up(heap, pIndex) end @@ -89,7 +89,7 @@ if (...) then -- @class function -- @treturn bool __true__ of no item is queued in the heap, __false__ otherwise -- @usage - -- if myHeap:empty() then + -- if myHeap:empty() then -- print('Heap is empty!') -- end function heap:empty() @@ -129,7 +129,7 @@ if (...) then -- @class function -- @treturn value a value previously pushed into the heap -- @usage - -- while not myHeap:empty() do + -- while not myHeap:empty() do -- local lowestValue = myHeap:pop() -- ... -- end @@ -148,18 +148,18 @@ if (...) then end --- Restores the `heap` property. - -- Reorders the `heap` with respect to the comparison function being used. - -- When given argument __item__ (a value existing in the `heap`), will sort from that very item in the `heap`. - -- Otherwise, the whole `heap` will be cheacked. + -- Reorders the `heap` with respect to the comparison function being used. + -- When given argument __item__ (a value existing in the `heap`), will sort from that very item in the `heap`. + -- Otherwise, the whole `heap` will be cheacked. -- @class function -- @tparam[opt] value item the modified value -- @treturn heap self (the calling `heap` itself, can be chained) - -- @usage myHeap:heapify() + -- @usage myHeap:heapify() function heap:heapify(item) if self._size == 0 then return end if item then local i = Utils.indexOf(self._heap,item) - if i then + if i then percolate_down(self, i) percolate_up(self, i) end diff --git a/sys/apis/jumper/core/node.lua b/sys/apis/jumper/core/node.lua index b0b9284..c476230 100644 --- a/sys/apis/jumper/core/node.lua +++ b/sys/apis/jumper/core/node.lua @@ -7,59 +7,26 @@ -- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search -- to the next neighbouring node having the lowest `f` cost. See `core.bheap` for more details. -- - if (...) then - --- The `Node` class.
- -- This class is callable. - -- Therefore,_ Node(...) _acts as a shortcut to_ Node:new(...). - -- @type Node local Node = {} Node.__index = Node - --- Inits a new `node` - -- @class function - -- @tparam int x the x-coordinate of the node on the collision map - -- @tparam int y the y-coordinate of the node on the collision map - -- @treturn node a new `node` - -- @usage local node = Node(3,4) function Node:new(x,y,z) - return setmetatable({_x = x, _y = y, _z = z }, Node) + return setmetatable({x = x, y = y, z = z }, Node) end -- Enables the use of operator '<' to compare nodes. -- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost function Node.__lt(A,B) return (A._f < B._f) end - --- Returns x-coordinate of a `node` - -- @class function - -- @treturn number the x-coordinate of the `node` - -- @usage local x = node:getX() - function Node:getX() return self._x end - - --- Returns y-coordinate of a `node` - -- @class function - -- @treturn number the y-coordinate of the `node` - -- @usage local y = node:getY() - function Node:getY() return self._y end - - function Node:getZ() return self._z end - - --- Returns x and y coordinates of a `node` - -- @class function - -- @treturn number the x-coordinate of the `node` - -- @treturn number the y-coordinate of the `node` - -- @usage local x, y = node:getPos() - function Node:getPos() return self._x, self._y, self._z end + function Node:getX() return self.x end + function Node:getY() return self.y end + function Node:getZ() return self.z end --- Clears temporary cached attributes of a `node`. -- Deletes the attributes cached within a given node after a pathfinding call. -- This function is internally used by the search algorithms, so you should not use it explicitely. - -- @class function - -- @treturn node self (the calling `node` itself, can be chained) - -- @usage - -- local thisNode = Node(1,2) - -- thisNode:reset() function Node:reset() self._g, self._h, self._f = nil, nil, nil self._opened, self._closed, self._parent = nil, nil, nil diff --git a/sys/apis/jumper/core/path.lua b/sys/apis/jumper/core/path.lua index e3bb98e..d3c5c16 100644 --- a/sys/apis/jumper/core/path.lua +++ b/sys/apis/jumper/core/path.lua @@ -7,27 +7,18 @@ -- if (...) then - --- The `Path` class.
- -- This class is callable. - -- Therefore, Path(...) acts as a shortcut to Path:new(...). - -- @type Path + + local t_remove = table.remove + local Path = {} Path.__index = Path - --- Inits a new `path`. - -- @class function - -- @treturn path a `path` - -- @usage local p = Path() function Path:new() return setmetatable({_nodes = {}}, Path) end --- Iterates on each single `node` along a `path`. At each step of iteration, -- returns the `node` plus a count value. Aliased as @{Path:nodes} - -- @class function - -- @treturn node a `node` - -- @treturn int the count for the number of nodes - -- @see Path:nodes -- @usage -- for node, count in p:iter() do -- ... @@ -42,6 +33,32 @@ if (...) then end end + --- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path` + -- consisting of straight moves. Does the opposite of @{Path:fill} + -- @class function + -- @treturn path self (the calling `path` itself, can be chained) + -- @see Path:fill + -- @usage p:filter() + function Path:filter() + local i = 2 + local xi,yi,zi,dx,dy,dz, olddx, olddy, olddz + xi,yi,zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z + dx, dy,dz = xi - self._nodes[i-1].x, yi-self._nodes[i-1].y, zi-self._nodes[i-1].z + while true do + olddx, olddy, olddz = dx, dy, dz + if self._nodes[i+1] then + i = i+1 + xi, yi, zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z + dx, dy, dz = xi - self._nodes[i-1].x, yi - self._nodes[i-1].y, zi - self._nodes[i-1].z + if olddx == dx and olddy == dy and olddz == dz then + t_remove(self._nodes, i-1) + i = i - 1 + end + else break end + end + return self + end + return setmetatable(Path, {__call = function(_,...) return Path:new(...) diff --git a/sys/apis/jumper/grid.lua b/sys/apis/jumper/grid.lua index 36f28af..ab48255 100644 --- a/sys/apis/jumper/grid.lua +++ b/sys/apis/jumper/grid.lua @@ -25,18 +25,9 @@ if (...) then {x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]] } - --- The `Grid` class.
- -- This class is callable. - -- Therefore,_ Grid(...) _acts as a shortcut to_ Grid:new(...). - -- @type Grid local Grid = {} Grid.__index = Grid - --- Inits a new `grid` - -- @class function - -- @tparam table Map dimensions - -- or a `string` with line-break chars (\n or \r) as row delimiters. - -- @treturn grid a new `grid` instance function Grid:new(dim) local newGrid = { } newGrid._min_x, newGrid._max_x = dim.x, dim.ex @@ -49,73 +40,38 @@ if (...) then return setmetatable(newGrid,Grid) end - --- Checks if `node` at [x,y] is __walkable__. - -- Will check if `node` at location [x,y] both *exists* on the collision map and *is walkable* - -- @class function - -- @tparam int x the x-location of the node - -- @tparam int y the y-location of the node - -- @tparam int z the z-location of the node - -- function Grid:isWalkableAt(x, y, z) local node = self:getNodeAt(x,y,z) return node and node.walkable ~= 1 end - --- Returns the `grid` width. - -- @class function - -- @treturn int the `grid` width - -- @usage print(myGrid:getWidth()) function Grid:getWidth() return self._width end - --- Returns the `grid` height. - -- @class function - -- @treturn int the `grid` height - -- @usage print(myGrid:getHeight()) function Grid:getHeight() return self._height end - --- Returns the set of nodes. - -- @class function - -- @treturn {{node,...},...} an array of nodes - -- @usage local nodes = myGrid:getNodes() function Grid:getNodes() return self._nodes end - --- Returns the `grid` bounds. Returned values corresponds to the upper-left - -- and lower-right coordinates (in tile units) of the actual `grid` instance. - -- @class function - -- @treturn int the upper-left corner x-coordinate - -- @treturn int the upper-left corner y-coordinate - -- @treturn int the lower-right corner x-coordinate - -- @treturn int the lower-right corner y-coordinate - -- @usage local left_x, left_y, right_x, right_y = myGrid:getBounds() function Grid:getBounds() return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z end --- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`. - -- @class function - -- @tparam node node a given `node` - -- @tparam[opt] string|int|func walkable the value for walkable locations - -- in the collision map array (see @{Grid:new}). - -- Defaults to __false__ when omitted. -- @treturn {node,...} an array of nodes neighbouring a given node - -- @usage - -- local aNode = myGrid:getNodeAt(5,6) - -- local neighbours = myGrid:getNeighbours(aNode, 0, true) function Grid:getNeighbours(node) local neighbours = {} for i = 1,#straightOffsets do local n = self:getNodeAt( - node._x + straightOffsets[i].x, - node._y + straightOffsets[i].y, - node._z + straightOffsets[i].z + node.x + straightOffsets[i].x, + node.y + straightOffsets[i].y, + node.z + straightOffsets[i].z ) - if n and self:isWalkableAt(n._x, n._y, n._z) then + if n and self:isWalkableAt(n.x, n.y, n.z) then neighbours[#neighbours+1] = n end end @@ -123,17 +79,7 @@ if (...) then return neighbours end - --- Returns the `node` at location [x,y,z]. - -- @class function - -- @name Grid:getNodeAt - -- @tparam int x the x-coordinate coordinate - -- @tparam int y the y-coordinate coordinate - -- @tparam int z the z-coordinate coordinate - -- @treturn node a `node` - -- @usage local aNode = myGrid:getNodeAt(2,2) - - -- Gets the node at location on a preprocessed grid - function Grid:getNodeAt(x,y,z) + function Grid:getNodeAt(x,y,z) if not x or not y or not z then return end if Utils.outOfRange(x,self._min_x,self._max_x) then return end if Utils.outOfRange(y,self._min_y,self._max_y) then return end diff --git a/sys/apis/jumper/pathfinder.lua b/sys/apis/jumper/pathfinder.lua index a209afb..93d48ff 100644 --- a/sys/apis/jumper/pathfinder.lua +++ b/sys/apis/jumper/pathfinder.lua @@ -28,11 +28,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --]] ---- The Pathfinder class - --- --- Implementation of the `pathfinder` class. - local _VERSION = "" local _RELEASEDATE = "" @@ -49,8 +44,6 @@ if (...) then --- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper. --
  • [A*](http://en.wikipedia.org/wiki/A*_search_algorithm)
  • - -- @finder Finders - -- @see Pathfinder:getFinders local Finders = { ['ASTAR'] = require (_PATH .. '.search.astar'), } @@ -62,21 +55,9 @@ if (...) then -- Performs a traceback from the goal node to the start node -- Only happens when the path was found - --- The `Pathfinder` class.
    - -- This class is callable. - -- Therefore,_ Pathfinder(...) _acts as a shortcut to_ Pathfinder:new(...). - -- @type Pathfinder local Pathfinder = {} Pathfinder.__index = Pathfinder - --- Inits a new `pathfinder` - -- @class function - -- @tparam grid grid a `grid` - -- @tparam[opt] string finderName the name of the `Finder` (search algorithm) to be used for search. - -- Defaults to `ASTAR` when not given (see @{Pathfinder:getFinders}). - -- @treturn pathfinder a new `pathfinder` instance - -- @usage - -- local finder = Pathfinder:new(myGrid, 'ASTAR') function Pathfinder:new(heuristic) local newPathfinder = {} setmetatable(newPathfinder, Pathfinder) @@ -85,23 +66,13 @@ if (...) then return newPathfinder end - --- Sets the `grid`. Defines the given `grid` as the one on which the `pathfinder` will perform the search. - -- @class function - -- @tparam grid grid a `grid` - -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained) - -- @usage myFinder:setGrid(myGrid) function Pathfinder:setGrid(grid) self._grid = grid return self end - --- Calculates a `path`. Returns the `path` from location __[startX, startY]__ to location __[endX, endY]__. + --- Calculates a `path`. Returns the `path` from start to end location -- Both locations must exist on the collision map. The starting location can be unwalkable. - -- @class function - -- @tparam int startX the x-coordinate for the starting location - -- @tparam int startY the y-coordinate for the starting location - -- @tparam int endX the x-coordinate for the goal location - -- @tparam int endY the y-coordinate for the goal location -- @treturn path a path (array of nodes) when found, otherwise nil -- @usage local path = myFinder:getPath(1,1,5,5) function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh) @@ -112,8 +83,8 @@ if (...) then return nil end - startNode._heading = ih - endNode._heading = oh + startNode.heading = ih + endNode.heading = oh assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ)) assert(endNode and self._grid:isWalkableAt(endX, endY, endZ), diff --git a/sys/apis/jumper/search/astar.lua b/sys/apis/jumper/search/astar.lua index ae730dc..d42f07c 100644 --- a/sys/apis/jumper/search/astar.lua +++ b/sys/apis/jumper/search/astar.lua @@ -18,7 +18,7 @@ if (...) then if node._g + mCost < neighbour._g then neighbour._parent = node neighbour._g = node._g + mCost - neighbour._heading = heading + neighbour.heading = heading end end @@ -64,10 +64,10 @@ if (...) then end --[[ - printf('x:%d y:%d z:%d g:%d', node._x, node._y, node._z, node._g) + printf('x:%d y:%d z:%d g:%d', node.x, node.y, node.z, node._g) for i = 1,#neighbours do local n = neighbours[i] - printf('x:%d y:%d z:%d f:%f g:%f h:%d', n._x, n._y, n._z, n._f, n._g, n._heading or -1) + printf('x:%d y:%d z:%d f:%f g:%f h:%d', n.x, n.y, n.z, n._f, n._g, n.heading or -1) end --]] diff --git a/sys/apis/point.lua b/sys/apis/point.lua index 85122f3..60a9d6d 100644 --- a/sys/apis/point.lua +++ b/sys/apis/point.lua @@ -2,6 +2,40 @@ local Util = require('util') local Point = { } +Point.facings = { + [ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' }, + [ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' }, + [ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' }, + [ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' }, +} + +Point.directions = { + [ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' }, + [ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' }, +} + +Point.headings = { + [ 0 ] = Point.facings[0], + [ 1 ] = Point.facings[1], + [ 2 ] = Point.facings[2], + [ 3 ] = Point.facings[3], + [ 4 ] = Point.directions[4], + [ 5 ] = Point.directions[5], + east = Point.facings[0], + south = Point.facings[1], + west = Point.facings[2], + north = Point.facings[3], + up = Point.directions[4], + down = Point.directions[5], +} + +Point.EAST = 0 +Point.SOUTH = 1 +Point.WEST = 2 +Point.NORTH = 3 +Point.UP = 4 +Point.DOWN = 5 + function Point.copy(pt) return { x = pt.x, y = pt.y, z = pt.z } end @@ -122,6 +156,10 @@ end -- given a set of points, find the one taking the least moves function Point.closest(reference, pts) + if #pts == 1 then + return pts[1] + end + local lpt, lm -- lowest for _,pt in pairs(pts) do local m = Point.calculateMoves(reference, pt) @@ -148,21 +186,87 @@ end function Point.adjacentPoints(pt) local pts = { } -local headings = { - [ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' }, - [ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' }, - [ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' }, - [ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' }, - [ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' }, - [ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' } -} - for _, hi in pairs(headings) do + for i = 0, 5 do + local hi = Point.headings[i] table.insert(pts, { x = pt.x + hi.xd, y = pt.y + hi.yd, z = pt.z + hi.zd }) end return pts end +-- get the point nearest A that is in the direction of B +function Point.nearestTo(pta, ptb) + local heading + + if pta.x < ptb.x then + heading = 0 + elseif pta.z < ptb.z then + heading = 1 + elseif pta.x > ptb.x then + heading = 2 + elseif pta.z > ptb.z then + heading = 3 + elseif pta.y < ptb.y then + heading = 4 + elseif pta.y > ptb.y then + heading = 5 + end + + if heading then + return { + x = pta.x + Point.headings[heading].xd, + y = pta.y + Point.headings[heading].yd, + z = pta.z + Point.headings[heading].zd, + } + end + + return pta -- error ? +end + +function Point.rotate(pt, facing) + local x, z = pt.x, pt.z + if facing == 1 then + pt.x = z + pt.z = -x + elseif facing == 2 then + pt.x = -x + pt.z = -z + elseif facing == 3 then + pt.x = -z + pt.z = x + end +end + +function Point.makeBox(pt1, pt2) + return { + x = pt1.x, + y = pt1.y, + z = pt1.z, + ex = pt2.x, + ey = pt2.y, + ez = pt2.z, + } +end + +-- expand box to include point +function Point.expandBox(box, pt) + if pt.x < box.x then + box.x = pt.x + elseif pt.x > box.ex then + box.ex = pt.x + end + if pt.y < box.y then + box.y = pt.y + elseif pt.y > box.ey then + box.ey = pt.y + end + if pt.z < box.z then + box.z = pt.z + elseif pt.z > box.ez then + box.ez = pt.z + end +end + function Point.normalizeBox(box) return { x = math.min(box.x, box.ex), @@ -183,20 +287,6 @@ function Point.inBox(pt, box) pt.z <= box.ez end -function Point.rotate(pt, facing) - local x, z = pt.x, pt.z - if facing == 1 then - pt.x = z - pt.z = -x - elseif facing == 2 then - pt.x = -x - pt.z = -z - elseif facing == 3 then - pt.x = -z - pt.z = x - end -end - return Point --[[ diff --git a/sys/apis/turtle/pathfind.lua b/sys/apis/turtle/pathfind.lua index a39495a..30322ed 100644 --- a/sys/apis/turtle/pathfind.lua +++ b/sys/apis/turtle/pathfind.lua @@ -1,7 +1,7 @@ _G.requireInjector() -local Grid = require ("jumper.grid") -local Pathfinder = require ("jumper.pathfinder") +local Grid = require('jumper.grid') +local Pathfinder = require('jumper.pathfinder') local Point = require('point') local Util = require('util') @@ -19,74 +19,46 @@ end -- map shrinks/grows depending upon blocks encountered -- the map will encompass any blocks encountered, the turtle position, and the destination local function mapDimensions(dest, blocks, boundingBox, dests) - local sx, sz, sy = turtle.point.x, turtle.point.z, turtle.point.y - local ex, ez, ey = turtle.point.x, turtle.point.z, turtle.point.y + local box = Point.makeBox(turtle.point, turtle.point) - local function adjust(pt) - if pt.x < sx then - sx = pt.x - elseif pt.x > ex then - ex = pt.x - end - if pt.y < sy then - sy = pt.y - elseif pt.y > ey then - ey = pt.y - end - if pt.z < sz then - sz = pt.z - elseif pt.z > ez then - ez = pt.z - end - end - - adjust(dest) + Point.expandBox(box, dest) for _,d in pairs(dests) do - adjust(d) + Point.expandBox(box, d) end for _,b in pairs(blocks) do - adjust(b) + Point.expandBox(box, b) end -- expand one block out in all directions if boundingBox then - sx = math.max(sx - 1, boundingBox.x) - sz = math.max(sz - 1, boundingBox.z) - sy = math.max(sy - 1, boundingBox.y) - ex = math.min(ex + 1, boundingBox.ex) - ez = math.min(ez + 1, boundingBox.ez) - ey = math.min(ey + 1, boundingBox.ey) + box.x = math.max(box.x - 1, boundingBox.x) + box.z = math.max(box.z - 1, boundingBox.z) + box.y = math.max(box.y - 1, boundingBox.y) + box.ex = math.min(box.ex + 1, boundingBox.ex) + box.ez = math.min(box.ez + 1, boundingBox.ez) + box.ey = math.min(box.ey + 1, boundingBox.ey) else - sx = sx - 1 - sz = sz - 1 - sy = sy - 1 - ex = ex + 1 - ez = ez + 1 - ey = ey + 1 + box.x = box.x - 1 + box.z = box.z - 1 + box.y = box.y - 1 + box.ex = box.ex + 1 + box.ez = box.ez + 1 + box.ey = box.ey + 1 end - return { - ex = ex, - ez = ez, - ey = ey, - x = sx, - z = sz, - y = sy - } + return box end local function nodeToPoint(node) - return { x = node:getX(), z = node:getZ(), y = node:getY() } + return { x = node.x, y = node.y, z = node.z, heading = node.heading } end -local heuristic = function(n, node) - local m, h = Point.calculateMoves( - { x = node._x, y = node._y, z = node._z, heading = node._heading }, - { x = n._x, y = n._y, z = n._z, heading = n._heading }) - - return m, h +local function heuristic(n, node) + return Point.calculateMoves(node, n) +-- { x = node.x, y = node.y, z = node.z, heading = node.heading }, +-- { x = n.x, y = n.y, z = n.z, heading = n.heading }) end local function dimsAreEqual(d1, d2) @@ -123,9 +95,6 @@ local function addSensorBlocks(blocks, sblocks) end local function selectDestination(pts, box, grid) - if #pts == 1 then - return pts[1] - end while #pts > 0 do local pt = Point.closest(turtle.point, pts) if box and not Point.inBox(pt, box) then @@ -143,16 +112,15 @@ local function pathTo(dest, options) local blocks = options.blocks or turtle.getState().blocks or { } local dests = options.dest or { dest } -- support alternative destinations local box = options.box or turtle.getState().box - - local lastDim = nil - local grid = nil + local lastDim + local grid if box then box = Point.normalizeBox(box) end -- Creates a pathfinder object - local myFinder = Pathfinder(heuristic) + local finder = Pathfinder(heuristic) while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do @@ -163,7 +131,7 @@ local function pathTo(dest, options) if not lastDim or not dimsAreEqual(dim, lastDim) then -- Creates a grid object grid = Grid(dim) - myFinder:setGrid(grid) + finder:setGrid(grid) lastDim = dim end @@ -173,7 +141,6 @@ local function pathTo(dest, options) dest = selectDestination(dests, box, grid) if not dest then --- error('failed to reach destination') return false, 'failed to reach destination' end if turtle.point.x == dest.x and turtle.point.z == dest.z and turtle.point.y == dest.y then @@ -185,24 +152,44 @@ local function pathTo(dest, options) local endPt = dest -- Calculates the path, and its length - local path = myFinder:getPath( + local path = finder:getPath( startPt.x, startPt.y, startPt.z, turtle.point.heading, endPt.x, endPt.y, endPt.z, dest.heading) if not path then Util.removeByValue(dests, dest) else + path:filter() + for node in path:nodes() do local pt = nodeToPoint(node) - if turtle.abort then + if turtle.isAborted() then return false, 'aborted' end +--if this is the next to last node +--and we are traveling up or down, then the +--heading for this node should be the heading of the last node +--or, maybe.. +--if last node is up or down (or either?) + -- use single turn method so the turtle doesn't turn around - -- when encountering obstacles -- IS THIS RIGHT ?? - if not turtle.gotoSingleTurn(pt.x, pt.z, pt.y) then - table.insert(blocks, pt) + -- when encountering obstacles + if not turtle.gotoSingleTurn(pt.x, pt.z, pt.y, pt.heading) then + local bpt = Point.nearestTo(turtle.point, pt) + + table.insert(blocks, bpt) + -- really need to check if the block we ran into was a turtle. + -- if so, this block should be temporary (1-2 secs) + + --local side = turtle.getSide(turtle.point, pt) + --if turtle.isTurtleAtSide(side) then + -- pt.timestamp = os.clock() + ? + --end + -- if dim has not changed, then need to update grid with + -- walkable = nil (after time has elapsed) + --if device.turtlesensorenvironment then -- addSensorBlocks(blocks, device.turtlesensorenvironment.sonicScan()) --end diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index 1f566e5..4cf9d76 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -3085,6 +3085,9 @@ function UI.Dialog:setParent() if not self.width then self.width = self.parent.width - 11 end + if self.width > self.parent.width then + self.width = self.parent.width + end self.x = math.floor((self.parent.width - self.width) / 2) + 1 self.y = math.floor((self.parent.height - self.height) / 2) + 1 UI.Page.setParent(self) diff --git a/sys/extensions/tgps.lua b/sys/extensions/tgps.lua deleted file mode 100644 index efb7288..0000000 --- a/sys/extensions/tgps.lua +++ /dev/null @@ -1,60 +0,0 @@ -local turtle = _G.turtle - -if not turtle or turtle.enableGPS then - return -end - -_G.requireInjector() - -local GPS = require('gps') -local Config = require('config') - -function turtle.enableGPS(timeout) - if turtle.point.gps then - return turtle.point - end - - local pt = GPS.getPointAndHeading(timeout) - if pt then - turtle.setPoint(pt, true) - return turtle.point - end -end - -function turtle.gotoGPSHome() - local config = { } - Config.load('gps', config) - - if config.home then - if turtle.enableGPS() then - turtle.pathfind(config.home) - end - end -end - -function turtle.setGPSHome() - local config = { } - Config.load('gps', config) - - if turtle.point.gps then - config.home = turtle.point - Config.update('gps', config) - else - local pt = GPS.getPoint() - if pt then - local originalHeading = turtle.point.heading - local heading = GPS.getHeading() - if heading then - local turns = (turtle.point.heading - originalHeading) % 4 - pt.heading = (heading - turns) % 4 - config.home = pt - Config.update('gps', config) - - pt = GPS.getPoint() - pt.heading = heading - turtle.setPoint(pt, true) - turtle.gotoPoint(config.home) - end - end - end -end diff --git a/sys/extensions/tl3.lua b/sys/extensions/tl3.lua index d51940f..fcf3d5f 100644 --- a/sys/extensions/tl3.lua +++ b/sys/extensions/tl3.lua @@ -1,8 +1,4 @@ -local os = _G.os -local peripheral = _G.peripheral -local turtle = _G.turtle - -if not turtle or turtle.getPoint then +if not _G.turtle then return end @@ -13,16 +9,25 @@ local synchronized = require('sync') local Util = require('util') local Pathing = require('turtle.pathfind') +local os = _G.os +local peripheral = _G.peripheral +local turtle = _G.turtle + local function noop() end +local headings = Point.headings +local state = { + status = 'idle', + abort = false, +} turtle.pathfind = Pathing.pathfind turtle.point = { x = 0, y = 0, z = 0, heading = 0 } -turtle.status = 'idle' -turtle.abort = false -local state = { } -function turtle.getPoint() return turtle.point end -function turtle.getState() return state end +function turtle.getPoint() return turtle.point end +function turtle.getState() return state end +function turtle.isAborted() return state.abort end +function turtle.getStatus() return state.status end +function turtle.setStatus(s) state.status = s end local function _defaultMove(action) while not action.move() do @@ -45,15 +50,13 @@ function turtle.setPoint(pt, isGPS) end function turtle.resetState() - --turtle.abort = false -- should be part of state - --turtle.status = 'idle' -- should be part of state + state.abort = false + state.status = 'idle' state.attackPolicy = noop state.digPolicy = noop state.movePolicy = _defaultMove state.moveCallback = noop Pathing.reset() - -turtle.abort = false return true end @@ -63,11 +66,8 @@ function turtle.reset() turtle.point.z = 0 turtle.point.heading = 0 -- should be facing turtle.point.gps = false - turtle.abort = false -- should be part of state - --turtle.status = 'idle' -- should be part of state turtle.resetState() - return true end @@ -126,31 +126,7 @@ function turtle.getAction(direction) return actions[direction] end --- [[ Heading data ]] -- -local headings = { - [ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' }, - [ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' }, - [ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' }, - [ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' }, - [ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' }, - [ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' } -} - -local namedHeadings = { - east = headings[0], - south = headings[1], - west = headings[2], - north = headings[3], - up = headings[4], - down = headings[5] -} - -function turtle.getHeadings() return headings end - function turtle.getHeadingInfo(heading) - if heading and type(heading) == 'string' then - return namedHeadings[heading] - end heading = heading or turtle.point.heading return headings[heading] end @@ -307,13 +283,13 @@ turtle.movePolicies = { if action.side == 'back' then return false end - local oldStatus = turtle.status + local oldStatus = state.status print('assured move: stuck') - turtle.status = 'stuck' + state.status = 'stuck' repeat os.sleep(1) until _defaultMove(action) - turtle.status = oldStatus + state.status = oldStatus end return true end, @@ -381,18 +357,17 @@ function turtle.turnAround() return turtle.point end --- combine with setHeading -function turtle.setNamedHeading(headingName) - local headingInfo = namedHeadings[headingName] - if headingInfo then - return turtle.setHeading(headingInfo.heading) - end - return false, 'Invalid heading' -end - function turtle.setHeading(heading) if not heading then - return + return false, 'Invalid heading' + end + + if type(heading) == 'string' then + local hi = headings[heading] + if not hi then + return false, 'Invalid heading' + end + heading = hi.heading end heading = heading % 4 @@ -484,7 +459,6 @@ function turtle.back() end function turtle.moveTowardsX(dx) - local direction = dx - turtle.point.x local move @@ -508,7 +482,6 @@ function turtle.moveTowardsX(dx) end function turtle.moveTowardsZ(dz) - local direction = dz - turtle.point.z local move @@ -534,7 +507,6 @@ end -- [[ go ]] -- -- 1 turn goto (going backwards if possible) function turtle.gotoSingleTurn(dx, dz, dy, dh) - dy = dy or turtle.point.y local function gx() @@ -593,7 +565,6 @@ function turtle.gotoSingleTurn(dx, dz, dy, dh) end local function gotoEx(dx, dz, dy) - -- determine the heading to ensure the least amount of turns -- first check is 1 turn needed - remaining require 2 turns if turtle.point.heading == 0 and turtle.point.x <= dx or @@ -628,7 +599,6 @@ end -- fallback goto - will turn around if was previously moving backwards local function gotoMultiTurn(dx, dz, dy) - if gotoEx(dx, dz, dy) then return true end @@ -659,18 +629,18 @@ local function gotoMultiTurn(dx, dz, dy) end function turtle.gotoPoint(pt) - return turtle.goto(pt.x, pt.z, pt.y, pt.heading) + return turtle._goto(pt.x, pt.z, pt.y, pt.heading) end -- go backwards - turning around if necessary to fight mobs / break blocks function turtle.goback() local hi = headings[turtle.point.heading] - return turtle.goto(turtle.point.x - hi.xd, turtle.point.z - hi.zd, turtle.point.y, turtle.point.heading) + return turtle._goto(turtle.point.x - hi.xd, turtle.point.z - hi.zd, turtle.point.y, turtle.point.heading) end function turtle.gotoYfirst(pt) - if turtle.gotoY(pt.y) then - if turtle.goto(pt.x, pt.z, nil, pt.heading) then + if turtle._gotoY(pt.y) then + if turtle._goto(pt.x, pt.z, nil, pt.heading) then turtle.setHeading(pt.heading) return true end @@ -678,7 +648,7 @@ function turtle.gotoYfirst(pt) end function turtle.gotoYlast(pt) - if turtle.goto(pt.x, pt.z, nil, pt.heading) then + if turtle._goto(pt.x, pt.z, nil, pt.heading) then if turtle.gotoY(pt.y) then turtle.setHeading(pt.heading) return true @@ -686,7 +656,7 @@ function turtle.gotoYlast(pt) end end -function turtle.goto(dx, dz, dy, dh) +function turtle._goto(dx, dz, dy, dh) if not turtle.gotoSingleTurn(dx, dz, dy, dh) then if not gotoMultiTurn(dx, dz, dy) then return false @@ -697,7 +667,7 @@ function turtle.goto(dx, dz, dy, dh) end -- avoid lint errors -turtle._goto = turtle.goto +turtle['goto'] = turtle._goto function turtle.gotoX(dx) turtle.headTowardsX(dx) @@ -738,7 +708,6 @@ end -- [[ Slot management ]] -- function turtle.getSlot(indexOrId, slots) - if type(indexOrId) == 'string' then slots = slots or turtle.getInventory() local _,c = string.gsub(indexOrId, ':', '') @@ -774,7 +743,6 @@ function turtle.getSlot(indexOrId, slots) end function turtle.select(indexOrId) - if type(indexOrId) == 'number' then return turtle.native.select(indexOrId) end @@ -925,7 +893,6 @@ function turtle.getItemCount(idOrName) end function turtle.equip(side, item) - if item then if not turtle.select(item) then return false, 'Unable to equip ' .. item @@ -938,6 +905,7 @@ function turtle.equip(side, item) return turtle.equipRight() end +-- [[ ]] -- function turtle.run(fn, ...) local args = { ... } local s, m @@ -947,25 +915,22 @@ function turtle.run(fn, ...) end synchronized(turtle, function() - turtle.abort = false - turtle.status = 'busy' turtle.resetState() s, m = pcall(function() fn(unpack(args)) end) - turtle.abort = false - turtle.status = 'idle' + turtle.resetState() if not s and m then - printError(m) + _G.printError(m) end end) return s, m end -function turtle.abortAction() - --if turtle.status ~= 'idle' then - turtle.abort = true +function turtle.abort(abort) + state.abort = abort + if abort then os.queueEvent('turtle_abort') - --end + end end -- [[ Pathing ]] -- @@ -989,9 +954,7 @@ function turtle.faceAgainst(pt, options) -- 4 sided options = options or { } options.dest = { } - for i = 0, 3 do - local hi = turtle.getHeadingInfo(i) - + for hi in pairs(Point.facings) do table.insert(options.dest, { x = pt.x + hi.xd, z = pt.z + hi.zd, @@ -1104,10 +1067,14 @@ local function _actionUpAt(action, pt, ...) end end -local function _actionXXXAt(action, pt, dir, ...) - local reversed = +local function _actionPlaceAt(action, pt, name, dir, facing) + if not dir then + return _actionAt(action, pt, name) + end + + local reversed = { [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, } - dir = reversed[dir] + dir = reversed[headings[dir].heading] local apt = { x = pt.x + headings[dir].xd, y = pt.y + headings[dir].yd, z = pt.z + headings[dir].zd, } @@ -1118,55 +1085,108 @@ local function _actionXXXAt(action, pt, dir, ...) apt.heading = (dir + 2) % 4 direction = 'forward' elseif dir == 4 then - apt.heading = pt.heading + apt.heading = facing direction = 'down' elseif dir == 5 then - apt.heading = pt.heading + apt.heading = facing direction = 'up' end if turtle.pathfind(apt) then - return action[direction](...) + return action[direction](name) end end -function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end -function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end -function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end -function turtle.detectUpAt(pt) return _actionUpAt(actionsAt.detect, pt) end +function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end +function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end +function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end +function turtle.detectUpAt(pt) return _actionUpAt(actionsAt.detect, pt) end -function turtle.digAt(pt) return _actionAt(actionsAt.dig, pt) end -function turtle.digDownAt(pt) return _actionDownAt(actionsAt.dig, pt) end -function turtle.digForwardAt(pt) return _actionForwardAt(actionsAt.dig, pt) end -function turtle.digUpAt(pt) return _actionUpAt(actionsAt.dig, pt) end +function turtle.digAt(pt) return _actionAt(actionsAt.dig, pt) end +function turtle.digDownAt(pt) return _actionDownAt(actionsAt.dig, pt) end +function turtle.digForwardAt(pt) return _actionForwardAt(actionsAt.dig, pt) end +function turtle.digUpAt(pt) return _actionUpAt(actionsAt.dig, pt) end -function turtle.attackAt(pt) return _actionAt(actionsAt.attack, pt) end -function turtle.attackDownAt(pt) return _actionDownAt(actionsAt.attack, pt) end -function turtle.attackForwardAt(pt) return _actionForwardAt(actionsAt.attack, pt) end -function turtle.attackUpAt(pt) return _actionUpAt(actionsAt.attack, pt) end +function turtle.attackAt(pt) return _actionAt(actionsAt.attack, pt) end +function turtle.attackDownAt(pt) return _actionDownAt(actionsAt.attack, pt) end +function turtle.attackForwardAt(pt) return _actionForwardAt(actionsAt.attack, pt) end +function turtle.attackUpAt(pt) return _actionUpAt(actionsAt.attack, pt) end -function turtle.placeAt(pt, arg) return _actionAt(actionsAt.place, pt, arg) end -function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end -function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end -function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end -function turtle.placeXXXAt(pt, dir, arg) return _actionXXXAt(actionsAt.place, pt, dir, arg) end +function turtle.placeAt(pt, arg, dir) return _actionPlaceAt(actionsAt.place, pt, arg, dir) end +function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end +function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end +function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end -function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end -function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end -function turtle.dropForwardAt(pt, ...) return _actionForwardAt(actionsAt.drop, pt, ...) end -function turtle.dropUpAt(pt, ...) return _actionUpAt(actionsAt.drop, pt, ...) end +function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end +function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end +function turtle.dropForwardAt(pt, ...) return _actionForwardAt(actionsAt.drop, pt, ...) end +function turtle.dropUpAt(pt, ...) return _actionUpAt(actionsAt.drop, pt, ...) end -function turtle.suckAt(pt, qty) return _actionAt(actionsAt.suck, pt, qty or 64) end -function turtle.suckDownAt(pt, qty) return _actionDownAt(actionsAt.suck, pt, qty or 64) end -function turtle.suckForwardAt(pt, qty) return _actionForwardAt(actionsAt.suck, pt, qty or 64) end -function turtle.suckUpAt(pt, qty) return _actionUpAt(actionsAt.suck, pt, qty or 64) end +function turtle.suckAt(pt, qty) return _actionAt(actionsAt.suck, pt, qty or 64) end +function turtle.suckDownAt(pt, qty) return _actionDownAt(actionsAt.suck, pt, qty or 64) end +function turtle.suckForwardAt(pt, qty) return _actionForwardAt(actionsAt.suck, pt, qty or 64) end +function turtle.suckUpAt(pt, qty) return _actionUpAt(actionsAt.suck, pt, qty or 64) end -function turtle.compareAt(pt) return _actionAt(actionsAt.compare, pt) end -function turtle.compareDownAt(pt) return _actionDownAt(actionsAt.compare, pt) end -function turtle.compareForwardAt(pt) return _actionForwardAt(actionsAt.compare, pt) end -function turtle.compareUpAt(pt) return _actionUpAt(actionsAt.compare, pt) end +function turtle.compareAt(pt) return _actionAt(actionsAt.compare, pt) end +function turtle.compareDownAt(pt) return _actionDownAt(actionsAt.compare, pt) end +function turtle.compareForwardAt(pt) return _actionForwardAt(actionsAt.compare, pt) end +function turtle.compareUpAt(pt) return _actionUpAt(actionsAt.compare, pt) end -function turtle.inspectAt(pt) return _actionAt(actionsAt.inspect, pt) end -function turtle.inspectDownAt(pt) return _actionDownAt(actionsAt.inspect, pt) end -function turtle.inspectForwardAt(pt) return _actionForwardAt(actionsAt.inspect, pt) end -function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, pt) end +function turtle.inspectAt(pt) return _actionAt(actionsAt.inspect, pt) end +function turtle.inspectDownAt(pt) return _actionDownAt(actionsAt.inspect, pt) end +function turtle.inspectForwardAt(pt) return _actionForwardAt(actionsAt.inspect, pt) end +function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, pt) end + +-- [[ GPS ]] -- +local GPS = require('gps') +local Config = require('config') + +function turtle.enableGPS(timeout) + --if turtle.point.gps then + -- return turtle.point + --end + + local pt = GPS.getPointAndHeading(timeout) + if pt then + turtle.setPoint(pt, true) + return turtle.point + end +end + +function turtle.gotoGPSHome() + local config = { } + Config.load('gps', config) + + if config.home then + if turtle.enableGPS() then + turtle.pathfind(config.home) + end + end +end + +function turtle.setGPSHome() + local config = { } + Config.load('gps', config) + + if turtle.point.gps then + config.home = turtle.point + Config.update('gps', config) + else + local pt = GPS.getPoint() + if pt then + local originalHeading = turtle.point.heading + local heading = GPS.getHeading() + if heading then + local turns = (turtle.point.heading - originalHeading) % 4 + pt.heading = (heading - turns) % 4 + config.home = pt + Config.update('gps', config) + + pt = GPS.getPoint() + pt.heading = heading + turtle.setPoint(pt, true) + turtle.gotoPoint(config.home) + end + end + end +end diff --git a/sys/network/snmp.lua b/sys/network/snmp.lua index 5fd917d..3eeb919 100644 --- a/sys/network/snmp.lua +++ b/sys/network/snmp.lua @@ -90,7 +90,7 @@ local function snmpConnection(socket) } if turtle then info.fuel = turtle.getFuelLevel() - info.status = turtle.status + info.status = turtle.getStatus() end socket:write(info) end @@ -144,7 +144,7 @@ local function sendInfo() info.uptime = math.floor(os.clock()) if turtle then info.fuel = turtle.getFuelLevel() - info.status = turtle.status + info.status = turtle.getStatus() info.point = turtle.point info.inventory = turtle.getInventory() info.slotIndex = turtle.getSelectedSlot() @@ -166,7 +166,7 @@ Event.onInterval(10, function() end) Event.on('turtle_response', function() - if turtle.status ~= info.status or + if turtle.getStatus() ~= info.status or turtle.fuel ~= info.fuel then sendInfo() end