script.py 15 KB

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