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

368
mine.lua
View file

@ -1,20 +1,91 @@
-- 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 = 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_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 = { x = 0, y = 0, z = 0, facing = FORWARD }
local mode = { v = vector.new(0, 0, 0), facing = FORWARD }
mode.mine = { forward = true }
local block = {}
function refuelUntil (amt)
while turtle.getFuelLevel() < amt do
if not turtle.refuel(8) then break end
end
---------------------- 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),
)
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()
@ -39,7 +110,6 @@ function moveInLine (delta)
end
function moveUpDown (delta)
local delta = delta
while delta > 0 do
if mode.mine.forward then
turtle.digUp()
@ -78,47 +148,58 @@ function turnToFace (new_direction)
mode.facing = new_direction
end
function getCurrentPos()
return mode.v
end
-- move to a specific point
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)
function moveAbs (v)
-- print(string.format("Moving to (%d, %d, %d)", v.x, v.y, v.z))
move(v - getCurrentPos())
end
-- move relative to the turtle's current location
function move (x, y, z)
function move (v)
-- x
if x > 0 then
if v.x > 0 then
turnToFace(RIGHT)
moveInLine(x)
elseif x < 0 then
moveInLine(v.x)
elseif v.x < 0 then
turnToFace(LEFT)
moveInLine(-x)
moveInLine(-v.x)
end
mode.x = mode.x + x
mode.v = mode.v + UNIT_X
-- z
if z > 0 then
if v.z > 0 then
turnToFace(FORWARD)
moveInLine(z)
moveInLine(v.z)
end
if z < 0 then
if v.z < 0 then
turnToFace(BACK)
moveInLine(-z)
moveInLine(-v.z)
end
mode.z = mode.z + z
mode.v = mode.v + UNIT_Z
-- y
moveUpDown(y)
mode.y = mode.y + y
moveUpDown(v.y)
mode.v = mode.v + UNIT_Y
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
local mine_state = mode.mine
mode.mine = { forward = true }
-- 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
moveAbs(mode.x, mode.y, 0)
moveAbs(0, 0, 0)
moveAbs(starting_point)
moveAbs(V_ZERO)
turnToFace(BACK)
-- deposit items
for slot = 2, 16 do
@ -127,124 +208,102 @@ function dropOffItems (go_back)
end
turtle.select(1)
-- Return to current position, facing forward
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
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(15) > 0
return turtle.getItemCount(FULL_CHECK_SLOT) > 0
end
function moveAndCheck (x, y, z)
move(x, y, z)
if inventoryFull() then dropOffItems(true) end
function checkAndDropOff(starting_point)
if inventoryFull() then dropOffItems(starting_point, 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)
-- fuel and go to starting corner
refuelUntil(FUEL_RESERVE)
mode.mine = { forward = true }
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)
-- 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
remaining_h = remaining_h - excess
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
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
return splitHorizontal(block)
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
-- certain distance away.
local block = {}
@ -260,30 +319,39 @@ function calculateBlock (x1, y1, z1, x2, y2, z2)
block[dim] = corner
block[dim .. "off"] = offset
end
normalize("x", x1, x2)
normalize("y", y1, y2)
normalize("z", z1, z2)
return block
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)
)
end
function run (x1, y1, z1, x2, y2, z2)
local block = calculateBlock(x1, y1, z1, x2, y2, z2)
mine(block)
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 })
-- return to base
mode.mine = {}
dropOffItems()
dropOffItems(starting_point)
turnToFace(FORWARD)
end
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 \
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 -1 for the first three.")
print("Fuel goes in the top-left slot of the turtle's inventory")
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 ()
@ -296,12 +364,14 @@ end
local arg = argsToNumbers()
if #arg == 3 then
-- 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])
run(V_ZERO, vector.new(arg[1], arg[2], arg[3]))
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
usage()
end