1

Compare commits

..

7 Commits

15 changed files with 1449 additions and 1016 deletions

9
.luarc.json Normal file
View File

@ -0,0 +1,9 @@
{
"diagnostics.globals": [
"printError",
"turtle",
"shell",
"peripheral",
"parallel"
]
}

14
.vscode/settings.json vendored
View File

@ -1,7 +1,9 @@
{ {
"Lua.diagnostics.globals": [ "Lua.diagnostics.globals": [
"printError", "printError",
"turtle", "turtle",
"shell" "shell",
] "peripheral",
"parallel"
]
} }

BIN
audio/bangarang.dfpwm Normal file

Binary file not shown.

View File

@ -0,0 +1,87 @@
local dfpwm = require("cc.audio.dfpwm")
---@alias AudioControllerConfig {buffer_length_seconds: number}
---@class AudioController
---@field config AudioControllerConfig
---@field play boolean
local AudioController = {}
AudioController.__index = AudioController
---@return AudioController
function AudioController:Create()
local t = {}
setmetatable(t, AudioController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.config = {
buffer_length_seconds = 1,
}
t.play = false
return t
end
-----------------------------------------------------------------------------------------------
-- Audio Methods
-----------------------------------------------------------------------------------------------
function AudioController:PlayAudio(filename)
self.play = true
local decoder = dfpwm.make_decoder()
while self.play do
for chunk in io.lines(("audio/%s.dfpwm"):format(filename), self.config.buffer_length_seconds * 1024) do
if not self.play then
break
end
local buffer = decoder(chunk)
while not self:GetSpeaker().playAudio(buffer) do
if not self.play then
break
end
---@diagnostic disable-next-line: undefined-field
os.pullEvent("speaker_audio_empty")
end
end
end
end
function AudioController:PlayAudioFactory(filename)
local function play()
self:PlayAudio(filename)
end
return play
end
-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------
---@return table | nil
function AudioController:GetSpeaker()
return peripheral.find("speaker")
end
function AudioController:StopPlaying()
self.play = false
self:GetSpeaker().stop()
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function AudioController:Run()
-- self:Configure()
self:PlayAudio("bangarang")
end
return AudioController

View File

@ -0,0 +1,82 @@
local TurtleController = require("controller.turtle_controller")
local AudioController = require("controller.audio_controller")
---@class AudioTestingController
---@field controller TurtleController
---@field audio AudioController
local AudioTestingController = {}
AudioTestingController.__index = AudioTestingController
---@return AudioTestingController
function AudioTestingController:Create()
local t = {}
setmetatable(t, AudioTestingController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create()
t.audio = AudioController:Create()
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
function AudioTestingController:TestAudioWithoutMovement()
local function dancing()
for _ = 1, 256 do
self.controller:TurnRelative(1)
end
self.audio:StopPlaying()
end
parallel.waitForAll(dancing, self.audio:PlayAudioFactory("bangarang"))
end
function AudioTestingController:TestAudioWithMovement()
local function movement()
for _ = 1, 10 do
self.controller:MoveForward(10)
self.controller:MoveVertical(3)
self.controller:TurnRelative(2)
self.controller:MoveForward(10)
self.controller:MoveVertical(-3)
self.controller:TurnRelative(2)
end
self.audio:StopPlaying()
end
parallel.waitForAll(movement, self.audio:PlayAudioFactory("bangarang"))
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function AudioTestingController:Run()
self.controller:Configure()
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:DisableMiningBelow()
print("There are multiple tests available:")
print("1: Audio without movement")
print("2: Audio with movement")
local choice = 0
while choice < 1 or choice > 2 do
print("Choose a test by entering its number:")
choice = tonumber(io.read()) or 0
end
if choice == 1 then
self:TestAudioWithoutMovement()
elseif choice == 2 then
self:TestAudioWithMovement()
end
end
return AudioTestingController

View File

@ -0,0 +1,330 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
local AudioController = require("controller.audio_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 audio AudioController
---@field config ExcavationControllerConfig
local ExcavationController = {}
ExcavationController.__index = ExcavationController
---@return ExcavationController
function ExcavationController:Create()
local t = {}
setmetatable(t, ExcavationController)
-----------------------------------------------------------------------------------------------
-- Fields
-----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create()
t.audio = AudioController: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)
if self.config.slices_right > 0 then
-- 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)
end
if self.config.slices_left > 0 then
-- 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)
end
-- Finish up
self.controller:MoveToPosition(0, 0, 0, self.controller.config.storage_direction)
self.controller:DropInventoryIntoChest()
self.controller:TurnToDirection(Direction.NORTH)
self.audio:StopPlaying()
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()
parallel.waitForAll(function()
self:Excavate()
end, self.audio:PlayAudioFactory("bangarang"))
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

