cook_svg2.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. '''
  2. @author: olivier.massot, 2019
  3. '''
  4. import heapq
  5. import sys
  6. DEBUG = True
  7. def log(x):
  8. if DEBUG: print(x, file=sys.stderr)
  9. # -- Base class
  10. class Base():
  11. def __repr__(self):
  12. return f"<{self.__class__.__name__}: {self.__dict__}>"
  13. # --- locations
  14. class Location(Base):
  15. name = ""
  16. passable = False
  17. class SpecialLocation(Location):
  18. pass
  19. class DishWasher(SpecialLocation):
  20. pass
  21. class IcecreamCrate(SpecialLocation):
  22. pass
  23. class BlueberriesCrate(SpecialLocation):
  24. pass
  25. class StrawberriesCrate(SpecialLocation):
  26. pass
  27. class DoughCrate(SpecialLocation):
  28. pass
  29. class ChoppingBoard(SpecialLocation):
  30. pass
  31. class Oven(SpecialLocation):
  32. def __init__(self):
  33. self._content = None
  34. self._timer = 0
  35. @property
  36. def content(self):
  37. return self._content
  38. @content.setter
  39. def content(self, content):
  40. self._content = match[content]
  41. @property
  42. def timer(self):
  43. return self._timer
  44. @timer.setter
  45. def timer(self, timer):
  46. self._timer = int(timer)
  47. def update(self, content, timer):
  48. self.content = content
  49. self.timer = timer
  50. class Window(SpecialLocation):
  51. pass
  52. class Start(Location):
  53. passable = True
  54. class Start0(Start): pass
  55. class Start1(Start): pass
  56. class FloorCell(Location):
  57. passable = True
  58. class EmptyTable(Location):
  59. pass
  60. locations = [DishWasher, IcecreamCrate, BlueberriesCrate, StrawberriesCrate, DoughCrate, ChoppingBoard, Oven, Window, Start0, Start1, FloorCell, EmptyTable]
  61. special_locations = [l for l in locations if l is SpecialLocation]
  62. tables = []
  63. # -- Grid
  64. class PathNode(tuple):
  65. def __new__(self, x, y, parent=None):
  66. n = tuple.__new__(self, (x, y))
  67. n.parent = parent
  68. n.cost = 0
  69. return n
  70. class Grid(Base):
  71. def __init__(self, cells):
  72. self.cells = cells
  73. self.w, self.h = len(cells[0]), len(cells)
  74. self.add_costs = {}
  75. def at(self, x, y):
  76. return self.cells[y][x]
  77. def flatten(self):
  78. return [(x, y, c) for y, row in enumerate(self.cells) for x, c in enumerate(row)]
  79. @property
  80. def coords(self):
  81. return [(x, y) for y in range(self.h) for x in range(self.w)]
  82. def where_are(self, content):
  83. return [(x, y) for x, y, c in self.flatten() if c is content]
  84. @staticmethod
  85. def distance(from_, to_):
  86. return abs(from_[0] - to_[0]) + abs(from_[1] - to_[1])
  87. def items(self):
  88. return [c for row in self.cells for c in row]
  89. def closest_in(self, from_, coords):
  90. return sorted([(c, Grid.distance(from_, c)) for c in coords], key=lambda k: k[1])[0]
  91. def closest(self, from_, content):
  92. return self.closest_in(from_, self.where_are(content))
  93. def neighbors(self, x, y, diags=True):
  94. neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
  95. if diags:
  96. neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
  97. return [(x, y) for x, y in neighs if 0 <= x < self.w and 0 <= y < self.h]
  98. def passable(self, x, y):
  99. return self.at(x, y).passable
  100. def cost(self, x, y):
  101. return 10 + self.add_costs.get((x, y), 0)
  102. def path(self, origin, target, incl_start=False):
  103. nodes = []
  104. origin = PathNode(*origin)
  105. targets = grid.neighbors(*target)
  106. heapq.heappush(nodes, (0, origin))
  107. while nodes:
  108. current = heapq.heappop(nodes)[1]
  109. if current in targets:
  110. path = []
  111. next_ = current
  112. while next_:
  113. if next_ != origin or incl_start:
  114. path.insert(0, next_)
  115. next_ = next_.parent
  116. return path
  117. neighbors = self.neighbors(*current, False)
  118. for x, y in neighbors:
  119. if not self.passable(x, y):
  120. continue
  121. cost = current.cost + self.cost(x, y)
  122. priority = cost + 10 * (abs(x - target[0]) + abs(y - target[1]))
  123. node = PathNode(x, y, current)
  124. node.cost = cost
  125. heapq.heappush(nodes, (priority, node))
  126. else:
  127. return None
  128. class Order(Base):
  129. def __init__(self, order):
  130. self.order = order
  131. class Dish(Base):
  132. location = DishWasher
  133. class BaseDessert(Base):
  134. name = ""
  135. class SimpleDessert(BaseDessert):
  136. location = None
  137. class PreparedDessert(BaseDessert):
  138. ingredients = []
  139. class CookedDessert(BaseDessert):
  140. ingredients = []
  141. class Ingredient(Base):
  142. location = None
  143. transformer = None
  144. class CookIngredient():
  145. location = None
  146. cooking_time = 0
  147. # --- desserts
  148. class IceCream(SimpleDessert):
  149. location = IcecreamCrate
  150. class Blueberries(SimpleDessert):
  151. location = BlueberriesCrate
  152. class Strawberries(Ingredient):
  153. location = StrawberriesCrate
  154. transformer = ChoppingBoard
  155. class ChoppedStrawberries(PreparedDessert):
  156. ingredients = [Strawberries]
  157. class Dough(CookIngredient):
  158. location = DoughCrate
  159. cooking_time = 10
  160. class Croissant(CookedDessert):
  161. location = Oven
  162. ingredients = [Dough]
  163. class Customer(Base):
  164. def __init__(self, item, award):
  165. self.order = [match[i] for i in item.split('-')]
  166. self.award = int(award)
  167. class Table(Base):
  168. def __init__(self, x, y, item):
  169. self.x = int(x)
  170. self.y = int(y)
  171. self.item = [match[i] for i in item.split('-')]
  172. @property
  173. def pos(self):
  174. return (self.x, self.y)
  175. # --- Actions
  176. class Action(Base):
  177. needs_dish = False
  178. needs_hands = False
  179. def __init__(self, location):
  180. self.location = location
  181. def locate(self, player):
  182. self.pos, self.dist = grid.closest(player.pos, self.location)
  183. class GetAction(Action):
  184. def __init__(self, subject):
  185. self.subject = subject
  186. self.location = self.subject.location
  187. def locate(self, player):
  188. available = grid.where_are(self.location)
  189. available += [t.pos for t in tables if self.subject == [t.item]]
  190. self.pos, self.dist = grid.closest_in(player.pos, available)
  191. class GetDessert(GetAction):
  192. needs_dish = True
  193. class GetIngredient(GetAction):
  194. needs_hands = True
  195. class GetDish(GetAction):
  196. def __init__(self):
  197. super().__init__(Dish)
  198. class Transform(Action):
  199. def __init__(self, subject):
  200. self.location = subject.transformer
  201. class Cook(Action):
  202. def __init__(self):
  203. self.location = Oven
  204. class GetCookedDessert(GetDessert):
  205. def __init__(self, subject):
  206. super().__init__(subject)
  207. self.location = Oven
  208. class Deliver(Action):
  209. def __init__(self):
  210. self.location = Window
  211. class DropHanded(Action):
  212. def __init__(self):
  213. self.subject = None
  214. self.location = EmptyTable
  215. class GetOnTable(GetAction):
  216. def __init__(self, pos):
  217. self._pos = pos
  218. def locate(self, player):
  219. self.pos = self._pos
  220. self.dist = Grid.distance(player.pos, self._pos)
  221. class GetDishOnTable(GetOnTable):
  222. pass
  223. class Cooker(Base):
  224. def __init__(self):
  225. self._x = -1
  226. self._y = -1
  227. self._in_hand = []
  228. self.order = []
  229. @property
  230. def x(self):
  231. return self._x
  232. @x.setter
  233. def x(self, x):
  234. self._x = int(x)
  235. @property
  236. def y(self):
  237. return self._y
  238. @y.setter
  239. def y(self, y):
  240. self._y = int(y)
  241. @property
  242. def pos(self):
  243. return (self.x, self.y)
  244. @property
  245. def in_hand(self):
  246. return self._in_hand
  247. @in_hand.setter
  248. def in_hand(self, item):
  249. self._in_hand = [x for x in [match[i] for i in item.split('-')] if x is not None]
  250. def take_order(self, order):
  251. self.order = order
  252. def order_fullfilled(self):
  253. self.order = []
  254. def update(self, x, y, item):
  255. self.x = x
  256. self.y = y
  257. self.in_hand = item
  258. @property
  259. def hands_free(self):
  260. return len(self._in_hand) == 0
  261. @property
  262. def hands_full(self):
  263. return any(issubclass(i, Ingredient) for i in self._in_hand)
  264. @property
  265. def dish_handed(self):
  266. return Dish in self.in_hand
  267. def eval_orders(self, customers):
  268. waiting = sorted(customers, reverse=True, key=lambda x: x.award)
  269. self.take_order(waiting[0].order)
  270. def todo(self):
  271. todo = []
  272. store = None
  273. for table in tables:
  274. if Dish in table.item and all((i in self.order and not i in self.in_hand) for i in table.item):
  275. store = table
  276. for item in self.order:
  277. if item in self.in_hand or (store and item in store.item):
  278. # already done
  279. continue
  280. if issubclass(item, SimpleDessert):
  281. todo.append(GetDessert(item))
  282. elif issubclass(item, PreparedDessert):
  283. on_table = next((t for t in tables if [item] == t.item), None)
  284. if on_table:
  285. todo.append(GetOnTable(on_table.pos))
  286. else:
  287. for ingredient in item.ingredients:
  288. if ingredient in self.in_hand:
  289. todo.append(Transform(ingredient))
  290. else:
  291. todo.append(GetIngredient(ingredient))
  292. elif issubclass(item, CookedDessert):
  293. on_table = next((t for t in tables if [item] in t.item), None)
  294. if on_table:
  295. todo.append(GetOnTable(on_table.pos))
  296. else:
  297. for ingredient in item.ingredients:
  298. if ingredient in self.in_hand:
  299. todo.append(Cook())
  300. elif type(item) == type(oven.content):
  301. todo.append(GetCookedDessert(item))
  302. elif type(ingredient) == type(oven.content):
  303. if oven.timer < 3:
  304. todo.append(GetCookedDessert(item))
  305. else:
  306. todo.append(GetIngredient(ingredient))
  307. elif issubclass(item, Dish):
  308. todo.append(GetDish())
  309. else:
  310. log(f"<!> Unknown order: {item}")
  311. if store:
  312. todo.append(GetOnTable(store.pos))
  313. # nothing left to do: deliver
  314. if not todo:
  315. todo = [Deliver()]
  316. # if the current order is not anymore in the queue, drop the dish
  317. if not self.order in [c.order for c in customers]:
  318. player.order = []
  319. todo = [DropHanded()]
  320. for action in todo:
  321. action.locate(self)
  322. return todo
  323. def act(self, action):
  324. log("{}, {}, {}, {}".format(action.needs_hands, self.hands_free, isinstance(action, GetAction), self.hands_full))
  325. if isinstance(action, Deliver) and action.pos in grid.neighbors(*self.pos):
  326. self.order = []
  327. elif (action.needs_hands and not self.hands_free) or (isinstance(action, GetAction) and self.hands_full):
  328. # cannot act, needs to drop the dish
  329. log("need to drop")
  330. action = DropHanded()
  331. action.locate(self)
  332. self.use(*action.pos)
  333. def use(self, x, y, msg=""):
  334. print("USE", x, y, msg)
  335. def move(self, x, y):
  336. print("MOVE", x, y)
  337. def wait(self):
  338. print("WAIT")
  339. # --- constants
  340. match = {
  341. '0': Start0,
  342. '1': Start1,
  343. 'B': BlueberriesCrate,
  344. 'I': IcecreamCrate,
  345. 'S': StrawberriesCrate,
  346. 'C': ChoppingBoard,
  347. 'H': DoughCrate,
  348. 'W': Window,
  349. '#': EmptyTable,
  350. 'D': DishWasher,
  351. '.': FloorCell,
  352. 'O': Oven,
  353. 'NONE': None,
  354. 'DISH': Dish,
  355. 'ICE_CREAM': IceCream,
  356. 'BLUEBERRIES': Blueberries,
  357. 'STRAWBERRIES': Strawberries,
  358. 'CHOPPED_STRAWBERRIES': ChoppedStrawberries,
  359. 'DOUGH': Dough,
  360. 'CROISSANT': Croissant
  361. }
  362. # --- input vars
  363. num_all_customers = int(input())
  364. all_customers = [Customer(*input().split()) for _ in range(num_all_customers)]
  365. grid = Grid([[match[c] for c in input()] for i in range(7)])
  366. log(f"{num_all_customers} customers: {all_customers}")
  367. log(f"grid: {grid}")
  368. player = Cooker()
  369. partner = Cooker()
  370. oven = next((i for i in grid.items() if type(i) is Oven), Oven())
  371. while True:
  372. # <--- turn input
  373. turns_remaining = int(input())
  374. player.update(*input().split())
  375. log(f"*** player: {player}")
  376. partner.update(*input().split())
  377. log(f"*** partner: {partner}")
  378. num_tables_with_items = int(input()) # the number of tables in the kitchen that currently hold an item
  379. tables = [Table(*input().split()) for _ in range(num_tables_with_items)]
  380. log(f"*** tables: {tables}")
  381. oven.update(*input().split())
  382. log(f"*** oven: {oven}")
  383. num_customers = int(input()) # the number of customers currently waiting for food
  384. customers = [Customer(*input().split()) for _ in range(num_customers)]
  385. log(f"*** customers: {customers}")
  386. # --->
  387. # ## SCRIPT
  388. # if no current order, take the most awarded
  389. if not player.order:
  390. player.eval_orders(customers)
  391. log('>>> new order taken')
  392. log(f"order: {player.order}")
  393. todo = player.todo()
  394. log(f"todo: {todo}")
  395. priorities = [p for p in [
  396. # order fulfilled: deliver
  397. next((a for a in todo if type(a) is Deliver), None),
  398. # something to take out from the oven!
  399. next((a for a in todo if type(a) is GetCookedDessert), None),
  400. # If cook has an ingredient in hands, he needs to prepare it
  401. next((a for a in todo if type(a) is Transform), None),
  402. # If cook has an cook_ingredient in hands, he needs to cook it
  403. next((a for a in todo if type(a) is Cook), None),
  404. # If hands are free and an ingredient is needed, we go for it first
  405. next((a for a in todo if a.needs_hands and player.hands_free), None),
  406. # there is a dish waiting on a table
  407. next((a for a in todo if type(a) is GetDishOnTable), None),
  408. # cook has a dessert in its hands and no dish, he have to take one
  409. next((a for a in todo if isinstance(a, GetDish) and any(h for h in player.in_hand if issubclass(h, BaseDessert))), None),
  410. ] if p is not None]
  411. if priorities:
  412. next_task = priorities[0]
  413. log(f"next task (priority): {next_task}")
  414. else:
  415. # else, go for the closest task
  416. tasks = sorted(todo, key=lambda x: x.dist)
  417. next_task = next(iter(tasks))
  418. log(f"next task (closest): {next_task}")
  419. # <--- Update moving costs
  420. # update grid movement costs following the probability of finding the partner here
  421. 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]
  422. grid.add_costs = {}
  423. for x, y in partner_could_be_there:
  424. k1 = 2 if (x, y) == partner.pos else 1
  425. # cell is next to a special cell, partner has more chance to stop there
  426. k2 = 2 if any((c for c in grid.neighbors(x, y) if isinstance(grid.at(*c), SpecialLocation))) else 1
  427. grid.add_costs[(x, y)] = k1 * k2 * 3
  428. log(grid.add_costs)
  429. # --->
  430. # <--- compute shortest path
  431. path = grid.path(player.pos, next_task.pos)
  432. log(path)
  433. # --->
  434. if path is not None:
  435. if len(path) > 0:
  436. if len(path) > 4:
  437. player.move(*path[3])
  438. else:
  439. player.move(*path[-1])
  440. else:
  441. player.act(next_task)
  442. else:
  443. player.act(next_task)