Dimencia

Untitled

Mar 24th, 2021
230
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.68 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.  
  99. function vectorToString(vec)
  100. return vec.x .. "," .. vec.y .. "," .. vec.z
  101. end
  102.  
  103. function SaveData()
  104. -- Updates our datafile with the turtle's position, orientation, and occupiedPositions (and maybe more later)
  105. local dataFile = fs.open("PathData", "w")
  106. local allData = {position=turtle.position, orientation=turtle.orientation, occupiedPositions=occupiedPositions}
  107. local dataString = json.encode(allData)
  108. dataFile.write(dataString)
  109. dataFile.flush()
  110. dataFile.close()
  111. end
  112.  
  113. function LoadData()
  114. local f = fs.open("PathData", "r")
  115. local allData = json.decode(f.readAll())
  116. if allData and allData.position and allData.orientation and allData.occupiedPositions then
  117. turtle.position = vec3(allData.position)
  118. turtle.orientation = vec3(allData.orientation)
  119. for k,v in ipairs(orientations) do
  120. if vectorToString(v) == vectorToString(turtle.orientation) then
  121. orientationIndex = k
  122. break
  123. end
  124. end
  125.  
  126. occupiedPositions = allData.occupiedPositions
  127. end
  128. f.close()
  129. end
  130.  
  131. function stringSplit (inputstr, sep)
  132. if sep == nil then
  133. sep = "%s"
  134. end
  135. local t={}
  136. for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
  137. table.insert(t, str)
  138. end
  139. return t
  140. end
  141.  
  142. function listLen(list)
  143. local count = 0
  144. for k,v in pairs(list) do
  145. if v ~= nil then count = count + 1 end
  146. end
  147. return count
  148. end
  149.  
  150. if fs.exists("PathData") then
  151. LoadData() -- Load before opening our write handle, which will erase everything
  152. end
  153.  
  154. SaveData() -- Make sure it's not empty if we don't make it to the next tick
  155.  
  156. if not turtle.methodsOverwritten then
  157. baseDig = turtle.dig
  158. turtle.dig = function() -- We may have to pause a tick to wait for gravel to fall...
  159. baseDig()
  160. detectBlocks() -- Check all occupied things after we dig
  161. end
  162.  
  163. baseForward = turtle.forward
  164. turtle.forward = function()
  165. detectBlocks()
  166. if baseForward() then
  167. local newPosition = turtle.position + turtle.orientation
  168. print("Moved forward from " .. vectorToString(turtle.position) .. " to " .. vectorToString(newPosition))
  169. turtle.position = newPosition
  170. detectBlocks()
  171. return true
  172. end
  173. return false
  174. end
  175.  
  176. baseUp = turtle.up
  177. turtle.up = function()
  178. detectBlocks()
  179. if baseUp() then
  180. local newPosition = turtle.position + vec3(0,1,0)
  181. print("Moved up from " .. vectorToString(turtle.position) .. " to " .. vectorToString(newPosition))
  182. turtle.position = newPosition
  183. detectBlocks()
  184. return true
  185. end
  186. return false
  187. end
  188.  
  189. baseDown = turtle.down
  190. turtle.down = function()
  191. detectBlocks()
  192. if baseDown() then
  193. local newPosition = turtle.position + vec3(0,-1,0)
  194. print("Moved down from " .. vectorToString(turtle.position) .. " to " .. vectorToString(newPosition))
  195. turtle.position = newPosition
  196. detectBlocks()
  197. return true
  198. end
  199. return false
  200. end
  201.  
  202. baseTurnLeft = turtle.turnLeft
  203. turtle.turnLeft = function()
  204. baseTurnLeft()
  205. local oldOrientation = turtle.orientation:clone()
  206. updateTurtleOrientationLeft()
  207. print("Turned left from " .. vectorToString(oldOrientation) .. " to " .. vectorToString(turtle.orientation))
  208. detectBlocks()
  209. end
  210.  
  211. baseTurnRight = turtle.turnRight
  212. turtle.turnRight = function()
  213. baseTurnRight()
  214. local oldOrientation = turtle.orientation:clone()
  215. updateTurtleOrientationRight()
  216. print("Turned right from " .. vectorToString(oldOrientation) .. " to " .. vectorToString(turtle.orientation))
  217. detectBlocks()
  218. end
  219. end
  220. turtle.methodsOverwritten = true
  221.  
  222.  
  223. function updateTurtleOrientationLeft()
  224.  
  225. orientationIndex = orientationIndex-1
  226. if orientationIndex < 1 then
  227. orientationIndex = #orientations
  228. end
  229. turtle.orientation = orientations[orientationIndex]
  230. end
  231.  
  232. function updateTurtleOrientationRight()
  233. orientationIndex = orientationIndex+1
  234. if orientationIndex > #orientations then
  235. orientationIndex = 1
  236. end
  237. turtle.orientation = orientations[orientationIndex]
  238. end
  239.  
  240.  
  241. --
  242. -- Pathfinding Stuff Below
  243. --
  244.  
  245. function turnToAdjacent(adjacentPosition) -- Only use on adjacent ones...
  246. print("Calculating turn from " .. vectorToString(turtle.position) .. " to " .. vectorToString(adjacentPosition))
  247. local newOrientation = adjacentPosition-turtle.position
  248. newOrientation.y = 0
  249. -- Now determine how to get from current, to here
  250. -- First, if it was y only, we're done
  251. if newOrientation == vec3() or newOrientation == turtle.orientation then return true end
  252.  
  253. -- Then iteration through orientations forward, if it's <=2 to the target we can go right, otherwise left
  254. for i=1,4 do
  255. local t = orientationIndex + i
  256. if t > #orientations then t = t - #orientations end
  257. if orientations[t] == newOrientation then
  258. if i < 2 then
  259. turtle.turnRight()
  260. return true
  261. elseif i == 2 then
  262. turtle.turnRight()
  263. turtle.turnRight()
  264. return true
  265. else
  266. turtle.turnLeft()
  267. return true
  268. end
  269. end
  270. end
  271. return false
  272. end
  273.  
  274. function detectBlocks()
  275. -- Detects all blocks and stores the data
  276. occupiedPositions[vectorToString(turtle.position+turtle.orientation)] = turtle.detect()
  277. occupiedPositions[vectorToString(turtle.position+vec3(0,1,0))] = turtle.detectUp()
  278. occupiedPositions[vectorToString(turtle.position+vec3(0,-1,0))] = turtle.detectDown()
  279. SaveData()
  280. end
  281.  
  282. function ComputeSquare(aSquare, currentSquare, targetPosition)
  283. aSquare.parent = currentSquare
  284. aSquare.G = currentSquare.G+1
  285. aSquare.H = (targetPosition-aSquare.position):len()*1.5
  286. aSquare.score = aSquare.G + aSquare.H
  287. end
  288.  
  289. function getAdjacentWalkableSquares(currentSquare)
  290. local results = {}
  291. for k,v in pairs(adjacentVectors) do
  292. local targetVec = currentSquare.position + v
  293. if not occupiedPositions[vectorToString(targetVec)] then -- I am unsure that this works, at least not reliably, it's weird
  294. results[targetVec] = {position=targetVec}
  295. end
  296. end
  297. return results
  298. end
  299.  
  300.  
  301. function GetPath(targetPosition)
  302. print("Getting path for turtle position " .. vectorToString(turtle.position))
  303. local currentSquare = {position=turtle.position,G=0,H=(targetPosition-turtle.position):len()*1.5}
  304. currentSquare.score = currentSquare.G + currentSquare.H -- Manually set these first, the rest rely on a parent
  305.  
  306. local openList = { } -- I guess this is a generic object, which has fields .position
  307. openList[vectorToString(currentSquare.position)] = currentSquare -- This makes it easier to add/remove
  308. local openHeap = minheap.new()
  309. openHeap:push(currentSquare,currentSquare.score)
  310. -- Suppose they also have a .score, .G, and .H, and .parent
  311. local closedList = {}
  312.  
  313. local tickCount = 1
  314.  
  315. local finalMove = nil
  316. repeat
  317. -- Get the square with the lowest score
  318. local currentSquare = openHeap:pop()
  319.  
  320. -- Add this to the closed list, no longer consider it for future moves
  321. closedList[vectorToString(currentSquare.position)] = true
  322. openList[vectorToString(currentSquare.position)] = nil -- Remove from open list
  323.  
  324. if currentSquare.position == targetPosition then
  325. -- We found the path target and put it in the list, we're done.
  326. finalMove = currentSquare
  327. break
  328. end
  329.  
  330. local adjacentSquares = getAdjacentWalkableSquares(currentSquare) -- Should never return occupied squares
  331. -- Returns us a list where the keys are positions, and values just have a position field. We add more fields to the values
  332. for pos,aSquare in pairs(adjacentSquares) do
  333. if not closedList[vectorToString(pos)] then -- Using vectors as keys doesn't work right, have to convert to string
  334. if not openList[vectorToString(pos)] then
  335. -- Compute G, H, and F, and set them on the square
  336. ComputeSquare(aSquare, currentSquare, targetPosition)
  337. -- Add for consideration in next step
  338. openList[vectorToString(pos)] = aSquare
  339. openHeap:push(aSquare,aSquare.score)
  340. elseif openList[vectorToString(pos)] then -- aSquare is already in the list, so it already has these params
  341. aSquare = openList[vectorToString(pos)] -- Use the existing object
  342. if currentSquare.G+1 < aSquare.G then
  343. -- Our path to aSquare is shorter, use our values, replaced into the object - which is already in the heap and list
  344. ComputeSquare(aSquare, currentSquare, targetPosition)
  345. end
  346. end
  347. end
  348. end
  349. tickCount = tickCount + 1
  350. if tickCount % 1000 == 0 then
  351. print("Checking 1000th position " .. vectorToString(currentSquare.position) .. " with score " .. currentSquare.score)
  352. sleep(0.1)
  353. end
  354.  
  355. until listLen(openList) == 0 or currentSquare.score > (currentSquare.position-targetPosition):len()*32
  356. -- We'll go up to 32 blocks out of the way, per 1 block away in straight-line space
  357.  
  358.  
  359. local curSquare = finalMove -- We set this above when we found it, start at the end
  360. -- Each one gets inserted in front of the previous one
  361. local finalMoves = {}
  362. while curSquare ~= nil do
  363. table.insert(finalMoves, 1, curSquare)
  364. curSquare = curSquare.parent
  365. end
  366. return finalMoves
  367. end
  368.  
  369. function followPath(moveList)
  370. for k,v in ipairs(moveList) do
  371. print("Performing move to adjacent square from " .. vectorToString(turtle.position) .. " to " .. vectorToString(v.position))
  372. local targetVector = v.position - turtle.position
  373. local success
  374. if v.position ~= turtle.position then
  375. if targetVector.y ~= 0 then
  376. -- Just go up or down
  377. if targetVector.y > 0 then
  378. success = turtle.up()
  379. if not success then occupiedPositions[vectorToString(v.position)] = true end
  380. else
  381. success = turtle.down()
  382. if not success then occupiedPositions[vectorToString(v.position)] = true end
  383. end
  384. else
  385. turnToAdjacent(v.position)
  386. success = turtle.forward()
  387. if not success then occupiedPositions[vectorToString(v.position)] = true end
  388. end
  389.  
  390. if not success then -- We were blocked for some reason, re-pathfind
  391. -- Find the target...
  392. print("Obstacle detected, calculating and following new path")
  393. --print("Occupied Positions: ", occupiedPositions)
  394. -- SO, this is really weird and really annoying.
  395. -- If this happens, we seem to often path back to the same spot, even though it's occupied
  396. -- But only sometimes, not always, it's wild.
  397. local lastTarget = nil
  398. for k2, v2 in ipairs(moveList) do
  399. lastTarget = v2
  400. end
  401. local newPath = GetPath(lastTarget.position)
  402. followPath(newPath)
  403. return
  404. end
  405. end
  406. end
  407. print("Path successfully followed, final position: " .. vectorToString(turtle.position))
  408. end
  409.  
  410. local arg = {...}
  411.  
  412.  
  413.  
  414. if arg["n"] > 0 then
  415. print(arg)
  416. if arg[1] == "reset" then
  417. turtle.position = vec3()
  418. turtle.orientation = initialOrientation
  419. turtle.orientationIndex = 1
  420. SaveData()
  421. elseif string.lower(arg[1]) == "setgps" and arg[2] then
  422. -- This is used to input the GPS position that the bot's start position is at
  423. -- Will then convert all occupiedPositions to match this new GPS position, so the data is globally usable
  424. -- Also simplifies entering waypoints and etc
  425. -- Second argument should be formatted as "x,y,z"
  426. -- Third argument is optionally, "north","south","east","west" to specify its starting direction
  427. local newPos = vec3(stringSplit(arg[2],","))
  428. if fs.exists("PathData.bak") then
  429. shell.run("delete","PathData.bak")
  430. end
  431. shell.run("copy","PathData","PathData.bak")
  432.  
  433. local newOP = {}
  434.  
  435. for k,v in pairs(occupiedPositions) do
  436. -- k is the string vector, v is a boolean
  437. -- Which is unfortunate when it comes time to edit them, but okay
  438. local vec = vec3(stringSplit(k,","))
  439. vec = vec + newPos
  440. newOP[vectorToString(vec)] = v
  441. end
  442. turtle.position = newPos
  443. if arg[3] then
  444. -- TODO: Do this later. We might have to move everything more based on this, if we do it
  445. -- Or just, leave it all relative to the orientation it started in, and rely on them to fix orientation when resetting
  446. end
  447. occupiedPositions = newOP
  448. SaveData()
  449. print("Positions updated to world positions")
  450. end
  451. end
  452.  
  453. -- K after this is whatever we want it to do...
  454.  
  455. -- Alright, let's call this a training routine.
  456. -- It should start facing the 'home' chest, which contains coal or fuel, and that's 0,0,0
  457.  
  458. -- Note that the chest ends up being 1,0,0, when we want to turn to face it while standing at 0,0,0
  459. repeat
  460. turnToAdjacent(vec3(1,0,0))
  461. turtle.select(1)
  462. turtle.suck()
  463. turtle.refuel()
  464. -- Generate some random coords. Stay within 16 or so blocks on each to keep it somewhat reasonable
  465. local target
  466. repeat
  467. target = vec3(math.random(-16,16),math.random(0,16),math.random(-16,16))
  468. until not occupiedPositions[vectorToString(target)]
  469. print("Getting path to target")
  470. local path = GetPath(target)
  471. followPath(path)
  472. print("Returning to base")
  473. target = vec3()
  474. path = GetPath(target)
  475. followPath(path)
  476. until 1 == 0
Advertisement
Add Comment
Please, Sign In to add comment