@ -1,203 +0,0 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
---@class TestingController
---@field controller TurtleController
local TestingController = {}
TestingController.__index = TestingController
---@return TestingController
function TestingController:Create()
local t = {}
setmetatable(t, TestingController)
t.controller = TurtleController:Create()
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
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
print("The turtle should be in its original location.")
print("Press Enter to continue")
_ = io.read()
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
print("The turtle should be in its original location.")
print("Press Enter to continue")
_ = io.read()
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
print("The turtle should be in its original location.")
print("Press Enter to continue")
_ = io.read()
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
print("The turtle should be in its original location.")
print("Press Enter to continue")
_ = io.read()
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)
_ = io.read()
self.controller:UnstockIfFull(true)
_ = io.read()
self.controller:MoveBack()
print("The turtle should be in its original location.")
print("Press Enter to continue")
_ = io.read()
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)
_ = io.read()
self.controller:RefuelIfEmpty(true)
_ = io.read()
self.controller:MoveBack()
print("The turtle should be in its original location.")
print("Press Enter to continue")
_ = io.read()
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function TestingController:Run()
self.controller:Configure()
self.controller:DisableMining()
print("There are multiple tests available:")
print("1: Relative movement with relative rotation")
print("2: Relative movement with absolute rotation")
print("3: Absolute movement without stack")
print("4: Absolute movement with stack")
print("5: All movement tests")
print("6: Unloading")
print("7: Refueling")
print("8: All non-movement tests")
print("9: All tests")
local choice = 0
while choice < 1 or choice > 9 do
print("Choose a test by entering its number:")
choice = tonumber(io.read()) or 0
end
if choice == 1 then
self:TestRelativeMovementWithRelativeRotation()
elseif choice == 2 then
self:TestRelativeMovementWithAbsoluteRotation()
elseif choice == 3 then
self:TestAbsoluteMovementWithoutStack()
elseif choice == 4 then
self:TestAbsoluteMovementWithStack()
elseif choice == 5 then
self:TestRelativeMovementWithRelativeRotation()
self:TestRelativeMovementWithAbsoluteRotation()
self:TestAbsoluteMovementWithoutStack()
self:TestAbsoluteMovementWithStack()
elseif choice == 6 then
self:TestUnstocking()
elseif choice == 7 then
self:TestRefueling()
elseif choice == 8 then
self:TestUnstocking()
self:TestRefueling()
elseif choice == 9 then
self:TestRelativeMovementWithRelativeRotation()
self:TestRelativeMovementWithAbsoluteRotation()
self:TestAbsoluteMovementWithoutStack()
self:TestAbsoluteMovementWithStack()
self:TestUnstocking()
self:TestRefueling()
end
end
return TestingController

View File

