Dimencia

Untitled

Mar 24th, 2021
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.88 KB | None | 0 0
  1.  
  2. -- This is a base turtle implementation that should allow it to pathfind using A* pathfinding
  3. -- Any movement or turning causes it to scan its environment and store data, allowing it to 'remember' where obstacles are
  4.  
  5. -- This is honestly doable. If I need a refresher later: https://www.raywenderlich.com/3016-introduction-to-a-pathfinding
  6. if not fs.exists("vec3.lua") then shell.run("wget", "https://raw.githubusercontent.com/Dimencia/Minecraft-Turtles/main/vec3.lua", "vec3.lua") end
  7. if not fs.exists("json.lua") then shell.run("wget", "https://raw.githubusercontent.com/Dimencia/Minecraft-Turtles/main/dkjson.lua", "json.lua") end
  8. if not fs.exists("heap.lua") then shell.run("wget", "https://gist.githubusercontent.com/H2NCH2COOH/1f929775db0a355ca6b6088a4662fe95/raw/1ccc4fc1d99ee6943fc66475f3feac6de8c83c31/heap.lua", "heap.lua") end
  9.  
  10. vec3 = require("vec3")
  11. json = require("json")
  12. minheap = require("heap")
  13.  
  14. logFile = fs.open("Logfile", "w")
  15.  
  16. -- First, check if we've already overwritten these funcs
  17.  
  18.  
  19. function newPrint(...)
  20. local result = ""
  21. for k,v in pairs(arg) do
  22. if k ~= "n" then
  23. result = result .. getDisplayString(v) .. " "
  24. end
  25. end
  26. oldPrint(result)
  27. logFile.writeLine(result)
  28. logFile.flush()
  29. end
  30. --if not turtle.methodsOverwritten then -- Unsure if turtle.methodsOverwritten doesn't persist, or if print resets itself, something is wrong
  31. oldPrint = print -- If you run it, then terminate and run it again, it didn't log anymore
  32. print = newPrint
  33. --end
  34.  
  35. if turtle.methodsOverwritten then
  36. print("Methods already overwritten, skipping them")
  37. end
  38.  
  39.  
  40. function getDisplayString(object)
  41. local result = ""
  42. if type(object) == "string" then
  43. result = result .. object
  44. elseif type(object) == "table" then
  45. if object.x then -- IDK how else to make sure it's a vec3
  46. result = result .. vectorToString(object)
  47. else
  48. for k,v in pairs(object) do
  49. result = result .. getDisplayString(k) .. ":" .. getDisplayString(v) .. " "
  50. end
  51. end
  52. elseif type(object) == "boolean" then
  53. if object then result = result .. "true" else result = result .. "false" end
  54. elseif object ~= nil then
  55. result = result .. object
  56. else
  57. result = result .. "nil"
  58. end
  59. return result
  60. end
  61.  
  62. occupiedPositions = {} -- The key is the vec3, and the value is true if occupied, or nil/false if not
  63. local initialOrientation = vec3(1,0,0)
  64. local initialPosition = vec3(0,0,0)
  65.  
  66. orientations = { vec3(1,0,0),
  67. vec3(0,0,1),
  68. vec3(-1,0,0),
  69. vec3(0,0,-1)} -- Where going higher in the list is turning right
  70. orientationIndex = 1
  71.  
  72. directions = { "east","south","west","north"} -- In the same order as orientations so orientationIndex can still be used
  73. -- This could get weird because the occupiedPositions might be in reference to the wrong x or z sign
  74. -- So for example, we were pointed north, but gave it the vector for east
  75. -- So when I went 'negative x' in the relative implementation, I was really going positive z
  76. -- so anything with coords of like, 10,0,-2 , is actually -2,0,-10
  77. -- Which, seems like I can do newOrientation-oldOrientation and multiply all coords by that
  78. -- But, that fails when it was an opposite direction.
  79. -- Which we can test for but it's weird that there's an edge case, is there not a better way? Is this wrong in other cases?
  80. -- I mean it's only 3 cases. Say I was pointed south instead, then my 'negative x' was negative z, but my positive x should be backwards and isn't
  81.  
  82. -- So what are our cases?
  83. -- If it went from north to south, reverse all X and Z
  84. -- If it went from east to west, reverse all X and Z...
  85. -- If it went from initialOrientation of east, and they tell us that's actually north, swap Z and X, and negate Z
  86. -- If it went from east to south, swap Z and X
  87. -- This is hard. Do it later.
  88.  
  89. adjacentVectors = { vec3(1,0,0),
  90. vec3(0,1,0),
  91. vec3(0,0,1),
  92. vec3(-1,0,0),
  93. vec3(0,-1,0),
  94. vec3(0,0,-1)} -- When looking for adjacents, we can iterate over this and add it to the position
  95.  
  96. turtle.orientation = initialOrientation
  97. turtle.position = initialPosition
  98. turtle.home = initialPosition
  99.  
  100. function vectorToString(vec)
  101. return vec.x .. "," .. vec.y .. "," .. vec.z
  102. end
  103.  
  104. function SaveData()
  105. -- Updates our datafile with the turtle's position, orientation, and occupiedPositions (and maybe more later)
  106. local dataFile = fs.open("PathData", "w")
  107. local allData = {position=turtle.position, orientation=turtle.orientation, occupiedPositions=occupiedPositions, home=turtle.home}
  108. local dataString = json.encode(allData)
  109. dataFile.write(dataString)
  110. dataFile.flush()
  111. dataFile.close()
  112. end
  113.  
  114. function LoadData()
  115. local f = fs.open("PathData", "r")
  116. local allData = json.decode(f.readAll())
  117. if allData and allData.position and allData.orientation and allData.occupiedPositions then
  118. turtle.position = vec3(allData.position)
  119. turtle.orientation = vec3(allData.orientation)
  120. for k,v in ipairs(orientations) do
  121. if vectorToString(v) == vectorToString(turtle.orientation) then
  122. orientationIndex = k
  123. break
  124. end
  125. end
  126. if allData.home then
  127. turtle.home = vec3(allData.home)
  128. end
  129. occupiedPositions = allData.occupiedPositions
  130. end
  131. f.close()
  132. end
  133.  
  134. function stringSplit (inputstr, sep)
  135. if sep == nil then
  136. sep = "%s"
  137. end
  138. local t={}
  139. for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
  140. table.insert(t, str)
  141. end
  142. return t
  143. end
  144.  
  145. function listLen(list)
  146. local count = 0
  147. for k,v in pairs(list) do
  148. if v ~= nil then count = count + 1 end
  149. end
  150. return count
  151. end
  152.  
  153. if fs.exists("PathData") then
  154. LoadData() -- Load before opening our write handle, which will erase everything
  155. end
  156.  
  157. SaveData() -- Make sure it's not empty if we don't make it to the next tick
  158.  
  159. if not turtle.methodsOverwritten then
  160. baseDig = turtle.dig
  161. turtle.dig = function() -- We may have to pause a tick to wait for gravel to fall...
  162. baseDig()
  163. detectBlocks() -- Check all occupied things after we dig
  164. end
  165.  
  166. baseForward = turtle.forward
  167. turtle.forward = function()
  168. detectBlocks()
  169. if baseForward() then
  170. local newPosition = turtle.position + turtle.orientation
  171. print("Moved forward from " .. vectorToString(turtle.position) .. " to " .. vectorToString(newPosition))
  172. turtle.position = newPosition
  173. detectBlocks()
  174. return true
  175. end
  176. return false
  177. end
  178.  
  179. baseUp = turtle.up
  180. turtle.up = function()
  181. detectBlocks()
  182. if baseUp() then
  183. local newPosition = turtle.position + vec3(0,1,0)
  184. print("Moved up from " .. vectorToString(turtle.position) .. " to " .. vectorToString(newPosition))
  185. turtle.position = newPosition
  186. detectBlocks()
  187. return true
  188. end
  189. return false
  190. end
  191.  
  192. baseDown = turtle.down
  193. turtle.down = function()
  194. detectBlocks()
  195. if baseDown() then
  196. local newPosition = turtle.position + vec3(0,-1,0)
  197. print("Moved down from " .. vectorToString(turtle.position) .. " to " .. vectorToString(newPosition))
  198. turtle.position = newPosition
  199. detectBlocks()
  200. return true
  201. end
  202. return false
  203. end
  204.  
  205. baseTurnLeft = turtle.turnLeft
  206. turtle.turnLeft = function()
  207. baseTurnLeft()
  208. local oldOrientation = turtle.orientation:clone()
  209. updateTurtleOrientationLeft()
  210. print("Turned left from " .. vectorToString(oldOrientation) .. " to " .. vectorToString(turtle.orientation))
  211. detectBlocks()
  212. end
  213.  
  214. baseTurnRight = turtle.turnRight
  215. turtle.turnRight = function()
  216. baseTurnRight()
  217. local oldOrientation = turtle.orientation:clone()
  218. updateTurtleOrientationRight()
  219. print("Turned right from " .. vectorToString(oldOrientation) .. " to " .. vectorToString(turtle.orientation))
  220. detectBlocks()
  221. end
  222. end
  223. turtle.methodsOverwritten = true
  224.  
  225.  
  226. function updateTurtleOrientationLeft()
  227.  
  228. orientationIndex = orientationIndex-1
  229. if orientationIndex < 1 then
  230. orientationIndex = #orientations
  231. end
  232. turtle.orientation = orientations[orientationIndex]
  233. end
  234.  
  235. function updateTurtleOrientationRight()
  236. orientationIndex = orientationIndex+1
  237. if orientationIndex > #orientations then
  238. orientationIndex = 1
  239. end
  240. turtle.orientation = orientations[orientationIndex]
  241. end
  242.  
  243.  
  244. --
  245. -- Pathfinding Stuff Below
  246. --
  247.  
  248. function turnToAdjacent(adjacentPosition) -- Only use on adjacent ones...
  249. print("Calculating turn from " .. vectorToString(turtle.position) .. " to " .. vectorToString(adjacentPosition))
  250. local newOrientation = adjacentPosition-turtle.position
  251. newOrientation.y = 0
  252. -- Now determine how to get from current, to here
  253. -- First, if it was y only, we're done
  254. if newOrientation == vec3() or newOrientation == turtle.orientation then return true end
  255.  
  256. -- Then iteration through orientations forward, if it's <=2 to the target we can go right, otherwise left
  257. for i=1,4 do
  258. local t = orientationIndex + i
  259. if t > #orientations then t = t - #orientations end
  260. if orientations[t] == newOrientation then
  261. if i < 2 then
  262. turtle.turnRight()
  263. return true
  264. elseif i == 2 then
  265. turtle.turnRight()
  266. turtle.turnRight()
  267. return true
  268. else
  269. turtle.turnLeft()
  270. return true
  271. end
  272. end
  273. end
  274. return false
  275. end
  276.  
  277. function detectBlocks()
  278. -- Detects all blocks and stores the data
  279. occupiedPositions[vectorToString(turtle.position+turtle.orientation)] = turtle.detect()
  280. occupiedPositions[vectorToString(turtle.position+vec3(0,1,0))] = turtle.detectUp()
  281. occupiedPositions[vectorToString(turtle.position+vec3(0,-1,0))] = turtle.detectDown()
  282. SaveData()
  283. end
  284.  
  285. function ComputeSquare(aSquare, currentSquare, targetPosition)
  286. aSquare.parent = currentSquare
  287. aSquare.G = currentSquare.G+1
  288. aSquare.H = (targetPosition-aSquare.position):len()*1.5
  289. aSquare.score = aSquare.G + aSquare.H
  290. end
  291.  
  292. function getAdjacentWalkableSquares(currentSquare)
  293. local results = {}
  294. for k,v in pairs(adjacentVectors) do
  295. local targetVec = currentSquare.position + v
  296. if not occupiedPositions[vectorToString(targetVec)] then -- I am unsure that this works, at least not reliably, it's weird
  297. results[targetVec] = {position=targetVec}
  298. end
  299. end
  300. return results
  301. end
  302.  
  303.  
  304. function GetPath(targetPosition)
  305. print("Getting path for turtle position " .. vectorToString(turtle.position))
  306. local currentSquare = {position=turtle.position,G=0,H=(targetPosition-turtle.position):len()*1.5}
  307. currentSquare.score = currentSquare.G + currentSquare.H -- Manually set these first, the rest rely on a parent
  308.  
  309. local openList = { } -- I guess this is a generic object, which has fields .position
  310. openList[vectorToString(currentSquare.position)] = currentSquare -- This makes it easier to add/remove
  311. local openHeap = minheap.new()
  312. openHeap:push(currentSquare,currentSquare.score)
  313. -- Suppose they also have a .score, .G, and .H, and .parent
  314. local closedList = {}
  315.  
  316. local tickCount = 1
  317.  
  318. local finalMove = nil
  319. repeat
  320. -- Get the square with the lowest score
  321. local currentSquare = openHeap:pop()
  322.  
  323. -- Add this to the closed list, no longer consider it for future moves
  324. closedList[vectorToString(currentSquare.position)] = true
  325. openList[vectorToString(currentSquare.position)] = nil -- Remove from open list
  326.  
  327. if currentSquare.position == targetPosition then
  328. -- We found the path target and put it in the list, we're done.
  329. finalMove = currentSquare
  330. break
  331. end
  332.  
  333. local adjacentSquares = getAdjacentWalkableSquares(currentSquare) -- Should never return occupied squares
  334. -- Returns us a list where the keys are positions, and values just have a position field. We add more fields to the values
  335. for pos,aSquare in pairs(adjacentSquares) do
  336. if not closedList[vectorToString(pos)] then -- Using vectors as keys doesn't work right, have to convert to string
  337. if not openList[vectorToString(pos)] then
  338. -- Compute G, H, and F, and set them on the square
  339. ComputeSquare(aSquare, currentSquare, targetPosition)
  340. -- Add for consideration in next step
  341. openList[vectorToString(pos)] = aSquare
  342. openHeap:push(aSquare,aSquare.score)
  343. elseif openList[vectorToString(pos)] then -- aSquare is already in the list, so it already has these params
  344. aSquare = openList[vectorToString(pos)] -- Use the existing object
  345. if currentSquare.G+1 < aSquare.G then
  346. -- Our path to aSquare is shorter, use our values, replaced into the object - which is already in the heap and list
  347. ComputeSquare(aSquare, currentSquare, targetPosition)
  348. end
  349. end
  350. end
  351. end
  352. tickCount = tickCount + 1
  353. if tickCount % 1000 == 0 then
  354. print("Checking 1000th position " .. vectorToString(currentSquare.position) .. " with score " .. currentSquare.score)
  355. sleep(0.1)
  356. end
  357.  
  358. until listLen(openList) == 0 or currentSquare.score > (currentSquare.position-targetPosition):len()*32
  359. -- We'll go up to 32 blocks out of the way, per 1 block away in straight-line space
  360.  
  361.  
  362. local curSquare = finalMove -- We set this above when we found it, start at the end
  363. -- Each one gets inserted in front of the previous one
  364. local finalMoves = {}
  365. while curSquare ~= nil do
  366. table.insert(finalMoves, 1, curSquare)
  367. curSquare = curSquare.parent
  368. end
  369. return finalMoves
  370. end
  371.  
  372. function followPath(moveList)
  373. for k,v in ipairs(moveList) do
  374. print("Performing move to adjacent square from " .. vectorToString(turtle.position) .. " to " .. vectorToString(v.position))
  375. local targetVector = v.position - turtle.position
  376. local success
  377. if v.position ~= turtle.position then
  378. if targetVector.y ~= 0 then
  379. -- Just go up or down
  380. if targetVector.y > 0 then
  381. success = turtle.up()
  382. if not success then occupiedPositions[vectorToString(v.position)] = true end
  383. else
  384. success = turtle.down()
  385. if not success then occupiedPositions[vectorToString(v.position)] = true end
  386. end
  387. else
  388. turnToAdjacent(v.position)
  389. success = turtle.forward()
  390. if not success then occupiedPositions[vectorToString(v.position)] = true end
  391. end
  392.  
  393. if not success then -- We were blocked for some reason, re-pathfind
  394. -- Find the target...
  395. print("Obstacle detected, calculating and following new path")
  396. --print("Occupied Positions: ", occupiedPositions)
  397. -- SO, this is really weird and really annoying.
  398. -- If this happens, we seem to often path back to the same spot, even though it's occupied
  399. -- But only sometimes, not always, it's wild.
  400. local lastTarget = nil
  401. for k2, v2 in ipairs(moveList) do
  402. lastTarget = v2
  403. end
  404. local newPath = GetPath(lastTarget.position)
  405. followPath(newPath)
  406. return
  407. end
  408. end
  409. end
  410. print("Path successfully followed, final position: " .. vectorToString(turtle.position))
  411. end
  412.  
  413. local arg = {...}
  414.  
  415.  
  416.  
  417. if arg[1] then
  418. print(arg)
  419. if arg[1] == "reset" then
  420. turtle.position = vec3()
  421. turtle.orientation = initialOrientation
  422. turtle.orientationIndex = 1
  423. SaveData()
  424. elseif string.lower(arg[1]) == "setgps" and arg[2] then
  425. -- This is used to input the GPS position that the bot's start position is at
  426. -- Will then convert all occupiedPositions to match this new GPS position, so the data is globally usable
  427. -- Also simplifies entering waypoints and etc
  428. -- Second argument should be formatted as "x,y,z"
  429. -- Third argument is optionally, "north","south","east","west" to specify its starting direction
  430. local newPos = vec3(stringSplit(arg[2],","))
  431. if fs.exists("PathData.bak") then
  432. shell.run("delete","PathData.bak")
  433. end
  434. shell.run("copy","PathData","PathData.bak")
  435.  
  436. local newOP = {}
  437.  
  438. for k,v in pairs(occupiedPositions) do
  439. -- k is the string vector, v is a boolean
  440. -- Which is unfortunate when it comes time to edit them, but okay
  441. local vec = vec3(stringSplit(k,","))
  442. vec = vec + newPos
  443. newOP[vectorToString(vec)] = v
  444. end
  445. turtle.position = newPos
  446. if arg[3] then
  447. -- TODO: Do this later. We might have to move everything more based on this, if we do it
  448. -- Or just, leave it all relative to the orientation it started in, and rely on them to fix orientation when resetting
  449. end
  450. occupiedPositions = newOP
  451. SaveData()
  452. print("Positions updated to world positions")
  453. end
  454. end
  455.  
  456. -- K after this is whatever we want it to do...
  457.  
  458. -- Alright, let's call this a training routine.
  459. -- It should start facing the 'home' chest, which contains coal or fuel, and that's 0,0,0
  460.  
  461. -- Note that the chest ends up being 1,0,0, when we want to turn to face it while standing at 0,0,0
  462. repeat
  463. if turtle.position ~= turtle.home then
  464. print("Returning to base")
  465. local path = GetPath(turtle.home)
  466. followPath(path)
  467. else
  468. turnToAdjacent(turtle.home+initialOrientation)
  469. turtle.select(1)
  470. turtle.suck()
  471. turtle.refuel()
  472. -- Generate some random coords. Stay within 16 or so blocks on each to keep it somewhat reasonable
  473. local target
  474. repeat
  475. target = turtle.home+vec3(math.random(-16,16),math.random(0,16),math.random(-16,16))
  476. until not occupiedPositions[vectorToString(target)]
  477. print("Getting path to target")
  478. local path = GetPath(target)
  479. followPath(path)
  480. end
  481. until 1 == 0
Advertisement
Add Comment
Please, Sign In to add comment