Advertisement
Guest User

veinm by Qendolin

a guest
Sep 22nd, 2023
1,309
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 33.26 KB | None | 0 0
  1. ---
  2. --- "veinm" version 1.0 by Qendolin
  3. --- 22.09.2023 for CC: Restiched 1.100.8
  4. --- License: MPL 2.0
  5. --- Contact: 'Qendolin' on https://forums.computercraft.cc/
  6. ---
  7. --- Usage: veinm <pattern>... [options...]
  8. ---
  9. --- Options:
  10. ---  -help
  11. ---  -junk <junk_pattern>...
  12. ---  -safe <width> [height] [depth]
  13. ---  -debug <filters>...
  14. ---
  15. --- Specify at least one <pattern> to match against the block ids which should get mined.
  16. --- The turtle will find and mine all connected blocks of these types.
  17. --- Example: `veinm granite$ andesite$` will mine all blocks ending with "granite" and "andesite".
  18. ---
  19. --- Specify zero or more <junk_pattern> to match against item ids in the turtle's inventory
  20. ---   which will get tossed out when the turtle is full.
  21. --- Example: `-junk stone$` will toss out any items ending with "stone" such as cobblestone.
  22. ---
  23. --- Specify a safe zone which the turtle will not enter.
  24. --- By default the safe zone extends infinitely behind the turtle starting position.
  25. --- Any dimension that is not specified is assumed to be infinite.
  26. --- Example: `-safe inf 11 10` will exclude an area behind the turtle that
  27. ---   is `infinitely` wide, 5 blocks up, 5 blocks down and 10 blocks deep
  28. ---
  29. --- All patterns use the lua pattern syntax: https://www.lua.org/pil/20.2.html
  30. ---
  31. --- When the turtle cannot find any more blocks to mine it will return to its starting position.
  32. --- It will also return to its starting position when it runs low on fuel.
  33. ---
  34. --- Setup:
  35. --- 1. Place the turtle facing away from a chest. In this chest the turtle will deposit the collected items.
  36. --- 2. Make sure a block that you wish to mine is directly in front of the turtle.
  37. --- 3. Place enough coal in the first slot of the turtle to fill it 50%. I recommend Blocks of Coal.
  38. --- 4. Run `veinm`
  39. --- 5. Make sure to keep the turtle loaded!!
  40. ---
  41.  
  42. FuelItem = "coal"
  43.  
  44.  
  45. ---@class Direction
  46. ---@field dx number
  47. ---@field dy number
  48. ---@field dz number
  49. ---@field index number
  50. ---@field name string
  51. Direction = {}
  52.  
  53. ---
  54. ---@param dx number
  55. ---@param dy number
  56. ---@param dz number
  57. ---@param index number
  58. ---@return Direction
  59. function Direction:new(dx, dy, dz, index, name)
  60.     local o = {}
  61.     setmetatable(o, self)
  62.     self.__index = self
  63.     o.dx = dx
  64.     o.dy = dy
  65.     o.dz = dz
  66.     o.index = index
  67.     o.name = name
  68.     return o
  69. end
  70.  
  71. Directions = {
  72.     None = Direction:new(0, 0, 0, -1, "none"),
  73.     Forward = Direction:new(1, 0, 0, 0, "+F"),
  74.     Right = Direction:new(0, 0, 1, 1, "+R"),
  75.     Backward = Direction:new(-1, 0, 0, 2, "-F"),
  76.     Left = Direction:new(0, 0, -1, 3, "-R"),
  77.     Up = Direction:new(0, 1, 0, -1, "+U"),
  78.     Down = Direction:new(0, -1, 0, -1, "-U"),
  79. }
  80.  
  81. Directions[0] = Directions.Forward
  82. Directions[1] = Directions.Right
  83. Directions[2] = Directions.Backward
  84. Directions[3] = Directions.Left
  85.  
  86. ---
  87. ---@param rotation Rotation
  88. ---@return Direction
  89. function Direction:add(rotation)
  90.     if self.index == -1 then
  91.         return self
  92.     end
  93.  
  94.     local index = self.index + rotation.index
  95.     index = math.fmod(math.fmod(index, 4) + 4, 4)
  96.     return Directions[index]
  97. end
  98.  
  99. ---
  100. ---@param other Direction
  101. ---@return Rotation
  102. function Direction:sub(other)
  103.     if self.index == -1 or other.index == -1 then
  104.         return Rotations.None
  105.     end
  106.  
  107.     local index = other.index - self.index
  108.     index = math.fmod(math.fmod(index, 4) + 4, 4)
  109.     return Rotations[index]
  110. end
  111.  
  112. ---
  113. ---@return string
  114. function Direction:toString()
  115.     return string.format("%d,%d,%d", self.dx, self.dy, self.dz)
  116. end
  117.  
  118. ---@class Rotation
  119. ---@field a number
  120. ---@field b number
  121. ---@field c number
  122. ---@field d number
  123. ---@field index number
  124. Rotation = {}
  125.  
  126. ---
  127. ---@param a number
  128. ---@param b number
  129. ---@param c number
  130. ---@param d number
  131. ---@param index number
  132. ---@return Rotation
  133. function Rotation:new(a, b, c, d, index)
  134.     local o = {}
  135.     setmetatable(o, self)
  136.     self.__index = self
  137.     o.a = a
  138.     o.b = b
  139.     o.c = c
  140.     o.d = d
  141.     o.index = index
  142.     return o
  143. end
  144.  
  145. Rotations = {
  146.     None = Rotation:new(1, 0, 0, 1, 0),
  147.     Right = Rotation:new(0, -1, 1, 0, 1),
  148.     Reverse = Rotation:new(-1, 0, 0, -1, 2),
  149.     Left = Rotation:new(0, 1, -1, 0, 3),
  150. }
  151.  
  152. Rotations[0] = Rotations.None
  153. Rotations[1] = Rotations.Right
  154. Rotations[2] = Rotations.Reverse
  155. Rotations[3] = Rotations.Left
  156.  
  157. ---@param other Rotation
  158. function Rotation:add(other)
  159.     local index = self.index + other.index
  160.     index = math.fmod(math.fmod(index, 4) + 4, 4)
  161.     return Rotations[index]
  162. end
  163.  
  164. ---@class Vector
  165. ---@field x number
  166. ---@field y number
  167. ---@field z number
  168. Vector = {
  169.     maxx = 1024,
  170.     maxy = 512,
  171.     maxz = 1024,
  172.     minx = -1023,
  173.     miny = -511,
  174.     minz = -1023,
  175. }
  176.  
  177. ---
  178. ---@param x number?
  179. ---@param y number?
  180. ---@param z number?
  181. ---@return Vector
  182. function Vector:new(x, y, z)
  183.     local o = {}
  184.     setmetatable(o, self)
  185.     self.__index = self
  186.     o.x = x or 0
  187.     o.y = y or 0
  188.     o.z = z or 0
  189.     return o
  190. end
  191.  
  192. Zero = Vector:new()
  193.  
  194. ---
  195. ---@param other Vector
  196. ---@return boolean
  197. function Vector:equals(other)
  198.     if self == nil and other == nil then
  199.         return true
  200.     end
  201.     if self == nil or other == nil then
  202.         return false
  203.     end
  204.     return self.x == other.x and self.y == other.y and self.z == other.z
  205. end
  206.  
  207. ---
  208. ---@param other Vector | Direction
  209. ---@return Vector
  210. function Vector:add(other)
  211.     if getmetatable(other) == Direction then
  212.         return Vector:new(self.x + other.dx, self.y + other.dy, self.z + other.dz)
  213.     end
  214.     return Vector:new(self.x + other.x, self.y + other.y, self.z + other.z)
  215. end
  216.  
  217. ---
  218. ---@param other Vector | Direction
  219. ---@return Vector
  220. function Vector:sub(other)
  221.     if getmetatable(other) == Direction then
  222.         return Vector:new(self.x - other.dx, self.y - other.dy, self.z - other.dz)
  223.     end
  224.     return Vector:new(self.x - other.x, self.y - other.y, self.z - other.z)
  225. end
  226.  
  227. ---
  228. ---@param rotation Rotation
  229. ---@return Vector
  230. function Vector:rotate(rotation)
  231.     local x = self.x * rotation.a + self.z * rotation.b
  232.     local z = self.x * rotation.c + self.z * rotation.d
  233.     return Vector:new(x, self.y, z)
  234. end
  235.  
  236. ---
  237. ---@param direction Direction
  238. ---@return Vector
  239. function Vector:relative(direction)
  240.     return self:add(direction)
  241. end
  242.  
  243. ---
  244. ---@param other Vector
  245. ---@return number
  246. function Vector:distance(other)
  247.     local dx = self.x - other.x
  248.     local dy = self.y - other.y
  249.     local dz = self.z - other.z
  250.     return math.sqrt(dx * dx + dy * dy + dz * dz)
  251. end
  252.  
  253. ---
  254. ---@param other Vector
  255. ---@return number
  256. function Vector:distanceSquare(other)
  257.     local dx = self.x - other.x
  258.     local dy = self.y - other.y
  259.     local dz = self.z - other.z
  260.     return dx * dx + dy * dy + dz * dz
  261. end
  262.  
  263. ---
  264. ---@param other Vector
  265. ---@return number
  266. function Vector:distanceManhattan(other)
  267.     local dx = self.x - other.x
  268.     local dy = self.y - other.y
  269.     local dz = self.z - other.z
  270.     return math.abs(dx) + math.abs(dy) + math.abs(dz)
  271. end
  272.  
  273. ---packs the vector into a 32bit int
  274. ---the allowed x and z range is -1023 to 1024
  275. ---the allowed y range is -511 to 512
  276. ---@alias VectorKey number
  277. ---@return VectorKey
  278. function Vector:toKey()
  279.     local k = 0
  280.     k = bit.bor(k, bit32.lshift(bit32.band(self.x, 0x7FF), 21))
  281.     k = bit.bor(k, bit32.lshift(bit32.band(self.z, 0x7FF), 10))
  282.     k = bit.bor(k, bit.band(self.y, 0x3FF), 16)
  283.     return k
  284. end
  285.  
  286. ---
  287. ---@return string
  288. function Vector:toString()
  289.     return string.format("%d,%d,%d", self.x, self.y, self.z)
  290. end
  291.  
  292. ---@alias QueueState "unknown" | "queued" | "visited" | "irrelevant" | "avoid"
  293.  
  294. ---@class TargetMap
  295. ---@field states table<VectorKey, QueueState>
  296. ---@field queue table<VectorKey, Vector>
  297. ---@field queuedCount number
  298. ---@field visitedCount number
  299. ---@field mindedCount number
  300. ---@field recent VectorKey[]
  301. ---@field recentIndex number
  302. TargetMap = {
  303.     states = {},
  304.     queue = {},
  305.     queuedCount = 0,
  306.     visitedCount = 0,
  307.     minedCount = 0,
  308.     recent = {},
  309.     recentIndex = 0
  310. }
  311.  
  312. --
  313. ---@return TargetMap
  314. function TargetMap:new()
  315.     local o = {}
  316.     setmetatable(o, self)
  317.     self.__index = self
  318.     o.states = {}
  319.     o.queue = {}
  320.     o.queuedCount = 0
  321.     o.visitedCount = 0
  322.     o.minedCount = 0
  323.     o.discoveredCount = 0
  324.     o.recent = {}
  325.     o.recentIndex = 0
  326.     return o
  327. end
  328.  
  329. ---
  330. ---@param pos Vector
  331. ---@return QueueState
  332. function TargetMap:get(pos)
  333.     local state = self.states[pos:toKey()]
  334.     if state == nil then
  335.         return "unknown"
  336.     end
  337.     return state
  338. end
  339.  
  340. ---
  341. ---@param pos Vector
  342. ---@param state QueueState
  343. function TargetMap:set(pos, state)
  344.     local key = pos:toKey()
  345.     local prev = self.states[key]
  346.     self.states[key] = state
  347.     if state == "queued" and prev ~= "queued" then
  348.         self.queue[key] = pos
  349.         self.recent[self.recentIndex] = key
  350.         self.recentIndex = math.fmod(self.recentIndex + 1, 4)
  351.         self.queuedCount = self.queuedCount + 1
  352.     end
  353.     if state ~= "queued" and prev == "queued" then
  354.         self.queue[key] = nil
  355.         self.queuedCount = self.queuedCount - 1
  356.     end
  357.     if state == "visited" and prev ~= "visited" then
  358.         self.visitedCount = self.visitedCount + 1
  359.     end
  360.     if state ~= nil and prev == nil then
  361.         self.discoveredCount = self.discoveredCount + 1
  362.     end
  363.     if state == "visited" and prev == "queued" then
  364.         self.minedCount = self.minedCount + 1
  365.     end
  366. end
  367.  
  368. ---
  369. ---@param from Vector
  370. ---@param facing Direction
  371. ---@return Vector | nil
  372. function TargetMap:findNextTarget(from, facing)
  373.     -- look in recent additions first, because there is a high
  374.     -- chance that it's right next to us
  375.     local bestPos = nil
  376.     local bestValue = math.huge
  377.     for index = 4, 1, -1 do
  378.         local pos = self.queue[self.recent[index]]
  379.         if pos ~= nil then
  380.             local value = self:evaluateTarget(from, pos, facing)
  381.             if value < bestValue then
  382.                 bestValue = value
  383.                 bestPos = pos
  384.             end
  385.         end
  386.     end
  387.     if bestValue <= 1 then
  388.         return bestPos
  389.     end
  390.     for _, pos in pairs(self.queue) do
  391.         if pos ~= false then
  392.             local value = self:evaluateTarget(from, pos, facing)
  393.             if value <= 1 then
  394.                 return pos
  395.             end
  396.  
  397.             if value < bestValue then
  398.                 bestValue = value
  399.                 bestPos = pos
  400.             end
  401.         end
  402.     end
  403.     return bestPos
  404. end
  405.  
  406. ---
  407. ---@param from Vector
  408. ---@param pos Vector
  409. ---@param facing Direction
  410. ---@return number
  411. function TargetMap:evaluateTarget(from, pos, facing)
  412.     local value = from:distanceManhattan(pos) + Zero:distance(pos) * 0.5
  413.     local discovery = self:evaluateDiscovery(pos, facing)
  414.     value = value - discovery * 0.35
  415.     if discovery <= 1 and pos.x == from.x + facing.dx and pos.z == from.z + facing.dz and pos.y == from.y then
  416.         -- straigt ahead bonus
  417.         value = value - 1
  418.     end
  419.     return value
  420. end
  421.  
  422. ---
  423. ---@param pos Vector
  424. ---@param facing Direction
  425. ---@return number
  426. function TargetMap:evaluateDiscovery(pos, facing)
  427.     local value = 0
  428.     local p = Vector:new(pos.x, pos.y + 1, pos.z)
  429.     if self.states[p:toKey()] == nil then
  430.         value = value + 1
  431.     end
  432.  
  433.     p.y = pos.y - 1
  434.     if self.states[p:toKey()] == nil then
  435.         value = value + 1
  436.     end
  437.  
  438.     p.y = pos.y
  439.     p.x = pos.x + facing.dx
  440.     p.z = pos.z + facing.dz
  441.     if self.states[p:toKey()] == nil then
  442.         value = value + 1
  443.     end
  444.  
  445.     return value
  446. end
  447.  
  448. ---@class WriteHandle
  449. ---@field write fun(text: string)
  450. ---@field writeLine fun(text: string)
  451. ---@field flush fun()
  452. ---@field close fun()
  453.  
  454. ---@class LogOp
  455. ---@field symbol string
  456. ---@field defaultArg any
  457. ---@field combine fun(args: any, arg: any): any
  458.  
  459. ---@type table<string, LogOp>
  460. LogOps = {
  461.     MoveForward = {
  462.         symbol = "h",
  463.         defaultArg = 0,
  464.         ---@param args number
  465.         ---@param arg number
  466.         ---@return number
  467.         combine = function(args, arg)
  468.             return args + arg
  469.         end
  470.     },
  471.     MoveVertical = {
  472.         symbol = "v",
  473.         defaultArg = 0,
  474.         ---@param args number
  475.         ---@param arg number
  476.         ---@return number
  477.         combine = function(args, arg)
  478.             return args + arg
  479.         end
  480.     },
  481.     Turn = {
  482.         symbol = "t",
  483.         defaultArg = 0,
  484.         ---@param args number
  485.         ---@param arg Rotation
  486.         ---@return number
  487.         combine = function(args, arg)
  488.             return math.fmod(math.fmod(args + arg.index, 4) + 4, 4)
  489.         end
  490.     }
  491. }
  492.  
  493. ---@class LogWriter
  494. ---@field op LogOp?
  495. ---@field args any
  496. ---@field file WriteHandle?
  497. LogWriter = {
  498.     op = nil,
  499.     args = nil,
  500.     file = nil,
  501. }
  502.  
  503. ---
  504. ---@param filename string
  505. ---@return LogWriter
  506. function LogWriter:new(filename)
  507.     local o = {}
  508.     setmetatable(o, self)
  509.     self.__index = self
  510.     o.op         = ""
  511.     o.args       = ""
  512.     fs.delete(filename)
  513.     o.file = fs.open(filename, "a")
  514.     return o
  515. end
  516.  
  517. ---
  518. ---@param op LogOp
  519. ---@param arg any
  520. function LogWriter:append(op, arg)
  521.     if self.op ~= op then
  522.         self:flush()
  523.         self.op = op
  524.         self.args = self.op.combine(op.defaultArg, arg)
  525.     else
  526.         self.args = self.op.combine(self.args, arg)
  527.     end
  528. end
  529.  
  530. function LogWriter:flush()
  531.     if self.op == nil then
  532.         return
  533.     end
  534.     if self.op.defaultArg == self.args then
  535.         return
  536.     end
  537.     self.file.write(self.op.symbol)
  538.     self.file.writeLine(tostring(self.args))
  539.     self.file.flush()
  540. end
  541.  
  542. ---@alias BlockName string
  543.  
  544. ---@class Program
  545. ---@field pos Vector
  546. ---@field targetPos Vector
  547. ---@field direction Direction
  548. ---@field map TargetMap
  549. ---@field targetPatterns string[]
  550. ---@field junkPatterns string[]
  551. ---@field tui TUI?
  552. ---@field stop boolean
  553. ---@field log LogWriter?
  554. ---@field safeZone AABB
  555. Program = {}
  556.  
  557. ---
  558. ---@return Program
  559. function Program:new()
  560.     local o = {}
  561.     setmetatable(o, self)
  562.     self.__index     = self
  563.     o.pos            = Vector:new()
  564.     o.targetPos      = Vector:new()
  565.     o.direction      = Directions.Forward
  566.     o.map            = TargetMap:new()
  567.     o.targetPatterns = {}
  568.     o.junkPatterns   = {}
  569.     o.tui            = TUI:new(o)
  570.     o.log            = LogWriter:new("veinm.log")
  571.     o.safeZone       = AABB:new(
  572.         Vector:new(-Vector.minx, -Vector.miny, -Vector.minz),
  573.         Vector:new(-1, Vector.maxy, Vector.maxz)
  574.     )
  575.     return o
  576. end
  577.  
  578. ---
  579. ---@return Vector
  580. function Program:posInFront()
  581.     return self.pos:add(self.direction)
  582. end
  583.  
  584. ---
  585. ---@param rotation Rotation
  586. ---@return Vector
  587. function Program:posRelative(rotation)
  588.     return self.pos:add(self.direction:add(rotation))
  589. end
  590.  
  591. ---
  592. ---@return Vector
  593. function Program:posAbove()
  594.     return self.pos:add(Directions.Up)
  595. end
  596.  
  597. ---
  598. ---@return Vector
  599. function Program:posBelow()
  600.     return self.pos:add(Directions.Down)
  601. end
  602.  
  603. function Program:turnLeft()
  604.     turtle.turnLeft()
  605.     self.direction = self.direction:add(Rotations.Left)
  606.     self.tui:draw()
  607.     self.log:append(LogOps.Turn, Rotations.Left)
  608. end
  609.  
  610. function Program:turnRight()
  611.     turtle.turnRight()
  612.     self.direction = self.direction:add(Rotations.Right)
  613.     self.tui:draw()
  614.     self.log:append(LogOps.Turn, Rotations.Right)
  615. end
  616.  
  617. function Program:move()
  618.     turtle.dig()
  619.     while not turtle.forward() do
  620.         sleep(0.25)
  621.         turtle.dig()
  622.     end
  623.  
  624.     self:finishMove(self.direction)
  625.     self.log:append(LogOps.MoveForward, 1)
  626. end
  627.  
  628. function Program:moveUp()
  629.     turtle.digUp()
  630.     while not turtle.up() do
  631.         sleep(0.25)
  632.         turtle.digUp()
  633.     end
  634.  
  635.     self:finishMove(Directions.Up)
  636.     self.log:append(LogOps.MoveVertical, 1)
  637. end
  638.  
  639. function Program:moveDown()
  640.     turtle.digDown()
  641.     while not turtle.down() do
  642.         sleep(0.25)
  643.         turtle.digDown()
  644.     end
  645.  
  646.     self:finishMove(Directions.Down)
  647.     self.log:append(LogOps.MoveVertical, -1)
  648. end
  649.  
  650. ---
  651. ---@param dir Direction
  652. function Program:finishMove(dir)
  653.     self.pos = self.pos:add(dir)
  654.     self:markPosVisited()
  655.     self:processBlocks(dir)
  656.     self.tui:draw()
  657. end
  658.  
  659. function Program:markPosVisited()
  660.     self.map:set(self.pos, "visited")
  661. end
  662.  
  663. ---
  664. ---@return BlockName
  665. function Program:blockInFront()
  666.     local succsess, data = turtle.inspect()
  667.     if succsess then
  668.         return data.name
  669.     else
  670.         return ""
  671.     end
  672. end
  673.  
  674. ---
  675. ---@return BlockName
  676. function Program:blockAbove()
  677.     local succsess, data = turtle.inspectUp()
  678.     if succsess then
  679.         return data.name
  680.     else
  681.         return ""
  682.     end
  683. end
  684.  
  685. ---
  686. ---@return BlockName
  687. function Program:blockBelow()
  688.     local succsess, data = turtle.inspectDown()
  689.     if succsess then
  690.         return data.name
  691.     else
  692.         return ""
  693.     end
  694. end
  695.  
  696. ---
  697. ---@param pos Vector
  698. ---@return boolean
  699. function Program:mayVisit(pos)
  700.     return not self.safeZone:inside(pos)
  701. end
  702.  
  703. ---
  704. ---@param name BlockName
  705. ---@return boolean
  706. function Program:isTarget(name)
  707.     for i, pattern in ipairs(self.targetPatterns) do
  708.         if string.find(name, pattern) then
  709.             return true
  710.         end
  711.     end
  712.     return false
  713. end
  714.  
  715. ---
  716. ---@param pos Vector
  717. ---@param name BlockName
  718. function Program:processBlock(pos, name)
  719.     if not self:mayVisit(pos) then
  720.         self.map:set(pos, "avoid")
  721.     elseif self:isTarget(name) then
  722.         self.map:set(pos, "queued")
  723.     else
  724.         self.map:set(pos, "irrelevant")
  725.     end
  726. end
  727.  
  728. function Program:processBlockInFront()
  729.     self:processBlock(self:posInFront(), self:blockInFront())
  730. end
  731.  
  732. ---
  733. ---@param pos Vector
  734. ---@return boolean
  735. function Program:hasProcessed(pos)
  736.     return self.map:get(pos) ~= "unknown"
  737. end
  738.  
  739. ---
  740. ---@param lastMove Direction
  741. function Program:processBlocks(lastMove)
  742.     -- up
  743.     if not self:hasProcessed(self:posAbove()) then
  744.         self:processBlock(self:posAbove(), self:blockAbove())
  745.     end
  746.     -- down
  747.     if not self:hasProcessed(self:posBelow()) then
  748.         self:processBlock(self:posBelow(), self:blockBelow())
  749.     end
  750.  
  751.     local lastMoveVertical = lastMove.dy ~= 0
  752.     local checkBack = lastMoveVertical and not self:hasProcessed(self:posRelative(Rotations.Reverse))
  753.  
  754.  
  755.     -- front
  756.     if not self:hasProcessed(self:posInFront()) then
  757.         self:processBlockInFront()
  758.     end
  759.  
  760.     local forward = self.direction
  761.     local right = forward:add(Rotations.Right)
  762.     local left = forward:add(Rotations.Left)
  763.     local back = forward:add(Rotations.Reverse)
  764.  
  765.     -- right
  766.     if not self:hasProcessed(self.pos:add(right)) then
  767.         self:turnTo(right)
  768.         self:processBlockInFront()
  769.  
  770.         if checkBack then
  771.             self:turnTo(back)
  772.             self:processBlockInFront()
  773.             checkBack = false
  774.         end
  775.     end
  776.  
  777.     -- left
  778.     if not self:hasProcessed(self.pos:add(left)) then
  779.         self:turnTo(left)
  780.         self:processBlockInFront()
  781.  
  782.         if checkBack then
  783.             self:turnTo(back)
  784.             self:processBlockInFront()
  785.             checkBack = false
  786.         end
  787.     end
  788.  
  789.     self.tui:draw()
  790. end
  791.  
  792. ---
  793. ---@param direction Direction
  794. function Program:turnTo(direction)
  795.     local rotation = self.direction:sub(direction)
  796.     if rotation == Rotations.None then
  797.         return
  798.     elseif rotation == Rotations.Right then
  799.         self:turnRight()
  800.     elseif rotation == Rotations.Reverse then
  801.         self:turnLeft()
  802.         self:turnLeft()
  803.     elseif rotation == Rotations.Left then
  804.         self:turnLeft()
  805.     end
  806. end
  807.  
  808. ---
  809. ---@param pos Vector
  810. function Program:moveTo(pos)
  811.     self.targetPos = pos
  812.     local delta = pos:sub(self.pos)
  813.  
  814.     if not self:mayVisit(pos) then
  815.         error(string.format("Impossible move to %s, inside safe zone", pos:toString()))
  816.     end
  817.  
  818.     for i = 1, delta.y, 1 do
  819.         if self.map:get(self:posAbove()) == "avoid" then
  820.             self:moveTo(self.safeZone:closestAvoidancePoint(self.pos))
  821.         end
  822.         self:moveUp()
  823.     end
  824.     for i = 1, -delta.y, 1 do
  825.         if self.map:get(self:posBelow()) == "avoid" then
  826.             self:moveTo(self.safeZone:closestAvoidancePoint(self.pos))
  827.         end
  828.         self:moveDown()
  829.     end
  830.  
  831.     local dir = Directions.None
  832.     if delta.x > 0 then
  833.         dir = Directions.Forward
  834.     elseif delta.x < 0 then
  835.         dir = Directions.Backward
  836.     end
  837.  
  838.     for i = 1, math.abs(delta.x), 1 do
  839.         self:turnTo(dir)
  840.         if not self:mayVisit(self:posInFront()) then
  841.             self:moveTo(self.safeZone:closestAvoidancePoint(self.pos))
  842.             self:turnTo(dir)
  843.         end
  844.         self:move()
  845.     end
  846.  
  847.     dir = Directions.None
  848.     if delta.z > 0 then
  849.         dir = Directions.Right
  850.     elseif delta.z < 0 then
  851.         dir = Directions.Left
  852.     end
  853.  
  854.     for i = 1, math.abs(delta.z), 1 do
  855.         self:turnTo(dir)
  856.         if not self:mayVisit(self:posInFront()) then
  857.             self:moveTo(self.safeZone:closestAvoidancePoint(self.pos))
  858.             self:turnTo(dir)
  859.         end
  860.         self:move()
  861.     end
  862. end
  863.  
  864. function Program:isInventoryFull()
  865.     local emptySlots = 0
  866.     for i = 1, 16, 1 do
  867.         local count = turtle.getItemCount(i)
  868.         if count == 0 then
  869.             emptySlots = emptySlots + 1
  870.         end
  871.     end
  872.     return emptySlots <= 1
  873. end
  874.  
  875. function Program:moveToStart()
  876.     if not self.pos:equals(Zero) then
  877.         self:moveTo(Zero)
  878.     end
  879.     self:turnTo(Directions.Forward)
  880. end
  881.  
  882. function Program:dropJunk()
  883.     self.tui:setStatus("Dropping junk")
  884.     for i = 1, 16, 1 do
  885.         turtle.select(i)
  886.         local type = turtle.getItemDetail(i)
  887.         if type then
  888.             local keep = true
  889.             for _, pattern in ipairs(self.junkPatterns) do
  890.                 if string.find(type.name, pattern) then
  891.                     keep = false
  892.                     break
  893.                 end
  894.             end
  895.  
  896.             if not keep then
  897.                 turtle.dropDown()
  898.             end
  899.         end
  900.     end
  901.     turtle.select(1)
  902.     self.tui:setStatus("")
  903. end
  904.  
  905. function Program:unload()
  906.     self:turnTo(Directions.Backward)
  907.     self.tui:setStatus("Unloading inventory")
  908.  
  909.     for i = 1, 16, 1 do
  910.         turtle.select(i)
  911.         local type = turtle.getItemDetail(i)
  912.         local isFuel = false
  913.         if type and string.find(type.name, FuelItem) then
  914.             isFuel = true
  915.             turtle.transferTo(1)
  916.         end
  917.         if not (isFuel and i == 1) then
  918.             turtle.drop()
  919.         end
  920.     end
  921.     turtle.select(1)
  922.  
  923.     self:turnTo(Directions.Forward)
  924.     self.tui:setStatus("")
  925. end
  926.  
  927. function Program:consumeFuel()
  928.     if turtle.getFuelLevel() >= turtle.getFuelLimit() - 1000 then
  929.         return
  930.     end
  931.     self.tui:setStatus("Refueling")
  932.     for slot = 1, 16, 1 do
  933.         turtle.select(slot)
  934.         while turtle.getFuelLevel() < turtle.getFuelLimit() - 1000 do
  935.             if not turtle.refuel(1) then
  936.                 break
  937.             end
  938.         end
  939.     end
  940.     turtle.select(1)
  941.     self.tui:setStatus("")
  942. end
  943.  
  944. function Program:unloadAndRefuel()
  945.     self:unload()
  946.     local minFuel = turtle.getFuelLimit() / 2
  947.     while turtle.getFuelLevel() < minFuel do
  948.         self.tui:setStatus(string.format("Insert %d fuel to continue", minFuel - turtle.getFuelLevel()))
  949.         sleep(1)
  950.         self.tui:draw()
  951.         self:consumeFuel()
  952.     end
  953. end
  954.  
  955. ---@class TUI
  956. ---@field statusMessage string
  957. ---@field program Program?
  958. ---@field tick number
  959. TUI = {}
  960.  
  961. ---
  962. ---@param program Program
  963. ---@return TUI
  964. function TUI:new(program)
  965.     local o = {}
  966.     setmetatable(o, self)
  967.     self.__index = self
  968.     o.statusMessage = ""
  969.     o.program = program
  970.     o.tick = 0
  971.     o.lastTick = os.clock()
  972.     return o
  973. end
  974.  
  975. ---
  976. ---@param message string
  977. function TUI:setStatus(message)
  978.     self.statusMessage = message
  979.     self:draw()
  980. end
  981.  
  982. function TUI:draw()
  983.     local w, h = term.getSize()
  984.     term.setCursorPos(1, 1)
  985.     term.clearLine()
  986.     term.write(TUI.ellipseString(self.statusMessage, w))
  987.  
  988.     term.setCursorPos(1, 3)
  989.     TUI:table(w, { 2, 1 }, 2,
  990.         { { "Position", self.program.pos:toString() }, { "Direction", self.program.direction.name } })
  991.  
  992.     local map = self.program.map
  993.     local efficiency = math.floor(100 * (map.minedCount + 1) / map.visitedCount)
  994.     term.setCursorPos(1, 6)
  995.     TUI:table(w, { 1, 1, 1 }, 2,
  996.         {
  997.             { "Discovered", map.discoveredCount },
  998.             { "Visited",    map.visitedCount },
  999.             { "Mined",      map.minedCount }
  1000.         })
  1001.     term.setCursorPos(1, 9)
  1002.     TUI:table(w, { 1, 1, 1 }, 2,
  1003.         {
  1004.             { "Queued",     map.queuedCount },
  1005.             { "Efficiency", efficiency .. "%" },
  1006.             { "Fuel",       turtle.getFuelLevel() }
  1007.         })
  1008.  
  1009.     term.setCursorPos(1, h)
  1010.     term.clearLine()
  1011.     if self.lastTick ~= math.floor(os.clock()) then
  1012.         self.tick = self.tick + 1
  1013.         self.lastTick = math.floor(os.clock())
  1014.     end
  1015.     term.write(string.rep(".", math.fmod(self.tick, 4) + 1))
  1016. end
  1017.  
  1018. ---
  1019. ---@param s string
  1020. ---@param len number
  1021. ---@return string
  1022. function TUI.ellipseString(s, len)
  1023.     if string.len(s) > len then
  1024.         return string.sub(s, 1, len - 3) .. "..."
  1025.     end
  1026.     return s
  1027. end
  1028.  
  1029. ---
  1030. ---@param width number
  1031. ---@param columns number[]
  1032. ---@param rows number
  1033. ---@param data any[][]
  1034. function TUI:table(width, columns, rows, data, options)
  1035.     local fractions = 0
  1036.     for _, fract in ipairs(columns) do
  1037.         fractions = fractions + fract
  1038.     end
  1039.     local availWidth = width
  1040.     local fractTotal = 0
  1041.     local colFmt = ""
  1042.     for col, fract in ipairs(columns) do
  1043.         local colStart = availWidth * (fractTotal / fractions)
  1044.         fractTotal = fractTotal + fract
  1045.         local colEnd = availWidth * (fractTotal / fractions)
  1046.         local size = math.floor(colEnd - colStart + 0.5)
  1047.         colFmt = colFmt .. "%-" .. tostring(size - 2) .. "s"
  1048.         if col < #columns then
  1049.             colFmt = colFmt .. "| "
  1050.         end
  1051.     end
  1052.     local cx, cy = term.getCursorPos()
  1053.     for row = 1, rows, 1 do
  1054.         local rowData = {}
  1055.         for col = 1, #data, 1 do
  1056.             local value = data[col][row] or ""
  1057.             table.insert(rowData, value)
  1058.         end
  1059.         term.clearLine()
  1060.         term.write(string.format(colFmt, table.unpack(rowData)))
  1061.         if row < rows then
  1062.             term.setCursorPos(cx, cy + row)
  1063.         end
  1064.     end
  1065. end
  1066.  
  1067. function Program:run()
  1068.     term.clear()
  1069.  
  1070.     self.tui:setStatus("Starting...")
  1071.  
  1072.     self:unloadAndRefuel()
  1073.  
  1074.     self:processBlocks(Directions.Forward)
  1075.     self:markPosVisited()
  1076.  
  1077.     while not self.stop do
  1078.         self:consumeFuel()
  1079.         if turtle.getFuelLevel() < self.pos:distanceManhattan(Zero) * 1.5 then
  1080.             self.tui:setStatus("Fuel low, returning to start")
  1081.             self:moveToStart()
  1082.             self:unloadAndRefuel()
  1083.         end
  1084.  
  1085.         if self:isInventoryFull() then
  1086.             self:dropJunk()
  1087.         end
  1088.         if self:isInventoryFull() then
  1089.             self.tui:setStatus("Going to unload inventory")
  1090.             self:moveToStart()
  1091.             self:unload()
  1092.         end
  1093.  
  1094.         if self.stop then
  1095.             break
  1096.         end
  1097.  
  1098.         local target = self.map:findNextTarget(self.pos, self.direction)
  1099.  
  1100.         if target ~= nil then
  1101.             self.tui:setStatus("Target: " .. target:toString())
  1102.         else
  1103.             self.tui:setStatus("No target, returning to start")
  1104.             self:moveToStart()
  1105.             self:unload()
  1106.             self.tui:setStatus("Finished")
  1107.             break
  1108.         end
  1109.  
  1110.         self:moveTo(target)
  1111.     end
  1112.  
  1113.     self.log:flush()
  1114.  
  1115.     while true do
  1116.         sleep(1)
  1117.         self.tui:draw()
  1118.     end
  1119. end
  1120.  
  1121. Debug = {
  1122.     enabled = false,
  1123.     filters = {},
  1124.     skip = 0,
  1125.     history = {}
  1126. }
  1127. ---
  1128. ---@param id string
  1129. ---@param msg? string
  1130. function DebugStep(id, msg)
  1131.     if not Debug.enabled then
  1132.         return
  1133.     end
  1134.  
  1135.     if #Debug.filters > 0 then
  1136.         local match = false
  1137.         for _, pattern in ipairs(Debug.filters) do
  1138.             if string.find(id, pattern) then
  1139.                 match = true
  1140.                 break
  1141.             end
  1142.         end
  1143.         if not match then
  1144.             return
  1145.         end
  1146.     end
  1147.  
  1148.     if Debug.skip > 0 then
  1149.         Debug.skip = Debug.skip - 1
  1150.         return
  1151.     end
  1152.  
  1153.     local w, h = term.getSize()
  1154.  
  1155.     for i = 0, 4, 1 do
  1156.         term.setCursorPos(1, h - i)
  1157.         term.clearLine()
  1158.     end
  1159.  
  1160.     if msg ~= nil then
  1161.         write(msg)
  1162.     end
  1163.  
  1164.     while true do
  1165.         term.setCursorPos(1, h)
  1166.         term.clearLine()
  1167.         term.write(string.format("@ %s. [space] [ctrl]", id))
  1168.         ---@diagnostic disable-next-line: undefined-field
  1169.         local ev = { os.pullEvent("key") }
  1170.         if ev[2] == 32 then
  1171.             -- space
  1172.             break
  1173.         elseif ev[2] == 341 then
  1174.             -- left control
  1175.             term.setCursorPos(1, h)
  1176.             term.clearLine()
  1177.             local cmd = read(nil, Debug.history)
  1178.             ---@diagnostic disable-next-line: deprecated
  1179.             local fn, err = loadstring(cmd)
  1180.             if fn then
  1181.                 RunDebugCmd(fn)
  1182.             else
  1183.                 write("Error: ", err)
  1184.             end
  1185.             table.insert(Debug.history, cmd)
  1186.         end
  1187.     end
  1188.  
  1189.     for i = 0, 4, 1 do
  1190.         term.setCursorPos(1, h - i)
  1191.         term.clearLine()
  1192.     end
  1193. end
  1194.  
  1195. function RunDebugCmd(__func)
  1196.     _G.echo = function(...)
  1197.         local w, h = term.getSize()
  1198.         for i = 0, 4, 1 do
  1199.             term.setCursorPos(1, h - i)
  1200.             term.clearLine()
  1201.         end
  1202.         local args = { n = select("#", ...), ... }
  1203.         for i = 1, args.n do
  1204.             io.stdout:write(tostring(args[i]))
  1205.         end
  1206.     end
  1207.     ---@diagnostic disable-next-line: deprecated
  1208.     setfenv(__func, getfenv())
  1209.     local status, err = pcall(__func)
  1210.     if not status then
  1211.         echo(err)
  1212.     end
  1213.     _G.echo = nil
  1214. end
  1215.  
  1216. ---@class AABB
  1217. ---@field min Vector
  1218. ---@field max Vector
  1219. AABB = {}
  1220.  
  1221. ---
  1222. ---@param min Vector
  1223. ---@param max Vector
  1224. ---@return AABB
  1225. function AABB:new(min, max)
  1226.     local o = {}
  1227.     setmetatable(o, self)
  1228.     self.__index = self
  1229.     o.min = min
  1230.     o.max = max
  1231.     return o
  1232. end
  1233.  
  1234. ---
  1235. ---@param pos Vector
  1236. ---@return boolean
  1237. function AABB:inside(pos)
  1238.     local min = self.min
  1239.     local max = self.max
  1240.     return pos.x >= min.x and pos.x <= max.x and
  1241.         pos.y >= min.y and pos.y <= max.y and
  1242.         pos.z >= min.z and pos.z <= max.z
  1243. end
  1244.  
  1245. ---
  1246. ---NOTE: I haven't fully tested this algorith
  1247. ---@param pos Vector
  1248. ---@return Vector
  1249. function AABB:closestAvoidancePoint(pos)
  1250.     local min = self.min
  1251.     local max = self.max
  1252.  
  1253.     local options = {
  1254.         { coord = 'x', value = min.x - 1, dist = math.abs(min.x - 1 - pos.x) },
  1255.         { coord = 'y', value = min.y - 1, dist = math.abs(min.y - 1 - pos.y) },
  1256.         { coord = 'z', value = min.z - 1, dist = math.abs(min.z - 1 - pos.z) },
  1257.         { coord = 'x', value = max.x + 1, dist = math.abs(max.x + 1 - pos.x) },
  1258.         { coord = 'y', value = max.y + 1, dist = math.abs(max.y + 1 - pos.y) },
  1259.         { coord = 'z', value = max.z + 1, dist = math.abs(max.z + 1 - pos.z) },
  1260.     }
  1261.  
  1262.     table.sort(options, function(a, b)
  1263.         return a.dist < b.dist
  1264.     end)
  1265.  
  1266.     local result = Vector:new(pos.x, pos.y, pos.z)
  1267.     result[options[2].coord] = options[2].value
  1268.  
  1269.     return result
  1270. end
  1271.  
  1272. ---
  1273. ---@param width (number | string)?
  1274. ---@param height (number | string)?
  1275. ---@param depth (number | string)?
  1276. function Program:parseSafeZone(width, height, depth)
  1277.     self.safeZone = AABB:new(Vector:new(), Vector:new())
  1278.  
  1279.     width = width or "inf"
  1280.     height = height or "inf"
  1281.     depth = depth or "inf"
  1282.  
  1283.     width = tonumber(width) or Vector.maxx * 2;
  1284.     height = tonumber(height) or Vector.maxy * 2
  1285.     depth = tonumber(depth) or Vector.maxz * 2
  1286.  
  1287.     self.safeZone.min.x = -math.ceil(depth)
  1288.     self.safeZone.max.x = -1
  1289.     self.safeZone.min.y = -math.ceil(height / 2 - 0.5)
  1290.     self.safeZone.max.y = math.ceil(height / 2 - 0.5)
  1291.     self.safeZone.min.z = -math.ceil(width / 2 - 0.5)
  1292.     self.safeZone.max.z = math.ceil(width / 2 - 0.5)
  1293. end
  1294.  
  1295. function Start(args)
  1296.     local junkPatterns = {}
  1297.     local targetPatterns = {}
  1298.     local safeZoneArgs = {}
  1299.     local activeList = targetPatterns
  1300.     local help = false
  1301.     for _, v in ipairs(args) do
  1302.         if string.find(v, "^-") then
  1303.             if v == "-junk" then
  1304.                 activeList = junkPatterns
  1305.             elseif v == "-safe" then
  1306.                 activeList = safeZoneArgs
  1307.             elseif v == "-help" then
  1308.                 help = true
  1309.                 break
  1310.             elseif v == "-debug" then
  1311.                 Debug.enabled = true
  1312.                 activeList = Debug.filters
  1313.             else
  1314.                 help = true
  1315.                 return
  1316.             end
  1317.         else
  1318.             table.insert(activeList, v)
  1319.         end
  1320.     end
  1321.     if #targetPatterns == 0 or help or #safeZoneArgs > 3 then
  1322.         print("Usage: veinm <pattern>... [options...]")
  1323.         print("Options:")
  1324.         print(" -help")
  1325.         print(" -junk <junk_pattern>...")
  1326.         print(" -safe <width> <height> <depth>")
  1327.         print(" -debug <filters>...")
  1328.         return
  1329.     end
  1330.  
  1331.     local app = Program:new()
  1332.     app.targetPatterns = targetPatterns
  1333.     app.junkPatterns = junkPatterns
  1334.     app:parseSafeZone(safeZoneArgs[1], safeZoneArgs[2], safeZoneArgs[3])
  1335.     app:run()
  1336. end
  1337.  
  1338. Start({ ... })
  1339.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement