From 0e73380ede7b724b84fa78cfedb51db0670c6ea2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 8 Jun 2021 21:46:05 +0200 Subject: [PATCH] Map-tested diagonals and crossover links --- evennia/contrib/map_and_pathfind/mapsystem.py | 11 +- evennia/contrib/map_and_pathfind/tests.py | 173 +++++++++++++++++- 2 files changed, 174 insertions(+), 10 deletions(-) diff --git a/evennia/contrib/map_and_pathfind/mapsystem.py b/evennia/contrib/map_and_pathfind/mapsystem.py index ad15a324be..8d22b8392b 100644 --- a/evennia/contrib/map_and_pathfind/mapsystem.py +++ b/evennia/contrib/map_and_pathfind/mapsystem.py @@ -76,7 +76,7 @@ See `./example_maps.py` for some empty grid areas to start from. from collections import defaultdict try: - from scipy.sparse.csgraph import dijkstra + from scipy.sparse.csgraph import dijkstra, breadth_first_order from scipy.sparse import csr_matrix from scipy import zeros except ImportError as err: @@ -105,7 +105,7 @@ _MAPSCAN = { "s": (0, -1), "sw": (-1, -1), "w": (-1, 0), - "nw": (1, -1) + "nw": (-1, 1) } _BIG = 999999999999 @@ -392,6 +392,9 @@ class MapLink: # from evennia import set_trace;set_trace() end_direction = self.get_directions(start_direction, xygrid).get(start_direction) if not end_direction: + if _steps is None: + # is perfectly okay to not be linking to a node + return None, 0, None raise MapParserError(f"Link at ({self.x}, {self.y}) was connected to " f"from {start_direction}, but does not link that way.") @@ -884,6 +887,10 @@ class Map: startnode = self.get_node_from_coord(startcoord) endnode = self.get_node_from_coord(endcoord) + if not endnode: + # no node at given coordinate. No path is possible. + return [], [] + if self.pathfinding_routes is None: self._calculate_path_matrix() diff --git a/evennia/contrib/map_and_pathfind/tests.py b/evennia/contrib/map_and_pathfind/tests.py index 5eca00d677..788f6ab04f 100644 --- a/evennia/contrib/map_and_pathfind/tests.py +++ b/evennia/contrib/map_and_pathfind/tests.py @@ -62,10 +62,86 @@ MAP2_DISPLAY = """ #-#-#-# """.strip() +MAP4 = r""" + + + 0 1 + + 1 #-# + |\| + 0 #-# + + + 0 1 + +""" + +MAP4 = r""" + + + 0 1 2 3 4 5 + + 5 #-#---# # + | / \ / + 4 # / # + |/ | + 3 # # + |\ / \ + 2 # #-# # + |/ \ / + 1 # # + / \ | + 0 # #---#-# + + + 0 1 2 3 4 5 + +""" + +MAP4_DISPLAY = r""" +#-#---# # + | / \ / + # / # + |/ | + # # + |\ / \ + # #-# # + |/ \ / + # # + / \ | +# #---#-# +""".strip() + +MAP5 = r""" + + + 0 1 2 3 4 + + 4 #-# #---# + x / + 3 #-#-# + |x x| + 2 #-#-#-# + | | | + 1 #-+-#-+-# + | | + 0 #---# + + + 0 1 2 3 4 + +""" + +MAP5_DISPLAY = r""" +#-# #---# + x / + #-#-# + |x x| +#-#-#-# +| | | +#-+-#-+-# + | | + #---# +""".strip() + class TestMap1(TestCase): """ - Test the Map class with a simple map and default symbol legend. + Test the Map class with a simple 4-node map """ @@ -131,23 +207,24 @@ class TestMap1(TestCase): self.assertEqual(expectlst, maplst[::-1]) # flip y-axis to match print direction @parameterized.expand([ - ((0, 0), '# \n| \n@-#'), - ((0, 1), '@-#\n| \n# '), - ((1, 0), ' #\n |\n#-@'), - ((1, 1), '#-@\n |\n #'), + ((0, 0), 1, '# \n| \n@-#'), + ((0, 1), 1, '@-#\n| \n# '), + ((1, 0), 1, ' #\n |\n#-@'), + ((1, 1), 1, '#-@\n |\n #'), + ((0, 0), 2, ''), ]) - def test_get_map_display__nodes__character(self, coord, expected): + def test_get_map_display__nodes__character(self, coord, dist, expected): """ Get sub-part of map with node-mode. """ - mapstr = self.map.get_map_display(coord, dist=1, mode='nodes', character='@') + mapstr = self.map.get_map_display(coord, dist=dist, mode='nodes', character='@') self.assertEqual(expected, mapstr) class TestMap2(TestCase): """ - Test with Map2 - a bigger map with some links crossing nodes. + Test with Map2 - a bigger map with multi-step links """ def setUp(self): @@ -251,3 +328,83 @@ class TestMap2(TestCase): mapstr = self.map.get_map_display(coord, dist=dist, mode='nodes', character='@', max_size=max_size) self.assertEqual(expected, mapstr) + + +class TestMap4(TestCase): + """ + Test Map4 - Map with diaginal links + + """ + def setUp(self): + self.map = mapsystem.Map({"map": MAP4}) + + def test_str_output(self): + """Check the display_map""" + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + self.assertEqual(MAP4_DISPLAY, stripped_map) + + @parameterized.expand([ + ((0, 0), (1, 0), ()), # no node at (1, 0)! + ((2, 0), (5, 0), ('e', 'e')), # straight path + ((0, 0), (1, 1), ('ne', )), + ((4, 1), (4, 3), ('nw', 'ne')), + ((4, 1), (4, 3), ('nw', 'ne')), + ((2, 2), (3, 5), ('nw', 'ne')), + ((2, 2), (1, 5), ('nw', 'n', 'n')), + ((5, 5), (0, 0), ('sw', 's', 'sw', 'w', 'sw', 'sw')), + ((5, 5), (0, 0), ('sw', 's', 'sw', 'w', 'sw', 'sw')), + ((5, 2), (1, 2), ('sw', 'nw', 'w', 'nw', 's')), + ((4, 1), (1, 1), ('s', 'w', 'nw')) + ]) + def test_shortest_path(self, startcoord, endcoord, expected_directions): + """ + Test shortest-path calculations throughout the grid. + + """ + directions, _ = self.map.get_shortest_path(startcoord, endcoord) + self.assertEqual(expected_directions, tuple(directions)) + + @parameterized.expand([ + ((2, 2), 2, None, + ' # \n / \n # / \n |/ \n # #\n \\ / ' + '\n # @-# \n |/ \\ \n # #\n / \\ \n# # '), + ((5, 2), 2, None, '') + ]) + def test_get_map_display__nodes__character(self, coord, dist, max_size, expected): + """ + Get sub-part of map with node-mode. + + """ + mapstr = self.map.get_map_display(coord, dist=dist, mode='nodes', character='@', + max_size=max_size) + print(repr(mapstr)) + self.assertEqual(expected, mapstr) + +class TestMap5(TestCase): + """ + Test Map5 - Map with + and x crossing links + + """ + def setUp(self): + self.map = mapsystem.Map({"map": MAP5}) + + def test_str_output(self): + """Check the display_map""" + stripped_map = "\n".join(line.rstrip() for line in str(self.map).split('\n')) + self.assertEqual(MAP5_DISPLAY, stripped_map) + + @parameterized.expand([ + ((1, 0), (1, 2), ('n',)), # cross + vertically + ((0, 1), (2, 1), ('e',)), # cross + horizontally + ((4, 1), (1, 0), ('w', 'w', 'n', 'e', 's')), + ((1, 2), (2, 3), ('ne', )), # cross x + ((1, 2), (2, 3), ('ne', )), + ((2, 2), (0, 4), ('w', 'ne', 'nw', 'w')), + ]) + def test_shortest_path(self, startcoord, endcoord, expected_directions): + """ + Test shortest-path calculations throughout the grid. + + """ + directions, _ = self.map.get_shortest_path(startcoord, endcoord) + self.assertEqual(expected_directions, tuple(directions))