Bmorr

a_star.lua

May 5th, 2023 (edited)
940
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 7.97 KB | None | 0 0
  1. --[[
  2.     pastebin get KSSyHz8g a_star.lua
  3. ]] --
  4. mapping = require("mapping")
  5.  
  6. local function heuristic(a, b)
  7.     return math.abs(a.x - b.x) + math.abs(a.y - b.y) + math.abs(a.z - b.z)
  8. end
  9.  
  10. local function neighbors(parent)
  11.     local moves = {{
  12.         x = 0,
  13.         y = 0,
  14.         z = -1,
  15.         d = 0
  16.     }, -- North
  17.     {
  18.         x = 1,
  19.         y = 0,
  20.         z = 0,
  21.         d = 1
  22.     }, -- East
  23.     {
  24.         x = 0,
  25.         y = 0,
  26.         z = 1,
  27.         d = 2
  28.     }, -- South
  29.     {
  30.         x = -1,
  31.         y = 0,
  32.         z = 0,
  33.         d = 3
  34.     }, -- West
  35.     {
  36.         x = 0,
  37.         y = 1,
  38.         z = 0,
  39.         d = parent.d
  40.     }, -- Up
  41.     {
  42.         x = 0,
  43.         y = -1,
  44.         z = 0,
  45.         d = parent.d
  46.     } -- Down
  47.     }
  48.  
  49.     local ret = {}
  50.  
  51.     for i, move in ipairs(moves) do
  52.         table.insert(ret, {
  53.             x = parent.x + move.x,
  54.             y = parent.y + move.y,
  55.             z = parent.z + move.z,
  56.             d = move.d
  57.         })
  58.     end
  59.  
  60.     return ret
  61. end
  62.  
  63. local function calculate_severity(map, node)
  64.     local voxel = mapping.World.getVoxel(map, node.x, node.y, node.z)
  65.     if not voxel or not voxel.name then
  66.         return 2
  67.     elseif string.find(voxel.name, "ore") then
  68.         return 0
  69.     elseif voxel.name == "minecraft:chest" then
  70.         return 3
  71.     elseif voxel.name == "minecraft:air" then
  72.         return 0
  73.     elseif voxel.name == "minecraft:bedrock" then
  74.         return math.huge
  75.     end
  76.     return 1 -- If it's a breakable block
  77. end
  78.  
  79. local function a_star(map, start, goal, severity, max_time)
  80.     severity = severity or 1
  81.     max_time = max_time or 10
  82.  
  83.     local world = mapping.World.new(map.chunkSize)
  84.  
  85.     local open = {}
  86.     local function mark_open(node)
  87.         local i = 1
  88.         while open[i] ~= nil and node.total_cost > open[i].total_cost do
  89.             i = i + 1
  90.         end
  91.         table.insert(open, i, node)
  92.     end
  93.  
  94.     start.g_cost = 0
  95.     start.total_cost = heuristic(start, goal)
  96.     mark_open(start)
  97.     mapping.World.setVoxel(world, start.x, start.y, start.z, start)
  98.  
  99.     local start_time = os.epoch("local")
  100.  
  101.     while #open > 0 do
  102.  
  103.         local current = table.remove(open, 1)
  104.         local voxel = mapping.World.getNode(world, current)
  105.  
  106.         -- print(string.format("%3d, %3d, %3d (%d)", current.x, current.y, current.z, current.d))
  107.  
  108.         if voxel == mapping.World.getNode(world, goal) then
  109.             -- print("Should have found path!")
  110.             break
  111.         end
  112.  
  113.         if (os.epoch("local") - start_time) / 1000 > max_time then
  114.             printError("A* timed out!")
  115.             break
  116.         end
  117.  
  118.         current.status = "closed"
  119.         voxel.status = "closed"
  120.  
  121.         for _, neighbor in ipairs(neighbors(voxel)) do
  122.             local node = mapping.World.getNode(world, neighbor)
  123.  
  124.             local next_cost = voxel.g_cost + 1 + (neighbor.d - current.d) % 3
  125.             next_cost = next_cost + calculate_severity(map, node)
  126.  
  127.             if node.parent then -- This node is open or closed
  128.                 if next_cost <= node.g_cost then
  129.                     node.parent = current
  130.                     node.d = neighbor.d
  131.  
  132.                     node.h_cost = heuristic(node, goal)
  133.                     node.g_cost = next_cost
  134.                     node.total_cost = node.g_cost + node.h_cost
  135.  
  136.                     node.status = "open"
  137.                     mark_open(node)
  138.                 end
  139.             else -- This is unvisited
  140.                 node.parent = current
  141.                 node.d = neighbor.d
  142.  
  143.                 node.h_cost = heuristic(node, goal)
  144.                 node.g_cost = next_cost
  145.                 node.total_cost = node.g_cost + node.h_cost
  146.  
  147.                 node.status = "open"
  148.                 mark_open(node)
  149.             end
  150.         end
  151.     end
  152.  
  153.     if mapping.World.getNode(world, goal).parent then
  154.         local path = {mapping.World.getNode(world, goal)}
  155.         while path[1] ~= mapping.World.getNode(world, start) do
  156.             print(path[1])
  157.             table.insert(path, 1, mapping.World.getNode(world, path[1].parent))
  158.         end
  159.         return path
  160.     end
  161.  
  162.     return nil
  163.  
  164. end
  165.  
  166. local severities = {
  167.     [0] = "minecraft:air",
  168.     [1] = "minecraft:stone",
  169.     [2] = nil,
  170.     [3] = "minecraft:chest",
  171.     [4] = "minecraft:bedrock",
  172.     [5] = "minecraft:iron_ore"
  173. }
  174.  
  175. local function generate_world(min, max)
  176.     local map = mapping.World.new(16)
  177.  
  178.     for x = min, max do
  179.         for y = min, max do
  180.             for z = min, max do
  181.                 mapping.World.setVoxel(map, x, y, z, {
  182.                     name = severities[math.random(0, 5)]
  183.                 })
  184.             end
  185.         end
  186.     end
  187.  
  188.     return map
  189. end
  190.  
  191. local function load_world(min, max)
  192.     local map = mapping.World.new(16)
  193.  
  194.     local size = max - min
  195.  
  196.     local file = io.open("../map.txt", "r")
  197.  
  198.     local line_num = 0
  199.     local line = file:readline()
  200.  
  201.     while line do
  202.         for x = 0, #line - 1 do
  203.             local c = line:sub(x + 1, x + 1)
  204.  
  205.             if c ~= "\n" then
  206.                 mapping.World.setVoxel(map, min + x, min + math.floor(line_num / size), min + line_num % size,
  207.                     severities[tonumber(c)])
  208.             end
  209.  
  210.         end
  211.         line_num = line_num + 1
  212.     end
  213.  
  214.     file:close()
  215.  
  216. end
  217.  
  218. local directions = {
  219.     [0] = vector.new(0, 0, -1),
  220.     [1] = vector.new(1, 0, 0),
  221.     [2] = vector.new(0, 0, 1),
  222.     [3] = vector.new(-1, 0, 0)
  223. }
  224.  
  225. local function convert_to_movement(path)
  226.  
  227.     local current = table.remove(path, 1)
  228.     local current_vec = vector.new(current.x, current.y, current.z)
  229.  
  230.     local moves = {}
  231.  
  232.     while #path > 0 do
  233.         local next = table.remove(path, 1)
  234.         local next_vec = vector.new(next.x, next.y, next.z)
  235.         local diff = next.d - current.d
  236.  
  237.         -- Left Left, Left, or Right
  238.         if math.abs(diff) == 2 then
  239.             table.insert(moves, "L")
  240.             table.insert(moves, "L")
  241.         elseif diff == 1 or diff == -3 then
  242.             table.insert(moves, "R")
  243.         elseif diff == -1 or diff == 3 then
  244.             table.insert(moves, "L")
  245.         end
  246.        
  247.         -- Up or Down
  248.         local y_diff = next.y - current.y
  249.         if y_diff > 0 then
  250.             table.insert(moves, "U")
  251.         elseif y_diff < 0 then
  252.             table.insert(moves, "D")
  253.         end
  254.        
  255.         -- Forward or Backward
  256.         local vec_diff = next_vec - current_vec
  257.         if directions[next.d] == vec_diff then
  258.             table.insert(moves, "F")
  259.         elseif directions[(next.d + 2) % 4] == vec_diff then
  260.             table.insert(moves, "B")
  261.         end
  262.  
  263.         current = next
  264.         current_vec = next_vec
  265.         moves.cost = current.total_cost
  266.  
  267.     end
  268.  
  269.     return moves
  270.  
  271. end
  272.  
  273. local function example(start, goal)
  274.  
  275.     local start = start or {
  276.         x = 0,
  277.         y = 0,
  278.         z = 0,
  279.         d = 0
  280.     }
  281.     local goal = goal or {
  282.         x = 6,
  283.         y = -6,
  284.         z = -6
  285.     }
  286.     local min = start.x
  287.     local max = start.x
  288.  
  289.     for k, v in pairs(start) do
  290.         min = math.min(min, v)
  291.         max = math.max(max, v)
  292.     end
  293.     for k, v in pairs(goal) do
  294.         min = math.min(min, v)
  295.         max = math.max(max, v)
  296.     end
  297.  
  298.     local map = generate_world(min - 3, max + 3)
  299.    
  300.     local start_time = os.epoch("local")
  301.     local path = a_star(map, start, goal)
  302.     local end_time = os.epoch("local")
  303.     if path == nil then
  304.         print(string.format("Could not find path! %.2fs", (end_time - start_time) / 1000))
  305.     else
  306.         print(string.format("Found a path in %.2fs!", (end_time - start_time) / 1000))
  307.         for _, node in ipairs(path) do
  308.             print(string.format("%3d, %3d, %3d (%d)", node.x, node.y, node.z, node.d))
  309.         end
  310.  
  311.         local moves = convert_to_movement(path)
  312.         for _, move in ipairs(moves) do
  313.             print(move)
  314.         end
  315.     end
  316.  
  317. end
  318.  
  319. return {
  320.     a_star = a_star,
  321.     example = example
  322. }
  323.  
Advertisement
Add Comment
Please, Sign In to add comment