@ -1,365 +1,546 @@
local Position = require("lib.position") local Position = require("lib.position")
local Direction = require("lib.direction") local Direction = require("lib.direction")
local Stack = require("lib.stack") local Stack = require("lib.stack")
---@alias TurtleControllerConfig {fuel_direction: Direction, fuel_name: string, refuel_amount: number, refuel_safety_margin: number, storage_direction: Direction} ---@alias TurtleControllerConfig {fuel_direction: Direction, fuel_name: string, refuel_amount: number, refuel_safety_margin: number, storage_direction: Direction}
---@class TurtleController ---@class TurtleController
---@field config TurtleControllerConfig ---@field config TurtleControllerConfig
---@field position Position ---@field position Position
---@field last_positions Stack ---@field last_positions Stack
---@field mine boolean ---@field mine_forward boolean
local TurtleController = {} ---@field mine_above boolean
TurtleController.__index = TurtleController ---@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 = {}
---@return TurtleController setmetatable(t, TurtleController)
function TurtleController:Create()
local t = {} -----------------------------------------------------------------------------------------------
setmetatable(t, TurtleController) -- Fields
-----------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------- t.config = {
-- Fields fuel_direction = Direction.EAST,
----------------------------------------------------------------------------------------------- fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name,
refuel_amount = 500,
refuel_safety_margin = 160,
t.config = { storage_direction = Direction.WEST,
fuel_direction = Direction.EAST, }
fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name, t.position = Position:Empty()
refuel_amount = 500, t.last_positions = Stack:Create()
refuel_safety_margin = 160, t.mine_forward = false
storage_direction = Direction.WEST, t.mine_above = false
} t.mine_below = false
t.position = Position:Empty()
t.last_positions = Stack:Create() return t
t.mine = false end
-----------------------------------------------------------------------------------------------
return t -- Movement Methods
end -----------------------------------------------------------------------------------------------
---Positive numbers turn clockwise, negative numbers turn counterclockwise
----------------------------------------------------------------------------------------------- ---@param number_of_turns number
-- Movement Methods function TurtleController:TurnRelative(number_of_turns)
----------------------------------------------------------------------------------------------- if number_of_turns % 4 == 0 then
return
end
---Positive numbers turn clockwise, negative numbers turn counterclockwise
---@param number_of_turns number -- Turn turtle
function TurtleController:TurnRelative(number_of_turns) local turns = number_of_turns % 4
if number_of_turns == 0 then if turns == 3 then
return -- print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, -1))
end
-- If we're rotating by 3, we could turn faster by rotating by 1 in the opposite direction
self:RefuelIfEmpty() turtle.turnLeft()
else
-- Turn turtle -- print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, turns))
local turns = number_of_turns % 4
if turns == 3 then for _ = 1, turns do
print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, -1)) turtle.turnRight()
end
-- If we're rotating by 3, we could turn faster by rotating by 1 in the opposite direction end
turtle.turnLeft()
else -- Update current_position
print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, turns)) self.position.dir = (self.position.dir + number_of_turns) % 4
end
for _ = 1,turns do
turtle.turnRight() ---@param direction Direction
end function TurtleController:TurnToDirection(direction)
end if self.position.dir == direction then
return
-- Update current_position end
self.position.dir = (self.position.dir + number_of_turns) % 4
end self:TurnRelative(direction - self.position.dir)
end
---@param direction Direction ---Move forward by a number of blocks depending on the current rotation
function TurtleController:TurnToDirection(direction) ---@param number_of_blocks number
if self.position.dir == direction then ---@param skip_unstocking boolean | nil
return ---@param skip_refueling boolean | nil
end function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_refueling)
skip_unstocking = skip_unstocking or false
self:TurnRelative(direction - self.position.dir) skip_refueling = skip_refueling or false
end if number_of_blocks == 0 then
return
end
---Move forward by a number of blocks depending on the current rotation
---@param number_of_blocks number -- print(("Turtle is moving forward by %d blocks..."):format(number_of_blocks))
function TurtleController:MoveRelative(number_of_blocks)
if number_of_blocks == 0 then for _ = 1, math.abs(number_of_blocks) do
return if not skip_refueling then
end self:RefuelIfEmpty()
end
print(("Turtle is moving forward by %d blocks..."):format(number_of_blocks)) if not skip_unstocking then
self:UnstockIfFull()
for _ = 1,math.abs(number_of_blocks) do end
self:RefuelIfEmpty()
self:UnstockIfFull() -- Mine/Move
if self.mine_forward then
-- Break blocks while not turtle.forward() do
if self.mine then if self:TestForBlock("minecraft:chest") then
while not turtle.forward() do error("Won't mine because a chest is in the way!")
turtle.dig() end
end turtle.dig()
turtle.digUp() end
turtle.digDown() elseif not turtle.forward() then
else error("Turtle failed to move forward!")
if not turtle.forward() then end
printError("Turtle wants to move without mining but is blocked!") if self.mine_above then
end if self:TestForBlock("minecraft:chest", true, false) then
end error("Won't mine because a chest is in the way!")
end
-- Update current_position turtle.digUp()
if self.position.dir == Direction.NORTH then end
self.position.z = self.position.z + 1 if self.mine_below then
elseif self.position.dir == Direction.SOUTH then if self:TestForBlock("minecraft:chest", false, true) then
self.position.z = self.position.z - 1 error("Won't mine because a chest is in the way!")
elseif self.position.dir == Direction.EAST then end
self.position.x = self.position.x + 1 turtle.digDown()
elseif self.position.dir == Direction.WEST then end
self.position.x = self.position.x - 1
end -- Update current position
end if self.position.dir == Direction.NORTH then
end self.position.z = self.position.z + 1
elseif self.position.dir == Direction.SOUTH then
self.position.z = self.position.z - 1
---@param x number The EAST/WEST axis (grows from WEST to EAST) elseif self.position.dir == Direction.EAST then
---@param y number The UP/DOWN axis (grows from DOWN to UP) self.position.x = self.position.x + 1
---@param z number The NORTH/SOUTH axis (grows from SOUTH to NORTH) elseif self.position.dir == Direction.WEST then
---@param dir Direction self.position.x = self.position.x - 1
function TurtleController:MoveToPosition(x, y, z, dir) end
print(("Turtle is moving to (%d, %d, %d)..."):format(x, y, z)) end
end
self:DisableMining()
self.last_positions:Push(Position:Copy(self.position)) ---Positive numbers move up, negative numbers move down.
---@param number_of_blocks number
if self.position.z > 1 then ---@param skip_unstocking boolean | nil
-- Move south once (if we're at the top) to not be blocked by the slice that is currently mined. ---@param skip_refueling boolean | nil
-- This assumes that we mine the full width back to front. function TurtleController:MoveVertical(number_of_blocks, skip_unstocking, skip_refueling)
self:TurnToDirection(Direction.SOUTH) skip_unstocking = skip_unstocking or false
self:MoveRelative(1) skip_refueling = skip_refueling or false
elseif self.position.z == 0 then if number_of_blocks == 0 then
-- Move north once (if we're at the bottom) to not be blocked by the chests return
self:TurnToDirection(Direction.NORTH) end
self:MoveRelative(1)
end -- print(("Turtle is moving vertically by %d blocks..."):format(number_of_blocks))
-- EAST/WEST axis (do first to not interfere with chests or unmined walls) local move_function = number_of_blocks > 0 and turtle.up or turtle.down
if self.position.x > x then local mine_function = number_of_blocks > 0 and turtle.digUp or turtle.digDown
self:TurnToDirection(Direction.WEST) local mine_enabled = number_of_blocks > 0 and self.mine_above or self.mine_below
elseif self.position.x < x then
self:TurnToDirection(Direction.EAST) for _ = 1, math.abs(number_of_blocks) do
end if not skip_refueling then
self:MoveRelative(math.abs(self.position.x - x)) self:RefuelIfEmpty()
end
-- NORTH/SOUTH axis if not skip_unstocking then
if self.position.z > z then self:UnstockIfFull()
self:TurnToDirection(Direction.SOUTH) end
elseif self.position.z < z then
self:TurnToDirection(Direction.NORTH) -- Mine/Move
end if mine_enabled then
self:MoveRelative(math.abs(self.position.z - z)) while not move_function() do
if self:TestForBlock("minecraft:chest", number_of_blocks > 0, number_of_blocks < 0) then
-- Direction error("Won't mine because a chest is in the way!")
self:TurnToDirection(dir or Direction.NORTH) end
mine_function()
-- Sanity check end
if not(self.position.x == x and self.position.y == y and self.position.z == z and self.position.dir == dir) then elseif not move_function() then
printError("TurtleController:MoveToPosition failed to move to target position!") error("Turtle failed to move vertically!")
end end
end
-- Update current position
if number_of_blocks > 0 then
function TurtleController:MoveBack() self.position.y = self.position.y + 1
local target_position = self.last_positions:Pop() elseif number_of_blocks < 0 then
self.position.y = self.position.y - 1
if target_position == nil then end
shell.exit() end
else end
self:MoveToPosition(target_position.x, target_position.y, target_position.z, target_position.dir)
self.last_positions:Pop() ---Stores the current position on the stack so we can return using TurtleController:MoveBack()
end function TurtleController:StorePosition()
self.last_positions:Push(Position:Copy(self.position))
end 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)
-- Inventory Methods ---@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 slot number ---@param skip_refueling boolean | nil
---@param count number function TurtleController:MoveToPosition(x, y, z, dir, skip_unstocking, skip_refueling)
---@return boolean -- print(("Turtle is moving to (%d, %d, %d)..."):format(x, y, z))
function TurtleController:SuckItem(slot, count)
local previous_slot = turtle.getSelectedSlot() -- Store the current position on the stack, so we can return using TurtleController:MoveBack()
self.last_positions:Push(Position:Copy(self.position))
turtle.select(slot or 1)
local sucked = turtle.suck(count) -- Store mining config so we can restore it later
local mine_forward = self.mine_forward
turtle.select(previous_slot) local mine_above = self.mine_above
local mine_below = self.mine_below
return sucked
end -- EAST/WEST axis (do first to not interfere with chests)
self:EnableMiningForward()
self:DisableMiningAbove()
---@param slot number self:DisableMiningBelow()
---@param count number if self.position.x > x then
---@return boolean self:TurnToDirection(Direction.WEST)
function TurtleController:DropItem(slot, count) elseif self.position.x < x then
local previous_slot = turtle.getSelectedSlot() self:TurnToDirection(Direction.EAST)
end
turtle.select(slot or 1) self:MoveForward(math.abs(x - self.position.x), skip_unstocking, skip_refueling)
local dropped = turtle.drop(count)
-- NORTH/SOUTH axis
turtle.select(previous_slot) if self.position.z > z then
self:TurnToDirection(Direction.SOUTH)
return dropped elseif self.position.z < z then
end self:TurnToDirection(Direction.NORTH)
end
self:MoveForward(math.abs(z - self.position.z), skip_unstocking, skip_refueling)
function TurtleController:DropInventory()
print("Dropping inventory into chest...") -- UP/DOWN axis
self:DisableMiningForward()
for slot = 1,16 do if y < self.position.y then
turtle.select(slot) self:EnableMiningBelow()
turtle.drop() elseif y > self.position.y then
end self:EnableMiningAbove()
end
turtle.select(1) self:MoveVertical(y - self.position.y, skip_unstocking, skip_refueling)
end
-- Direction
self:TurnToDirection(dir or Direction.NORTH)
---@param skip_inventory_check boolean | nil
function TurtleController:UnstockIfFull(skip_inventory_check) -- Restore mining config
skip_inventory_check = skip_inventory_check or false if mine_forward then
if not skip_inventory_check and self:HasInventorySpace() then self:EnableMiningForward()
return else
end self:DisableMiningForward()
end
print("Turtle is unstocking...") if mine_above then
self:EnableMiningAbove()
self:MoveToPosition(0, 0, 0, self.config.storage_direction) else
self:DropInventory() self:DisableMiningAbove()
self:RefuelIfEmpty() end
if mine_below then
self:MoveBack() self:EnableMiningBelow()
end else
self:DisableMiningBelow()
end
---@param skip_fuel_check boolean | nil
function TurtleController:RefuelIfEmpty(skip_fuel_check) -- Sanity check
skip_fuel_check = skip_fuel_check or false if not (self.position.x == x and self.position.y == y and self.position.z == z and self.position.dir == dir) then
if not skip_fuel_check and self:HasFuel() then error("TurtleController:MoveToPosition failed to move to target position!")
return end
end end
print("Turtle is refueling...") ---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)
-- Clear our inventory into the storage chest ---@param y number The UP/DOWN axis (grows from DOWN to UP)
self:MoveToPosition(0, 0, 0, self.config.storage_direction) ---@param z number The NORTH/SOUTH axis (grows from SOUTH to NORTH)
self:DropInventory() ---@param dir Direction
---@param skip_unstocking boolean | nil
-- Prepare refueling ---@param skip_refueling boolean | nil
self:TurnToDirection(self.config.fuel_direction) function TurtleController:MoveByVector(x, y, z, dir, skip_unstocking, skip_refueling)
turtle.select(1) self:MoveToPosition(
self.position.x + x,
-- Include the distance to the last position when refueling self.position.y + y,
-- to keep the amount of work done between refuelings constant self.position.z + z,
local target_fuel_level = self.config.refuel_amount dir,
local last_position = self.last_positions:Peek() skip_unstocking,
if last_position == nil then skip_refueling
shell.exit() )
else end
target_fuel_level = target_fuel_level + last_position.x + last_position.y + last_position.z
end ---Move to the previously stored position and pop this position from the stack.
---@param skip_unstocking boolean | nil
-- Refuel until we hit the refuel_amount ---@param skip_refueling boolean | nil
local before_level = turtle.getFuelLevel() function TurtleController:MoveBack(skip_unstocking, skip_refueling)
repeat local last_position = self.last_positions:Pop()
if not self:SuckItem(1, 1) then
printError("Failed to suck fuel out of fuel chest!") if last_position == nil then
shell.exit() error("Failed to obtain last_position to move back!")
end else
turtle.refuel() self:MoveToPosition(
until turtle.getFuelLevel() >= target_fuel_level last_position.x,
local after_level = turtle.getFuelLevel() last_position.y,
last_position.z,
self:MoveBack() last_position.dir,
skip_unstocking,
print(("Refuelled %d units, current level is %d (old level was %d)"):format(after_level - before_level, after_level, before_level)) skip_refueling
end )
-- Pop the stack because MoveToPosition pushes our current position
----------------------------------------------------------------------------------------------- self.last_positions:Pop()
-- Management Methods end
----------------------------------------------------------------------------------------------- end
-----------------------------------------------------------------------------------------------
function TurtleController:Configure() -- Testing Methods
local config_complete = false -----------------------------------------------------------------------------------------------
while not config_complete do ---@param block_name string
-- Check for valid fuel ---@param above boolean | nil
while self.config.fuel_name == "" do ---@param below boolean | nil
print("Please put valid fuel into the first slot and press Enter!") ---@return boolean
local _ = io.read() function TurtleController:TestForBlock(block_name, above, below)
self.config.fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name above = above or false
end below = below or false
if above and below then
print("A chest with fuel has to be placed beside the turtle's starting position. Is this chest...") error("Can only test for blocks in one direction!")
print(("...right of the turtle (enter %d)?"):format(Direction.EAST)) end
print(("...behind the turtle (enter %d)?"):format(Direction.SOUTH))
print(("...left of the turtle (enter %d)?"):format(Direction.WEST)) local inspect_function = turtle.inspect
print("By default, the chest is assumed on the left.") if above then
self.config.fuel_direction = tonumber(io.read()) or Direction.WEST inspect_function = turtle.inspectUp
elseif below then
print("A chest to store mined blocks has to be placed beside the turtle's starting position. Is this chest...") inspect_function = turtle.inspectDown
print(("...right of the turtle (enter %d)?"):format(Direction.EAST)) end
print(("...behind the turtle (enter %d)?"):format(Direction.SOUTH))
print(("...left of the turtle (enter %d)?"):format(Direction.WEST)) local has_block, block_data = inspect_function()
print("By default, the chest is assumed on the right.")
self.config.storage_direction = tonumber(io.read()) or Direction.EAST if not has_block then
return false
print("Configuration complete!") end
print(("Turtle will consume \"%s\" for fuel. Put the desired fuel into the first slot to change."):format(self.config.fuel_name))
print("Do you want to accept the configuration (enter 1, otherwise 0)?") return block_data.name == block_name
config_complete = tonumber(io.read()) == 1 end
end
end -----------------------------------------------------------------------------------------------
-- Inventory Methods
-----------------------------------------------------------------------------------------------
function TurtleController:EnableMining()
self.mine = true ---@return boolean
end function TurtleController:HasFuel()
local level = turtle.getFuelLevel()
local distance_home = self.position.x + self.position.y + self.position.z
function TurtleController:DisableMining()
self.mine = false return level > distance_home + self.config.refuel_safety_margin
end end
---@return boolean
function TurtleController:HasFuel() function TurtleController:HasInventorySpace()
local level = turtle.getFuelLevel() for slot = 1, 16 do
local distance_home = self.position.x + self.position.y + self.position.z if turtle.getItemDetail(slot) == nil then
return true
return level > distance_home + self.config.refuel_safety_margin end
end end
return false
function TurtleController:HasInventorySpace() end
for slot = 1,16 do
if turtle.getItemDetail(slot) == nil then ---@param slot number
return true ---@param count number
end ---@return boolean
end function TurtleController:SuckItemFromChest(slot, count)
if not self:TestForBlock("minecraft:chest") then
return false error("Can't suck item: no chest!")
end end
return TurtleController local previous_slot = turtle.getSelectedSlot()
turtle.select(slot or 1)
local sucked = turtle.suck(count)
turtle.select(previous_slot)
return sucked
end
---@param slot number
---@param count number
---@return boolean
function TurtleController:DropItemIntoChest(slot, count)
if not self:TestForBlock("minecraft:chest") then
error("Can't drop item: no chest!")
end
local previous_slot = turtle.getSelectedSlot()
turtle.select(slot or 1)
local dropped = turtle.drop(count)
turtle.select(previous_slot)
return dropped
end
function TurtleController:DropInventoryIntoChest()
if not self:TestForBlock("minecraft:chest") then
error("Can't drop item: no chest!")
end
print("Dropping inventory into chest...")
for slot = 1, 16 do
turtle.select(slot)
turtle.drop()
end
turtle.select(1)
end
---@param skip_inventory_check boolean | nil
function TurtleController:UnstockIfFull(skip_inventory_check)
skip_inventory_check = skip_inventory_check or false
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, true, true)
self:DropInventoryIntoChest()
self:RefuelIfEmpty()
self:TurnToDirection(Direction.NORTH)
self:MoveForward(1)
self:MoveBack(true, true)
end
---@param skip_fuel_check boolean | nil
function TurtleController:RefuelIfEmpty(skip_fuel_check)
skip_fuel_check = skip_fuel_check or false
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, true, true)
self:DropInventoryIntoChest()
-- Prepare refueling
self:TurnToDirection(self.config.fuel_direction)
turtle.select(1)
-- Include the distance to the last position when refueling
-- to keep the amount of work done between refuelings constant
local target_fuel_level = self.config.refuel_amount
local last_position = self.last_positions:Peek()
if last_position == nil then
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
-- Refuel until we hit the refuel_amount
local before_level = turtle.getFuelLevel()
repeat
if not self:SuckItemFromChest(1, 1) then
error("Failed to suck fuel out of fuel chest!")
end
turtle.refuel()
until turtle.getFuelLevel() >= target_fuel_level
local after_level = turtle.getFuelLevel()
self:TurnToDirection(Direction.NORTH)
self:MoveForward(1)
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
-----------------------------------------------------------------------------------------------
-- Management Methods
-----------------------------------------------------------------------------------------------
function TurtleController:Configure()
local config_complete = false
while not config_complete do
-- Check for valid fuel
while self.config.fuel_name == "" do
print("Please put valid fuel into the first slot and press Enter!")
local _ = io.read()
self.config.fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name
end
print("A chest with fuel has to be placed beside the turtle's starting position. Is this chest...")
print(("...right of the turtle (enter %d)?"):format(Direction.EAST))
print(("...behind the turtle (enter %d)?"):format(Direction.SOUTH))
print(("...left of the turtle (enter %d)?"):format(Direction.WEST))
print("By default, the chest is assumed on the left.")
self.config.fuel_direction = tonumber(io.read()) or Direction.WEST
print("A chest to store mined blocks has to be placed beside the turtle's starting position. Is this chest...")
print(("...right of the turtle (enter %d)?"):format(Direction.EAST))
print(("...behind the turtle (enter %d)?"):format(Direction.SOUTH))
print(("...left of the turtle (enter %d)?"):format(Direction.WEST))
print("By default, the chest is assumed on the right.")
self.config.storage_direction = tonumber(io.read()) or Direction.EAST
print("Configuration complete!")
print(
('Turtle will consume "%s" for fuel. Put the desired fuel into the first slot to change.'):format(
self.config.fuel_name
)
)
print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1
end
end
function TurtleController:EnableMiningForward()
self.mine_forward = true
end
function TurtleController:DisableMiningForward()
self.mine_forward = false
end
function TurtleController:EnableMiningAbove()
self.mine_above = true
end
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

