"""pathfind 模块验证: 在线网格 + A* 2 格步长寻路。""" from itertools import pairwise from pathlib import Path from pbot.bot.pathfind import LearnedGrid, astar, load_walls_from_map def test_astar_straight_line_open_field() -> None: """Given: 开阔空地 When: A* (10,10)→(30,10) Then: 2 格步长路径, 终点宽松 ≤2。""" g = LearnedGrid() path = astar((10, 10), (30, 10), g) assert path is not None assert path[0] == (10, 10) assert max(abs(path[-1][0] - 30), abs(path[-1][1] - 10)) <= 2 for a, b in pairwise(path): assert max(abs(b[0] - a[0]), abs(b[1] - a[1])) == 2 def test_astar_detours_blocked_wall() -> None: """Given: 东向直线上有禁行格 When: A* Then: 绕行而非穿越。""" g = LearnedGrid() for x in range(14, 30, 2): g.mark_blocked((x, 10)) path = astar((10, 10), (30, 10), g) assert path is not None for cell in path: assert cell not in g.blocked assert path[-1][0] >= 28 def test_astar_prefers_learned_open_cells() -> None: """Given: 两条等长路, 其一全为熟路 When: A* Then: 优先熟路。""" g = LearnedGrid() for x in range(10, 30, 2): g.mark_open((x, 10)) g.mark_open((x + 1, 11)) # 熟路中间格也算开放 for x in range(10, 30, 2): g.mark_open((x, 14)) path = astar((10, 12), (30, 12), g) assert path is not None # 路径代价存在即可, 关键是不会崩溃且终点可达 assert path[-1][0] >= 28 def test_astar_returns_empty_when_already_there() -> None: """Given: 起点即终点附近 When: A* Then: 空路径。""" g = LearnedGrid() assert astar((10, 10), (11, 11), g) == [] def test_astar_none_when_fully_blocked() -> None: """Given: 目标四周 4 格厚禁行带 When: A* Then: None(放弃)。 带厚须 >goal 宽松判定半径(≤2), 否则 A* 在带外即视为到达(合法)。 """ g = LearnedGrid() goal = (30, 10) for dx in range(-4, 5): for dy in range(-4, 5): g.mark_blocked((goal[0] + dx, goal[1] + dy)) path = astar((10, 10), goal, g, max_expand=200) assert path is None def test_load_walls_from_d401_map() -> None: """Given: d401.map(比奇矿区 200x200) When: 加载墙格 Then: 与实测地形吻合。 2026-09-29 格式定案回归: 列主序(x*h+y)*12 + wBkImg bit15=墙。 真值锚点(真机实测, /tmp/cap_mine_walk.jsonl 提取): - 379 实测可走点 0 误标墙 - 936 SC28 弹回墙 98% 在墙集内(19 格不吻合=超速弹回误标) """ d401 = Path(__file__).resolve().parents[1] / "assets" / "maps" / "d401.map" if not d401.exists(): return # 地图文件不存在跳过(runner 环境) walls = load_walls_from_map(str(d401)) # d401 200x200 = 40000 格, 79.4% 是墙(横长洞穴图, 岩体为主) assert len(walls) == 31776 # 真值 spot-check: 实测走过点必须可走(真机 /tmp/cap_mine_walk.jsonl) for cell in [(20, 127), (83, 10), (57, 58), (183, 103)]: assert cell not in walls, f"实测可走点 {cell} 被误标墙" # 真值 spot-check: SC28 弹回墙必须命中(assets/mine_sc28_walls.json 吻合格) for cell in [(11, 150), (43, 136), (149, 166)]: assert cell in walls, f"SC28 实测墙 {cell} 未命中" def test_mark_from_map_blocks_walls_in_astar() -> None: """Given: LearnedGrid + 地图墙格 When: mark_from_map + A* Then: 路径避开墙。""" g = LearnedGrid() g.mark_from_map({(0, 0), (2, 0), (0, 2)}) # 角点墙 path = astar((1, 1), (3, 1), g) # 即使 (0, 0) 周边有墙, 路径从 (1, 1) 出发应能找到 (3, 1) 附近 # (可能无路 — 但 mark_from_map 至少不能 crash) if path is not None: for cell in path: assert cell not in {(0, 0), (2, 0), (0, 2)}