cc-stuff/mine.lua
2025-12-01 19:58:46 -05:00

377 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"
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
-- 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()
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 = mode.v + UNIT_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 = mode.v + UNIT_Z
-- y
moveUpDown(v.y)
mode.v = mode.v + UNIT_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)
-- Turn off mining
mode.mine = { forward = true }
-- Note current position (assuming facing FORWARD)
local pos_state = vector.new(mode.v.x, mode.v.y, mode.v.z)
-- 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
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
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 = {}
local normalize = function (dim, c1, c2)
-- pick the closest point
-- offset goes to the other point
local corner, offset
if math.abs(c1) <= math.abs(c2) then
corner, offset = c1, c2 - c1
else
corner, offset = c2, c1 - c2
end
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)
)
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 })
-- 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