@ -0,0 +1,220 @@
local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller")
---@class TurtleTestingController
---@field controller TurtleController
local TurtleTestingController = {}
TurtleTestingController.__index = TurtleTestingController
---@return TurtleTestingController
function TurtleTestingController:Create()
local t = {}
setmetatable(t, TurtleTestingController)
t.controller = TurtleController:Create()
return t
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
-----------------------------------------------------------------------------------------------
function TurtleTestingController:TestRelativeMovementWithRelativeRotation()
print("Testing relative movement with relative rotation...")
-- 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
function TurtleTestingController:TestRelativeMovementWithAbsoluteRotation()
print("Testing relative movement with absolute rotation...")
-- 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
function TurtleTestingController:TestAbsoluteMovementWithoutStack()
print("Testing absolute movement without stack...")
-- 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
function TurtleTestingController:TestAbsoluteMovementWithStack()
print("Testing absolute movement with stack...")
-- 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
function TurtleTestingController:TestUnstocking()
print("Testing inventory unloading...")
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()
print("The turtle should be in its original location.")
print("Press Enter to continue")
_ = io.read()
end
function TurtleTestingController:TestRefueling()
print("Testing refueling...")
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()
print("The turtle should be in its original location.")
print("Press Enter to continue")
_ = io.read()
end
-----------------------------------------------------------------------------------------------
-- Main Method
-----------------------------------------------------------------------------------------------
function TurtleTestingController: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")
print("2: Relative movement with absolute rotation")
print("3: Absolute movement without stack")
print("4: Absolute movement with stack")
print("5: All movement tests")
print("6: Unloading")
print("7: Refueling")
print("8: All non-movement tests")
print("9: All tests")
local choice = 0
while choice < 1 or choice > 9 do
print("Choose a test by entering its number:")
choice = tonumber(io.read()) or 0
end
if choice == 1 then
self:TestRelativeMovementWithRelativeRotation()
elseif choice == 2 then
self:TestRelativeMovementWithAbsoluteRotation()
elseif choice == 3 then
self:TestAbsoluteMovementWithoutStack()
elseif choice == 4 then
self:TestAbsoluteMovementWithStack()
elseif choice == 5 then
self:TestRelativeMovementWithRelativeRotation()
self:TestRelativeMovementWithAbsoluteRotation()
self:TestAbsoluteMovementWithoutStack()
self:TestAbsoluteMovementWithStack()
elseif choice == 6 then
self:TestUnstocking()
elseif choice == 7 then
self:TestRefueling()
elseif choice == 8 then
self:TestUnstocking()
self:TestRefueling()
elseif choice == 9 then
self:TestRelativeMovementWithRelativeRotation()
self:TestRelativeMovementWithAbsoluteRotation()
self:TestAbsoluteMovementWithoutStack()
self:TestAbsoluteMovementWithStack()
self:TestUnstocking()
self:TestRefueling()
end
end
return TurtleTestingController

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,9 +1,9 @@
---@enum Direction ---@enum Direction
local Direction = { local Direction = {
NORTH = 0, NORTH = 0,
EAST = 1, EAST = 1,
SOUTH = 2, SOUTH = 2,
WEST = 3 WEST = 3,
} }
return Direction return Direction

