Skip to content

Instantly share code, notes, and snippets.

@4poc
Created December 7, 2012 21:57
Show Gist options
  • Save 4poc/4236849 to your computer and use it in GitHub Desktop.
Save 4poc/4236849 to your computer and use it in GitHub Desktop.
Playing around with computercraft turtles
-- Stack (for Lua 5.1)#http://snippets.luacode.org/snippets/stack_97
function NewStack(t)
local Stack = {
push = function(self, ...)
for _, v in ipairs{...} do
self[#self+1] = v
end
end,
pop = function(self, num)
local num = num or 1
if num > #self then
-- error("underflow in NewStack-created stack")
return nil
end
local ret = {}
for i = num, 1, -1 do
ret[#ret+1] = table.remove(self)
end
return unpack(ret)
end
}
return setmetatable(t or {}, {__index = Stack})
end
function SplitString(str, sep)
local stack = NewStack()
repeat
-- BUG: http://computercraft.info/wiki/index.php?title=String_%28API%29
local i = string.find(str..'', sep)
if i then
stack:push(string.sub(str, 0, i - 1))
str = string.sub(str, i + 1)
else
stack:push(str)
end
until i == nil
return stack
end
function JoinStack(stack, sep)
local str = ''
for i=1,#stack do
str = str..stack[i]
if i ~= #stack then
str = str..sep
end
end
return str
end
function SerializeVector(vec)
return JoinStack({
vec.x,
vec.y,
vec.z
}, ',')
end
function UnserializeVector(str)
vector_data = SplitString(str, ',')
return vector.new(tonumber(vector_data[1]), tonumber(vector_data[2]), tonumber(vector_data[3]))
end
FACING_SOUTH = 0 -- south (x=, y=, z+)
FACING_WEST = 1 -- west (x-, y=, z=)
FACING_NORTH = 2 -- north (x=, y=, z-)
FACING_EAST = 3 -- east (x+, y=, z=)
WALKER_FILE = 'mole_walker.dat'
-- if there is a mob or player in front of the bot, it will
-- try 5 times before giving up (with 1 second delay inbetween)
BLOCKED_WAITS = 5
-- describes a path moved by the bot
Path = {}
Path.__index = Path
function Path.new(name, parent)
local obj = {}
setmetatable(obj, Path)
-- name of this walkpath
obj.name = name or 'origin'
-- edges of directional normalized vectors
obj.edges = NewStack()
-- parent path
obj.parent = parent or nil
return obj
end
function Path:add(dir)
self.edges:push(dir)
end
function Path:newBranch(name)
print('new path branch '..name)
return Path.new(name, self)
end
-- traverse back through all path edges (recursivly) up to the
-- start of <name>-path, calls go_fnk for each edge,
-- removes the edge when go_fnk returns true
-- stops if go_fnk returns false except when visit_all is set to true then
-- it won't delete edges
function Path:backtrack(name, go_fnk, visit_all)
name = name or self.name
local count = #self.edges
for i=1,count do
local idx = count - (i-1)
local dir = self.edges[idx]
dir = dir * -1
if go_fnk(dir, self.name, idx) then
print('remove edge')
self.edges:pop()
elseif not visit_all then
return false
end
end
if self.parent and self.name ~= name then
print('Recursive backtracking for parent.')
return self.parent:backtrack(name, go_fnk, visit_all)
end
return true
end
-- returns the distance in blocks to the root of the path specified by name
function Path:distanceTo(name, idx)
local distance = 0
self:backtrack(name, function (dir, cur_name, cur_idx)
if idx and name == cur_name and cur_idx < idx then
return false -- stop here
end
distance = distance + 1
return false -- do not move or remove path edge!
end)
return distance
end
-- traverse down the path and returns the first non-empty path or origin
function Path:getFirstNonEmptyPath()
if not self.parent or #self.edges > 0 then
return self
elseif self.parent and #self.edges == 0 then
return self.parent:getFirstNonEmptyPath()
end
end
function Path:serializePoints()
if #self.edges == 0 then
return 'nil'
end
local data = NewStack()
for i=1,#self.edges do
data:push(SerializeVector(self.edges[i]))
end
return JoinStack(data,'|')
end
function Path:unserializePoints(str)
local stack = NewStack()
if str == 'nil' then
return stack
end
data = SplitString(str, '|')
for i=1,#data do
stack:push(UnserializeVector(data[i]))
end
return stack
end
function Path:save(file)
file.write(tostring(self.name)..'\n')
file.write(self:serializePoints())
file.write('\n')
if self.parent then
self.parent:save(file)
end
end
function Path:load(file)
local name = file.readLine()
if not name then
return false
end
self.name = name
self.edges = self:unserializePoints(file.readLine())
-- try to load parent path:
local parent_path = Path.new()
if parent_path:load(file) then
-- print('parent walker init.. '..#parent_walker.cache)
self.parent = parent_path
end
return true
end
function Path:printDebug()
if #self.edges == 0 then
print(self.name..' -> no path entries')
else
local tmp = NewStack()
for i=1,#self.edges do
tmp:push(self.edges[i]:tostring())
end
print(self.name..' -> '..JoinStack(tmp, ' '))
end
if self.parent then
self.parent:printDebug()
end
end
-- walking api, keeps track of current position and memorizes
-- paths also memorizes and manages fuel level
Walker = {}
Walker.__index = Walker
function Walker.new()
local obj = {}
setmetatable(obj, Walker)
-- the current position of the bot
obj.position = vector.new(0, 0, 0)
obj.facing = FACING_SOUTH
-- remember movements
obj.path = Path.new()
-- internals
-- counter of times waited to blocked path to free itself
obj.blockedWaited = 0 -- see also BLOCKED_WAITS
return obj
end
function Walker:north(distance)
distance = distance or 1
return self:go(vector.new(0, 0, -1 * distance))
end
function Walker:south(distance)
distance = distance or 1
return self:go(vector.new(0, 0, distance))
end
function Walker:east(distance)
distance = distance or 1
return self:go(vector.new(distance, 0, 0))
end
function Walker:west(distance)
distance = distance or 1
return self:go(vector.new(-1 * distance, 0, 0))
end
function Walker:up(distance)
distance = distance or 1
return self:go(vector.new(0, distance, 0))
end
function Walker:down(distance)
distance = distance or 1
return self:go(vector.new(0, -1 * distance, 0))
end
-- smart goto implementation
function Walker:goto(x, y, z)
local dst = vector.new(x, y, z)
-- traverse down through path list to search for closest edge to the dst
-- by searching the minimum of the distance between the edges and the dest
local pos = self.position
local min_dist = nil
local min_name
local min_idx
self.path:backtrack('origin', function (dir, name, idx)
pos = pos + dir
dist = (dst - pos):length() -- distance between current pos and destination
if not min_dist or min_dist > dist then
min_dist = dist
min_name = name
min_idx = idx
end
return false -- so that it doesnt remove any edges!
end, true) -- visit all so that it visits all and never stops
if not min_dist then
print('FATAL: this should never happen!')
return false
end
if ((dst - self.position):length()) < min_dist then
-- we are currently already the closest to the destination, no
-- need to backtrack!
else
-- backtrack to the closest point on the path
self:backtrack(min_name, min_idx)
end
end
-- turns the bot in the direction supplied, with the least possible turns
function Walker:turn(new_facing)
if facing == self.facing then
return
end
-- print('facing turn: '..(self.facing)..' => '..new_facing)
local off = self.facing - new_facing
local op = 'turnRight'
-- print("off = "..off)
if off < -2 or off == 1 then
op = 'turnLeft'
end
while new_facing ~= self.facing do
if not turtle[op]() then
-- this should never happen? turns dont need fuel!
print('FATAL: Failed to '..op)
return false
end
if op == 'turnRight' then
if self.facing == 3 then
self.facing = 0
else
self.facing = self.facing + 1
end
else
if self.facing == 0 then
self.facing = 3
else
self.facing = self.facing - 1
end
end
end
end
function Walker:requireFuel(distance)
if self.callbackRequireFuel then
return self.callbackRequireFuel(distance)
else
return true
end
end
-- this method moves the bot along the gone path in the cache,
-- back to the starting postion of this walker node
function Walker:backtrack(name, stop_idx)
stop_idx = stop_idx or 0
distance = self.path:distanceTo(name)
if name ~= 'origin' then
distance = distance*2 + self.path:distanceTo('origin')
end
if not self:requireFuel(distance) then
print('FATAL: cannot move not enough fuel available!')
return false
end
self.path:backtrack(name, function (dir, cur_name, cur_idx)
ret = self:go(dir, true)
print('cur_name => '..cur_name)
print('cur_idx => '..cur_idx)
if cur_name == name and cur_idx == stop_idx then
return false
end
return ret
end)
-- cleanup: remove empty path nodes
self.path = self.path:getFirstNonEmptyPath()
end
function Walker:move(dir, distance, backtracking)
if not self:requireFuel(distance*2 + self.path:distanceTo('origin')) then
print('FATAL: cannot move not enough fuel available!')
return false
end
local op = 'forward'
if dir.y > 0 then
op = 'up'
elseif dir.y < 0 then
op = 'down'
end
for i=1,distance do
if turtle[op]() then
-- change position and remember movement
self.position = self.position + dir
-- print('backtracking = '..self.path.backtracking)
-- print('add to path: '..dir:tostring())
print(self.path)
if not backtracking then
print('add to path: '..dir:tostring())
self.path:add(dir)
end
write(op..' movement to position: ')
print(walk.position)
self.blockedWaited = 0
else
-- MINING mode etc.
if not turtle.detect() then
-- entity in front probl.
end
if self:waitForBlocking() then
return self:move(dir, i)
end
print('WARNING: cannot move')
return false
end
end
return true
end
function Walker:go(dir, backtracking)
local normalDir = dir:normalize()
if dir.x > 0 then
self:turn(FACING_EAST)
return self:move(normalDir, dir.x, backtracking)
elseif dir.x < 0 then
self:turn(FACING_WEST)
return self:move(normalDir, dir.x * -1, backtracking)
end
if dir.y > 0 then
return self:move(normalDir, dir.y, backtracking)
elseif dir.y < 0 then
return self:move(normalDir, dir.y * -1, backtracking)
end
if dir.z > 0 then
self:turn(FACING_SOUTH)
return self:move(normalDir, dir.z, norm, backtracking)
elseif dir.z < 0 then
self:turn(FACING_NORTH)
return self:move(normalDir, dir.z * -1, backtracking)
end
end
function Walker:newBranch(name)
print('open new branch with name = '..name)
self.path = self.path:newBranch(name)
end
function Walker:waitForBlocking()
write('Waiting for blocked path to free...')
print(self.blockedWaited)
self.blockedWaited = self.blockedWaited + 1
if (self.blockedWaited < BLOCKED_WAITS) then
os.sleep(1)
return true
else
return false
end
end
function Walker:save()
file = fs.open(WALKER_FILE, 'w')
file.write(SerializeVector(self.position)..'\n')
file.write(self.facing..'\n')
self.path:save(file)
file.close()
end
function Walker:load()
file = fs.open(WALKER_FILE, 'r')
if not file then
return false
end
self.position = UnserializeVector(file.readLine())
self.facing = tonumber(file.readLine())
self.path:load(file)
file.close()
end
function Walker:printDebug()
print('Walker pos: '..self.position:tostring()..' (f: '..self.facing..')')
self.path:printDebug()
end
-- keeps track of the inventory of the bot
Inventory = {}
Inventory.__index = Inventory
function Inventory.new()
local obj = {}
setmetatable(obj, Inventory)
return obj
end
-- function Inventory:
print 'Hold Ctrl+T to terminate program.'
walk = Walker.new()
walk:load()
walk.callbackRequireFuel = function (needed)
fuelLevel = turtle.getFuelLevel()
needed = needed - fuelLevel
if needed > 0 then
print('fuel requirement: '..needed..' vs. '..fuelLevel)
-- gather more fuel...
end
if needed > 0 then
return false
end
return true
end
-- walk:south(8)
-- walk:up(2)
--walk:north(8)
-- walk:newBranch('branch')
-- walk:west(4)
-- walk:south(32)
-- sleep(3)
walk:printDebug()
-- walk:goto(0, 1, 8)
walk:backtrack('origin')
walk:save()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment