Basic pathfinding: Complete!

This commit is contained in:
Emi Simpson 2023-02-11 17:08:26 -05:00
parent 873d622d25
commit 4c95e515e0
Signed by: Emi
GPG key ID: A12F2C2FFDC3D847
3 changed files with 42 additions and 29 deletions

31
main.py Normal file
View file

@ -0,0 +1,31 @@
from emis_funky_funktions import *
from sys import argv
from operator import eq
from a_star import pathfind
from read_in import load_world_from_paths
from world import Point, World
def pathfind_world(world: World, start: Point, end: Point) -> Option[Tuple[List[Point], int]]:
"Given a `World` with a start and finish point embedded, find the best route"
return pathfind(
world.neighbors,
p(world.heuristic, end),
p(eq, end),
start
)
def main(terrain_path: str, elevation_path: str, path_output: str, image_output: str):
print(
pathfind_world(
unwrap_r(
load_world_from_paths(terrain_path, elevation_path)
),
Point(200, 475),
Point(200, 200)
)
)
if __name__ == '__main__':
main(*argv[1:])

View file

@ -1,6 +1,6 @@
from emis_funky_funktions import * from emis_funky_funktions import *
from shared import Terrain from world import Terrain, World
from PIL import Image from PIL import Image
@ -202,7 +202,7 @@ def load_elevations(path: str) -> Result[Iterator[int], UnparsableElevation]:
with open(path) as file: with open(path) as file:
return read_elevations(file) return read_elevations(file)
def load_world_from_paths(map_path: str, elevation_path: str) -> Result[Iterable[Tuple[Terrain, int]], UnparsableElevation | UnrecognizedColor]: def load_world_from_paths(map_path: str, elevation_path: str) -> Result[World, UnparsableElevation | UnrecognizedColor]:
""" """
Read world information (terrain and elevation) from file paths. Read world information (terrain and elevation) from file paths.
@ -214,7 +214,7 @@ def load_world_from_paths(map_path: str, elevation_path: str) -> Result[Iterable
return bind_res( return bind_res(
lambda terrain_data: lambda terrain_data:
map_res( map_res(
p(zip, terrain_data), lambda elevation_data: World((*zip(terrain_data, elevation_data),)),
load_elevations(elevation_path) load_elevations(elevation_path)
), ),
load_image(map_path) load_image(map_path)

View file

@ -62,12 +62,6 @@ class World:
the elevation in millimeters at that point. the elevation in millimeters at that point.
""" """
start: Point
"Where routing should start"
finish: Point
"Where routing should finish"
width: int width: int
"The number of pixels wide the map is" "The number of pixels wide the map is"
@ -82,15 +76,11 @@ class World:
def __init__(self, def __init__(self,
tiles: Sequence[Tuple[Terrain, int]], tiles: Sequence[Tuple[Terrain, int]],
start: Point,
finish: Point,
width: int = 395, width: int = 395,
lon_scale: int = 10_290, lon_scale: int = 10_290,
lat_scale: int = 7_550 lat_scale: int = 7_550
): ):
self.tiles = tiles self.tiles = tiles
self.start = start
self.finish = finish
self.width = width self.width = width
self.lon_scale = lon_scale self.lon_scale = lon_scale
self.lat_scale = lat_scale self.lat_scale = lat_scale
@ -107,7 +97,7 @@ class World:
This does not take into account the presence, value, or elevation of tiles This does not take into account the presence, value, or elevation of tiles
represented on these points. represented on these points.
>>> world = World([], None, None, lon_scale=10_290, lat_scale=7_550) >>> world = World([], lon_scale=10_290, lat_scale=7_550)
>>> world._adjacency(Point(13, 12)) #doctest: +NORMALIZE_WHITESPACE >>> world._adjacency(Point(13, 12)) #doctest: +NORMALIZE_WHITESPACE
[((12, 11), 12762), [((12, 11), 12762),
((13, 11), 7550), ((13, 11), 7550),
@ -141,7 +131,7 @@ class World:
Points do not need to be adjacent! Points do not need to be adjacent!
>>> world = World([(Terrain.BRUSH, 1_000), (Terrain.BRUSH, 1_100)], None, None) >>> world = World([(Terrain.BRUSH, 1_000), (Terrain.BRUSH, 1_100)])
>>> world.elevation_difference(Point(0, 0), Point(1, 0)) >>> world.elevation_difference(Point(0, 0), Point(1, 0))
100 100
""" """
@ -175,8 +165,6 @@ class World:
... [ (Terrain.OPEN_LAND, 1_000), (Terrain.OOB, 950) ... [ (Terrain.OPEN_LAND, 1_000), (Terrain.OOB, 950)
... , (Terrain.ROUGH_MEADOW, 1_080), (Terrain.EASY_FOREST, 1_010) ... , (Terrain.ROUGH_MEADOW, 1_080), (Terrain.EASY_FOREST, 1_010)
... ], ... ],
... None, # We're not interested in a start state
... None, # nor an end state
... width = 2, # Our simple world is only two tiles wide ... width = 2, # Our simple world is only two tiles wide
... lon_scale = 500, # Pick an easy number for example ... lon_scale = 500, # Pick an easy number for example
... lat_scale = 400 # and remember that these are in millimeters! ... lat_scale = 400 # and remember that these are in millimeters!
@ -261,9 +249,9 @@ class World:
and self[adj_point][0] != Terrain.OOB and self[adj_point][0] != Terrain.OOB
] ]
def heuristic(self, p: Point) -> int: def heuristic(self, a: Point, b: Point) -> int:
""" """
Estimate the time it will take to travel between a point and the finish Estimate the time it will take to travel between two points
The following assumptions are made to speed up the process, at the cost of The following assumptions are made to speed up the process, at the cost of
accuracy: accuracy:
@ -279,14 +267,14 @@ class World:
access world data at all, and the results of the computation depend exclusively on access world data at all, and the results of the computation depend exclusively on
the start point, the finish point, and the longitudinal/latitudinal scales. the start point, the finish point, and the longitudinal/latitudinal scales.
>>> world = World(None, None, Point(3, 5), lon_scale = 500, lat_scale = 400) >>> world = World([], lon_scale = 500, lat_scale = 400)
>>> world.heuristic(Point(0, 0)) >>> world.heuristic(Point(0, 0), Point(3, 5))
1632000 1632000
""" """
# Taxicab distance in each direction # Taxicab distance in each direction
lon_tiles_raw = abs(p.x - self.finish.x) lon_tiles_raw = abs(a.x - b.x)
lat_tiles_raw = abs(p.y - self.finish.y) lat_tiles_raw = abs(a.y - b.y)
# The number of moves necessary, allowing diagonal moves # The number of moves necessary, allowing diagonal moves
lon_moves_real = max(0, lon_tiles_raw - lat_tiles_raw) lon_moves_real = max(0, lon_tiles_raw - lat_tiles_raw)
@ -306,12 +294,6 @@ class World:
return estimated_speed * total_flat_distance return estimated_speed * total_flat_distance
def goal(self, p: Point) -> bool:
"""
Equivalent to `p == world.finish`
"""
return p == self.finish
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod() doctest.testmod()