View File

@ -1,70 +1,69 @@
local Direction = require("lib.direction") local Direction = require("lib.direction")
---@class Position ---@class Position
---@field x number ---@field x number
---@field y number ---@field y number
---@field z number ---@field z number
---@field dir Direction ---@field dir Direction
local Position = {} local Position = {}
Position.__index = Position Position.__index = Position
---@return Position
---@return Position function Position:Empty()
function Position:Empty() local t = {}
local t = {} setmetatable(t, Position)
setmetatable(t, Position)
t.x = 0
t.x = 0 t.y = 0
t.y = 0 t.z = 0
t.z = 0 t.dir = Direction.NORTH
t.dir = Direction.NORTH
return t
return t end
end
---@param x number
---@param x number ---@param y number
---@param y number ---@param z number
---@param z number ---@param dir Direction
---@param dir Direction ---@return Position
---@return Position function Position:Create(x, y, z, dir)
function Position:Create(x, y, z, dir) local t = {}
local t = {} setmetatable(t, Position)
setmetatable(t, Position)
t.x = x
t.x = x t.y = y
t.y = y t.z = z
t.z = z t.dir = dir or Direction.NORTH
t.dir = dir or Direction.NORTH
return t
return t end
end
---@param other Position
---@param other Position ---@return Position
---@return Position function Position:Copy(other)
function Position:Copy(other) local t = {}
local t = {} setmetatable(t, Position)
setmetatable(t, Position)
t.x = other.x
t.x = other.x t.y = other.y
t.y = other.y t.z = other.z
t.z = other.z t.dir = other.dir
t.dir = other.dir
return t
return t end
end
---@param other Position
---@param other Position function Position:Add(other)
function Position:Add(other) self.x = self.x + other.x
self.x = self.x + other.x self.y = self.y + other.y
self.y = self.y + other.y self.z = self.z + other.z
self.z = self.z + other.z end
end
---@param other Position
---@param other Position function Position:Subtract(other)
function Position:Subtract(other) self.x = self.x - other.x
self.x = self.x - other.x self.y = self.y - other.y
self.y = self.y - other.y self.z = self.z - other.z
self.z = self.z - other.z end
end
return Position
return Position

