#!/usr/bin/env python3 """Navigation computer of the SS Probably Fine. Usage: python3 nav.py DESTINATION python3 nav.py --list python3 nav.py --all """ import sys from collections.abc import Iterator # Distances between neighboring waypoints, in million km. DISTANCES = { ("earth", "moon"): 0.4, ("earth", "gateway"): 0.4, ("gateway", "mars"): 225.0, ("gateway", "venus"): 41.0, ("mars", "phobos"): 0.01, ("mars", "deimos"): 0.02, ("venus", "mercury"): 48.0, ("mars", "ceres"): 265.0, ("gateway", "milliways"): 42.0, ("ceres", "vesta"): 96.0, ("ceres", "europa"): 399.0, ("europa", "io"): 0.25, ("europa", "ganymede"): 0.4, ("ganymede", "callisto"): 0.8, ("europa", "titan"): 655.0, ("titan", "enceladus"): 1.0, ("titan", "pluto"): 4387.0, } # Every route starts at Earth. ROUTES = { "moon": ["earth", "moon"], "mars": ["earth", "gateway", "mars"], "venus": ["earth", "gateway", "venus"], "phobos": ["earth", "gateway", "mars", "phobos"], "deimos": ["earth", "gateway", "mars", "deimos"], "mercury": ["earth", "gateway", "venus", "mercury"], "ceres": ["earth", "gateway", "mars", "ceres"], "milliways": ["earth", "gateway", "milliways"], "vesta": ["earth", "gateway", "mars", "ceres", "vesta"], "europa": ["earth", "gateway", "mars", "ceres", "europa"], "io": ["earth", "gateway", "mars", "ceres", "europa", "io"], "ganymede": ["earth", "gateway", "mars", "ceres", "europa", "ganymede"], "callisto": ["earth", "gateway", "mars", "ceres", "europa", "ganymede", "callisto"], "titan": ["earth", "gateway", "mars", "ceres", "europa", "titan"], "enceladus": ["earth", "gateway", "mars", "ceres", "europa", "titan", "enceladus"], "pluto": ["earth", "gateway", "mars", "ceres", "europa", "titan", "pluto"], } NAMES = { "earth": "Earth", "moon": "Moon", "gateway": "Lunar Gateway", "mars": "Mars", "venus": "Venus", "phobos": "Phobos", "deimos": "Deimos", "mercury": "Mercury", "ceres": "Ceres", "milliways": "Restaurant at the End of the Universe", "vesta": "Vesta", "europa": "Europa", "io": "Io", "ganymede": "Ganymede", "callisto": "Callisto", "titan": "Titan", "enceladus": "Enceladus", "pluto": "Pluto", } CRUISE_SPEED = 0.87 # million km per day FUEL_PER_MKM = 0.142 # tonnes of fuel per million km LAUNCH_FUEL = 380.0 # tonnes of fuel to escape Earth's gravity def distance(a: str, b: str) -> float: """Return the distance between two neighboring waypoints.""" if (a, b) in DISTANCES: return DISTANCES[(a, b)] return DISTANCES[(b, a)] def legs(route: list[str]) -> Iterator[tuple[str, str]]: """Yield the legs (pairs of consecutive waypoints) of a route.""" for i in range(1, len(route) - 1): yield route[i], route[i + 1] def plot_course(destination: str) -> tuple[list[str], float, float, float]: """Return route, distance, travel time (days) and fuel (t).""" route = ROUTES[destination] total = 0.0 fuel = 0.0 for start, end in legs(route): d = distance(start, end) total += d fuel += d * FUEL_PER_MKM if start == "earth": fuel += LAUNCH_FUEL return route, total, total / CRUISE_SPEED, fuel def asteroid_route(belt: str) -> list[str]: """Return a route through the asteroid belt (currently: via Ceres).""" return ROUTES["ceres"] + [belt] def print_plan(destination: str) -> None: """Print the flight plan for a destination.""" route, total, days, fuel = plot_course(destination) print("Route: " + " -> ".join(NAMES[w] for w in route)) print(f"Distance: {total:.1f} million km") print(f"Travel time: {round(days)} days") print(f"Fuel required: {round(fuel)} t") def main() -> None: if len(sys.argv) != 2: sys.exit(__doc__) if sys.argv[1] == "--list": for key in sorted(ROUTES): print(f"{key:12} {NAMES[key]}") return if sys.argv[1] == "--all": for key in sorted(ROUTES): print_plan(key) print() return destination = sys.argv[1].lower() if destination not in ROUTES: known = ", ".join(sorted(ROUTES)) sys.exit(f"Unknown destination: {destination} (known: {known})") print_plan(destination) if __name__ == "__main__": main()