Giantpizzahead1

Computercraft Auto Turtle Miner

Mar 14th, 2023 (edited)
41
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.67 KB | None | 0 0
  1. --[==[
  2. Turtle Miner
  3. By Giantpizzahead
  4.  
  5. Usage:
  6. - Place an inventory directly behind the turtle, possibly hoppered to more inventories.
  7. - Refuel the turtle to 1000 fuel (or more).
  8. - Place 1 coal in the top left slot.
  9. - Download the program: "pastebin get jnLTS0Cj miner".
  10. - Run the program: "miner".
  11.  
  12. Todo: Improve fuel efficiency (randomize order of neighbor selection to some extent)
  13. ]==]
  14.  
  15. -- Print miner information and manual checks
  16. print("\n-----------------------------")
  17. print("Turtle Miner - By Giantpizzahead")
  18. print("-----------------------------")
  19. print("This turtle will clear out a rectangular prism to its front and right.")
  20. print("The bottom-left corner of the prism will be at the turtle's location.")
  21. print("All mined items will be placed into an inventory behind the turtle.")
  22. print("Some of the mined coal will be used for refueling.")
  23. write("Press enter to continue... ")
  24. read()
  25.  
  26. -- Load blacklist file
  27. print("\n-----------------------------")
  28. local file = fs.open("blacklist.txt", "r")
  29. if file == nil then
  30. print("Downloading default blacklist.txt from pastebin...")
  31. shell.run("pastebin get Nzy3hK70 blacklist.txt")
  32. file = fs.open("blacklist.txt", "r")
  33. print("\n-----------------------------")
  34. end
  35. local blacklist = {}
  36. local line = file.readLine()
  37. while line ~= nil do
  38. local blockName, action = line:match("^(%S+)%s+(%S+)$")
  39. if blockName ~= nil and action ~= nil then
  40. if action ~= "AVOID" and action ~= "DROP" then
  41. error("ERROR: Invalid action '" .. action .. "' for block '" .. blockName .. "'")
  42. end
  43. blacklist[blockName] = action
  44. end
  45. line = file.readLine()
  46. end
  47. file.close()
  48.  
  49. -- Print the blacklist
  50. print("The blacklist.txt file is used to avoid or throw away blocks.")
  51. print("Current blacklist:")
  52. lines = 0
  53. for blockName, action in pairs(blacklist) do
  54. print(blockName .. " - " .. action)
  55. lines = lines + 1
  56. if lines == 5 then
  57. print("... (truncated)")
  58. break
  59. end
  60. end
  61. if lines == 0 then
  62. print("(Empty)")
  63. end
  64. write("\nPress enter to continue... ")
  65. read()
  66.  
  67. -- Manual setup instructions
  68. print("\n-----------------------------")
  69. print("Before continuing, ensure that:")
  70. print("- The turtle is above bedrock level.")
  71. print("- An inventory is directly behind the turtle.")
  72. print("- There is at least 1 coal in the top left slot.")
  73. print("- The turtle can refuel to 1000 fuel (currently " .. turtle.getFuelLevel() .. ")")
  74. write("Press enter to continue... ")
  75. read()
  76.  
  77. -- Fuel checks
  78. REFUEL_AMOUNT = 80
  79. local tempData = turtle.getItemDetail(1)
  80. if tempData == nil or tempData.name ~= "minecraft:coal" then
  81. error("ERROR: No coal in the top left slot!")
  82. elseif turtle.getFuelLevel() + REFUEL_AMOUNT * (turtle.getItemCount(1)-1) < 1000 then
  83. error("ERROR: Not enough starting fuel! Add more coal to the top left slot.")
  84. end
  85.  
  86. -- Get side length and height
  87. print("\n-----------------------------")
  88. write("Side length of the area to quarry (default 16): ")
  89. local S = read()
  90. if S == "" then
  91. S = 16
  92. else
  93. S = tonumber(S)
  94. end
  95. write("Height of the area to quarry (default 16): ")
  96. local H = read()
  97. if H == "" then
  98. H = 16
  99. else
  100. H = tonumber(H)
  101. end
  102.  
  103. print("\n-----------------------------")
  104. local chestsRecommended = 0
  105. local droppingCobble = blacklist["minecraft:cobblestone"] == "DROP"
  106. if droppingCobble then
  107. chestsRecommended = math.ceil(S*S*H / 32 / 54 / 4 + 0.5)
  108. else
  109. chestsRecommended = math.ceil(S*S*H / 32 / 54 + 0.5)
  110. end
  111. print("Quarry area: " .. S .. "x" .. S .. "x" .. H .. " (" .. S*S*H .. " blocks)")
  112. print("# of large chests recommended: " .. chestsRecommended)
  113. if droppingStone then
  114. print("(Assuming common blocks like cobblestone are dropped)")
  115. end
  116. print("Place these behind the turtle - the side with the chest icon.")
  117. write("Begin? (y/n) ")
  118. local input = read()
  119. if input ~= "y" then
  120. print("Exiting...")
  121. return
  122. end
  123.  
  124. -- Initial setup
  125. local currPos = {1, 1, 1}
  126. local currFace = 0
  127.  
  128. -- Refuels the turtle if needed
  129. function refuelIfNeeded()
  130. if turtle.getFuelLevel() < turtle.getFuelLimit() - REFUEL_AMOUNT then
  131. --- Refuel
  132. if turtle.getItemCount(1) > 1 then
  133. turtle.select(1)
  134. turtle.refuel(1)
  135. print("Refueled (fuel left = " .. turtle.getFuelLevel() .. ")")
  136. elseif turtle.getFuelLevel() == 0 then
  137. error("ERROR: Out of fuel!")
  138. elseif turtle.getFuelLevel() % 100 == 0 then
  139. print("Cannot refuel (fuel left = " .. turtle.getFuelLevel() .. ")")
  140. end
  141. end
  142. end
  143.  
  144. -- Dictionary mapping direction names to changes in coordinates
  145. local dirToCoords = {
  146. ["forward"] = {0, 0, 1},
  147. ["right"] = {1, 0, 0},
  148. ["back"] = {0, 0, -1},
  149. ["left"] = {-1, 0, 0},
  150. ["up"] = {0, 1, 0},
  151. ["down"] = {0, -1, 0},
  152. }
  153.  
  154. -- Dictionaries mapping direction names to face IDs
  155. local dirToFace = {
  156. ["forward"] = 0,
  157. ["right"] = 1,
  158. ["back"] = 2,
  159. ["left"] = 3,
  160. }
  161. local faceToDir = {
  162. [0] = "forward",
  163. [1] = "right",
  164. [2] = "back",
  165. [3] = "left",
  166. }
  167.  
  168. -- Turns toward a direction
  169. function faceTowards(dir)
  170. local face = dirToFace[dir]
  171. local diff = (face - currFace) % 4
  172. if diff == 1 then
  173. turtle.turnRight()
  174. elseif diff == 2 then
  175. turtle.turnRight()
  176. turtle.turnRight()
  177. elseif diff == 3 then
  178. turtle.turnLeft()
  179. end
  180. currFace = face
  181. end
  182.  
  183. -- Moves toward a direction
  184. function move(dir)
  185. local moved = false
  186. if dir == "up" then
  187. moved = turtle.up()
  188. elseif dir == "down" then
  189. moved = turtle.down()
  190. else
  191. faceTowards(dir)
  192. moved = turtle.forward()
  193. end
  194. if moved then
  195. -- Update currPos
  196. local dx, dy, dz = unpack(dirToCoords[dir])
  197. currPos[1] = currPos[1] + dx
  198. currPos[2] = currPos[2] + dy
  199. currPos[3] = currPos[3] + dz
  200. end
  201. return moved
  202. end
  203.  
  204. -- Gets the direction from position 1 to position 2
  205. function getDirFromPos(pos1, pos2)
  206. local dx, dy, dz = pos2[1] - pos1[1], pos2[2] - pos1[2], pos2[3] - pos1[3]
  207. for dir, coords in pairs(dirToCoords) do
  208. if coords[1] == dx and coords[2] == dy and coords[3] == dz then
  209. return dir
  210. end
  211. end
  212. error("ERROR: Invalid positions", pos1[1], pos1[2], pos1[3], "and", pos2[1], pos2[2], pos2[3])
  213. return nil
  214. end
  215.  
  216. -- Attempts to inspect a direction; returns nil or block name
  217. function inspect(dir)
  218. local success, data
  219. if dir == "up" then
  220. success, data = turtle.inspectUp()
  221. elseif dir == "down" then
  222. success, data = turtle.inspectDown()
  223. else
  224. faceTowards(dir)
  225. success, data = turtle.inspect()
  226. end
  227. return success and data.name or nil
  228. end
  229.  
  230. -- Attempts to dig toward a direction; returns true if successful, false otherwise
  231. function dig(dir)
  232. local dug = false
  233. if dir == "up" then
  234. dug = turtle.digUp()
  235. elseif dir == "down" then
  236. dug = turtle.digDown()
  237. else
  238. faceTowards(dir)
  239. dug = turtle.dig()
  240. end
  241. return dug
  242. end
  243.  
  244. -- Creates a new 3D array for the target volume (N = Not mined, S = Skipped, M = Mined)
  245. function create3DArray()
  246. local arr = {}
  247. for x = 1, S do
  248. arr[x] = {}
  249. for y = 1, H do
  250. arr[x][y] = {}
  251. for z = 1, S do
  252. arr[x][y][z] = "N"
  253. end
  254. end
  255. end
  256. return arr
  257. end
  258.  
  259. -- Gets a path to the closest unmined square. Returns nil if no path exists.
  260. -- If a target position is specified, the path will be to that position (or nil).
  261. -- Source: OpenAI :D
  262. function findPath(arr, targetPos, allowUnmined)
  263. if allowUnmined == nil then
  264. allowUnmined = true
  265. end
  266. -- Create a queue for BFS and mark the start node as visited
  267. xi, yi, zi = currPos[1], currPos[2], currPos[3]
  268. local queue = {}
  269. local visited = {}
  270. visited[xi] = {}
  271. visited[xi][yi] = {}
  272. visited[xi][yi][zi] = true
  273. queue[#queue+1] = {xi, yi, zi, {}}
  274.  
  275. -- Perform BFS until the target node is found
  276. while #queue > 0 do
  277. local node = table.remove(queue, 1)
  278. local x, y, z, path = node[1], node[2], node[3], node[4]
  279.  
  280. -- Check if a minable square has been found
  281. if (targetPos == nil and arr[x][y][z] == "N") or
  282. (targetPos ~= nil and x == targetPos[1] and y == targetPos[2] and z == targetPos[3]) then
  283. -- print("Found path")
  284. return path
  285. end
  286.  
  287. -- Add unvisited neighboring nodes to the queue
  288. orderedDirs = {"forward", "back", "left", "right", "up", "down"}
  289. for dir = 1, 6 do
  290. local neighbor = dirToCoords[orderedDirs[dir]]
  291. local dx, dy, dz = neighbor[1], neighbor[2], neighbor[3]
  292. local nx, ny, nz = x + dx, y + dy, z + dz
  293. -- print("Checking neighbor", nx, ny, nz)
  294. -- Check if the neighbor is within bounds (or 1 outside of the grid)
  295. if nx >= 1 and nx <= S and ny >= 1 and ny <= H and nz >= 1 and nz <= S then
  296. -- Check if the neighbor has not been visited and is valid
  297. if arr[nx][ny][nz] ~= "S" and
  298. (allowUnmined or arr[nx][ny][nz] ~= "N") and
  299. (not visited[nx] or not visited[nx][ny] or not visited[nx][ny][nz]) then
  300. -- Mark the neighbor as visited and add it to the queue
  301. visited[nx] = visited[nx] or {}
  302. visited[nx][ny] = visited[nx][ny] or {}
  303. visited[nx][ny][nz] = true
  304. local npath = {}
  305. for i, p in ipairs(path) do
  306. npath[i] = p
  307. end
  308. npath[#npath+1] = {nx, ny, nz}
  309. queue[#queue+1] = {nx, ny, nz, npath}
  310. end
  311. end
  312. end
  313. end
  314.  
  315. -- If a minable node cannot be reached, return nil
  316. return nil
  317. end
  318.  
  319. -- Follows the given path
  320. function followPath(arr, path, ignoreLast)
  321. if ignoreLast == nil then
  322. ignoreLast = false
  323. end
  324. for i = 1, #path do
  325. if ignoreLast and i == #path then
  326. break
  327. end
  328. local nextPos = path[i]
  329. local dir = getDirFromPos(currPos, nextPos)
  330. local moved = move(dir)
  331. if not moved then
  332. if arr[nextPos[1]][nextPos[2]][nextPos[3]] == "N" then
  333. blocksToMine = blocksToMine - 1
  334. end
  335. arr[nextPos[1]][nextPos[2]][nextPos[3]] = "S"
  336. return false
  337. end
  338. end
  339. return true
  340. end
  341.  
  342. -- Moves to the given position (or closest unmined position), returns the path (or nil if no path exists)
  343. function forceMoveTo(arr, targetPos, ignoreLast, attempts)
  344. if attempts == nil then
  345. attempts = 0
  346. end
  347. if attempts > 100 then
  348. return nil
  349. end
  350.  
  351. local path = findPath(arr, targetPos, ignoreLast)
  352. -- printPath(path)
  353. -- printLayer(arr, currPos[2])
  354. if path == nil then
  355. return nil
  356. end
  357.  
  358. local moved = followPath(arr, path, ignoreLast)
  359. if not moved then
  360. -- Try again
  361. return forceMoveTo(arr, targetPos, ignoreLast, attempts+1)
  362. end
  363. -- write("Position: ")
  364. -- printPos(currPos)
  365. -- print(ignoreLast, attempts)
  366. return path
  367. end
  368.  
  369. -- Prints a position (for debugging)
  370. function printPos(pos)
  371. print("(" .. pos[1] .. ", " .. pos[2] .. ", " .. pos[3] .. ")")
  372. end
  373.  
  374. -- Prints a list of locations (for debugging)
  375. function printPath(path)
  376. if path == nil then
  377. print("Path: nil")
  378. return
  379. end
  380. print("Path: ")
  381. for _, p in ipairs(path) do
  382. printPos(p)
  383. end
  384. end
  385.  
  386. -- Prints the current layer (for debugging)
  387. function printLayer(arr, y)
  388. print("Layer " .. y .. ": ")
  389. for rz = 1, S do
  390. local row = ""
  391. for x = 1, S do
  392. local z = S+1-rz
  393. local char = arr[x][y][z]
  394. if char == "N" then
  395. char = "X"
  396. elseif char == "S" then
  397. char = "*"
  398. else -- M
  399. char = " "
  400. end
  401. if currPos[1] == x and currPos[2] == y and currPos[3] == z then
  402. char = "O"
  403. end
  404. row = row .. char
  405. end
  406. print(" " .. row)
  407. end
  408. end
  409.  
  410. -- Gets the number of unfilled slots in the turtle's inventory
  411. function getNumFreeSlots()
  412. local numFreeSlots = 0
  413. for i = 2, 16 do
  414. if turtle.getItemCount(i) == 0 then
  415. numFreeSlots = numFreeSlots + 1
  416. end
  417. end
  418. return numFreeSlots
  419. end
  420.  
  421. -- Deposits mined items into the chest
  422. function depositItems(arr)
  423. print("Depositing items...")
  424. -- Move to the chest
  425. local path = forceMoveTo(arr, {1, 1, 1}, false)
  426. if path == nil then
  427. error("ERROR: Could not find path to chest!")
  428. end
  429. faceTowards("back")
  430. for i = 2, 16 do
  431. turtle.select(i)
  432. -- Only deposit items that are not blacklisted
  433. if blacklist[turtle.getItemDetail().name] == "DROP" then
  434. turtle.dropDown()
  435. else
  436. turtle.drop()
  437. end
  438. end
  439. end
  440.  
  441. -- Get progress
  442. function getProgress(toMine)
  443. return math.floor((S*S*H - toMine) / (S*S*H)*100)
  444. end
  445.  
  446. -- Main loop
  447. local arr = create3DArray()
  448. arr[1][1][1] = "M"
  449. blocksToMine = S*S*H
  450. -- While there is still a reachable block to mine
  451. while true do
  452. refuelIfNeeded()
  453.  
  454. -- Move to the closest unmined square (if there is one)
  455. path = forceMoveTo(arr, nil, true)
  456. if path == nil then
  457. break
  458. end
  459.  
  460. -- Check if the square to mine is mineable
  461. local mineX, mineY, mineZ = path[#path][1], path[#path][2], path[#path][3]
  462. local mineDir = getDirFromPos(currPos, path[#path])
  463. local blockName = inspect(mineDir)
  464. if blacklist[blockName] == "AVOID" then
  465. -- Mark the square as skipped
  466. arr[mineX][mineY][mineZ] = "S"
  467. else
  468. -- Mine the square and mark it as mined
  469. local result = dig(mineDir) -- If no block is here, this will work anyways
  470. if not result and blockName ~= nil then
  471. arr[mineX][mineY][mineZ] = "S"
  472. else
  473. arr[mineX][mineY][mineZ] = "M"
  474. end
  475. end
  476. blocksToMine = blocksToMine - 1
  477.  
  478. -- Deposit items if necessary
  479. if getNumFreeSlots() == 0 then
  480. depositItems(arr)
  481. end
  482.  
  483. -- Print progress
  484. if getProgress(blocksToMine) ~= getProgress(blocksToMine-1) then
  485. print("Progress: " .. (S*S*H - blocksToMine) .. "/" .. (S*S*H) .. " (" .. getProgress(blocksToMine) .. "%)")
  486. end
  487. end
  488.  
  489. -- Finish up
  490. depositItems(arr)
  491. faceTowards("forward")
  492. print("Done mining! :)")
  493.  
Add Comment
Please, Sign In to add comment