Map contrib working with all links and nodes
This commit is contained in:
parent
ea63071c64
commit
6c2722a3f2
2 changed files with 339 additions and 71 deletions
|
|
@ -173,7 +173,7 @@ class MapNode:
|
||||||
self.xy_steps_in_direction = {}
|
self.xy_steps_in_direction = {}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"<MapNode {self.node_index} XY=({self.X},{self.Y}) ({self.symbol})>"
|
return f"<MapNode '{self.symbol}' {self.node_index} XY=({round(self.X)},{round(self.Y)})"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
@ -274,13 +274,13 @@ class MapLink:
|
||||||
# n,ne,e,se,s,sw,w,nw. A link is described as {startpos:endpoit}, like connecting
|
# n,ne,e,se,s,sw,w,nw. A link is described as {startpos:endpoit}, like connecting
|
||||||
# the named corners with a line. If the inverse direction is also possible, it
|
# the named corners with a line. If the inverse direction is also possible, it
|
||||||
# must also be specified. So a south-northward, two-way link would be described
|
# must also be specified. So a south-northward, two-way link would be described
|
||||||
# as {"s": "n", "n": "s"}. The get_directions method can be customized to
|
# as {"s": "n", "n": "s"}. The get_direction method can be customized to
|
||||||
# dynamically modify this during parsing.
|
# return something else.
|
||||||
directions = {}
|
directions = {}
|
||||||
# this is required for pathfinding. Each weight is defined as {startpos:weight}, where
|
# this is required for pathfinding. Each weight is defined as {startpos:weight}, where
|
||||||
# the startpos is the direction of the cell (n,ne etc) where the link *starts*. The
|
# the startpos is the direction of the cell (n,ne etc) where the link *starts*. The
|
||||||
# weight is a value > 0, smaller than _BIG. The get_directions method can be
|
# weight is a value > 0, smaller than _BIG. The get_weight method can be
|
||||||
# customized to modify this during parsing.
|
# customized to modify to return something else.
|
||||||
weights = {}
|
weights = {}
|
||||||
default_weight = 1
|
default_weight = 1
|
||||||
# This setting only applies if this is the *first* link in a chain of multiple links. Usually,
|
# This setting only applies if this is the *first* link in a chain of multiple links. Usually,
|
||||||
|
|
@ -304,7 +304,7 @@ class MapLink:
|
||||||
self.display_symbol = self.symbol
|
self.display_symbol = self.symbol
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"<LinkNode xy=({self.x},{self.y}) ({self.symbol})>"
|
return f"<LinkNode '{self.symbol}' XY=({round(self.x / 2)},{round(self.y / 2)})>"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
@ -324,6 +324,8 @@ class MapLink:
|
||||||
dict: Mapping {direction: node_or_link} wherever such was found.
|
dict: Mapping {direction: node_or_link} wherever such was found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# if (self.x, self.y) == (4, 8):
|
||||||
|
# from evennia import set_trace;set_trace()
|
||||||
if not directions:
|
if not directions:
|
||||||
directions = _REVERSE_DIRECTIONS
|
directions = _REVERSE_DIRECTIONS
|
||||||
links = {}
|
links = {}
|
||||||
|
|
@ -331,10 +333,15 @@ class MapLink:
|
||||||
dx, dy = _MAPSCAN[direction]
|
dx, dy = _MAPSCAN[direction]
|
||||||
end_x, end_y = self.x + dx, self.y + dy
|
end_x, end_y = self.x + dx, self.y + dy
|
||||||
if end_x in xygrid and end_y in xygrid[end_x]:
|
if end_x in xygrid and end_y in xygrid[end_x]:
|
||||||
links[direction] = xygrid[end_x][end_y]
|
# there is is something there, we need to check if it is either
|
||||||
|
# a map node or a link connecting in our direction
|
||||||
|
node_or_link = xygrid[end_x][end_y]
|
||||||
|
if (hasattr(node_or_link, "node_index")
|
||||||
|
or node_or_link.get_direction(direction, xygrid)):
|
||||||
|
links[direction] = node_or_link
|
||||||
return links
|
return links
|
||||||
|
|
||||||
def get_directions(self, start_direction, xygrid):
|
def get_direction(self, start_direction, xygrid):
|
||||||
"""
|
"""
|
||||||
Hook to override for customizing how the directions are
|
Hook to override for customizing how the directions are
|
||||||
determined.
|
determined.
|
||||||
|
|
@ -344,13 +351,18 @@ class MapLink:
|
||||||
xygrid (dict): 2D dict with x,y coordinates as keys.
|
xygrid (dict): 2D dict with x,y coordinates as keys.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: The directions map {start_direction:end_direction} of
|
str: The 'out' direction side of the link - where the link
|
||||||
the link. By default this is just self.directions.
|
leads to.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
With the default legend, if the link is a straght vertical link
|
||||||
|
(`|`) and `start_direction` is `s` (link is approached from
|
||||||
|
from the south side), then this function will return `n'.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.directions
|
return self.directions.get(start_direction)
|
||||||
|
|
||||||
def get_weights(self, start_direction, xygrid, current_weight):
|
def get_weight(self, start_direction, xygrid, current_weight):
|
||||||
"""
|
"""
|
||||||
Hook to override for customizing how the weights are determined.
|
Hook to override for customizing how the weights are determined.
|
||||||
|
|
||||||
|
|
@ -361,11 +373,10 @@ class MapLink:
|
||||||
we are progressing down a multi-step path.
|
we are progressing down a multi-step path.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: The directions map {start_direction:weight} of
|
int: The weight to use for a link from `start_direction`.
|
||||||
the link. By default this is just self.weights
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.weights
|
return self.weights.get(start_direction, self.default_weight)
|
||||||
|
|
||||||
def traverse(self, start_direction, xygrid, _weight=0, _linklen=1, _steps=None):
|
def traverse(self, start_direction, xygrid, _weight=0, _linklen=1, _steps=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -389,26 +400,26 @@ class MapLink:
|
||||||
MapParserError: If a link lead to nowhere.
|
MapParserError: If a link lead to nowhere.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# from evennia import set_trace;set_trace()
|
end_direction = self.get_direction(start_direction, xygrid)
|
||||||
end_direction = self.get_directions(start_direction, xygrid).get(start_direction)
|
|
||||||
if not end_direction:
|
if not end_direction:
|
||||||
if _steps is None:
|
if _steps is None:
|
||||||
# is perfectly okay to not be linking to a node
|
# is perfectly okay to not be linking to a node
|
||||||
return None, 0, None
|
return None, 0, None
|
||||||
raise MapParserError(f"Link at ({self.x}, {self.y}) was connected to "
|
raise MapParserError(f"Link '{self.symbol}' at "
|
||||||
f"from {start_direction}, but does not link that way.")
|
f"XY=({round(self.x / 2)},{round(self.y / 2)}) "
|
||||||
|
f"was connected to from the direction {start_direction}, but "
|
||||||
|
"is not set up to link in that direction.")
|
||||||
|
|
||||||
dx, dy = _MAPSCAN[end_direction]
|
dx, dy = _MAPSCAN[end_direction]
|
||||||
end_x, end_y = self.x + dx, self.y + dy
|
end_x, end_y = self.x + dx, self.y + dy
|
||||||
try:
|
try:
|
||||||
next_target = xygrid[end_x][end_y]
|
next_target = xygrid[end_x][end_y]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise MapParserError(f"Link at ({self.x}, {self.y}) points to "
|
raise MapParserError(f"Link '{self.symbol}' at "
|
||||||
f"empty space in direction {end_direction}!")
|
f"XY=({round(self.x / 2)},{round(self.y / 2)}) "
|
||||||
|
"points to empty space in the direction {end_direction}!")
|
||||||
|
|
||||||
_weight += self.get_weights(
|
_weight += self.get_weight(start_direction, xygrid, _weight)
|
||||||
start_direction, xygrid, _weight).get(
|
|
||||||
start_direction, self.default_weight)
|
|
||||||
if _steps is None:
|
if _steps is None:
|
||||||
_steps = []
|
_steps = []
|
||||||
_steps.append(_REVERSE_DIRECTIONS[start_direction])
|
_steps.append(_REVERSE_DIRECTIONS[start_direction])
|
||||||
|
|
@ -513,33 +524,46 @@ class DynamicMapLink(MapLink):
|
||||||
"""
|
"""
|
||||||
symbol = "o"
|
symbol = "o"
|
||||||
|
|
||||||
def get_directions(self, start_direction, xygrid):
|
def get_direction(self, start_direction, xygrid):
|
||||||
# get all visually connected links
|
# get all visually connected links
|
||||||
directions = {}
|
if not hasattr(self, '_cached_directions'):
|
||||||
links = list(self.get_visually_connected(xygrid).keys())
|
# try to get from cache where possible
|
||||||
loop_links = links.copy()
|
directions = {}
|
||||||
# first get all cross-through links
|
unhandled_links = list(self.get_visually_connected(xygrid).keys())
|
||||||
for direction in loop_links:
|
|
||||||
if _REVERSE_DIRECTIONS[direction] in loop_links:
|
|
||||||
directions[direction] = links.pop(direction)
|
|
||||||
|
|
||||||
# check if we have any non-cross-through paths to handle
|
# get all straight lines (n-s, sw-ne etc) we can trace through
|
||||||
if len(links) != 2:
|
# the dynamic link and remove them from the unhandled_links list
|
||||||
links = "-".join(links)
|
unhandled_links_copy = unhandled_links.copy()
|
||||||
raise MapParserError(
|
for direction in unhandled_links_copy:
|
||||||
f"dynamic link at grid ({self.x, self.y}) cannot determine "
|
if _REVERSE_DIRECTIONS[direction] in unhandled_links_copy:
|
||||||
f"where how to connect links leading to/from {links}.")
|
directions[direction] = _REVERSE_DIRECTIONS[
|
||||||
directions[links[0]] = links[1]
|
unhandled_links.pop(unhandled_links.index(direction))]
|
||||||
directions[links[1]] = links[0]
|
|
||||||
|
|
||||||
return directions
|
# 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"Dynamic Link '{self.symbol}' at "
|
||||||
|
f"XY=({round(self.x / 2)},{round(self.y / 2)}) 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._cached_directions = directions
|
||||||
|
|
||||||
|
return self._cached_directions.get(start_direction)
|
||||||
|
|
||||||
|
|
||||||
# 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,
|
"#": MapNode,
|
||||||
"o": MapLink,
|
|
||||||
"|": NSMapLink,
|
"|": NSMapLink,
|
||||||
"-": EWMapLink,
|
"-": EWMapLink,
|
||||||
"/": NESWMapLink,
|
"/": NESWMapLink,
|
||||||
|
|
@ -550,6 +574,7 @@ DEFAULT_LEGEND = {
|
||||||
"^": SNOneWayMapLink,
|
"^": SNOneWayMapLink,
|
||||||
"<": EWOneWayMapLink,
|
"<": EWOneWayMapLink,
|
||||||
">": WEOneWayMapLink,
|
">": WEOneWayMapLink,
|
||||||
|
"o": DynamicMapLink,
|
||||||
}
|
}
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
@ -662,7 +687,7 @@ class Map:
|
||||||
pathfinding_graph = zeros((nnodes, nnodes))
|
pathfinding_graph = zeros((nnodes, nnodes))
|
||||||
# build a matrix representing the map graph, with 0s as impassable areas
|
# build a matrix representing the map graph, with 0s as impassable areas
|
||||||
for inode, node in self.node_index_map.items():
|
for inode, node in self.node_index_map.items():
|
||||||
pathfinding_graph[:, inode] = node.linkweights(nnodes)
|
pathfinding_graph[inode, :] = node.linkweights(nnodes)
|
||||||
|
|
||||||
# create a sparse matrix to represent link relationships from each node
|
# create a sparse matrix to represent link relationships from each node
|
||||||
pathfinding_matrix = csr_matrix(pathfinding_graph)
|
pathfinding_matrix = csr_matrix(pathfinding_graph)
|
||||||
|
|
@ -699,6 +724,7 @@ 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
|
||||||
|
|
@ -706,7 +732,7 @@ class Map:
|
||||||
topleft_marker_x = line.find(mapcorner_symbol)
|
topleft_marker_x = line.find(mapcorner_symbol)
|
||||||
if topleft_marker_x != -1:
|
if topleft_marker_x != -1:
|
||||||
break
|
break
|
||||||
if topleft_marker_x == -1 or topleft_marker_y == -1:
|
if -1 in (topleft_marker_x, topleft_marker_y):
|
||||||
raise MapParserError(f"No top-left corner-marker ({mapcorner_symbol}) found!")
|
raise MapParserError(f"No top-left corner-marker ({mapcorner_symbol}) found!")
|
||||||
|
|
||||||
# find the position (in the string as a whole) of the bottom-left corner-marker
|
# find the position (in the string as a whole) of the bottom-left corner-marker
|
||||||
|
|
@ -719,11 +745,13 @@ class Map:
|
||||||
raise MapParserError(f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
|
raise MapParserError(f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
|
||||||
"Make sure it lines up with the top-left corner-marker "
|
"Make sure it lines up with the top-left corner-marker "
|
||||||
f"(found at column {topleft_marker_x} of the string).")
|
f"(found at column {topleft_marker_x} of the string).")
|
||||||
|
# the actual coordinate is dy below the topleft marker so we need to shift
|
||||||
|
botleft_marker_y += topleft_marker_y + 1
|
||||||
|
|
||||||
# in-string_position of the top- and bottom-left grid corners (2 steps in from marker)
|
# in-string_position of the top- and bottom-left grid corners (2 steps in from marker)
|
||||||
# the bottom-left corner is also the origo (0,0) of the grid.
|
# the bottom-left corner is also the origo (0,0) of the grid.
|
||||||
topleft_y = topleft_marker_y + 2
|
topleft_y = topleft_marker_y + 2
|
||||||
origo_x, origo_y = botleft_marker_x + 2, botleft_marker_y + 2
|
origo_x, origo_y = botleft_marker_x + 2, botleft_marker_y - 1
|
||||||
|
|
||||||
# highest actually filled grid points
|
# highest actually filled grid points
|
||||||
max_x = 0
|
max_x = 0
|
||||||
|
|
@ -747,7 +775,8 @@ class Map:
|
||||||
mapnode_or_link_class = self.legend.get(char)
|
mapnode_or_link_class = self.legend.get(char)
|
||||||
if not mapnode_or_link_class:
|
if not mapnode_or_link_class:
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
f"Symbol '{char}' on xygrid position ({ix},{iy}) is not found in LEGEND."
|
f"Symbol '{char}' on XY=({round(ix / 2)},{round(iy / 2)}) "
|
||||||
|
"is not found in LEGEND."
|
||||||
)
|
)
|
||||||
if hasattr(mapnode_or_link_class, "node_index"):
|
if hasattr(mapnode_or_link_class, "node_index"):
|
||||||
# A mapnode. Mapnodes can only be placed on even grid positions, where
|
# A mapnode. Mapnodes can only be placed on even grid positions, where
|
||||||
|
|
@ -755,8 +784,9 @@ class Map:
|
||||||
|
|
||||||
if not (even_iy and ix % 2 == 0):
|
if not (even_iy and ix % 2 == 0):
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
f"Symbol '{char}' (xygrid ({ix},{iy}) marks a Node but is located "
|
f"Symbol '{char}' on XY=({round(ix / 2)},{round(iy / 2)}) marks a "
|
||||||
"between valid (X,Y) positions!")
|
"MapNode but is located between integer (X,Y) positions (only "
|
||||||
|
"Links can be placed between coordinates)!")
|
||||||
|
|
||||||
# save the node to several different maps for different uses
|
# save the node to several different maps for different uses
|
||||||
# in both coordinate systems
|
# in both coordinate systems
|
||||||
|
|
@ -861,8 +891,8 @@ class Map:
|
||||||
|
|
||||||
iX, iY = coords
|
iX, iY = coords
|
||||||
if not ((0 <= iX <= self.max_X) and (0 <= iY <= self.max_Y)):
|
if not ((0 <= iX <= self.max_X) and (0 <= iY <= self.max_Y)):
|
||||||
raise MapError("get_node_from_coord got coordinate {coords} which is "
|
raise MapError(f"get_node_from_coord got coordinate {coords} which is "
|
||||||
"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).")
|
f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).")
|
||||||
try:
|
try:
|
||||||
return self.XYgrid[coords[0]][coords[1]]
|
return self.XYgrid[coords[0]][coords[1]]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -887,10 +917,17 @@ class Map:
|
||||||
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)
|
||||||
|
|
||||||
if not endnode:
|
if not (startnode and endnode):
|
||||||
# no node at given coordinate. No path is possible.
|
# no node at given coordinate. No path is possible.
|
||||||
return [], []
|
return [], []
|
||||||
|
|
||||||
|
try:
|
||||||
|
istartnode = startnode.node_index
|
||||||
|
inextnode = endnode.node_index
|
||||||
|
except AttributeError:
|
||||||
|
raise MapError(f"Map.get_shortest_path received start/end nodes {startnode} and "
|
||||||
|
f"{endnode}. They must both be MapNodes (not Links)")
|
||||||
|
|
||||||
if self.pathfinding_routes is None:
|
if self.pathfinding_routes is None:
|
||||||
self._calculate_path_matrix()
|
self._calculate_path_matrix()
|
||||||
|
|
||||||
|
|
@ -899,8 +936,6 @@ class Map:
|
||||||
|
|
||||||
path = [endnode]
|
path = [endnode]
|
||||||
directions = []
|
directions = []
|
||||||
istartnode = startnode.node_index
|
|
||||||
inextnode = endnode.node_index
|
|
||||||
|
|
||||||
while pathfinding_routes[istartnode, inextnode] != -9999:
|
while pathfinding_routes[istartnode, inextnode] != -9999:
|
||||||
# the -9999 is set by algorithm for unreachable nodes or if trying
|
# the -9999 is set by algorithm for unreachable nodes or if trying
|
||||||
|
|
@ -945,31 +980,28 @@ class Map:
|
||||||
indexing `outlist[iy][ix]` in that order.
|
indexing `outlist[iy][ix]` in that order.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
If outputting an output list, the y-axis must first be
|
If outputting a list, the y-axis must first be reversed before printing since printing
|
||||||
reversed since printing happens top-bottom and the y coordinate
|
happens top-bottom and the y coordinate system goes bottom-up. This can be done simply
|
||||||
system goes bottom-up. This can be done simply with
|
with this before building the final string to send/print.
|
||||||
|
|
||||||
reversed = outlist[::-1]
|
printable_order_list = outlist[::-1]
|
||||||
|
|
||||||
before starting the printout loop.
|
If mode='nodes', a `dist` of 2 will give the following result in a row of nodes:
|
||||||
|
|
||||||
If `only_nodes` is True, a dist of 2 will give the following
|
|
||||||
result in a row of nodes:
|
|
||||||
|
|
||||||
#-#-@----------#-#
|
#-#-@----------#-#
|
||||||
|
|
||||||
This display may grow much bigger than expected (both horizontally
|
This display may thus visually grow much bigger than expected (both horizontally and
|
||||||
and vertically). consider setting `max_size` if wanting to restrict the display size.
|
vertically). consider setting `max_size` if wanting to restrict the display size. Also
|
||||||
also note that link 'weights' are *included* in this estimate, so
|
note that link 'weights' are *included* in this estimate, so if links have weights > 1,
|
||||||
if links have weights > 1, fewer nodes will be found for a given `dist`.
|
fewer nodes may be found for a given `dist`.
|
||||||
|
|
||||||
If `only_nodes` is False, dist of 2 would give
|
If mode=`scan`, a dist of 2 on the above example would instead give
|
||||||
|
|
||||||
#-@--
|
#-@--
|
||||||
|
|
||||||
This is more of a 'moving' overview type of map that just displays a part of the grid
|
This mode simply shows a cut-out subsection of the map you are on. The `dist` is
|
||||||
you are on. It does not consider links or weights and may also show nodes not
|
measured on xygrid, so two steps per XY coordinate. It does not consider links or
|
||||||
actually reachable at the moment:
|
weights and may also show nodes not actually reachable at the moment:
|
||||||
|
|
||||||
| |
|
| |
|
||||||
# @-#
|
# @-#
|
||||||
|
|
@ -1039,7 +1071,6 @@ class Map:
|
||||||
xmin, ymin = min(xmin, ix0), min(ymin, iy0)
|
xmin, ymin = min(xmin, ix0), min(ymin, iy0)
|
||||||
xmax, ymax = max(xmax, ix0), max(ymax, iy0)
|
xmax, ymax = max(xmax, ix0), max(ymax, iy0)
|
||||||
|
|
||||||
# from evennia import set_trace;set_trace()
|
|
||||||
ixc, iyc = ix - xmin, iy - ymin
|
ixc, iyc = ix - xmin, iy - ymin
|
||||||
# note - override width/height here since our grid is
|
# note - override width/height here since our grid is
|
||||||
# now different from the original for future cropping
|
# now different from the original for future cropping
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,110 @@ MAP5_DISPLAY = r"""
|
||||||
#---#
|
#---#
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
|
MAP6 = r"""
|
||||||
|
|
||||||
|
+ 0 1 2
|
||||||
|
|
||||||
|
2 #-#
|
||||||
|
| |
|
||||||
|
1 #>#
|
||||||
|
|
||||||
|
0 #>#
|
||||||
|
|
||||||
|
+ 0 1 2
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
MAP6_DISPLAY = r"""
|
||||||
|
#-#
|
||||||
|
| |
|
||||||
|
#>#
|
||||||
|
|
||||||
|
#>#
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
MAP7 = r"""
|
||||||
|
|
||||||
|
+ 0 1 2 3 4
|
||||||
|
|
||||||
|
4 #-#-#-#
|
||||||
|
^ |
|
||||||
|
3 | #>#
|
||||||
|
| | |
|
||||||
|
2 #-#-#-#
|
||||||
|
^ v
|
||||||
|
1 #---#-#
|
||||||
|
| | |
|
||||||
|
0 #-#>#-#<#
|
||||||
|
|
||||||
|
+ 0 1 2 3 4
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
MAP7_DISPLAY = r"""
|
||||||
|
#-#-#-#
|
||||||
|
^ |
|
||||||
|
| #>#
|
||||||
|
| | |
|
||||||
|
#-#-#-#
|
||||||
|
^ v
|
||||||
|
#---#-#
|
||||||
|
| | |
|
||||||
|
#-#>#-#<#
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
|
MAP8 = r"""
|
||||||
|
+ 0 1 2
|
||||||
|
|
||||||
|
2 #-#
|
||||||
|
|
|
||||||
|
1 #-o-#
|
||||||
|
|
|
||||||
|
0 #-#
|
||||||
|
|
||||||
|
+ 0 1 2
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
MAP8_DISPLAY = r"""
|
||||||
|
#-#
|
||||||
|
|
|
||||||
|
#-o-#
|
||||||
|
|
|
||||||
|
#-#
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
|
MAP9 = r"""
|
||||||
|
+ 0 1 2 3 4 5
|
||||||
|
|
||||||
|
4 #-#-o o o-o
|
||||||
|
| \|/| | |
|
||||||
|
3 #-o-o-# o-#
|
||||||
|
| /|\ |
|
||||||
|
2 o-o-#-# o
|
||||||
|
| | /
|
||||||
|
1 #-o-#-o-#
|
||||||
|
| /
|
||||||
|
0 #---#-o
|
||||||
|
|
||||||
|
+ 0 1 2 3 4 5
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
MAP9_DISPLAY = r"""
|
||||||
|
#-#-o o o-o
|
||||||
|
| \|/| | |
|
||||||
|
#-o-o-# o-#
|
||||||
|
| /|\ |
|
||||||
|
o-o-#-# o
|
||||||
|
| | /
|
||||||
|
#-o-#-o-#
|
||||||
|
| /
|
||||||
|
#---#-o
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
class TestMap1(TestCase):
|
class TestMap1(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
@ -377,7 +481,7 @@ class TestMap4(TestCase):
|
||||||
"""
|
"""
|
||||||
mapstr = self.map.get_map_display(coord, dist=dist, mode='nodes', character='@',
|
mapstr = self.map.get_map_display(coord, dist=dist, mode='nodes', character='@',
|
||||||
max_size=max_size)
|
max_size=max_size)
|
||||||
print(repr(mapstr))
|
# print(repr(mapstr))
|
||||||
self.assertEqual(expected, mapstr)
|
self.assertEqual(expected, mapstr)
|
||||||
|
|
||||||
class TestMap5(TestCase):
|
class TestMap5(TestCase):
|
||||||
|
|
@ -408,3 +512,136 @@ class TestMap5(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 TestMap6(TestCase):
|
||||||
|
"""
|
||||||
|
Test Map6 - Small map with one-way links
|
||||||
|
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.map = mapsystem.Map({"map": MAP6})
|
||||||
|
|
||||||
|
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(MAP6_DISPLAY, stripped_map)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
((0, 0), (1, 0), ('e',)), # cross one-way
|
||||||
|
((1, 0), (0, 0), ()), # blocked
|
||||||
|
((0, 1), (1, 1), ('e',)), # should still take shortest
|
||||||
|
((1, 1), (0, 1), ('n', 'w', 's')), # take long way around
|
||||||
|
])
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
class TestMap7(TestCase):
|
||||||
|
"""
|
||||||
|
Test Map6 - Bigger map with one-way links in different directions
|
||||||
|
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.map = mapsystem.Map({"map": MAP7})
|
||||||
|
|
||||||
|
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(MAP7_DISPLAY, stripped_map)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
((0, 0), (2, 0), ('e', 'e')), # cross one-way
|
||||||
|
((2, 0), (0, 0), ('e', 'n', 'w', 's', 'w')), # blocked, long way around
|
||||||
|
((4, 0), (3, 0), ('w',)),
|
||||||
|
((3, 0), (4, 0), ('n', 'e', 's')),
|
||||||
|
((1, 1), (1, 2), ('n',)),
|
||||||
|
((1, 2), (1, 1), ('e', 'e', 's', 'w')),
|
||||||
|
((3, 1), (1, 4), ('w', 'n', 'n')),
|
||||||
|
((0, 4), (0, 0), ('e', 'e', 'e', 's', 's', 's', 'w', 's', '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))
|
||||||
|
|
||||||
|
|
||||||
|
class TestMap8(TestCase):
|
||||||
|
"""
|
||||||
|
Test Map6 - Small test of dynamic link node
|
||||||
|
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.map = mapsystem.Map({"map": MAP8})
|
||||||
|
|
||||||
|
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(MAP8_DISPLAY, stripped_map)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
((1, 0), (1, 2), ('n', )),
|
||||||
|
((1, 2), (1, 0), ('s', )),
|
||||||
|
((0, 1), (2, 1), ('e', )),
|
||||||
|
((2, 1), (0, 1), ('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))
|
||||||
|
|
||||||
|
|
||||||
|
class TestMap9(TestCase):
|
||||||
|
"""
|
||||||
|
Test Map6 - Small test of dynamic link node
|
||||||
|
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.map = mapsystem.Map({"map": MAP9})
|
||||||
|
|
||||||
|
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(MAP9_DISPLAY, stripped_map)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
((2, 0), (2, 2), ('n',)),
|
||||||
|
((0, 0), (5, 3), ('e', 'e')),
|
||||||
|
((5, 1), (0, 3), ('w', 'w', 'n', 'w')),
|
||||||
|
((1, 1), (2, 2), ('n', 'w', 's')),
|
||||||
|
((5, 3), (5, 3), ()),
|
||||||
|
((5, 3), (0, 4), ('s', 'n', 'w', 'n')),
|
||||||
|
((1, 4), (3, 3), ('e', 'w', '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), 1, None, ' #-o \n | \n# o \n| | \no-o-@-#\n '
|
||||||
|
'| \n o \n | \n # '),
|
||||||
|
])
|
||||||
|
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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue