mirror of
https://github.com/evennia/evennia.git
synced 2026-03-23 00:06:30 +01:00
Add map teleporter markers
This commit is contained in:
parent
b7067fe1d2
commit
02e3256c8a
2 changed files with 334 additions and 116 deletions
|
|
@ -20,8 +20,8 @@ as up and down. These are indicated in code as 'n', 'ne', 'e', 'se', 's', 'sw',
|
|||
1
|
||||
+ 0 1 2 3 4 5 6 7 8 9 0
|
||||
|
||||
10 # #
|
||||
\ d
|
||||
10 # # # # #
|
||||
\ I I I d
|
||||
9 #-#-#-# |
|
||||
|\ | u
|
||||
8 #-#-#-#-----#-----o
|
||||
|
|
@ -34,7 +34,7 @@ as up and down. These are indicated in code as 'n', 'ne', 'e', 'se', 's', 'sw',
|
|||
/ |
|
||||
4 #-----+-# #---#
|
||||
\ | | \ /
|
||||
3 #-#-#-# x #
|
||||
3 #b#-#-# x #
|
||||
| | / \ u
|
||||
2 #-#-#---#
|
||||
^ d
|
||||
|
|
@ -93,6 +93,8 @@ except ImportError as err:
|
|||
"the SciPy package. Install with `pip install scipy'.")
|
||||
from evennia.utils.utils import variable_from_module, mod_import
|
||||
|
||||
_BIG = 999999999999
|
||||
|
||||
|
||||
_REVERSE_DIRECTIONS = {
|
||||
"n": "s",
|
||||
|
|
@ -102,7 +104,7 @@ _REVERSE_DIRECTIONS = {
|
|||
"s": "n",
|
||||
"sw": "ne",
|
||||
"w": "e",
|
||||
"nw": "se"
|
||||
"nw": "se",
|
||||
}
|
||||
|
||||
_MAPSCAN = {
|
||||
|
|
@ -113,16 +115,22 @@ _MAPSCAN = {
|
|||
"s": (0, -1),
|
||||
"sw": (-1, -1),
|
||||
"w": (-1, 0),
|
||||
"nw": (-1, 1)
|
||||
"nw": (-1, 1),
|
||||
}
|
||||
|
||||
_BIG = 999999999999
|
||||
|
||||
|
||||
# errors for Map system
|
||||
|
||||
class MapError(RuntimeError):
|
||||
pass
|
||||
|
||||
def __init__(self, error="", node_or_link=None):
|
||||
prefix = ""
|
||||
if node_or_link:
|
||||
prefix = (f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
|
||||
f"at XY=({node_or_link.X:g},{node_or_link.Y:g}) ")
|
||||
self.node_or_link = node_or_link
|
||||
self.message = f"{prefix}{error}"
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class MapParserError(MapError):
|
||||
pass
|
||||
|
|
@ -253,9 +261,8 @@ class MapNode:
|
|||
first_step_name = steps[0].direction_aliases.get(direction, direction)
|
||||
if first_step_name in self.closest_neighbor_names:
|
||||
raise MapParserError(
|
||||
f"MapNode '{self.symbol}' at XY=({self.X:g},{self.Y:g}) has more "
|
||||
f"than one outgoing direction '{first_step_name}'. All directions "
|
||||
"out of a node must be unique.")
|
||||
f"has more than one outgoing direction '{first_step_name}'. "
|
||||
"All directions out of a node must be unique.", self)
|
||||
self.closest_neighbor_names[first_step_name] = direction
|
||||
|
||||
node_index = end_node.node_index
|
||||
|
|
@ -441,21 +448,25 @@ class MapLink:
|
|||
end_direction = self.get_direction(start_direction, xygrid)
|
||||
if not end_direction:
|
||||
if _steps is None:
|
||||
# is perfectly okay to not be linking to a node
|
||||
# is perfectly okay to not be linking back on the first step (to a node)
|
||||
return None, 0, None
|
||||
raise MapParserError(f"Link '{self.symbol}' at "
|
||||
f"XY=({self.X:g},{self.Y:g}) "
|
||||
f"was connected to from the direction {start_direction}, but "
|
||||
"is not set up to link in that direction.")
|
||||
raise MapParserError(
|
||||
f"was connected to from the direction {start_direction}, but "
|
||||
"is not set up to link in that direction.", self)
|
||||
|
||||
dx, dy = _MAPSCAN[end_direction]
|
||||
# note that if `get_direction` returns an unknown direction, this will be equivalent
|
||||
# to pointing to an empty location, which makes sense
|
||||
dx, dy = _MAPSCAN.get(end_direction, (_BIG, _BIG))
|
||||
end_x, end_y = self.x + dx, self.y + dy
|
||||
try:
|
||||
next_target = xygrid[end_x][end_y]
|
||||
except KeyError:
|
||||
raise MapParserError(f"Link '{self.symbol}' at "
|
||||
f"XY=({self.X:g},{self.Y:g}) "
|
||||
"points to empty space in the direction {end_direction}!")
|
||||
# check if we have some special action up our sleeve
|
||||
next_target = self.at_empty_target(end_direction, xygrid)
|
||||
|
||||
if not next_target:
|
||||
raise MapParserError(
|
||||
f"points to empty space in the direction {end_direction}!", self)
|
||||
|
||||
_weight += self.get_weight(start_direction, xygrid, _weight)
|
||||
if _steps is None:
|
||||
|
|
@ -470,11 +481,10 @@ class MapLink:
|
|||
_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],
|
||||
_REVERSE_DIRECTIONS.get(end_direction, end_direction),
|
||||
xygrid, _weight=_weight, _linklen=_linklen + 1, _steps=_steps)
|
||||
|
||||
def get_linked_neighbors(self, xygrid, directions=None):
|
||||
|
|
@ -506,25 +516,26 @@ class MapLink:
|
|||
links[direction] = node_or_link
|
||||
return links
|
||||
|
||||
def get_display_symbol(self, xygrid, **kwargs):
|
||||
def at_empty_target(self, start_direction, end_direction, xygrid):
|
||||
"""
|
||||
Hook to override for customizing how the display_symbol is determined.
|
||||
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.
|
||||
|
||||
Kwargs:
|
||||
mapinstance (Map): The current Map instance.
|
||||
|
||||
Returns:
|
||||
str: The display-symbol to use. This must visually be a single character
|
||||
but could have color markers, use a unicode font etc.
|
||||
MapNode, MapLink or None: The next target to go to from here. `None` if this
|
||||
is an error that should be reported.
|
||||
|
||||
Notes:
|
||||
By default, just setting .display_symbol is enough.
|
||||
This is usually a mapping error (returning `None`) but may have practical use, such as
|
||||
teleporting or transitioning to another map.
|
||||
|
||||
"""
|
||||
return self.symbol if self.display_symbol is None else self.display_symbol
|
||||
return None
|
||||
|
||||
def get_direction(self, start_direction, xygrid, **kwargs):
|
||||
"""
|
||||
|
|
@ -563,14 +574,210 @@ class MapLink:
|
|||
"""
|
||||
return self.weights.get(start_direction, self.default_weight)
|
||||
|
||||
def get_display_symbol(self, xygrid, **kwargs):
|
||||
"""
|
||||
Hook to override for customizing how the display_symbol is determined.
|
||||
This is called after all other hooks, at map visualization.
|
||||
|
||||
Args:
|
||||
xygrid (dict): 2D dict with x,y coordinates as keys.
|
||||
|
||||
Kwargs:
|
||||
mapinstance (Map): The current Map instance.
|
||||
|
||||
Returns:
|
||||
str: The display-symbol to use. This must visually be a single character
|
||||
but could have color markers, use a unicode font etc.
|
||||
|
||||
Notes:
|
||||
By default, just setting .display_symbol is enough.
|
||||
|
||||
"""
|
||||
return self.symbol if self.display_symbol is None else self.display_symbol
|
||||
|
||||
class SmartRerouterMapLink(MapLink):
|
||||
r"""
|
||||
A 'smart' link without visible direction, but which uses its topological surroundings
|
||||
to figure out how it connects. All such links are two-way. It can be used to create 'knees' and
|
||||
multi-crossings of links. Remember that this is still a link, so user will not 'stop' at it,
|
||||
even if placed on an XY position!
|
||||
|
||||
If there are links on cardinally opposite sites, these are considered pass-throughs, and
|
||||
If determining the path of a set of input/output directions this is not possible, or there is an
|
||||
uneven number of links, an `MapParserError` is raised.
|
||||
|
||||
Example with the RedirectLink:
|
||||
::
|
||||
/
|
||||
-o - this is ok, there can only be one path, e-ne
|
||||
|
||||
|
|
||||
-o- - equivalent to '+', one n-s and one w-e link crossing
|
||||
|
|
||||
|
||||
\|/
|
||||
-o- - all are passing straight through
|
||||
/|\
|
||||
|
||||
-o- - w-e pass straight through, other link is sw-s
|
||||
/|
|
||||
|
||||
-o - invalid; impossible to know which input goes to which output
|
||||
/|
|
||||
|
||||
"""
|
||||
multilink = True
|
||||
|
||||
def get_direction(self, start_direction, xygrid):
|
||||
"""
|
||||
Dynamically determine the direction based on a source direction and grid topology.
|
||||
|
||||
"""
|
||||
# get all visually connected links
|
||||
if not self.directions:
|
||||
directions = {}
|
||||
unhandled_links = list(self.get_linked_neighbors(xygrid).keys())
|
||||
|
||||
# get all straight lines (n-s, sw-ne etc) we can trace through
|
||||
# the dynamic link and remove them from the unhandled_links list
|
||||
unhandled_links_copy = unhandled_links.copy()
|
||||
for direction in unhandled_links_copy:
|
||||
if _REVERSE_DIRECTIONS[direction] in unhandled_links_copy:
|
||||
directions[direction] = _REVERSE_DIRECTIONS[
|
||||
unhandled_links.pop(unhandled_links.index(direction))]
|
||||
|
||||
# check if we have any non-cross-through paths left to handle
|
||||
n_unhandled = len(unhandled_links)
|
||||
if n_unhandled:
|
||||
# still remaining unhandled links. If there's not exactly
|
||||
# one 'incoming' and one 'outgoing' we can't figure out
|
||||
# where to go in a non-ambiguous way.
|
||||
if n_unhandled != 2:
|
||||
links = ", ".join(unhandled_links)
|
||||
raise MapParserError(
|
||||
f"cannot determine how to connect in/out directions {links}.", self)
|
||||
|
||||
directions[unhandled_links[0]] = unhandled_links[1]
|
||||
directions[unhandled_links[1]] = unhandled_links[0]
|
||||
|
||||
self.directions = directions
|
||||
|
||||
return self.directions.get(start_direction)
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
one side one-way, the effect will be that of a one-way teleport.
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
t #
|
||||
/ | - moving ne from the left node will bring the user to the rightmost node
|
||||
-# t as if the two teleporters were connected (two way).
|
||||
|
||||
-#-t t># - one-way teleport from left to right.
|
||||
|
||||
#t - invalid, may only connect to another link
|
||||
|
||||
#-t-# - invalid, only one connected link is allowed.
|
||||
|
||||
"""
|
||||
symbol = 't'
|
||||
# usually invisible
|
||||
display_symbol = ' '
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.paired_teleporter = None
|
||||
|
||||
def at_empty_target(self, start_direction, xygrid):
|
||||
"""
|
||||
Called during traversal, when finding an unknown direction out of the link (same as
|
||||
targeting a link at an empty spot on the grid). This will also search for
|
||||
a unique, matching teleport to send to.
|
||||
|
||||
Args:
|
||||
start_direction (str): The direction (n, ne etc) from which this traversal originates
|
||||
for this link.
|
||||
xygrid (dict): 2D dict with x,y coordinates as keys.
|
||||
|
||||
Returns:
|
||||
TeleporterMapLink: The paired teleporter.
|
||||
|
||||
Raises:
|
||||
MapParserError: We raise this explicitly rather than returning `None` if we don't find
|
||||
another teleport. This avoids us getting the default (and in this case confusing)
|
||||
'pointing to an empty space' error we'd get if returning `None`.
|
||||
|
||||
"""
|
||||
if not self.paired_teleporter:
|
||||
# scan for another teleporter
|
||||
symbol = self.symbol
|
||||
found_teleporters = []
|
||||
for iy, line in xygrid.items():
|
||||
for ix, node_or_link in xygrid[iy].items():
|
||||
if node_or_link.symbol == symbol and node_or_link is not self:
|
||||
found_teleporters.append(node_or_link)
|
||||
|
||||
if not found_teleporters:
|
||||
raise MapParserError("found no matching teleporter to link to.", self)
|
||||
if len(found_teleporters) > 1:
|
||||
raise MapParserError(
|
||||
"found too many matching teleporters (must be exactly one more): "
|
||||
f"{found_teleporters}", self)
|
||||
|
||||
other_teleporter = found_teleporters[0]
|
||||
# link the two so we don't need to scan again for the other one
|
||||
self.paired_teleporter = other_teleporter
|
||||
other_teleporter.paired_teleporter = self
|
||||
|
||||
return self.paired_teleporter
|
||||
|
||||
def get_direction(self, start_direction, xygrid):
|
||||
"""
|
||||
Figure out the connected link and paired teleport.
|
||||
|
||||
"""
|
||||
if not self.directions:
|
||||
neighbors = self.get_linked_neighbors(xygrid)
|
||||
|
||||
if len(neighbors) != 1:
|
||||
raise MapParserError("must have exactly one link connected to it.", self)
|
||||
direction, link = next(iter(neighbors.items()))
|
||||
if hasattr(link, 'node_index'):
|
||||
raise MapParserError("can only connect to a Link. Found {link} in "
|
||||
"direction {direction}.", self)
|
||||
# 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':
|
||||
# called while traversing another teleport
|
||||
# - we must make sure we can always access/leave the teleport.
|
||||
self.directions = {"teleport": direction,
|
||||
direction: "teleport"}
|
||||
else:
|
||||
# called while traversing a normal link
|
||||
self.directions = {start_direction: "teleport",
|
||||
"teleport": direction}
|
||||
|
||||
return self.directions.get(start_direction)
|
||||
|
||||
|
||||
class SmartMapLink(MapLink):
|
||||
"""
|
||||
A 'smart' link withot visible direction, but which uses its topological surroundings
|
||||
to figure out how it connects. A limited link will prefer to connect two Nodes directly and
|
||||
if there are more than two nodes directly neighboring, it will raise an MapParserError.
|
||||
If two nodes are not found, it will link to any combination of links- or nodes as long as
|
||||
it can un-ambiguously determine which direction they lead.
|
||||
to figure out how it connects. Unlike the `SmartRerouterMapLink`, this link type is
|
||||
also a 'direction' of its own and can thus connect directly to nodes. It can only describe
|
||||
one transition and will prefer connecting two nodes if there are other possibilities. If the
|
||||
linking is unclear or there are more than two nodes directly neighboring, a MapParserError will
|
||||
be raised. If two nodes are not found, it will link to any combination of links- or nodes as
|
||||
long as it can un-ambiguously determine which direction they lead.
|
||||
|
||||
Placing a smart-link directly between two nodes/links will always be a two-way connection,
|
||||
whereas if it connects a node with another link, it will be a one-way connection in the
|
||||
|
|
@ -629,10 +836,9 @@ class SmartMapLink(MapLink):
|
|||
directions[direction] = _REVERSE_DIRECTIONS[direction]
|
||||
else:
|
||||
raise MapParserError(
|
||||
f"MapLink '{self.symbol}' at "
|
||||
f"XY=({self.X:g},{self.Y:g}) must have exactly two connections - either "
|
||||
f"must have exactly two connections - either "
|
||||
f"two nodes or unambiguous link directions. Found neighbor(s) in directions "
|
||||
f"{list(neighbors.keys())}.")
|
||||
f"{list(neighbors.keys())}.", self)
|
||||
|
||||
self.directions = directions
|
||||
return self.directions.get(start_direction)
|
||||
|
|
@ -709,79 +915,6 @@ class InvisibleSmartMapLink(SmartMapLink):
|
|||
return self._cached_display_symbol
|
||||
|
||||
|
||||
class SmartRerouterMapLink(MapLink):
|
||||
r"""
|
||||
A 'smart' link without visible direction, but which uses its topological surroundings
|
||||
to figure out how it connects. The rerouter can only be connected to with other links, not
|
||||
by nodes. All such links are two-way. It can be used to create 'knees' and multi-crossings
|
||||
of links. Remember that this is still a link, so user will not 'stop' at it, even if
|
||||
placed on an XY position!
|
||||
|
||||
If there are links on cardinally opposite sites, these are considered pass-throughs, and
|
||||
If determining the path of a set of input/output directions this is not possible, or there is an
|
||||
uneven number of links, an `MapParserError` is raised.
|
||||
|
||||
Example with the RedirectLink:
|
||||
::
|
||||
/
|
||||
-o - this is ok, there can only be one path, e-ne
|
||||
|
||||
|
|
||||
-o- - equivalent to '+', one n-s and one w-e link crossing
|
||||
|
|
||||
|
||||
\|/
|
||||
-o- - all are passing straight through
|
||||
/|\
|
||||
|
||||
-o- - w-e pass straight through, other link is sw-s
|
||||
/|
|
||||
|
||||
-o - invalid; impossible to know which input goes to which output
|
||||
/|
|
||||
|
||||
"""
|
||||
multilink = True
|
||||
|
||||
def get_direction(self, start_direction, xygrid):
|
||||
"""
|
||||
Dynamically determine the direction based on a source direction and grid topology.
|
||||
|
||||
"""
|
||||
# get all visually connected links
|
||||
if not self.directions:
|
||||
directions = {}
|
||||
unhandled_links = list(self.get_linked_neighbors(xygrid).keys())
|
||||
|
||||
# get all straight lines (n-s, sw-ne etc) we can trace through
|
||||
# the dynamic link and remove them from the unhandled_links list
|
||||
unhandled_links_copy = unhandled_links.copy()
|
||||
for direction in unhandled_links_copy:
|
||||
if _REVERSE_DIRECTIONS[direction] in unhandled_links_copy:
|
||||
directions[direction] = _REVERSE_DIRECTIONS[
|
||||
unhandled_links.pop(unhandled_links.index(direction))]
|
||||
|
||||
# check if we have any non-cross-through paths left to handle
|
||||
n_unhandled = len(unhandled_links)
|
||||
if n_unhandled:
|
||||
# still remaining unhandled links. If there's not exactly
|
||||
# one 'incoming' and one 'outgoing' we can't figure out
|
||||
# where to go in a non-ambiguous way.
|
||||
if n_unhandled != 2:
|
||||
links = ", ".join(unhandled_links)
|
||||
raise MapParserError(
|
||||
f"MapLink '{self.symbol}' at "
|
||||
f"XY=({self.X:g},{self.Y:g}) cannot determine "
|
||||
f"how to connect in/out directions {links}.")
|
||||
|
||||
directions[unhandled_links[0]] = unhandled_links[1]
|
||||
directions[unhandled_links[1]] = unhandled_links[0]
|
||||
|
||||
self.directions = directions
|
||||
|
||||
return self.directions.get(start_direction)
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Default nodes and link classes
|
||||
|
||||
|
|
@ -913,6 +1046,7 @@ DEFAULT_LEGEND = {
|
|||
"d": DownMapLink,
|
||||
"b": BlockedMapLink,
|
||||
"i": InterruptMapLink,
|
||||
't': TeleporterMapLink,
|
||||
}
|
||||
|
||||
# --------------------------------------------
|
||||
|
|
@ -1106,9 +1240,10 @@ class Map:
|
|||
|
||||
mapstring = self.mapstring
|
||||
if mapstring.count(mapcorner_symbol) < 2:
|
||||
raise MapParserError(f"The mapstring must have at least two '{mapcorner_symbol}' "
|
||||
"symbols marking the upper- and bottom-left corners of the "
|
||||
"grid area.")
|
||||
raise MapParserError(
|
||||
f"The mapstring must have at least two '{mapcorner_symbol}' "
|
||||
"symbols marking the upper- and bottom-left corners of the "
|
||||
"grid area.")
|
||||
|
||||
# find the the position (in the string as a whole) of the top-left corner-marker
|
||||
maplines = mapstring.split("\n")
|
||||
|
|
@ -1184,8 +1319,9 @@ class Map:
|
|||
|
||||
else:
|
||||
# we have a link at this xygrid position (this is ok everywhere)
|
||||
xygrid[ix][iy] = mapnode_or_link_class(x=ix, y=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
|
||||
# via the detected linkages.
|
||||
for node in node_index_map.values():
|
||||
|
|
@ -1299,6 +1435,8 @@ 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -289,6 +289,30 @@ MAP10_DISPLAY = r"""
|
|||
""".strip()
|
||||
|
||||
|
||||
MAP11 = r"""
|
||||
|
||||
+ 0 1 2 3
|
||||
|
||||
2 #-#
|
||||
\
|
||||
1 t t
|
||||
\
|
||||
0 #-#
|
||||
|
||||
+ 0 1 2 3
|
||||
|
||||
"""
|
||||
|
||||
|
||||
MAP11_DISPLAY = r"""
|
||||
#-#
|
||||
\
|
||||
|
||||
\
|
||||
#-#
|
||||
""".strip()
|
||||
|
||||
|
||||
class TestMap1(TestCase):
|
||||
"""
|
||||
Test the Map class with a simple 4-node map
|
||||
|
|
@ -793,3 +817,59 @@ class TestMap10(TestCase):
|
|||
self.assertEqual(expected_path, tuple(strpositions))
|
||||
|
||||
|
||||
class TestMap11(TestCase):
|
||||
"""
|
||||
Test Map11 - a map teleporter links.
|
||||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP11})
|
||||
|
||||
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(MAP11_DISPLAY, stripped_map)
|
||||
|
||||
@parameterized.expand([
|
||||
((2, 0), (1, 2), ('e', 'nw', 'e')),
|
||||
((1, 2), (2, 0), ('w', 'se', '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))
|
||||
|
||||
@parameterized.expand([
|
||||
((3, 0), (0, 2), ('nw', ),
|
||||
((3, 0), (2.5, 0.5), (2.0, 1.0), (1.0, 1.0), (0.5, 1.5), (0, 2))),
|
||||
((0, 2), (3, 0), ('se', ),
|
||||
((0, 2), (0.5, 1.5), (1.0, 1.0), (2.0, 1.0), (2.5, 0.5), (3, 0))),
|
||||
])
|
||||
def test_paths(self, startcoord, endcoord, expected_directions, expected_path):
|
||||
"""
|
||||
Test path locations.
|
||||
|
||||
"""
|
||||
directions, path = self.map.get_shortest_path(startcoord, endcoord)
|
||||
self.assertEqual(expected_directions, tuple(directions))
|
||||
strpositions = [(step.X, step.Y) for step in path]
|
||||
self.assertEqual(expected_path, tuple(strpositions))
|
||||
|
||||
@parameterized.expand([
|
||||
((2, 0), (1, 2), 3, None, '..# \n . \n . . \n . \n @..'),
|
||||
((1, 2), (2, 0), 3, None, '..@ \n . \n . . \n . \n #..'),
|
||||
|
||||
])
|
||||
def test_get_visual_range_with_path(self, coord, target, dist, max_size, expected):
|
||||
"""
|
||||
Get visual range with a path-to-target marked.
|
||||
|
||||
"""
|
||||
mapstr = self.map.get_visual_range(coord, dist=dist, mode='nodes',
|
||||
target=target, target_path_style=".",
|
||||
character='@',
|
||||
max_size=max_size)
|
||||
self.assertEqual(expected, mapstr)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue