1

Compare commits

...

2 Commits

6 changed files with 562 additions and 406 deletions

View File

@ -0,0 +1,324 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
---@alias ExcavationControllerConfig {slice_length: number, slice_height: number, center_slice_width: number, slice_width: number, slice_padding: number, slices_left: number, slices_right: number}
---@class ExcavationController
---@field controller TurtleController
---@field config ExcavationControllerConfig
local ExcavationController = {}
ExcavationController.__index = ExcavationController
---@return ExcavationController
function ExcavationController:Create()
local t = {}
setmetatable(t, ExcavationController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create()
t.config = {
slice_length = 0,
slice_height = 0,
center_slice_width = 0,
slice_width = 0,
slice_padding = 0,
slices_left = 0,
slices_right = 0,
}
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
---Excavates a single 1x1/1x2 tunnel of configured length.
---Will leave the turtle wherever it ends up.
---Only unstocks/refuels if required.
---@param mine_above boolean
---@param mine_below boolean
function ExcavationController:Excavate_1x1or2or3xL(mine_above, mine_below)
self.controller:EnableMiningForward()
if mine_above then
self.controller:EnableMiningAbove()
end
if mine_below then
self.controller:EnableMiningBelow()
end
self.controller:MoveForward(self.config.slice_length)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:DisableMiningBelow()
end
---Excavates a single 1xH partial slice of configured length.
---Will leave the turtle in its starting position.
---Only unstocks/refuels if required.
function ExcavationController:Excavate_1xHxL()
self.controller:StorePosition()
-- We can mine 2 blocks heigh in a single go
local number_of_1x3xL = math.floor(self.config.slice_height / 3)
-- Prepare for mining the blocks below the turtle
if number_of_1x3xL > 0 then
self.controller:EnableMiningAbove()
self.controller:MoveVertical(1)
self.controller:DisableMiningAbove()
end
-- Mine 1x3xL
for i = 1,number_of_1x3xL do
self:Excavate_1x1or2or3xL(true, true)
self.controller:TurnRelative(2)
-- Don't move upwards in the last iteration so we can decide
-- how much to move depending on how much we still have to excavate
if i == number_of_1x3xL then
break
end
self.controller:EnableMiningAbove()
self.controller:MoveVertical(4)
self.controller:MoveVertical(-1)
self.controller:DisableMiningAbove()
end
-- Mine remaining 1x2xL or 1x1xL
if self.config.slice_height % 3 == 2 then
self.controller:EnableMiningAbove()
self.controller:MoveVertical(3)
self.controller:MoveVertical(-1)
self.controller:DisableMiningAbove()
self:Excavate_1x1or2or3xL(true, false)
elseif self.config.slice_height % 3 == 1 then
self.controller:EnableMiningAbove()
self.controller:MoveVertical(3)
self.controller:MoveVertical(-1)
self.controller:DisableMiningAbove()
self:Excavate_1x1or2or3xL(false, false)
end
self.controller:EnableMiningBelow()
self.controller:MoveBack()
self.controller:DisableMiningBelow()
end
---Excavates a single WxH slice of configured length (EAST to WEST).
---Will leave the turtle in its starting position.
---Only unstocks/refuels if required.
---@param center_slice boolean | nil
function ExcavationController:Excavate_WxHxL(center_slice)
self.controller:StorePosition()
center_slice = center_slice or false
local width = center_slice and self.config.center_slice_width or self.config.slice_width
for i = 1,width do
self:Excavate_1xHxL()
if i == width then
break
end
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(1)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
end
self.controller:MoveBack()
end
---Excavates all slices.
---Will leave the turtle refueled and unstocked in its 0x0x0 position.
function ExcavationController:Excavate()
-- Enter excavation area
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(1)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
-- Excavate center slice (start from right/back/bottom)
-- Uneven center widths will be centered, even center widths will have a block more on the right
print("Excavating center slice...")
local center_width_right = math.floor(self.config.center_slice_width / 2)
self.controller:StorePosition()
self.controller:TurnToDirection(Direction.EAST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(center_width_right)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
self:Excavate_WxHxL(true)
self.controller:MoveBack() -- (0, 0, 1)
-- Move to right slices starting location
local padded_width_right = self.config.slices_right * (self.config.slice_width + self.config.slice_padding)
self.controller:StorePosition()
self.controller:TurnToDirection(Direction.EAST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(center_width_right + padded_width_right)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
-- Excavate right slices
print("Excavating right slices")
for i = 1,self.config.slices_right do
self:Excavate_WxHxL()
if i == self.config.slices_right then
break
end
-- Move to the next slice
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(self.config.slice_width + self.config.slice_padding)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
end
self.controller:MoveBack() -- (0, 0, 1)
-- Move to left slices starting location
local center_width_left = self.config.center_slice_width - center_width_right
self.controller:StorePosition()
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(center_width_left)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
-- Excavate left slices
print("Excavate left slices")
for i = 1,self.config.slices_left do
-- Move to the next slice
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(self.config.slice_padding)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
self:Excavate_WxHxL()
if i == self.config.slices_left then
break
end
self.controller:TurnToDirection(Direction.WEST)
self.controller:EnableMiningForward()
self.controller:EnableMiningAbove()
self.controller:MoveForward(self.config.slice_width)
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:TurnToDirection(Direction.NORTH)
end
self.controller:MoveBack() -- (0, 0, 1)
-- Finish up
self.controller:MoveToPosition(0, 0, 0, self.controller.config.storage_direction)
self.controller:DropInventory()
self.controller:TurnToDirection(Direction.NORTH)
end
-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------
function ExcavationController:Configure()
local config_complete = false
while not config_complete do
print("How long should each slice be?")
self.config.slice_length = tonumber(io.read()) or 3
print("How high should each slice be?")
self.config.slice_height = tonumber(io.read()) or 3
print("How wide should the center slice be?")
self.config.center_slice_width = tonumber(io.read()) or 1
print("How wide should the other slices be?")
self.config.slice_width = tonumber(io.read()) or 1
print("How many blocks should remain between each slice?")
self.config.slice_padding = tonumber(io.read()) or 0
print("How many slices should the turtle mine to its right?")
self.config.slices_right = tonumber(io.read()) or 1
print("How many slices should the turtle mine to its left?")
self.config.slices_left = tonumber(io.read()) or 1
local padded_width_left = self.config.slices_left * (self.config.slice_width + self.config.slice_padding)
local padded_width_right = self.config.slices_right * (self.config.slice_width + self.config.slice_padding)
local padded_width = padded_width_left + padded_width_right + self.config.center_slice_width
local mined_width_left = self.config.slices_left * self.config.slice_width
local mined_width_right = self.config.slices_right * self.config.slice_width
local mined_width = mined_width_left + mined_width_right + self.config.center_slice_width
print("Configuration complete!")
print(("Mining area spans %d x %d x %d (width x height x forward), totalling %d blocks."):format(
padded_width, self.config.slice_height, self.config.slice_length, padded_width * self.config.slice_height * self.config.slice_length
))
print(("Turtle will mine %d block wide slices (%d wide in the center) with %d blocks of padding, totalling %d mined blocks."):format(
self.config.slice_width, self.config.center_slice_width, self.config.slice_padding, mined_width * self.config.slice_height * self.config.slice_length
))
print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1
end
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function ExcavationController:Run()
self.controller:Configure()
self:Configure()
-- Consume our starting fuel and refuel to the full amount
turtle.select(1)
turtle.refuel()
self.controller:RefuelIfEmpty()
self:Excavate()
end
return ExcavationController

View File

@ -1,148 +0,0 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
---@alias NetheriteControllerConfig {mine_forward: number, slices_left: number, slices_right: number, slices_padding: number}
---@class NetheriteController
---@field controller TurtleController
---@field config NetheriteControllerConfig
local NetheriteController = {}
NetheriteController.__index = NetheriteController
-- TODO: This controller can't return to unstock/refuel if stuck in the middle of a slice
---@return NetheriteController
function NetheriteController:Create()
local t = {}
setmetatable(t, NetheriteController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create()
t.config = {
mine_forward = 0,
slices_left = 0,
slices_right = 0,
slices_padding = 0,
}
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
function NetheriteController:Excavate()
-- Enter the excavation area
self.controller:EnableMining()
self.controller:MoveRelative(1)
local right_padded_width = self.config.slices_right * (self.config.slices_padding + 1)
local left_padded_width = self.config.slices_left * (self.config.slices_padding + 1)
-- Move to the bottom right corner of the layer
self.controller:TurnToDirection(Direction.EAST)
self.controller:MoveRelative(right_padded_width)
self.controller:TurnToDirection(Direction.NORTH)
self.controller:MoveRelative(self.config.mine_forward)
self.controller:TurnToDirection(Direction.WEST)
self.controller:MoveRelative(left_padded_width + right_padded_width)
self.controller:TurnToDirection(Direction.SOUTH)
self.controller:MoveRelative(self.config.mine_forward)
self.controller:TurnToDirection(Direction.EAST)
self.controller:MoveRelative(left_padded_width + right_padded_width)
self.controller:TurnToDirection(Direction.NORTH)
-- Zig zag mine the full width from back to front
local turn_dir = -1
for i = 1,self.config.slices_right + self.config.slices_left do
self.controller:MoveRelative(self.config.mine_forward)
-- Skip the last turn, as we won't mine that slice
if i == self.config.slices_right + self.config.slices_left then
break
end
self.controller:TurnRelative(turn_dir)
self.controller:MoveRelative(self.config.slices_padding + 1)
self.controller:TurnRelative(turn_dir)
turn_dir = -1 * turn_dir
end
if self.controller.position.z > 1 then
self.controller:TurnToDirection(Direction.SOUTH)
self.controller:MoveRelative(self.controller.position.z - 1)
self.controller:MoveToPosition(0, 0, 0, Direction.NORTH)
end
-- Unload before doing the next layer
self.controller:TurnToDirection(self.controller.config.storage_direction)
self.controller:DropInventory()
self.controller:TurnToDirection(Direction.NORTH)
end
-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------
function NetheriteController:Configure()
local config_complete = false
while not config_complete do
print("How many blocks should the turtle mine forward?")
self.config.mine_forward = tonumber(io.read()) or 3
print("How many slices should the turtle to its right?")
self.config.slices_right = tonumber(io.read()) or 3
print("How many blocks should the turtle mine to its left?")
self.config.slices_left = tonumber(io.read()) or 3
print("How many blocks should the turtle leave between the slices?")
self.config.slices_padding = tonumber(io.read()) or 3
local width = self.config.slices_left + self.config.slices_right + 1
local height = 3
print("Configuration complete!")
print(("Turtle will mine an area of %d x %d x %d (forward x width x height, excluding padding) totalling %d blocks."):format(
self.config.mine_forward, width, height, self.config.mine_forward * width * height
))
print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1
end
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function NetheriteController:Run()
self.controller:Configure()
self:Configure()
-- Consume our starting fuel and refuel to the full amount
turtle.select(1)
turtle.refuel()
self.controller:RefuelIfEmpty()
self:Excavate()
end
return NetheriteController

View File

@ -26,21 +26,24 @@ end
function TestingController:TestRelativeMovementWithRelativeRotation()
print("Testing relative movement with relative rotation...")
-- N: BotCenter
self.controller:MoveRelative(3)
self.controller:TurnRelative(1) -- E: TopCenter
self.controller:MoveRelative(3)
self.controller:TurnRelative(2) -- W: TopRight
self.controller:MoveRelative(3)
self.controller:TurnRelative(10) -- E: TopCenter
self.controller:MoveRelative(3)
self.controller:TurnRelative(-3) -- S: TopRight
self.controller:MoveRelative(3)
self.controller:TurnRelative(1) -- W: BotRight
self.controller:MoveRelative(3)
self.controller:TurnRelative(1) -- N: BotCenter
-- N: BackCenter (Center)
self.controller:MoveForward(3)
self.controller:TurnRelative(1) -- E: FrontCenter (Center)
self.controller:MoveForward(3)
self.controller:TurnRelative(2) -- W: FrontRight (Center)
self.controller:MoveVertical(3)
self.controller:TurnRelative(8) -- W: FrontRight (Top)
self.controller:MoveForward(3)
self.controller:TurnRelative(3) -- S: FrontCenter (Top)
self.controller:MoveForward(3)
self.controller:TurnRelative(9) -- W: BackCenter (Top)
self.controller:MoveVertical(-3)
self.controller:TurnRelative(1) -- N: BackCenter (Center)
print("The turtle should be in its original location.")
print(("The turtle has internal position (%d, %d, %d) and internal direction %d"):format(
self.controller.position.x, self.controller.position.y, self.controller.position.z, self.controller.position.dir
))
print("Press Enter to continue")
_ = io.read()
end
@ -49,21 +52,24 @@ end
function TestingController:TestRelativeMovementWithAbsoluteRotation()
print("Testing relative movement with absolute rotation...")
-- N: BotCenter
self.controller:MoveRelative(3)
self.controller:TurnToDirection(Direction.EAST) -- E: TopCenter
self.controller:MoveRelative(3)
self.controller:TurnToDirection(Direction.WEST) -- W: TopRight
self.controller:MoveRelative(3)
self.controller:TurnToDirection(Direction.EAST) -- E: TopCenter
self.controller:MoveRelative(3)
self.controller:TurnToDirection(Direction.SOUTH) -- S: TopRight
self.controller:MoveRelative(3)
self.controller:TurnToDirection(Direction.WEST) -- W: BotRight
self.controller:MoveRelative(3)
self.controller:TurnToDirection(Direction.NORTH) -- N: BotCenter
-- N: BackCenter (Center)
self.controller:MoveForward(3)
self.controller:TurnToDirection(Direction.EAST) -- E: FrontCenter (Center)
self.controller:MoveForward(3)
self.controller:TurnToDirection(Direction.WEST) -- W: FrontRight (Center)
self.controller:MoveVertical(3)
self.controller:TurnToDirection(Direction.WEST) -- W: FrontRight (Top)
self.controller:MoveForward(3)
self.controller:TurnToDirection(Direction.SOUTH) -- S: FrontCenter (Top)
self.controller:MoveForward(3)
self.controller:TurnToDirection(Direction.WEST) -- W: BackCenter (Top)
self.controller:MoveVertical(-3)
self.controller:TurnToDirection(Direction.NORTH) -- N: BackCenter (Center)
print("The turtle should be in its original location.")
print(("The turtle has internal position (%d, %d, %d) and internal direction %d"):format(
self.controller.position.x, self.controller.position.y, self.controller.position.z, self.controller.position.dir
))
print("Press Enter to continue")
_ = io.read()
end
@ -72,20 +78,18 @@ end
function TestingController:TestAbsoluteMovementWithoutStack()
print("Testing absolute movement without stack...")
-- N: BotCenter
self.controller:MoveToPosition(0, 0, 3, Direction.EAST) -- E: TopCenter
_ = io.read()
self.controller:MoveToPosition(3, 0, 3, Direction.WEST) -- W: TopRight
_ = io.read()
self.controller:MoveToPosition(0, 0, 3, Direction.EAST) -- E: TopCenter
_ = io.read()
self.controller:MoveToPosition(3, 0, 3, Direction.SOUTH) -- S: TopRight
_ = io.read()
self.controller:MoveToPosition(3, 0, 0, Direction.WEST) -- W: BotRight
_ = io.read()
self.controller:MoveToPosition(0, 0, 0, Direction.NORTH) -- N: BotCenter
-- N: BackCenter (Center)
self.controller:MoveToPosition(0, 0, 3, Direction.EAST) -- E: FrontCenter (Center)
self.controller:MoveToPosition(3, 0, 3, Direction.WEST) -- W: FrontRight (Center)
self.controller:MoveToPosition(3, 3, 3, Direction.WEST) -- W: FrontRight (Top)
self.controller:MoveToPosition(0, 3, 3, Direction.SOUTH) -- S: FrontCenter (Top)
self.controller:MoveToPosition(0, 3, 0, Direction.WEST) -- W: BackCenter (Top)
self.controller:MoveToPosition(0, 0, 0, Direction.NORTH) -- N: BackCenter (Center)
print("The turtle should be in its original location.")
print(("The turtle has internal position (%d, %d, %d) and internal direction %d"):format(
self.controller.position.x, self.controller.position.y, self.controller.position.z, self.controller.position.dir
))
print("Press Enter to continue")
_ = io.read()
end
@ -94,16 +98,16 @@ end
function TestingController:TestAbsoluteMovementWithStack()
print("Testing absolute movement with stack...")
-- N: BotCenter
self.controller:MoveToPosition(0, 0, 3, Direction.EAST) -- E: TopCenter
_ = io.read()
self.controller:MoveBack() -- N: BotCenter
_ = io.read()
self.controller:MoveToPosition(3, 0, 3, Direction.WEST) -- W: TopRight
_ = io.read()
self.controller:MoveBack() -- N: BotCenter
-- N: BotCenter (Center)
self.controller:MoveToPosition(0, 0, 3, Direction.EAST) -- E: TopCenter (Center)
self.controller:MoveBack() -- N: BotCenter (Center)
self.controller:MoveToPosition(3, 3, 3, Direction.WEST) -- W: TopRight (Top)
self.controller:MoveBack() -- N: BotCenter (Center)
print("The turtle should be in its original location.")
print(("The turtle has internal position (%d, %d, %d) and internal direction %d"):format(
self.controller.position.x, self.controller.position.y, self.controller.position.z, self.controller.position.dir
))
print("Press Enter to continue")
_ = io.read()
end
@ -111,12 +115,13 @@ end
function TestingController:TestUnstocking()
print("Testing inventory unloading...")
print("Please fill multiple inventory slots with items.")
_ = io.read()
self.controller:MoveToPosition(3, 0, 3, Direction.WEST)
print("Please fill the entire inventory with items.")
print("Press Enter to continue")
_ = io.read()
self.controller:UnstockIfFull(true)
print("Press Enter to continue")
_ = io.read()
self.controller:MoveBack()
@ -128,12 +133,13 @@ end
function TestingController:TestRefueling()
print("Testing refueling...")
print("Please fill multiple inventory slots with items.")
_ = io.read()
self.controller:MoveToPosition(3, 0, 3, Direction.WEST)
print("Please fill multiple inventory slots with items.")
print("Press Enter to continue")
_ = io.read()
self.controller:RefuelIfEmpty(true)
print("Press Enter to continue")
_ = io.read()
self.controller:MoveBack()
@ -150,7 +156,9 @@ end
function TestingController:Run()
self.controller:Configure()
self.controller:DisableMining()
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:DisableMiningBelow()
print("There are multiple tests available:")
print("1: Relative movement with relative rotation")

View File

@ -8,15 +8,13 @@ local Stack = require("lib.stack")
---@field config TurtleControllerConfig
---@field position Position
---@field last_positions Stack
---@field mine boolean
---@field mine_forward boolean
---@field mine_above boolean
---@field mine_below boolean
local TurtleController = {}
TurtleController.__index = TurtleController
-- TODO: StackOverflow once the inventory is full...
-- TODO: Test if there's a chest when dropping/sucking/mining (don't mine chests!)
---@return TurtleController
function TurtleController:Create()
local t = {}
@ -37,7 +35,9 @@ function TurtleController:Create()
}
t.position = Position:Empty()
t.last_positions = Stack:Create()
t.mine = false
t.mine_forward = false
t.mine_above = false
t.mine_below = false
return t
@ -52,21 +52,19 @@ end
---Positive numbers turn clockwise, negative numbers turn counterclockwise
---@param number_of_turns number
function TurtleController:TurnRelative(number_of_turns)
if number_of_turns == 0 then
if number_of_turns % 4 == 0 then
return
end
self:RefuelIfEmpty()
-- Turn turtle
local turns = number_of_turns % 4
if turns == 3 then
print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, -1))
-- print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, -1))
-- If we're rotating by 3, we could turn faster by rotating by 1 in the opposite direction
turtle.turnLeft()
else
print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, turns))
-- print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, turns))
for _ = 1,turns do
turtle.turnRight()
@ -90,31 +88,41 @@ end
---Move forward by a number of blocks depending on the current rotation
---@param number_of_blocks number
function TurtleController:MoveRelative(number_of_blocks)
---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil
function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_refueling)
skip_unstocking = skip_unstocking or false
skip_refueling = skip_refueling or false
if number_of_blocks == 0 then
return
end
print(("Turtle is moving forward by %d blocks..."):format(number_of_blocks))
-- print(("Turtle is moving forward by %d blocks..."):format(number_of_blocks))
for _ = 1,math.abs(number_of_blocks) do
if not skip_refueling then
self:RefuelIfEmpty()
end
if not skip_unstocking then
self:UnstockIfFull()
end
-- Break blocks
if self.mine then
-- Mine/Move
if self.mine_forward then
while not turtle.forward() do
turtle.dig()
end
turtle.digUp()
turtle.digDown()
else
if not turtle.forward() then
printError("Turtle wants to move without mining but is blocked!")
elseif not turtle.forward() then
error("Turtle failed to move forward!")
end
if self.mine_above then
turtle.digUp()
end
if self.mine_below then
turtle.digDown()
end
-- Update current_position
-- Update current position
if self.position.dir == Direction.NORTH then
self.position.z = self.position.z + 1
elseif self.position.dir == Direction.SOUTH then
@ -127,35 +135,77 @@ function TurtleController:MoveRelative(number_of_blocks)
end
end
---Positive numbers move up, negative numbers move down.
---@param number_of_blocks number
---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil
function TurtleController:MoveVertical(number_of_blocks, skip_unstocking, skip_refueling)
skip_unstocking = skip_unstocking or false
skip_refueling = skip_refueling or false
if number_of_blocks == 0 then
return
end
-- print(("Turtle is moving vertically by %d blocks..."):format(number_of_blocks))
local move_function = number_of_blocks > 0 and turtle.up or turtle.down
local mine_function = number_of_blocks > 0 and turtle.digUp or turtle.digDown
local mine_enabled = number_of_blocks > 0 and self.mine_above or self.mine_below
for _ = 1,math.abs(number_of_blocks) do
if not skip_refueling then
self:RefuelIfEmpty()
end
if not skip_unstocking then
self:UnstockIfFull()
end
-- Mine
if mine_enabled then
mine_function()
end
-- Move
if not move_function() then
error("Turtle failed to move vertically!")
end
-- Update current position
if number_of_blocks > 0 then
self.position.y = self.position.y + 1
elseif number_of_blocks < 0 then
self.position.y = self.position.y - 1
end
end
end
---Stores the current position on the stack so we can return using TurtleController:MoveBack()
function TurtleController:StorePosition()
self.last_positions:Push(Position:Copy(self.position))
end
---Move to an absolute position. Stores the current position on the stack so we can return using TurtleController:MoveBack()
---@param x number The EAST/WEST axis (grows from WEST to EAST)
---@param y number The UP/DOWN axis (grows from DOWN to UP)
---@param z number The NORTH/SOUTH axis (grows from SOUTH to NORTH)
---@param dir Direction
function TurtleController:MoveToPosition(x, y, z, dir)
print(("Turtle is moving to (%d, %d, %d)..."):format(x, y, z))
---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil
function TurtleController:MoveToPosition(x, y, z, dir, skip_unstocking, skip_refueling)
-- print(("Turtle is moving to (%d, %d, %d)..."):format(x, y, z))
self:DisableMining()
-- Store the current position on the stack, so we can return using TurtleController:MoveBack()
self.last_positions:Push(Position:Copy(self.position))
if self.position.z > 1 then
-- Move south once (if we're at the top) to not be blocked by the slice that is currently mined.
-- This assumes that we mine the full width back to front.
self:TurnToDirection(Direction.SOUTH)
self:MoveRelative(1)
elseif self.position.z == 0 then
-- Move north once (if we're at the bottom) to not be blocked by the chests
self:TurnToDirection(Direction.NORTH)
self:MoveRelative(1)
end
-- EAST/WEST axis (do first to not interfere with chests or unmined walls)
-- EAST/WEST axis (do first to not interfere with chests)
if self.position.x > x then
self:TurnToDirection(Direction.WEST)
elseif self.position.x < x then
self:TurnToDirection(Direction.EAST)
end
self:MoveRelative(math.abs(self.position.x - x))
self:MoveForward(math.abs(x - self.position.x), skip_unstocking, skip_refueling)
-- NORTH/SOUTH axis
if self.position.z > z then
@ -163,28 +213,47 @@ function TurtleController:MoveToPosition(x, y, z, dir)
elseif self.position.z < z then
self:TurnToDirection(Direction.NORTH)
end
self:MoveRelative(math.abs(self.position.z - z))
self:MoveForward(math.abs(z - self.position.z), skip_unstocking, skip_refueling)
-- UP/DOWN axis
self:MoveVertical(y - self.position.y, skip_unstocking, skip_refueling)
-- Direction
self:TurnToDirection(dir or Direction.NORTH)
-- Sanity check
if not(self.position.x == x and self.position.y == y and self.position.z == z and self.position.dir == dir) then
printError("TurtleController:MoveToPosition failed to move to target position!")
error("TurtleController:MoveToPosition failed to move to target position!")
end
end
function TurtleController:MoveBack()
local target_position = self.last_positions:Pop()
---Move by a vector. Stores the current position on the stack so we can return using TurtleController:MoveBack()
---@param x number The EAST/WEST axis (grows from WEST to EAST)
---@param y number The UP/DOWN axis (grows from DOWN to UP)
---@param z number The NORTH/SOUTH axis (grows from SOUTH to NORTH)
---@param dir Direction
---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil
function TurtleController:MoveByVector(x, y, z, dir, skip_unstocking, skip_refueling)
self:MoveToPosition(self.position.x + x, self.position.y + y, self.position.z + z, dir, skip_unstocking, skip_refueling)
end
if target_position == nil then
shell.exit()
---Move to the previously stored position and pop this position from the stack.
---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil
function TurtleController:MoveBack(skip_unstocking, skip_refueling)
local last_position = self.last_positions:Pop()
if last_position == nil then
error("Failed to obtain last_position to move back!")
else
self:MoveToPosition(target_position.x, target_position.y, target_position.z, target_position.dir)
self:MoveToPosition(last_position.x, last_position.y, last_position.z, last_position.dir, skip_unstocking, skip_refueling)
-- Pop the stack because MoveToPosition pushes our current position
self.last_positions:Pop()
end
end
@ -193,6 +262,27 @@ end
-----------------------------------------------------------------------------------------------
---@return boolean
function TurtleController:HasFuel()
local level = turtle.getFuelLevel()
local distance_home = self.position.x + self.position.y + self.position.z
return level > distance_home + self.config.refuel_safety_margin
end
---@return boolean
function TurtleController:HasInventorySpace()
for slot = 1,16 do
if turtle.getItemDetail(slot) == nil then
return true
end
end
return false
end
---@param slot number
---@param count number
---@return boolean
@ -241,14 +331,17 @@ function TurtleController:UnstockIfFull(skip_inventory_check)
if not skip_inventory_check and self:HasInventorySpace() then
return
end
if skip_inventory_check then
print(("HasInventorySpace() returned %s"):format(self:HasInventorySpace()))
end
print("Turtle is unstocking...")
self:MoveToPosition(0, 0, 0, self.config.storage_direction)
self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true)
self:DropInventory()
self:RefuelIfEmpty()
self:MoveBack()
self:MoveBack(true, true)
end
@ -258,11 +351,14 @@ function TurtleController:RefuelIfEmpty(skip_fuel_check)
if not skip_fuel_check and self:HasFuel() then
return
end
if skip_fuel_check then
print(("HasFuel() returned %s"):format(self:HasFuel()))
end
print("Turtle is refueling...")
-- Clear our inventory into the storage chest
self:MoveToPosition(0, 0, 0, self.config.storage_direction)
self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true)
self:DropInventory()
-- Prepare refueling
@ -274,7 +370,7 @@ function TurtleController:RefuelIfEmpty(skip_fuel_check)
local target_fuel_level = self.config.refuel_amount
local last_position = self.last_positions:Peek()
if last_position == nil then
shell.exit()
error("Failed to obtain last_position while refueling!")
else
target_fuel_level = target_fuel_level + last_position.x + last_position.y + last_position.z
end
@ -283,14 +379,13 @@ function TurtleController:RefuelIfEmpty(skip_fuel_check)
local before_level = turtle.getFuelLevel()
repeat
if not self:SuckItem(1, 1) then
printError("Failed to suck fuel out of fuel chest!")
shell.exit()
error("Failed to suck fuel out of fuel chest!")
end
turtle.refuel()
until turtle.getFuelLevel() >= target_fuel_level
local after_level = turtle.getFuelLevel()
self:MoveBack()
self:MoveBack(true, true)
print(("Refuelled %d units, current level is %d (old level was %d)"):format(after_level - before_level, after_level, before_level))
end
@ -334,32 +429,34 @@ function TurtleController:Configure()
end
function TurtleController:EnableMining()
self.mine = true
function TurtleController:EnableMiningForward()
self.mine_forward = true
end
function TurtleController:DisableMining()
self.mine = false
function TurtleController:DisableMiningForward()
self.mine_forward = false
end
function TurtleController:HasFuel()
local level = turtle.getFuelLevel()
local distance_home = self.position.x + self.position.y + self.position.z
return level > distance_home + self.config.refuel_safety_margin
function TurtleController:EnableMiningAbove()
self.mine_above = true
end
function TurtleController:HasInventorySpace()
for slot = 1,16 do
if turtle.getItemDetail(slot) == nil then
return true
end
end
return false
function TurtleController:DisableMiningAbove()
self.mine_above = false
end
function TurtleController:EnableMiningBelow()
self.mine_below = true
end
function TurtleController:DisableMiningBelow()
self.mine_below = false
end
return TurtleController

