1
This commit is contained in:
2025-10-07 14:52:17 +02:00
parent b71267bf42
commit a5b159fca4
10 changed files with 1169 additions and 1178 deletions

9
.luarc.json Normal file
View File

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

16
.vscode/settings.json vendored
View File

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

View File

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

View File

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

View File

@ -1,211 +1,220 @@
local Direction = require("lib.direction") local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller") local TurtleController = require("controller.turtle_controller")
---@class TestingController ---@class TestingController
---@field controller TurtleController ---@field controller TurtleController
local TestingController = {} local TestingController = {}
TestingController.__index = TestingController TestingController.__index = TestingController
---@return TestingController
---@return TestingController function TestingController:Create()
function TestingController:Create() local t = {}
local t = {} setmetatable(t, TestingController)
setmetatable(t, TestingController)
t.controller = TurtleController:Create()
t.controller = TurtleController:Create()
return t
return t end
end
-----------------------------------------------------------------------------------------------
-- Behavior Methods
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
-- Behavior Methods
----------------------------------------------------------------------------------------------- function TestingController:TestRelativeMovementWithRelativeRotation()
print("Testing relative movement with relative rotation...")
function TestingController:TestRelativeMovementWithRelativeRotation() -- N: BackCenter (Center)
print("Testing relative movement with relative rotation...") self.controller:MoveForward(3)
self.controller:TurnRelative(1) -- E: FrontCenter (Center)
-- N: BackCenter (Center) self.controller:MoveForward(3)
self.controller:MoveForward(3) self.controller:TurnRelative(2) -- W: FrontRight (Center)
self.controller:TurnRelative(1) -- E: FrontCenter (Center) self.controller:MoveVertical(3)
self.controller:MoveForward(3) self.controller:TurnRelative(8) -- W: FrontRight (Top)
self.controller:TurnRelative(2) -- W: FrontRight (Center) self.controller:MoveForward(3)
self.controller:MoveVertical(3) self.controller:TurnRelative(3) -- S: FrontCenter (Top)
self.controller:TurnRelative(8) -- W: FrontRight (Top) self.controller:MoveForward(3)
self.controller:MoveForward(3) self.controller:TurnRelative(9) -- W: BackCenter (Top)
self.controller:TurnRelative(3) -- S: FrontCenter (Top) self.controller:MoveVertical(-3)
self.controller:MoveForward(3) self.controller:TurnRelative(1) -- N: BackCenter (Center)
self.controller:TurnRelative(9) -- W: BackCenter (Top)
self.controller:MoveVertical(-3) print("The turtle should be in its original location.")
self.controller:TurnRelative(1) -- N: BackCenter (Center) print(
("The turtle has internal position (%d, %d, %d) and internal direction %d"):format(
print("The turtle should be in its original location.") self.controller.position.x,
print(("The turtle has internal position (%d, %d, %d) and internal direction %d"):format( self.controller.position.y,
self.controller.position.x, self.controller.position.y, self.controller.position.z, self.controller.position.dir self.controller.position.z,
)) self.controller.position.dir
print("Press Enter to continue") )
_ = io.read() )
end print("Press Enter to continue")
_ = io.read()
end
function TestingController:TestRelativeMovementWithAbsoluteRotation()
print("Testing relative movement with absolute rotation...") function TestingController:TestRelativeMovementWithAbsoluteRotation()
print("Testing relative movement with absolute rotation...")
-- N: BackCenter (Center)
self.controller:MoveForward(3) -- N: BackCenter (Center)
self.controller:TurnToDirection(Direction.EAST) -- E: FrontCenter (Center) self.controller:MoveForward(3)
self.controller:MoveForward(3) self.controller:TurnToDirection(Direction.EAST) -- E: FrontCenter (Center)
self.controller:TurnToDirection(Direction.WEST) -- W: FrontRight (Center) self.controller:MoveForward(3)
self.controller:MoveVertical(3) self.controller:TurnToDirection(Direction.WEST) -- W: FrontRight (Center)
self.controller:TurnToDirection(Direction.WEST) -- W: FrontRight (Top) self.controller:MoveVertical(3)
self.controller:MoveForward(3) self.controller:TurnToDirection(Direction.WEST) -- W: FrontRight (Top)
self.controller:TurnToDirection(Direction.SOUTH) -- S: FrontCenter (Top) self.controller:MoveForward(3)
self.controller:MoveForward(3) self.controller:TurnToDirection(Direction.SOUTH) -- S: FrontCenter (Top)
self.controller:TurnToDirection(Direction.WEST) -- W: BackCenter (Top) self.controller:MoveForward(3)
self.controller:MoveVertical(-3) self.controller:TurnToDirection(Direction.WEST) -- W: BackCenter (Top)
self.controller:TurnToDirection(Direction.NORTH) -- N: BackCenter (Center) 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( print("The turtle should be in its original location.")
self.controller.position.x, self.controller.position.y, self.controller.position.z, self.controller.position.dir print(
)) ("The turtle has internal position (%d, %d, %d) and internal direction %d"):format(
print("Press Enter to continue") self.controller.position.x,
_ = io.read() self.controller.position.y,
end self.controller.position.z,
self.controller.position.dir
)
function TestingController:TestAbsoluteMovementWithoutStack() )
print("Testing absolute movement without stack...") print("Press Enter to continue")
_ = io.read()
-- N: BackCenter (Center) end
self.controller:MoveToPosition(0, 0, 3, Direction.EAST) -- E: FrontCenter (Center)
self.controller:MoveToPosition(3, 0, 3, Direction.WEST) -- W: FrontRight (Center) function TestingController:TestAbsoluteMovementWithoutStack()
self.controller:MoveToPosition(3, 3, 3, Direction.WEST) -- W: FrontRight (Top) print("Testing absolute movement without stack...")
self.controller:MoveToPosition(0, 3, 3, Direction.SOUTH) -- S: FrontCenter (Top)
self.controller:MoveToPosition(0, 3, 0, Direction.WEST) -- W: BackCenter (Top) -- N: BackCenter (Center)
self.controller:MoveToPosition(0, 0, 0, Direction.NORTH) -- 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)
print("The turtle should be in its original location.") self.controller:MoveToPosition(3, 3, 3, Direction.WEST) -- W: FrontRight (Top)
print(("The turtle has internal position (%d, %d, %d) and internal direction %d"):format( self.controller:MoveToPosition(0, 3, 3, Direction.SOUTH) -- S: FrontCenter (Top)
self.controller.position.x, self.controller.position.y, self.controller.position.z, self.controller.position.dir self.controller:MoveToPosition(0, 3, 0, Direction.WEST) -- W: BackCenter (Top)
)) self.controller:MoveToPosition(0, 0, 0, Direction.NORTH) -- N: BackCenter (Center)
print("Press Enter to continue")
_ = io.read() print("The turtle should be in its original location.")
end print(
("The turtle has internal position (%d, %d, %d) and internal direction %d"):format(
self.controller.position.x,
function TestingController:TestAbsoluteMovementWithStack() self.controller.position.y,
print("Testing absolute movement with stack...") self.controller.position.z,
self.controller.position.dir
-- N: BotCenter (Center) )
self.controller:MoveToPosition(0, 0, 3, Direction.EAST) -- E: TopCenter (Center) )
self.controller:MoveBack() -- N: BotCenter (Center) print("Press Enter to continue")
self.controller:MoveToPosition(3, 3, 3, Direction.WEST) -- W: TopRight (Top) _ = io.read()
self.controller:MoveBack() -- N: BotCenter (Center) end
print("The turtle should be in its original location.") function TestingController:TestAbsoluteMovementWithStack()
print(("The turtle has internal position (%d, %d, %d) and internal direction %d"):format( print("Testing absolute movement with stack...")
self.controller.position.x, self.controller.position.y, self.controller.position.z, self.controller.position.dir
)) -- N: BotCenter (Center)
print("Press Enter to continue") self.controller:MoveToPosition(0, 0, 3, Direction.EAST) -- E: TopCenter (Center)
_ = io.read() self.controller:MoveBack() -- N: BotCenter (Center)
end self.controller:MoveToPosition(3, 3, 3, Direction.WEST) -- W: TopRight (Top)
self.controller:MoveBack() -- N: BotCenter (Center)
function TestingController:TestUnstocking() print("The turtle should be in its original location.")
print("Testing inventory unloading...") print(
("The turtle has internal position (%d, %d, %d) and internal direction %d"):format(
self.controller:MoveToPosition(3, 0, 3, Direction.WEST) self.controller.position.x,
print("Please fill the entire inventory with items.") self.controller.position.y,
print("Press Enter to continue") self.controller.position.z,
_ = io.read() self.controller.position.dir
self.controller:UnstockIfFull(true) )
print("Press Enter to continue") )
_ = io.read() print("Press Enter to continue")
self.controller:MoveBack() _ = io.read()
end
print("The turtle should be in its original location.")
print("Press Enter to continue") function TestingController:TestUnstocking()
_ = io.read() print("Testing inventory unloading...")
end
self.controller:MoveToPosition(3, 0, 3, Direction.WEST)
print("Please fill the entire inventory with items.")
function TestingController:TestRefueling() print("Press Enter to continue")
print("Testing refueling...") _ = io.read()
self.controller:UnstockIfFull(true)
self.controller:MoveToPosition(3, 0, 3, Direction.WEST) print("Press Enter to continue")
print("Please fill multiple inventory slots with items.") _ = io.read()
print("Press Enter to continue") self.controller:MoveBack()
_ = io.read()
self.controller:RefuelIfEmpty(true) print("The turtle should be in its original location.")
print("Press Enter to continue") print("Press Enter to continue")
_ = io.read() _ = io.read()
self.controller:MoveBack() end
print("The turtle should be in its original location.") function TestingController:TestRefueling()
print("Press Enter to continue") print("Testing refueling...")
_ = io.read()
end self.controller:MoveToPosition(3, 0, 3, Direction.WEST)
print("Please fill multiple inventory slots with items.")
print("Press Enter to continue")
----------------------------------------------------------------------------------------------- _ = io.read()
-- Main Method self.controller:RefuelIfEmpty(true)
----------------------------------------------------------------------------------------------- print("Press Enter to continue")
_ = io.read()
self.controller:MoveBack()
function TestingController:Run()
self.controller:Configure() print("The turtle should be in its original location.")
self.controller:DisableMiningForward() print("Press Enter to continue")
self.controller:DisableMiningAbove() _ = io.read()
self.controller:DisableMiningBelow() end
print("There are multiple tests available:") -----------------------------------------------------------------------------------------------
print("1: Relative movement with relative rotation") -- Main Method
print("2: Relative movement with absolute rotation") -----------------------------------------------------------------------------------------------
print("3: Absolute movement without stack")
print("4: Absolute movement with stack") function TestingController:Run()
print("5: All movement tests") self.controller:Configure()
print("6: Unloading") self.controller:DisableMiningForward()
print("7: Refueling") self.controller:DisableMiningAbove()
print("8: All non-movement tests") self.controller:DisableMiningBelow()
print("9: All tests")
print("There are multiple tests available:")
local choice = 0 print("1: Relative movement with relative rotation")
while choice < 1 or choice > 9 do print("2: Relative movement with absolute rotation")
print("Choose a test by entering its number:") print("3: Absolute movement without stack")
choice = tonumber(io.read()) or 0 print("4: Absolute movement with stack")
end print("5: All movement tests")
print("6: Unloading")
if choice == 1 then print("7: Refueling")
self:TestRelativeMovementWithRelativeRotation() print("8: All non-movement tests")
elseif choice == 2 then print("9: All tests")
self:TestRelativeMovementWithAbsoluteRotation()
elseif choice == 3 then local choice = 0
self:TestAbsoluteMovementWithoutStack() while choice < 1 or choice > 9 do
elseif choice == 4 then print("Choose a test by entering its number:")
self:TestAbsoluteMovementWithStack() choice = tonumber(io.read()) or 0
elseif choice == 5 then end
self:TestRelativeMovementWithRelativeRotation()
self:TestRelativeMovementWithAbsoluteRotation() if choice == 1 then
self:TestAbsoluteMovementWithoutStack() self:TestRelativeMovementWithRelativeRotation()
self:TestAbsoluteMovementWithStack() elseif choice == 2 then
elseif choice == 6 then self:TestRelativeMovementWithAbsoluteRotation()
self:TestUnstocking() elseif choice == 3 then
elseif choice == 7 then self:TestAbsoluteMovementWithoutStack()
self:TestRefueling() elseif choice == 4 then
elseif choice == 8 then self:TestAbsoluteMovementWithStack()
self:TestUnstocking() elseif choice == 5 then
self:TestRefueling() self:TestRelativeMovementWithRelativeRotation()
elseif choice == 9 then self:TestRelativeMovementWithAbsoluteRotation()
self:TestRelativeMovementWithRelativeRotation() self:TestAbsoluteMovementWithoutStack()
self:TestRelativeMovementWithAbsoluteRotation() self:TestAbsoluteMovementWithStack()
self:TestAbsoluteMovementWithoutStack() elseif choice == 6 then
self:TestAbsoluteMovementWithStack() self:TestUnstocking()
self:TestUnstocking() elseif choice == 7 then
self:TestRefueling() self:TestRefueling()
end elseif choice == 8 then
end self:TestUnstocking()
self:TestRefueling()
elseif choice == 9 then
return TestingController self:TestRelativeMovementWithRelativeRotation()
self:TestRelativeMovementWithAbsoluteRotation()
self:TestAbsoluteMovementWithoutStack()
self:TestAbsoluteMovementWithStack()
self:TestUnstocking()
self:TestRefueling()
end
end
return TestingController

