CC-navigation/starNav
2016-02-07 18:20:06 +00:00

291 lines
7.6 KiB
Plaintext

if not tinyMap then
if not os.loadAPI("tinyMap") then
error("could not load tinyMap API")
end
end
if not aStar then
if not os.loadAPI("aStar") then
error("could not load aStar API")
end
end
if not location then
if not os.loadAPI("location") then
error("could not load location API")
end
end
local position
local SESSION_MAX_DISTANCE_DEFAULT = 32
local SESSION_COORD_CLEAR = 1
local SESSION_COORD_BLOCKED = 2
local SESSION_COORD_DISTANCE = math.huge
local MAIN_COORD_CLEAR = nil
local MAIN_COORD_BLOCKED = 1
local MAIN_COORD_DISTANCE = 1024
local sessionMidPoint
local sessionMaxDistance
local sessionMap
local mainMap = tinyMap.new("starNavMaps")
local function sup_norm(a, b)
return math.max(math.abs(a.x - b.x), math.abs(a.y - b.y), math.abs(a.z - b.z))
end
local function distanceFunc(a, b)
local sessionMapA, sessionMapB = sessionMap:get(a), sessionMap:get(b)
if sup_norm(a, sessionMidPoint) > sessionMaxDistance then
return SESSION_COORD_DISTANCE -- first coord is outside the search region
elseif sup_norm(b, sessionMidPoint) > sessionMaxDistance then
return SESSION_COORD_DISTANCE -- second coord is outside the search region
elseif sessionMapA == SESSION_COORD_BLOCKED or sessionMapB == SESSION_COORD_BLOCKED then
return SESSION_COORD_DISTANCE -- one of the coords has been found to be blocked this during this session
elseif sessionMapA == SESSION_COORD_CLEAR and sessionMapA == SESSION_COORD_CLEAR then
return aStar.distance(a, b) -- both coords has been found to be clear this during this session
elseif mainMap:get(a) or mainMap:get(b) then
return MAIN_COORD_DISTANCE
end
return aStar.distance(a, b) -- we are assuming both coords are clear
end
local function getHeading()
local i = 0
while turtle.detect() do
if i == 4 then
if turtle.up() then
i = 0
else
error("help I'm trapped in a ridiculous place")
end
else
turtle.turnRight()
i = i + 1
end
end
local p1 = {gps.locate()}
if #p1 == 3 then
p1 = vector.new(unpack(p1))
else
error("no gps signal - phase 1")
end
i = 0
while not turtle.forward() do
if i > 5 then error("couldn't move to determine direction") end
i = i + 1
sleep(1)
end
local p2 = {gps.locate()}
turtle.back()
if #p2 == 3 then
p2 = vector.new(unpack(p2))
else
error("no gps signal - phase 2")
end
local dir = p2 - p1
if dir.x == 1 then
return 3
elseif dir.x == -1 then
return 1
elseif dir.z == 1 then
return 0
elseif dir.z == -1 then
return 2
else
error("could not determine direction - phase 3")
end
end
local function detect(currPos, adjPos)
local dir = adjPos - currPos
if dir.y == 1 then
return turtle.detectUp()
elseif dir.y == -1 then
return turtle.detectDown()
elseif dir.x == 1 then
position:setHeading(3)
return turtle.detect()
elseif dir.x == -1 then
position:setHeading(1)
return turtle.detect()
elseif dir.z == 1 then
position:setHeading(0)
return turtle.detect()
elseif dir.z == -1 then
position:setHeading(2)
return turtle.detect()
else
return false
end
end
local function inspect(currPos, adjPos)
local dir = adjPos - currPos
if dir.y == 1 then
return turtle.inspectUp()
elseif dir.y == -1 then
return turtle.inspectDown()
elseif dir.x == 1 then
position:setHeading(3)
return turtle.inspect()
elseif dir.x == -1 then
position:setHeading(1)
return turtle.inspect()
elseif dir.z == 1 then
position:setHeading(0)
return turtle.inspect()
elseif dir.z == -1 then
position:setHeading(2)
return turtle.inspect()
else
return false
end
end
local function updateCoord(coord, isBlocked)
if isBlocked then
sessionMap:set(pos, SESSION_COORD_BLOCKED)
mainMap:set(pos, MAIN_COORD_BLOCKED)
else
sessionMap:set(pos, SESSION_COORD_CLEAR)
mainMap:set(pos, MAIN_COORD_CLEAR)
end
end
local function detectAll(currPos)
for _, pos in ipairs(aStar.adjacent(currPos)) do -- better order of checking directions
updateCoord(pos, detect(currPos, pos))
end
end
local function findSensor()
for _, side in ipairs(peripheral.getNames()) do
if peripheral.getType(side) == "turtlesensorenvironment" then
return side
end
end
return false
end
local function scan(currPos)
local sensorSide = findSensor()
if sensorSide then
local rawBlockInfo = peripheral.call(sensorSide, "sonicScan")
local sortedBlockInfo = aStar.newMap()
for _, blockInfo in ipairs(rawBlockInfo) do
sortedBlockInfo:set(currPos + vector.new(blockInfo.x, blockInfo.y, blockInfo.z), blockInfo)
end
local toCheckQueue = {}
for _, pos in ipairs(aStar.adjacent(currPos)) do
if sortedBlockInfo:get(pos) then
table.insert(toCheckQueue, pos)
end
end
while toCheckQueue[1] do
local pos = table.remove(toCheckQueue, 1)
local blockInfo = sortedBlockInfo:get(pos)
if blockInfo.type == "AIR" then
for _, pos2 in ipairs(aStar.adjacent(pos)) do
local blockInfo2 = sortedBlockInfo:get(pos2)
if blockInfo2 and not blockInfo2.checked then
table.insert(toCheckQueue, pos2)
end
end
updateCoord(pos, false)
else
updateCoord(pos, true)
end
blockInfo.checked = true
end
else
detectAll(currPos)
end
mainMap:save()
end
local function move(currPos, adjPos)
if not detect(position, adjPos) then
local dir = adjPos - currPos
if dir.y == 1 then
return position:up()
elseif dir.y == -1 then
return position:down()
else
return position:forward()
end
else
return false
end
end
function goto(x, y, z, maxDistance)
if turtle.getFuelLevel() == 0 then
return false, "ran out of fuel"
end
if not position then
local heading = getHeading()
local currPos = {gps.locate()}
if #currPos == 3 then
local x, y, z = unpack(currPos)
position = location.new(x, y, z, heading)
else
return false, "couldn't determine location"
end
end
local goal = vector.new(tonumber(x), tonumber(y), tonumber(z))
sessionMap = aStar.newMap()
sessionMidPoint = vector.new(math.floor((goal.x + position.x)/2), math.floor((goal.y + position.y)/2), math.floor((goal.z + position.z)/2))
sessionMaxDistance = (type(maxDistance) == "number" and maxDistance) or math.max(2*sup_norm(sessionMidPoint, goal), SESSION_MAX_DISTANCE_DEFAULT)
local path = aStar.compute(distanceFunc, position, goal)
if not path then
return false, "no known path to goal"
end
while not aStar.vectorEquals(position, goal) do
local movePos = table.remove(path)
while not move(position, movePos) do
local blockPresent, blockData = inspect(position, movePos)
local recalculate, isTurtle = false, false
if blockPresent and (blockData.name == "ComputerCraft:CC-TurtleAdvanced" or blockData.name == "ComputerCraft:CC-Turtle") then -- there is a turtle in the way
sleep(math.random(0, 3))
local blockPresent2, blockData2 = inspect(position, movePos)
if blockPresent2 and (blockData2.name == "ComputerCraft:CC-TurtleAdvanced" or blockData2.name == "ComputerCraft:CC-Turtle") then -- the turtle is still there
sessionMap:set(movePos, SESSION_COORD_BLOCKED)
recalculate, isTurtle = true, true
end
elseif blockPresent then
scan(position) -- update map info
recalculate = true
elseif turtle.getFuelLevel() == 0 then
return false, "ran out of fuel"
else
sleep(1)
end
if recalculate then
if sessionMap:get(goal) == SESSION_COORD_BLOCKED then return false, "goal is blocked" end
path = aStar.compute(distanceFunc, position, goal)
if not path then
return false, "no known path to goal"
end
movePos = table.remove(path)
if isTurtle then
sessionMap:set(movePos, nil)
end
end
end
if mainMap:get(movePos) then
mainMap:set(movePos, MAIN_COORD_CLEAR)
end
end
return true
end
function getPosition()
if position then
return position:value()
end
end