Forum

> > CS2D > Scripts > Doubt with A* Pathfinding
Forums overviewCS2D overview Scripts overviewLog in to reply

English Doubt with A* Pathfinding

6 replies
To the start Previous 1 Next To the start

old Doubt with A* Pathfinding

Starkkz
Moderator Off Offline

Quote
Finally I made it work, but it's not fully working. I mean, my problem is that it search the path and doesn't return a shorter path.

Spoiler >


I tested it using this other part.
Spoiler >


Try to use it in de_dust, with the coords:
1
X: 37 Y: 4
To
1
X: 37 Y: 6

It will give you a wrong path, anyways its a walkable path.

My Opinion: I think at the part you search a low G cost, I made something wrong.

old Re: Doubt with A* Pathfinding

Starkkz
Moderator Off Offline

Quote
That's why i prefer A*, it works very well. But it gives a larger path. Or you mean it gives a fast path, but larger?

old Re: Doubt with A* Pathfinding

Starkkz
Moderator Off Offline

Quote
Hmm, watching it better. The F cost always gives the same, I should try with H because that's the cost from the current node to the last node. And about that, the other nodes can be found with G cost, the F cost is not necessary.

old Re: Doubt with A* Pathfinding

Lee
Moderator Off Offline

Quote
Adapted from http://en.wikipedia.org/wiki/A-star_algorithm

Sample run (overloading CS2D's native functions)
http://codepad.org/TaoK5hdk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
function table.find(t, x)
	for _,v in ipairs(t) do if x == v then return _ end end
end

function pqueue(initial, cmp)
	cmp = cmp or function(a,b) return a<b end
	return setmetatable({unpack(initial or {})}, {
		__index = {
			push = function(self, v)
				table.insert(self, v)
				local next = #self
				local prev = math.floor(next/2)
				while next > 1 and self[next] < self[prev] do
					-- swap up
					self[next], self[prev] = self[prev], self[next]
					next = prev
					prev = math.floor(next/2)
				end
				self[next] = v
				self.size = self.size + 1
			end,
			size = 0,
			pop = function(self)
				if #self < 2 then
					return table.remove(self)
				end
				local r = self[1]
				self[1] = table.remove(self)
				local root = 1
				if #self > 1 then
					local size = #self
					local v = self[root]
					while root < size do
						local child = 2*root
						if child < size then
							if child+1 < size and self[child+1] < self[child] then child = child + 1 end
							if cmp(self[child], self[root]) then
								-- swap down
								self[root], self[child] = self[child], self[root]
								root = child
							else
								self[root] = v
								break
							end
						else
							self[root] = v
							break
						end
					end
				end
				self.size = self.size - 1
				return r
			end}})
end


local xsize = map("xsize")
local ysize = map("ysize")

local function dist(point, goal)
	-- simple euclidean distance as our heuristic function
	return (goal%xsize - point%xsize)^2 + (math.floor(goal/xsize - point/xsize))^2
end

local function h()
	-- dist takes care of everything, weigh everything else with equal favorability
	return 0
end

local function neighbors(next)
	-- return a list of neighboring tiles
	local near = {}
	if next/xsize >= 1 then
		table.insert(near, next-xsize)
	elseif next/xsize < ysize then
		table.insert(near, next+xsize)
	end
	if next%xsize > 0 then
		table.insert(near, next-1)
	elseif next%xsize < xsize then
		table.insert(near, next+1)
	end
	return ipairs(near)
end

-- our graph is the strongly connected rectangular mesh
-- nodes/vertices are numbers hashed as numbers with y*xsize+x
function search(start, goal)
	local this = start
	local g_, h_, f_
	local visited, graph, path = {}, pqueue({}, function(a, b)
		return g_[a]+h(a, goal) < g_[b]+h(b, goal)
	end), {}

	graph:push(start)

	-- setup our heuristic memcache
	g_, h_, f_ =
		{[start] = 0},
		{[start] = h(start, goal)},
		{[start] = h(start, goal)}

	while graph.size > 0 do
		this = graph:pop()

		-- end condition
		if this == goal then
			-- need to backtrace from goal to start in path
			function backtrace(this)
				if path[this] then
					local p = backtrace(path[this])
					table.insert(p, this)
					return p
				else
					return {this}
				end
			end
			return backtrace(this)
		end

		visited[this] = true
		for _,next in neighbors(this) do

			if not visited[next] and
				tile(next%xsize, math.floor(next/xsize), "walkable") then

				local gscore = g_[this] + dist(this, next)
				local advance
				if not table.find(graph, next) then
					graph:push(next)
					advance = true
				elseif gscore < g_[next] then
					advance = true
				else
					advance = false
				end

				if advance then
					path[next] = this

					-- f'(x) = g(x) + h'(x)
					g_[next] = gscore
					h_[next] = h(next, goal)
					f_[next] = g_[next] + h_[next]
				end
			end
		end
	end

end

Technically, we shouldn't use euclidean distance function as our heuristic as we're limiting the neighbor finding function to only hrz/vrt adjacent tiles. To get the correct estimated distance, don't square the components.

We also use a heap structure to keep track of our path, this gives us nlog(n) time complexity on the search algorithm rather than n^2.

Tentatively, we treat the map as a vertex matrix, but for efficiency's sake, namely that tables are expensive, we hash each vertex node as a single integer unique for each x and y. The most simple/straightforward bijection from N^2 to N in this case is hash(x,y) = y*xsize+x (for countably infinite sets, this can be proven via Cantor's diagonalizing argument, for finite sets, the proof is trivial). So to search for a path between (start.x, start.y) and (end.x, end.y), you would need to hash both before passing them into the function.

1
2
3
4
function point_hash(x,y)
	return x+y*map("xsize")
end
search(point_hash(startx, starty), point_hash(endx, endy))

The current heuristic assumes that all tiles are equally favorable, and hence only weighs based on distance. This in turn defeats the main advantages of A* or kruskal type derivative algorithms in that they can use any arbitrary massing function. If you do not care whether a gatefield is better than a longer route, then for efficiency's sake, you should look into Djkstra's algorithm for unweighted graphs. If not, you may need to change the h function accordingly.

old Re: Doubt with A* Pathfinding

Starkkz
Moderator Off Offline

Quote
Huh thanks for the explaination and the demonstration of the script, I'm not sure because I don't know but I think that the fastest pathfinding algorithm is A*, anyways I'll search for the Dijkstra. If I can find better methods than the currently used.
Thank you so much.
To the start Previous 1 Next To the start
Log in to reply Scripts overviewCS2D overviewForums overview