base_ai.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. '''
  2. @author: olivier.massot, 2019
  3. '''
  4. import heapq
  5. import itertools
  6. import sys
  7. def log(x):
  8. print(x, file=sys.stderr)
  9. # TODO: can I take ingredients not in order? If I can, find the shortest path trought all elements >> yes, but only one ingredient without a dish.
  10. # TODO: if partner on the way, wait if another path is 4+ cells longer
  11. # TODO: Should I tell wich I am doing to my partner?
  12. # TODO: When should I drop th plate on a table?
  13. # TODO: Check if a plate matching an order is already ready
  14. # TODO: don't take an order that is already taken by partner (how?)
  15. # Cells
  16. BLUEBERRIES_CRATE = "B"
  17. ICE_CREAM_CRATE = "I"
  18. WINDOW = "W"
  19. EMPTY_TABLE = "#"
  20. DISHWASHER = "D"
  21. FLOOR_CELL = "."
  22. # Items
  23. NONE = "NONE"
  24. DISH = "DISH"
  25. ICE_CREAM = "ICE_CREAM"
  26. BLUEBERRIES = "BLUEBERRIES"
  27. crate = {DISH: DISHWASHER,
  28. ICE_CREAM: ICE_CREAM_CRATE,
  29. BLUEBERRIES: BLUEBERRIES_CRATE,
  30. WINDOW: WINDOW}
  31. class Base():
  32. def __repr__(self):
  33. return f"<{self.__class__.__name__}: {self.__dict__}>"
  34. class Customer(Base):
  35. def __init__(self, item, award):
  36. self.item = item
  37. self.award = int(award)
  38. class Cook(Base):
  39. def __init__(self, x, y, item):
  40. self.x = int(x)
  41. self.y = int(y)
  42. self.item = item
  43. self.order = ""
  44. self.plan = []
  45. # self.todo = []
  46. # self.current_target = ""
  47. @property
  48. def pos(self):
  49. return (self.x, self.y)
  50. def take_order(self, grid, order):
  51. self.grid = grid
  52. self.order = order
  53. # self.todo = order.split('-')
  54. self.update_plan()
  55. # self.current_target = where[self.plan[0]]
  56. def update_plan(self):
  57. todo = self.order.split('-')
  58. steps = [crate[t] for t in todo]
  59. # test different paths. DISH should be either first or second in the list, and window should be last.
  60. cases = [c for c in itertools.permutations(steps)]
  61. # special: dish can not be after second position:
  62. cases = [c for i, c in enumerate(cases) if not (i > 1 and c == DISH)]
  63. # window is always at the end:
  64. cases += [WINDOW]
  65. # multply cases by all combinations of coordinates
  66. exploded = [[self.grid.where_are(e) for e in case] for case in cases]
  67. routes = sum([list(itertools.product(*r)) for r in exploded], [])
  68. # take the shortest path
  69. shortest = min([r for r in routes], key=lambda r: len(self.grid.path_trough(self.pos, *r)))
  70. self.plan = shortest
  71. def act(self):
  72. # take the first action in the plan
  73. target = self.plan[0]
  74. # path = self.grid.path_to(self.pos, target)
  75. # if can act this turn, consider the action as completed
  76. if target in self.grid.neighbors(*self.pos):
  77. target.pop(0)
  78. self.use(target)
  79. if not self.plan:
  80. self.done()
  81. def done(self):
  82. self.order = ""
  83. self.plan = []
  84. # self._objective = ""
  85. # self.todo = []
  86. # self.current_target = ""
  87. # @property
  88. # def current_need(self):
  89. # for t in self.todo:
  90. # if not t in self.item:
  91. # return t
  92. # return NONE
  93. def use(self, x, y, msg=""):
  94. print("USE", x, y, msg)
  95. def move(self, x, y):
  96. print("MOVE", x, y)
  97. class Table(Base):
  98. def __init__(self, x, y, item):
  99. self.x = int(x)
  100. self.y = int(y)
  101. self.item = item
  102. class Oven(Base):
  103. def __init__(self, contents, timer):
  104. self.contents = contents
  105. self.timer = int(timer)
  106. class NoPathFound(Exception):
  107. pass
  108. class PathNode(tuple):
  109. def __new__(self, x, y, parent=None):
  110. n = tuple.__new__(self, (x, y))
  111. n.parent = parent
  112. n.cost = 0
  113. return n
  114. class Grid(Base):
  115. def __init__(self, cells):
  116. self.cells = cells
  117. def at(self, x, y):
  118. return self.cells[y][x]
  119. def is_in(self, x, y):
  120. try:
  121. self.at(x, y)
  122. return True
  123. except IndexError:
  124. return False
  125. def flatten(self):
  126. return [(x, y, c) for y, row in enumerate(self.cells) for x, c in enumerate(row)]
  127. def where_are(self, content):
  128. return [(x, y) for x, y, c in self.flatten() if c == content]
  129. def is_passable(self, x, y):
  130. return self.at(x, y) in (FLOOR_CELL, "0", "1")
  131. def closest(self, from_, content):
  132. return min(self.where_are(content), key=lambda k: (abs(from_[0] - k[0]) + abs(from_[1] - k[1])))
  133. def neighbors(self, x, y):
  134. return [(xn, yn) for xn, yn in [(x - 1, y - 1), (x, y - 1), (x + 1, y - 1), \
  135. (x - 1, y), (x + 1, y) , \
  136. (x - 1, y + 1), (x, y + 1), (x + 1, y + 1)]
  137. if self.is_in(xn, yn)]
  138. def path_to(self, origin, target):
  139. nodes = []
  140. origin = PathNode(*origin)
  141. heapq.heappush(nodes, (0, origin))
  142. while nodes:
  143. current = heapq.heappop(nodes)[1]
  144. if current == target:
  145. break
  146. for x, y in self.neighbors(*current):
  147. node = PathNode(x, y, current)
  148. if not self.is_passable(*node):
  149. continue
  150. node.cost = current.cost + 1
  151. priority = node.cost + (abs(node[0] - target[0]) + abs(node[1] - target[1]))
  152. heapq.heappush(nodes, (priority, node))
  153. else:
  154. raise NoPathFound("no path were found to the targetted location {}".format(target))
  155. result = [target]
  156. while current != origin:
  157. result.append(tuple(current))
  158. current = current.parent
  159. result.append(origin)
  160. result.reverse()
  161. return result
  162. def path_trough(self, origin, *steps):
  163. return sum([self.path_to(origin, s) for s in steps], [])
  164. # input vars
  165. num_all_customers = int(input())
  166. all_customers = [Customer(*input().split()) for _ in range(num_all_customers)]
  167. grid = Grid([list(input()) for i in range(7)])
  168. log(f"{num_all_customers} customers: {all_customers}")
  169. log(f"grid: {grid}")
  170. while True:
  171. turns_remaining = int(input())
  172. player = Cook(*input().split())
  173. log(f"*** player: {player}")
  174. partner = Cook(*input().split())
  175. log(f"*** partner: {partner}")
  176. num_tables_with_items = int(input()) # the number of tables in the kitchen that currently hold an item
  177. tables = [Table(*input().split()) for _ in range(num_tables_with_items)]
  178. log(f"*** tables: {tables}")
  179. oven = Oven(*input().split())
  180. log(f"*** oven: {oven}")
  181. num_customers = int(input()) # the number of customers currently waiting for food
  182. customers = [Customer(*input().split()) for _ in range(num_customers)]
  183. log(f"*** customers: {customers}")
  184. # if no current order, take the most beneficial
  185. if not player.order:
  186. queue = sorted(customers, reverse=True, key=lambda x: x.award)
  187. # what is my partner doing?
  188. player.take_order(grid, queue[0].item)
  189. log(f'>>> new order taken: {player.order}')
  190. player.act()
  191. #print("WAIT")
  192. # log(f'todo: {player.todo}')
  193. # log(f'currently seeking: {player.current_need}')
  194. # if player.current_need == NONE:
  195. # target = WINDOW
  196. # log(f'current target: {target}')
  197. # player.done()
  198. # else:
  199. # target = where[player.current_need]
  200. # log(f'current target: {target}')
  201. #
  202. # closest = grid.closest(player.pos, target)
  203. # log(f"closest: {closest}")
  204. # player.use(*closest)