cook_svg.py 15 KB

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