Compare commits

..

2 commits

Author SHA1 Message Date
Emerson Rosen-Jones
d0ab016dcf mine.lua: rewrite checkpoint 1 2025-12-01 19:58:46 -05:00
Emerson Rosen-Jones
4f0876fb64 Start of mine.lua rewrite 2025-12-01 09:25:20 -05:00

370
mine.lua
View file

@ -1,20 +1,91 @@
-- Mine a hole of depth by width by heigth, starting from 0, 0, -1 relative to -- Mine a hole of depth by width by heigth, starting from 0, 0, -1 relative to
-- current position and facing -- current position and facing
-- +x = right of start, +y = forward of start, +z = up of start -- +x = right of start, +y = up of start, +z = forward of start
local FUEL_RESERVE = 800 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 FORWARD, RIGHT, BACK, LEFT = 0, 1, 2, 3
local mode = { x = 0, y = 0, z = 0, facing = FORWARD } local mode = { v = vector.new(0, 0, 0), facing = FORWARD }
mode.mine = { forward = true } mode.mine = { forward = true }
local block = {}
function refuelUntil (amt) ---------------------- BLOCK MATH ------------------------
while turtle.getFuelLevel() < amt do
if not turtle.refuel(8) then break end function copy_v(v)
end return vector.new(
v.x,
v.y,
v.z,
)
end 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),
)
end
}
bmetatable = {
__name = "Block",
__index = Block,
}
local new_block = function (v1, v2)
return setmetatable(
{ v1 = v1, v2 = v2 },
bmetatable
)
end
---------------------- BASIC MOVEMENT ---------------------
function mineSides () function mineSides ()
if mode.mine.up then if mode.mine.up then
turtle.digUp() turtle.digUp()
@ -39,7 +110,6 @@ function moveInLine (delta)
end end
function moveUpDown (delta) function moveUpDown (delta)
local delta = delta
while delta > 0 do while delta > 0 do
if mode.mine.forward then if mode.mine.forward then
turtle.digUp() turtle.digUp()
@ -78,47 +148,58 @@ function turnToFace (new_direction)
mode.facing = new_direction mode.facing = new_direction
end end
function getCurrentPos()
return mode.v
end
-- move to a specific point -- move to a specific point
function moveAbs (x, y, z) function moveAbs (v)
-- print(string.format("Moving to (%d, %d, %d)", x, y, z)) -- print(string.format("Moving to (%d, %d, %d)", v.x, v.y, v.z))
move(x - mode.x, y - mode.y, z - mode.z) move(v - getCurrentPos())
end end
-- move relative to the turtle's current location -- move relative to the turtle's current location
function move (x, y, z) function move (v)
-- x -- x
if x > 0 then if v.x > 0 then
turnToFace(RIGHT) turnToFace(RIGHT)
moveInLine(x) moveInLine(v.x)
elseif x < 0 then elseif v.x < 0 then
turnToFace(LEFT) turnToFace(LEFT)
moveInLine(-x) moveInLine(-v.x)
end end
mode.x = mode.x + x mode.v = mode.v + UNIT_X
-- z -- z
if z > 0 then if v.z > 0 then
turnToFace(FORWARD) turnToFace(FORWARD)
moveInLine(z) moveInLine(v.z)
end end
if z < 0 then if v.z < 0 then
turnToFace(BACK) turnToFace(BACK)
moveInLine(-z) moveInLine(-v.z)
end end
mode.z = mode.z + z mode.v = mode.v + UNIT_Z
-- y -- y
moveUpDown(y) moveUpDown(v.y)
mode.y = mode.y + y mode.v = mode.v + UNIT_Y
end end
function dropOffItems (go_back) -------------- 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)
-- Turn off mining -- Turn off mining
local mine_state = mode.mine
mode.mine = { forward = true } mode.mine = { forward = true }
-- Note current position (assuming facing FORWARD) -- Note current position (assuming facing FORWARD)
local pos_state = { x = mode.x, y = mode.y, z = mode.z } local pos_state = vector.new(mode.v.x, mode.v.y, mode.v.z)
-- Return to origin, facing BACK -- Return to origin, facing BACK
moveAbs(mode.x, mode.y, 0) moveAbs(starting_point)
moveAbs(0, 0, 0) moveAbs(V_ZERO)
turnToFace(BACK) turnToFace(BACK)
-- deposit items -- deposit items
for slot = 2, 16 do for slot = 2, 16 do
@ -127,124 +208,102 @@ function dropOffItems (go_back)
end end
turtle.select(1) turtle.select(1)
-- Return to current position, facing forward -- Return to current position, facing forward
if go_back then moveAbs(pos_state.x, pos_state.y, pos_state.z) end if go_back then
-- Turn mining back on, if it was on moveAbs(starting_point)
mode.mine = mine_state moveAbs(pos_state)
end
end end
function inventoryFull () function inventoryFull ()
-- leave a little extra space just in case -- leave a little extra space just in case
return turtle.getItemCount(15) > 0 return turtle.getItemCount(FULL_CHECK_SLOT) > 0
end end
function moveAndCheck (x, y, z) function checkAndDropOff(starting_point)
move(x, y, z) if inventoryFull() then dropOffItems(starting_point, true) end
if inventoryFull() then dropOffItems(true) end
refuelUntil(FUEL_RESERVE) refuelUntil(FUEL_RESERVE)
end 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) function mine (block)
-- fuel and go to starting corner -- assumptions: block is mineable in a single pass and oriented to the
refuelUntil(FUEL_RESERVE) -- currentPos
mode.mine = { forward = true } -- rough algo draft:
moveAbs(block.x, block.y, block.z) -- 1. go to "starting point" at one end of block
local z_dir -- a. don't move vertically if you don't have to
if block.zoff > 1 then z_dir = 1 else z_dir = -1 end -- b. set mining mode beforehand
local remaining_h = math.abs(block.z + block.zoff - mode.z) + 1 -- 2. move to the ending point
-- Initial pass; leave remaining_h at a nice multiple of 3 local height = block.v2.y
local excess = remaining_h % 3 if height % 3 == 0 then
if excess > 0 then mode.mine = { up = true, forward = true, down = true }
minePlane(block.xoff, block.yoff, (excess - 1) * z_dir) elseif height == 2 then
if excess == 1 then mode.mine = { forward = true, up = true }
move(0, 0, -z_dir) elseif height == -2 then
end mode.mine = { forward = true, down = true }
remaining_h = remaining_h - excess
else
move(0, 0, z_dir)
minePlane(block.xoff, block.yoff, 3)
remaining_h = remaining_h - 3
end end
-- Start mining planes local start_v = copy_v(block.v1)
while remaining_h > 0 do local end_v = block.v1 + block.v2
-- calculate orientation of next plane if height > 2 then
local w, l start_v = start_v + UNIT_Y
if mode.y == block.y then end_v = end_v + UNIT_Y
l = block.yoff elseif height < -2 then
else start_v = start_v - UNIT_Y
l = -block.yoff end_v = end_v - UNIT_Y
end end
local diff_from_start = math.abs(mode.x - block.x) move(start_v)
-- print(string.format("Distance from start: %d", diff_from_start)) move(end_v)
if mode.x == block.x then end
w = block.xoff
else function canMine (block)
w = -block.xoff local tooHigh = block.v2.y > 3
end -- we can only mine in a 1-wide strip at a time
-- move down into the new plane and mine it local tooWide = block.v2.x > 1 and block.v2.z > 1
move(0, 0, z_dir * 3) return not (tooHigh or tooWide)
minePlane(w, l, 3) end
remaining_h = remaining_h - 3
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
end end
function calculateBlock (x1, y1, z1, x2, y2, z2) function process (stack, starting_point)
-- START code taken from `mine`
-- fuel and go to starting point
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)
else
local new, remainder = split(working, mode.v)
table.insert(stack, remainder)
table.insert(stack, new)
end
return process (stack)
end
function calculateBlock (v1, v2)
-- Using the coordinates, construct a block of certain dimensions, a -- Using the coordinates, construct a block of certain dimensions, a
-- certain distance away. -- certain distance away.
local block = {} local block = {}
@ -260,30 +319,39 @@ function calculateBlock (x1, y1, z1, x2, y2, z2)
block[dim] = corner block[dim] = corner
block[dim .. "off"] = offset block[dim .. "off"] = offset
end end
normalize("x", x1, x2) normalize("x", v1.x, v2.x)
normalize("y", y1, y2) normalize("y", v1.y, v2.y)
normalize("z", z1, z2) normalize("z", v1.z, v2.z)
return block return new_block(
vector.new(block.x, block.y, block.z)
vector.new(block.xoff, block.yoff, block.zoff)
)
end end
function run (x1, y1, z1, x2, y2, z2) function run (v1, v2)
local block = calculateBlock(x1, y1, z1, x2, y2, z2) -- TODO calculate starting_point, the place turtle will move to before
mine(block) -- moving to the first corner of block
local block = new_block(v1, v2 - v1)
process({ block })
-- return to base -- return to base
mode.mine = {} dropOffItems(starting_point)
dropOffItems()
turnToFace(FORWARD) turnToFace(FORWARD)
end end
function usage () function usage ()
print("Usage: mine x1 y1 z1 x2 y2 z2 OR mine x1 y1 z1") 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 \ print("Mine from the first set of coordinates to the second in a \
rectangular prism.") rectangular prism.")
print("The coordinates are relative to the facing of the turtle at the \ print("The coordinates are relative to the facing of the turtle at the \
start of the program.") start of the program.")
print("If invoked with only three arguments, act as if they are the last \ print("If invoked with only three arguments, act as if they are the last \
three arguments and substitute 0 0 -1 for the first three.") three arguments and substitute 0 0 0 for the first three.")
print("Fuel goes in the top-left slot of the turtle's inventory") 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 end
function argsToNumbers () function argsToNumbers ()
@ -296,12 +364,14 @@ end
local arg = argsToNumbers() local arg = argsToNumbers()
if #arg == 3 then if #arg == 3 then
-- TODO make the 3 arg version the same as it used to be run(V_ZERO, vector.new(arg[1], arg[2], arg[3]))
-- 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 elseif #arg == 6 then
run(arg[1], arg[2], arg[3], arg[4], arg[5], arg[6]) 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 else
usage() usage()
end end