Added path length calculations
This commit is contained in:
parent
1e420e83e9
commit
8236281181
98
world.py
98
world.py
|
@ -52,6 +52,15 @@ class Point(NamedTuple):
|
|||
def __repr__(self):
|
||||
return f'({self.x}, {self.y})'
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OobError:
|
||||
"""
|
||||
The distance cannot be computed because one point is out of bounds
|
||||
|
||||
This means off the map, not the OOB terrain type.
|
||||
"""
|
||||
p: Point
|
||||
|
||||
@dataclass
|
||||
class World:
|
||||
tiles: Sequence[Tuple[Terrain, int]]
|
||||
|
@ -270,6 +279,8 @@ class World:
|
|||
>>> world = World([], lon_scale = 500, lat_scale = 400)
|
||||
>>> world.heuristic(Point(0, 0), Point(3, 5))
|
||||
1360000
|
||||
|
||||
For a slower algorithm that also includes elevation, see `calculate_distance()`.
|
||||
"""
|
||||
|
||||
# Taxicab distance in each direction
|
||||
|
@ -294,6 +305,93 @@ class World:
|
|||
|
||||
return estimated_speed * total_flat_distance
|
||||
|
||||
def calculate_distance(self, a: Point, b:Point) -> Result[int, OobError]:
|
||||
"""
|
||||
Calculate the distance between the centers of two tiles, incl elevation difference
|
||||
|
||||
Looks up the elevation at both points **a** and **b**, converting them into points
|
||||
in 3D space, then computes the integer distance between
|
||||
|
||||
For a faster algorithm that does not include elevation, see `heuristic()`.
|
||||
|
||||
>>> world = World( # We instantiate a simple, small world
|
||||
... [ (Terrain.OPEN_LAND, 1_000), (Terrain.BRUSH, 850), (Terrain.WET, 500)
|
||||
... , (Terrain.ROUGH_MEADOW, 950), (Terrain.EASY_FOREST, 750), (Terrain.WET, 500)
|
||||
... ],
|
||||
... width = 3, # Our simple world is only two tiles wide
|
||||
... lon_scale = 600, # Pick an easy number for example
|
||||
... lat_scale = 500
|
||||
... )
|
||||
>>> world.calculate_distance(Point(0, 0), Point(2, 1))
|
||||
Ok(1392)
|
||||
>>> world.calculate_distance(Point(0, 0), Point(2, 2))
|
||||
Err(OobError(p=(2, 2)))
|
||||
|
||||
Notice that this is distance as-the-crow-flies between two points in 3D space. If
|
||||
you're looking for actual distance, you must either use two adjacent points, or
|
||||
first pathfind between those two points and calculate the distance that way.
|
||||
"""
|
||||
lon_dist = abs(a.x - b.x) * self.lon_scale
|
||||
lat_dist = abs(a.y - b.y) * self.lat_scale
|
||||
try:
|
||||
a_elev = self[a][1]
|
||||
except IndexError:
|
||||
return Err(OobError(a))
|
||||
try:
|
||||
b_elev = self[b][1]
|
||||
except IndexError:
|
||||
return Err(OobError(b))
|
||||
elev_dist = abs(a_elev - b_elev)
|
||||
return Ok(isqrt(lon_dist * lon_dist + lat_dist * lat_dist + elev_dist * elev_dist))
|
||||
|
||||
@tco_rec
|
||||
def calculate_path_length(self, points: Sequence[Point], addend: int = 0) -> Result[int, OobError]:
|
||||
"""
|
||||
Calculate the length of a path to the greatest degree of accuracy possible
|
||||
|
||||
Points are expected to be a sequence of at least two adjacent points. The
|
||||
returned distance will be the distance travelling between the centers of the tiles
|
||||
of each tile in sequence, including elevation changes.
|
||||
|
||||
Asequential points will not result in an error, but will result in a drop in
|
||||
accuracy. Diagonal moves to adjacent tiles, however, are valid and allowed.
|
||||
|
||||
Any points which fall outside the bounds of the map will result in an `OobError`
|
||||
indicating the first point which was out of bounds.
|
||||
|
||||
On a successful run, the returned units will be in the units the world was
|
||||
instantiated with, typically millimeters.
|
||||
|
||||
>>> world = World( # We instantiate a simple, small world
|
||||
... [ (Terrain.OPEN_LAND, 1_000), (Terrain.BRUSH, 850), (Terrain.WET, 500)
|
||||
... , (Terrain.ROUGH_MEADOW, 950), (Terrain.EASY_FOREST, 750), (Terrain.WET, 500)
|
||||
... ],
|
||||
... width = 3, # Our simple world is only two tiles wide
|
||||
... lon_scale = 600, # Pick an easy number for example
|
||||
... lat_scale = 500
|
||||
... )
|
||||
>>> world.calculate_path_length([Point(0,0), Point(1, 0), Point(2,1)])
|
||||
Ok(1473)
|
||||
|
||||
Calling this method with only two points is equivalent to calling
|
||||
`calculate_distance()`.
|
||||
|
||||
>>> world.calculate_path_length([Point(0, 0), Point(2, 1)])
|
||||
Ok(1392)
|
||||
>>> world.calculate_distance(Point(0, 0), Point(2, 1))
|
||||
Ok(1392)
|
||||
"""
|
||||
match points:
|
||||
case [a, b, *rest]:
|
||||
match self.calculate_distance(a, b):
|
||||
case Ok(this_dist):
|
||||
return Recur(self, (b, *rest), addend + this_dist)
|
||||
case err:
|
||||
return Return(Ok(err))
|
||||
case _: # single or empty
|
||||
return Return(Ok(addend))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
Loading…
Reference in a new issue