1 # This file is part of NIT (http://www.nitlanguage.org).
3 # Copyright 2011-2013 Alexis Laferrière <alexis.laf@xymus.net>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Services related to pathfinding of graphs using A*
18 # A single graph may have different properties according to the `PathContext` used
22 # # Weighted graph (letters are nodes, digits are weights):
30 # var graph = new Graph[Node,WeigthedLink[Node]]
32 # var na = new Node(graph)
33 # var nb = new Node(graph)
34 # var nc = new Node(graph)
35 # var nd = new Node(graph)
36 # var ne = new Node(graph)
38 # var lab = new WeigthedLink[Node](graph, na, nb, 2)
39 # var lac = new WeigthedLink[Node](graph, na, nc, 3)
40 # var lbd = new WeigthedLink[Node](graph, nb, nd, 1)
41 # var lcd = new WeigthedLink[Node](graph, nc, nd, 3)
42 # var lde = new WeigthedLink[Node](graph, nd, ne, 8)
44 # var context = new WeightedPathContext[Node, WeigthedLink[Node]](graph)
46 # var path = na.path_to(ne, 100, context)
47 # assert path != null else print "No possible path"
49 # while not path.at_end_of_path do
55 protected fun debug_a_star
: Bool do return false
56 private fun debug
(msg
: String) do if debug_a_star
then
57 stderr
.write
"a_star debug: {msg}\n"
66 var graph
: Graph[N
, Link[N
]]
68 init(graph
: Graph[N
, Link[N
]])
75 var links
: Set[Link[N
]] = new HashSet[Link[N
]]
77 # used to check if node has been searched in one pathfinding
78 private var last_pathfinding_evocation
: Int = 0
80 # cost up to in current evocation
81 # lifetime limited to evocation of path_to
82 private var best_cost_up_to
: Int = 0
85 # lifetime limited to evocation of path_to
86 private var best_source
: nullable N
= null
88 # is in frontier or buckets
89 # lifetime limited to evocation of path_to
90 private var open
: Bool = false
93 protected fun cost_to
(other
: N
): Int do return 1
95 # Main functionnality, returns path from `self` to `dest`
96 fun path_to
(dest
: Node, max_cost
: Int, context
: PathContext[N
, Link[N
]]): nullable Path[N
]
100 var nbr_buckets
= context
.worst_cost
+ 1 # graph.max_heuristic_cost
101 var buckets
= new Array[List[Node]].with_capacity
(nbr_buckets
)
103 for i
in [0 .. nbr_buckets
[ do
104 var l
= new List[Node]
109 graph
.pathfinding_current_evocation
+= 1
114 self.last_pathfinding_evocation
= graph
.pathfinding_current_evocation
115 self.best_cost_up_to
= 0
117 while cost
< max_cost
do
118 var frontier_node
: nullable Node = null
120 var bucket_searched
: Int = 0
122 # find next valid node in frontier/buckets
124 var current_bucket
= buckets
[cost
% nbr_buckets
]
126 if current_bucket
.is_empty
then # move to next bucket
127 debug
"b {cost} {cost % nbr_buckets} {buckets[cost % nbr_buckets].hash}"
131 if bucket_searched
> nbr_buckets
then break
134 frontier_node
= current_bucket
.pop
136 if frontier_node
.open
then break
141 if frontier_node
== null then
145 else if frontier_node
== dest
then
146 debug
"picked {frontier_node}, is destination"
148 var path
= new Path[N
](cost
)
150 while frontier_node
!= self do
151 path
.nodes
.unshift
(frontier_node
)
152 frontier_node
= frontier_node
.best_source
.as(not null)
157 # adds all next nodes to frontier/buckets
159 frontier_node
.open
= false
161 debug
"w exploring adjacents of {frontier_node}"
163 for link
in frontier_node
.links
do
164 var peek_node
= link
.to
165 debug
"v {context.is_blocked(link)} {peek_node.last_pathfinding_evocation != graph.pathfinding_current_evocation} {peek_node.best_cost_up_to > frontier_node.best_cost_up_to + context.cost(link)}, {peek_node.best_cost_up_to} > {frontier_node.best_cost_up_to} + {context.cost(link)}"
166 if not context
.is_blocked
(link
) and
167 (peek_node
.last_pathfinding_evocation
!= graph
.pathfinding_current_evocation
or
169 peek_node
.best_cost_up_to
> cost
+ context
.cost
(link
)))
172 peek_node
.open
= true
173 peek_node
.last_pathfinding_evocation
= graph
.pathfinding_current_evocation
174 peek_node
.best_cost_up_to
= cost
+ context
.cost
(link
)
175 peek_node
.best_source
= frontier_node
177 var at_bucket
= buckets
[(peek_node
.best_cost_up_to
+peek_node
.cost_to
(dest
)) % nbr_buckets
]
178 at_bucket
.add
(peek_node
)
180 debug
"u putting {peek_node} at {peek_node.best_cost_up_to+peek_node.cost_to(dest)} -> {(peek_node.best_cost_up_to+peek_node.cost_to(dest)) % nbr_buckets} {at_bucket.hash}, {cost}+{context.cost(link)}"
190 # Find closes node with matching caracteristic
191 # TODO remove closures
192 fun find_closest
(max_to_search
: Int): nullable N
!with
(n
: N
): Bool
194 if with
(self) then return self
196 var frontier
= new List[N
]
197 graph
.pathfinding_current_evocation
+= 1
198 var current_evocation
= graph
.pathfinding_current_evocation
201 self.last_pathfinding_evocation
= current_evocation
204 while not frontier
.is_empty
do
205 var node
= frontier
.shift
207 for link
in node
.links
do
209 if to
.last_pathfinding_evocation
!= current_evocation
then
210 if with
(to
) then return to
213 to
.last_pathfinding_evocation
= current_evocation
218 if i
> max_to_search
then return null
228 var graph
: Graph[N
, L
]
233 init(graph
: Graph[N
, L
], from
, to
: N
)
244 class Graph[N
:Node, L
:Link[N
]]
245 var nodes
: Set[N
] = new HashSet[N
]
246 var links
: Set[L
] = new HashSet[L
]
248 #var max_link_cost: Int = 0
249 #var max_heuristic_cost: Int = 0
251 fun add_node
(node
: N
): N
258 fun add_link
(link
: L
): L
262 #if link.cost > max_link_cost then max_link_cost = link.cost
264 link
.from
.links
.add
(link
)
269 # used to check if nodes have been searched in one pathfinding
270 var pathfinding_current_evocation
: Int = 0
273 # Result from pathfinding, a walkable path
278 var nodes
= new List[N
]
280 init (cost
: Int) do total_cost
= cost
285 assert nodes
.length
>= at
else print
"a_star::Path::step failed, is at_end_of_path"
293 fun peek_step
: N
do return nodes
[at
]
295 fun at_end_of_path
: Bool do return at
>= nodes
.length
298 # Context related to an evocation of pathfinding
299 class PathContext[N
: Node, L
: Link[N
]]
300 var graph
: Graph[N
, L
]
302 # Worst cost of all the link's costs
303 fun worst_cost
: Int is abstract
306 fun cost
(link
: Link[N
]): Int is abstract
308 # Is that link blocked?
309 fun is_blocked
(link
: Link[N
]): Bool is abstract
312 fun heuristic_cost
(a
, b
: Node): Int is abstract
317 ### Additionnal classes, may be useful
320 # Simple context with constant cost on each links
321 # Warning: A* is not optimize for such a case
322 class ConstantPathContext[N
: Node, L
: Link[N
]]
323 super PathContext[N
, L
]
325 redef fun worst_cost
do return 1
326 redef fun cost
(l
) do return 1
327 redef fun is_blocked
(l
) do return false
328 redef fun heuristic_cost
(a
, b
) do return 1 # TODO
331 class WeightedPathContext[N
: Node, L
: WeigthedLink[N
]]
332 super PathContext[N
,L
]
334 init(graph
: Graph[N
,L
])
339 for l
in graph
.links
do
341 if cost
> worst_cost
then worst_cost
= cost
343 self.worst_cost
= worst_cost
346 redef var worst_cost
: Int
352 redef fun is_blocked
(l
) do return false
353 redef fun heuristic_cost
(a
, b
) do return 10 # TODO
356 class WeigthedLink[N
: Node]
361 init(graph
: Graph[N
,L
], from
, to
: N
, weight
: Int)