1

Compare commits

...

5 Commits

13 changed files with 1348 additions and 1071 deletions

9
.luarc.json Normal file
View File

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

View File

@ -2,6 +2,8 @@
"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

@ -1,27 +1,27 @@
local Direction = require("lib.direction") local Direction = require("lib.direction")
local TurtleController = require("controller.turtle_controller") 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} ---@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 ---@class ExcavationController
---@field controller TurtleController ---@field controller TurtleController
---@field audio AudioController
---@field config ExcavationControllerConfig ---@field config ExcavationControllerConfig
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 -- Fields
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
t.controller = TurtleController:Create() t.controller = TurtleController:Create()
t.audio = AudioController:Create()
t.config = { t.config = {
slice_length = 0, slice_length = 0,
slice_height = 0, slice_height = 0,
@ -32,16 +32,13 @@ function ExcavationController:Create()
slices_right = 0, slices_right = 0,
} }
return t 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.
@ -63,7 +60,6 @@ function ExcavationController:Excavate_1x1or2or3xL(mine_above, mine_below)
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.
@ -120,7 +116,6 @@ function ExcavationController:Excavate_1xHxL()
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.
@ -150,7 +145,6 @@ function ExcavationController:Excavate_WxHxL(center_slice)
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()
@ -176,6 +170,7 @@ function ExcavationController:Excavate()
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
-- 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()
@ -206,7 +201,9 @@ function ExcavationController:Excavate()
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
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()
@ -245,19 +242,19 @@ function ExcavationController:Excavate()
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
-- 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:DropInventoryIntoChest()
self.controller:TurnToDirection(Direction.NORTH) self.controller:TurnToDirection(Direction.NORTH)
self.audio:StopPlaying()
end end
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
-- Management Methods -- Management Methods
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
function ExcavationController:Configure() function ExcavationController:Configure()
local config_complete = false local config_complete = false
@ -291,23 +288,31 @@ function ExcavationController:Configure()
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(
("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)?") print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1 config_complete = tonumber(io.read()) == 1
end end
end end
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
-- Main Method -- Main Method
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
function ExcavationController:Run() function ExcavationController:Run()
self.controller:Configure() self.controller:Configure()
self:Configure() self:Configure()
@ -317,8 +322,9 @@ function ExcavationController:Run()
turtle.refuel() turtle.refuel()
self.controller:RefuelIfEmpty() self.controller:RefuelIfEmpty()
parallel.waitForAll(function()
self:Excavate() self:Excavate()
end, self.audio:PlayAudioFactory("bangarang"))
end end
return ExcavationController return ExcavationController

View File

@ -1,211 +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: 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 TestingController: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 TestingController: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 TestingController: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 TestingController: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 TestingController: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 TestingController:Run()
self.controller:Configure()
self.controller:DisableMiningForward()
self.controller:DisableMiningAbove()
self.controller:DisableMiningBelow()
print("There are multiple tests available:")
print("1: Relative movement with relative rotation")
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

@ -14,18 +14,15 @@ local Stack = require("lib.stack")
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 = { t.config = {
fuel_direction = Direction.EAST, fuel_direction = Direction.EAST,
fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name, fuel_name = turtle.getItemDetail(1) == nil and "" or turtle.getItemDetail(1).name,
@ -39,16 +36,13 @@ function TurtleController:Create()
t.mine_above = false t.mine_above = false
t.mine_below = false t.mine_below = false
return t return t
end end
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
-- Movement Methods -- Movement Methods
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
---Positive numbers turn clockwise, negative numbers turn counterclockwise ---Positive numbers turn clockwise, negative numbers turn counterclockwise
---@param number_of_turns number ---@param number_of_turns number
function TurtleController:TurnRelative(number_of_turns) function TurtleController:TurnRelative(number_of_turns)
@ -75,7 +69,6 @@ function TurtleController:TurnRelative(number_of_turns)
self.position.dir = (self.position.dir + number_of_turns) % 4 self.position.dir = (self.position.dir + number_of_turns) % 4
end end
---@param direction Direction ---@param direction Direction
function TurtleController:TurnToDirection(direction) function TurtleController:TurnToDirection(direction)
if self.position.dir == direction then if self.position.dir == direction then
@ -85,7 +78,6 @@ function TurtleController:TurnToDirection(direction)
self:TurnRelative(direction - self.position.dir) self:TurnRelative(direction - self.position.dir)
end end
---Move forward by a number of blocks depending on the current rotation ---Move forward by a number of blocks depending on the current rotation
---@param number_of_blocks number ---@param number_of_blocks number
---@param skip_unstocking boolean | nil ---@param skip_unstocking boolean | nil
@ -110,15 +102,24 @@ function TurtleController:MoveForward(number_of_blocks, skip_unstocking, skip_re
-- Mine/Move -- Mine/Move
if self.mine_forward then if self.mine_forward then
while not turtle.forward() do while not turtle.forward() do
if self:TestForBlock("minecraft:chest") then
error("Won't mine because a chest is in the way!")
end
turtle.dig() turtle.dig()
end end
elseif not turtle.forward() then elseif not turtle.forward() then
error("Turtle failed to move forward!") error("Turtle failed to move forward!")
end end
if self.mine_above then if self.mine_above then
if self:TestForBlock("minecraft:chest", true, false) then
error("Won't mine because a chest is in the way!")
end
turtle.digUp() turtle.digUp()
end end
if self.mine_below then if self.mine_below then
if self:TestForBlock("minecraft:chest", false, true) then
error("Won't mine because a chest is in the way!")
end
turtle.digDown() turtle.digDown()
end end
@ -160,13 +161,15 @@ function TurtleController:MoveVertical(number_of_blocks, skip_unstocking, skip_r
self:UnstockIfFull() self:UnstockIfFull()
end end
-- Mine -- Mine/Move
if mine_enabled then if mine_enabled then
while not move_function() do
if self:TestForBlock("minecraft:chest", number_of_blocks > 0, number_of_blocks < 0) then
error("Won't mine because a chest is in the way!")
end
mine_function() mine_function()
end end
elseif not move_function() then
-- Move
if not move_function() then
error("Turtle failed to move vertically!") error("Turtle failed to move vertically!")
end end
@ -179,13 +182,11 @@ function TurtleController:MoveVertical(number_of_blocks, skip_unstocking, skip_r
end end
end end
---Stores the current position on the stack so we can return using TurtleController:MoveBack() ---Stores the current position on the stack so we can return using TurtleController:MoveBack()
function TurtleController:StorePosition() function TurtleController:StorePosition()
self.last_positions:Push(Position:Copy(self.position)) 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() ---Move to an absolute position. Stores the current position on the stack so we can return using TurtleController:MoveBack()
---@param x number The EAST/WEST axis (grows from WEST to EAST) ---@param x number The EAST/WEST axis (grows from WEST to EAST)
---@param y number The UP/DOWN axis (grows from DOWN to UP) ---@param y number The UP/DOWN axis (grows from DOWN to UP)
@ -199,7 +200,15 @@ function TurtleController:MoveToPosition(x, y, z, dir, skip_unstocking, skip_ref
-- Store 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()
self.last_positions:Push(Position:Copy(self.position)) self.last_positions:Push(Position:Copy(self.position))
-- Store mining config so we can restore it later
local mine_forward = self.mine_forward
local mine_above = self.mine_above
local mine_below = self.mine_below
-- EAST/WEST axis (do first to not interfere with chests) -- EAST/WEST axis (do first to not interfere with chests)
self:EnableMiningForward()
self:DisableMiningAbove()
self:DisableMiningBelow()
if self.position.x > x then if self.position.x > x then
self:TurnToDirection(Direction.WEST) self:TurnToDirection(Direction.WEST)
elseif self.position.x < x then elseif self.position.x < x then
@ -216,18 +225,40 @@ function TurtleController:MoveToPosition(x, y, z, dir, skip_unstocking, skip_ref
self:MoveForward(math.abs(z - self.position.z), skip_unstocking, skip_refueling) self:MoveForward(math.abs(z - self.position.z), skip_unstocking, skip_refueling)
-- UP/DOWN axis -- UP/DOWN axis
self:DisableMiningForward()
if y < self.position.y then
self:EnableMiningBelow()
elseif y > self.position.y then
self:EnableMiningAbove()
end
self:MoveVertical(y - self.position.y, skip_unstocking, skip_refueling) self:MoveVertical(y - self.position.y, skip_unstocking, skip_refueling)
-- Direction -- Direction
self:TurnToDirection(dir or Direction.NORTH) self:TurnToDirection(dir or Direction.NORTH)
-- Restore mining config
if mine_forward then
self:EnableMiningForward()
else
self:DisableMiningForward()
end
if mine_above then
self:EnableMiningAbove()
else
self:DisableMiningAbove()
end
if mine_below then
self:EnableMiningBelow()
else
self:DisableMiningBelow()
end
-- Sanity check -- Sanity check
if not (self.position.x == x and self.position.y == y and self.position.z == z and self.position.dir == dir) then if not (self.position.x == x and self.position.y == y and self.position.z == z and self.position.dir == dir) then
error("TurtleController:MoveToPosition failed to move to target position!") error("TurtleController:MoveToPosition failed to move to target position!")
end end
end end
---Move by a vector. Stores the current position on the stack so we can return using TurtleController:MoveBack() ---Move by a vector. Stores the current position on the stack so we can return using TurtleController:MoveBack()
---@param x number The EAST/WEST axis (grows from WEST to EAST) ---@param x number The EAST/WEST axis (grows from WEST to EAST)
---@param y number The UP/DOWN axis (grows from DOWN to UP) ---@param y number The UP/DOWN axis (grows from DOWN to UP)
@ -236,10 +267,16 @@ end
---@param skip_unstocking boolean | nil ---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil ---@param skip_refueling boolean | nil
function TurtleController:MoveByVector(x, y, z, dir, skip_unstocking, skip_refueling) function TurtleController:MoveByVector(x, y, z, dir, skip_unstocking, skip_refueling)
self:MoveToPosition(self.position.x + x, self.position.y + y, self.position.z + z, dir, skip_unstocking, skip_refueling) self:MoveToPosition(
self.position.x + x,
self.position.y + y,
self.position.z + z,
dir,
skip_unstocking,
skip_refueling
)
end end
---Move to the previously stored position and pop this position from the stack. ---Move to the previously stored position and pop this position from the stack.
---@param skip_unstocking boolean | nil ---@param skip_unstocking boolean | nil
---@param skip_refueling boolean | nil ---@param skip_refueling boolean | nil
@ -249,19 +286,55 @@ function TurtleController:MoveBack(skip_unstocking, skip_refueling)
if last_position == nil then if last_position == nil then
error("Failed to obtain last_position to move back!") error("Failed to obtain last_position to move back!")
else else
self:MoveToPosition(last_position.x, last_position.y, last_position.z, last_position.dir, skip_unstocking, skip_refueling) self:MoveToPosition(
last_position.x,
last_position.y,
last_position.z,
last_position.dir,
skip_unstocking,
skip_refueling
)
-- Pop the stack because MoveToPosition pushes our current position -- Pop the stack because MoveToPosition pushes our current position
self.last_positions:Pop() self.last_positions:Pop()
end end
end end
-----------------------------------------------------------------------------------------------
-- Testing Methods
-----------------------------------------------------------------------------------------------
---@param block_name string
---@param above boolean | nil
---@param below boolean | nil
---@return boolean
function TurtleController:TestForBlock(block_name, above, below)
above = above or false
below = below or false
if above and below then
error("Can only test for blocks in one direction!")
end
local inspect_function = turtle.inspect
if above then
inspect_function = turtle.inspectUp
elseif below then
inspect_function = turtle.inspectDown
end
local has_block, block_data = inspect_function()
if not has_block then
return false
end
return block_data.name == block_name
end
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
-- Inventory Methods -- Inventory Methods
----------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------
---@return boolean ---@return boolean
function TurtleController:HasFuel() function TurtleController:HasFuel()
local level = turtle.getFuelLevel() local level = turtle.getFuelLevel()
@ -270,7 +343,6 @@ function TurtleController:HasFuel()
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
@ -282,11 +354,14 @@ function TurtleController:HasInventorySpace()
return false return false
end end
---@param slot number ---@param slot number
---@param count number ---@param count number
---@return boolean ---@return boolean
function TurtleController:SuckItem(slot, count) function TurtleController:SuckItemFromChest(slot, count)
if not self:TestForBlock("minecraft:chest") then
error("Can't suck item: no chest!")
end
local previous_slot = turtle.getSelectedSlot() local previous_slot = turtle.getSelectedSlot()
turtle.select(slot or 1) turtle.select(slot or 1)
@ -297,11 +372,14 @@ function TurtleController:SuckItem(slot, count)
return sucked return sucked
end end
---@param slot number ---@param slot number
---@param count number ---@param count number
---@return boolean ---@return boolean
function TurtleController:DropItem(slot, count) 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() local previous_slot = turtle.getSelectedSlot()
turtle.select(slot or 1) turtle.select(slot or 1)
@ -312,8 +390,11 @@ function TurtleController:DropItem(slot, count)
return dropped return dropped
end end
function TurtleController:DropInventoryIntoChest()
if not self:TestForBlock("minecraft:chest") then
error("Can't drop item: no chest!")
end
function TurtleController:DropInventory()
print("Dropping inventory into chest...") print("Dropping inventory into chest...")
for slot = 1, 16 do for slot = 1, 16 do
@ -324,7 +405,6 @@ function TurtleController:DropInventory()
turtle.select(1) turtle.select(1)
end end
---@param skip_inventory_check boolean | nil ---@param skip_inventory_check boolean | nil
function TurtleController:UnstockIfFull(skip_inventory_check) function TurtleController:UnstockIfFull(skip_inventory_check)
skip_inventory_check = skip_inventory_check or false skip_inventory_check = skip_inventory_check or false
@ -338,13 +418,14 @@ function TurtleController:UnstockIfFull(skip_inventory_check)
print("Turtle is unstocking...") print("Turtle is unstocking...")
self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true) self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true)
self:DropInventory() self:DropInventoryIntoChest()
self:RefuelIfEmpty() self:RefuelIfEmpty()
self:TurnToDirection(Direction.NORTH)
self:MoveForward(1)
self:MoveBack(true, true) self:MoveBack(true, true)
end end
---@param skip_fuel_check boolean | nil ---@param skip_fuel_check boolean | nil
function TurtleController:RefuelIfEmpty(skip_fuel_check) function TurtleController:RefuelIfEmpty(skip_fuel_check)
skip_fuel_check = skip_fuel_check or false skip_fuel_check = skip_fuel_check or false
@ -359,7 +440,7 @@ function TurtleController:RefuelIfEmpty(skip_fuel_check)
-- Clear our inventory into the storage chest -- Clear our inventory into the storage chest
self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true) self:MoveToPosition(0, 0, 0, self.config.storage_direction, true, true)
self:DropInventory() self:DropInventoryIntoChest()
-- Prepare refueling -- Prepare refueling
self:TurnToDirection(self.config.fuel_direction) self:TurnToDirection(self.config.fuel_direction)
@ -378,24 +459,30 @@ function TurtleController:RefuelIfEmpty(skip_fuel_check)
-- Refuel until we hit the refuel_amount -- Refuel until we hit the refuel_amount
local before_level = turtle.getFuelLevel() local before_level = turtle.getFuelLevel()
repeat repeat
if not self:SuckItem(1, 1) then if not self:SuckItemFromChest(1, 1) then
error("Failed to suck fuel out of fuel chest!") error("Failed to suck fuel out of fuel chest!")
end end
turtle.refuel() turtle.refuel()
until turtle.getFuelLevel() >= target_fuel_level until turtle.getFuelLevel() >= target_fuel_level
local after_level = turtle.getFuelLevel() local after_level = turtle.getFuelLevel()
self:TurnToDirection(Direction.NORTH)
self:MoveForward(1)
self:MoveBack(true, true) 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)) 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() function TurtleController:Configure()
local config_complete = false local config_complete = false
@ -422,41 +509,38 @@ function TurtleController:Configure()
self.config.storage_direction = tonumber(io.read()) or Direction.EAST self.config.storage_direction = tonumber(io.read()) or Direction.EAST
print("Configuration complete!") 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(
('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)?") print("Do you want to accept the configuration (enter 1, otherwise 0)?")
config_complete = tonumber(io.read()) == 1 config_complete = tonumber(io.read()) == 1
end end
end end
function TurtleController:EnableMiningForward() function TurtleController:EnableMiningForward()
self.mine_forward = true self.mine_forward = true
end 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() function TurtleController:DisableMiningAbove()
self.mine_above = false self.mine_above = false
end end
function TurtleController:EnableMiningBelow() function TurtleController:EnableMiningBelow()
self.mine_below = true self.mine_below = true
end end
function TurtleController:DisableMiningBelow() function TurtleController:DisableMiningBelow()
self.mine_below = false self.mine_below = false
end end
return TurtleController 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

@ -3,7 +3,7 @@ local Direction = {
NORTH = 0, NORTH = 0,
EAST = 1, EAST = 1,
SOUTH = 2, SOUTH = 2,
WEST = 3 WEST = 3,
} }
return Direction return Direction

View File

@ -8,7 +8,6 @@ local Direction = require("lib.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 = {}

View File

@ -1,11 +1,8 @@
local Position = require("lib.position")
---@class Stack ---@class Stack
---@field elements Position[] ---@field elements Position[]
local Stack = {} local Stack = {}
Stack.__index = Stack Stack.__index = Stack
---@return Stack ---@return Stack
function Stack:Create() function Stack:Create()
-- stack table -- stack table
@ -18,7 +15,6 @@ function Stack:Create()
return t return t
end end
---@param element Position ---@param element Position
function Stack:Push(element) function Stack:Push(element)
if element == nil or element == {} then if element == nil or element == {} then
@ -29,7 +25,6 @@ function Stack:Push(element)
table.insert(self.elements, element) table.insert(self.elements, element)
end end
---@return Position | nil ---@return Position | nil
function Stack:Pop() function Stack:Pop()
if self:Count() == 0 then if self:Count() == 0 then
@ -40,7 +35,6 @@ function Stack:Pop()
return table.remove(self.elements) return table.remove(self.elements)
end end
---@return Position | nil ---@return Position | nil
function Stack:Peek() function Stack:Peek()
if self:Count() == 0 then if self:Count() == 0 then
@ -51,11 +45,9 @@ function Stack:Peek()
return self.elements[#self.elements] return self.elements[#self.elements]
end end
---@return number ---@return number
function Stack:Count() function Stack:Count()
return #self.elements return #self.elements
end end
return Stack return Stack

View File

@ -1,15 +1,20 @@
local TestingController = require("controller.testing_controller") local TurtleTestingController = require("controller.turtle_testing_controller")
local AudioTestingController = require("controller.audio_testing_controller")
local ExcavationController = require("controller.excavation_controller") local ExcavationController = require("controller.excavation_controller")
local AudioController = require("controller.audio_controller")
local controllers = { local controllers = {
TestingController:Create(), TurtleTestingController:Create(),
AudioTestingController:Create(),
ExcavationController:Create(), ExcavationController:Create(),
AudioController:Create(),
} }
print("Multiple controllers are available:") print("Multiple controllers are available:")
print("1: Testing Mode") print("1: Turtle Testing Mode")
print("2: Volume Excavation") print("2: Audio Testing Mode")
print("3: Volume Excavation")
print("4: Play Bangarang")
local choice = 0 local choice = 0
while choice < 1 or choice > #controllers do while choice < 1 or choice > #controllers do
@ -19,11 +24,13 @@ end
controllers[choice]:Run() 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: StackOverflow once the inventory is full, unstocking doesn't work... -- TODO: Add shortcuts for the volume excavation:
-- Is the InventoryFull check wrong? -- - Tunnel forward 3x2 or 3x3 with specified length
-- TODO: Test if there's a chest when dropping/sucking/mining (don't mine chests!) -- - Excavate downwards (quarry) in a square, specify side length
-- TODO: Add controller to build rooms with walls of a specified material -- TODO: Add controller to build rooms with walls of a specified material
-- TODO: Support specifying the mining height -- TODO: Add a stair building mode (specify side length, min 2x2)
-- TODO: Add configurable trash_list with items that won't be picked up -- 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)