From b5cb13732ed4b53029d7a1de0d1ab92c8a3a81e8 Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 6 Oct 2025 22:29:33 +0200 Subject: [PATCH] Merge netherite/excavation controllers, overhaul movement logic with verticality support --- controller/excavation_controller.lua | 324 ++++++++++++++++++++ controller/netherite_controller.lua | 148 --------- controller/testing_controller.lua | 2 + controller/turtle_controller.lua | 19 +- controller/volume_excavation_controller.lua | 131 -------- main.lua | 18 +- 6 files changed, 350 insertions(+), 292 deletions(-) create mode 100644 controller/excavation_controller.lua delete mode 100644 controller/netherite_controller.lua delete mode 100644 controller/volume_excavation_controller.lua diff --git a/controller/excavation_controller.lua b/controller/excavation_controller.lua new file mode 100644 index 0000000..1339b74 --- /dev/null +++ b/controller/excavation_controller.lua @@ -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 \ No newline at end of file diff --git a/controller/netherite_controller.lua b/controller/netherite_controller.lua deleted file mode 100644 index b8d3a3b..0000000 --- a/controller/netherite_controller.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/controller/testing_controller.lua b/controller/testing_controller.lua index e17828a..1a552c5 100644 --- a/controller/testing_controller.lua +++ b/controller/testing_controller.lua @@ -157,6 +157,8 @@ end function TestingController:Run() self.controller:Configure() self.controller:DisableMiningForward() + self.controller:DisableMiningAbove() + self.controller:DisableMiningBelow() print("There are multiple tests available:") print("1: Relative movement with relative rotation") diff --git a/controller/turtle_controller.lua b/controller/turtle_controller.lua index e2dc90c..3e44521 100644 --- a/controller/turtle_controller.lua +++ b/controller/turtle_controller.lua @@ -107,9 +107,13 @@ function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_re self:UnstockIfFull() end - -- Mine + -- Mine/Move if self.mine_forward then - turtle.dig() + while not turtle.forward() do + turtle.dig() + end + elseif not turtle.forward() then + error("Turtle failed to move forward!") end if self.mine_above then turtle.digUp() @@ -118,11 +122,6 @@ function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_re turtle.digDown() end - -- Move - if not turtle.forward() then - error("Turtle failed to move forward!") - end - -- Update current position if self.position.dir == Direction.NORTH then self.position.z = self.position.z + 1 @@ -181,6 +180,12 @@ function TurtleController:MoveVertical(number_of_blocks, skip_unstocking, skip_r 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) diff --git a/controller/volume_excavation_controller.lua b/controller/volume_excavation_controller.lua deleted file mode 100644 index ba3551e..0000000 --- a/controller/volume_excavation_controller.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/main.lua b/main.lua index 62af19b..15d6772 100644 --- a/main.lua +++ b/main.lua @@ -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 @@ -20,4 +17,13 @@ while choice < 1 or choice > #controllers do choice = tonumber(io.read()) or 0 end -controllers[choice]:Run() \ No newline at end of file +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) \ No newline at end of file