script_refact.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. '''
  2. @author: olivier.massot, 2019
  3. '''
  4. import heapq
  5. import sys
  6. DEBUG = True
  7. def log(x):
  8. if DEBUG:
  9. print(x, file=sys.stderr)
  10. # Cells
  11. START_0 = '0'
  12. START_1 = '1'
  13. BLUEBERRIES_CRATE = "B"
  14. ICE_CREAM_CRATE = "I"
  15. STRAWBERRIES_CRATE = "S"
  16. CHOPPING_BOARD = "C"
  17. DOUGH_CRATE = "H"
  18. WINDOW = "W"
  19. EMPTY_TABLE = "#"
  20. DISHWASHER = "D"
  21. FLOOR_CELL = "."
  22. OVEN = "O"
  23. # Items
  24. NONE = "NONE"
  25. DISH = "DISH"
  26. ICE_CREAM = "ICE_CREAM"
  27. BLUEBERRIES = "BLUEBERRIES"
  28. STRAWBERRIES = "STRAWBERRIES"
  29. CHOPPED_STRAWBERRIES = "CHOPPED_STRAWBERRIES"
  30. DOUGH = "DOUGH"
  31. CROISSANT = "CROISSANT"
  32. location = {DISH: DISHWASHER,
  33. ICE_CREAM: ICE_CREAM_CRATE,
  34. BLUEBERRIES: BLUEBERRIES_CRATE,
  35. STRAWBERRIES: STRAWBERRIES_CRATE,
  36. CHOPPED_STRAWBERRIES: CHOPPING_BOARD,
  37. CROISSANT: OVEN,
  38. DOUGH: DOUGH_CRATE,
  39. OVEN: OVEN,
  40. WINDOW: WINDOW}
  41. special = list(location.values())
  42. preq = {CHOPPED_STRAWBERRIES: STRAWBERRIES,
  43. CROISSANT: DOUGH}
  44. needs_preparation = {STRAWBERRIES: CHOPPED_STRAWBERRIES,
  45. DOUGH: CROISSANT}
  46. class Base():
  47. def __repr__(self):
  48. return f"<{self.__class__.__name__}: {self.__dict__}>"
  49. class Customer(Base):
  50. def __init__(self, item, award):
  51. self.item = item
  52. self.award = int(award)
  53. class Cook(Base):
  54. def __init__(self, x, y, item):
  55. self.x = int(x)
  56. self.y = int(y)
  57. self.item = item
  58. self.order = []
  59. @property
  60. def pos(self):
  61. return (self.x, self.y)
  62. def update(self, x, y, item):
  63. self.x = int(x)
  64. self.y = int(y)
  65. self.item = item
  66. def use(self, x, y, msg=""):
  67. print("USE", x, y, msg)
  68. def move(self, x, y):
  69. print("MOVE", x, y)
  70. def wait(self):
  71. print("WAIT")
  72. class Table(Base):
  73. def __init__(self, x, y, item):
  74. self.x = int(x)
  75. self.y = int(y)
  76. self.item = item
  77. class Oven(Base):
  78. def __init__(self, contents, timer):
  79. self.contents = contents
  80. self.timer = int(timer)
  81. class PathNode(tuple):
  82. def __new__(self, x, y, parent=None):
  83. n = tuple.__new__(self, (x, y))
  84. n.parent = parent
  85. n.cost = 0
  86. return n
  87. class Grid(Base):
  88. def __init__(self, cells):
  89. self.cells = cells
  90. self.w, self.h = len(cells[0]), len(cells)
  91. self.add_costs = {}
  92. def at(self, x, y):
  93. return self.cells[y][x]
  94. def flatten(self):
  95. return [(x, y, c) for y, row in enumerate(self.cells) for x, c in enumerate(row)]
  96. @property
  97. def coords(self):
  98. return [(x, y) for y in range(self.h) for x in range(self.w)]
  99. def where_are(self, content):
  100. return [(x, y) for x, y, c in self.flatten() if c == content]
  101. @staticmethod
  102. def distance(from_, to_):
  103. return abs(from_[0] - to_[0]) + abs(from_[1] - to_[1])
  104. def closest(self, from_, content):
  105. return sorted([(c, Grid.distance(from_, c)) for c in self.where_are(content)], key=lambda k: k[1])[0]
  106. def neighbors(self, x, y, diags=True):
  107. neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
  108. if diags:
  109. neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
  110. return [(x, y) for x, y in neighs if 0 <= x < self.w and 0 <= y < self.h]
  111. def passable(self, x, y):
  112. return self.at(x, y) in (FLOOR_CELL, START_0, START_1)
  113. def cost(self, x, y):
  114. return 10 + self.add_costs.get((x, y), 0)
  115. def path(self, origin, target, incl_start=False):
  116. nodes = []
  117. origin = PathNode(*origin)
  118. targets = grid.neighbors(*target)
  119. heapq.heappush(nodes, (0, origin))
  120. while nodes:
  121. current = heapq.heappop(nodes)[1]
  122. if current in targets:
  123. path = []
  124. next_ = current
  125. while next_:
  126. if next_ != origin or incl_start:
  127. path.insert(0, next_)
  128. next_ = next_.parent
  129. return path
  130. neighbors = self.neighbors(*current, False)
  131. for x, y in neighbors:
  132. if not self.passable(x, y):
  133. continue
  134. cost = current.cost + self.cost(x, y)
  135. priority = cost + 10 * (abs(x - target[0]) + abs(y - target[1]))
  136. node = PathNode(x, y, current)
  137. node.cost = cost
  138. heapq.heappush(nodes, (priority, node))
  139. else:
  140. return None
  141. # input vars
  142. num_all_customers = int(input())
  143. all_customers = [Customer(*input().split()) for _ in range(num_all_customers)]
  144. grid = Grid([list(input()) for i in range(7)])
  145. log(f"{num_all_customers} customers: {all_customers}")
  146. log(f"grid: {grid}")
  147. class Task(Base):
  148. def __init__(self, name, from_):
  149. self.name = name
  150. self.loc = location[name]
  151. self.pos, self.dist = grid.closest(from_, self.loc)
  152. player = Cook(-1, -1, "")
  153. partner = Cook(-1, -1, "")
  154. class Action(Base):
  155. name = ""
  156. needs_free_hands = False
  157. where = ""
  158. NOT_STARTED = 0
  159. RUNNING = 1
  160. ENDED = 2
  161. class Preparation(Base):
  162. name = ""
  163. steps = []
  164. oven_time = 0
  165. def __init__(self):
  166. self.pending = self.steps
  167. self.cooked = False
  168. @property
  169. def complete(self):
  170. return not self.pending
  171. @property
  172. def available(self):
  173. return not self.oven_time or self.cooked
  174. def whats_next(self):
  175. return self.pending.pop(0)
  176. class Bluberries(Preparation):
  177. name = BLUEBERRIES
  178. steps = [BLUEBERRIES_CRATE]
  179. class Icecream(Preparation):
  180. name = ICE_CREAM
  181. steps = [ICE_CREAM_CRATE]
  182. class ChoppedStrawberries(Preparation):
  183. name = CHOPPED_STRAWBERRIES
  184. steps = [STRAWBERRIES_CRATE, CHOPPING_BOARD]
  185. class Croissant(Preparation):
  186. name = CROISSANT
  187. steps = [DOUGH_CRATE, OVEN]
  188. oven_time = 10
  189. preparations = {BLUEBERRIES: Bluberries,
  190. ICE_CREAM: Icecream,
  191. CHOPPED_STRAWBERRIES: ChoppedStrawberries,
  192. CROISSANT: Croissant}
  193. class Order():
  194. def __init__(self, order):
  195. self.order = [preparations[o] for o in order]
  196. def complete(self):
  197. return all(k for k in self.order in player.item)
  198. def update(self, player_item):
  199. for p in self.order:
  200. if p.name in player_item:
  201. p.complete = True
  202. def on_dish(self):
  203. return DISH in player.item
  204. def pending(self):
  205. if self.complete:
  206. return [WINDOW]
  207. return [p.pending[0] for p in self.pending if p.available and not p.complete]
  208. while True:
  209. turns_remaining = int(input())
  210. player.update(*input().split())
  211. log(f"*** player: {player}")
  212. partner.update(*input().split())
  213. log(f"*** partner: {partner}")
  214. num_tables_with_items = int(input()) # the number of tables in the kitchen that currently hold an item
  215. tables = [Table(*input().split()) for _ in range(num_tables_with_items)]
  216. log(f"*** tables: {tables}")
  217. oven = Oven(*input().split())
  218. log(f"*** oven: {oven}")
  219. num_customers = int(input()) # the number of customers currently waiting for food
  220. customers = [Customer(*input().split()) for _ in range(num_customers)]
  221. log(f"*** customers: {customers}")
  222. ### SCRIPT
  223. # if no current order, take the most beneficial
  224. if not player.order:
  225. queue = sorted(customers, reverse=True, key=lambda x: x.award)
  226. player.order = queue[0].item.split('-')
  227. log(f'>>> new order taken: {player.order}')
  228. todo = [Task(t, player.pos) for t in player.order if t not in player.item]
  229. log(f"todo: {todo}")
  230. if not todo:
  231. # no task left, target is the window
  232. next_task = Task(WINDOW, player.pos)
  233. player.order, todo = [], []
  234. elif player.item in needs_preparation:
  235. # cook has an ingredient in hands, he needs to prepare it
  236. next_task = Task(needs_preparation[player.item], player.pos)
  237. elif any((t.name in preq for t in todo)) and not DISH in player.item:
  238. # hands shall be free to handle ingredients, we go for it first if possible
  239. next_task = Task(next((preq[t.name] for t in todo if t.name in preq)), player.pos)
  240. elif oven.timer < 1:
  241. # oven has dinged
  242. next_task = Task(OVEN, player.pos)
  243. elif player.item != NONE and not DISH in player.item:
  244. # cook has something in its hands and no dish, he have to take one
  245. next_task = next((t for t in todo if t.name == DISH))
  246. else:
  247. # else, go for the closest task
  248. tasks = sorted(todo, key= lambda x: x.dist)
  249. next_task = next(iter(tasks), None)
  250. log(f"next_task: {next_task}")
  251. # update grid movement costs following the probability of finding the partner here
  252. partner_could_be_there = [(x, y) for x, y in grid.coords if grid.passable(x, y) and grid.distance(partner.pos, (x, y)) <= 4]
  253. grid.add_costs = {}
  254. for x, y in partner_could_be_there:
  255. k1 = 2 if (x, y) == partner.pos else 1
  256. # cell is next to a special cell, partner has more chance to stop there
  257. k2 = 2 if any((c for c in grid.neighbors(x, y) if grid.at(*c) in special )) else 1
  258. grid.add_costs[(x, y)] = k1 * k2 * 3
  259. log(grid.add_costs)
  260. path = grid.path(player.pos, next_task.pos)
  261. log(path)
  262. if path is not None:
  263. if len(path) > 0:
  264. if len(path) > 4:
  265. player.move(*path[3])
  266. else:
  267. player.move(*path[-1])
  268. else:
  269. player.use(*next_task.pos)
  270. else:
  271. player.use(*next_task.pos)