Add block and interrupt nodes/links to map
This commit is contained in:
parent
0926067a33
commit
9c0985cf22
2 changed files with 268 additions and 40 deletions
|
|
@ -146,9 +146,14 @@ class MapNode:
|
||||||
tags around it. For further customization, the `.get_display_symbol` method receives
|
tags around it. For further customization, the `.get_display_symbol` method receives
|
||||||
the full grid and can return a dynamically determined display symbol. If set to `None`,
|
the full grid and can return a dynamically determined display symbol. If set to `None`,
|
||||||
the `symbol` is used.
|
the `symbol` is used.
|
||||||
|
- `interrupt_path` (bool): If this is set, the shortest-path algorithm will include this
|
||||||
|
node as normally, but stop when reaching it, even if not having reached its target yet. This
|
||||||
|
is useful for marking 'points of interest' along a route, or places where you are not
|
||||||
|
expected to be able to continue without some further in-game action not covered by the map
|
||||||
|
(such as a guard or locked gate etc).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# symbol used in map definition
|
# symbol used to identify this link on the map
|
||||||
symbol = '#'
|
symbol = '#'
|
||||||
# if printing this node should show another symbol. If set
|
# if printing this node should show another symbol. If set
|
||||||
# to the empty string, use `symbol`.
|
# to the empty string, use `symbol`.
|
||||||
|
|
@ -159,8 +164,11 @@ class MapNode:
|
||||||
|
|
||||||
# this should always be left True and avoids inifinite loops during querying.
|
# this should always be left True and avoids inifinite loops during querying.
|
||||||
multilink = True
|
multilink = True
|
||||||
|
# this will interrupt a shortest-path step (useful for 'points' of interest, stop before
|
||||||
|
# a door etc).
|
||||||
|
interrupt_path = False
|
||||||
|
|
||||||
def __init__(self, x, y, node_index):
|
def __init__(self, x, y, node_index=0):
|
||||||
"""
|
"""
|
||||||
Initialize the mapnode.
|
Initialize the mapnode.
|
||||||
|
|
||||||
|
|
@ -195,6 +203,8 @@ class MapNode:
|
||||||
# {startdirection: [direction, ...], ...}
|
# {startdirection: [direction, ...], ...}
|
||||||
# where the directional path-lists also include the start-direction
|
# where the directional path-lists also include the start-direction
|
||||||
self.xy_steps_to_node = {}
|
self.xy_steps_to_node = {}
|
||||||
|
# direction-names of the closest neighbors to the node
|
||||||
|
self.closest_neighbor_names = {}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"<MapNode '{self.symbol}' {self.node_index} XY=({self.X},{self.Y})"
|
return f"<MapNode '{self.symbol}' {self.node_index} XY=({self.X},{self.Y})"
|
||||||
|
|
@ -235,6 +245,19 @@ class MapNode:
|
||||||
|
|
||||||
if end_node:
|
if end_node:
|
||||||
# the link could be followed to an end node!
|
# the link could be followed to an end node!
|
||||||
|
|
||||||
|
# check the actual direction-alias to use, since this may be
|
||||||
|
# different than the xygrid cardinal directions. There must be
|
||||||
|
# no duplicates out of this node or there will be a
|
||||||
|
# multi-match error later!
|
||||||
|
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.")
|
||||||
|
self.closest_neighbor_names[first_step_name] = direction
|
||||||
|
|
||||||
node_index = end_node.node_index
|
node_index = end_node.node_index
|
||||||
self.weights[node_index] = weight
|
self.weights[node_index] = weight
|
||||||
self.links[direction] = end_node
|
self.links[direction] = end_node
|
||||||
|
|
@ -247,8 +270,7 @@ class MapNode:
|
||||||
# shortest-route solver
|
# shortest-route solver
|
||||||
shortest_route = self.shortest_route_to_node.get(node_index, ("", [], _BIG))[2]
|
shortest_route = self.shortest_route_to_node.get(node_index, ("", [], _BIG))[2]
|
||||||
if weight < shortest_route:
|
if weight < shortest_route:
|
||||||
self.shortest_route_to_node[node_index] = (
|
self.shortest_route_to_node[node_index] = (first_step_name, steps, weight)
|
||||||
steps[0].direction_aliases.get(direction, direction), steps, weight)
|
|
||||||
|
|
||||||
def linkweights(self, nnodes):
|
def linkweights(self, nnodes):
|
||||||
"""
|
"""
|
||||||
|
|
@ -335,10 +357,12 @@ class MapLink:
|
||||||
- `multilink` (bool): If set, this link accepts links from all directions. It will usually
|
- `multilink` (bool): If set, this link accepts links from all directions. It will usually
|
||||||
use a custom get_direction to determine what these are based on surrounding topology. This
|
use a custom get_direction to determine what these are based on surrounding topology. This
|
||||||
setting is necessary to avoid infinite loops when such multilinks are next to each other.
|
setting is necessary to avoid infinite loops when such multilinks are next to each other.
|
||||||
|
- `interrupt_path` (bool): If set, a shortest-path solution will include this link as normal,
|
||||||
|
but will stop short of actually moving past this link.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# link setup
|
# symbol for identifying this link on the map
|
||||||
symbol = "|"
|
symbol = ""
|
||||||
# if `None`, use .symbol
|
# if `None`, use .symbol
|
||||||
display_symbol = None
|
display_symbol = None
|
||||||
default_weight = 1
|
default_weight = 1
|
||||||
|
|
@ -367,6 +391,9 @@ class MapLink:
|
||||||
# this shortcuts neighbors trying to figure out if they can connect to this link
|
# this shortcuts neighbors trying to figure out if they can connect to this link
|
||||||
# - if this is set, they always can (similarly as to a node)
|
# - if this is set, they always can (similarly as to a node)
|
||||||
multilink = False
|
multilink = False
|
||||||
|
# this link does not block/reroute pathfinding, but makes the actual path always stop when
|
||||||
|
# trying to cross it.
|
||||||
|
interrupt_path = False
|
||||||
|
|
||||||
def __init__(self, x, y):
|
def __init__(self, x, y):
|
||||||
"""
|
"""
|
||||||
|
|
@ -486,6 +513,9 @@ class MapLink:
|
||||||
Args:
|
Args:
|
||||||
xygrid (dict): 2D dict with x,y coordinates as keys.
|
xygrid (dict): 2D dict with x,y coordinates as keys.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
mapinstance (Map): The current Map instance.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The display-symbol to use. This must visually be a single character
|
str: The display-symbol to use. This must visually be a single character
|
||||||
but could have color markers, use a unicode font etc.
|
but could have color markers, use a unicode font etc.
|
||||||
|
|
@ -542,7 +572,7 @@ class SmartMapLink(MapLink):
|
||||||
If two nodes are not found, it will link to any combination of links- or nodes as long as
|
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.
|
it can un-ambiguously determine which direction they lead.
|
||||||
|
|
||||||
Placing a limited link directly between two nodes/links will always be a two-way connection,
|
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
|
whereas if it connects a node with another link, it will be a one-way connection in the
|
||||||
direction of the link.
|
direction of the link.
|
||||||
|
|
||||||
|
|
@ -550,7 +580,12 @@ class SmartMapLink(MapLink):
|
||||||
::
|
::
|
||||||
|
|
||||||
#
|
#
|
||||||
u - moving up and down from the two nodes (two-way)
|
u - moving up in BOTH directions will bring you to the other node (two-way)
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
d - this better represents the 'real' up/down behavior.
|
||||||
|
u
|
||||||
#
|
#
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -558,13 +593,12 @@ class SmartMapLink(MapLink):
|
||||||
u
|
u
|
||||||
#
|
#
|
||||||
|
|
||||||
#
|
#-#
|
||||||
d - (this would be equivalent to the first example but with a longer link)
|
u - okay since the up-link prioritizes the nodes
|
||||||
u
|
|
||||||
#
|
#
|
||||||
|
|
||||||
#u#
|
#u#
|
||||||
u - This is okay since they up-links will prefer connecting their nodes
|
u - invalid since top-left node has two 'up' directions to go to
|
||||||
#
|
#
|
||||||
|
|
||||||
# |
|
# |
|
||||||
|
|
@ -580,8 +614,7 @@ class SmartMapLink(MapLink):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# get all visually connected links
|
# get all visually connected links
|
||||||
if not hasattr(self, '_cached_directions'):
|
if not self.directions:
|
||||||
# rebuild cache
|
|
||||||
directions = {}
|
directions = {}
|
||||||
neighbors = self.get_linked_neighbors(xygrid)
|
neighbors = self.get_linked_neighbors(xygrid)
|
||||||
nodes = [direction for direction, neighbor in neighbors.items()
|
nodes = [direction for direction, neighbor in neighbors.items()
|
||||||
|
|
@ -601,8 +634,79 @@ class SmartMapLink(MapLink):
|
||||||
f"two nodes or unambiguous link directions. Found neighbor(s) in directions "
|
f"two nodes or unambiguous link directions. Found neighbor(s) in directions "
|
||||||
f"{list(neighbors.keys())}.")
|
f"{list(neighbors.keys())}.")
|
||||||
|
|
||||||
self._cached_directions = directions
|
self.directions = directions
|
||||||
return self._cached_directions.get(start_direction)
|
return self.directions.get(start_direction)
|
||||||
|
|
||||||
|
|
||||||
|
class InvisibleSmartMapLink(SmartMapLink):
|
||||||
|
"""
|
||||||
|
This is a smart maplink that does not show as such on the map - instead it will figure out
|
||||||
|
how it should look had it been one of the 'normal' cardinal-direction links and display
|
||||||
|
itself as that instead. This doesn't change its functionality, only the symbol shown
|
||||||
|
on the map display. This only works for cardinal-direction links.
|
||||||
|
|
||||||
|
It makes use of `display_symbol_aliases` mapping, which maps a sorted set of
|
||||||
|
`((start, end), (end, start))` (two-way) or `((start, end),)` (one-way) directions
|
||||||
|
to a symbol in the current map legend - this is the symbol alias to use. The matching
|
||||||
|
MapLink or MapNode will be initialized at the current position only for the purpose of getting
|
||||||
|
its display_symbol.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
display_symbol_aliases = `{(('n', 's'), ('s', n')): '|', ...}`
|
||||||
|
|
||||||
|
If no `display_symbol_aliases` are given, the regular display_symbol is used.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this allows for normal movement directions even if the invisible-node
|
||||||
|
# is marked with a different symbol.
|
||||||
|
direction_aliases = {
|
||||||
|
'n': 'n', 'ne': 'ne', 'e': 'e', 'se': 'se',
|
||||||
|
's': 's', 'sw': 'sw', 'w': 'w', 'nw': 'nw'
|
||||||
|
}
|
||||||
|
|
||||||
|
# replace current link position with what the smart links "should" look like
|
||||||
|
display_symbol_aliases = {
|
||||||
|
(('n', 's'), ('s', 'n')): '|',
|
||||||
|
(('n', 's'),): 'v',
|
||||||
|
(('s', 'n')): '^',
|
||||||
|
(('e', 'w'), ('w', 'e')): '-',
|
||||||
|
(('e', 'w'),): '>',
|
||||||
|
(('w', 'e'),): '<',
|
||||||
|
(('nw', 'se'), ('sw', 'ne')): '\\',
|
||||||
|
(('ne', 'sw'), ('sw', 'ne')): '/',
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_display_symbol(self, xygrid, **kwargs):
|
||||||
|
"""
|
||||||
|
The SmartMapLink already calculated the directions before this, so we
|
||||||
|
just need to figure out what to replace this with in order to make this 'invisible'
|
||||||
|
|
||||||
|
Depending on how we are connected, we figure out how the 'normal' link
|
||||||
|
should look and use that instead.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not hasattr(self, "_cached_display_symbol"):
|
||||||
|
mapinstance = kwargs['mapinstance']
|
||||||
|
|
||||||
|
legend = mapinstance.legend
|
||||||
|
default_symbol = (
|
||||||
|
self.symbol if self.display_symbol is None else self.display_symbol)
|
||||||
|
self._cached_display_symbol = default_symbol
|
||||||
|
|
||||||
|
dirtuple = tuple((key, self.directions[key])
|
||||||
|
for key in sorted(self.directions.keys()))
|
||||||
|
|
||||||
|
replacement_symbol = self.display_symbol_aliases.get(dirtuple, default_symbol)
|
||||||
|
|
||||||
|
if replacement_symbol != self.symbol:
|
||||||
|
node_or_link_class = legend.get(replacement_symbol)
|
||||||
|
if node_or_link_class:
|
||||||
|
# initiate class in the current location and run get_display_symbol
|
||||||
|
# to get what it would show.
|
||||||
|
self._cached_display_symbol = node_or_link_class(
|
||||||
|
self.x, self.y).get_display_symbol(xygrid, **kwargs)
|
||||||
|
return self._cached_display_symbol
|
||||||
|
|
||||||
|
|
||||||
class SmartRerouterMapLink(MapLink):
|
class SmartRerouterMapLink(MapLink):
|
||||||
|
|
@ -645,8 +749,7 @@ class SmartRerouterMapLink(MapLink):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# get all visually connected links
|
# get all visually connected links
|
||||||
if not hasattr(self, '_cached_directions'):
|
if not self.directions:
|
||||||
# try to get from cache where possible
|
|
||||||
directions = {}
|
directions = {}
|
||||||
unhandled_links = list(self.get_linked_neighbors(xygrid).keys())
|
unhandled_links = list(self.get_linked_neighbors(xygrid).keys())
|
||||||
|
|
||||||
|
|
@ -674,14 +777,24 @@ class SmartRerouterMapLink(MapLink):
|
||||||
directions[unhandled_links[0]] = unhandled_links[1]
|
directions[unhandled_links[0]] = unhandled_links[1]
|
||||||
directions[unhandled_links[1]] = unhandled_links[0]
|
directions[unhandled_links[1]] = unhandled_links[0]
|
||||||
|
|
||||||
self._cached_directions = directions
|
self.directions = directions
|
||||||
|
|
||||||
return self._cached_directions.get(start_direction)
|
return self.directions.get(start_direction)
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------
|
# ----------------------------------
|
||||||
# Default nodes and link classes
|
# Default nodes and link classes
|
||||||
|
|
||||||
|
class BasicMapNode(MapNode):
|
||||||
|
"""Basic map Node"""
|
||||||
|
symbol = "#"
|
||||||
|
|
||||||
|
class InterruptMapNode(MapNode):
|
||||||
|
"""A point of interest, where pathfinder will stop"""
|
||||||
|
symbol = "i"
|
||||||
|
display_symbol = "#"
|
||||||
|
interrupt_path = True
|
||||||
|
|
||||||
class NSMapLink(MapLink):
|
class NSMapLink(MapLink):
|
||||||
"""Two-way, North-South link"""
|
"""Two-way, North-South link"""
|
||||||
symbol = "|"
|
symbol = "|"
|
||||||
|
|
@ -744,14 +857,35 @@ class WEOneWayMapLink(MapLink):
|
||||||
class UpMapLink(SmartMapLink):
|
class UpMapLink(SmartMapLink):
|
||||||
"""Up direction. Note that this still uses the xygrid!"""
|
"""Up direction. Note that this still uses the xygrid!"""
|
||||||
symbol = 'u'
|
symbol = 'u'
|
||||||
|
|
||||||
# all movement over this link is 'up', regardless of where on the xygrid we move.
|
# all movement over this link is 'up', regardless of where on the xygrid we move.
|
||||||
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
||||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
||||||
|
|
||||||
|
|
||||||
class DownMapLink(UpMapLink):
|
class DownMapLink(UpMapLink):
|
||||||
"""Works exactly like `UpMapLink` but for the 'down' direction."""
|
"""Works exactly like `UpMapLink` but for the 'down' direction."""
|
||||||
symbol = 'd'
|
symbol = 'd'
|
||||||
|
# all movement over this link is 'down', regardless of where on the xygrid we move.
|
||||||
|
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
||||||
|
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
||||||
|
|
||||||
|
|
||||||
|
class InterruptMapLink(InvisibleSmartMapLink):
|
||||||
|
"""A (still passable) link that causes the pathfinder to stop before crossing."""
|
||||||
|
symbol = "i"
|
||||||
|
interrupt_path = True
|
||||||
|
|
||||||
|
|
||||||
|
class BlockedMapLink(InvisibleSmartMapLink):
|
||||||
|
"""
|
||||||
|
A high-weight (but still passable) link that causes the shortest-path algorithm to consider this
|
||||||
|
a blocked path. The block will not show up in the map display, paths will just never use this
|
||||||
|
link.
|
||||||
|
|
||||||
|
"""
|
||||||
|
symbol = 'b'
|
||||||
|
weights = {'n': _BIG, 'ne': _BIG, 'e': _BIG, 'se': _BIG,
|
||||||
|
's': _BIG, 'sw': _BIG, 'w': _BIG, 'nw': _BIG}
|
||||||
|
|
||||||
|
|
||||||
class RouterMapLink(SmartRerouterMapLink):
|
class RouterMapLink(SmartRerouterMapLink):
|
||||||
|
|
@ -762,7 +896,8 @@ class RouterMapLink(SmartRerouterMapLink):
|
||||||
# these are all symbols used for x,y coordinate spots
|
# these are all symbols used for x,y coordinate spots
|
||||||
# at (0,1) etc.
|
# at (0,1) etc.
|
||||||
DEFAULT_LEGEND = {
|
DEFAULT_LEGEND = {
|
||||||
"#": MapNode,
|
"#": BasicMapNode,
|
||||||
|
"I": InterruptMapNode,
|
||||||
"|": NSMapLink,
|
"|": NSMapLink,
|
||||||
"-": EWMapLink,
|
"-": EWMapLink,
|
||||||
"/": NESWMapLink,
|
"/": NESWMapLink,
|
||||||
|
|
@ -776,6 +911,8 @@ DEFAULT_LEGEND = {
|
||||||
"o": RouterMapLink,
|
"o": RouterMapLink,
|
||||||
"u": UpMapLink,
|
"u": UpMapLink,
|
||||||
"d": DownMapLink,
|
"d": DownMapLink,
|
||||||
|
"b": BlockedMapLink,
|
||||||
|
"i": InterruptMapLink,
|
||||||
}
|
}
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
@ -973,7 +1110,6 @@ class Map:
|
||||||
"symbols marking the upper- and bottom-left corners of the "
|
"symbols marking the upper- and bottom-left corners of the "
|
||||||
"grid area.")
|
"grid area.")
|
||||||
|
|
||||||
# from evennia import set_trace;set_trace()
|
|
||||||
# find the the position (in the string as a whole) of the top-left corner-marker
|
# find the the position (in the string as a whole) of the top-left corner-marker
|
||||||
maplines = mapstring.split("\n")
|
maplines = mapstring.split("\n")
|
||||||
topleft_marker_x, topleft_marker_y = -1, -1
|
topleft_marker_x, topleft_marker_y = -1, -1
|
||||||
|
|
@ -1059,7 +1195,7 @@ class Map:
|
||||||
display_map = [[" "] * (max_x + 1) for _ in range(max_y + 1)]
|
display_map = [[" "] * (max_x + 1) for _ in range(max_y + 1)]
|
||||||
for ix, ydct in xygrid.items():
|
for ix, ydct in xygrid.items():
|
||||||
for iy, node_or_link in ydct.items():
|
for iy, node_or_link in ydct.items():
|
||||||
display_map[iy][ix] = node_or_link.get_display_symbol(xygrid)
|
display_map[iy][ix] = node_or_link.get_display_symbol(xygrid, mapinstance=self)
|
||||||
|
|
||||||
# store
|
# store
|
||||||
self.max_x, self.max_y = max_x, max_y
|
self.max_x, self.max_y = max_x, max_y
|
||||||
|
|
@ -1183,7 +1319,6 @@ class Map:
|
||||||
pathfinding_routes = self.pathfinding_routes
|
pathfinding_routes = self.pathfinding_routes
|
||||||
node_index_map = self.node_index_map
|
node_index_map = self.node_index_map
|
||||||
|
|
||||||
# from evennia import set_trace;set_trace()
|
|
||||||
path = [endnode]
|
path = [endnode]
|
||||||
directions = []
|
directions = []
|
||||||
|
|
||||||
|
|
@ -1193,15 +1328,23 @@ class Map:
|
||||||
# we are working backwards).
|
# we are working backwards).
|
||||||
inextnode = pathfinding_routes[istartnode, inextnode]
|
inextnode = pathfinding_routes[istartnode, inextnode]
|
||||||
nextnode = node_index_map[inextnode]
|
nextnode = node_index_map[inextnode]
|
||||||
|
|
||||||
shortest_route_to = nextnode.shortest_route_to_node[path[-1].node_index]
|
shortest_route_to = nextnode.shortest_route_to_node[path[-1].node_index]
|
||||||
|
|
||||||
directions.append(shortest_route_to[0])
|
directions.append(shortest_route_to[0])
|
||||||
path.extend(shortest_route_to[1] + [nextnode])
|
path.extend(shortest_route_to[1][::-1] + [nextnode])
|
||||||
|
|
||||||
|
if any(1 for step in shortest_route_to[1] if step.interrupt_path):
|
||||||
|
# detected an interrupt in linkage - discard what we have so far
|
||||||
|
directions = []
|
||||||
|
path = [nextnode]
|
||||||
|
|
||||||
|
if nextnode.interrupt_path and nextnode is not startnode:
|
||||||
|
directions = []
|
||||||
|
path = [nextnode]
|
||||||
|
|
||||||
# we have the path - reverse to get the correct order
|
# we have the path - reverse to get the correct order
|
||||||
path = path[::-1]
|
|
||||||
directions = directions[::-1]
|
directions = directions[::-1]
|
||||||
|
path = path[::-1]
|
||||||
|
|
||||||
return directions, path
|
return directions, path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -234,26 +234,58 @@ o-o-#-# o
|
||||||
MAP9 = r"""
|
MAP9 = r"""
|
||||||
+ 0 1 2 3
|
+ 0 1 2 3
|
||||||
|
|
||||||
3 #-# #-#
|
3 #-#-#-#
|
||||||
d d |
|
d d d
|
||||||
2 | | |
|
2 | | |
|
||||||
u | u
|
u u u
|
||||||
1 #-#-#-#
|
1 #-# #-#
|
||||||
u d
|
u d
|
||||||
0 #u# #d#
|
0 #d# #u#
|
||||||
|
|
||||||
+ 0 1 2 3
|
+ 0 1 2 3
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MAP9_DISPLAY = r"""
|
MAP9_DISPLAY = r"""
|
||||||
#-# #-#
|
|
||||||
d d |
|
|
||||||
| | |
|
|
||||||
u | u
|
|
||||||
#-#-#-#
|
#-#-#-#
|
||||||
|
d d d
|
||||||
|
| | |
|
||||||
|
u u u
|
||||||
|
#-# #-#
|
||||||
u d
|
u d
|
||||||
#u# #d#
|
#d# #u#
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
|
MAP10 = r"""
|
||||||
|
|
||||||
|
+ 0 1 2 3
|
||||||
|
|
||||||
|
4 #---#-#
|
||||||
|
b |
|
||||||
|
3 #i#---#
|
||||||
|
|/|
|
||||||
|
2 # #-I-#
|
||||||
|
|
|
||||||
|
1 #-#b#-#
|
||||||
|
| | b
|
||||||
|
0 #b#-#-#
|
||||||
|
|
||||||
|
+ 0 1 2 3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# note that I,i,b are invisible
|
||||||
|
MAP10_DISPLAY = r"""
|
||||||
|
#---#-#
|
||||||
|
/ |
|
||||||
|
#-#---#
|
||||||
|
|/|
|
||||||
|
# #-#-#
|
||||||
|
|
|
||||||
|
#-#-#-#
|
||||||
|
| | |
|
||||||
|
#-#-#-#
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -686,7 +718,7 @@ class TestMap8(TestCase):
|
||||||
|
|
||||||
class TestMap9(TestCase):
|
class TestMap9(TestCase):
|
||||||
"""
|
"""
|
||||||
Test the Map class with a simple 4-node map
|
Test Map9 - a map with up/down links.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -699,7 +731,9 @@ class TestMap9(TestCase):
|
||||||
|
|
||||||
@parameterized.expand([
|
@parameterized.expand([
|
||||||
((0, 0), (0, 1), ('u',)),
|
((0, 0), (0, 1), ('u',)),
|
||||||
((0, 0), (1, 0), ('u',)),
|
((0, 0), (1, 0), ('d',)),
|
||||||
|
((1, 0), (2, 1), ('d', 'u', 'e', 'u', 'e', 'd')),
|
||||||
|
((2, 1), (0, 1), ('u', 'w', 'd', 'w')),
|
||||||
])
|
])
|
||||||
def test_shortest_path(self, startcoord, endcoord, expected_directions):
|
def test_shortest_path(self, startcoord, endcoord, expected_directions):
|
||||||
"""
|
"""
|
||||||
|
|
@ -708,3 +742,54 @@ class TestMap9(TestCase):
|
||||||
"""
|
"""
|
||||||
directions, _ = self.map.get_shortest_path(startcoord, endcoord)
|
directions, _ = self.map.get_shortest_path(startcoord, endcoord)
|
||||||
self.assertEqual(expected_directions, tuple(directions))
|
self.assertEqual(expected_directions, tuple(directions))
|
||||||
|
|
||||||
|
|
||||||
|
class TestMap10(TestCase):
|
||||||
|
"""
|
||||||
|
Test Map10 - a map with blocked- and interrupt links/nodes. These are
|
||||||
|
'invisible' nodes and won't show up in the map display.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.map = mapsystem.Map({"map": MAP10})
|
||||||
|
|
||||||
|
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(MAP10_DISPLAY, stripped_map)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
((0, 0), (1, 0), ('n', 'e', 's')),
|
||||||
|
((3, 0), (3, 1), ()), # the blockage hinders this
|
||||||
|
((1, 3), (0, 4), ('e', 'n', 'w', 'w')),
|
||||||
|
((0, 1), (3, 2), ('e', 'n', 'e')), # path interrupted by I node
|
||||||
|
((0, 1), (0, 3), ('e', 'n', 'n')), # path interrupted by i link
|
||||||
|
((1, 3), (0, 3), ()),
|
||||||
|
((3, 2), (2, 2), ('w',)),
|
||||||
|
((3, 2), (1, 2), ('w',)),
|
||||||
|
((3, 3), (0, 3), ('w',)),
|
||||||
|
((2, 2), (3, 2), ('e',)),
|
||||||
|
])
|
||||||
|
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), (3, 2), ('e', ), ((2, 2), (2.5, 2), (3, 2))),
|
||||||
|
((3, 3), (0, 3), ('w', ), ((3, 3), (2.5, 3.0), (2.0, 3.0), (1.5, 3.0), (1, 3))),
|
||||||
|
])
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue