script.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. import heapq
  2. import sys
  3. import time
  4. t0 = time.time()
  5. def log(*msg):
  6. print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr)
  7. class Queue():
  8. def __init__(self):
  9. self.items = []
  10. def __bool__(self):
  11. return bool(self.items)
  12. def __repr__(self):
  13. return str(self.items)
  14. def values(self):
  15. return (v for _, v in self.items)
  16. def put(self, item, priority):
  17. heapq.heappush(self.items, (priority, item))
  18. def fput(self, item, priority):
  19. while priority in [p for p, _ in self.items]:
  20. priority += 1
  21. self.put(item, priority)
  22. def get(self):
  23. return heapq.heappop(self.items)[1]
  24. class PathNode(tuple):
  25. def __new__(self, x, y, parent=None):
  26. n = tuple.__new__(self, (x, y))
  27. n.parent = parent
  28. n.cost = 0
  29. return n
  30. def __repr__(self):
  31. return f"<{self[0]}, {self[1]}, c:{self.cost}>"
  32. def ancestors(self):
  33. ancestors = []
  34. p = self.parent
  35. while p:
  36. ancestors.insert(0, p)
  37. p = p.parent
  38. return ancestors
  39. class Waypoint(tuple):
  40. def __new__(self, x, y, neighbors=None):
  41. n = tuple.__new__(self, (x, y))
  42. n.neighbors = neighbors or []
  43. return n
  44. def __repr__(self):
  45. return f"<({self[0]},{self[1]}), n:{self.neighbors}>"
  46. MOVES = {(0, -1) : "UP", (0, 1): "DOWN", (-1, 0): "LEFT", (1, 0) : "RIGHT"}
  47. h, w, countdown = [int(i) for i in input().split()]
  48. UP = "UP"
  49. RIGHT = "RIGHT"
  50. DOWN = "DOWN"
  51. LEFT = "LEFT"
  52. MOVES = [UP, RIGHT, DOWN, LEFT]
  53. class Grid():
  54. UNKNOWN = 0
  55. WALL = 1
  56. EMPTY = 2
  57. VECT = {UP: (0,-1), RIGHT: (1,0), DOWN: (0,1), LEFT: (-1,0)}
  58. INV = {UP: DOWN, RIGHT: LEFT, LEFT: RIGHT, DOWN: UP}
  59. def __init__(self, w, h):
  60. self.w = w
  61. self.h = h
  62. self.cells = {(x, y): Grid.UNKNOWN for x in range(self.w) for y in range(self.h)}
  63. self.start = None
  64. self.control_room = None
  65. self.kirk = None
  66. self.neighbors = {p: self.get_neighbors(*p) for p in self.cells}
  67. self.waypoints = []
  68. self.to_explore = None
  69. def update(self, kirk, scan):
  70. self.previous_pos = self.kirk
  71. self.kirk = kirk
  72. if not self.start:
  73. self.start = kirk
  74. for y, row in enumerate(scan):
  75. for x, c in enumerate(row):
  76. if c == "C":
  77. self.control_room = (x, y)
  78. self.cells[(x, y)] = {"#": Grid.WALL, "?": Grid.UNKNOWN}.get(c, Grid.EMPTY)
  79. if self.to_explore is not None:
  80. if self.cells[self.to_explore] != Grid.UNKNOWN:
  81. self.to_explore = None
  82. self.update_corners()
  83. def _repr_cell(self, p, show=[]):
  84. if p == self.kirk:
  85. return "T"
  86. elif p == self.start:
  87. return "S"
  88. elif p == self.control_room:
  89. return "C"
  90. elif self.cells[p] == Grid.WALL:
  91. return "#"
  92. elif p in show:
  93. return "x"
  94. elif self.cells[p] == Grid.EMPTY:
  95. return "."
  96. else:
  97. return "?"
  98. def graph(self, show=[]):
  99. return "\n".join(["".join([self._repr_cell((x, y), show) for x in range(self.w)]) for y in range(self.h)])
  100. def set_visited(self, p):
  101. self.cells[p] = Grid.EMPTY
  102. def set_wall(self, p):
  103. self.cells[p] = Grid.WALL
  104. def unknown_gravity_center(self):
  105. wx, wy, wtotal = 0,0,0
  106. for x, y in self.cells:
  107. if self.cells[(x, y)] == Grid.UNKNOWN:
  108. wx += x
  109. wy += y
  110. wtotal += 1
  111. return (wx // wtotal, wy // wtotal) if wtotal else None
  112. @staticmethod
  113. def pos_after(current, move):
  114. x, y = current
  115. dx, dy = Grid.VECT[move]
  116. return x + dx, y + dy
  117. def get_move_to(self, p):
  118. return next(m for m in MOVES if grid.pos_after(grid.kirk, m) == p)
  119. @staticmethod
  120. def dist(p1, p2):
  121. xa, ya = p1
  122. xb, yb = p2
  123. return abs(xa - xb) + abs(ya - yb)
  124. def get_neighbors(self, x, y, diags=False):
  125. neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
  126. if diags:
  127. neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
  128. return [(x, y) for x, y in neighs if 0 <= x < self.w and 0 <= y < self.h]
  129. def line(self, x1, y1, x2, y2):
  130. # special case
  131. if (x1, y1) == (x2, y2):
  132. return [(x1, y1)]
  133. # diagonal symmetry
  134. vertically_oriented = (abs(y2 - y1) > abs(x2 - x1))
  135. if vertically_oriented:
  136. y1, x1, y2, x2 = x1, y1, x2, y2
  137. # horizontal symmetry
  138. reversed_sym = (x1 > x2)
  139. if reversed_sym:
  140. x2, y2, x1, y1 = x1, y1, x2, y2
  141. # angle
  142. dx, dy = x2 - x1, y2 - y1
  143. alpha = (abs(dy) / dx)
  144. offset = 0.0
  145. step = 1 if dy > 0 else -1
  146. result = []
  147. y = y1
  148. for x in range(x1, x2 + 1):
  149. if vertically_oriented:
  150. result.append((y, x))
  151. else:
  152. result.append((x, y))
  153. offset += alpha
  154. if offset > 0.5:
  155. y += step
  156. offset -= 1.0
  157. if reversed_sym:
  158. result.reverse()
  159. return result
  160. def _get_quarters(self, x, y):
  161. return {(x - 1, y - 1): [(x - 1, y), (x - 1, y - 1), (x, y - 1)],
  162. (x + 1, y - 1): [(x, y - 1), (x + 1, y - 1), (x + 1, y)],
  163. (x + 1, y + 1): [(x + 1, y), (x + 1, y + 1), (x, y + 1)],
  164. (x - 1, y + 1): [(x, y + 1), (x - 1, y + 1), (x - 1, y)]}
  165. def update_corners(self):
  166. corners = set()
  167. for p, c in self.cells.items():
  168. if c == Grid.WALL:
  169. for corner, quarter in self._get_quarters(*p).items():
  170. if all(self.cells.get(n, Grid.WALL) != Grid.WALL for n in quarter):
  171. corners.add(corner)
  172. self.corners = list(corners)
  173. def way(self, start, target, known_only=False):
  174. nodes = Queue()
  175. origin = PathNode(*start)
  176. nodes.put(origin, 0)
  177. waypoints = set(self.corners) | {start, target}
  178. neighbors = {}
  179. passable = [Grid.EMPTY] if known_only else [Grid.EMPTY, Grid.UNKNOWN]
  180. for waypoint in waypoints:
  181. neighbors[waypoint] = []
  182. for other in waypoints:
  183. if other is waypoint:
  184. continue
  185. sight = self.line(*other, *waypoint)[1:-1]
  186. if all(self.cells.get(p, "#") in passable for p in sight) and not any(w in sight for w in waypoints):
  187. neighbors[waypoint].append((other, Grid.dist(waypoint, other)))
  188. while nodes:
  189. current = nodes.get()
  190. if current == target:
  191. path = []
  192. previous = current
  193. while previous:
  194. if previous != start:
  195. path.insert(0, previous)
  196. previous = previous.parent
  197. return path
  198. for n, dist in neighbors[current]:
  199. if n in current.ancestors():
  200. continue
  201. cost = current.cost + dist
  202. priority = cost + Grid.dist(n, target)
  203. node = PathNode(*n, current)
  204. node.cost = cost
  205. nodes.put(node, priority)
  206. return None
  207. def path(self, start, target, known_only=False):
  208. nodes = Queue()
  209. origin = PathNode(*start)
  210. nodes.put(origin, 0)
  211. neighbors = []
  212. its, break_on, broken = 0, 10000, False
  213. self.costs = {}
  214. for p, c in self.cells.items():
  215. cost = 0
  216. if c == Grid.WALL:
  217. cost = -1
  218. elif known_only and c == Grid.UNKNOWN:
  219. cost = -1
  220. else:
  221. cost = 2 if c == Grid.UNKNOWN else 1
  222. self.costs[p] = cost
  223. while nodes:
  224. current = nodes.get()
  225. if current == target or broken:
  226. path = []
  227. previous = current
  228. while previous:
  229. if previous != start:
  230. path.insert(0, previous)
  231. previous = previous.parent
  232. return path
  233. neighbors = self.neighbors[current]
  234. for x, y in neighbors:
  235. if (x, y) == current.parent:
  236. continue
  237. its += 1
  238. if its > break_on:
  239. broken = True
  240. break
  241. if self.costs[(x, y)] < 0:
  242. continue
  243. cost = current.cost + self.costs[(x, y)]
  244. priority = cost + Grid.dist((x, y), target)
  245. node = PathNode(x, y, current)
  246. node.cost = cost
  247. nodes.put(node, priority)
  248. return None
  249. def optim_path(self, origin, target, known_only=False, first_steps=False):
  250. way = self.way(origin, target, known_only)
  251. if way:
  252. if first_steps:
  253. path = self.path(origin, way[0], known_only)
  254. else:
  255. way.insert(0, origin)
  256. path = sum([self.path(start, target, known_only) for start, target in zip(way, way[1:])], [])
  257. else:
  258. path = self.path(origin, target, known_only)
  259. return path
  260. def explore(self):
  261. if not self.to_explore is None:
  262. return self.to_explore
  263. buffer = [self.kirk]
  264. tested = set()
  265. while buffer:
  266. new_buffer = []
  267. for p in buffer:
  268. for n in self.neighbors[p]:
  269. if n in tested:
  270. continue
  271. c = self.cells[n]
  272. if c == Grid.UNKNOWN:
  273. return n
  274. if c != Grid.WALL:
  275. new_buffer.append(n)
  276. tested.add(n)
  277. buffer = new_buffer
  278. return None
  279. grid = Grid(w, h)
  280. fuel = 1201
  281. coming_back = False
  282. turn = 0
  283. comeback_found = None
  284. while True:
  285. turn += 1
  286. fuel -= 1
  287. y, x = [int(i) for i in input().split()]
  288. grid.update((x, y), [list(input()) for j in range(grid.h)])
  289. if grid.kirk == grid.control_room:
  290. coming_back = True
  291. log("Position:", grid.kirk, "CR:", grid.control_room)
  292. log("Fuel:", fuel, "Countdown:", countdown)
  293. path = []
  294. if grid.control_room is None:
  295. log("> Looking for the control room")
  296. target = grid.explore()
  297. path = grid.optim_path(grid.kirk, target)
  298. next_move = grid.get_move_to(path[0])
  299. elif not coming_back:
  300. log("> Have found the control room")
  301. if not comeback_found:
  302. comeback = grid.optim_path(grid.control_room, grid.start, True)
  303. else:
  304. comeback = comeback_found
  305. log("comeback's length: ", len(comeback) if comeback else None)
  306. log("comeback: ", comeback)
  307. if not comeback:
  308. log("<!> Path to start can not be computed")
  309. if fuel > 300:
  310. log("<!> Path to start can not be computed: explore")
  311. target = grid.explore()
  312. path = grid.optim_path(grid.kirk, target)
  313. next_move = grid.get_move_to(path[0])
  314. else:
  315. log("<!> Path to start can not be computed: go to control room")
  316. path = grid.optim_path(grid.kirk, grid.control_room)
  317. next_move = grid.get_move_to(path[0])
  318. elif len(comeback) > countdown:
  319. log("<!> Path to start is to long, keep exploring")
  320. target = grid.explore()
  321. path = grid.optim_path(grid.kirk, target)
  322. next_move = grid.get_move_to(path[0])
  323. else:
  324. log("> Go to the control room")
  325. comeback_found = comeback
  326. path = grid.optim_path(grid.kirk, grid.control_room)
  327. next_move = grid.get_move_to(path[0])
  328. else:
  329. log("> Come back to the ship")
  330. path = grid.optim_path(grid.kirk, grid.start, True, True)
  331. next_move = grid.get_move_to(path[0])
  332. log("\n"+grid.graph(path))
  333. print(next_move)