Basic pathfinding: Complete!
This commit is contained in:
parent
873d622d25
commit
4c95e515e0
31
main.py
Normal file
31
main.py
Normal 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:])
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
34
world.py
34
world.py
|
|
@ -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()
|
||||||
Loading…
Reference in a new issue