mirror of
https://github.com/evennia/evennia.git
synced 2026-03-26 09:46:32 +01:00
Add extended-link tracking
This commit is contained in:
parent
4f69efdcae
commit
48c7bb303c
2 changed files with 90 additions and 40 deletions
|
|
@ -165,6 +165,12 @@ class MapNode:
|
|||
self.weights = {}
|
||||
# lowest direction to a given neighbor
|
||||
self.cheapest_to_node = {}
|
||||
# maps the directions (on the xygrid NOT on XYgrid!) taken if stepping
|
||||
# out from this node in a given direction until you get to the end node.
|
||||
# This catches eventual longer link chains that would otherwise be lost
|
||||
# {startdirection: [direction, ...], ...}
|
||||
# where the directional path-lists also include the start-direction
|
||||
self.xy_steps_in_direction = {}
|
||||
|
||||
def __str__(self):
|
||||
return f"<MapNode {self.node_index} XY=({self.X},{self.Y}) ({self.symbol})>"
|
||||
|
|
@ -194,13 +200,16 @@ class MapNode:
|
|||
# just because there is a link here, doesn't mean it's
|
||||
# connected to this node. If so the `end_node` will be None.
|
||||
|
||||
end_node, weight = link.traverse(_REVERSE_DIRECTIONS[direction], xygrid)
|
||||
end_node, weight, steps = link.traverse(_REVERSE_DIRECTIONS[direction], xygrid)
|
||||
if end_node:
|
||||
# the link could be followed to an end node!
|
||||
node_index = end_node.node_index
|
||||
self.links[direction] = end_node
|
||||
self.weights[node_index] = weight
|
||||
|
||||
# this is useful for map building later
|
||||
self.xy_steps_in_direction[direction] = steps
|
||||
|
||||
cheapest = self.cheapest_to_node.get(node_index, ("", _BIG))[1]
|
||||
if weight < cheapest:
|
||||
self.cheapest_to_node[node_index] = (direction, weight)
|
||||
|
|
@ -352,9 +361,9 @@ class MapLink:
|
|||
"""
|
||||
return self.weights
|
||||
|
||||
def traverse(self, start_direction, xygrid, _weight=0, _linklen=1):
|
||||
def traverse(self, start_direction, xygrid, _weight=0, _linklen=1, _steps=None):
|
||||
"""
|
||||
Recursively traverse a set of links.
|
||||
Recursively traverse the links out of this Linknode..
|
||||
|
||||
Args:
|
||||
start_direction (str): The direction (n, ne etc) from which
|
||||
|
|
@ -363,9 +372,12 @@ class MapLink:
|
|||
Kwargs:
|
||||
_weight (int): Internal use.
|
||||
_linklen (int): Internal use.
|
||||
_steps (list): Internal use.
|
||||
|
||||
Returns:
|
||||
tuple: The (node, weight) result of the traversal.
|
||||
tuple: The (node, weight, links) result of the traversal, where links
|
||||
is a list of directions (n, ne etc) that describes how to to get
|
||||
to the node on the grid. This includes the first direction.
|
||||
|
||||
Raises:
|
||||
MapParserError: If a link lead to nowhere.
|
||||
|
|
@ -388,17 +400,24 @@ class MapLink:
|
|||
_weight += self.get_weights(
|
||||
start_direction, xygrid, _weight).get(
|
||||
start_direction, self.default_weight)
|
||||
if _steps is None:
|
||||
_steps = []
|
||||
_steps.append(_REVERSE_DIRECTIONS[start_direction])
|
||||
|
||||
if hasattr(next_target, "node_index"):
|
||||
# we reached a node, this is the end of the link.
|
||||
# we average the weight across all traversed link segments.
|
||||
return next_target, (
|
||||
_weight / max(1, _linklen) if self.average_long_link_weights else _weight)
|
||||
return (
|
||||
next_target,
|
||||
_weight / max(1, _linklen) if self.average_long_link_weights else _weight,
|
||||
_steps
|
||||
)
|
||||
|
||||
else:
|
||||
# we hit another link. Progress recursively.
|
||||
return next_target.traverse(
|
||||
_REVERSE_DIRECTIONS[end_direction],
|
||||
xygrid, _weight=_weight, _linklen=_linklen + 1)
|
||||
xygrid, _weight=_weight, _linklen=_linklen + 1, _steps=_steps)
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
|
|
@ -624,28 +643,6 @@ class Map:
|
|||
def __repr__(self):
|
||||
return f"<Map {self.max_X}x{self.max_Y}, {len(self.node_index_map)} nodes>"
|
||||
|
||||
def _get_node_from_coord(self, X, Y):
|
||||
"""
|
||||
Get a MapNode from a coordinate.
|
||||
|
||||
Args:
|
||||
X (int): X-coordinate on XY (game) grid.
|
||||
Y (int): Y-coordinate on XY (game) grid.
|
||||
|
||||
Returns:
|
||||
MapNode: The node found at the given coordinates.
|
||||
|
||||
|
||||
"""
|
||||
if not self.XYgrid:
|
||||
self.parse()
|
||||
|
||||
try:
|
||||
return self.XYgrid[X][Y]
|
||||
except IndexError:
|
||||
raise MapError("_get_node_from_coord got coordinate ({x},{y}) which is "
|
||||
"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).")
|
||||
|
||||
def _calculate_path_matrix(self):
|
||||
"""
|
||||
Solve the pathfinding problem using Dijkstra's algorithm.
|
||||
|
|
@ -834,6 +831,28 @@ class Map:
|
|||
# process the new(?) data
|
||||
self._parse()
|
||||
|
||||
def get_node_from_coord(self, X, Y):
|
||||
"""
|
||||
Get a MapNode from a coordinate.
|
||||
|
||||
Args:
|
||||
X (int): X-coordinate on XY (game) grid.
|
||||
Y (int): Y-coordinate on XY (game) grid.
|
||||
|
||||
Returns:
|
||||
MapNode: The node found at the given coordinates.
|
||||
|
||||
|
||||
"""
|
||||
if not self.XYgrid:
|
||||
self.parse()
|
||||
|
||||
try:
|
||||
return self.XYgrid[X][Y]
|
||||
except IndexError:
|
||||
raise MapError("get_node_from_coord got coordinate ({x},{y}) which is "
|
||||
"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).")
|
||||
|
||||
def get_shortest_path(self, startcoord, endcoord):
|
||||
"""
|
||||
Get the shortest route between two points on the grid.
|
||||
|
|
@ -850,8 +869,8 @@ class Map:
|
|||
the full path including the start- and end-node.
|
||||
|
||||
"""
|
||||
startnode = self._get_node_from_coord(*startcoord)
|
||||
endnode = self._get_node_from_coord(*endcoord)
|
||||
startnode = self.get_node_from_coord(*startcoord)
|
||||
endnode = self.get_node_from_coord(*endcoord)
|
||||
|
||||
if not self.pathfinding_routes:
|
||||
self._calculate_path_matrix()
|
||||
|
|
@ -955,7 +974,7 @@ class Map:
|
|||
|
||||
node_index_map = self.node_index_map
|
||||
|
||||
center_node = self._get_node_from_coord(iX, iY)
|
||||
center_node = self.get_node_from_coord(iX, iY)
|
||||
# find all reachable nodes within a (weighted) distance of `dist`
|
||||
for inode, node_dist in enumerate(self.dist_matrix[center_node.node_index]):
|
||||
if node_dist > dist:
|
||||
|
|
@ -963,14 +982,25 @@ class Map:
|
|||
# we have a node within 'dist' from us, get, the route to it
|
||||
node = node_index_map[inode]
|
||||
_, path = self.get_shortest_path(node.iX, node.iY)
|
||||
|
||||
# follow directions to figure out which map coords to display
|
||||
node0 = node
|
||||
ix0, iy0 = ix, iy
|
||||
# for path_element in path:
|
||||
# dx, dy = _MAPSCAN[direction]
|
||||
# ix0, iy0 = ix0 + dx, iy0 + dy
|
||||
# xmax, ymax = max(xmax, ix0), max(ymax, iy0)
|
||||
# points.append((ix0, iy0))
|
||||
for path_element in path:
|
||||
if isinstance(path_element, str):
|
||||
# a direction - this can lead to following
|
||||
# a longer link-chain chain
|
||||
for dstep in node0.xy_steps_in_direction[path_element]:
|
||||
dx, dy = _MAPSCAN[dstep]
|
||||
ix0, iy0 = ix0 + dx, iy0 + dy
|
||||
xmax, ymax = max(xmax, ix0), max(ymax, iy0)
|
||||
points.append((ix0, iy0))
|
||||
else:
|
||||
# a Mapnode
|
||||
node0 = path_element
|
||||
ix0, iy0 = node0.ix, node0.iy
|
||||
points.append((ix0, iy0))
|
||||
|
||||
|
||||
|
||||
else:
|
||||
# dist measures individual grid points
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class TestMap1(TestCase):
|
|||
self.assertEqual(str(self.map).strip(), MAP1_DISPLAY)
|
||||
|
||||
def test_node_from_coord(self):
|
||||
node = self.map._get_node_from_coord(1, 1)
|
||||
node = self.map.get_node_from_coord(1, 1)
|
||||
self.assertEqual(node.X, 1)
|
||||
self.assertEqual(node.x, 2)
|
||||
self.assertEqual(node.X, 1)
|
||||
|
|
@ -148,7 +148,7 @@ class TestMap2(TestCase):
|
|||
|
||||
def test_node_from_coord(self):
|
||||
for mapnode in self.map.node_index_map.values():
|
||||
node = self.map._get_node_from_coord(mapnode.X, mapnode.Y)
|
||||
node = self.map.get_node_from_coord(mapnode.X, mapnode.Y)
|
||||
self.assertEqual(node, mapnode)
|
||||
self.assertEqual(node.x // 2, node.X)
|
||||
self.assertEqual(node.y // 2, node.Y)
|
||||
|
|
@ -184,3 +184,23 @@ class TestMap2(TestCase):
|
|||
"""
|
||||
mapstr = self.map.get_map_display(coord, dist=4, character='@')
|
||||
self.assertEqual(expected, mapstr)
|
||||
|
||||
def test_extended_path_tracking__horizontal(self):
|
||||
node = self.map.get_node_from_coord(4, 1)
|
||||
self.assertEqual(
|
||||
node.xy_steps_in_direction,
|
||||
{'e': ['e'],
|
||||
's': ['s'],
|
||||
'w': ['w', 'w', 'w']}
|
||||
)
|
||||
|
||||
def test_extended_path_tracking__vertical(self):
|
||||
node = self.map.get_node_from_coord(2, 2)
|
||||
self.assertEqual(
|
||||
node.xy_steps_in_direction,
|
||||
{'n': ['n', 'n', 'n'],
|
||||
'e': ['e'],
|
||||
's': ['s'],
|
||||
'w': ['w']}
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue