Skip to content

Instantly share code, notes, and snippets.

@dfontana
Last active April 4, 2021 17:54
Show Gist options
  • Save dfontana/d72ae5868a87adeb6345dbe6f041138d to your computer and use it in GitHub Desktop.
Save dfontana/d72ae5868a87adeb6345dbe6f041138d to your computer and use it in GitHub Desktop.
cc-quarry-deploy
-- Quarry
--
-- Utilizes a static grid system of relative coordinates to an origin (0,0,0) and directions.
-- In this case, the cardinal directions map to the positive domain; eg: N is positive z, E is
-- positive X.
--
-- The turtle should stay within the positive x,y,z directions. This is just to simplify the
-- general turtle handling overall, albeit much of the code functions without this assumption
--
-- The turtle assumes the following setup ([] = Block or Air, T = Turtle, C = Chest)
-- the turtle should be placed facing Up (in this diagram; eg away from the chests)
--
-- [][][][][][]
-- [][][][][][]
-- [][][][][][]
-- T [][][][][]
-- C C C C C
--
-- Note: Be sure to label your turtles (`label set foo`) so you don't lose fuel or programs
os.loadAPI("ccqArg")
os.loadAPI("ccqPrim")
os.loadAPI("ccqUtil")
-- Constants used to reset the turtle
local ORIGIN_LOC = {x=0,y=0,z=0}
local ORIGIN_DIR = 'N'
-- Offset coordinates from the turtle's current location. This means
-- the turtle uses relative location. If the program reboots the turtle
-- will be lost
local currentLoc = {x=0,y=0,z=0}
local currentDir = 'N'
-- Stores where the turtle stopped when it goes to return home. Is used to
-- help the turtle bee-line back to where it was efficiently
local leftOffAtLoc = {x=0,y=0,z=0}
local leftOffAtDir = 'N'
local tArgs = {...}
-- ===========================================================================
-- ================= MAIN LOOP =================================
-- ===========================================================================
function mainLoop()
-- Parse args and unpack to globals
local args = ccqArg.parseArgs(tArgs)
if args == false then
return
end
END_LOC = args.loc
-- DEFECTS
-- 1. Turtle digs one too many columns, can we do better?
print("[Main] Going to "..ccqUtil.locString(END_LOC))
while currentLoc.y >= END_LOC.y do
layerRoutine()
local originShaft = {x=ORIGIN_LOC.x,z=ORIGIN_LOC.z,y=currentLoc.y}
goToLoc(originShaft, ORIGIN_DIR)
if digDown(3) == false then
goToLoc(ORIGIN_LOC, ORIGIN_DIR)
print("Something blocked moving down!")
return
end
end
dumpInventory()
end
function layerRoutine()
while currentLoc.x < END_LOC.x do
local isEvn = currentLoc.x % 2 == 0
local rowRotation = isEvn and 'S' or 'N'
while rowCheck(isEvn) do
print("[Main] "..ccqUtil.locString(currentLoc))
if digRoutine() == 1 then
return
end
end
currentDir = ccqPrim.rotate(currentDir, 'E')
if digRoutine() == 1 then
return
end
currentDir = ccqPrim.rotate(currentDir, rowRotation)
end
end
function rowCheck(isEvn)
if isEvn then
return currentLoc.z < END_LOC.z
else
return currentLoc.z > 0
end
end
function digRoutine()
if ccqPrim.hasFuelToMove(ccqUtil.costToDest(currentLoc, ORIGIN_LOC)) == false then
print("[Dig] Cannot move without stranding, returning home")
goToLoc(ORIGIN_LOC, ORIGIN_DIR)
return 1
end
if digForward() == 0 then
print("[Dig] Inventory Full, Need to Empty")
ccqUtil.copyLocToLoc(leftOffAtLoc, currentLoc)
leftOffAtDir = currentDir
dumpInventory()
resume()
end
return 0
end
-- ===========================================================================
-- ================= HIGH LEVEL APIS ===================================
-- ===========================================================================
-- Will return the turtle home, followed by moving to each chest dumping its
-- inventory until its empty or theres no more chest room, which ever comes first.
-- It will then return home again.
--
-- In the event it's inventory isn't empty after this the function will return
-- false, otherwise true
function dumpInventory()
print("[Dump] Emptying Inventory")
goToLoc(ORIGIN_LOC, ORIGIN_DIR)
currentDir = ccqPrim.rotate(currentDir, 'E')
local notChest = false
repeat
print("[Dump] Current "..ccqUtil.locString(currentLoc))
forward()
currentDir = ccqPrim.rotate(currentDir, 'S')
-- are we looking at a chest?
local success, data = turtle.inspect()
if success == false or data.name ~= 'minecraft:chest' then
print("[Dump] No more chests, stopping")
break
end
-- Dump inventory
for i = 1, 16 do
turtle.select(i)
-- Skip fuel items && empty slots
if turtle.getItemCount() ~= 0 and turtle.refuel(0) == false then
if turtle.drop() == false then
-- inventory is full, go to next
break
end
end
end
turtle.select(1)
currentDir = ccqPrim.rotate(currentDir,'E')
until notChest
print("[Dump] Stopped at "..ccqUtil.locString(currentLoc))
goToLoc(ORIGIN_LOC, ORIGIN_DIR)
end
-- Returns the turtle to where it left off, but only if it has at least
-- (2 * Fuel-for-Trip) + 1 (to ensure it can get home).
-- This will return false if it could not do this as a result.
function resume()
if ccqPrim.hasFuelToMove(2 * ccqUtil.costToDest(currentLoc, leftOffAtLoc)) == false then
print("[Resume] Cannot resume, not enough fuel")
return false
end
print("[Resume] Returning to left off loc")
goToLoc(leftOffAtLoc, leftOffAtDir)
end
-- Sends the turtle to the given loc and direction
function goToLoc(loc, direction)
local xDiff = currentLoc.x - loc.x
local xDir = xDiff > 0 and 'W' or 'E'
travel(xDiff, xDir)
local zDiff = currentLoc.z - loc.z
local zDir = zDiff > 0 and 'S' or 'N'
travel(zDiff, zDir)
local yDiff = currentLoc.y - loc.y
local yDir = yDiff > 0 and 'D' or 'U'
travel(yDiff, yDir)
currentDir = ccqPrim.rotate(currentDir, direction)
ccqUtil.copyLocToLoc(currentLoc, loc)
currentDir = direction
end
-- Travel the given number of units in the given direction
-- units maybe positive or negative, it doesn't matter
function travel(units, direction)
if units ~= 0 then
if direction ~= 'U' and direction ~= 'D' then
-- Rotate when on cartesian plane
currentDir = ccqPrim.rotate(currentDir, direction)
end
for i = 1, math.abs(units) do
if direction == 'U' then
if turtle.detectUp() then
turtle.digUp()
end
turtle.up()
elseif direction == 'D' then
if turtle.detectDown() then
turtle.digDown()
end
turtle.down()
else
if turtle.detect() then
turtle.dig()
end
turtle.forward()
end
end
end
end
-- Digs the 3 blocks in front of the turtle and moves forward
-- Returns the number of items it was able to pick up
-- If the turtle returns 0 enough its suggestive there's nothing left for it to fit
function digForward()
if turtle.detectUp() then
local successUp, dataUp = turtle.inspectUp()
if successUp and ccqPrim.canFitItem(dataUp.name) then
turtle.digUp()
else
return 0
end
end
if turtle.detectDown() then
local successDn, dataDn = turtle.inspectDown()
if successDn and ccqPrim.canFitItem(dataDn.name) then
turtle.digDown()
else
return 0
end
end
if turtle.detect() then
local success, data = turtle.inspect()
if success and ccqPrim.canFitItem(data.name) then
turtle.dig()
else
return 0
end
end
forward()
return -1
end
-- Move the turtle down {times}. If there are blocks in the way it will
-- dig itself out. If it can't dig or move through, then return false
function digDown(times)
for i=1,times do
if turtle.detectDown() then
if turtle.digDown() == false then
return false
end
end
if turtle.down() == false then
return false
end
currentLoc.y = currentLoc.y - 1
end
return true
end
-- Move the Bot Forward, updating the locRef with the result
function forward()
if turtle.forward() then
if currentDir == 'N' then
currentLoc.z = currentLoc.z + 1
elseif currentDir == 'S' then
currentLoc.z = currentLoc.z - 1
elseif currentDir == 'E' then
currentLoc.x = currentLoc.x + 1
else
currentLoc.x = currentLoc.x - 1
end
return true
end
return false
end
mainLoop()
function parseArgs(tArgs)
if #tArgs < 2 or #tArgs > 3 then
print("Usage: cc <width> <length> [<depth> = 0]")
return false
end
local endLoc = {x=tonumber(tArgs[1]),y=0,z=tonumber(tArgs[2])}
if tArgs[3] ~= nil then
endLoc.y = 0 - tonumber(tArgs[3])
end
return {loc=endLoc}
end
-- Primitives are actions a turtle might take, that don't alter a global state
-- or reference. They can occur on their own accord and have no side effects
-- on the overall state. They can, however, alter something about the bot:
-- position, rotation, etc.
-- Attempts to consume anything from the inventory to increase fuel level.
-- Since it's not plausible to detect how much something is worth in fuel
-- this method instead naively tries to increase fuel level one by one until
-- the level is unchanged or maxed out, meaning there's no more fuel to be consumed.
--
-- To be conservative this will only run when fuel is at 75%
function refuel()
local currentLevel = turtle.getFuelLevel()
local fuelLimit = turtle.getFuelLimit()
if currentLevel > 1000 then
return
end
print("[Refuel] Fuel Level: "..currentLevel.." / "..fuelLimit)
repeat
local consumedSomething = false
for i = 1, 16 do
turtle.select(i)
if turtle.refuel(0) then
turtle.refuel(1)
consumedSomething = true
break
end
end
turtle.select(1)
if consumedSomething == false then
print("[Refuel] No more fuel to consume")
break
end
newLevel = turtle.getFuelLevel()
print("[Refuel] Fuel Level: " .. newLevel)
until newLevel > fuelLimit * 0.75
end
-- Checks inventory fullness against the given item for space
-- Returning true if the item could be fit
function canFitItem(itemName)
for i = 1, 16 do
turtle.select(i)
local slot = turtle.getItemDetail(i)
if slot == nil or (slot and slot.name == itemName and turtle.getItemSpace(i) ~= 0) then
turtle.select(1)
return true
end
end
turtle.select(1)
print("[Fit] Cannot fit Item")
return false
end
-- Rotate the turtle until it faces the given Dir
-- Must provide the direction it is currently facing
-- Will return the final direction its facing for ease of setting vars.
local LEFT_DIR = {N='W',W='S',S='E',E='N'}
function rotate(facing, direction)
if facing == direction then
return direction
end
turtle.turnLeft()
return rotate(LEFT_DIR[facing], direction)
end
-- Determine if there is enough fuel left to execute an action that consumes one fuel (eg move forward)
-- after arriving at the destionation (the given cost). This will implicitly refuel the turtle as
-- much as it can before computing the boolean
--
-- Consumes: costToDest -- the cost to move to the destination (see costToDest function)
--
-- return true if so, false if not
function hasFuelToMove(costToDest)
refuel()
return turtle.getFuelLevel() - costToDest >= 1
end
-- Utils are functions that aren't specific to quarrying; they just assist
-- the programmer. Think toString, mathematics, or data movement.
-- They do not mutate any global state
-- Computes the cost to travel from current location to the given loc tuple
-- using manhattan distance: |x2-x1| + |y2-y1| + |z2-z1|
function costToDest(locA, locB)
return math.abs(locA.x - locB.x) + math.abs(locA.y - locB.y) + math.abs(locA.z - locB.z)
end
-- Copies loc b into loc a, to prevent reference mutation
-- (assigning a = b would move the reference).
function copyLocToLoc(a, b)
a.x = b.x
a.y = b.y
a.z = b.z
end
-- Convert a Loc tuple into a String for easy printing
function locString(loc)
return "Loc{"..loc.x..","..loc.z..","..loc.y.."}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment