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
|
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
|
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.
|
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
|
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 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'
|
symbol = 't'
|
||||||
# usually invisible
|
# usually invisible
|
||||||
display_symbol = ' '
|
display_symbol = ' '
|
||||||
|
direction_name = 'teleport'
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
@ -756,19 +757,78 @@ class TeleporterMapLink(MapLink):
|
||||||
# the string 'teleport' will not be understood by the traverser, leading to
|
# the string 'teleport' will not be understood by the traverser, leading to
|
||||||
# this being interpreted as an empty target and the `at_empty_target`
|
# this being interpreted as an empty target and the `at_empty_target`
|
||||||
# hook firing when trying to traverse this link.
|
# 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
|
# called while traversing another teleport
|
||||||
# - we must make sure we can always access/leave the teleport.
|
# - we must make sure we can always access/leave the teleport.
|
||||||
self.directions = {"teleport": direction,
|
self.directions = {direction_name: direction,
|
||||||
direction: "teleport"}
|
direction: direction_name}
|
||||||
else:
|
else:
|
||||||
# called while traversing a normal link
|
# called while traversing a normal link
|
||||||
self.directions = {start_direction: "teleport",
|
self.directions = {start_direction: direction_name,
|
||||||
"teleport": direction}
|
direction_name: direction}
|
||||||
|
|
||||||
return self.directions.get(start_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):
|
class SmartMapLink(MapLink):
|
||||||
"""
|
"""
|
||||||
A 'smart' link withot visible direction, but which uses its topological surroundings
|
A 'smart' link withot visible direction, but which uses its topological surroundings
|
||||||
|
|
@ -1008,7 +1068,6 @@ class InterruptMapLink(InvisibleSmartMapLink):
|
||||||
symbol = "i"
|
symbol = "i"
|
||||||
interrupt_path = True
|
interrupt_path = True
|
||||||
|
|
||||||
|
|
||||||
class BlockedMapLink(InvisibleSmartMapLink):
|
class BlockedMapLink(InvisibleSmartMapLink):
|
||||||
"""
|
"""
|
||||||
A high-weight (but still passable) link that causes the shortest-path algorithm to consider this
|
A high-weight (but still passable) link that causes the shortest-path algorithm to consider this
|
||||||
|
|
@ -1047,6 +1106,7 @@ DEFAULT_LEGEND = {
|
||||||
"b": BlockedMapLink,
|
"b": BlockedMapLink,
|
||||||
"i": InterruptMapLink,
|
"i": InterruptMapLink,
|
||||||
't': TeleporterMapLink,
|
't': TeleporterMapLink,
|
||||||
|
'T': MapTransitionLink,
|
||||||
}
|
}
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
@ -1097,7 +1157,7 @@ class Map:
|
||||||
# we normally only accept one single character for the legend key
|
# we normally only accept one single character for the legend key
|
||||||
legend_key_exceptions = ("\\")
|
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.
|
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,
|
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'
|
this should be a dict with a key 'map' and optionally a 'legend'
|
||||||
dicts to specify the map structure.
|
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:
|
Notes:
|
||||||
The map deals with two sets of coorinate systems:
|
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)
|
# we have a link at this xygrid position (this is ok everywhere)
|
||||||
xygrid[ix][iy] = mapnode_or_link_class(ix, iy)
|
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
|
# second pass: Here we loop over all nodes and have them connect to each other
|
||||||
# via the detected linkages.
|
# via the detected linkages.
|
||||||
for node in node_index_map.values():
|
for node in node_index_map.values():
|
||||||
|
|
@ -1435,8 +1497,6 @@ class Map:
|
||||||
the full path including the start- and end-node.
|
the full path including the start- and end-node.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# from evennia import set_trace;set_trace()
|
|
||||||
|
|
||||||
startnode = self.get_node_from_coord(startcoord)
|
startnode = self.get_node_from_coord(startcoord)
|
||||||
endnode = self.get_node_from_coord(endcoord)
|
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 unittest import TestCase
|
||||||
from parameterized import parameterized
|
from parameterized import parameterized
|
||||||
from . import mapsystem
|
from . import mapsystem
|
||||||
|
|
||||||
|
|
||||||
MAP1 = """
|
MAP1 = """
|
||||||
|
|
||||||
+ 0 1 2
|
+ 0 1 2
|
||||||
|
|
@ -873,3 +874,112 @@ class TestMap11(TestCase):
|
||||||
character='@',
|
character='@',
|
||||||
max_size=max_size)
|
max_size=max_size)
|
||||||
self.assertEqual(expected, mapstr)
|
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