cc-stuff/mine.lua
2025-12-13 00:10:27 -05:00

384 lines
10 KiB
Lua

-- 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
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 = 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 (%d, %d, %d)", v.x, v.y, v.z))
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 height = block.v2.y + 1
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
moveAbs(start_v)
moveAbs(end_v)
end
function canMine (block)
local tooHigh = 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 with the smaller length)
if block.v2.z <= block.v2.x then
return block:take(1, "z")
else
return block:take(1, "x")
end
end
function split (block)
local tooHigh = function (block)
return 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