diff --git a/mine.lua b/mine.lua index 8aa1cea..a3af8e8 100644 --- a/mine.lua +++ b/mine.lua @@ -1,91 +1,20 @@ -- 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 +-- +x = right of start, +y = forward of start, +z = up 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" - -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 } +local mode = { x = 0, y = 0, z = 0, facing = FORWARD } mode.mine = { forward = true } +local block = {} ----------------------- BLOCK MATH ------------------------ - -function copy_v(v) - return vector.new( - v.x, - v.y, - v.z, - ) -end - -local bmetatable --- 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) - 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 - return self - end, - take = function (self, amt, dir) - -- TODO - if dir ~= "x" and dir ~= "y" and dir ~= "z" then return nil end - - local new, remainder = self:copy(), self - new.v1[dir] = amt - remainder.v1[dir] = remainder.v1[dir] + amt - remainder.v2[dir] = remainder.v2[dir] - amt - - return new, remainder - end, - copy = function (self) - return Block.new( - vector.new(self.v1.x, self.v1.y, self.v1.z), - vector.new(self.v2.x, self.v2.y, self.v2.z), - ) +function refuelUntil (amt) + while turtle.getFuelLevel() < amt do + if not turtle.refuel(8) then break end end -} - -bmetatable = { - __name = "Block", - __index = Block, -} - -local new_block = function (v1, v2) - return setmetatable( - { v1 = v1, v2 = v2 }, - bmetatable - ) end ----------------------- BASIC MOVEMENT --------------------- - function mineSides () if mode.mine.up then turtle.digUp() @@ -110,6 +39,7 @@ function moveInLine (delta) end function moveUpDown (delta) + local delta = delta while delta > 0 do if mode.mine.forward then turtle.digUp() @@ -148,58 +78,47 @@ function turnToFace (new_direction) mode.facing = new_direction end -function getCurrentPos() - return mode.v -end - -- move to a specific point -function moveAbs (v) - -- print(string.format("Moving to (%d, %d, %d)", v.x, v.y, v.z)) - move(v - getCurrentPos()) +function moveAbs (x, y, z) + -- print(string.format("Moving to (%d, %d, %d)", x, y, z)) + move(x - mode.x, y - mode.y, z - mode.z) end -- move relative to the turtle's current location -function move (v) +function move (x, y, z) -- x - if v.x > 0 then + if x > 0 then turnToFace(RIGHT) - moveInLine(v.x) - elseif v.x < 0 then + moveInLine(x) + elseif x < 0 then turnToFace(LEFT) - moveInLine(-v.x) + moveInLine(-x) end - mode.v = mode.v + UNIT_X + mode.x = mode.x + x -- z - if v.z > 0 then + if z > 0 then turnToFace(FORWARD) - moveInLine(v.z) + moveInLine(z) end - if v.z < 0 then + if z < 0 then turnToFace(BACK) - moveInLine(-v.z) + moveInLine(-z) end - mode.v = mode.v + UNIT_Z + mode.z = mode.z + z -- y - moveUpDown(v.y) - mode.v = mode.v + UNIT_Y + moveUpDown(y) + mode.y = mode.y + 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) +function dropOffItems (go_back) -- Turn off mining + local mine_state = mode.mine mode.mine = { forward = true } -- Note current position (assuming facing FORWARD) - local pos_state = vector.new(mode.v.x, mode.v.y, mode.v.z) + local pos_state = { x = mode.x, y = mode.y, z = mode.z } -- Return to origin, facing BACK - moveAbs(starting_point) - moveAbs(V_ZERO) + moveAbs(mode.x, mode.y, 0) + moveAbs(0, 0, 0) turnToFace(BACK) -- deposit items for slot = 2, 16 do @@ -208,102 +127,124 @@ function dropOffItems (starting_point, go_back) end turtle.select(1) -- Return to current position, facing forward - if go_back then - moveAbs(starting_point) - moveAbs(pos_state) - end + if go_back then moveAbs(pos_state.x, pos_state.y, pos_state.z) end + -- Turn mining back on, if it was on + mode.mine = mine_state end function inventoryFull () -- leave a little extra space just in case - return turtle.getItemCount(FULL_CHECK_SLOT) > 0 + return turtle.getItemCount(15) > 0 end -function checkAndDropOff(starting_point) - if inventoryFull() then dropOffItems(starting_point, true) end +function moveAndCheck (x, y, z) + move(x, y, z) + if inventoryFull() then dropOffItems(true) end refuelUntil(FUEL_RESERVE) end +function generateNextSteps (w, l) + -- assumptions: + -- 0 for l or w means don't move in that axis + -- 1 for w means mine on right side, -1 means left side + -- any greater absolute value of w assumes that the turtle starts in a + -- column of three and can mine on both sides out of the gate + local DIRECTION = {} + if w < 0 then DIRECTION.X = -1 else DIRECTION.X = 1 end + if l < 0 then DIRECTION.Y = -1 else DIRECTION.Y = 1 end + local remaining_width = math.abs(w) + 1 + local column_len = math.abs(l) + local step_buffer = {} + return function () + local next_step = {} + if #step_buffer == 0 then + if remaining_width < 1 then return nil end + -- Algorithm: + -- 1. mine l blocks in the y-direction + next_step = { x = 0, y = DIRECTION.Y * column_len } + table.insert(step_buffer, next_step) + + DIRECTION.Y = -DIRECTION.Y + + remaining_width = remaining_width - 1 + + next_step = { x = 0, y = 0 } + if remaining_width > 0 then + -- 2. go in the x direction for the next y-sweep + next_step.x = DIRECTION.X + table.insert(step_buffer, next_step) + end + end + next_step = table.remove(step_buffer, 1) + return next_step.x, next_step.y + end +end + +function minePlane (w, l, h) + -- mine a plane starting from the current location l blocks in the + -- y-direction and w blocks in the x-direction + -- print(string.format("Mining a plane of %d by %d by %d", w, l, h)) + mode.mine = { forward = true } + if h == 1 then + mode.mine.up = true + elseif h == -1 then + mode.mine.down = true + elseif h ~= 0 then + mode.mine.up = true + mode.mine.down = true + end + for x, y in generateNextSteps(w, l) do + -- print(string.format("Moving %d, %d relative to current pos.", + -- x, y)) + moveAndCheck(x, y, 0) + end +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 height = block.v2.y - if height % 3 == 0 then - mode.mine = { up = true, forward = true, down = true } - elseif height == 2 then - mode.mine = { forward = true, up = true } - elseif height == -2 then - mode.mine = { forward = true, down = true } - end - 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 - move(start_v) - move(end_v) -end - -function canMine (block) - local tooHigh = block.v2.y > 3 - -- we can only mine in a 1-wide strip at a time - local tooWide = block.v2.x > 1 and block.v2.z > 1 - 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) - -- Future work: decide whether to take a slice off of the x or z direction - -- (criteria?) - return block:take(1, "x") -end - -function split (block) - local tooHigh = function (block) - return block.v2.y > 3 - end - block = block:orient(getCurrentPos()) - if tooHigh(block) then - return block:take(3, "y") - else - return splitHorizontal(block) - end -end - -function process (stack, starting_point) - -- START code taken from `mine` - -- fuel and go to starting point + -- fuel and go to starting corner refuelUntil(FUEL_RESERVE) mode.mine = { forward = true } - moveAbs(starting_point) - -- END - -- TODO - if #stack == 0 then return nil end - local working = table.remove(stack):orient(getCurrentPos()) - if canMine(working) then - checkAndDropOff(starting_point) - mine(working) + moveAbs(block.x, block.y, block.z) + local z_dir + if block.zoff > 1 then z_dir = 1 else z_dir = -1 end + local remaining_h = math.abs(block.z + block.zoff - mode.z) + 1 + -- Initial pass; leave remaining_h at a nice multiple of 3 + local excess = remaining_h % 3 + if excess > 0 then + minePlane(block.xoff, block.yoff, (excess - 1) * z_dir) + if excess == 1 then + move(0, 0, -z_dir) + end + remaining_h = remaining_h - excess else - local new, remainder = split(working, mode.v) - table.insert(stack, remainder) - table.insert(stack, new) + move(0, 0, z_dir) + minePlane(block.xoff, block.yoff, 3) + remaining_h = remaining_h - 3 + end + -- Start mining planes + while remaining_h > 0 do + -- calculate orientation of next plane + local w, l + if mode.y == block.y then + l = block.yoff + else + l = -block.yoff + end + local diff_from_start = math.abs(mode.x - block.x) + -- print(string.format("Distance from start: %d", diff_from_start)) + if mode.x == block.x then + w = block.xoff + else + w = -block.xoff + end + -- move down into the new plane and mine it + move(0, 0, z_dir * 3) + minePlane(w, l, 3) + remaining_h = remaining_h - 3 end - return process (stack) end -function calculateBlock (v1, v2) +function calculateBlock (x1, y1, z1, x2, y2, z2) -- Using the coordinates, construct a block of certain dimensions, a -- certain distance away. local block = {} @@ -319,39 +260,30 @@ function calculateBlock (v1, v2) block[dim] = corner block[dim .. "off"] = offset end - normalize("x", v1.x, v2.x) - normalize("y", v1.y, v2.y) - normalize("z", v1.z, v2.z) - return new_block( - vector.new(block.x, block.y, block.z) - vector.new(block.xoff, block.yoff, block.zoff) - ) + normalize("x", x1, x2) + normalize("y", y1, y2) + normalize("z", z1, z2) + return block 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) - process({ block }) +function run (x1, y1, z1, x2, y2, z2) + local block = calculateBlock(x1, y1, z1, x2, y2, z2) + mine(block) -- return to base - dropOffItems(starting_point) + mode.mine = {} + dropOffItems() 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("Usage: mine x1 y1 z1 x2 y2 z2 OR mine x1 y1 z1") 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.") + three arguments and substitute 0 0 -1 for the first three.") + print("Fuel goes in the top-left slot of the turtle's inventory") end function argsToNumbers () @@ -364,14 +296,12 @@ end local arg = argsToNumbers() if #arg == 3 then - run(V_ZERO, vector.new(arg[1], arg[2], arg[3])) + -- TODO make the 3 arg version the same as it used to be + -- i.e. `mine 2 3 1` digs a 2-wide, 3-long, 1-deep hole starting under the + -- bot + run(0, 0, -1, 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) + run(arg[1], arg[2], arg[3], arg[4], arg[5], arg[6]) else usage() end