cook.py 18 KB

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