| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- """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)}
|