Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
✓ ANSWER ACCEPTED

How do you return nodes from shortest path A* algorithm?

I wrote this A* Shortest Path algorithm using a Priority Queue which works when I map nodes a certain way, but not when I do it another way. Here is the base code for the algorithm:

My heuristic is the distance from the node to the goal node.

-- Example node = {v = "N0", c = {}}
-- v is the node value, also the name
-- c is the children/neighbors of the node in a table

function aStarSearch(start, goal)
	local queue = {}
	local visited = {}
	
	local function testVisited(node)
		for i,v in pairs(visited) do
			if v == node then
				return true
			end
		end
		
		return false
	end
	
	start.d = 0
	table.insert(queue, start)

	local current = nil
	
    while #queue > 0 do
		current = queue[1]
		table.remove(queue, 1)
		
		if not testVisited(current) then
			table.insert(visited, current)
			
			print(current.v, goal.v)
            if current.v == goal.v then
				table.insert(path, current)
				return path
			end

            local neighbors = current.c
			
			for i,v in pairs(neighbors) do
				if not testVisited(v) then
					local predictedDistance = (parts[v.v].Position - parts[goal.v].Position).magnitude
					local neighborDistance = (parts[current.v].Position - parts[v.v].Position).magnitude
					local totalDistance = (parts[current.v].Position - parts[start.v].Position).magnitude + neighborDistance + predictedDistance
					
					if predictedDistance < totalDistance then
						--print(current.v, v.v)
						if #current.c > 0 then 
							table.insert(path, current)
						end
						table.insert(queue, v)
					end
				end
			end
		end
	end
end	

The current way it get's the shortest path is by adding nodes to a path variable here:

if predictedDistance < totalDistance then
	--print(current.v, v.v)
	if #current.c > 0 then 
		table.insert(path, current)
	end
	table.insert(queue, v)
end

This works for when no node has the same neighbor as any other node, or in visual form, this:

No Loops

I already know the issue with this is that if it takes a wrong path and has to backtrack, it has already added the wrong nodes to the path variable.

This happens 95% of the time when I add loops:

Loops

This is how I map the nodes initially:

for i = 1,10 do
	local newNode = createNode("N"..i, {})
	if i == 1 then
		addChild(findNode(start.v), newNode)
	else
		addChild(findNode(lastNode.v), newNode)
		
		local r = math.random(1,2)
		if r == 1 then
			lastNode = newNode
		end
	end
end

And if I want to add loops I do this:

for i,v in pairs(nodes) do
	local r = math.random(1,4)
	local n = math.random(1,10)
	
	while n == i do
		n = math.random(1,10)
	end
	
	addChild(findNode(v.v), nodes[n])
end

So in short my question is, how do I return the CORRECT path and not include any nodes that perhaps ended in a dead end, or were wrong for another way? Perhaps the way I wrote the algorithm is incorrect, which it very well might be since this is my first A* algorithm, so if you see any weird things I do please point them out.

3 comments

0

dam

0

this might take a day to answer

0

You just wrote math.random() with two parameter, math.random() uses 3 parameters, math.random(startvalue endvalue, breakvalue)

1 answer

✓ Accepted answer

What you want to construct is a list that maps a node to the best node to reach it from. Such value might be getting replaced while the algorithm is running, so you then reconstruct the path from the list after the algorithm has finished running.

Currently you are missing this step so you get stuck because there is no way to replace a node with a better guess. Instead of inserting the current node to the path table, insert it to a hash table where the neighbor is the key and the current node the value, then you can reconstruct the path iterating that table.

Essentially

if predictedDistance < totalDistance then
	--print(current.v, v.v)
	if #current.c > 0 then
	    path[v]  = current
	end
	table.insert(queue, v)
end

--Then reconstruct after returning path
function reconstructPath(path, goal)
	local path = {}
	local current = goal
	repeat
		table.insert(path, 1, current)
		current = path[current]
	until not current
end

Since path[current] gives the best node to reach current from. You start at the end, the goal, and work your way up.

6 comments

0

dam

0

Took me hours to fix my code, then implement this part of it, thank you.

0

also, can i ask why u decided to create this instead of using the built in pathfinding service? is it not good to use the built in pathfinding server?

0

I am pretty sure the built in path finding is faster.

0

xd

0

@Zafirua it's for a school project actually lol

This discussion is preserved as it was. New answers, comments, and votes are closed.