View File

@ -1,131 +0,0 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
---@alias VolumeExcavationControllerConfig {mine_forward: number, mine_left: number, mine_right: number}
---@class VolumeExcavationController
---@field controller TurtleController
---@field config VolumeExcavationControllerConfig
local VolumeExcavationController = {}
VolumeExcavationController.__index = VolumeExcavationController
---@return VolumeExcavationController
function VolumeExcavationController:Create()
local t = {}
setmetatable(t, VolumeExcavationController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create()
t.config = {
mine_forward = 0,
mine_left = 0,
mine_right = 0,
}
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
function VolumeExcavationController:Excavate()
-- Enter the excavation area
self.controller:EnableMining()
self.controller:MoveRelative(1)
-- Move to the bottom right corner of the layer
self.controller:TurnToDirection(Direction.EAST)
self.controller:MoveRelative(self.config.mine_right)
self.controller:TurnToDirection(Direction.WEST)
-- Zig zag mine the full width from back to front
-- The direction is important so we can travel to start without colliding with unminded walls
local turn_dir = 1
for i = 1,self.config.mine_forward do
self.controller:MoveRelative(self.config.mine_left + self.config.mine_right)
-- Skip the last turn, as we won't mine that slice
-- We want to stay in the mined area so we can move back freely
if i == self.config.mine_forward then
break
end
-- Skip the turning if we're mining in a straight line
if self.config.mine_left + self.config.mine_right > 0 then
self.controller:TurnRelative(turn_dir)
self.controller:MoveRelative(1)
self.controller:TurnRelative(turn_dir)
end
turn_dir = -1 * turn_dir
end
self.controller:MoveToPosition(0, 0, 0, Direction.NORTH)
-- Unload before doing the next layer
self.controller:TurnToDirection(self.controller.config.storage_direction)
self.controller:DropInventory()
self.controller:TurnToDirection(Direction.NORTH)
end
-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------
function VolumeExcavationController:Configure()
local config_complete = false
while not config_complete do
print("How many blocks should the turtle mine forward?")
self.config.mine_forward = tonumber(io.read()) or 3
print("How many blocks should the turtle to its right?")
self.config.mine_right = tonumber(io.read()) or 3
print("How many blocks should the turtle mine to its left?")
self.config.mine_left = tonumber(io.read()) or 3
local width = self.config.mine_left + self.config.mine_right + 1
local height = 3
print("Configuration complete!")
print(("Turtle will mine an area of %d x %d x %d (forward x width x height) totalling %d blocks."):format(
self.config.mine_forward, width, height, self.config.mine_forward * width * height
))
print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1
end
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function VolumeExcavationController:Run()
self.controller:Configure()
self:Configure()
-- Consume our starting fuel and refuel to the full amount
turtle.select(1)
turtle.refuel()
self.controller:RefuelIfEmpty()
self:Excavate()
end
return VolumeExcavationController

View File

@ -1,18 +1,15 @@
local TestingController = require("controller.testing_controller")
local VolumeExcavationController = require("controller.volume_excavation_controller")
local NetheriteController = require("controller.netherite_controller")
local ExcavationController = require("controller.excavation_controller")
local controllers = {
TestingController:Create(),
VolumeExcavationController:Create(),
NetheriteController:Create(),
ExcavationController:Create(),
}
print("Multiple controllers are available:")
print("1: Testing Mode")
print("2: Volume Excavation")
print("3: Netherite Excavation")
local choice = 0
while choice < 1 or choice > #controllers do
@ -21,3 +18,12 @@ while choice < 1 or choice > #controllers do
end
controllers[choice]:Run()
-- TODO: StackOverflow once the inventory is full, unstocking doesn't work...
-- Is the InventoryFull check wrong?
-- TODO: Test if there's a chest when dropping/sucking/mining (don't mine chests!)
-- TODO: Add controller to build rooms with walls of a specified material
-- TODO: Support specifying the mining height
-- TODO: Add configurable trash_list with items that won't be picked up
-- TODO: Add a speaker to the turtle (listen to music while mining)