Add stress-tests, start adding map-transitions
This commit is contained in:
parent
686c17c4a1
commit
2932beb769
2 changed files with 184 additions and 14 deletions
|
|
@ -668,7 +668,7 @@ class TeleporterMapLink(MapLink):
|
|||
"""
|
||||
The teleport link works by connecting to nowhere - and will then continue
|
||||
on another teleport link with the same symbol elsewhere on the map. The teleport
|
||||
must connect in only one direction, and only to another Link.
|
||||
symbol must connect to only one other link (not to a node).
|
||||
|
||||
For this to work, there must be exactly one other teleport with the same `.symbol` on the map.
|
||||
The two teleports will always operate as two-way connections, but by making the 'out-link' on
|
||||
|
|
@ -683,14 +683,15 @@ class TeleporterMapLink(MapLink):
|
|||
|
||||
-#-t t># - one-way teleport from left to right.
|
||||
|
||||
#t - invalid, may only connect to another link
|
||||
-#t - invalid, may only connect to another link
|
||||
|
||||
#-t-# - invalid, only one connected link is allowed.
|
||||
-#-t-# - invalid, only one connected link is allowed.
|
||||
|
||||
"""
|
||||
symbol = 't'
|
||||
# usually invisible
|
||||
display_symbol = ' '
|
||||
direction_name = 'teleport'
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
|
@ -756,19 +757,78 @@ class TeleporterMapLink(MapLink):
|
|||
# the string 'teleport' will not be understood by the traverser, leading to
|
||||
# this being interpreted as an empty target and the `at_empty_target`
|
||||
# hook firing when trying to traverse this link.
|
||||
if start_direction == 'teleport':
|
||||
direction_name = self.direction_name
|
||||
if start_direction == direction_name:
|
||||
# called while traversing another teleport
|
||||
# - we must make sure we can always access/leave the teleport.
|
||||
self.directions = {"teleport": direction,
|
||||
direction: "teleport"}
|
||||
self.directions = {direction_name: direction,
|
||||
direction: direction_name}
|
||||
else:
|
||||
# called while traversing a normal link
|
||||
self.directions = {start_direction: "teleport",
|
||||
"teleport": direction}
|
||||
self.directions = {start_direction: direction_name,
|
||||
direction_name: direction}
|
||||
|
||||
return self.directions.get(start_direction)
|
||||
|
||||
|
||||
class MapTransitionLink(TeleporterMapLink):
|
||||
"""
|
||||
This link teleports the user to another map and lets them continue moving
|
||||
from there. Like the TeleporterMapLink, the map-transition symbol must connect to only one other
|
||||
link (not directly to a node).
|
||||
|
||||
The other map will be scanned for a matching `.symbol` that must also be a MapTransitionLink.
|
||||
The link is always two-way, but the link connecting to the transition can be one-way to create
|
||||
a one-way transition. Make new links with different symbols (like A, B, C, ...) to link
|
||||
multiple maps together.
|
||||
|
||||
Note that unlike for teleports, pathfinding will *not* work across the map-transition.
|
||||
|
||||
Examples:
|
||||
::
|
||||
|
||||
map1 map2
|
||||
|
||||
T
|
||||
/ T-# - movement to the transition-link will continue on the other map.
|
||||
-#
|
||||
|
||||
T
|
||||
/
|
||||
-# T># - one-way link from map1 to map2
|
||||
|
||||
-#t - invalid, may only connect to another link
|
||||
|
||||
-#-t-# - invalid, only one connected link is allowed.
|
||||
|
||||
"""
|
||||
symbol = 'T'
|
||||
display_symbol = ' '
|
||||
direction_name = 'transition'
|
||||
interrupt_path = True
|
||||
|
||||
map1_name = 'map'
|
||||
map2_name = 'map'
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.map1 = None
|
||||
self.map2 = None
|
||||
|
||||
def at_empty_target(self, start_direction, end_direction, xygrid):
|
||||
"""
|
||||
This is called by .traverse when it finds this link pointing to nowhere.
|
||||
|
||||
Args:
|
||||
start_direction (str): The direction (n, ne etc) from which
|
||||
this traversal originates for this link.
|
||||
end_direction (str): The direction found from `get_direction` earlier.
|
||||
xygrid (dict): 2D dict with x,y coordinates as keys.
|
||||
|
||||
"""
|
||||
# TODO - this needs some higher-level handler to work.
|
||||
|
||||
|
||||
class SmartMapLink(MapLink):
|
||||
"""
|
||||
A 'smart' link withot visible direction, but which uses its topological surroundings
|
||||
|
|
@ -1008,7 +1068,6 @@ class InterruptMapLink(InvisibleSmartMapLink):
|
|||
symbol = "i"
|
||||
interrupt_path = True
|
||||
|
||||
|
||||
class BlockedMapLink(InvisibleSmartMapLink):
|
||||
"""
|
||||
A high-weight (but still passable) link that causes the shortest-path algorithm to consider this
|
||||
|
|
@ -1047,6 +1106,7 @@ DEFAULT_LEGEND = {
|
|||
"b": BlockedMapLink,
|
||||
"i": InterruptMapLink,
|
||||
't': TeleporterMapLink,
|
||||
'T': MapTransitionLink,
|
||||
}
|
||||
|
||||
# --------------------------------------------
|
||||
|
|
@ -1097,7 +1157,7 @@ class Map:
|
|||
# we normally only accept one single character for the legend key
|
||||
legend_key_exceptions = ("\\")
|
||||
|
||||
def __init__(self, map_module_or_dict):
|
||||
def __init__(self, map_module_or_dict, name="map"):
|
||||
"""
|
||||
Initialize the map parser by feeding it the map.
|
||||
|
||||
|
|
@ -1105,6 +1165,9 @@ class Map:
|
|||
map_module_or_dict (str, module or dict): Path or module pointing to a map. If a dict,
|
||||
this should be a dict with a key 'map' and optionally a 'legend'
|
||||
dicts to specify the map structure.
|
||||
name (str, optional): Unique identifier for this map. Needed if the game uses
|
||||
more than one map. Used when referencing this map during map transitions,
|
||||
baking of pathfinding matrices etc.
|
||||
|
||||
Notes:
|
||||
The map deals with two sets of coorinate systems:
|
||||
|
|
@ -1321,7 +1384,6 @@ class Map:
|
|||
# we have a link at this xygrid position (this is ok everywhere)
|
||||
xygrid[ix][iy] = mapnode_or_link_class(ix, iy)
|
||||
|
||||
# from evennia import set_trace;set_trace()
|
||||
# second pass: Here we loop over all nodes and have them connect to each other
|
||||
# via the detected linkages.
|
||||
for node in node_index_map.values():
|
||||
|
|
@ -1435,8 +1497,6 @@ class Map:
|
|||
the full path including the start- and end-node.
|
||||
|
||||
"""
|
||||
# from evennia import set_trace;set_trace()
|
||||
|
||||
startnode = self.get_node_from_coord(startcoord)
|
||||
endnode = self.get_node_from_coord(endcoord)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ Tests for the Mapsystem
|
|||
|
||||
"""
|
||||
|
||||
from time import time
|
||||
from random import randint
|
||||
from unittest import TestCase
|
||||
from parameterized import parameterized
|
||||
from . import mapsystem
|
||||
|
||||
|
||||
MAP1 = """
|
||||
|
||||
+ 0 1 2
|
||||
|
|
@ -873,3 +874,112 @@ class TestMap11(TestCase):
|
|||
character='@',
|
||||
max_size=max_size)
|
||||
self.assertEqual(expected, mapstr)
|
||||
|
||||
|
||||
class TestMapStressTest(TestCase):
|
||||
"""
|
||||
Performance test of map patfinder and visualizer.
|
||||
|
||||
#-#-#-#-#....
|
||||
|x|x|x|x|
|
||||
#-#-#-#-#
|
||||
|x|x|x|x|
|
||||
#-#-#-#-#
|
||||
|x|x|x|x|
|
||||
#-#-#-#-#
|
||||
...
|
||||
|
||||
This should be a good stress-testing scenario because most each internal node has a maxiumum
|
||||
number of connections and options to consider.
|
||||
|
||||
"""
|
||||
|
||||
def _get_grid(self, Xsize, Ysize):
|
||||
edge = f"+ {' ' * Xsize * 2}"
|
||||
l1 = f"\n {'#-' * Xsize}#"
|
||||
l2 = f"\n {'|x' * Xsize}|"
|
||||
|
||||
return f"{edge}\n{(l1 + l2) * Ysize}{l1}\n\n{edge}"
|
||||
|
||||
@parameterized.expand([
|
||||
((10, 10), 0.01),
|
||||
((100, 100), 1),
|
||||
])
|
||||
def test_grid_creation(self, gridsize, max_time):
|
||||
"""
|
||||
Test of grid-creataion performance for Nx, Ny grid.
|
||||
|
||||
"""
|
||||
Xmax, Ymax = gridsize
|
||||
grid = self._get_grid(Xmax, Ymax)
|
||||
# print(f"\n\n{grid}\n")
|
||||
t0 = time()
|
||||
mapsystem.Map({'map': grid})
|
||||
t1 = time()
|
||||
self.assertLess(t1 - t0, max_time, f"Map creation of ({Xmax}x{Ymax}) grid slower "
|
||||
f"than expected {max_time}s.")
|
||||
|
||||
@parameterized.expand([
|
||||
((10, 10), 10**-4),
|
||||
((20, 20), 10**-4),
|
||||
])
|
||||
def test_grid_pathfind(self, gridsize, max_time):
|
||||
"""
|
||||
Test pathfinding performance for Nx, Ny grid.
|
||||
|
||||
"""
|
||||
Xmax, Ymax = gridsize
|
||||
grid = self._get_grid(Xmax, Ymax)
|
||||
mapobj = mapsystem.Map({'map': grid})
|
||||
|
||||
t0 = time()
|
||||
mapobj._calculate_path_matrix()
|
||||
t1 = time()
|
||||
# print(f"pathfinder matrix for grid {Xmax}x{Ymax}: {t1 - t0}s")
|
||||
|
||||
# get the maximum distance and 9 other random points in the grid
|
||||
start_end_points = [((0, 0), (Xmax-1, Ymax-1))]
|
||||
for _ in range(9):
|
||||
start_end_points.append(((randint(0, Xmax), randint(0, Ymax)),
|
||||
(randint(0, Xmax), randint(0, Ymax))))
|
||||
|
||||
t0 = time()
|
||||
for startcoord, endcoord in start_end_points:
|
||||
mapobj.get_shortest_path(startcoord, endcoord)
|
||||
t1 = time()
|
||||
self.assertLess((t1 - t0) / 10, max_time, f"Pathfinding for ({Xmax}x{Ymax}) grid slower "
|
||||
f"than expected {max_time}s.")
|
||||
|
||||
@parameterized.expand([
|
||||
((10, 10), 4, 0.01),
|
||||
((20, 20), 4, 0.01),
|
||||
])
|
||||
def test_grid_visibility(self, gridsize, dist, max_time):
|
||||
"""
|
||||
Test grid visualization performance for Nx, Ny grid for
|
||||
different visibility distances.
|
||||
|
||||
"""
|
||||
Xmax, Ymax = gridsize
|
||||
grid = self._get_grid(Xmax, Ymax)
|
||||
mapobj = mapsystem.Map({'map': grid})
|
||||
|
||||
t0 = time()
|
||||
mapobj._calculate_path_matrix()
|
||||
t1 = time()
|
||||
# print(f"pathfinder matrix for grid {Xmax}x{Ymax}: {t1 - t0}s")
|
||||
|
||||
# get random center points in grid and a range of targets to visualize the
|
||||
# path to
|
||||
start_end_points = [((0, 0), (Xmax-1, Ymax-1))] # include max distance
|
||||
for _ in range(9):
|
||||
start_end_points.append(((randint(0, Xmax), randint(0, Ymax)),
|
||||
(randint(0, Xmax), randint(0, Ymax))))
|
||||
|
||||
t0 = time()
|
||||
for coord, target in start_end_points:
|
||||
mapobj.get_visual_range(coord, dist=dist, mode='nodes', character='@', target=target)
|
||||
t1 = time()
|
||||
self.assertLess((t1 - t0) / 10, max_time,
|
||||
f"Visual Range calculation for ({Xmax}x{Ymax}) grid "
|
||||
f"slower than expected {max_time}s.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue