1
0
mirror of https://github.com/kepler155c/opus synced 2024-12-24 23:50:26 +00:00

refactor + cleanup

This commit is contained in:
kepler155c@gmail.com 2017-10-27 20:24:48 -04:00
parent cac15722b8
commit 1b9450017d
12 changed files with 371 additions and 430 deletions

View File

@ -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 <a href="http://www.policyalmanac.org/games/binaryHeaps.htm">binary heap</a>
-- 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

View File

@ -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.<br/>
-- This class is callable.
-- Therefore,_ <code>Node(...)</code> _acts as a shortcut to_ <code>Node:new(...)</code>.
-- @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

View File

@ -7,27 +7,18 @@
--
if (...) then
--- The `Path` class.<br/>
-- This class is callable.
-- Therefore, <em><code>Path(...)</code></em> acts as a shortcut to <em><code>Path:new(...)</code></em>.
-- @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(...)

View File

@ -25,18 +25,9 @@ if (...) then
{x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]]
}
--- The `Grid` class.<br/>
-- This class is callable.
-- Therefore,_ <code>Grid(...)</code> _acts as a shortcut to_ <code>Grid:new(...)</code>.
-- @type Grid
local Grid = {}
Grid.__index = Grid
--- Inits a new `grid`
-- @class function
-- @tparam table Map dimensions
-- or a `string` with line-break chars (<code>\n</code> or <code>\r</code>) 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 <x,y> 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

View File

@ -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.
-- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li>
-- @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.<br/>
-- This class is callable.
-- Therefore,_ <code>Pathfinder(...)</code> _acts as a shortcut to_ <code>Pathfinder:new(...)</code>.
-- @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),

View File

@ -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
--]]

View File

@ -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
--[[

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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