Created
December 7, 2012 21:57
-
-
Save 4poc/4236849 to your computer and use it in GitHub Desktop.
Playing around with computercraft turtles
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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