View File

@ -1,61 +1,53 @@
local Position = require("lib.position") ---@class Stack
---@field elements Position[]
---@class Stack local Stack = {}
---@field elements Position[] Stack.__index = Stack
local Stack = {}
Stack.__index = Stack ---@return Stack
function Stack:Create()
-- stack table
---@return Stack local t = {}
function Stack:Create() setmetatable(t, Stack)
-- stack table
local t = {} -- entry table
setmetatable(t, Stack) t.elements = {}
-- entry table return t
t.elements = {} end
return t ---@param element Position
end function Stack:Push(element)
if element == nil or element == {} then
printError("Failed to push empty element to the stack!")
---@param element Position return
function Stack:Push(element) end
if element == nil or element == {} then
printError("Failed to push empty element to the stack!") table.insert(self.elements, element)
return end
end
---@return Position | nil
table.insert(self.elements, element) function Stack:Pop()
end if self:Count() == 0 then
printError("Failed to pop element from the empty stack!")
return nil
---@return Position | nil end
function Stack:Pop()
if self:Count() == 0 then return table.remove(self.elements)
printError("Failed to pop element from the empty stack!") end
return nil
end ---@return Position | nil
function Stack:Peek()
return table.remove(self.elements) if self:Count() == 0 then
end printError("Failed to peek element from the empty stack!")
return nil
end
---@return Position | nil
function Stack:Peek() return self.elements[#self.elements]
if self:Count() == 0 then end
printError("Failed to peek element from the empty stack!")
return nil ---@return number
end function Stack:Count()
return #self.elements
return self.elements[#self.elements] end
end
return Stack
---@return number
function Stack:Count()
return #self.elements
end
return Stack

View File

@ -1,23 +1,36 @@
local TestingController = require("controller.testing_controller") local TurtleTestingController = require("controller.turtle_testing_controller")
local VolumeExcavationController = require("controller.volume_excavation_controller") local AudioTestingController = require("controller.audio_testing_controller")
local NetheriteController = require("controller.netherite_controller") local ExcavationController = require("controller.excavation_controller")
local AudioController = require("controller.audio_controller")
local controllers = { local controllers = {
TestingController:Create(), TurtleTestingController:Create(),
VolumeExcavationController:Create(), AudioTestingController:Create(),
NetheriteController:Create(), ExcavationController:Create(),
} AudioController:Create(),
}
print("Multiple controllers are available:")
print("1: Testing Mode") print("Multiple controllers are available:")
print("2: Volume Excavation") print("1: Turtle Testing Mode")
print("3: Netherite Excavation") print("2: Audio Testing Mode")
print("3: Volume Excavation")
local choice = 0 print("4: Play Bangarang")
while choice < 1 or choice > #controllers do
print("Choose a controller by entering its number:") local choice = 0
choice = tonumber(io.read()) or 0 while choice < 1 or choice > #controllers do
end print("Choose a controller by entering its number:")
choice = tonumber(io.read()) or 0
controllers[choice]:Run() end
controllers[choice]:Run()
-- TODO: When refueling but chest is missing, wait there until the user puts a chest with fuel
-- TODO: Improve error handling: Don't abort the program in all cases,
-- but wait until the user fixes the issue (e.g. places a chest)
-- TODO: Add shortcuts for the volume excavation:
-- - Tunnel forward 3x2 or 3x3 with specified length
-- - Excavate downwards (quarry) in a square, specify side length
-- TODO: Add controller to build rooms with walls of a specified material
-- TODO: Add a stair building mode (specify side length, min 2x2)
-- TODO: Add configurable trash_list with items that won't be picked up