script.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. '''
  2. @author: olivier.massot, 2019
  3. '''
  4. import heapq
  5. import sys
  6. import time
  7. # TODO: what about the PIVOT_BACK in requirements?
  8. # TODO: do not use a rock for interception if it is behind indiana
  9. #
  10. t0 = time.time()
  11. def log(*msg):
  12. print("{} - ".format(str(time.time() - t0)[:5]), *msg, file=sys.stderr)
  13. TOP = 0
  14. LEFT = 1
  15. RIGHT = 2
  16. BOTTOM = 3
  17. DIRS = {TOP: (0, -1), BOTTOM: (0, 1), RIGHT: (1, 0), LEFT: (-1, 0)}
  18. PIVOT_UNCHANGED = 0
  19. PIVOT_LEFT = 1
  20. PIVOT_RIGHT = 2
  21. PIVOT_BACK = 3
  22. PIVOTS = {PIVOT_UNCHANGED: 0, PIVOT_LEFT: 1, PIVOT_RIGHT: 1, PIVOT_BACK: 2}
  23. COMMAND = {PIVOT_LEFT: "LEFT", PIVOT_RIGHT: "RIGHT"}
  24. SYM = {TOP: BOTTOM, BOTTOM: TOP, LEFT: RIGHT, RIGHT: LEFT}
  25. class Queue():
  26. def __init__(self):
  27. self.items = []
  28. def __bool__(self):
  29. return bool(self.items)
  30. def __repr__(self):
  31. return str(self.items)
  32. def values(self):
  33. return (v for _, v in self.items)
  34. def put(self, item, priority):
  35. heapq.heappush(self.items, (priority, item))
  36. def fput(self, item, priority):
  37. while priority in [p for p, _ in self.items]:
  38. priority += 1
  39. self.put(item, priority)
  40. def get(self):
  41. return heapq.heappop(self.items)[1]
  42. def fget(self):
  43. return heapq.heappop(self.items)
  44. class Position(tuple):
  45. def __new__(self, x, y, face):
  46. n = tuple.__new__(self, (x, y,face))
  47. return n
  48. @property
  49. def x(self):
  50. return self[0]
  51. @property
  52. def y(self):
  53. return self[1]
  54. @property
  55. def coords(self):
  56. return self[:2]
  57. @property
  58. def face(self):
  59. return self[2]
  60. class Item():
  61. def __init__(self, x, y, side):
  62. self.pos = Position(x, y, side)
  63. @classmethod
  64. def from_input(cls, x, y, strface):
  65. return cls(int(x), int(y), ["TOP", "LEFT", "RIGHT", "BOTTOM"].index(strface))
  66. class Indi(Item):
  67. graph = "I"
  68. class Rock(Item):
  69. graph = "R"
  70. def __init__(self, *args):
  71. super().__init__(*args)
  72. self.trajectory = None
  73. self.crash_on = None
  74. class RockThreat(Rock):
  75. graph = "T"
  76. class Piece():
  77. type = 0
  78. pipes = {}
  79. pivoted = {PIVOT_LEFT: 0, PIVOT_RIGHT: 0, PIVOT_BACK: 0}
  80. graph = ["...",
  81. "...",
  82. "..."]
  83. def __init__(self, x, y, locked=False):
  84. self.x = int(x)
  85. self.y = int(y)
  86. self.locked = locked
  87. def __repr__(self):
  88. return "<{}{}:{}>".format(self.type, "X"*self.locked, (self.x, self.y))
  89. def result(self, from_):
  90. return self.pipes[from_]
  91. def open_on(self, face_in):
  92. return face_in in self.pipes
  93. def apply(self, face_in):
  94. return self.pipes.get(face_in, None)
  95. def connects(self, face_in, face_out):
  96. return self.apply(face_in) == face_out
  97. def useless_pivot(self, pivot):
  98. return self.pivoted[pivot] == self.type
  99. class Piece0(Piece):
  100. pass
  101. class Piece1(Piece0):
  102. type = 1
  103. pipes = {TOP: BOTTOM, RIGHT: BOTTOM, LEFT: BOTTOM}
  104. pivoted = {PIVOT_LEFT: 1, PIVOT_RIGHT: 1, PIVOT_BACK: 1}
  105. graph = ["x x",
  106. " ",
  107. "x x"]
  108. class Piece2(Piece0):
  109. type = 2
  110. pipes = {LEFT: RIGHT, RIGHT: LEFT}
  111. pivoted = {PIVOT_LEFT: 3, PIVOT_RIGHT: 3, PIVOT_BACK: 2}
  112. graph = ["xxx",
  113. " ",
  114. "xxx"]
  115. class Piece3(Piece0):
  116. type = 3
  117. pipes = {TOP: BOTTOM}
  118. pivoted = {PIVOT_LEFT: 2, PIVOT_RIGHT: 2, PIVOT_BACK: 3}
  119. graph = ["x x",
  120. "x x",
  121. "x x"]
  122. class Piece4(Piece0):
  123. type = 4
  124. pipes = {TOP: LEFT, RIGHT: BOTTOM}
  125. pivoted = {PIVOT_LEFT: 5, PIVOT_RIGHT: 5, PIVOT_BACK: 4}
  126. graph = ["x x",
  127. " / ",
  128. "x x"]
  129. class Piece5(Piece0):
  130. type = 5
  131. pipes = {LEFT: BOTTOM, TOP: RIGHT}
  132. pivoted = {PIVOT_LEFT: 4, PIVOT_RIGHT: 4, PIVOT_BACK: 5}
  133. graph = ["x x",
  134. " \ ",
  135. "x x"]
  136. class Piece6(Piece0):
  137. type = 6
  138. pipes = {LEFT: RIGHT, RIGHT: LEFT}
  139. pivoted = {PIVOT_LEFT: 9, PIVOT_RIGHT: 7, PIVOT_BACK: 8}
  140. graph = ["x x",
  141. " ",
  142. "xxx"]
  143. class Piece7(Piece0):
  144. type = 7
  145. pipes = {TOP: BOTTOM, RIGHT: BOTTOM}
  146. pivoted = {PIVOT_LEFT: 6, PIVOT_RIGHT: 8, PIVOT_BACK: 9}
  147. graph = ["x x",
  148. "x ",
  149. "x x"]
  150. class Piece8(Piece0):
  151. type = 8
  152. pipes = {RIGHT: BOTTOM, LEFT: BOTTOM}
  153. pivoted = {PIVOT_LEFT: 7, PIVOT_RIGHT: 9, PIVOT_BACK: 6}
  154. graph = ["xxx",
  155. " ",
  156. "x x"]
  157. class Piece9(Piece0):
  158. type = 9
  159. pipes = {TOP: BOTTOM, LEFT: BOTTOM}
  160. pivoted = {PIVOT_LEFT: 8, PIVOT_RIGHT: 6, PIVOT_BACK: 7}
  161. graph = ["x x",
  162. " x",
  163. "x x"]
  164. class Piece10(Piece0):
  165. type = 10
  166. pipes = {TOP: LEFT}
  167. pivoted = {PIVOT_LEFT: 13, PIVOT_RIGHT: 11, PIVOT_BACK: 12}
  168. graph = ["x x",
  169. " x",
  170. "xxx"]
  171. class Piece11(Piece0):
  172. type = 11
  173. pipes = {TOP: RIGHT}
  174. pivoted = {PIVOT_LEFT: 10, PIVOT_RIGHT: 12, PIVOT_BACK: 13}
  175. graph = ["x x",
  176. "x ",
  177. "xxx"]
  178. class Piece12(Piece0):
  179. type = 12
  180. pipes = {RIGHT: BOTTOM}
  181. pivoted = {PIVOT_LEFT: 11, PIVOT_RIGHT: 13, PIVOT_BACK: 10}
  182. graph = ["xxx",
  183. "x ",
  184. "x x"]
  185. class Piece13(Piece0):
  186. type = 13
  187. pipes = {LEFT: BOTTOM}
  188. pivoted = {PIVOT_LEFT: 12, PIVOT_RIGHT: 10, PIVOT_BACK: 11}
  189. graph = ["xxx",
  190. " x",
  191. "x x"]
  192. classes = [Piece0, Piece1, Piece2, Piece3, Piece4,
  193. Piece5, Piece6, Piece7, Piece8, Piece9,
  194. Piece10, Piece11, Piece12, Piece13]
  195. def new_piece(x, y, v):
  196. x, y, v = int(x), int(y), int(v)
  197. type_, locked = abs(v), v < 0
  198. piece = classes[type_](x, y, locked)
  199. return piece
  200. def turn(piece, pivot):
  201. if pivot == PIVOT_UNCHANGED:
  202. return piece
  203. if piece.locked:
  204. raise Exception("can not pivot a locked piece")
  205. pivoted_type = piece.pivoted[pivot]
  206. if pivoted_type == piece.type:
  207. return piece
  208. return classes[pivoted_type](piece.x, piece.y)
  209. class Trajectory(list):
  210. def __init__(self, *args):
  211. super().__init__(args)
  212. self.stops = []
  213. self.interceptions = []
  214. def elide(self, at):
  215. new_t = Trajectory(*self[:at])
  216. new_t.stops = self.stops
  217. return new_t
  218. def __repr__(self):
  219. return "<{},stops={},intercept:{}>".format(list(self), self.stops, self.interceptions)
  220. class PathNode():
  221. def __init__(self, position, parent=None, pivot=PIVOT_UNCHANGED):
  222. self.pos = position
  223. self.pivot = pivot
  224. self.parent = parent
  225. self.cost = 0
  226. self.round = 0
  227. self.require = []
  228. self.deadend = False
  229. def __lt__(self, other):
  230. return self.pos < other.pos
  231. def __repr__(self):
  232. return f"<{self.pos}, pivot:{self.pivot}, cost:{self.cost}, round:{self.round}, require:{self.require}, deadend:{self.deadend}>"
  233. class DeadEnd(Exception):
  234. pass
  235. class Collision(Exception):
  236. pass
  237. class Grid():
  238. def __init__(self, w, h, rows, x_exit):
  239. self.w = w
  240. self.h = h
  241. self.cells = {(x, y): new_piece(x, y, v) for y, row in enumerate(rows) for x, v in enumerate(row)}
  242. # add exit
  243. self.exit = (x_exit, h)
  244. self.cells[(x_exit, h)] = Piece1(x_exit, h, locked=True)
  245. self.indi = None
  246. self.rocks = []
  247. def get_cell(self, coords):
  248. return self.cells.get(coords, Piece0(*coords))
  249. def update(self, indi, rocks):
  250. self.indi = indi
  251. self.rocks = rocks
  252. self.include_threats()
  253. self.update_rocks_moves()
  254. @property
  255. def items(self):
  256. return [self.indi] + self.rocks
  257. def graph(self):
  258. res = "\n "
  259. res += "".join([f"{x:02d} " for x in range(self.w)])
  260. res += "\n"
  261. items = []
  262. if self.indi:
  263. items.append(self.indi)
  264. items += reversed(self.rocks)
  265. for y in range(self.h):
  266. for i in range(3):
  267. if i ==0:
  268. res += "{:02d} ".format(y)
  269. else:
  270. res += " "
  271. for x in range(self.w):
  272. piece = grid.cells[(x, y)]
  273. line = list(piece.graph[i])
  274. for item in items:
  275. if item.pos.coords == (x, y):
  276. if item.pos.face == TOP and i == 0:
  277. line[1] = item.graph
  278. elif item.pos.face == RIGHT and i == 1:
  279. line[2] = item.graph
  280. elif item.pos.face == LEFT and i == 1:
  281. line[0] = item.graph
  282. elif item.pos.face == BOTTOM and i == 2:
  283. line[1] = item.graph
  284. if item.pos.x == x_exit and item.pos.y == h - 1 and i == 2:
  285. line[1] = "E"
  286. line = "".join(line)
  287. if piece.locked:
  288. line = line.replace("x", "#")
  289. res += line
  290. # res += "|"
  291. res += "\n"
  292. return res
  293. def item_at(self, x, y, side=None):
  294. return next((i.pos.coords == (x, y) and (side is None or i.pos.face == side) for i in self.items), None)
  295. @staticmethod
  296. def dist(p1, p2):
  297. xa, ya = p1
  298. xb, yb = p2
  299. return abs(xa - xb) + abs(ya - yb)
  300. def collide(self, pos1, pos2, pivot=PIVOT_UNCHANGED):
  301. if pos1.coords != pos2.coords:
  302. return False
  303. if pos1.coords == self.exit:
  304. return False
  305. if pos1 == pos2:
  306. return True
  307. piece = self.get_cell(pos1.coords)
  308. if pivot:
  309. piece = turn(piece, pivot)
  310. if type(piece) in [Piece4, Piece5]:
  311. return piece.apply(pos1.face) == pos2.face
  312. if not piece.open_on(pos1.face) or not piece.open_on(pos2.face):
  313. return False
  314. return True
  315. def include_threats(self):
  316. threats = []
  317. for p, cell in self.cells.items():
  318. x, y = p
  319. if x == 0:
  320. if LEFT in cell.pipes and not self.item_at(x, y):
  321. threats.append(RockThreat(x, y, LEFT))
  322. elif x == (self.w - 1) and not self.item_at(x, y):
  323. if RIGHT in cell.pipes:
  324. threats.append(RockThreat(x, y, RIGHT))
  325. if y == 0:
  326. if TOP in cell.pipes and not self.item_at(x, y):
  327. threats.append(RockThreat(x, y, TOP))
  328. self.rocks += threats
  329. def interception_path(self, rock, target):
  330. subject = rock
  331. nodes = Queue()
  332. origin = PathNode(subject.pos)
  333. nodes.put(origin, 0)
  334. its, break_on = 0, 2000
  335. while nodes:
  336. current = nodes.get()
  337. its += 1
  338. if its > break_on:
  339. log("interception pathfinding broken")
  340. return None
  341. for at_ in range(len(target.trajectory)):
  342. if self.collide(current.pos, target.trajectory[at_]):
  343. path = []
  344. previous = current.parent
  345. while previous:
  346. if previous != origin:
  347. path.insert(0, previous)
  348. previous = previous.parent
  349. return path, at_
  350. next_pos = self.after(current.pos, current.pivot)
  351. if next_pos is None:
  352. log(f"! Next piece doesn't exist: ", current.pos, current.pivot)
  353. continue
  354. next_cell = self.get_cell(next_pos.coords)
  355. for pivot, rotations in PIVOTS.items():
  356. if rotations > 0:
  357. if next_cell.locked:
  358. continue
  359. elif next_cell.pivoted[pivot] == next_cell.type:
  360. # useless rotation
  361. continue
  362. if current is origin and rotations > 1:
  363. continue
  364. pivoted = turn(next_cell, pivot)
  365. if pivoted.open_on(next_pos.face):
  366. node = PathNode(next_pos, current, pivot)
  367. node.cost = current.cost + rotations
  368. node.round = current.round + 1
  369. priority = node.cost
  370. nodes.put(node, priority)
  371. return []
  372. def update_rocks_moves(self):
  373. for rock in self.rocks:
  374. trajectory = Trajectory(*self.trajectory(rock))
  375. if trajectory:
  376. rock.crash_on = self.after(trajectory[-1])
  377. for i, pos in enumerate(trajectory):
  378. piece = self.cells[pos.coords]
  379. if piece.locked or i == 0:
  380. continue
  381. # stops
  382. for pivot in [PIVOT_LEFT, PIVOT_RIGHT, PIVOT_BACK]:
  383. if piece.useless_pivot(pivot):
  384. continue
  385. pos_after = self.after(pos, pivot)
  386. if not pos_after or not self.get_cell(pos_after.coords).open_on(pos_after.face):
  387. stop = (i, pivot)
  388. trajectory.stops.append(stop)
  389. break
  390. rock.trajectory = trajectory
  391. self.apply_collisions()
  392. for rock in self.rocks:
  393. # find an interception possibility
  394. for other in self.rocks:
  395. if rock is other:
  396. continue
  397. res = self.interception_path(other, rock)
  398. if res:
  399. path, at_ = res
  400. if any(self.collide(self.indi.pos, n.pos) for n in path):
  401. continue
  402. rock.trajectory.interceptions += [(n.pos, n.pivot, at_) for n in path if n.pivot]
  403. # log([rock.trajectory for rock in self.rocks])
  404. log([rock.crash_on for rock in self.rocks])
  405. def apply_collisions(self):
  406. real_rocks = [rock for rock in self.rocks if type(rock) is Rock]
  407. if not real_rocks:
  408. return
  409. for turn in range(max([len(rock.trajectory) for rock in real_rocks])):
  410. for i in range(len(real_rocks)):
  411. for j in range(i+1, len(real_rocks)):
  412. try:
  413. if self.collide(real_rocks[i].trajectory[turn], real_rocks[j].trajectory[turn]):
  414. log("Two rocks collide at ", real_rocks[i].trajectory[turn])
  415. self.rocks[i].trajectory = self.rocks[i].trajectory.elide(turn + 1)
  416. self.rocks[j].trajectory = self.rocks[j].trajectory.elide(turn + 1)
  417. except IndexError:
  418. pass
  419. def after(self, position, pivot=PIVOT_UNCHANGED):
  420. x, y, face = position
  421. piece = self.cells[(x, y)]
  422. piece = turn(piece, pivot)
  423. face_out = piece.apply(face)
  424. if face_out is None:
  425. return None
  426. dx, dy = DIRS[face_out]
  427. position = Position(x+dx, y+dy, SYM[face_out])
  428. return position
  429. def trajectory(self, item, with_start=True):
  430. path = []
  431. current = item.pos
  432. while current is not None:
  433. path.append(current)
  434. next_pos = self.after(current)
  435. if not next_pos or next_pos.coords == self.exit or not self.get_cell(next_pos.coords).open_on(next_pos.face):
  436. break
  437. current = self.after(current)
  438. return path if with_start else path[1:]
  439. def path(self):
  440. subject = self.indi
  441. nodes = Queue()
  442. origin = PathNode(subject.pos)
  443. nodes.put(origin, 0)
  444. its, break_on = 0, 6000
  445. broken = False
  446. while nodes:
  447. current = nodes.get()
  448. its += 1
  449. if its > break_on:
  450. log("pathfinding broken")
  451. broken = True
  452. if broken or current.pos.coords == self.exit:
  453. path = []
  454. previous = current.parent
  455. while previous:
  456. if previous != origin:
  457. path.insert(0, previous)
  458. previous = previous.parent
  459. return path
  460. next_pos = self.after(current.pos, current.pivot)
  461. if next_pos is None:
  462. log(f"! Next piece doesn't exist: ", current.pos, current.pivot)
  463. continue
  464. next_cell = self.get_cell(next_pos.coords)
  465. requirements = set()
  466. deadend = False
  467. try:
  468. for rock in self.rocks:
  469. for round_ in (current.round +1, current.round):
  470. if len(rock.trajectory) > round_ and self.collide(rock.trajectory[round_], next_pos):
  471. alternatives = [Requirement(rock.trajectory[i], pivot, i) for i, pivot in rock.trajectory.stops if 0 < i <= round_]
  472. if alternatives:
  473. require = alternatives[-1]
  474. requirements.add(require)
  475. break
  476. else:
  477. interceptions = [Requirement(pos, pivot, i) for pos, pivot, i in rock.trajectory.interceptions if 0 < i <= round_]
  478. if interceptions:
  479. requirements |= set(interceptions)
  480. elif type(rock) == Rock: #unstoppable rock
  481. log("node is deadend: ", next_pos.coords)
  482. raise DeadEnd
  483. except DeadEnd:
  484. deadend = True
  485. for pivot, rotations in PIVOTS.items():
  486. if rotations > 0:
  487. if next_cell.locked:
  488. continue
  489. elif next_cell.useless_pivot(pivot):
  490. # useless rotation
  491. continue
  492. if current is origin and rotations > 1:
  493. continue
  494. collision = False
  495. for rock in self.rocks:
  496. if not type(rock) is Rock:
  497. continue
  498. if rock.crash_on and current.round + 1 == len(rock.trajectory) and self.collide(rock.crash_on, next_pos, pivot):
  499. log(f"pivoting from {pivot} would lead to a collision at {next_pos}")
  500. collision = True
  501. break
  502. pivoted = turn(next_cell, pivot)
  503. if pivoted.open_on(next_pos.face):
  504. node = PathNode(next_pos, current, pivot)
  505. node.cost = current.cost + rotations + len(requirements) + 3 * deadend + 5 * collision
  506. node.round = current.round + 1
  507. node.require = requirements
  508. node.deadend = deadend
  509. priority = node.cost
  510. nodes.put(node, priority)
  511. return []
  512. def apply(self, action):
  513. self.cells[action.coords] = turn(self.cells[action.coords], action.pivot)
  514. class Action():
  515. def __init__(self, coords, pivot):
  516. self.coords = coords[:2]
  517. self.pivot = pivot
  518. def command(self):
  519. return "{} {} {}".format(*self.coords, COMMAND.get(self.pivot, "Invalid"))
  520. def __repr__(self):
  521. return f"<{self.command()}>"
  522. def __eq__(self, other):
  523. return self.coords == other.coords and self.pivot == other.pivot
  524. def __hash__(self):
  525. return 0
  526. class Requirement(Action):
  527. def __init__(self, coords, pivot, last_chance_at):
  528. super().__init__(coords, pivot)
  529. self.last_chance_at = last_chance_at
  530. def command(self):
  531. return "{} {} {}".format(*self.coords, COMMAND.get(self.pivot, "Invalid"))
  532. def __repr__(self):
  533. return f"<REQ {self.command()} before {self.last_chance_at}>"
  534. # Get input
  535. w, h = [int(i) for i in input().split()]
  536. rows = [input().split() for _ in range(h)]
  537. x_exit = int(input())
  538. # instanciate grid
  539. grid = Grid(w, h, rows, x_exit)
  540. while True:
  541. # get input
  542. indi = Indi.from_input(*input().split())
  543. rocks = [Rock.from_input(*input().split()) for _ in range(int(input()))]
  544. # update grid
  545. grid.update(indi, rocks)
  546. log(grid.graph())
  547. path = grid.path()
  548. log(path)
  549. if path:
  550. plan = [None for _ in range(len(path))]
  551. # basic plan
  552. for i, node in enumerate(path):
  553. if node.pivot in [PIVOT_LEFT, PIVOT_RIGHT]:
  554. plan[i] = Action(node.pos, node.pivot)
  555. elif node.pivot == PIVOT_BACK:
  556. plan[i] = Action(node.pos, PIVOT_RIGHT)
  557. log("first plan:", plan)
  558. # add requirements
  559. requirements = set()
  560. for i, node in enumerate(path):
  561. if node.pivot == PIVOT_BACK:
  562. action = Requirement(node.pos, PIVOT_RIGHT, i)
  563. requirements.add(action)
  564. for action in node.require:
  565. requirements.add(action)
  566. log(requirements)
  567. for action in sorted(requirements, key=lambda x: x.last_chance_at):
  568. for j in range(action.last_chance_at - 1, -1, -1):
  569. if j < len(plan) and plan[j] is None:
  570. plan[j] = action
  571. break
  572. else:
  573. log("! action required could not be inserted: ", action)
  574. log("completed:", plan)
  575. else:
  576. log("no path, use previous plan")
  577. try:
  578. action = plan.pop(0)
  579. if not next((type(a) is Requirement for a in plan if not a is None), False):
  580. while plan and not action:
  581. action = plan.pop(0)
  582. if action:
  583. grid.apply(action)
  584. print(action.command())
  585. else:
  586. print("WAIT")
  587. except IndexError:
  588. print("WAIT")