Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[==[
- Turtle Miner
- By Giantpizzahead
- Usage:
- - Place an inventory directly behind the turtle, possibly hoppered to more inventories.
- - Refuel the turtle to 1000 fuel (or more).
- - Place 1 coal in the top left slot.
- - Download the program: "pastebin get jnLTS0Cj miner".
- - Run the program: "miner".
- Todo: Improve fuel efficiency (randomize order of neighbor selection to some extent)
- ]==]
- -- Print miner information and manual checks
- print("\n-----------------------------")
- print("Turtle Miner - By Giantpizzahead")
- print("-----------------------------")
- print("This turtle will clear out a rectangular prism to its front and right.")
- print("The bottom-left corner of the prism will be at the turtle's location.")
- print("All mined items will be placed into an inventory behind the turtle.")
- print("Some of the mined coal will be used for refueling.")
- write("Press enter to continue... ")
- read()
- -- Load blacklist file
- print("\n-----------------------------")
- local file = fs.open("blacklist.txt", "r")
- if file == nil then
- print("Downloading default blacklist.txt from pastebin...")
- shell.run("pastebin get Nzy3hK70 blacklist.txt")
- file = fs.open("blacklist.txt", "r")
- print("\n-----------------------------")
- end
- local blacklist = {}
- local line = file.readLine()
- while line ~= nil do
- local blockName, action = line:match("^(%S+)%s+(%S+)$")
- if blockName ~= nil and action ~= nil then
- if action ~= "AVOID" and action ~= "DROP" then
- error("ERROR: Invalid action '" .. action .. "' for block '" .. blockName .. "'")
- end
- blacklist[blockName] = action
- end
- line = file.readLine()
- end
- file.close()
- -- Print the blacklist
- print("The blacklist.txt file is used to avoid or throw away blocks.")
- print("Current blacklist:")
- lines = 0
- for blockName, action in pairs(blacklist) do
- print(blockName .. " - " .. action)
- lines = lines + 1
- if lines == 5 then
- print("... (truncated)")
- break
- end
- end
- if lines == 0 then
- print("(Empty)")
- end
- write("\nPress enter to continue... ")
- read()
- -- Manual setup instructions
- print("\n-----------------------------")
- print("Before continuing, ensure that:")
- print("- The turtle is above bedrock level.")
- print("- An inventory is directly behind the turtle.")
- print("- There is at least 1 coal in the top left slot.")
- print("- The turtle can refuel to 1000 fuel (currently " .. turtle.getFuelLevel() .. ")")
- write("Press enter to continue... ")
- read()
- -- Fuel checks
- REFUEL_AMOUNT = 80
- local tempData = turtle.getItemDetail(1)
- if tempData == nil or tempData.name ~= "minecraft:coal" then
- error("ERROR: No coal in the top left slot!")
- elseif turtle.getFuelLevel() + REFUEL_AMOUNT * (turtle.getItemCount(1)-1) < 1000 then
- error("ERROR: Not enough starting fuel! Add more coal to the top left slot.")
- end
- -- Get side length and height
- print("\n-----------------------------")
- write("Side length of the area to quarry (default 16): ")
- local S = read()
- if S == "" then
- S = 16
- else
- S = tonumber(S)
- end
- write("Height of the area to quarry (default 16): ")
- local H = read()
- if H == "" then
- H = 16
- else
- H = tonumber(H)
- end
- print("\n-----------------------------")
- local chestsRecommended = 0
- local droppingCobble = blacklist["minecraft:cobblestone"] == "DROP"
- if droppingCobble then
- chestsRecommended = math.ceil(S*S*H / 32 / 54 / 4 + 0.5)
- else
- chestsRecommended = math.ceil(S*S*H / 32 / 54 + 0.5)
- end
- print("Quarry area: " .. S .. "x" .. S .. "x" .. H .. " (" .. S*S*H .. " blocks)")
- print("# of large chests recommended: " .. chestsRecommended)
- if droppingStone then
- print("(Assuming common blocks like cobblestone are dropped)")
- end
- print("Place these behind the turtle - the side with the chest icon.")
- write("Begin? (y/n) ")
- local input = read()
- if input ~= "y" then
- print("Exiting...")
- return
- end
- -- Initial setup
- local currPos = {1, 1, 1}
- local currFace = 0
- -- Refuels the turtle if needed
- function refuelIfNeeded()
- if turtle.getFuelLevel() < turtle.getFuelLimit() - REFUEL_AMOUNT then
- --- Refuel
- if turtle.getItemCount(1) > 1 then
- turtle.select(1)
- turtle.refuel(1)
- print("Refueled (fuel left = " .. turtle.getFuelLevel() .. ")")
- elseif turtle.getFuelLevel() == 0 then
- error("ERROR: Out of fuel!")
- elseif turtle.getFuelLevel() % 100 == 0 then
- print("Cannot refuel (fuel left = " .. turtle.getFuelLevel() .. ")")
- end
- end
- end
- -- Dictionary mapping direction names to changes in coordinates
- local dirToCoords = {
- ["forward"] = {0, 0, 1},
- ["right"] = {1, 0, 0},
- ["back"] = {0, 0, -1},
- ["left"] = {-1, 0, 0},
- ["up"] = {0, 1, 0},
- ["down"] = {0, -1, 0},
- }
- -- Dictionaries mapping direction names to face IDs
- local dirToFace = {
- ["forward"] = 0,
- ["right"] = 1,
- ["back"] = 2,
- ["left"] = 3,
- }
- local faceToDir = {
- [0] = "forward",
- [1] = "right",
- [2] = "back",
- [3] = "left",
- }
- -- Turns toward a direction
- function faceTowards(dir)
- local face = dirToFace[dir]
- local diff = (face - currFace) % 4
- if diff == 1 then
- turtle.turnRight()
- elseif diff == 2 then
- turtle.turnRight()
- turtle.turnRight()
- elseif diff == 3 then
- turtle.turnLeft()
- end
- currFace = face
- end
- -- Moves toward a direction
- function move(dir)
- local moved = false
- if dir == "up" then
- moved = turtle.up()
- elseif dir == "down" then
- moved = turtle.down()
- else
- faceTowards(dir)
- moved = turtle.forward()
- end
- if moved then
- -- Update currPos
- local dx, dy, dz = unpack(dirToCoords[dir])
- currPos[1] = currPos[1] + dx
- currPos[2] = currPos[2] + dy
- currPos[3] = currPos[3] + dz
- end
- return moved
- end
- -- Gets the direction from position 1 to position 2
- function getDirFromPos(pos1, pos2)
- local dx, dy, dz = pos2[1] - pos1[1], pos2[2] - pos1[2], pos2[3] - pos1[3]
- for dir, coords in pairs(dirToCoords) do
- if coords[1] == dx and coords[2] == dy and coords[3] == dz then
- return dir
- end
- end
- error("ERROR: Invalid positions", pos1[1], pos1[2], pos1[3], "and", pos2[1], pos2[2], pos2[3])
- return nil
- end
- -- Attempts to inspect a direction; returns nil or block name
- function inspect(dir)
- local success, data
- if dir == "up" then
- success, data = turtle.inspectUp()
- elseif dir == "down" then
- success, data = turtle.inspectDown()
- else
- faceTowards(dir)
- success, data = turtle.inspect()
- end
- return success and data.name or nil
- end
- -- Attempts to dig toward a direction; returns true if successful, false otherwise
- function dig(dir)
- local dug = false
- if dir == "up" then
- dug = turtle.digUp()
- elseif dir == "down" then
- dug = turtle.digDown()
- else
- faceTowards(dir)
- dug = turtle.dig()
- end
- return dug
- end
- -- Creates a new 3D array for the target volume (N = Not mined, S = Skipped, M = Mined)
- function create3DArray()
- local arr = {}
- for x = 1, S do
- arr[x] = {}
- for y = 1, H do
- arr[x][y] = {}
- for z = 1, S do
- arr[x][y][z] = "N"
- end
- end
- end
- return arr
- end
- -- Gets a path to the closest unmined square. Returns nil if no path exists.
- -- If a target position is specified, the path will be to that position (or nil).
- -- Source: OpenAI :D
- function findPath(arr, targetPos, allowUnmined)
- if allowUnmined == nil then
- allowUnmined = true
- end
- -- Create a queue for BFS and mark the start node as visited
- xi, yi, zi = currPos[1], currPos[2], currPos[3]
- local queue = {}
- local visited = {}
- visited[xi] = {}
- visited[xi][yi] = {}
- visited[xi][yi][zi] = true
- queue[#queue+1] = {xi, yi, zi, {}}
- -- Perform BFS until the target node is found
- while #queue > 0 do
- local node = table.remove(queue, 1)
- local x, y, z, path = node[1], node[2], node[3], node[4]
- -- Check if a minable square has been found
- if (targetPos == nil and arr[x][y][z] == "N") or
- (targetPos ~= nil and x == targetPos[1] and y == targetPos[2] and z == targetPos[3]) then
- -- print("Found path")
- return path
- end
- -- Add unvisited neighboring nodes to the queue
- orderedDirs = {"forward", "back", "left", "right", "up", "down"}
- for dir = 1, 6 do
- local neighbor = dirToCoords[orderedDirs[dir]]
- local dx, dy, dz = neighbor[1], neighbor[2], neighbor[3]
- local nx, ny, nz = x + dx, y + dy, z + dz
- -- print("Checking neighbor", nx, ny, nz)
- -- Check if the neighbor is within bounds (or 1 outside of the grid)
- if nx >= 1 and nx <= S and ny >= 1 and ny <= H and nz >= 1 and nz <= S then
- -- Check if the neighbor has not been visited and is valid
- if arr[nx][ny][nz] ~= "S" and
- (allowUnmined or arr[nx][ny][nz] ~= "N") and
- (not visited[nx] or not visited[nx][ny] or not visited[nx][ny][nz]) then
- -- Mark the neighbor as visited and add it to the queue
- visited[nx] = visited[nx] or {}
- visited[nx][ny] = visited[nx][ny] or {}
- visited[nx][ny][nz] = true
- local npath = {}
- for i, p in ipairs(path) do
- npath[i] = p
- end
- npath[#npath+1] = {nx, ny, nz}
- queue[#queue+1] = {nx, ny, nz, npath}
- end
- end
- end
- end
- -- If a minable node cannot be reached, return nil
- return nil
- end
- -- Follows the given path
- function followPath(arr, path, ignoreLast)
- if ignoreLast == nil then
- ignoreLast = false
- end
- for i = 1, #path do
- if ignoreLast and i == #path then
- break
- end
- local nextPos = path[i]
- local dir = getDirFromPos(currPos, nextPos)
- local moved = move(dir)
- if not moved then
- if arr[nextPos[1]][nextPos[2]][nextPos[3]] == "N" then
- blocksToMine = blocksToMine - 1
- end
- arr[nextPos[1]][nextPos[2]][nextPos[3]] = "S"
- return false
- end
- end
- return true
- end
- -- Moves to the given position (or closest unmined position), returns the path (or nil if no path exists)
- function forceMoveTo(arr, targetPos, ignoreLast, attempts)
- if attempts == nil then
- attempts = 0
- end
- if attempts > 100 then
- return nil
- end
- local path = findPath(arr, targetPos, ignoreLast)
- -- printPath(path)
- -- printLayer(arr, currPos[2])
- if path == nil then
- return nil
- end
- local moved = followPath(arr, path, ignoreLast)
- if not moved then
- -- Try again
- return forceMoveTo(arr, targetPos, ignoreLast, attempts+1)
- end
- -- write("Position: ")
- -- printPos(currPos)
- -- print(ignoreLast, attempts)
- return path
- end
- -- Prints a position (for debugging)
- function printPos(pos)
- print("(" .. pos[1] .. ", " .. pos[2] .. ", " .. pos[3] .. ")")
- end
- -- Prints a list of locations (for debugging)
- function printPath(path)
- if path == nil then
- print("Path: nil")
- return
- end
- print("Path: ")
- for _, p in ipairs(path) do
- printPos(p)
- end
- end
- -- Prints the current layer (for debugging)
- function printLayer(arr, y)
- print("Layer " .. y .. ": ")
- for rz = 1, S do
- local row = ""
- for x = 1, S do
- local z = S+1-rz
- local char = arr[x][y][z]
- if char == "N" then
- char = "X"
- elseif char == "S" then
- char = "*"
- else -- M
- char = " "
- end
- if currPos[1] == x and currPos[2] == y and currPos[3] == z then
- char = "O"
- end
- row = row .. char
- end
- print(" " .. row)
- end
- end
- -- Gets the number of unfilled slots in the turtle's inventory
- function getNumFreeSlots()
- local numFreeSlots = 0
- for i = 2, 16 do
- if turtle.getItemCount(i) == 0 then
- numFreeSlots = numFreeSlots + 1
- end
- end
- return numFreeSlots
- end
- -- Deposits mined items into the chest
- function depositItems(arr)
- print("Depositing items...")
- -- Move to the chest
- local path = forceMoveTo(arr, {1, 1, 1}, false)
- if path == nil then
- error("ERROR: Could not find path to chest!")
- end
- faceTowards("back")
- for i = 2, 16 do
- turtle.select(i)
- -- Only deposit items that are not blacklisted
- if blacklist[turtle.getItemDetail().name] == "DROP" then
- turtle.dropDown()
- else
- turtle.drop()
- end
- end
- end
- -- Get progress
- function getProgress(toMine)
- return math.floor((S*S*H - toMine) / (S*S*H)*100)
- end
- -- Main loop
- local arr = create3DArray()
- arr[1][1][1] = "M"
- blocksToMine = S*S*H
- -- While there is still a reachable block to mine
- while true do
- refuelIfNeeded()
- -- Move to the closest unmined square (if there is one)
- path = forceMoveTo(arr, nil, true)
- if path == nil then
- break
- end
- -- Check if the square to mine is mineable
- local mineX, mineY, mineZ = path[#path][1], path[#path][2], path[#path][3]
- local mineDir = getDirFromPos(currPos, path[#path])
- local blockName = inspect(mineDir)
- if blacklist[blockName] == "AVOID" then
- -- Mark the square as skipped
- arr[mineX][mineY][mineZ] = "S"
- else
- -- Mine the square and mark it as mined
- local result = dig(mineDir) -- If no block is here, this will work anyways
- if not result and blockName ~= nil then
- arr[mineX][mineY][mineZ] = "S"
- else
- arr[mineX][mineY][mineZ] = "M"
- end
- end
- blocksToMine = blocksToMine - 1
- -- Deposit items if necessary
- if getNumFreeSlots() == 0 then
- depositItems(arr)
- end
- -- Print progress
- if getProgress(blocksToMine) ~= getProgress(blocksToMine-1) then
- print("Progress: " .. (S*S*H - blocksToMine) .. "/" .. (S*S*H) .. " (" .. getProgress(blocksToMine) .. "%)")
- end
- end
- -- Finish up
- depositItems(arr)
- faceTowards("forward")
- print("Done mining! :)")
Add Comment
Please, Sign In to add comment