Advertisement
sneakybeakylike

CCTweaked Graph: unweighted, undirected

May 17th, 2024 (edited)
508
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.69 KB | None | 0 0
  1. turtle_direction = nil
  2. -- helper function to print date and time to a file
  3. -- format: "[mm-dd-yyyy | hr:min:sec] "
  4. output_file = "graph_error_log.txt"
  5. local function write_date_time(file_name)
  6.     local file = io.open(file_name, "a")
  7.     -- Dividing by 1000 converts it from milliseconds to seconds.
  8.     --local time = os.epoch("local") / 1000
  9.     --local time_table = os.date("*t", time)
  10.     --file:write("[" .. time_table.month .. "-" .. time_table.day .. "-" .. time_table.year .. " | ")
  11.     --file:write(time_table.hour .. ":" .. time_table.minute .. ":" .. time_table.sec .. "] ")
  12.     file:write("[DAY " .. os.day("ingame") .. "] ")
  13.     file:close()
  14. end
  15. -- initialize Graph metatable (sort of a class)
  16. -- Graph is undirected and unweighted using an adjacency list.
  17. -- vertices store (x, y, z) coordinates and block data
  18. -- block data is in the format of a table returned by the turtle.inspect() function
  19. Graph = {}
  20.  
  21. -- below are some Graph functions
  22. -- graph initialization function
  23. function Graph:initialize_graph()
  24.     self.vertices = {}
  25. end
  26. -- given x, y, z coordinates returns formatted string for vertex key
  27. local function create_vertex_key(x, y, z)
  28.     return string.format("%d,%d,%d", x, y, z)
  29. end
  30. -- adds a vertex given x, y, z coordinates and blockdata
  31. -- returns true if worked, nil otherwise
  32. function Graph:add_vertex(x, y, z, data)
  33.     local key = create_vertex_key(x, y, z)
  34.     if not self.vertices[key] then
  35.         self.vertices[key] = {
  36.             x = x,
  37.             y = y,
  38.             z = z,
  39.             data = data,
  40.             edges = {}
  41.         }
  42.         return true
  43.     else
  44.         write_date_time(output_file)
  45.         local file = io.open(output_file, "a")
  46.         file:write("Attemped to add already existing vertex: (" .. x .. ", " .. y .. ", " .. z .. ")\n")
  47.         file:write("-> existing block: {" .. self.vertices[key].data.name .. "}\n")
  48.         file:write("-> new? block: {" .. data.name .. "}\n")
  49.         file:close()
  50.         return nil
  51.     end
  52. end
  53. -- removes a vertex given x, y, z coordinates
  54. -- returns true if worked, nil otherwise
  55. function Graph:remove_vertex(x, y, z)
  56.     local key = create_vertex_key(x, y, z)
  57.     if self.vertices[key] then
  58.         -- remove edges connected to this vertex
  59.         for adjacent in pairs(self.vertices[key].edges) do
  60.             self.vertices[adjacent].edges[key] = nil
  61.         end
  62.         -- now remove self from graph's vertices list
  63.         self.vertices[key] = nil
  64.         return true
  65.     else
  66.         write_date_time(output_file)
  67.         local file = io.open(output_file, "a")
  68.         file:write("Attemped to remove vertex that does not exist: (" .. key .. ")\n")
  69.         file:close()
  70.         return nil
  71.     end
  72. end
  73. -- add an edge given two sets of (x, y, z) coordinates
  74. -- returns true if worked, nil otherwise
  75. function Graph:add_edge(x1, y1, z1, x2, y2, z2)
  76.     local key1 = create_vertex_key(x1, y1, z1)
  77.     local key2 = create_vertex_key(x2, y2, z2)
  78.     if self.vertices[key1] and self.vertices[key2] then
  79.         self.vertices[key1].edges[key2] = true
  80.         self.vertices[key2].edges[key1] = true
  81.         return true
  82.     else
  83.         -- one or both of the vertices does not exist
  84.         write_date_time(output_file)
  85.         local file = io.open(output_file, "a")
  86.         file:write("Attemped to add edge between two vertices. One or both does not exist\n")
  87.         file:write("-> key1: (" .. key1 .. ")\n")
  88.         file:write("-> key2: (" .. key2 .. ")\n")
  89.         if not self.vertices[key1] then
  90.             file:write("-> key1 does not exist\n")
  91.         end
  92.         if not self.vertices[key2] then
  93.             file:write("-> key2 does not exist\n")
  94.         end
  95.         file:close()
  96.         return nil
  97.     end
  98. end
  99. -- removes an edge given two sets of (x, y, z) coordinates
  100. -- returns true if worked, nil otherwise
  101. function Graph:remove_edge(x1, y1, z1, x2, y2, z2)
  102.     local key1 = create_vertex_key(x1, y1, z1)
  103.     local key2 = create_vertex_key(x2, y2, z2)
  104.     if self.vertices[key1] and self.vertices[key2] then
  105.         self.vertices[key1].edges[key2] = nil
  106.         self.vertices[key2].edges[key1] = nil
  107.         return true
  108.     else
  109.         -- one or both of the vertices does not exist
  110.         write_date_time(output_file)
  111.         local file = io.open(output_file, "a")
  112.         file:write("Attemped to remove edge between two vertices. One or both does not exist\n")
  113.         file:write("-> key1: (" .. key1 .. ")\n")
  114.         file:write("-> key2: (" .. key2 .. ")\n")
  115.         if not self.vertices[key1] then
  116.             file:write("-> key1 does not exist\n")
  117.         end
  118.         if not self.vertices[key2] then
  119.             file:write("-> key2 does not exist\n")
  120.         end
  121.         file:close()
  122.         return nil
  123.     end
  124. end
  125. -- estimate distance between two points using manhattan distance
  126. -- used for the a* algorithm
  127. local function manhattan_distance(x1, y1, z1, x2, y2, z2)
  128.     return math.abs(x2 - x1) + math.abs(y2 - y1) + math.abs(z2 - z1)
  129. end
  130. function Graph:a_star(start_x, start_y, start_z, goal_x, goal_y, goal_z)
  131.     local start_key = create_vertex_key(start_x, start_y, start_z)
  132.     local goal_key = create_vertex_key(goal_x, goal_y, goal_z)
  133.    
  134.     -- check to make sure start and goal vertices exist
  135.     if not self.vertices[start_key] or self.vertices[goal_key] then
  136.         write_date_time(output_file)
  137.         local file = io.open(output_file, "a")
  138.         file:write("Attemped to find distance between two vertices. One or both does not exist\n")
  139.         file:write("-> start_key: (" .. start_key .. ")\n")
  140.         file:write("-> goal_key: (" .. goal_key .. ")\n")
  141.         if not self.vertices[start_key] then
  142.             file:write("-> start_key does not exist\n")
  143.         end
  144.         if not self.vertices[goal_key] then
  145.             file:write("-> goal_key does not exist\n")
  146.         end
  147.         file:close()
  148.         return nil
  149.     end
  150.     -- at this point we know both vertices exist
  151.     local open_set = { [start_key] = true }
  152.     local came_from = {}
  153.     local g_score = { [start_key] = 0 }
  154.     local f_score = { [start_key] = manhattan_distance(start_x, start_y, start_z, goal_x, goal_y, goal_z) }
  155.  
  156.     while next(open_set) do
  157.         -- Find the node in open_set with the lowest f_score
  158.         local current_key, current_f = nil, math.huge
  159.         for key in pairs(open_set) do
  160.             if f_score[key] and f_score[key] < current_f then
  161.                 current_key, current_f = key, f_score[key]
  162.             end
  163.         end
  164.  
  165.         -- Check if we reached the goal
  166.         if current_key == goal_key then
  167.             -- Reconstruct the path
  168.             local path = {}
  169.             while current_key do
  170.                 table.insert(path, 1, current_key)
  171.                 current_key = came_from[current_key]
  172.             end
  173.             return path
  174.         end
  175.  
  176.         open_set[current_key] = nil
  177.  
  178.         local current_vertex = self.vertices[current_key]
  179.         for neighbor_key in pairs(current_vertex.edges) do
  180.             local neighbor_vertex = self.vertices[neighbor_key]
  181.             local non_solid_block = false
  182.             if neighbor_vertex.data.name == "minecraft:air" then
  183.                 non_solid_block = true
  184.             elseif neighbor_vertex.data.name == "minecraft:water" then
  185.                 non_solid_block = true
  186.             elseif neighbor_vertex.data.name == "minecraft:lava" then
  187.                 non_solid_block = true
  188.             end
  189.             if non_solid_block then
  190.                 local tentative_g_score = g_score[current_key] + 1 -- All edges have the same weight of 1
  191.  
  192.                 if not g_score[neighbor_key] or tentative_g_score < g_score[neighbor_key] then
  193.                     came_from[neighbor_key] = current_key
  194.                     g_score[neighbor_key] = tentative_g_score
  195.                     f_score[neighbor_key] = tentative_g_score + manhattan_distance(
  196.                         self.vertices[neighbor_key].x, self.vertices[neighbor_key].y, self.vertices[neighbor_key].z,
  197.                         goal_x, goal_y, goal_z
  198.                     )
  199.                     open_set[neighbor_key] = true
  200.                 end
  201.             end
  202.         end
  203.     end
  204.     return nil -- No path found
  205. end
  206. --wrapper function for gps.locate()
  207. --returns x, y, z if successful
  208. --returns nil, nil, nil if unsuccesful and writes error to output_file
  209. function locate()
  210.     local x = nil
  211.     local y = nil
  212.     local z = nil
  213.     x, y, z = gps.locate()
  214.     if not x or not y or not z then
  215.         write_date_time(output_file)
  216.         local file = io.open(output_file, "a")
  217.         file:write("Failed to locate turtle. Check GPS constellation\n")
  218.         file:close()
  219.         return nil, nil, nil
  220.     end
  221.     return x, y, z
  222. end
  223. --wrapper function for turtle.inspect and its variations
  224. --returns the result of the turtle.inspect function and also saves the data to the graph
  225. --direction is "forward", "up", or "down"
  226. function inspect(direction, graph)
  227.     local inspect_success = nil
  228.     local inspect_data = nil
  229.     local inspect_x, inspect_y, inspect_z = nil
  230.     local turtle_x, turtle_y, turtle_z = locate()
  231.     if not turtle_x or not turtle_y or not turtle_z then
  232.         return nil, nil
  233.     end
  234.     if direction == "forward" then
  235.         inspect_success, inspect_data = turtle.inspect()
  236.         if turtle_direction == "south" then
  237.             inspect_x = turtle_x
  238.             inspect_y = turtle_y
  239.             inspect_z = turtle_z + 1
  240.         elseif turtle_direction == "north" then
  241.             inspect_x = turtle_x
  242.             inspect_y = turtle_y
  243.             inspect_z = turtle_z - 1
  244.         elseif turtle_direction == "east" then
  245.             inspect_x = turtle_x + 1
  246.             inspect_y = turtle_y
  247.             inspect_z = turtle_z
  248.         elseif turtle_direction == "west" then
  249.             inspect_x = turtle_x - 1
  250.             inspect_y = turtle_y
  251.             inspect_z = turtle_z
  252.         end
  253.     elseif direction == "up" then
  254.         inspect_success, inspect_data = turtle.inspectUp()
  255.         inspect_x = turtle_x
  256.         inspect_y = turtle_y + 1
  257.         inspect_z = turtle_z
  258.     elseif direction == "down" then
  259.         inspect_success, inspect_data = turtle.inspectDown()
  260.         inspect_x = turtle_x
  261.         inspect_y = turtle_y - 1
  262.         inspect_z = turtle_z
  263.     end
  264.     --block other than air
  265.     if inspect_success then
  266.         graph:add_vertex(inspect_x, inspect_y, inspect_z, inspect_data)
  267.     else -- air block
  268.         inspect_data = {
  269.             ["name"] = "minecraft:air",
  270.             ["tags"] = nil,
  271.             ["state"] = nil
  272.         }
  273.         graph:add_vertex(inspect_x, inspect_y, inspect_z, inspect_data)
  274.     end
  275.     return inspect_success, inspect_data
  276. end
  277.  
  278. --function to check for fuel
  279. --refuels if out of fuel
  280. --returns true if turtle has fuel or was able to refuel
  281. --shutsdown the computer if out of fuel
  282. function checkAndRefuel()
  283.     if turtle.getFuelLevel() == 0 then
  284.         -- Select the slot with coal
  285.         for slot = 1, 16 do
  286.             if turtle.getItemCount(slot) > 0 then
  287.                 turtle.select(slot)
  288.                 if turtle.refuel(1) then
  289.                     return true
  290.                 end
  291.             end
  292.         end
  293.         write_date_time(output_file)
  294.         local file = io.open(output_file, "a")
  295.         file:write("No fuel found!\n")
  296.         file:close()
  297.         os.shutdown()
  298.         return false
  299.     end
  300.     return true
  301. end
  302.  
  303. --wrapper function for turtle.forward() and the other movement functions
  304. --direction can be "forward", "back", "up", or "down"
  305. --returns true or false if the move was successfully completed
  306. --if turtle could not move, writes reason it could not move to output_file
  307. function move(direction)
  308.     if not checkAndRefuel() then
  309.         write_date_time(output_file)
  310.         local file = io.open(output_file, "a")
  311.         file:write("Failed to move due to lack of fuel\n")
  312.         file:close()
  313.         return false
  314.     end
  315.     local move_success, move_data = nil
  316.     if direction == "forward" then
  317.         move_success, move_data = turtle.forward()
  318.     elseif direction == "back" then
  319.         move_success, move_data = turtle.back()
  320.     elseif direction == "up" then
  321.         move_success, move_data = turtle.up()
  322.     elseif direction == "down" then
  323.         move_success, move_data = turtle.down()
  324.     end
  325.     if not move_success then
  326.         write_date_time(output_file)
  327.         local file = io.open(output_file, "a")
  328.         file:write("Failed to move due to:\n")
  329.         file:write("-> " .. move_date .. "\n")
  330.         file:close()
  331.     end
  332.     return move_success
  333. end
  334.  
  335. --wrapper function for turtle.turnLeft() and turtle.turnRight()
  336. --stores current direction in global variable turtle_direction
  337. --returns true or false if the turtle could successfully turn
  338. --if the turtle could not turn then writes the reason why to output_file
  339. --direction can be "left" or "right"
  340. --if turtle_direction is nil, does not modify it.
  341. function turn(direction)
  342.     local turn_success = nil
  343.     local turn_data = nil
  344.     if direction == "left" then
  345.         turn_success, turn_data = turtle.turnLeft()
  346.         if turn_success then
  347.             if turtle_direction == "north" then
  348.                 turtle_direction = "west"
  349.             elseif turtle_direction == "west" then
  350.                 turtle_direction = "south"
  351.             elseif turtle_direction == "south" then
  352.                 turtle_direction = "east"
  353.             elseif turtle_direction == "east" then
  354.                 turtle_direction = "north"
  355.             end
  356.         end
  357.     elseif direction == "right" then
  358.         turn_success, turn_data = turtle.turnRight()
  359.         if turn_success then
  360.             if turtle_direction == "north" then
  361.                 turtle_direction = "east"
  362.             elseif turtle_direction == "east" then
  363.                 turtle_direction = "south"
  364.             elseif turtle_direction == "south" then
  365.                 turtle_direction = "west"
  366.             elseif turtle_direction == "west" then
  367.                 turtle_direction = "north"
  368.             end
  369.         end
  370.     end
  371.     if not turn_success then
  372.         write_date_time(output_file)
  373.         local file = io.open(output_file, "a")
  374.         file:write("Failed to turn\n")
  375.         file:write("-> direction: " .. direction .. "\n")
  376.         file:write("-> reason: " .. turn_data .. "\n")
  377.         file:close()
  378.         return false
  379.     end
  380.     return true
  381. end
  382.  
  383. --determines what direction the turtle is facing
  384. --modifies the global turtle_direction variable
  385. --returns true or false if successful
  386. --sets global turtle_direction variable to nil if any issues occur
  387. --does not preserve direction the turtle was facing from before this function was called
  388. function determine_direction()
  389.     turtle_direction = nil
  390.     local x1, y1, z1 = locate()
  391.     local x2, y2, z2
  392.     local move_direction
  393.     if not x1 or not y1 or not z1 then
  394.         return false
  395.     end
  396.     --test which direction we are able to move in
  397.     local turn_left_count = 0
  398.     while turtle.detect() do
  399.         turn_left_count = turn_left_count + 1
  400.         if not turn("left") then
  401.             return false
  402.         end
  403.         if turn_left_count > 3 then
  404.             --no air blocks immediately around the turtle
  405.             write_date_time(output_file)
  406.             local file = io.open(output_file, "a")
  407.             file:write("Unable to determine direction in determine_direction()\n")
  408.             file:write("-> no air blocks surrounding the turtle\n")
  409.             file:close()
  410.             return false
  411.         end
  412.     end
  413.     if move("forward") then
  414.         x2, y2, z2 = locate()
  415.         if not x2 or not y2 or not z2 then
  416.             turtle_direction = nil
  417.             return false
  418.         end
  419.         if not move("back") then
  420.             write_date_time(output_file)
  421.             local file = io.open(output_file, "a")
  422.             file:write("Failed to correct position after calling determine_direction()\n")
  423.             file:close()
  424.             return false
  425.         end
  426.     else
  427.         --unable to move
  428.         write_date_time(output_file)
  429.         local file = io.open(output_file, "a")
  430.         file:write("Unable to determine direction in determine_direction()\n")
  431.         file:write("-> not able to move AFTER determining valid move direction\n")
  432.         file:close()
  433.         return false
  434.     end
  435.     -- successfully completed the moves, now we can calculate the direction
  436.     if z2 > z1 then
  437.         turtle_direction = "south"
  438.     elseif z1 < z2 then
  439.         turtle_direction = "north"
  440.     elseif x2 > x1 then
  441.         turtle_direction = "east"
  442.     elseif x1 < x2 then
  443.         turtle_direction = "west"
  444.     else
  445.         write_date_time(output_file)
  446.         local file = io.open(output_file, "a")
  447.         file:write("Unable to determine direction in determine_direction()\n")
  448.         file:write("-> turtle was unable to move for some unknown reason\n")
  449.         file:close()
  450.         return false
  451.     end
  452.     return true
  453. end
  454.  
  455. --creates a .nft image file for 1 layer of the graph
  456. --input is the graph and the y value for the layer to be generated
  457. --outputs to the graph_image.nft file
  458. --returns true or false if successful
  459. function generate_graph_image(graph, y)
  460.     local monitor = peripheral.find("monitor")
  461.     local width, height = monitor.getSize()
  462.     local center_x, _, center_z = locate()
  463.     if not center_x or not center_z then
  464.         write_date_time(output_file)
  465.         local file = io.open(output_file, "a")
  466.         file:write("Unable to locate turtle during generate_graph_image()\n")
  467.         file:close()
  468.         return false
  469.     end
  470.     local image_file = io.open("graph_image.nft", "w")
  471.     local start_image_x, start_image_z
  472.     local end_image_x, end_image_z
  473.     start_image_x = center_x - math.floor(width / 2)
  474.     start_image_z = center_z - math.floor(height / 2)
  475.     end_image_x = start_image_x + width
  476.     end_image_z = start_image_z + height
  477.     local key = nil
  478.     local block = nil
  479.     for h = start_image_z, end_image_z do
  480.         for w = start_image_x, end_image_x do
  481.             key = create_vertex_key(w, y, h)
  482.             if graph.vertices[key] then
  483.                 --we have data on this block
  484.                 block = graph.vertices[key]
  485.                 if block.data.name == "minecraft:air" then
  486.                     image_file:write("8")
  487.                 elseif block.data.name == "minecraft:lava" then
  488.                     image_file:write("e")
  489.                 elseif block.data.name == "minecraft:water" then
  490.                     image_file:write("b")
  491.                 else
  492.                     image_file:write("7")
  493.                 end
  494.             else
  495.                 image_file:write("0")
  496.             end
  497.         end
  498.         image_file:write("\n")
  499.     end
  500.     image_file:close()
  501.     return true
  502. end
  503.  
  504. --example usage of the graph and also the graph image
  505. local graph = {}
  506. setmetatable(graph, {__index = Graph})
  507. graph:initialize_graph()
  508.  
  509. local block_types = {"minecraft:air", "minecraft:stone", "minecraft:lava", "minecraft:water"}
  510. math.randomseed(os.time())
  511.  
  512. -- generate 60 x 40 vertices
  513. local block_data_file = io.open("block_data_file", "w")
  514. local temp_x, temp_y, temp_z = locate()
  515. if not temp_x or not temp_y or not temp_z then
  516.     block_data_file:close()
  517.     return
  518. end
  519.  
  520. for i = temp_x - 30, temp_x + 30 do
  521.     for j = temp_z - 20, temp_z + 20 do
  522.         local block_type = block_types[math.random(#block_types)]
  523.         local block_data = {
  524.             ["name"] = block_type,
  525.             ["tags"] = nil,
  526.             ["state"] = nil
  527.         }
  528.         graph:add_vertex(i, 0, j, block_data)
  529.         local coordinate_string = create_vertex_key(i, 0, j)
  530.         block_data_file:write("(" .. coordinate_string .. "): " .. block_type .. "\n")
  531.     end
  532. end
  533. block_data_file:close()
  534. generate_graph_image(graph, 0)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement