"""How far a day's movement points reach on the live Hesper map.

Written 2026-09-13 (session 49) to answer Round 54's open number: does the
movement tank refill 1 point an hour (the human's preference) or 1 per 3 hours
(the author's cut, which keeps today's 8 tiles a day)? The map is the argument,
so this measures the map.

Input: `terrain-turn13.txt`, one character per tile, 512 rows of 512, taken from
GET /api/tiles in 64 rectangles of 64x64 at turn 13 (the `t` field of the compact
record). Codes: ~ water, . plain, f forest, h hill, d desert, M mountain, i ice.
Cost table: hesper/vocab/v1.json movement_modes ground.

    python reach.py            # prints the numbers quoted in Round 55

Re-fetch the snapshot with `python reach.py --fetch` (writes the txt; ~10 s).
"""
import heapq
import random
import statistics
import sys
from pathlib import Path

HERE = Path(__file__).parent
CODE = {"water": "~", "plain": ".", "forest": "f", "hill": "h", "desert": "d",
        "mountain": "M", "ice": "i"}
NAME = {v: k for k, v in CODE.items()}
COST = {"plain": 1, "desert": 2, "forest": 2, "hill": 3, "ice": 4, "mountain": 6,
        "water": None}
N = 512


def fetch():
    import requests
    grid = {}
    for by in range(0, N, 64):
        for bx in range(0, N, 64):
            r = requests.get("https://hesper.untilnextsession.com/api/tiles",
                             params=dict(x=bx, y=by, w=64, h=64), timeout=30)
            r.raise_for_status()
            d = r.json()
            recs = d.get("tiles", d if isinstance(d, list) else d.get("records", []))
            for t in recs:
                grid[(t["x"], t["y"])] = t["t"]
    rows = ["".join(CODE[grid[(x, y)]] for x in range(N)) for y in range(N)]
    (HERE / "terrain-turn13.txt").write_text("\n".join(rows) + "\n")


def load():
    rows = (HERE / "terrain-turn13.txt").read_text().split("\n")[:N]
    return {(x, y): NAME[rows[y][x]] for y in range(N) for x in range(N)}


def dijkstra(terr, src, stop_at=None):
    dist = {src: 0}
    pq = [(0, src)]
    while pq:
        d, (x, y) = heapq.heappop(pq)
        if d > dist.get((x, y), 1e9):
            continue
        if stop_at and (x, y) != src and terr[(x, y)] in stop_at:
            return d
        for nx, ny in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
            c = COST.get(terr.get((nx, ny)))
            if c is None:
                continue
            nd = d + c
            if nd < dist.get((nx, ny), 1e9):
                dist[(nx, ny)] = nd
                heapq.heappush(pq, (nd, (nx, ny)))
    return None if stop_at else dist


def main():
    if "--fetch" in sys.argv:
        fetch()
    terr = load()
    land = [p for p, t in terr.items() if t not in ("water", "ice")]
    print("land tiles (not water, not ice):", len(land))
    home = (256, 256)
    d = dijkstra(terr, home)
    far = max(d, key=d.get)
    d2 = dijkstra(terr, far)
    far2 = max(d2, key=d2.get)
    print("widest walk across the continent (movement points):", d2[far2], far, "->", far2)
    for budget in (8, 24):
        print(f"days to cross at {budget} points a day: {d2[far2] / budget:.0f}")
    random.seed(1)
    samples = random.sample(land, 300)
    for name in ("forest", "hill", "mountain"):
        ds = sorted(v for v in (dijkstra(terr, s, stop_at={name}) for s in samples) if v is not None)
        print(f"nearest {name}: median {statistics.median(ds)} p90 {ds[int(.9 * len(ds)) - 1]} max {ds[-1]}")
    r8, r24 = [], []
    for s in samples[:60]:
        dd = dijkstra(terr, s)
        r8.append(sum(1 for v in dd.values() if v <= 8))
        r24.append(sum(1 for v in dd.values() if v <= 24))
    print("tiles a day reaches, median: 8 points ->", statistics.median(r8),
          "; 24 points ->", statistics.median(r24))


if __name__ == "__main__":
    main()
