-- Mine a hole of depth by width by heigth, starting from 0, 0, -1 relative to -- current position and facing -- +x = right of start, +y = up of start, +z = forward of start local FUEL_RESERVE = 800 local FUEL_REFUEL_UNIT = 8 -- how many items to refuel with at a time local FULL_CHECK_SLOT = 14 -- slot to check for "fullness" -- what size of a dimension is considered big enough to split into medium-size -- pieces local BIG_DIM = 7 local V_ZERO = vector.new(0, 0, 0) local UNIT_X = vector.new(1, 0, 0) local UNIT_Y = vector.new(0, 1, 0) local UNIT_Z = vector.new(0, 0, 1) local FORWARD, RIGHT, BACK, LEFT = 0, 1, 2, 3 local mode = { v = vector.new(0, 0, 0), facing = FORWARD } mode.mine = { forward = true } ---------------------- BLOCK MATH ------------------------ function copy_v(v) return vector.new( v.x, v.y, v.z ) end local bmetatable, new_block -- what is this? a type that holds a starting position and a dimention as -- two vectors -- TODO learn about metatables local Block = { -- orient our corner and offset to a vector orient = function (self, pos) self.v1 = self.v1 - pos local dims = { x = { self.v1.x, self.v1.x + self.v2.x }, y = { self.v1.y, self.v1.y + self.v2.y }, z = { self.v1.z, self.v1.z + self.v2.z } } for dim, points in pairs(dims) do -- pick the closer point -- return to point-offset format local point, offset if math.abs(points[1]) <= math.abs(points[2]) then point = points[1] offset = points[2] - points[1] else point = points[2] offset = points[1] - points[2] end self.v1[dim] = point self.v2[dim] = offset end self.v1 = self.v1 + pos return self end, take = function (self, amt, dir) -- TODO if dir ~= "x" and dir ~= "y" and dir ~= "z" then return nil end local block_offset = -1 if self.v2[dir] < 0 then amt = amt * -1 block_offset = 1 end local new, remainder = self:copy(), self new.v2[dir] = amt + block_offset remainder.v1[dir] = remainder.v1[dir] + amt remainder.v2[dir] = remainder.v2[dir] - amt return new, remainder end, copy = function (self) return new_block( vector.new(self.v1.x, self.v1.y, self.v1.z), vector.new(self.v2.x, self.v2.y, self.v2.z) ) end, tostring = function (self) -- TODO revisit? return string.format('%s %s', self.v1:tostring(), self.v2:tostring()) end } bmetatable = { __name = "Block", __index = Block, } new_block = function (v1, v2) return setmetatable( { v1 = v1, v2 = v2 }, bmetatable ) end ---------------------- BASIC MOVEMENT --------------------- function mineSides () if mode.mine.up then turtle.digUp() end if mode.mine.down then turtle.digDown() end end function moveInLine (delta) mineSides() while delta > 0 do if mode.mine.forward then turtle.dig() end local success = turtle.forward() if success then mineSides() delta = delta - 1 end end end function moveUpDown (delta) while delta > 0 do if mode.mine.forward then turtle.digUp() end local success = turtle.up() if success then delta = delta - 1 end end while delta < 0 do if mode.mine.forward then turtle.digDown() end local success = turtle.down() if success then delta = delta + 1 end end end function turnToFace (new_direction) local delta = new_direction - mode.facing if delta > 2 then delta = delta - 4 elseif delta < -2 then delta = delta + 4 end while delta > 0 do turtle.turnRight() delta = delta - 1 end while delta < 0 do turtle.turnLeft() delta = delta + 1 end mode.facing = new_direction end function getCurrentPos() return mode.v end -- move to a specific point function moveAbs (v) -- print(string.format("Moving to (%s)", v:tostring())) move(v - getCurrentPos()) end -- move relative to the turtle's current location function move (v) -- x if v.x > 0 then turnToFace(RIGHT) moveInLine(v.x) elseif v.x < 0 then turnToFace(LEFT) moveInLine(-v.x) end mode.v.x = mode.v.x + v.x -- z if v.z > 0 then turnToFace(FORWARD) moveInLine(v.z) end if v.z < 0 then turnToFace(BACK) moveInLine(-v.z) end mode.v.z = mode.v.z + v.z -- y moveUpDown(v.y) mode.v.y = mode.v.y + v.y end -------------- DROPPING OFF AND REFUELING ------------------- function refuelUntil (amt) while turtle.getFuelLevel() < amt do if not turtle.refuel(FUEL_REFUEL_UNIT) then break end end end function dropOffItems (starting_point, go_back) print("Time to drop off what I got") -- Turn off mining mode.mine = { forward = true } -- Note current position (assuming facing FORWARD) local pos_state = getCurrentPos() -- Return to origin, facing BACK moveAbs(starting_point) moveAbs(V_ZERO) turnToFace(BACK) -- deposit items for slot = 2, 16 do turtle.select(slot) turtle.drop() end turtle.select(1) -- Return to current position, facing forward if go_back then moveAbs(starting_point) moveAbs(pos_state) end end function inventoryFull () -- leave a little extra space just in case return turtle.getItemCount(FULL_CHECK_SLOT) > 0 end function checkAndDropOff(starting_point) if inventoryFull() then dropOffItems(starting_point, true) end refuelUntil(FUEL_RESERVE) end function mine (block) -- assumptions: block is mineable in a single pass and oriented to the -- currentPos -- rough algo draft: -- 1. go to "starting point" at one end of block -- a. don't move vertically if you don't have to -- b. set mining mode beforehand -- 2. move to the ending point local offset = 1 if block.v2.y < 0 then offset = -1 end local height = block.v2.y + offset local start_v = copy_v(block.v1) local end_v = block.v1 + block.v2 if height > 2 then start_v = start_v + UNIT_Y end_v = end_v - UNIT_Y elseif height < -2 then start_v = start_v - UNIT_Y end_v = end_v + UNIT_Y end moveAbs(start_v) if height % 3 == 0 then mode.mine = { up = true, forward = true, down = true } elseif height == 2 then mode.mine = { forward = true, up = true } end_v = end_v - UNIT_Y elseif height == -2 then mode.mine = { forward = true, down = true } end_v = end_v + UNIT_Y end moveAbs(end_v) end function canMine (block) local tooHigh = math.abs(block.v2.y) > 2 -- we can only mine in a 1-wide strip at a time local tooWide = (math.abs(block.v2.x) > 0) and (math.abs(block.v2.z) > 0) return not (tooHigh or tooWide) end function splitHorizontal (block) -- TODO make this less naive? -- Future work: potentially split a large block into smaller blocks -- based on currentPos (think splitting down the middle instead of -- splitting off of one end) local bigBlock = function (block) return (block.v2.x >= BIG_DIM) and (block.v2.z >= BIG_DIM) end local inMiddleOfBlock = function (block) local middle_of_x = differentSigns(block.v1.x, block.v2.x) local middle_of_z = differentSigns(block.v1.z, block.v2.z) return middle_of_x or middle_of_z end local inMiddleOfBigBlock = function (block) return bigBlock(block) and inMiddleOfBlock(block) end if inMiddleOfBigBlock(block) then -- TODO split the big block at where the turtle is near end -- decide whether to take a slice off of the x or z direction -- (criteria: take the dimension that is not too big) if math.abs(block.v2.z) < BIG_DIM then return block:take(1, "z") else return block:take(1, "x") end end function split (block) local tooHigh = function (block) return math.abs(block.v2.y) > 2 end if tooHigh(block) then return block:take(3, "y") else return splitHorizontal(block) end end function process (stack, starting_point) -- fuel and go to starting point refuelUntil(FUEL_RESERVE) mode.mine = { forward = true } if #stack == 0 then return nil end local working = table.remove(stack):orient(getCurrentPos()) print("Working:", working:tostring()) if canMine(working) then checkAndDropOff(starting_point) mine(working) else local new, remainder = split(working, mode.v) table.insert(stack, remainder) table.insert(stack, new) end return process (stack, starting_point) end function run (v1, v2) -- TODO calculate starting_point, the place turtle will move to before -- moving to the first corner of block local block = new_block(v1, v2 - v1) local starting_point = V_ZERO process({ block }, starting_point) -- return to base dropOffItems(starting_point) turnToFace(FORWARD) end function usage () print("Usage: mine x1 y1 z1 x2 y2 z2 OR mine x1 y1 z1 OR mine x1 y1 z1 \ x2 y2 z2 x_turtle y_turtle z_turtle.") print("Mine from the first set of coordinates to the second in a \ rectangular prism.") print("The coordinates are relative to the facing of the turtle at the \ start of the program.") print("If invoked with only three arguments, act as if they are the last \ three arguments and substitute 0 0 0 for the first three.") print("If invoked with nine arguments, interpret the first two sets as \ world coordinates of the rectangular prism and the third set as \ the coordinates of the turtle's starting position, \ ASSUMING FACING NORTH.") print("Fuel goes in the top-left slot of the turtle's inventory.") end function argsToNumbers () local num_args = {} for i, v in ipairs(arg) do num_args[i] = tonumber(v) end return num_args end local arg = argsToNumbers() if #arg == 3 then run(V_ZERO, vector.new(arg[1], arg[2], arg[3])) elseif #arg == 6 then run(vector.new(arg[1], arg[2], arg[3]), vector.new(arg[4], arg[5], arg[6])) elseif #arg == 9 then local turtle_pos = vector.new(arg[7], arg[8], arg[9]) local v1, v2 = vector.new(arg[1], arg[2], arg[3]) , vector.new(arg[4], arg[5], arg[6]) run(v1 - turtle_pos, v2 - turtle_pos) else usage() end