test.py 9.0 KB

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