test.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. '''
  2. @author: olivier.massot, 2019
  3. '''
  4. import heapq
  5. import time
  6. class Queue():
  7. def __init__(self):
  8. self.items = []
  9. def __bool__(self):
  10. return bool(self.items)
  11. def __repr__(self):
  12. return str(self.items)
  13. def values(self):
  14. return (v for _, v in self.items)
  15. def put(self, item, priority):
  16. heapq.heappush(self.items, (priority, item))
  17. def fput(self, item, priority):
  18. while priority in [p for p, _ in self.items]:
  19. priority += 1
  20. self.put(item, priority)
  21. def get(self):
  22. return heapq.heappop(self.items)[1]
  23. class PathNode(tuple):
  24. def __new__(self, x, y, parent=None):
  25. n = tuple.__new__(self, (x, y))
  26. n.parent = parent
  27. n.cost = 0
  28. return n
  29. def __repr__(self):
  30. return f"<{self[0]}, {self[1]}, c:{self.cost}>"
  31. def ancestors(self):
  32. ancestors = []
  33. p = self.parent
  34. while p:
  35. ancestors.insert(0, p)
  36. p = p.parent
  37. return ancestors
  38. class Waypoint(tuple):
  39. def __new__(self, x, y, neighbors=None):
  40. n = tuple.__new__(self, (x, y))
  41. n.neighbors = neighbors or []
  42. return n
  43. def __repr__(self):
  44. return f"<({self[0]},{self[1]}), n:{self.neighbors}>"
  45. class Grid():
  46. def __init__(self, grid, start, target):
  47. rows = grid.strip().split("\n")
  48. self.w = len(rows[0])
  49. self.h = len(rows)
  50. self.cells = {(x, y): c for y, row in enumerate(rows) for x, c in enumerate(list(row))}
  51. self.control_room = target
  52. self.kirk = start
  53. self.neighbors = {p: self.get_neighbors(*p) for p in self.cells}
  54. self.update_corners()
  55. def _repr_cell(self, p, show=[]):
  56. if p == self.kirk:
  57. return "T"
  58. elif p == self.control_room:
  59. return "C"
  60. elif p in show:
  61. return "x"
  62. else:
  63. return self.cells[p]
  64. def graph(self, show=[]):
  65. return "\n".join(["".join([self._repr_cell((x, y), show) for x in range(self.w)]) for y in range(self.h)])
  66. @staticmethod
  67. def dist(from_, to_):
  68. xa, ya = from_
  69. xb, yb = to_
  70. return abs(xa - xb) + abs(ya - yb)
  71. def get_neighbors(self, x, y, diags=False):
  72. neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
  73. if diags:
  74. neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
  75. return [(x, y) for x, y in neighs if 0 <= x < self.w and 0 <= y < self.h]
  76. def line(self, x1, y1, x2, y2):
  77. # special case
  78. if (x1, y1) == (x2, y2):
  79. return [(x1, y1)]
  80. # diagonal symmetry
  81. vertically_oriented = (abs(y2 - y1) > abs(x2 - x1))
  82. if vertically_oriented:
  83. y1, x1, y2, x2 = x1, y1, x2, y2
  84. # horizontal symmetry
  85. reversed_sym = (x1 > x2)
  86. if reversed_sym:
  87. x2, y2, x1, y1 = x1, y1, x2, y2
  88. # angle
  89. dx, dy = x2 - x1, y2 - y1
  90. alpha = (abs(dy) / dx)
  91. offset = 0.0
  92. step = 1 if dy > 0 else -1
  93. result = []
  94. y = y1
  95. for x in range(x1, x2 + 1):
  96. if vertically_oriented:
  97. result.append((y, x))
  98. else:
  99. result.append((x, y))
  100. offset += alpha
  101. if offset > 0.5:
  102. y += step
  103. offset -= 1.0
  104. if reversed_sym:
  105. result.reverse()
  106. return result
  107. def _get_quarters(self, x, y):
  108. return {(x - 1, y - 1): [(x - 1, y), (x - 1, y - 1), (x, y - 1)],
  109. (x + 1, y - 1): [(x, y - 1), (x + 1, y - 1), (x + 1, y)],
  110. (x + 1, y + 1): [(x + 1, y), (x + 1, y + 1), (x, y + 1)],
  111. (x - 1, y + 1): [(x, y + 1), (x - 1, y + 1), (x - 1, y)]}
  112. def update_corners(self):
  113. corners = set()
  114. for p, c in self.cells.items():
  115. if c == "#":
  116. for corner, quarter in self._get_quarters(*p).items():
  117. if all(self.cells.get(n, "#") != "#" for n in quarter):
  118. corners.add(corner)
  119. self.corners = list(corners)
  120. def print_nodes(self, node):
  121. def _get_parent(node):
  122. parents = []
  123. if node.parent:
  124. parents.insert(0, node.parent)
  125. parents = _get_parent(node.parent) + parents
  126. return parents
  127. path_to = _get_parent(node)
  128. print(node, path_to)
  129. def way(self, start, target, known_only=False):
  130. nodes = Queue()
  131. origin = PathNode(*start)
  132. nodes.put(origin, 0)
  133. waypoints = set(self.corners) | {start, target}
  134. neighbors = {}
  135. passable = ["."] if known_only else [".", "?"]
  136. for waypoint in waypoints:
  137. neighbors[waypoint] = []
  138. for other in waypoints:
  139. if other is waypoint:
  140. continue
  141. sight = self.line(*other, *waypoint)[1:-1]
  142. if all(self.cells.get(p, "#") in passable for p in sight) and not any(w in sight for w in waypoints):
  143. neighbors[waypoint].append((other, Grid.dist(waypoint, other)))
  144. while nodes:
  145. current = nodes.get()
  146. # self.print_nodes(current)
  147. if current == target:
  148. path = []
  149. previous = current
  150. while previous:
  151. if previous != start:
  152. path.insert(0, previous)
  153. previous = previous.parent
  154. return path
  155. for n, dist in neighbors[current]:
  156. if n in current.ancestors():
  157. continue
  158. cost = current.cost + dist
  159. # cost = current.cost + 1
  160. priority = cost + Grid.dist(n, target)
  161. node = PathNode(*n, current)
  162. node.cost = cost
  163. nodes.put(node, priority)
  164. return None
  165. def path(self, start, target, known_only=False):
  166. nodes = Queue()
  167. origin = PathNode(*start)
  168. nodes.put(origin, 0)
  169. neighbors = []
  170. its, break_on, broken = 0, 10000000, False
  171. self.costs = {}
  172. for p, c in self.cells.items():
  173. cost = 0
  174. if c == "#":
  175. cost = -1
  176. elif known_only and c == "?":
  177. cost = -1
  178. else:
  179. cost = 2 if c == "?" else 1
  180. self.costs[p] = cost
  181. while nodes:
  182. current = nodes.get()
  183. if current == target or broken:
  184. path = []
  185. previous = current
  186. while previous:
  187. if previous != start:
  188. path.insert(0, previous)
  189. previous = previous.parent
  190. return path
  191. neighbors = self.neighbors[current]
  192. for x, y in neighbors:
  193. if (x, y) == current.parent:
  194. continue
  195. its += 1
  196. if its > break_on:
  197. broken = True
  198. break
  199. if self.costs[(x, y)] < 0:
  200. continue
  201. cost = current.cost + self.costs[(x, y)]
  202. priority = cost + Grid.dist((x, y), target)
  203. node = PathNode(x, y, current)
  204. node.cost = cost
  205. nodes.put(node, priority)
  206. return None
  207. def optim_path(self, origin, target, known_only=False, first_steps=False):
  208. way = self.way(origin, target, known_only)
  209. print(way)
  210. if way:
  211. if first_steps:
  212. path = self.path(origin, way[0], known_only)
  213. else:
  214. way.insert(0, origin)
  215. path = sum([self.path(start, target, known_only) for start, target in zip(way, way[1:])], [])
  216. else:
  217. path = self.path(origin, target, known_only)
  218. return path
  219. def explore(self):
  220. buffer = [self.kirk]
  221. tested = set()
  222. while buffer:
  223. new_buffer = []
  224. for p in buffer:
  225. for n in self.neighbors[p]:
  226. if n in tested:
  227. continue
  228. c = self.cells[n]
  229. if c == "?":
  230. return n
  231. if c != "#":
  232. new_buffer.append(n)
  233. tested.add(n)
  234. buffer = new_buffer
  235. return None
  236. raw = """
  237. ##############################
  238. #............................#
  239. #.#######################.#..#
  240. #.......................#.#..#
  241. #.....#.................#.#..#
  242. #.#######################.#..#
  243. #.....##......##......#....###
  244. #...####..##..##..##..#..#...#
  245. #.........##......##.....#...#
  246. ?##########################.##
  247. ?......#......#..............#
  248. ???....#.....................#
  249. ???.#..####################..#
  250. ???..........................#
  251. ???###########################
  252. """
  253. start = (4, 11)
  254. target = (6, 3)
  255. grid = Grid(raw, start, target)
  256. print(grid.graph(grid.corners))
  257. print("\n\n")
  258. t0 = time.time()
  259. path = grid.optim_path(start, target, True)
  260. print(time.time() - t0)
  261. print(len(path))
  262. print(grid.graph(path))
  263. print(path)
  264. # t0 = time.time()
  265. # path = grid.path(start, target, True)
  266. # print(time.time() - t0)
  267. #
  268. # print(len(path))
  269. # print(grid.graph(path))
  270. # print(path)