View File

@ -1,462 +1,457 @@
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_forward boolean ---@field mine_forward boolean
---@field mine_above boolean ---@field mine_above boolean
---@field mine_below boolean ---@field mine_below boolean
local TurtleController = {} local TurtleController = {}
TurtleController.__index = TurtleController TurtleController.__index = TurtleController
---@return TurtleController
---@return TurtleController function TurtleController:Create()
function TurtleController:Create() local t = {}
local t = {} setmetatable(t, TurtleController)
setmetatable(t, TurtleController)
-----------------------------------------------------------------------------------------------
-- Fields
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
-- Fields
----------------------------------------------------------------------------------------------- t.config = {
fuel_direction = Direction.EAST,
fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name,
t.config = { refuel_amount = 500,
fuel_direction = Direction.EAST, refuel_safety_margin = 160,
fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name, storage_direction = Direction.WEST,
refuel_amount = 500, }
refuel_safety_margin = 160, t.position = Position:Empty()
storage_direction = Direction.WEST, t.last_positions = Stack:Create()
} t.mine_forward = false
t.position = Position:Empty() t.mine_above = false
t.last_positions = Stack:Create() t.mine_below = false
t.mine_forward = false
t.mine_above = false return t
t.mine_below = 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 % 4 == 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
-- Turn turtle turtle.turnLeft()
local turns = number_of_turns % 4 else
if turns == 3 then -- print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, turns))
-- print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, -1))
for _ = 1, turns do
-- If we're rotating by 3, we could turn faster by rotating by 1 in the opposite direction turtle.turnRight()
turtle.turnLeft() end
else end
-- print(("Turtle is turning by %d (shortened to %d)..."):format(number_of_turns, turns))
-- Update current_position
for _ = 1,turns do self.position.dir = (self.position.dir + number_of_turns) % 4
turtle.turnRight() end
end
end ---@param direction Direction
function TurtleController:TurnToDirection(direction)
-- Update current_position if self.position.dir == direction then
self.position.dir = (self.position.dir + number_of_turns) % 4 return
end end
self:TurnRelative(direction - self.position.dir)
---@param direction Direction end
function TurtleController:TurnToDirection(direction)
if self.position.dir == direction then ---Move forward by a number of blocks depending on the current rotation
return ---@param number_of_blocks number
end ---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil
self:TurnRelative(direction - self.position.dir) function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_refueling)
end skip_unstocking = skip_unstocking or false
skip_refueling = skip_refueling or false
if number_of_blocks == 0 then
---Move forward by a number of blocks depending on the current rotation return
---@param number_of_blocks number end
---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil -- print(("Turtle is moving forward by %d blocks..."):format(number_of_blocks))
function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_refueling)
skip_unstocking = skip_unstocking or false for _ = 1, math.abs(number_of_blocks) do
skip_refueling = skip_refueling or false if not skip_refueling then
if number_of_blocks == 0 then self:RefuelIfEmpty()
return end
end if not skip_unstocking then
self:UnstockIfFull()
-- print(("Turtle is moving forward by %d blocks..."):format(number_of_blocks)) end
for _ = 1,math.abs(number_of_blocks) do -- Mine/Move
if not skip_refueling then if self.mine_forward then
self:RefuelIfEmpty() while not turtle.forward() do
end turtle.dig()
if not skip_unstocking then end
self:UnstockIfFull() elseif not turtle.forward() then
end error("Turtle failed to move forward!")
end
-- Mine/Move if self.mine_above then
if self.mine_forward then turtle.digUp()
while not turtle.forward() do end
turtle.dig() if self.mine_below then
end turtle.digDown()
elseif not turtle.forward() then end
error("Turtle failed to move forward!")
end -- Update current position
if self.mine_above then if self.position.dir == Direction.NORTH then
turtle.digUp() self.position.z = self.position.z + 1
end elseif self.position.dir == Direction.SOUTH then
if self.mine_below then self.position.z = self.position.z - 1
turtle.digDown() elseif self.position.dir == Direction.EAST then
end self.position.x = self.position.x + 1
elseif self.position.dir == Direction.WEST then
-- Update current position self.position.x = self.position.x - 1
if self.position.dir == Direction.NORTH then end
self.position.z = self.position.z + 1 end
elseif self.position.dir == Direction.SOUTH then end
self.position.z = self.position.z - 1
elseif self.position.dir == Direction.EAST then ---Positive numbers move up, negative numbers move down.
self.position.x = self.position.x + 1 ---@param number_of_blocks number
elseif self.position.dir == Direction.WEST then ---@param skip_unstocking boolean | nil
self.position.x = self.position.x - 1 ---@param skip_refueling boolean | nil
end function TurtleController:MoveVertical(number_of_blocks, skip_unstocking, skip_refueling)
end skip_unstocking = skip_unstocking or false
end skip_refueling = skip_refueling or false
if number_of_blocks == 0 then
---Positive numbers move up, negative numbers move down. return
---@param number_of_blocks number end
---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil -- print(("Turtle is moving vertically by %d blocks..."):format(number_of_blocks))
function TurtleController:MoveVertical(number_of_blocks, skip_unstocking, skip_refueling)
skip_unstocking = skip_unstocking or false local move_function = number_of_blocks > 0 and turtle.up or turtle.down
skip_refueling = skip_refueling or false local mine_function = number_of_blocks > 0 and turtle.digUp or turtle.digDown
if number_of_blocks == 0 then local mine_enabled = number_of_blocks > 0 and self.mine_above or self.mine_below
return
end for _ = 1, math.abs(number_of_blocks) do
if not skip_refueling then
-- print(("Turtle is moving vertically by %d blocks..."):format(number_of_blocks)) self:RefuelIfEmpty()
end
local move_function = number_of_blocks > 0 and turtle.up or turtle.down if not skip_unstocking then
local mine_function = number_of_blocks > 0 and turtle.digUp or turtle.digDown self:UnstockIfFull()
local mine_enabled = number_of_blocks > 0 and self.mine_above or self.mine_below end
for _ = 1,math.abs(number_of_blocks) do -- Mine
if not skip_refueling then if mine_enabled then
self:RefuelIfEmpty() mine_function()
end end
if not skip_unstocking then
self:UnstockIfFull() -- Move
end if not move_function() then
error("Turtle failed to move vertically!")
-- Mine end
if mine_enabled then
mine_function() -- Update current position
end if number_of_blocks > 0 then
self.position.y = self.position.y + 1
-- Move elseif number_of_blocks < 0 then
if not move_function() then self.position.y = self.position.y - 1
error("Turtle failed to move vertically!") end
end end
end
-- Update current position
if number_of_blocks > 0 then ---Stores the current position on the stack so we can return using TurtleController:MoveBack()
self.position.y = self.position.y + 1 function TurtleController:StorePosition()
elseif number_of_blocks < 0 then self.last_positions:Push(Position:Copy(self.position))
self.position.y = self.position.y - 1 end
end
end ---Move to an absolute position. Stores the current position on the stack so we can return using TurtleController:MoveBack()
end ---@param x number The EAST/WEST axis (grows from WEST to EAST)
---@param y number The UP/DOWN axis (grows from DOWN to UP)
---@param z number The NORTH/SOUTH axis (grows from SOUTH to NORTH)
---Stores the current position on the stack so we can return using TurtleController:MoveBack() ---@param dir Direction
function TurtleController:StorePosition() ---@param skip_unstocking boolean | nil
self.last_positions:Push(Position:Copy(self.position)) ---@param skip_refueling boolean | nil
end function TurtleController:MoveToPosition(x, y, z, dir, skip_unstocking, skip_refueling)
-- print(("Turtle is moving to (%d, %d, %d)..."):format(x, y, z))
---Move to an absolute position. Stores the current position on the stack so we can return using TurtleController:MoveBack() -- Store 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) self.last_positions:Push(Position:Copy(self.position))
---@param y number The UP/DOWN axis (grows from DOWN to UP)
---@param z number The NORTH/SOUTH axis (grows from SOUTH to NORTH) -- EAST/WEST axis (do first to not interfere with chests)
---@param dir Direction if self.position.x > x then
---@param skip_unstocking boolean | nil self:TurnToDirection(Direction.WEST)
---@param skip_refueling boolean | nil elseif self.position.x < x then
function TurtleController:MoveToPosition(x, y, z, dir, skip_unstocking, skip_refueling) self:TurnToDirection(Direction.EAST)
-- print(("Turtle is moving to (%d, %d, %d)..."):format(x, y, z)) end
self:MoveForward(math.abs(x - self.position.x), skip_unstocking, skip_refueling)
-- Store the current position on the stack, so we can return using TurtleController:MoveBack()
self.last_positions:Push(Position:Copy(self.position)) -- NORTH/SOUTH axis
if self.position.z > z then
-- EAST/WEST axis (do first to not interfere with chests) self:TurnToDirection(Direction.SOUTH)
if self.position.x > x then elseif self.position.z < z then
self:TurnToDirection(Direction.WEST) self:TurnToDirection(Direction.NORTH)
elseif self.position.x < x then end
self:TurnToDirection(Direction.EAST) self:MoveForward(math.abs(z - self.position.z), skip_unstocking, skip_refueling)
end
self:MoveForward(math.abs(x - self.position.x), skip_unstocking, skip_refueling) -- UP/DOWN axis
self:MoveVertical(y - self.position.y, skip_unstocking, skip_refueling)
-- NORTH/SOUTH axis
if self.position.z > z then -- Direction
self:TurnToDirection(Direction.SOUTH) self:TurnToDirection(dir or Direction.NORTH)
elseif self.position.z < z then
self:TurnToDirection(Direction.NORTH) -- 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
self:MoveForward(math.abs(z - self.position.z), skip_unstocking, skip_refueling) error("TurtleController:MoveToPosition failed to move to target position!")
end
-- UP/DOWN axis end
self:MoveVertical(y - self.position.y, skip_unstocking, skip_refueling)
---Move by a vector. Stores the current position on the stack so we can return using TurtleController:MoveBack()
-- Direction ---@param x number The EAST/WEST axis (grows from WEST to EAST)
self:TurnToDirection(dir or Direction.NORTH) ---@param y number The UP/DOWN axis (grows from DOWN to UP)
---@param z number The NORTH/SOUTH axis (grows from SOUTH to NORTH)
-- Sanity check ---@param dir Direction
if not(self.position.x == x and self.position.y == y and self.position.z == z and self.position.dir == dir) then ---@param skip_unstocking boolean | nil
error("TurtleController:MoveToPosition failed to move to target position!") ---@param skip_refueling boolean | nil
end function TurtleController:MoveByVector(x, y, z, dir, skip_unstocking, skip_refueling)
end self:MoveToPosition(
self.position.x + x,
self.position.y + y,
---Move by a vector. Stores the current position on the stack so we can return using TurtleController:MoveBack() self.position.z + z,
---@param x number The EAST/WEST axis (grows from WEST to EAST) dir,
---@param y number The UP/DOWN axis (grows from DOWN to UP) skip_unstocking,
---@param z number The NORTH/SOUTH axis (grows from SOUTH to NORTH) skip_refueling
---@param dir Direction )
---@param skip_unstocking boolean | nil end
---@param skip_refueling boolean | nil
function TurtleController:MoveByVector(x, y, z, dir, skip_unstocking, skip_refueling) ---Move to the previously stored position and pop this position from the stack.
self:MoveToPosition(self.position.x + x, self.position.y + y, self.position.z + z, dir, skip_unstocking, skip_refueling) ---@param skip_unstocking boolean | nil
end ---@param skip_refueling boolean | nil
function TurtleController:MoveBack(skip_unstocking, skip_refueling)
local last_position = self.last_positions:Pop()
---Move to the previously stored position and pop this position from the stack.
---@param skip_unstocking boolean | nil if last_position == nil then
---@param skip_refueling boolean | nil error("Failed to obtain last_position to move back!")
function TurtleController:MoveBack(skip_unstocking, skip_refueling) else
local last_position = self.last_positions:Pop() self:MoveToPosition(
last_position.x,
if last_position == nil then last_position.y,
error("Failed to obtain last_position to move back!") last_position.z,
else last_position.dir,
self:MoveToPosition(last_position.x, last_position.y, last_position.z, last_position.dir, skip_unstocking, skip_refueling) skip_unstocking,
skip_refueling
-- Pop the stack because MoveToPosition pushes our current position )
self.last_positions:Pop()
end -- Pop the stack because MoveToPosition pushes our current position
end self.last_positions:Pop()
end
end
-----------------------------------------------------------------------------------------------
-- Inventory Methods -----------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------- -- Inventory Methods
-----------------------------------------------------------------------------------------------
---@return boolean ---@return boolean
function TurtleController:HasFuel() function TurtleController:HasFuel()
local level = turtle.getFuelLevel() local level = turtle.getFuelLevel()
local distance_home = self.position.x + self.position.y + self.position.z local distance_home = self.position.x + self.position.y + self.position.z
return level > distance_home + self.config.refuel_safety_margin return level > distance_home + self.config.refuel_safety_margin
end end
---@return boolean
---@return boolean function TurtleController:HasInventorySpace()
function TurtleController:HasInventorySpace() for slot = 1, 16 do
for slot = 1,16 do if turtle.getItemDetail(slot) == nil then
if turtle.getItemDetail(slot) == nil then return true
return true end
end end
end
return false
return false end
end
---@param slot number
---@param count number
---@param slot number ---@return boolean
---@param count number function TurtleController:SuckItem(slot, count)
---@return boolean local previous_slot = turtle.getSelectedSlot()
function TurtleController:SuckItem(slot, count)
local previous_slot = turtle.getSelectedSlot() turtle.select(slot or 1)
local sucked = turtle.suck(count)
turtle.select(slot or 1)
local sucked = turtle.suck(count) turtle.select(previous_slot)
turtle.select(previous_slot) return sucked
end
return sucked
end ---@param slot number
---@param count number
---@return boolean
---@param slot number function TurtleController:DropItem(slot, count)
---@param count number local previous_slot = turtle.getSelectedSlot()
---@return boolean
function TurtleController:DropItem(slot, count) turtle.select(slot or 1)
local previous_slot = turtle.getSelectedSlot() local dropped = turtle.drop(count)
turtle.select(slot or 1) turtle.select(previous_slot)
local dropped = turtle.drop(count)
return dropped
turtle.select(previous_slot) end
return dropped function TurtleController:DropInventory()
end print("Dropping inventory into chest...")
for slot = 1, 16 do
function TurtleController:DropInventory() turtle.select(slot)
print("Dropping inventory into chest...") turtle.drop()
end
for slot = 1,16 do
turtle.select(slot) turtle.select(1)
turtle.drop() end
end
---@param skip_inventory_check boolean | nil
turtle.select(1) function TurtleController:UnstockIfFull(skip_inventory_check)
end skip_inventory_check = skip_inventory_check or false
if not skip_inventory_check and self:HasInventorySpace() then
return
---@param skip_inventory_check boolean | nil end
function TurtleController:UnstockIfFull(skip_inventory_check) if skip_inventory_check then
skip_inventory_check = skip_inventory_check or false print(("HasInventorySpace() returned %s"):format(self:HasInventorySpace()))
if not skip_inventory_check and self:HasInventorySpace() then end
return
end print("Turtle is unstocking...")
if skip_inventory_check then
print(("HasInventorySpace() returned %s"):format(self:HasInventorySpace())) self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true)
end self:DropInventory()
self:RefuelIfEmpty()
print("Turtle is unstocking...")
self:MoveBack(true, true)
self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true) end
self:DropInventory()
self:RefuelIfEmpty() ---@param skip_fuel_check boolean | nil
function TurtleController:RefuelIfEmpty(skip_fuel_check)
self:MoveBack(true, true) skip_fuel_check = skip_fuel_check or false
end if not skip_fuel_check and self:HasFuel() then
return
end
---@param skip_fuel_check boolean | nil if skip_fuel_check then
function TurtleController:RefuelIfEmpty(skip_fuel_check) print(("HasFuel() returned %s"):format(self:HasFuel()))
skip_fuel_check = skip_fuel_check or false end
if not skip_fuel_check and self:HasFuel() then
return print("Turtle is refueling...")
end
if skip_fuel_check then -- Clear our inventory into the storage chest
print(("HasFuel() returned %s"):format(self:HasFuel())) self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true)
end self:DropInventory()
print("Turtle is refueling...") -- Prepare refueling
self:TurnToDirection(self.config.fuel_direction)
-- Clear our inventory into the storage chest turtle.select(1)
self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true)
self:DropInventory() -- Include the distance to the last position when refueling
-- to keep the amount of work done between refuelings constant
-- Prepare refueling local target_fuel_level = self.config.refuel_amount
self:TurnToDirection(self.config.fuel_direction) local last_position = self.last_positions:Peek()
turtle.select(1) if last_position == nil then
error("Failed to obtain last_position while refueling!")
-- Include the distance to the last position when refueling else
-- to keep the amount of work done between refuelings constant target_fuel_level = target_fuel_level + last_position.x + last_position.y + last_position.z
local target_fuel_level = self.config.refuel_amount end
local last_position = self.last_positions:Peek()
if last_position == nil then -- Refuel until we hit the refuel_amount
error("Failed to obtain last_position while refueling!") local before_level = turtle.getFuelLevel()
else repeat
target_fuel_level = target_fuel_level + last_position.x + last_position.y + last_position.z if not self:SuckItem(1, 1) then
end error("Failed to suck fuel out of fuel chest!")
end
-- Refuel until we hit the refuel_amount turtle.refuel()
local before_level = turtle.getFuelLevel() until turtle.getFuelLevel() >= target_fuel_level
repeat local after_level = turtle.getFuelLevel()
if not self:SuckItem(1, 1) then
error("Failed to suck fuel out of fuel chest!") self:MoveBack(true, true)
end
turtle.refuel() print(
until turtle.getFuelLevel() >= target_fuel_level ("Refuelled %d units, current level is %d (old level was %d)"):format(
local after_level = turtle.getFuelLevel() after_level - before_level,
after_level,
self:MoveBack(true, true) before_level
)
print(("Refuelled %d units, current level is %d (old level was %d)"):format(after_level - before_level, after_level, before_level)) )
end end
-----------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------- -- Management Methods
-- Management Methods -----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
function TurtleController:Configure()
local config_complete = false
function TurtleController:Configure()
local config_complete = false while not config_complete do
-- Check for valid fuel
while not config_complete do while self.config.fuel_name == "" do
-- Check for valid fuel print("Please put valid fuel into the first slot and press Enter!")
while self.config.fuel_name == "" do local _ = io.read()
print("Please put valid fuel into the first slot and press Enter!") self.config.fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name
local _ = io.read() end
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("A chest with fuel has to be placed beside the turtle's starting position. Is this chest...") print(("...behind the turtle (enter %d)?"):format(Direction.SOUTH))
print(("...right of the turtle (enter %d)?"):format(Direction.EAST)) print(("...left of the turtle (enter %d)?"):format(Direction.WEST))
print(("...behind the turtle (enter %d)?"):format(Direction.SOUTH)) print("By default, the chest is assumed on the left.")
print(("...left of the turtle (enter %d)?"):format(Direction.WEST)) self.config.fuel_direction = tonumber(io.read()) or 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("A chest to store mined blocks has to be placed beside the turtle's starting position. Is this chest...") print(("...behind the turtle (enter %d)?"):format(Direction.SOUTH))
print(("...right of the turtle (enter %d)?"):format(Direction.EAST)) print(("...left of the turtle (enter %d)?"):format(Direction.WEST))
print(("...behind the turtle (enter %d)?"):format(Direction.SOUTH)) print("By default, the chest is assumed on the right.")
print(("...left of the turtle (enter %d)?"):format(Direction.WEST)) self.config.storage_direction = tonumber(io.read()) or Direction.EAST
print("By default, the chest is assumed on the right.")
self.config.storage_direction = tonumber(io.read()) or Direction.EAST print("Configuration complete!")
print(
print("Configuration complete!") ('Turtle will consume "%s" for fuel. Put the desired fuel into the first slot to change.'):format(
print(("Turtle will consume \"%s\" for fuel. Put the desired fuel into the first slot to change."):format(self.config.fuel_name)) self.config.fuel_name
print("Do you want to accept the configuration (enter 1, otherwise 0)?") )
config_complete = tonumber(io.read()) == 1 )
end print("Do you want to accept the configuration (enter 1, otherwise 0)?")
end config_complete = tonumber(io.read()) == 1
end
end
function TurtleController:EnableMiningForward()
self.mine_forward = true function TurtleController:EnableMiningForward()
end self.mine_forward = true
end
function TurtleController:DisableMiningForward() function TurtleController:DisableMiningForward()
self.mine_forward = false self.mine_forward = false
end end
function TurtleController:EnableMiningAbove()
function TurtleController:EnableMiningAbove() self.mine_above = true
self.mine_above = true end
end
function TurtleController:DisableMiningAbove()
self.mine_above = false
function TurtleController:DisableMiningAbove() end
self.mine_above = false
end function TurtleController:EnableMiningBelow()
self.mine_below = true
end
function TurtleController:EnableMiningBelow()
self.mine_below = true function TurtleController:DisableMiningBelow()
end self.mine_below = false
end
function TurtleController:DisableMiningBelow() return TurtleController
self.mine_below = false
end
return TurtleController

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,32 +1,30 @@
local TestingController = require("controller.testing_controller") local TestingController = require("controller.testing_controller")
local ExcavationController = require("controller.excavation_controller") local ExcavationController = require("controller.excavation_controller")
local AudioController = require("controller.audio_controller") local AudioController = require("controller.audio_controller")
local controllers = {
local controllers = { TestingController:Create(),
TestingController:Create(), ExcavationController:Create(),
ExcavationController:Create(), AudioController:Create(),
AudioController:Create(), }
}
print("Multiple controllers are available:")
print("Multiple controllers are available:") print("1: Testing Mode")
print("1: Testing Mode") print("2: Volume Excavation")
print("2: Volume Excavation") print("3: Play Bangarang")
print("3: Play Bangarang")
local choice = 0
local choice = 0 while choice < 1 or choice > #controllers do
while choice < 1 or choice > #controllers do print("Choose a controller by entering its number:")
print("Choose a controller by entering its number:") choice = tonumber(io.read()) or 0
choice = tonumber(io.read()) or 0 end
end
controllers[choice]:Run()
controllers[choice]:Run()
-- TODO: StackOverflow once the inventory is full, unstocking doesn't work...
-- Is the InventoryFull check wrong?
-- TODO: StackOverflow once the inventory is full, unstocking doesn't work... -- TODO: Test if there's a chest when dropping/sucking/mining (don't mine chests!)
-- Is the InventoryFull check wrong? -- TODO: Add controller to build rooms with walls of a specified material
-- TODO: Test if there's a chest when dropping/sucking/mining (don't mine chests!) -- TODO: Support specifying the mining height
-- TODO: Add controller to build rooms with walls of a specified material -- TODO: Add configurable trash_list with items that won't be picked up
-- TODO: Support specifying the mining height -- TODO: Add a speaker to the turtle (listen to music while mining)
-- 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)