diff --git a/sys/apis/event.lua b/sys/apis/event.lua
index 26f316c..9e8d90b 100644
--- a/sys/apis/event.lua
+++ b/sys/apis/event.lua
@@ -24,9 +24,9 @@ function Routine:terminate()
end
function Routine:resume(event, ...)
- if coroutine.status(self.co) == 'running' then
- return
- end
+ --if coroutine.status(self.co) == 'running' then
+ --return
+ --end
if not self.co then
error('Cannot resume a dead routine')
diff --git a/sys/apis/jumper/core/assert.lua b/sys/apis/jumper/core/assert.lua
deleted file mode 100644
index dc5430f..0000000
--- a/sys/apis/jumper/core/assert.lua
+++ /dev/null
@@ -1,105 +0,0 @@
--- Various assertion function for API methods argument-checking
-
-if (...) then
-
- -- Dependancies
- local _PATH = (...):gsub('%.core.assert$','')
- local Utils = require (_PATH .. '.core.utils')
-
- -- Local references
- local lua_type = type
- local floor = math.floor
- local concat = table.concat
- local next = next
- local pairs = pairs
- local getmetatable = getmetatable
-
- -- Is I an integer ?
- local function isInteger(i)
- return lua_type(i) ==('number') and (floor(i)==i)
- end
-
- -- Override lua_type to return integers
- local function type(v)
- return isInteger(v) and 'int' or lua_type(v)
- end
-
- -- Does the given array contents match a predicate type ?
- local function arrayContentsMatch(t,...)
- local n_count = Utils.arraySize(t)
- if n_count < 1 then return false end
- local init_count = t[0] and 0 or 1
- local n_count = (t[0] and n_count-1 or n_count)
- local types = {...}
- if types then types = concat(types) end
- for i=init_count,n_count,1 do
- if not t[i] then return false end
- if types then
- if not types:match(type(t[i])) then return false end
- end
- end
- return true
- end
-
- -- Checks if arg is a valid array map
- local function isMap(m)
- if not arrayContentsMatch(m, 'table') then return false end
- local lsize = Utils.arraySize(m[next(m)])
- for k,v in pairs(m) do
- if not arrayContentsMatch(m[k], 'string', 'int') then return false end
- if Utils.arraySize(v)~=lsize then return false end
- end
- return true
- end
-
- -- Checks if s is a valid string map
- local function isStringMap(s)
- if lua_type(s) ~= 'string' then return false end
- local w
- for row in s:gmatch('[^\n\r]+') do
- if not row then return false end
- w = w or #row
- if w ~= #row then return false end
- end
- return true
- end
-
- -- Does instance derive straight from class
- local function derives(instance, class)
- return getmetatable(instance) == class
- end
-
- -- Does instance inherits from class
- local function inherits(instance, class)
- return (getmetatable(getmetatable(instance)) == class)
- end
-
- -- Is arg a boolean
- local function isBoolean(b)
- return (b==true or b==false)
- end
-
- -- Is arg nil ?
- local function isNil(n)
- return (n==nil)
- end
-
- local function matchType(value, types)
- return types:match(type(value))
- end
-
- return {
- arrayContentsMatch = arrayContentsMatch,
- derives = derives,
- inherits = inherits,
- isInteger = isInteger,
- isBool = isBoolean,
- isMap = isMap,
- isStrMap = isStringMap,
- isOutOfRange = isOutOfRange,
- isNil = isNil,
- type = type,
- matchType = matchType
- }
-
-end
diff --git a/sys/apis/jumper/core/heuristics.lua b/sys/apis/jumper/core/heuristics.lua
deleted file mode 100644
index a7c73bc..0000000
--- a/sys/apis/jumper/core/heuristics.lua
+++ /dev/null
@@ -1,98 +0,0 @@
---- Heuristic functions for search algorithms.
--- A distance heuristic
--- provides an *estimate of the optimal distance cost* from a given location to a target.
--- As such, it guides the pathfinder to the goal, helping it to decide which route is the best.
---
--- This script holds the definition of some built-in heuristics available through jumper.
---
--- Distance functions are internally used by the `pathfinder` to evaluate the optimal path
--- from the start location to the goal. These functions share the same prototype:
--- local function myHeuristic(nodeA, nodeB)
--- -- function body
--- end
--- Jumper features some built-in distance heuristics, namely `MANHATTAN`, `EUCLIDIAN`, `DIAGONAL`, `CARDINTCARD`.
--- You can also supply your own heuristic function, following the same template as above.
-
-
-local abs = math.abs
-local sqrt = math.sqrt
-local sqrt2 = sqrt(2)
-local max, min = math.max, math.min
-
-local Heuristics = {}
- --- Manhattan distance.
- --
This heuristic is the default one being used by the `pathfinder` object.
- --
Evaluates as distance = |dx|+|dy|
- -- @class function
- -- @tparam node nodeA a node
- -- @tparam node nodeB another node
- -- @treturn number the distance from __nodeA__ to __nodeB__
- -- @usage
- -- -- First method
- -- pathfinder:setHeuristic('MANHATTAN')
- -- -- Second method
- -- local Distance = require ('jumper.core.heuristics')
- -- pathfinder:setHeuristic(Distance.MANHATTAN)
- function Heuristics.MANHATTAN(nodeA, nodeB)
- local dx = abs(nodeA._x - nodeB._x)
- local dy = abs(nodeA._y - nodeB._y)
- local dz = abs(nodeA._z - nodeB._z)
- return (dx + dy + dz)
- end
-
- --- Euclidian distance.
- --
Evaluates as distance = squareRoot(dx*dx+dy*dy)
- -- @class function
- -- @tparam node nodeA a node
- -- @tparam node nodeB another node
- -- @treturn number the distance from __nodeA__ to __nodeB__
- -- @usage
- -- -- First method
- -- pathfinder:setHeuristic('EUCLIDIAN')
- -- -- Second method
- -- local Distance = require ('jumper.core.heuristics')
- -- pathfinder:setHeuristic(Distance.EUCLIDIAN)
- function Heuristics.EUCLIDIAN(nodeA, nodeB)
- local dx = nodeA._x - nodeB._x
- local dy = nodeA._y - nodeB._y
- local dz = nodeA._z - nodeB._z
- return sqrt(dx*dx+dy*dy+dz*dz)
- end
-
- --- Diagonal distance.
- --
Evaluates as distance = max(|dx|, abs|dy|)
- -- @class function
- -- @tparam node nodeA a node
- -- @tparam node nodeB another node
- -- @treturn number the distance from __nodeA__ to __nodeB__
- -- @usage
- -- -- First method
- -- pathfinder:setHeuristic('DIAGONAL')
- -- -- Second method
- -- local Distance = require ('jumper.core.heuristics')
- -- pathfinder:setHeuristic(Distance.DIAGONAL)
- function Heuristics.DIAGONAL(nodeA, nodeB)
- local dx = abs(nodeA._x - nodeB._x)
- local dy = abs(nodeA._y - nodeB._y)
- return max(dx,dy)
- end
-
- --- Cardinal/Intercardinal distance.
- --
Evaluates as distance = min(dx, dy)*squareRoot(2) + max(dx, dy) - min(dx, dy)
- -- @class function
- -- @tparam node nodeA a node
- -- @tparam node nodeB another node
- -- @treturn number the distance from __nodeA__ to __nodeB__
- -- @usage
- -- -- First method
- -- pathfinder:setHeuristic('CARDINTCARD')
- -- -- Second method
- -- local Distance = require ('jumper.core.heuristics')
- -- pathfinder:setHeuristic(Distance.CARDINTCARD)
- function Heuristics.CARDINTCARD(nodeA, nodeB)
- local dx = abs(nodeA._x - nodeB._x)
- local dy = abs(nodeA._y - nodeB._y)
- return min(dx,dy) * sqrt2 + max(dx,dy) - min(dx,dy)
- end
-
-return Heuristics
\ No newline at end of file
diff --git a/sys/apis/jumper/core/lookuptable.lua b/sys/apis/jumper/core/lookuptable.lua
deleted file mode 100644
index 74cf997..0000000
--- a/sys/apis/jumper/core/lookuptable.lua
+++ /dev/null
@@ -1,32 +0,0 @@
-local addNode(self, node, nextNode, ed)
- if not self._pathDB[node] then self._pathDB[node] = {} end
- self._pathDB[node][ed] = (nextNode == ed and node or nextNode)
-end
-
--- Path lookupTable
-local lookupTable = {}
-lookupTable.__index = lookupTable
-
-function lookupTable:new()
- local lut = {_pathDB = {}}
- return setmetatable(lut, lookupTable)
-end
-
-function lookupTable:addPath(path)
- local st, ed = path._nodes[1], path._nodes[#path._nodes]
- for node, count in path:nodes() do
- local nextNode = path._nodes[count+1]
- if nextNode then addNode(self, node, nextNode, ed) end
- end
-end
-
-function lookupTable:hasPath(nodeA, nodeB)
- local found
- found = self._pathDB[nodeA] and self._path[nodeA][nodeB]
- if found then return true, true end
- found = self._pathDB[nodeB] and self._path[nodeB][nodeA]
- if found then return true, false end
- return false
-end
-
-return lookupTable
\ No newline at end of file
diff --git a/sys/apis/jumper/core/node.lua b/sys/apis/jumper/core/node.lua
index 733978c..b0b9284 100644
--- a/sys/apis/jumper/core/node.lua
+++ b/sys/apis/jumper/core/node.lua
@@ -4,14 +4,12 @@
-- and then cached within the `grid`.
--
-- In the following implementation, nodes can be compared using the `<` operator. The comparison is
--- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search
+-- 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
- local assert = assert
-
--- The `Node` class.
-- This class is callable.
-- Therefore,_ Node(...)
_acts as a shortcut to_ Node:new(...)
.
@@ -26,7 +24,7 @@ if (...) then
-- @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, _clearance = {}}, Node)
+ return setmetatable({_x = x, _y = y, _z = z }, Node)
end
-- Enables the use of operator '<' to compare nodes.
@@ -36,48 +34,24 @@ if (...) then
--- Returns x-coordinate of a `node`
-- @class function
-- @treturn number the x-coordinate of the `node`
- -- @usage local x = node:getX()
+ -- @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()
+ -- @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()
+ -- @usage local x, y = node:getPos()
function Node:getPos() return self._x, self._y, self._z end
-
- --- Returns the amount of true [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)
- -- for a given `node`
- -- @class function
- -- @tparam string|int|func walkable the value for walkable locations in the collision map array.
- -- @treturn int the clearance of the `node`
- -- @usage
- -- -- Assuming walkable was 0
- -- local clearance = node:getClearance(0)
- function Node:getClearance(walkable)
- return self._clearance[walkable]
- end
-
- --- Removes the clearance value for a given walkable.
- -- @class function
- -- @tparam string|int|func walkable the value for walkable locations in the collision map array.
- -- @treturn node self (the calling `node` itself, can be chained)
- -- @usage
- -- -- Assuming walkable is defined
- -- node:removeClearance(walkable)
- function Node:removeClearance(walkable)
- self._clearance[walkable] = nil
- return self
- 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.
@@ -91,10 +65,10 @@ if (...) then
self._opened, self._closed, self._parent = nil, nil, nil
return self
end
-
+
return setmetatable(Node,
- {__call = function(self,...)
- return Node:new(...)
+ {__call = function(_,...)
+ return Node:new(...)
end}
)
end
\ No newline at end of file
diff --git a/sys/apis/jumper/core/path.lua b/sys/apis/jumper/core/path.lua
index 826c5e4..e3bb98e 100644
--- a/sys/apis/jumper/core/path.lua
+++ b/sys/apis/jumper/core/path.lua
@@ -6,17 +6,7 @@
-- It should normally not be used explicitely, yet it remains fully accessible.
--
-
if (...) then
-
- -- Dependencies
- local _PATH = (...):match('(.+)%.path$')
- local Heuristic = require (_PATH .. '.heuristics')
-
- -- Local references
- local abs, max = math.abs, math.max
- local t_insert, t_remove = table.insert, table.remove
-
--- The `Path` class.
-- This class is callable.
-- Therefore, Path(...)
acts as a shortcut to Path:new(...)
.
@@ -42,8 +32,8 @@ if (...) then
-- for node, count in p:iter() do
-- ...
-- end
- function Path:iter()
- local i,pathLen = 1,#self._nodes
+ function Path:nodes()
+ local i = 1
return function()
if self._nodes[i] then
i = i+1
@@ -51,150 +41,9 @@ if (...) then
end
end
end
-
- --- Iterates on each single `node` along a `path`. At each step of iteration,
- -- returns a `node` plus a count value. Alias for @{Path:iter}
- -- @class function
- -- @name Path:nodes
- -- @treturn node a `node`
- -- @treturn int the count for the number of nodes
- -- @see Path:iter
- -- @usage
- -- for node, count in p:nodes() do
- -- ...
- -- end
- Path.nodes = Path.iter
-
- --- Evaluates the `path` length
- -- @class function
- -- @treturn number the `path` length
- -- @usage local len = p:getLength()
- function Path:getLength()
- local len = 0
- for i = 2,#self._nodes do
- len = len + Heuristic.EUCLIDIAN(self._nodes[i], self._nodes[i-1])
- end
- return len
- end
-
- --- Counts the number of steps.
- -- Returns the number of waypoints (nodes) in the current path.
- -- @class function
- -- @tparam node node a node to be added to the path
- -- @tparam[opt] int index the index at which the node will be inserted. If omitted, the node will be appended after the last node in the path.
- -- @treturn path self (the calling `path` itself, can be chained)
- -- @usage local nSteps = p:countSteps()
- function Path:addNode(node, index)
- index = index or #self._nodes+1
- t_insert(self._nodes, index, node)
- return self
- end
-
-
- --- `Path` filling modifier. Interpolates between non contiguous nodes along a `path`
- -- to build a fully continuous `path`. This maybe useful when using search algorithms such as Jump Point Search.
- -- Does the opposite of @{Path:filter}
- -- @class function
- -- @treturn path self (the calling `path` itself, can be chained)
- -- @see Path:filter
- -- @usage p:fill()
- function Path:fill()
- local i = 2
- local xi,yi,dx,dy
- local N = #self._nodes
- local incrX, incrY
- while true do
- xi,yi = self._nodes[i]._x,self._nodes[i]._y
- dx,dy = xi-self._nodes[i-1]._x,yi-self._nodes[i-1]._y
- if (abs(dx) > 1 or abs(dy) > 1) then
- incrX = dx/max(abs(dx),1)
- incrY = dy/max(abs(dy),1)
- t_insert(self._nodes, i, self._grid:getNodeAt(self._nodes[i-1]._x + incrX, self._nodes[i-1]._y +incrY))
- N = N+1
- else i=i+1
- end
- if i>N then break end
- end
- return self
- 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,dx,dy, olddx, olddy
- xi,yi = self._nodes[i]._x, self._nodes[i]._y
- dx, dy = xi - self._nodes[i-1]._x, yi-self._nodes[i-1]._y
- while true do
- olddx, olddy = dx, dy
- if self._nodes[i+1] then
- i = i+1
- xi, yi = self._nodes[i]._x, self._nodes[i]._y
- dx, dy = xi - self._nodes[i-1]._x, yi - self._nodes[i-1]._y
- if olddx == dx and olddy == dy then
- t_remove(self._nodes, i-1)
- i = i - 1
- end
- else break end
- end
- return self
- end
-
- --- Clones a `path`.
- -- @class function
- -- @treturn path a `path`
- -- @usage local p = path:clone()
- function Path:clone()
- local p = Path:new()
- for node in self:nodes() do p:addNode(node) end
- return p
- end
-
- --- Checks if a `path` is equal to another. It also supports *filtered paths* (see @{Path:filter}).
- -- @class function
- -- @tparam path p2 a path
- -- @treturn boolean a boolean
- -- @usage print(myPath:isEqualTo(anotherPath))
- function Path:isEqualTo(p2)
- local p1 = self:clone():filter()
- local p2 = p2:clone():filter()
- for node, count in p1:nodes() do
- if not p2._nodes[count] then return false end
- local n = p2._nodes[count]
- if n._x~=node._x or n._y~=node._y then return false end
- end
- return true
- end
-
- --- Reverses a `path`.
- -- @class function
- -- @treturn path self (the calling `path` itself, can be chained)
- -- @usage myPath:reverse()
- function Path:reverse()
- local _nodes = {}
- for i = #self._nodes,1,-1 do
- _nodes[#_nodes+1] = self._nodes[i]
- end
- self._nodes = _nodes
- return self
- end
-
- --- Appends a given `path` to self.
- -- @class function
- -- @tparam path p a path
- -- @treturn path self (the calling `path` itself, can be chained)
- -- @usage myPath:append(anotherPath)
- function Path:append(p)
- for node in p:nodes() do self:addNode(node) end
- return self
- end
-
return setmetatable(Path,
- {__call = function(self,...)
+ {__call = function(_,...)
return Path:new(...)
end
})
diff --git a/sys/apis/jumper/core/utils.lua b/sys/apis/jumper/core/utils.lua
index 11ef901..1ebf876 100644
--- a/sys/apis/jumper/core/utils.lua
+++ b/sys/apis/jumper/core/utils.lua
@@ -5,125 +5,20 @@ if (...) then
-- Dependencies
local _PATH = (...):gsub('%.utils$','')
local Path = require (_PATH .. '.path')
- local Node = require (_PATH .. '.node')
-- Local references
local pairs = pairs
- local type = type
local t_insert = table.insert
- local assert = assert
- local coroutine = coroutine
-- Raw array items count
local function arraySize(t)
local count = 0
- for k,v in pairs(t) do
+ for _ in pairs(t) do
count = count+1
end
return count
end
- -- Parses a string map and builds an array map
- local function stringMapToArray(str)
- local map = {}
- local w, h
- for line in str:gmatch('[^\n\r]+') do
- if line then
- w = not w and #line or w
- assert(#line == w, 'Error parsing map, rows must have the same size!')
- h = (h or 0) + 1
- map[h] = {}
- for char in line:gmatch('.') do
- map[h][#map[h]+1] = char
- end
- end
- end
- return map
- end
-
- -- Collects and returns the keys of a given array
- local function getKeys(t)
- local keys = {}
- for k,v in pairs(t) do keys[#keys+1] = k end
- return keys
- end
-
- -- Calculates the bounds of a 2d array
- local function getArrayBounds(map)
- local min_x, max_x
- local min_y, max_y
- for y in pairs(map) do
- min_y = not min_y and y or (ymax_y and y or max_y)
- for x in pairs(map[y]) do
- min_x = not min_x and x or (xmax_x and x or max_x)
- end
- end
- return min_x,max_x,min_y,max_y
- end
-
- -- Converts an array to a set of nodes
- local function arrayToNodes(map)
- local min_x, max_x
- local min_y, max_y
- local min_z, max_z
- local nodes = {}
- for y in pairs(map) do
- min_y = not min_y and y or (ymax_y and y or max_y)
- nodes[y] = {}
- for x in pairs(map[y]) do
- min_x = not min_x and x or (xmax_x and x or max_x)
- nodes[y][x] = {}
- for z in pairs(map[y][x]) do
- min_z = not min_z and z or (zmax_z and z or max_z)
- nodes[y][x][z] = Node:new(x,y,z)
- end
- end
- end
- return nodes,
- (min_x or 0), (max_x or 0),
- (min_y or 0), (max_y or 0),
- (min_z or 0), (max_z or 0)
- end
-
- -- Iterator, wrapped within a coroutine
- -- Iterates around a given position following the outline of a square
- local function around()
- local iterf = function(x0, y0, z0, s)
- local x, y, z = x0-s, y0-s, z0-s
- coroutine.yield(x, y, z)
- repeat
- x = x + 1
- coroutine.yield(x,y,z)
- until x == x0+s
- repeat
- y = y + 1
- coroutine.yield(x,y,z)
- until y == y0 + s
- repeat
- z = z + 1
- coroutine.yield(x,y,z)
- until z == z0 + s
- repeat
- x = x - 1
- coroutine.yield(x, y,z)
- until x == x0-s
- repeat
- y = y - 1
- coroutine.yield(x,y,z)
- until y == y0-s+1
- repeat
- z = z - 1
- coroutine.yield(x,y,z)
- until z == z0-s+1
- end
- return coroutine.create(iterf)
- end
-
-- Extract a path from a given start/end position
local function traceBackPath(finder, node, startNode)
local path = Path:new()
@@ -151,17 +46,11 @@ if (...) then
local function outOfRange(i,low,up)
return (i< low or i > up)
end
-
+
return {
arraySize = arraySize,
- getKeys = getKeys,
indexOf = indexOf,
outOfRange = outOfRange,
- getArrayBounds = getArrayBounds,
- arrayToNodes = arrayToNodes,
- strToMap = stringMapToArray,
- around = around,
- drAround = drAround,
traceBackPath = traceBackPath
}
diff --git a/sys/apis/jumper/grid.lua b/sys/apis/jumper/grid.lua
index b2cd20f..36f28af 100644
--- a/sys/apis/jumper/grid.lua
+++ b/sys/apis/jumper/grid.lua
@@ -2,7 +2,8 @@
-- Implementation of the `grid` class.
-- The `grid` is a implicit graph which represents the 2D
-- world map layout on which the `pathfinder` object will run.
--- During a search, the `pathfinder` object needs to save some critical values. These values are cached within each `node`
+-- During a search, the `pathfinder` object needs to save some critical values.
+-- These values are cached within each `node`
-- object, and the whole set of nodes are tight inside the `grid` object itself.
if (...) then
@@ -12,16 +13,10 @@ if (...) then
-- Local references
local Utils = require (_PATH .. '.core.utils')
- local Assert = require (_PATH .. '.core.assert')
local Node = require (_PATH .. '.core.node')
-- Local references
- local pairs = pairs
- local assert = assert
- local next = next
local setmetatable = setmetatable
- local floor = math.floor
- local coroutine = coroutine
-- Offsets for straights moves
local straightOffsets = {
@@ -30,12 +25,6 @@ if (...) then
{x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]]
}
- -- Offsets for diagonal moves
- local diagonalOffsets = {
- {x = -1, y = -1} --[[NW]], {x = 1, y = -1}, --[[NE]]
- {x = -1, y = 1} --[[SW]], {x = 1, y = 1}, --[[SE]]
- }
-
--- The `Grid` class.
-- This class is callable.
-- Therefore,_ Grid(...)
_acts as a shortcut to_ Grid:new(...)
.
@@ -43,46 +32,21 @@ if (...) then
local Grid = {}
Grid.__index = Grid
- -- Specialized grids
- local PreProcessGrid = setmetatable({},Grid)
- local PostProcessGrid = setmetatable({},Grid)
- PreProcessGrid.__index = PreProcessGrid
- PostProcessGrid.__index = PostProcessGrid
- PreProcessGrid.__call = function (self,x,y,z)
- return self:getNodeAt(x,y,z)
- end
- PostProcessGrid.__call = function (self,x,y,z,create)
- if create then return self:getNodeAt(x,y,z) end
- return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z]
- end
-
--- Inits a new `grid`
-- @class function
- -- @tparam table|string map A collision map - (2D array) with consecutive indices (starting at 0 or 1)
+ -- @tparam table Map dimensions
-- or a `string` with line-break chars (\n
or \r
) as row delimiters.
- -- @tparam[opt] bool cacheNodeAtRuntime When __true__, returns an empty `grid` instance, so that
- -- later on, indexing a non-cached `node` will cause it to be created and cache within the `grid` on purpose (i.e, when needed).
- -- This is a __memory-safe__ option, in case your dealing with some tight memory constraints.
- -- Defaults to __false__ when omitted.
-- @treturn grid a new `grid` instance
- -- @usage
- -- -- A simple 3x3 grid
- -- local myGrid = Grid:new({{0,0,0},{0,0,0},{0,0,0}})
- --
- -- -- A memory-safe 3x3 grid
- -- myGrid = Grid('000\n000\n000', true)
- function Grid:new(map, cacheNodeAtRuntime)
- if type(map) == 'string' then
- assert(Assert.isStrMap(map), 'Wrong argument #1. Not a valid string map')
- map = Utils.strToMap(map)
- end
- --assert(Assert.isMap(map),('Bad argument #1. Not a valid map'))
- assert(Assert.isBool(cacheNodeAtRuntime) or Assert.isNil(cacheNodeAtRuntime),
- ('Bad argument #2. Expected \'boolean\', got %s.'):format(type(cacheNodeAtRuntime)))
- if cacheNodeAtRuntime then
- return PostProcessGrid:new(map,walkable)
- end
- return PreProcessGrid:new(map,walkable)
+ function Grid:new(dim)
+ local newGrid = { }
+ newGrid._min_x, newGrid._max_x = dim.x, dim.ex
+ newGrid._min_y, newGrid._max_y = dim.y, dim.ey
+ newGrid._min_z, newGrid._max_z = dim.z, dim.ez
+ newGrid._nodes = { }
+ newGrid._width = (newGrid._max_x-newGrid._min_x)+1
+ newGrid._height = (newGrid._max_y-newGrid._min_y)+1
+ newGrid._length = (newGrid._max_z-newGrid._min_z)+1
+ return setmetatable(newGrid,Grid)
end
--- Checks if `node` at [x,y] is __walkable__.
@@ -90,41 +54,11 @@ if (...) then
-- @class function
-- @tparam int x the x-location of the node
-- @tparam int y the y-location of the 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.
- -- If this parameter is a function, it should be prototyped as __f(value)__ and return a `boolean`:
- -- __true__ when value matches a __walkable__ `node`, __false__ otherwise. If this parameter is not given
- -- while location [x,y] __is valid__, this actual function returns __true__.
- -- @tparam[optchain] int clearance the amount of clearance needed. Defaults to 1 (normal clearance) when not given.
- -- @treturn bool __true__ if `node` exists and is __walkable__, __false__ otherwise
- -- @usage
- -- -- Always true
- -- print(myGrid:isWalkableAt(2,3))
+ -- @tparam int z the z-location of the node
--
- -- -- True if node at [2,3] collision map value is 0
- -- print(myGrid:isWalkableAt(2,3,0))
- --
- -- -- True if node at [2,3] collision map value is 0 and has a clearance higher or equal to 2
- -- print(myGrid:isWalkableAt(2,3,0,2))
- --
- function Grid:isWalkableAt(x, y, z, walkable, clearance)
- local nodeValue = self._map[y] and self._map[y][x] and self._map[y][x][z]
- if nodeValue then
- if not walkable then return true end
- else
- return false
- end
- local hasEnoughClearance = not clearance and true or false
- if not hasEnoughClearance then
- if not self._isAnnotated[walkable] then return false end
- local node = self:getNodeAt(x,y,z)
- local nodeClearance = node:getClearance(walkable)
- hasEnoughClearance = (nodeClearance >= clearance)
- end
- if self._eval then
- return walkable(nodeValue) and hasEnoughClearance
- end
- return ((nodeValue == walkable) and hasEnoughClearance)
+ function Grid:isWalkableAt(x, y, z)
+ local node = self:getNodeAt(x,y,z)
+ return node and node.walkable ~= 1
end
--- Returns the `grid` width.
@@ -143,14 +77,6 @@ if (...) then
return self._height
end
- --- Returns the collision map.
- -- @class function
- -- @treturn map the collision map (see @{Grid:new})
- -- @usage local map = myGrid:getMap()
- function Grid:getMap()
- return self._map
- end
-
--- Returns the set of nodes.
-- @class function
-- @treturn {{node,...},...} an array of nodes
@@ -174,18 +100,14 @@ if (...) then
--- 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.
- -- @tparam[optchain] bool allowDiagonal when __true__, allows adjacent nodes are included (8-neighbours).
- -- Defaults to __false__ when omitted.
- -- @tparam[optchain] bool tunnel When __true__, allows the `pathfinder` to tunnel through walls when heading diagonally.
- -- @tparam[optchain] int clearance When given, will prune for the neighbours set all nodes having a clearance value lower than the passed-in value
+ -- @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, walkable, allowDiagonal, tunnel, clearance)
+ function Grid:getNeighbours(node)
local neighbours = {}
for i = 1,#straightOffsets do
local n = self:getNodeAt(
@@ -193,227 +115,31 @@ if (...) then
node._y + straightOffsets[i].y,
node._z + straightOffsets[i].z
)
- if n and self:isWalkableAt(n._x, n._y, n._z, walkable, clearance) then
+ if n and self:isWalkableAt(n._x, n._y, n._z) then
neighbours[#neighbours+1] = n
end
end
- if not allowDiagonal then return neighbours end
-
- tunnel = not not tunnel
- for i = 1,#diagonalOffsets do
- local n = self:getNodeAt(
- node._x + diagonalOffsets[i].x,
- node._y + diagonalOffsets[i].y
- )
- if n and self:isWalkableAt(n._x, n._y, walkable, clearance) then
- if tunnel then
- neighbours[#neighbours+1] = n
- else
- local skipThisNode = false
- local n1 = self:getNodeAt(node._x+diagonalOffsets[i].x, node._y)
- local n2 = self:getNodeAt(node._x, node._y+diagonalOffsets[i].y)
- if ((n1 and n2) and not self:isWalkableAt(n1._x, n1._y, walkable, clearance) and not self:isWalkableAt(n2._x, n2._y, walkable, clearance)) then
- skipThisNode = true
- end
- if not skipThisNode then neighbours[#neighbours+1] = n end
- end
- end
- end
-
return neighbours
end
- --- Grid iterator. Iterates on every single node
- -- in the `grid`. Passing __lx, ly, ex, ey__ arguments will iterate
- -- only on nodes inside the bounding-rectangle delimited by those given coordinates.
- -- @class function
- -- @tparam[opt] int lx the leftmost x-coordinate of the rectangle. Default to the `grid` leftmost x-coordinate (see @{Grid:getBounds}).
- -- @tparam[optchain] int ly the topmost y-coordinate of the rectangle. Default to the `grid` topmost y-coordinate (see @{Grid:getBounds}).
- -- @tparam[optchain] int ex the rightmost x-coordinate of the rectangle. Default to the `grid` rightmost x-coordinate (see @{Grid:getBounds}).
- -- @tparam[optchain] int ey the bottom-most y-coordinate of the rectangle. Default to the `grid` bottom-most y-coordinate (see @{Grid:getBounds}).
- -- @treturn node a `node` on the collision map, upon each iteration step
- -- @treturn int the iteration count
- -- @usage
- -- for node, count in myGrid:iter() do
- -- print(node:getX(), node:getY(), count)
- -- end
- function Grid:iter(lx,ly,lz,ex,ey,ez)
- local min_x = lx or self._min_x
- local min_y = ly or self._min_y
- local min_z = lz or self._min_z
- local max_x = ex or self._max_x
- local max_y = ey or self._max_y
- local max_z = ez or self._max_z
-
- local x, y, z
- z = min_z
- return function()
- x = not x and min_x or x+1
- if x > max_x then
- x = min_x
- y = y+1
- end
- y = not y and min_y or y+1
- if y > max_y then
- y = min_y
- z = z+1
- end
- if z > max_z then
- z = nil
- end
- return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z] or self:getNodeAt(x,y,z)
- end
- end
-
- --- Grid iterator. Iterates on each node along the outline (border) of a squared area
- -- centered on the given node.
- -- @tparam node node a given `node`
- -- @tparam[opt] int radius the area radius (half-length). Defaults to __1__ when not given.
- -- @treturn node a `node` at each iteration step
- -- @usage
- -- for node in myGrid:around(node, 2) do
- -- ...
- -- end
- function Grid:around(node, radius)
- local x, y, z = node._x, node._y, node._z
- radius = radius or 1
- local _around = Utils.around()
- local _nodes = {}
- repeat
- local state, x, y, z = coroutine.resume(_around,x,y,z,radius)
- local nodeAt = state and self:getNodeAt(x, y, z)
- if nodeAt then _nodes[#_nodes+1] = nodeAt end
- until (not state)
- local _i = 0
- return function()
- _i = _i+1
- return _nodes[_i]
- end
- end
-
- --- Each transformation. Calls the given function on each `node` in the `grid`,
- -- passing the `node` as the first argument to function __f__.
- -- @class function
- -- @tparam func f a function prototyped as __f(node,...)__
- -- @tparam[opt] vararg ... args to be passed to function __f__
- -- @treturn grid self (the calling `grid` itself, can be chained)
- -- @usage
- -- local function printNode(node)
- -- print(node:getX(), node:getY())
- -- end
- -- myGrid:each(printNode)
- function Grid:each(f,...)
- for node in self:iter() do f(node,...) end
- return self
- end
-
- --- Each (in range) transformation. Calls a function on each `node` in the range of a rectangle of cells,
- -- passing the `node` as the first argument to function __f__.
- -- @class function
- -- @tparam int lx the leftmost x-coordinate coordinate of the rectangle
- -- @tparam int ly the topmost y-coordinate of the rectangle
- -- @tparam int ex the rightmost x-coordinate of the rectangle
- -- @tparam int ey the bottom-most y-coordinate of the rectangle
- -- @tparam func f a function prototyped as __f(node,...)__
- -- @tparam[opt] vararg ... args to be passed to function __f__
- -- @treturn grid self (the calling `grid` itself, can be chained)
- -- @usage
- -- local function printNode(node)
- -- print(node:getX(), node:getY())
- -- end
- -- myGrid:eachRange(1,1,8,8,printNode)
- function Grid:eachRange(lx,ly,ex,ey,f,...)
- for node in self:iter(lx,ly,ex,ey) do f(node,...) end
- return self
- end
-
- --- Map transformation.
- -- Calls function __f(node,...)__ on each `node` in a given range, passing the `node` as the first arg to function __f__ and replaces
- -- it with the returned value. Therefore, the function should return a `node`.
- -- @class function
- -- @tparam func f a function prototyped as __f(node,...)__
- -- @tparam[opt] vararg ... args to be passed to function __f__
- -- @treturn grid self (the calling `grid` itself, can be chained)
- -- @usage
- -- local function nothing(node)
- -- return node
- -- end
- -- myGrid:imap(nothing)
- function Grid:imap(f,...)
- for node in self:iter() do
- node = f(node,...)
- end
- return self
- end
-
- --- Map in range transformation.
- -- Calls function __f(node,...)__ on each `node` in a rectangle range, passing the `node` as the first argument to the function and replaces
- -- it with the returned value. Therefore, the function should return a `node`.
- -- @class function
- -- @tparam int lx the leftmost x-coordinate coordinate of the rectangle
- -- @tparam int ly the topmost y-coordinate of the rectangle
- -- @tparam int ex the rightmost x-coordinate of the rectangle
- -- @tparam int ey the bottom-most y-coordinate of the rectangle
- -- @tparam func f a function prototyped as __f(node,...)__
- -- @tparam[opt] vararg ... args to be passed to function __f__
- -- @treturn grid self (the calling `grid` itself, can be chained)
- -- @usage
- -- local function nothing(node)
- -- return node
- -- end
- -- myGrid:imap(1,1,6,6,nothing)
- function Grid:imapRange(lx,ly,ex,ey,f,...)
- for node in self:iter(lx,ly,ex,ey) do
- node = f(node,...)
- end
- return self
- end
-
- -- Specialized grids
- -- Inits a preprocessed grid
- function PreProcessGrid:new(map)
- local newGrid = {}
- newGrid._map = map
- newGrid._nodes, newGrid._min_x, newGrid._max_x, newGrid._min_y, newGrid._max_y, newGrid._min_z, newGrid._max_z = Utils.arrayToNodes(newGrid._map)
- newGrid._width = (newGrid._max_x-newGrid._min_x)+1
- newGrid._height = (newGrid._max_y-newGrid._min_y)+1
- newGrid._length = (newGrid._max_z-newGrid._min_z)+1
- newGrid._isAnnotated = {}
- return setmetatable(newGrid,PreProcessGrid)
- end
-
- -- Inits a postprocessed grid
- function PostProcessGrid:new(map)
- local newGrid = {}
- newGrid._map = map
- newGrid._nodes = {}
- newGrid._min_x, newGrid._max_x, newGrid._min_y, newGrid._max_y = Utils.getArrayBounds(newGrid._map)
- newGrid._width = (newGrid._max_x-newGrid._min_x)+1
- newGrid._height = (newGrid._max_y-newGrid._min_y)+1
- newGrid._isAnnotated = {}
- return setmetatable(newGrid,PostProcessGrid)
- end
-
- --- Returns the `node` at location [x,y].
+ --- 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 PreProcessGrid:getNodeAt(x,y,z)
- return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z] or nil
- end
-
- -- Gets the node at location on a postprocessed grid
- function PostProcessGrid: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
if Utils.outOfRange(z,self._min_z,self._max_z) then return end
+
+ -- inefficient
if not self._nodes[y] then self._nodes[y] = {} end
if not self._nodes[y][x] then self._nodes[y][x] = {} end
if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end
diff --git a/sys/apis/jumper/pathfinder.lua b/sys/apis/jumper/pathfinder.lua
index 1d64265..a209afb 100644
--- a/sys/apis/jumper/pathfinder.lua
+++ b/sys/apis/jumper/pathfinder.lua
@@ -41,53 +41,24 @@ if (...) then
-- Dependencies
local _PATH = (...):gsub('%.pathfinder$','')
local Utils = require (_PATH .. '.core.utils')
- local Assert = require (_PATH .. '.core.assert')
- local Heap = require (_PATH .. '.core.bheap')
- local Heuristic = require (_PATH .. '.core.heuristics')
- local Grid = require (_PATH .. '.grid')
- local Path = require (_PATH .. '.core.path')
-- Internalization
- local t_insert, t_remove = table.insert, table.remove
- local floor = math.floor
local pairs = pairs
local assert = assert
- local type = type
- local setmetatable, getmetatable = setmetatable, getmetatable
+ local setmetatable = setmetatable
--- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper.
- --
-- [A*](http://en.wikipedia.org/wiki/A*_search_algorithm)
- -- [Dijkstra](http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)
- -- [Theta Astar](http://aigamedev.com/open/tutorials/theta-star-any-angle-paths/)
- -- [BFS](http://en.wikipedia.org/wiki/Breadth-first_search)
- -- [DFS](http://en.wikipedia.org/wiki/Depth-first_search)
- -- [JPS](http://harablog.wordpress.com/2011/09/07/jump-point-search/)
-- @finder Finders
-- @see Pathfinder:getFinders
local Finders = {
['ASTAR'] = require (_PATH .. '.search.astar'),
--- ['DIJKSTRA'] = require (_PATH .. '.search.dijkstra'),
--- ['THETASTAR'] = require (_PATH .. '.search.thetastar'),
- ['BFS'] = require (_PATH .. '.search.bfs'),
--- ['DFS'] = require (_PATH .. '.search.dfs'),
--- ['JPS'] = require (_PATH .. '.search.jps')
}
-- Will keep track of all nodes expanded during the search
-- to easily reset their properties for the next pathfinding call
local toClear = {}
- --- Search modes. Refers to the search modes. In ORTHOGONAL mode, 4-directions are only possible when moving,
- -- including North, East, West, South. In DIAGONAL mode, 8-directions are possible when moving,
- -- including North, East, West, South and adjacent directions.
- --
- -- ORTHOGONAL
- -- DIAGONAL
- -- @mode Modes
- -- @see Pathfinder:getModes
- local searchModes = {['DIAGONAL'] = true, ['ORTHOGONAL'] = true}
-
-- Performs a traceback from the goal node to the start node
-- Only happens when the path was found
@@ -103,259 +74,27 @@ if (...) then
-- @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}).
- -- @tparam[optchain] string|int|func walkable the value for __walkable__ nodes.
- -- If this parameter is a function, it should be prototyped as __f(value)__, returning a boolean:
- -- __true__ when value matches a __walkable__ `node`, __false__ otherwise.
-- @treturn pathfinder a new `pathfinder` instance
-- @usage
- -- -- Example one
- -- local finder = Pathfinder:new(myGrid, 'ASTAR', 0)
- --
- -- -- Example two
- -- local function walkable(value)
- -- return value > 0
- -- end
- -- local finder = Pathfinder(myGrid, 'JPS', walkable)
- function Pathfinder:new(grid, finderName, walkable)
+ -- local finder = Pathfinder:new(myGrid, 'ASTAR')
+ function Pathfinder:new(heuristic)
local newPathfinder = {}
setmetatable(newPathfinder, Pathfinder)
- --newPathfinder:setGrid(grid)
- newPathfinder:setFinder(finderName)
- --newPathfinder:setWalkable(walkable)
- newPathfinder:setMode('DIAGONAL')
- newPathfinder:setHeuristic('MANHATTAN')
- newPathfinder:setTunnelling(false)
+ self._finder = Finders.ASTAR
+ self._heuristic = heuristic
return newPathfinder
end
- --- Evaluates [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)
- -- for the whole `grid`. It should be called only once, unless the collision map or the
- -- __walkable__ attribute changes. The clearance values are calculated and cached within the grid nodes.
- -- @class function
- -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
- -- @usage myFinder:annotateGrid()
- function Pathfinder:annotateGrid()
- assert(self._walkable, 'Finder must implement a walkable value')
- for x=self._grid._max_x,self._grid._min_x,-1 do
- for y=self._grid._max_y,self._grid._min_y,-1 do
- local node = self._grid:getNodeAt(x,y)
- if self._grid:isWalkableAt(x,y,self._walkable) then
- local nr = self._grid:getNodeAt(node._x+1, node._y)
- local nrd = self._grid:getNodeAt(node._x+1, node._y+1)
- local nd = self._grid:getNodeAt(node._x, node._y+1)
- if nr and nrd and nd then
- local m = nrd._clearance[self._walkable] or 0
- m = (nd._clearance[self._walkable] or 0)0
- -- end
- function Pathfinder:setWalkable(walkable)
- assert(Assert.matchType(walkable,'stringintfunctionnil'),
- ('Wrong argument #1. Expected \'string\', \'number\' or \'function\', got %s.'):format(type(walkable)))
- self._walkable = walkable
- self._grid._eval = type(self._walkable) == 'function'
- return self
- end
-
- --- Gets the __walkable__ value or function.
- -- @class function
- -- @treturn string|int|func the `walkable` value or function
- -- @usage local walkable = myFinder:getWalkable()
- function Pathfinder:getWalkable()
- return self._walkable
- end
-
- --- Defines the `finder`. It refers to the search algorithm used by the `pathfinder`.
- -- Default finder is `ASTAR`. Use @{Pathfinder:getFinders} to get the list of available finders.
- -- @class function
- -- @tparam string finderName the name of the `finder` to be used for further searches.
- -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
- -- @usage
- -- --To use Breadth-First-Search
- -- myFinder:setFinder('BFS')
- -- @see Pathfinder:getFinders
- function Pathfinder:setFinder(finderName)
- if not finderName then
- if not self._finder then
- finderName = 'ASTAR'
- else return
- end
- end
- assert(Finders[finderName],'Not a valid finder name!')
- self._finder = finderName
- return self
- end
-
- --- Returns the name of the `finder` being used.
- -- @class function
- -- @treturn string the name of the `finder` to be used for further searches.
- -- @usage local finderName = myFinder:getFinder()
- function Pathfinder:getFinder()
- return self._finder
- end
-
- --- Returns the list of all available finders names.
- -- @class function
- -- @treturn {string,...} array of built-in finders names.
- -- @usage
- -- local finders = myFinder:getFinders()
- -- for i, finderName in ipairs(finders) do
- -- print(i, finderName)
- -- end
- function Pathfinder:getFinders()
- return Utils.getKeys(Finders)
- end
-
- --- Sets a heuristic. This is a function internally used by the `pathfinder` to find the optimal path during a search.
- -- Use @{Pathfinder:getHeuristics} to get the list of all available `heuristics`. One can also define
- -- his own `heuristic` function.
- -- @class function
- -- @tparam func|string heuristic `heuristic` function, prototyped as __f(dx,dy)__ or as a `string`.
- -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
- -- @see Pathfinder:getHeuristics
- -- @see core.heuristics
- -- @usage myFinder:setHeuristic('MANHATTAN')
- function Pathfinder:setHeuristic(heuristic)
- assert(Heuristic[heuristic] or (type(heuristic) == 'function'),'Not a valid heuristic!')
- self._heuristic = Heuristic[heuristic] or heuristic
- return self
- end
-
- --- Returns the `heuristic` used. Returns the function itself.
- -- @class function
- -- @treturn func the `heuristic` function being used by the `pathfinder`
- -- @see core.heuristics
- -- @usage local h = myFinder:getHeuristic()
- function Pathfinder:getHeuristic()
- return self._heuristic
- end
-
- --- Gets the list of all available `heuristics`.
- -- @class function
- -- @treturn {string,...} array of heuristic names.
- -- @see core.heuristics
- -- @usage
- -- local heur = myFinder:getHeuristic()
- -- for i, heuristicName in ipairs(heur) do
- -- ...
- -- end
- function Pathfinder:getHeuristics()
- return Utils.getKeys(Heuristic)
- end
-
- --- Defines the search `mode`.
- -- The default search mode is the `DIAGONAL` mode, which implies 8-possible directions when moving (north, south, east, west and diagonals).
- -- In `ORTHOGONAL` mode, only 4-directions are allowed (north, south, east and west).
- -- Use @{Pathfinder:getModes} to get the list of all available search modes.
- -- @class function
- -- @tparam string mode the new search `mode`.
- -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
- -- @see Pathfinder:getModes
- -- @see Modes
- -- @usage myFinder:setMode('ORTHOGONAL')
- function Pathfinder:setMode(mode)
- assert(searchModes[mode],'Invalid mode')
- self._allowDiagonal = (mode == 'DIAGONAL')
- return self
- end
-
- --- Returns the search mode.
- -- @class function
- -- @treturn string the current search mode
- -- @see Modes
- -- @usage local mode = myFinder:getMode()
- function Pathfinder:getMode()
- return (self._allowDiagonal and 'DIAGONAL' or 'ORTHOGONAL')
- end
-
- --- Gets the list of all available search modes.
- -- @class function
- -- @treturn {string,...} array of search modes.
- -- @see Modes
- -- @usage local modes = myFinder:getModes()
- -- for modeName in ipairs(modes) do
- -- ...
- -- end
- function Pathfinder:getModes()
- return Utils.getKeys(searchModes)
- end
-
- --- Enables tunnelling. Defines the ability for the `pathfinder` to tunnel through walls when heading diagonally.
- -- This feature __is not compatible__ with Jump Point Search algorithm (i.e. enabling it will not affect Jump Point Search)
- -- @class function
- -- @tparam bool bool a boolean
- -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
- -- @usage myFinder:setTunnelling(true)
- function Pathfinder:setTunnelling(bool)
- assert(Assert.isBool(bool), ('Wrong argument #1. Expected boolean, got %s'):format(type(bool)))
- self._tunnel = bool
- return self
- end
-
- --- Returns tunnelling feature state.
- -- @class function
- -- @treturn bool tunnelling feature actual state
- -- @usage local isTunnellingEnabled = myFinder:getTunnelling()
- function Pathfinder:getTunnelling()
- return self._tunnel
- end
-
--- Calculates a `path`. Returns the `path` from location __[startX, startY]__ to location __[endX, endY]__.
-- Both locations must exist on the collision map. The starting location can be unwalkable.
-- @class function
@@ -363,11 +102,9 @@ if (...) then
-- @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
- -- @tparam int clearance the amount of clearance (i.e the pathing agent size) to consider
-- @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, clearance)
-
+ function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh)
self:reset()
local startNode = self._grid:getNodeAt(startX, startY, startZ)
local endNode = self._grid:getNodeAt(endX, endY, endZ)
@@ -381,14 +118,15 @@ if (...) then
assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ))
assert(endNode and self._grid:isWalkableAt(endX, endY, endZ),
('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ))
- local _endNode = Finders[self._finder](self, startNode, endNode, clearance, toClear)
+ local _endNode = self._finder(self, startNode, endNode, toClear)
if _endNode then
return Utils.traceBackPath(self, _endNode, startNode)
end
return nil
end
- --- Resets the `pathfinder`. This function is called internally between successive pathfinding calls, so you should not
+ --- Resets the `pathfinder`. This function is called internally between
+ -- successive pathfinding calls, so you should not
-- use it explicitely, unless under specific circumstances.
-- @class function
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
@@ -399,7 +137,6 @@ if (...) then
return self
end
-
-- Returns Pathfinder class
Pathfinder._VERSION = _VERSION
Pathfinder._RELEASEDATE = _RELEASEDATE
@@ -408,5 +145,4 @@ if (...) then
return self:new(...)
end
})
-
end
diff --git a/sys/apis/jumper/search/astar.lua b/sys/apis/jumper/search/astar.lua
index 3e1b323..ae730dc 100644
--- a/sys/apis/jumper/search/astar.lua
+++ b/sys/apis/jumper/search/astar.lua
@@ -5,16 +5,14 @@
if (...) then
-- Internalization
- local ipairs = ipairs
local huge = math.huge
-- Dependancies
local _PATH = (...):match('(.+)%.search.astar$')
- local Heuristics = require (_PATH .. '.core.heuristics')
local Heap = require (_PATH.. '.core.bheap')
-- Updates G-cost
- local function computeCost(node, neighbour, finder, clearance, heuristic)
+ local function computeCost(node, neighbour, heuristic)
local mCost, heading = heuristic(neighbour, node) -- Heuristics.EUCLIDIAN(neighbour, node)
if node._g + mCost < neighbour._g then
@@ -25,31 +23,24 @@ if (...) then
end
-- Updates vertex node-neighbour
- local function updateVertex(finder, openList, node, neighbour, endNode, clearance, heuristic, overrideCostEval)
+ local function updateVertex(openList, node, neighbour, endNode, heuristic)
local oldG = neighbour._g
- local cmpCost = overrideCostEval or computeCost
- cmpCost(node, neighbour, finder, clearance, heuristic)
+ computeCost(node, neighbour, heuristic)
if neighbour._g < oldG then
- local nClearance = neighbour._clearance[finder._walkable]
- local pushThisNode = clearance and nClearance and (nClearance >= clearance)
- if (clearance and pushThisNode) or (not clearance) then
- if neighbour._opened then neighbour._opened = false end
- neighbour._h = heuristic(endNode, neighbour)
- neighbour._f = neighbour._g + neighbour._h
- openList:push(neighbour)
- neighbour._opened = true
- end
+ if neighbour._opened then neighbour._opened = false end
+ neighbour._h = heuristic(endNode, neighbour)
+ neighbour._f = neighbour._g + neighbour._h
+ openList:push(neighbour)
+ neighbour._opened = true
end
end
-- Calculates a path.
-- Returns the path from location `` to location ``.
- return function (finder, startNode, endNode, clearance, toClear, overrideHeuristic, overrideCostEval)
-
- local heuristic = overrideHeuristic or finder._heuristic
+ return function (finder, startNode, endNode, toClear)
local openList = Heap()
startNode._g = 0
- startNode._h = heuristic(endNode, startNode)
+ startNode._h = finder._heuristic(endNode, startNode)
startNode._f = startNode._g + startNode._h
openList:push(startNode)
toClear[startNode] = true
@@ -59,17 +50,17 @@ if (...) then
local node = openList:pop()
node._closed = true
if node == endNode then return node end
- local neighbours = finder._grid:getNeighbours(node, finder._walkable, finder._allowDiagonal, finder._tunnel)
+ local neighbours = finder._grid:getNeighbours(node)
for i = 1,#neighbours do
local neighbour = neighbours[i]
if not neighbour._closed then
toClear[neighbour] = true
if not neighbour._opened then
neighbour._g = huge
- neighbour._parent = nil
+ neighbour._parent = nil
end
- updateVertex(finder, openList, node, neighbour, endNode, clearance, heuristic, overrideCostEval)
- end
+ updateVertex(openList, node, neighbour, endNode, finder._heuristic)
+ end
end
--[[
@@ -81,8 +72,6 @@ if (...) then
--]]
end
-
- return nil
+ return nil
end
-
end
diff --git a/sys/apis/jumper/search/bfs.lua b/sys/apis/jumper/search/bfs.lua
deleted file mode 100644
index e0abf5a..0000000
--- a/sys/apis/jumper/search/bfs.lua
+++ /dev/null
@@ -1,46 +0,0 @@
--- Breadth-First search algorithm
-
-if (...) then
- -- Internalization
- local t_remove = table.remove
-
- local function breadth_first_search(finder, openList, node, endNode, clearance, toClear)
- local neighbours = finder._grid:getNeighbours(node, finder._walkable, finder._allowDiagonal, finder._tunnel)
- for i = 1,#neighbours do
- local neighbour = neighbours[i]
- if not neighbour._closed and not neighbour._opened then
- local nClearance = neighbour._clearance[finder._walkable]
- local pushThisNode = clearance and nClearance and (nClearance >= clearance)
- if (clearance and pushThisNode) or (not clearance) then
- openList[#openList+1] = neighbour
- neighbour._opened = true
- neighbour._parent = node
- toClear[neighbour] = true
- end
- end
- end
-
- end
-
- -- Calculates a path.
- -- Returns the path from location `` to location ``.
- return function (finder, startNode, endNode, clearance, toClear)
-
- local openList = {} -- We'll use a FIFO queue (simple array)
- openList[1] = startNode
- startNode._opened = true
- toClear[startNode] = true
-
- local node
- while (#openList > 0) do
- node = openList[1]
- t_remove(openList,1)
- node._closed = true
- if node == endNode then return node end
- breadth_first_search(finder, openList, node, endNode, clearance, toClear)
- end
-
- return nil
- end
-
-end
\ No newline at end of file
diff --git a/sys/apis/turtle/pathfind.lua b/sys/apis/turtle/pathfind.lua
index ec6bc79..a39495a 100644
--- a/sys/apis/turtle/pathfind.lua
+++ b/sys/apis/turtle/pathfind.lua
@@ -7,59 +7,46 @@ local Util = require('util')
local turtle = _G.turtle
-local WALKABLE = 0
-
-local function createMap(dim)
- local map = { }
- for _ = 1, dim.ez do
- local row = {}
- for _ = 1, dim.ex do
- local col = { }
- for _ = 1, dim.ey do
- table.insert(col, WALKABLE)
- end
- table.insert(row, col)
+local function addBlock(grid, b, dim)
+ if Point.inBox(b, dim) then
+ local node = grid:getNodeAt(b.x, b.y, b.z)
+ if node then
+ node.walkable = 1
end
- table.insert(map, row)
end
-
- return map
-end
-
-local function addBlock(map, dim, b)
- map[b.z + dim.oz][b.x + dim.ox][b.y + dim.oy] = 1
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)
+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 function adjust(pt)
if pt.x < sx then
sx = pt.x
- end
- if pt.z < sz then
- sz = pt.z
+ elseif pt.x > ex then
+ ex = pt.x
end
if pt.y < sy then
sy = pt.y
- end
- if pt.x > ex then
- ex = pt.x
- end
- if pt.z > ez then
- ez = pt.z
- end
- if pt.y > ey then
+ 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)
- for _,b in ipairs(blocks) do
+ for _,d in pairs(dests) do
+ adjust(d)
+ end
+
+ for _,b in pairs(blocks) do
adjust(b)
end
@@ -81,29 +68,23 @@ local function mapDimensions(dest, blocks, boundingBox)
end
return {
- ex = ex - sx + 1,
- ez = ez - sz + 1,
- ey = ey - sy + 1,
- ox = -sx + 1,
- oz = -sz + 1,
- oy = -sy + 1
+ ex = ex,
+ ez = ez,
+ ey = ey,
+ x = sx,
+ z = sz,
+ y = sy
}
end
--- shifting and coordinate flipping
-local function pointToMap(dim, pt)
- return { x = pt.x + dim.ox, z = pt.y + dim.oy, y = pt.z + dim.oz }
-end
-
-local function nodeToPoint(dim, node)
- return { x = node:getX() - dim.ox, z = node:getY() - dim.oz, y = node:getZ() - dim.oy }
+local function nodeToPoint(node)
+ return { x = node:getX(), z = node:getZ(), y = node:getY() }
end
local heuristic = function(n, node)
-
local m, h = Point.calculateMoves(
- { x = node._x, z = node._y, y = node._z, heading = node._heading },
- { x = n._x, z = n._y, y = n._z, heading = n._heading })
+ { 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
end
@@ -112,9 +93,9 @@ local function dimsAreEqual(d1, d2)
return d1.ex == d2.ex and
d1.ey == d2.ey and
d1.ez == d2.ez and
- d1.ox == d2.ox and
- d1.oy == d2.oy and
- d1.oz == d2.oz
+ d1.x == d2.x and
+ d1.y == d2.y and
+ d1.z == d2.z
end
-- turtle sensor returns blocks in relation to the world - not turtle orientation
@@ -122,7 +103,6 @@ end
-- really kinda dumb since it returns the coordinates as offsets of our location
-- instead of true coordinates
local function addSensorBlocks(blocks, sblocks)
-
for _,b in pairs(sblocks) do
if b.type ~= 'AIR' then
local pt = { x = turtle.point.x, y = turtle.point.y + b.y, z = turtle.point.z }
@@ -142,28 +122,29 @@ local function addSensorBlocks(blocks, sblocks)
end
end
-local function selectDestination(pts, box, map, dim)
-
+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)) or
- map[pt.z + dim.oz][pt.x + dim.ox][pt.y + dim.oy] == 1 then
+ if box and not Point.inBox(pt, box) then
+ Util.removeByValue(pts, pt)
+ else
+ if grid:isWalkableAt(pt.x, pt.y, pt.z) then
+ return pt
+ end
Util.removeByValue(pts, pt)
- else
- return pt
end
end
end
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 map = nil
local grid = nil
if box then
@@ -171,32 +152,26 @@ local function pathTo(dest, options)
end
-- Creates a pathfinder object
- local myFinder = Pathfinder(grid, 'ASTAR', WALKABLE)
-
- myFinder:setMode('ORTHOGONAL')
- myFinder:setHeuristic(heuristic)
+ local myFinder = Pathfinder(heuristic)
while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do
-- map expands as we encounter obstacles
- local dim = mapDimensions(dest, blocks, box)
+ local dim = mapDimensions(dest, blocks, box, dests)
-- reuse map if possible
if not lastDim or not dimsAreEqual(dim, lastDim) then
- map = createMap(dim)
-- Creates a grid object
- grid = Grid(map)
+ grid = Grid(dim)
myFinder:setGrid(grid)
- myFinder:setWalkable(WALKABLE)
lastDim = dim
end
-
- for _,b in ipairs(blocks) do
- addBlock(map, dim, b)
+ for _,b in pairs(blocks) do
+ addBlock(grid, b, dim)
end
- dest = selectDestination(dests, box, map, dim)
+ dest = selectDestination(dests, box, grid)
if not dest then
-- error('failed to reach destination')
return false, 'failed to reach destination'
@@ -206,8 +181,8 @@ local function pathTo(dest, options)
end
-- Define start and goal locations coordinates
- local startPt = pointToMap(dim, turtle.point)
- local endPt = pointToMap(dim, dest)
+ local startPt = turtle.point
+ local endPt = dest
-- Calculates the path, and its length
local path = myFinder:getPath(
@@ -218,7 +193,7 @@ local function pathTo(dest, options)
Util.removeByValue(dests, dest)
else
for node in path:nodes() do
- local pt = nodeToPoint(dim, node)
+ local pt = nodeToPoint(node)
if turtle.abort then
return false, 'aborted'
@@ -262,6 +237,12 @@ return {
turtle.getState().blocks = blocks
end,
+ addBlock = function(block)
+ if turtle.getState().blocks then
+ table.insert(turtle.getState().blocks, block)
+ end
+ end,
+
reset = function()
turtle.getState().box = nil
turtle.getState().blocks = nil
diff --git a/sys/extensions/tl3.lua b/sys/extensions/tl3.lua
index 4fb5f56..d51940f 100644
--- a/sys/extensions/tl3.lua
+++ b/sys/extensions/tl3.lua
@@ -969,8 +969,23 @@ function turtle.abortAction()
end
-- [[ Pathing ]] --
-function turtle.faceAgainst(pt, options) -- 4 sided
+function turtle.setPersistent(isPersistent)
+ if isPersistent then
+ Pathing.setBlocks({ })
+ else
+ Pathing.setBlocks()
+ end
+end
+function turtle.setPathingBox(box)
+ Pathing.setBox(box)
+end
+
+function turtle.addWorldBlock(pt)
+ Pathing.addBlock(pt)
+end
+
+function turtle.faceAgainst(pt, options) -- 4 sided
options = options or { }
options.dest = { }
@@ -989,7 +1004,6 @@ function turtle.faceAgainst(pt, options) -- 4 sided
end
function turtle.moveAgainst(pt, options) -- 6 sided
-
options = options or { }
options.dest = { }
@@ -1090,6 +1104,32 @@ local function _actionUpAt(action, pt, ...)
end
end
+local function _actionXXXAt(action, pt, dir, ...)
+ local reversed =
+ { [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, }
+ dir = reversed[dir]
+ local apt = { x = pt.x + headings[dir].xd,
+ y = pt.y + headings[dir].yd,
+ z = pt.z + headings[dir].zd, }
+ local direction
+
+ -- ex: place a block at this point, from above, facing east
+ if dir < 4 then
+ apt.heading = (dir + 2) % 4
+ direction = 'forward'
+ elseif dir == 4 then
+ apt.heading = pt.heading
+ direction = 'down'
+ elseif dir == 5 then
+ apt.heading = pt.heading
+ direction = 'up'
+ end
+
+ if turtle.pathfind(apt) then
+ return action[direction](...)
+ 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
@@ -1109,6 +1149,7 @@ function turtle.placeAt(pt, arg) return _actionAt(actionsAt.place, pt, ar
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.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end
function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end
diff --git a/sys/network/proxy.lua b/sys/network/proxy.lua
index 034f61f..62dab17 100644
--- a/sys/network/proxy.lua
+++ b/sys/network/proxy.lua
@@ -24,9 +24,10 @@ Event.addRoutine(function()
while true do
local data = socket:read()
if not data then
+ print('proxy: lost connection from ' .. socket.dhost)
break
end
- socket:write({ proxy[data.fn](unpack(data.args)) })
+ socket:write({ proxy[data.fn](table.unpack(data.args)) })
end
end
end)
diff --git a/sys/services/network.lua b/sys/services/network.lua
index 3000f83..7ba9dd0 100644
--- a/sys/services/network.lua
+++ b/sys/services/network.lua
@@ -17,7 +17,7 @@ local function netUp()
_G.requireInjector()
local Event = require('event')
-_G._e2 = _ENV
+
for _,file in pairs(fs.list('sys/network')) do
local fn, msg = Util.run(_ENV, 'sys/network/' .. file)
if not fn then
@@ -48,8 +48,6 @@ print('Net daemon started')
local function startNetwork()
print('Starting network services')
-_G._e1 = _ENV
-
local success, msg = Util.runFunction(
Util.shallowCopy(_ENV), netUp)